// Copyright (C) 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package parser implements a SPIR-V assembly parser.
package parser

import (
	"fmt"
	"io"
	"log"
	"strings"
	"unicode"
	"unicode/utf8"

	"../schema"
)

// Type is an enumerator of token types.
type Type int

// Type enumerators
const (
	Ident  Type = iota // Foo
	PIdent             // %32, %foo
	Integer
	Float
	String
	Operator
	Comment
	Newline
)

func (t Type) String() string {
	switch t {
	case Ident:
		return "Ident"
	case PIdent:
		return "PIdent"
	case Integer:
		return "Integer"
	case Float:
		return "Float"
	case String:
		return "String"
	case Operator:
		return "Operator"
	case Comment:
		return "Comment"
	default:
		return "<unknown>"
	}
}

// Token represents a single lexed token.
type Token struct {
	Type  Type
	Range Range
}

func (t Token) String() string { return fmt.Sprintf("{%v %v}", t.Type, t.Range) }

// Text returns the tokens text from the source.
func (t Token) Text(lines []string) string { return t.Range.Text(lines) }

// Range represents an interval in a text file.
type Range struct {
	Start Position
	End   Position
}

func (r Range) String() string { return fmt.Sprintf("[%v %v]", r.Start, r.End) }

// Text returns the text for the given Range in the provided lines.
func (r Range) Text(lines []string) string {
	sl, sc := r.Start.Line-1, r.Start.Column-1
	if sl < 0 || sc < 0 || sl > len(lines) || sc > len(lines[sl]) {
		return fmt.Sprintf("<invalid start position %v>", r.Start)
	}
	el, ec := r.End.Line-1, r.End.Column-1
	if el < 0 || ec < 0 || el > len(lines) || ec > len(lines[sl]) {
		return fmt.Sprintf("<invalid end position %v>", r.End)
	}

	sb := strings.Builder{}
	if sl != el {
		sb.WriteString(lines[sl][sc:])
		for l := sl + 1; l < el; l++ {
			sb.WriteString(lines[l])
		}
		sb.WriteString(lines[el][:ec])
	} else {
		sb.WriteString(lines[sl][sc:ec])
	}
	return sb.String()
}

// Contains returns true if p is in r.
func (r Range) Contains(p Position) bool {
	return !(p.LessThan(r.Start) || p.GreaterThan(r.End))
}

func (r *Range) grow(o Range) {
	if !r.Start.IsValid() || o.Start.LessThan(r.Start) {
		r.Start = o.Start
	}
	if !r.End.IsValid() || o.End.GreaterThan(r.End) {
		r.End = o.End
	}
}

// Position holds a line and column position in a text file.
type Position struct {
	Line, Column int
}

func (p Position) String() string { return fmt.Sprintf("%v:%v", p.Line, p.Column) }

// IsValid returns true if the position has a line and column greater than 1.
func (p Position) IsValid() bool { return p.Line > 0 && p.Column > 0 }

// LessThan returns true iff o is before p.
func (p Position) LessThan(o Position) bool {
	switch {
	case !p.IsValid() || !o.IsValid():
		return false
	case p.Line < o.Line:
		return true
	case p.Line > o.Line:
		return false
	case p.Column < o.Column:
		return true
	default:
		return false
	}
}

// GreaterThan returns true iff o is greater than p.
func (p Position) GreaterThan(o Position) bool {
	switch {
	case !p.IsValid() || !o.IsValid():
		return false
	case p.Line > o.Line:
		return true
	case p.Line < o.Line:
		return false
	case p.Column > o.Column:
		return true
	default:
		return false
	}
}

type lexer struct {
	source string
	lexerState
	diags []Diagnostic
	e     error
}

type lexerState struct {
	offset int      // byte offset in source
	toks   []*Token // all the lexed tokens
	pos    Position // current position
}

// err appends an fmt.Printf style error into l.diags for the given token.
func (l *lexer) err(tok *Token, msg string, args ...interface{}) {
	rng := Range{}
	if tok != nil {
		rng = tok.Range
	}
	l.diags = append(l.diags, Diagnostic{
		Range:    rng,
		Severity: SeverityError,
		Message:  fmt.Sprintf(msg, args...),
	})
}

