// Copyright 2017 The Bazel Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package syntax provides a Starlark parser and abstract syntax tree.
package syntax // import "go.starlark.net/syntax"

// A Node is a node in a Starlark syntax tree.
type Node interface {
	// Span returns the start and end position of the expression.
	Span() (start, end Position)

	// Comments returns the comments associated with this node.
	// It returns nil if RetainComments was not specified during parsing,
	// or if AllocComments was not called.
	Comments() *Comments

	// AllocComments allocates a new Comments node if there was none.
	// This makes possible to add new comments using Comments() method.
	AllocComments()
}

// A Comment represents a single # comment.
type Comment struct {
	Start Position
	Text  string // without trailing newline
}

// Comments collects the comments associated with an expression.
type Comments struct {
	Before []Comment // whole-line comments before this expression
	Suffix []Comment // end-of-line comments after this expression (up to 1)

	// For top-level expressions only, After lists whole-line
	// comments following the expression.
	After []Comment
}

// A commentsRef is a possibly-nil reference to a set of comments.
// A commentsRef is embedded in each type of syntax node,
// and provides its Comments and AllocComments methods.
type commentsRef struct{ ref *Comments }

// Comments returns the comments associated with a syntax node,
// or nil if AllocComments has not yet been called.
func (cr commentsRef) Comments() *Comments { return cr.ref }

// AllocComments enables comments to be associated with a syntax node.
func (cr *commentsRef) AllocComments() {
	if cr.ref == nil {
		cr.ref = new(Comments)
	}
}

// Start returns the start position of the expression.
func Start(n Node) Position {
	start, _ := n.Span()
	return start
}

// End returns the end position of the expression.
func End(n Node) Position {
	_, end := n.Span()
	return end
}

// A File represents a Starlark file.
type File struct {
	commentsRef
	Path  string
	Stmts []Stmt

	Module interface{} // a *resolve.Module, set by resolver
}

func (x *File) Span() (start, end Position) {
	if len(x.Stmts) == 0 {
		return
	}
	start, _ = x.Stmts[0].Span()
	_, end = x.Stmts[len(x.Stmts)-1].Span()
	return start, end
}

// A Stmt is a Starlark statement.
type Stmt interface {
	Node
	stmt()
}

func (*AssignStmt) stmt() {}
func (*BranchStmt) stmt() {}
func (*DefStmt) stmt()    {}
func (*ExprStmt) stmt()   {}
func (*ForStmt) stmt()    {}
func (*WhileStmt) stmt()  {}
func (*IfStmt) stmt()     {}
func (*LoadStmt) stmt()   {}
func (*ReturnStmt) stmt() {}

// An AssignStmt represents an assignment:
//	x = 0
//	x, y = y, x
// 	x += 1
type AssignStmt struct {
	commentsRef
	OpPos Position
	Op    Token // = EQ | {PLUS,MINUS,STAR,PERCENT}_EQ
	LHS   Expr
	RHS   Expr
}

func (x *AssignStmt) Span() (start, end Position) {
	start, _ = x.LHS.Span()
	_, end = x.RHS.Span()
	return
}

// A DefStmt represents a function definition.
type DefStmt struct {
	commentsRef
	Def    Position
	Name   *Ident
	Params []Expr // param = ident | ident=expr | * | *ident | **ident
	Body   []Stmt

	Function interface{} // a *resolve.Function, set by resolver
}

func (x *DefStmt) Span() (start, end Position) {
	_, end = x.Body[len(x.Body)-1].Span()
	return x.Def, end
}

// An ExprStmt is an expression evaluated for side effects.
type ExprStmt struct {
	commentsRef
	X Expr
}

func (x *ExprStmt) Span() (start, end Position) {
	return x.X.Span()
}

// An IfStmt is a conditional: If Cond: True; else: False.
// 'elseif' is desugared into a chain of IfStmts.
type IfStmt struct {
	commentsRef
	If      Position // IF or ELIF
	Cond    Expr
	True    []Stmt
	ElsePos Position // ELSE or ELIF
	False   []Stmt   // optional
}

func (x *IfStmt) Span() (start, end Position) {
	body := x.False
	if body == nil {
		body = x.True
	}
	_, end = body[len(body)-1].Span()
	return x.If, end
}

// A LoadStmt loads another module and binds names from it:
// load(Module, "x", y="foo").
//
// The AST is slightly unfaithful to the concrete syntax here because
// Starlark's load statement, so that it can be implemented in Python,
// binds some names (like y above) with an identifier and some (like x)
// without.  For consistency we create fake identifiers for all the
// strings.
type LoadStmt struct {
	commentsRef
	Load   Position
	Module *Literal // a string
	From   []*Ident // name defined in loading module
	To     []*Ident // name in loaded module
	Rparen Position
}