// next returns the next rune, or io.EOF if the last rune has already been
// consumed.
func (l *lexer) next() rune {
	if l.offset >= len(l.source) {
		l.e = io.EOF
		return 0
	}
	r, n := utf8.DecodeRuneInString(l.source[l.offset:])
	l.offset += n
	if n == 0 {
		l.e = io.EOF
		return 0
	}
	if r == '\n' {
		l.pos.Line++
		l.pos.Column = 1
	} else {
		l.pos.Column++
	}
	return r
}

// save returns the current lexerState.
func (l *lexer) save() lexerState {
	return l.lexerState
}

// restore restores the current lexer state with s.
func (l *lexer) restore(s lexerState) {
	l.lexerState = s
}

// pident processes the PIdent token at the current position.
// The lexer *must* know the next token is a PIdent before calling.
func (l *lexer) pident() {
	tok := &Token{Type: PIdent, Range: Range{Start: l.pos, End: l.pos}}
	if r := l.next(); r != '%' {
		log.Fatalf("lexer expected '%%', got '%v'", r)
		return
	}
	for l.e == nil {
		s := l.save()
		r := l.next()
		if !isAlphaNumeric(r) && r != '_' {
			l.restore(s)
			break
		}
	}
	tok.Range.End = l.pos
	l.toks = append(l.toks, tok)
}

// numberOrIdent processes the Ident, Float or Integer token at the current
// position.
func (l *lexer) numberOrIdent() {
	const Unknown Type = -1
	tok := &Token{Type: Unknown, Range: Range{Start: l.pos, End: l.pos}}
loop:
	for l.e == nil {
		s := l.save()
		r := l.next()
		switch {
		case r == '-', r == '+', isNumeric(r):
			continue
		case isAlpha(r), r == '_':
			switch tok.Type {
			case Unknown:
				tok.Type = Ident
			case Float, Integer:
				l.err(tok, "invalid number")
				return
			}
		case r == '.':
			switch tok.Type {
			case Unknown:
				tok.Type = Float
			default:
				l.restore(s)
				break loop
			}
		default:
			if tok.Type == Unknown {
				tok.Type = Integer
			}
			l.restore(s)
			break loop
		}
	}
	tok.Range.End = l.pos
	l.toks = append(l.toks, tok)
}

// string processes the String token at the current position.
// The lexer *must* know the next token is a String before calling.
func (l *lexer) string() {
	tok := &Token{Type: String, Range: Range{Start: l.pos, End: l.pos}}
	if r := l.next(); r != '"' {
		log.Fatalf("lexer expected '\"', got '%v'", r)
		return
	}
	escape := false
	for l.e == nil {
		switch l.next() {
		case '"':
			if !escape {
				tok.Range.End = l.pos
				l.toks = append(l.toks, tok)
				return
			}
		case '\\':
			escape = !escape
		default:
			escape = false
		}
	}
}

// operator processes the Operator token at the current position.
// The lexer *must* know the next token is a Operator before calling.
func (l *lexer) operator() {
	tok := &Token{Type: Operator, Range: Range{Start: l.pos, End: l.pos}}
	for l.e == nil {
		switch l.next() {
		case '=', '|':
			tok.Range.End = l.pos
			l.toks = append(l.toks, tok)
			return
		}
	}
}

// lineComment processes the Comment token at the current position.
// The lexer *must* know the next token is a Comment before calling.
func (l *lexer) lineComment() {
	tok := &Token{Type: Comment, Range: Range{Start: l.pos, End: l.pos}}
	if r := l.next(); r != ';' {
		log.Fatalf("lexer expected ';', got '%v'", r)
		return
	}
	for l.e == nil {
		s := l.save()
		switch l.next() {
		case '\n':
			l.restore(s)
			tok.Range.End = l.pos
			l.toks = append(l.toks, tok)
			return
		}
	}
}

// newline processes the Newline token at the current position.
// The lexer *must* know the next token is a Newline before calling.
func (l *lexer) newline() {
	tok := &Token{Type: Newline, Range: Range{Start: l.pos, End: l.pos}}
	if r := l.next(); r != '\n' {
		log.Fatalf("lexer expected '\n', got '%v'", r)
		return
	}
	tok.Range.End = l.pos
	l.toks = append(l.toks, tok)
}

// lex returns all the tokens and diagnostics after lexing source.
func lex(source string) ([]*Token, []Diagnostic, error) {
	l := lexer{source: source, lexerState: lexerState{pos: Position{1, 1}}}

	lastPos := Position{}
	for l.e == nil {
		// Integrity check that the parser is making progress
		if l.pos == lastPos {
			log.Panicf("Parsing stuck at %v", l.pos)
		}
		lastPos = l.pos

		s := l.save()
		r := l.next()
		switch {
		case r == '%':
			l.restore(s)
			l.pident()
		case r == '+' || r == '-' || r == '_' || isAlphaNumeric(r):
			l.restore(s)
			l.numberOrIdent()
		case r == '"':
			l.restore(s)
			l.string()
		case r == '=', r == '|':
			l.restore(s)
			l.operator()
		case r == ';':
			l.restore(s)
			l.lineComment()
		case r == '\n':
			l.restore(s)
			l.newline()
		}
	}
	if l.e != nil && l.e != io.EOF {
		return nil, nil, l.e
	}
	return l.toks, l.diags, nil
}

func isNumeric(r rune) bool      { return unicode.IsDigit(r) }
func isAlpha(r rune) bool        { return unicode.IsLetter(r) }
func isAlphaNumeric(r rune) bool { return isAlpha(r) || isNumeric(r) }

type parser struct {
	lines          []string                    // all source lines
	toks           []*Token                    // all tokens
	diags          []Diagnostic                // parser emitted diagnostics
	idents         map[string]*Identifier      // identifiers by name
	mappings       map[*Token]interface{}      // tokens to semantic map
	extInstImports map[string]schema.OpcodeMap // extension imports by identifier
	insts          []*Instruction              // all instructions
}

func (p *parser) parse() error {
	for i := 0; i < len(p.toks); {
		if p.newline(i) || p.comment(i) {
			i++
			continue
		}
		if n := p.instruction(i); n > 0 {
			i += n
		} else {
			p.unexpected(i)
			i++
		}
	}
	return nil
}

// instruction parses the instruction starting at the i'th token.
func (p *parser) instruction(i int) (n int) {
	inst := &Instruction{}

	switch {
	case p.opcode(i) != nil:
		inst.Opcode = p.opcode(i)
		inst.Tokens = []*Token{p.tok(i)}
		p.mappings[p.tok(i)] = inst
		n++
	case p.opcode(i+2) != nil: // try '%id' '='
		inst.Result, inst.Opcode = p.pident(i), p.opcode(i+2)
		if inst.Result == nil || p.operator(i+1) != "=" {
			return 0
		}
		n += 3
		inst.Tokens = []*Token{p.tok(i), p.tok(i + 1), p.tok(i + 2)}
		p.mappings[p.tok(i+2)] = inst
	default:
		return
	}

	expectsResult := len(inst.Opcode.Operands) > 0 && IsResult(inst.Opcode.Operands[0].Kind)
	operands := inst.Opcode.Operands
	switch {
	case inst.Result != nil && !expectsResult:
		p.err(inst.Result, "'%s' does not have a result", inst.Opcode.Opname)
		return
	case inst.Result == nil && expectsResult:
		p.err(p.tok(i), "'%s' expects a result", inst.Opcode.Opname)
		return
	case inst.Result != nil && expectsResult:
		// Check the result is of the correct type
		o := inst.Opcode.Operands[0]
		p.operand(o.Name, o.Kind, i, false)
		operands = operands[1:]
		p.addIdentDef(inst.Result.Text(p.lines), inst, p.tok(i))
	}

	processOperand := func(o schema.Operand) bool {
		if p.newline(i + n) {
			return false
		}

		switch o.Quantifier {
		case schema.Once:
			if op, c := p.operand(o.Name, o.Kind, i+n, false); op != nil {
				inst.Tokens = append(inst.Tokens, op.Tokens...)
				n += c
			}
		case schema.ZeroOrOnce:
			if op, c := p.operand(o.Name, o.Kind, i+n, true); op != nil {
				inst.Tokens = append(inst.Tokens, op.Tokens...)
				n += c
			}
		case schema.ZeroOrMany:
			for !p.newline(i + n) {
				if op, c := p.operand(o.Name, o.Kind, i+n, true); op != nil {
					inst.Tokens = append(inst.Tokens, op.Tokens...)
					n += c
				} else {
					return false
				}
			}
		}
		return true
	}

	for _, o := range operands {
		if !processOperand(o) {
			break
		}

		if inst.Opcode == schema.OpExtInst && n == 4 {
			extImportTok, extNameTok := p.tok(i+n), p.tok(i+n+1)
			extImport := extImportTok.Text(p.lines)
			if extOpcodes, ok := p.extInstImports[extImport]; ok {
				extName := extNameTok.Text(p.lines)
				if extOpcode, ok := extOpcodes[extName]; ok {
					n += 2 // skip ext import, ext name
					for _, o := range extOpcode.Operands {
						if !processOperand(o) {
							break
						}
					}
				} else {
					p.err(extNameTok, "Unknown extension opcode '%s'", extName)
				}
			} else {
				p.err(extImportTok, "Expected identifier to OpExtInstImport")
			}
		}
	}

	for _, t := range inst.Tokens {
		inst.Range.grow(t.Range)
	}

	p.insts = append(p.insts, inst)

	if inst.Opcode == schema.OpExtInstImport && len(inst.Tokens) >= 4 {
		// Instruction is a OpExtInstImport. Keep track of this.
		extTok := inst.Tokens[3]
		extName := strings.Trim(extTok.Text(p.lines), `"`)
		extOpcodes, ok := schema.ExtOpcodes[extName]
		if !ok {
			p.err(extTok, "Unknown extension '%s'", extName)
		}
		extImport := inst.Result.Text(p.lines)
		p.extInstImports[extImport] = extOpcodes
	}

	return
}