func (x *LoadStmt) Span() (start, end Position) {
	return x.Load, x.Rparen
}

// ModuleName returns the name of the module loaded by this statement.
func (x *LoadStmt) ModuleName() string { return x.Module.Value.(string) }

// A BranchStmt changes the flow of control: break, continue, pass.
type BranchStmt struct {
	commentsRef
	Token    Token // = BREAK | CONTINUE | PASS
	TokenPos Position
}

func (x *BranchStmt) Span() (start, end Position) {
	return x.TokenPos, x.TokenPos.add(x.Token.String())
}

// A ReturnStmt returns from a function.
type ReturnStmt struct {
	commentsRef
	Return Position
	Result Expr // may be nil
}

func (x *ReturnStmt) Span() (start, end Position) {
	if x.Result == nil {
		return x.Return, x.Return.add("return")
	}
	_, end = x.Result.Span()
	return x.Return, end
}

// An Expr is a Starlark expression.
type Expr interface {
	Node
	expr()
}

func (*BinaryExpr) expr()    {}
func (*CallExpr) expr()      {}
func (*Comprehension) expr() {}
func (*CondExpr) expr()      {}
func (*DictEntry) expr()     {}
func (*DictExpr) expr()      {}
func (*DotExpr) expr()       {}
func (*Ident) expr()         {}
func (*IndexExpr) expr()     {}
func (*LambdaExpr) expr()    {}
func (*ListExpr) expr()      {}
func (*Literal) expr()       {}
func (*ParenExpr) expr()     {}
func (*SliceExpr) expr()     {}
func (*TupleExpr) expr()     {}
func (*UnaryExpr) expr()     {}

// An Ident represents an identifier.
type Ident struct {
	commentsRef
	NamePos Position
	Name    string

	Binding interface{} // a *resolver.Binding, set by resolver
}

func (x *Ident) Span() (start, end Position) {
	return x.NamePos, x.NamePos.add(x.Name)
}

// A Literal represents a literal string or number.
type Literal struct {
	commentsRef
	Token    Token // = STRING | INT
	TokenPos Position
	Raw      string      // uninterpreted text
	Value    interface{} // = string | int64 | *big.Int
}

func (x *Literal) Span() (start, end Position) {
	return x.TokenPos, x.TokenPos.add(x.Raw)
}

// A ParenExpr represents a parenthesized expression: (X).
type ParenExpr struct {
	commentsRef
	Lparen Position
	X      Expr
	Rparen Position
}

func (x *ParenExpr) Span() (start, end Position) {
	return x.Lparen, x.Rparen.add(")")
}

// A CallExpr represents a function call expression: Fn(Args).
type CallExpr struct {
	commentsRef
	Fn     Expr
	Lparen Position
	Args   []Expr // arg = expr | ident=expr | *expr | **expr
	Rparen Position
}

func (x *CallExpr) Span() (start, end Position) {
	start, _ = x.Fn.Span()
	return start, x.Rparen.add(")")
}

// A DotExpr represents a field or method selector: X.Name.
type DotExpr struct {
	commentsRef
	X       Expr
	Dot     Position
	NamePos Position
	Name    *Ident
}

func (x *DotExpr) Span() (start, end Position) {
	start, _ = x.X.Span()
	_, end = x.Name.Span()
	return
}

// A Comprehension represents a list or dict comprehension:
// [Body for ... if ...] or {Body for ... if ...}
type Comprehension struct {
	commentsRef
	Curly   bool // {x:y for ...} or {x for ...}, not [x for ...]
	Lbrack  Position
	Body    Expr
	Clauses []Node // = *ForClause | *IfClause
	Rbrack  Position
}

func (x *Comprehension) Span() (start, end Position) {
	return x.Lbrack, x.Rbrack.add("]")
}

// A ForStmt represents a loop: for Vars in X: Body.
type ForStmt struct {
	commentsRef
	For  Position
	Vars Expr // name, or tuple of names
	X    Expr
	Body []Stmt
}

func (x *ForStmt) Span() (start, end Position) {
	_, end = x.Body[len(x.Body)-1].Span()
	return x.For, end
}

// A WhileStmt represents a while loop: while X: Body.
type WhileStmt struct {
	commentsRef
	While Position
	Cond  Expr
	Body  []Stmt
}

func (x *WhileStmt) Span() (start, end Position) {
	_, end = x.Body[len(x.Body)-1].Span()
	return x.While, end
}