// operand parses the operand with the name n, kind k, starting at the i'th
// token.
func (p *parser) operand(n string, k *schema.OperandKind, i int, optional bool) (*Operand, int) {
	tok := p.tok(i)
	if tok == nil {
		return nil, 0
	}

	op := &Operand{
		Name:   n,
		Kind:   k,
		Tokens: []*Token{tok},
	}
	p.mappings[tok] = op

	switch k.Category {
	case schema.OperandCategoryBitEnum, schema.OperandCategoryValueEnum:
		s := tok.Text(p.lines)
		for _, e := range k.Enumerants {
			if e.Enumerant == s {
				count := 1
				for _, param := range e.Parameters {
					p, c := p.operand(param.Name, param.Kind, i+count, false)
					if p != nil {
						op.Tokens = append(op.Tokens, p.Tokens...)
						op.Parameters = append(op.Parameters, p)
					}
					count += c
				}

				// Handle bitfield '|' chains
				if p.tok(i+count).Text(p.lines) == "|" {
					count++ // '|'
					p, c := p.operand(n, k, i+count, false)
					if p != nil {
						op.Tokens = append(op.Tokens, p.Tokens...)
						op.Parameters = append(op.Parameters, p)
					}
					count += c
				}

				return op, count
			}
		}
		if !optional {
			p.err(p.tok(i), "invalid operand value '%s'", s)
		}

		return nil, 0

	case schema.OperandCategoryID:
		id := p.pident(i)
		if id != nil {
			p.addIdentRef(p.tok(i))
			return op, 1
		}
		if !optional {
			p.err(p.tok(i), "operand requires id, got '%s'", tok.Text(p.lines))
		}
		return nil, 0

	case schema.OperandCategoryLiteral:
		switch tok.Type {
		case String, Integer, Float, Ident:
			return op, 1
		}
		if !optional {
			p.err(p.tok(i), "operand requires literal, got '%s'", tok.Text(p.lines))
		}
		return nil, 0

	case schema.OperandCategoryComposite:
		n := 1
		for _, b := range k.Bases {
			o, c := p.operand(b.Kind, b, i+n, optional)
			if o != nil {
				op.Tokens = append(op.Tokens, o.Tokens...)
			}
			n += c
		}
		return op, n

	default:
		p.err(p.tok(i), "OperandKind '%s' has unexpected category '%s'", k.Kind, k.Category)
		return nil, 0
	}
}

// tok returns the i'th token, or nil if i is out of bounds.
func (p *parser) tok(i int) *Token {
	if i < 0 || i >= len(p.toks) {
		return nil
	}
	return p.toks[i]
}

// opcode returns the schema.Opcode for the i'th token, or nil if the i'th token
// does not represent an opcode.
func (p *parser) opcode(i int) *schema.Opcode {
	if tok := p.ident(i); tok != nil {
		name := tok.Text(p.lines)
		if inst, found := schema.Opcodes[name]; found {
			return inst
		}
	}
	return nil
}

// operator returns the operator for the i'th token, or and empty string if the
// i'th token is not an operator.
func (p *parser) operator(i int) string {
	if tok := p.tok(i); tok != nil && tok.Type == Operator {
		return tok.Text(p.lines)
	}
	return ""
}

// ident returns the i'th token if it is an Ident, otherwise nil.
func (p *parser) ident(i int) *Token {
	if tok := p.tok(i); tok != nil && tok.Type == Ident {
		return tok
	}
	return nil
}