// A ForClause represents a for clause in a list comprehension: for Vars in X.
type ForClause struct {
	commentsRef
	For  Position
	Vars Expr // name, or tuple of names
	In   Position
	X    Expr
}

func (x *ForClause) Span() (start, end Position) {
	_, end = x.X.Span()
	return x.For, end
}

// An IfClause represents an if clause in a list comprehension: if Cond.
type IfClause struct {
	commentsRef
	If   Position
	Cond Expr
}

func (x *IfClause) Span() (start, end Position) {
	_, end = x.Cond.Span()
	return x.If, end
}

// A DictExpr represents a dictionary literal: { List }.
type DictExpr struct {
	commentsRef
	Lbrace Position
	List   []Expr // all *DictEntrys
	Rbrace Position
}

func (x *DictExpr) Span() (start, end Position) {
	return x.Lbrace, x.Rbrace.add("}")
}

// A DictEntry represents a dictionary entry: Key: Value.
// Used only within a DictExpr.
type DictEntry struct {
	commentsRef
	Key   Expr
	Colon Position
	Value Expr
}

func (x *DictEntry) Span() (start, end Position) {
	start, _ = x.Key.Span()
	_, end = x.Value.Span()
	return start, end
}

// A LambdaExpr represents an inline function abstraction.
//
// Although they may be added in future, lambda expressions are not
// currently part of the Starlark spec, so their use is controlled by the
// resolver.AllowLambda flag.
type LambdaExpr struct {
	commentsRef
	Lambda Position
	Params []Expr // param = ident | ident=expr | * | *ident | **ident
	Body   Expr

	Function interface{} // a *resolve.Function, set by resolver
}

func (x *LambdaExpr) Span() (start, end Position) {
	_, end = x.Body.Span()
	return x.Lambda, end
}

// A ListExpr represents a list literal: [ List ].
type ListExpr struct {
	commentsRef
	Lbrack Position
	List   []Expr
	Rbrack Position
}

func (x *ListExpr) Span() (start, end Position) {
	return x.Lbrack, x.Rbrack.add("]")
}

// CondExpr represents the conditional: X if COND else ELSE.
type CondExpr struct {
	commentsRef
	If      Position
	Cond    Expr
	True    Expr
	ElsePos Position
	False   Expr
}

func (x *CondExpr) Span() (start, end Position) {
	start, _ = x.True.Span()
	_, end = x.False.Span()
	return start, end
}

// A TupleExpr represents a tuple literal: (List).
type TupleExpr struct {
	commentsRef
	Lparen Position // optional (e.g. in x, y = 0, 1), but required if List is empty
	List   []Expr
	Rparen Position
}

func (x *TupleExpr) Span() (start, end Position) {
	if x.Lparen.IsValid() {
		return x.Lparen, x.Rparen
	} else {
		return Start(x.List[0]), End(x.List[len(x.List)-1])
	}
}

// A UnaryExpr represents a unary expression: Op X.
//
// As a special case, UnaryOp{Op:Star} may also represent
// the star parameter in def f(*args) or def f(*, x).
type UnaryExpr struct {
	commentsRef
	OpPos Position
	Op    Token
	X     Expr // may be nil if Op==STAR
}

func (x *UnaryExpr) Span() (start, end Position) {
	if x.X != nil {
		_, end = x.X.Span()
	} else {
		end = x.OpPos.add("*")
	}
	return x.OpPos, end
}

// A BinaryExpr represents a binary expression: X Op Y.
//
// As a special case, BinaryExpr{Op:EQ} may also
// represent a named argument in a call f(k=v)
// or a named parameter in a function declaration
// def f(param=default).
type BinaryExpr struct {
	commentsRef
	X     Expr
	OpPos Position
	Op    Token
	Y     Expr
}

func (x *BinaryExpr) Span() (start, end Position) {
	start, _ = x.X.Span()
	_, end = x.Y.Span()
	return start, end
}

// A SliceExpr represents a slice or substring expression: X[Lo:Hi:Step].
type SliceExpr struct {
	commentsRef
	X            Expr
	Lbrack       Position
	Lo, Hi, Step Expr // all optional
	Rbrack       Position
}

func (x *SliceExpr) Span() (start, end Position) {
	start, _ = x.X.Span()
	return start, x.Rbrack
}

// An IndexExpr represents an index expression: X[Y].
type IndexExpr struct {
	commentsRef
	X      Expr
	Lbrack Position
	Y      Expr
	Rbrack Position
}

func (x *IndexExpr) Span() (start, end Position) {
	start, _ = x.X.Span()
	return start, x.Rbrack
}