// pident returns the i'th token if it is an PIdent, otherwise nil.
func (p *parser) pident(i int) *Token {
	if tok := p.tok(i); tok != nil && tok.Type == PIdent {
		return tok
	}
	return nil
}

// comment returns true if the i'th token is a Comment, otherwise false.
func (p *parser) comment(i int) bool {
	if tok := p.tok(i); tok != nil && tok.Type == Comment {
		return true
	}
	return false
}

// newline returns true if the i'th token is a Newline, otherwise false.
func (p *parser) newline(i int) bool {
	if tok := p.tok(i); tok != nil && tok.Type == Newline {
		return true
	}
	return false
}

// unexpected emits an 'unexpected token error' for the i'th token.
func (p *parser) unexpected(i int) {
	p.err(p.toks[i], "syntax error: unexpected '%s'", p.toks[i].Text(p.lines))
}

// addIdentDef records the token definition for the instruction inst with the
// given id.
func (p *parser) addIdentDef(id string, inst *Instruction, def *Token) {
	i, existing := p.idents[id]
	if !existing {
		i = &Identifier{}
		p.idents[id] = i
	}
	if i.Definition == nil {
		i.Definition = inst
	} else {
		p.err(def, "id '%v' redeclared", id)
	}
}

// addIdentRef adds a identifier reference for the token ref.
func (p *parser) addIdentRef(ref *Token) {
	id := ref.Text(p.lines)
	i, existing := p.idents[id]
	if !existing {
		i = &Identifier{}
		p.idents[id] = i
	}
	i.References = append(i.References, ref)
}

// err appends an fmt.Printf style error into l.diags for the given token.
func (p *parser) err(tok *Token, msg string, args ...interface{}) {
	rng := Range{}
	if tok != nil {
		rng = tok.Range
	}
	p.diags = append(p.diags, Diagnostic{
		Range:    rng,
		Severity: SeverityError,
		Message:  fmt.Sprintf(msg, args...),
	})
}

// Parse parses the SPIR-V assembly string source, returning the parse results.
func Parse(source string) (Results, error) {
	toks, diags, err := lex(source)
	if err != nil {
		return Results{}, err
	}
	lines := strings.SplitAfter(source, "\n")
	p := parser{
		lines:          lines,
		toks:           toks,
		idents:         map[string]*Identifier{},
		mappings:       map[*Token]interface{}{},
		extInstImports: map[string]schema.OpcodeMap{},
	}
	if err := p.parse(); err != nil {
		return Results{}, err
	}
	diags = append(diags, p.diags...)
	return Results{
		Lines:       lines,
		Tokens:      toks,
		Diagnostics: p.diags,
		Identifiers: p.idents,
		Mappings:    p.mappings,
	}, nil
}

// IsResult returns true if k is used to store the result of an instruction.
func IsResult(k *schema.OperandKind) bool {
	switch k {
	case schema.OperandKindIdResult, schema.OperandKindIdResultType:
		return true
	default:
		return false
	}
}

// Results holds the output of Parse().
type Results struct {
	Lines       []string
	Tokens      []*Token
	Diagnostics []Diagnostic
	Identifiers map[string]*Identifier // identifiers by name
	Mappings    map[*Token]interface{} // tokens to semantic map
}

// Instruction describes a single instruction instance
type Instruction struct {
	Tokens   []*Token       // all the tokens that make up the instruction
	Result   *Token         // the token that represents the result of the instruction, or nil
	Operands []*Operand     // the operands of the instruction
	Range    Range          // the textual range of the instruction
	Opcode   *schema.Opcode // the opcode for the instruction
}

// Operand describes a single operand instance
type Operand struct {
	Name       string              // name of the operand
	Kind       *schema.OperandKind // kind of the operand
	Tokens     []*Token            // all the tokens that make up the operand
	Parameters []*Operand          // all the parameters for the operand
}

// Identifier describes a single, unique SPIR-V identifier (i.e. %32)
type Identifier struct {
	Definition *Instruction // where the identifier was defined
	References []*Token     // all the places the identifier was referenced
}

// Severity is an enumerator of diagnositc seeverities
type Severity int

// Severity levels
const (
	SeverityError Severity = iota
	SeverityWarning
	SeverityInformation
	SeverityHint
)

// Diagnostic holds a single diagnostic message that was generated while
// parsing.
type Diagnostic struct {
	Range    Range
	Severity Severity
	Message  string
}
