// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module ast

import v.token
import v.table
import v.errors

pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl

pub type Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral |
	CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall |
	ComptimeSelector | ConcatExpr | EnumVal | FloatLiteral | GoExpr | Ident | IfExpr |
	IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | Likely | LockExpr | MapInit |
	MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr | SelectExpr |
	SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | StructInit |
	Type | TypeOf | UnsafeExpr

pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt |
	EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt |
	GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | Return | SqlStmt |
	StructDecl | TypeDecl

// NB: when you add a new Expr or Stmt type with a .pos field, remember to update
// the .position() token.Position methods too.
pub type ScopeObject = ConstField | GlobalField | Var

// TOOD: replace table.Param
pub type Node = ConstField | EnumField | Expr | Field | File | GlobalField | IfBranch |
	MatchBranch | ScopeObject | SelectBranch | Stmt | StructField | StructInitField |
	table.Param

pub struct Type {
pub:
	typ table.Type
	pos token.Position
}

// `{stmts}` or `unsafe {stmts}`
pub struct Block {
pub:
	stmts     []Stmt
	is_unsafe bool
	pos       token.Position
}

// | IncDecStmt k
// Stand-alone expression in a statement list.
pub struct ExprStmt {
pub:
	expr     Expr
	pos      token.Position
	comments []Comment
	is_expr  bool
pub mut:
	typ table.Type
}

pub struct IntegerLiteral {
pub:
	val string
	pos token.Position
}

pub struct FloatLiteral {
pub:
	val string
	pos token.Position
}

pub struct StringLiteral {
pub:
	val      string
	is_raw   bool
	language table.Language
	pos      token.Position
}

// 'name: $name'
pub struct StringInterLiteral {
pub:
	vals       []string
	exprs      []Expr
	fwidths    []int
	precisions []int
	pluss      []bool
	fills      []bool
	fmt_poss   []token.Position
	pos        token.Position
pub mut:
	expr_types []table.Type
	fmts       []byte
	need_fmts  []bool // an explicit non-default fmt required, e.g. `x`
}

pub struct CharLiteral {
pub:
	val string
	pos token.Position
}

pub struct BoolLiteral {
pub:
	val bool
	pos token.Position
}

// `foo.bar`
pub struct SelectorExpr {
pub:
	pos        token.Position
	expr       Expr // expr.field_name
	field_name string
	is_mut     bool // is used for the case `if mut ident.selector is MyType {`, it indicates if the root ident is mutable
	mut_pos    token.Position
	next_token token.Kind
pub mut:
	expr_type       table.Type // type of `Foo` in `Foo.bar`
	typ             table.Type // type of the entire thing (`Foo.bar`)
	name_type       table.Type // T in `T.name` or typeof in `typeof(expr).name`
	scope           &Scope
	from_embed_type table.Type // holds the type of the embed that the method is called from
}

// root_ident returns the origin ident where the selector started.
pub fn (e &SelectorExpr) root_ident() Ident {
	mut root := e.expr
	for root is SelectorExpr {
		// TODO: remove this line
		selector_expr := root as SelectorExpr
		root = selector_expr.expr
	}
	return root as Ident
}

// module declaration
pub struct Module {
pub:
	name       string
	attrs      []table.Attr
	pos        token.Position
	name_pos   token.Position // `name` in import name
	is_skipped bool // module main can be skipped in single file programs
}

pub struct StructField {
pub:
	pos              token.Position
	type_pos         token.Position
	comments         []Comment
	default_expr     Expr
	has_default_expr bool
	attrs            []table.Attr
	is_public        bool
pub mut:
	name string
	typ  table.Type
}

pub struct Field {
pub:
	name string
	pos  token.Position
pub mut:
	typ table.Type
}

// const field in const declaration group
pub struct ConstField {
pub:
	mod    string
	name   string
	expr   Expr // the value expr of field; everything after `=`
	is_pub bool
	pos    token.Position
pub mut:
	typ      table.Type // the type of the const field, it can be any type in V
	comments []Comment  // comments before current const field
}

// const declaration
pub struct ConstDecl {
pub:
	is_pub bool
	pos    token.Position
pub mut:
	fields       []ConstField // all the const fields in the `const (...)` block
	end_comments []Comment    // comments that after last const field
	is_block     bool // const() block
}

pub struct StructDecl {
pub:
	pos       token.Position
	name      string
	gen_types []table.Type
	is_pub    bool
	// _pos fields for vfmt
	mut_pos      int // mut:
	pub_pos      int // pub:
	pub_mut_pos  int // pub mut:
	module_pos   int // module:
	language     table.Language
	is_union     bool
	attrs        []table.Attr
	end_comments []Comment
	embeds       []Embed
pub mut:
	fields []StructField
}

pub struct Embed {
pub:
	typ table.Type
	pos token.Position
}

pub struct StructEmbedding {
pub:
	name string
	typ  table.Type
	pos  token.Position
}

pub struct InterfaceDecl {
pub:
	name         string
	field_names  []string
	is_pub       bool
	methods      []FnDecl
	pos          token.Position
	pre_comments []Comment
}

pub struct StructInitField {
pub:
	expr          Expr
	pos           token.Position
	comments      []Comment
	next_comments []Comment
pub mut:
	name          string
	typ           table.Type
	expected_type table.Type
}

pub struct StructInitEmbed {
pub:
	expr          Expr
	pos           token.Position
	comments      []Comment
	next_comments []Comment
pub mut:
	name          string
	typ           table.Type
	expected_type table.Type
}

pub struct StructInit {
pub:
	pos      token.Position
	is_short bool
pub mut:
	pre_comments         []Comment
	typ                  table.Type
	update_expr          Expr
	update_expr_type     table.Type
	update_expr_comments []Comment
	has_update_expr      bool
	fields               []StructInitField
	embeds               []StructInitEmbed
}

// import statement
pub struct Import {
pub:
	mod       string // the module name of the import
	alias     string // the `x` in `import xxx as x`
	pos       token.Position
	mod_pos   token.Position
	alias_pos token.Position
pub mut:
	syms []ImportSymbol // the list of symbols in `import {symbol1, symbol2}`
}

// import symbol,for import {symbol} syntax
pub struct ImportSymbol {
pub:
	pos  token.Position
	name string
}

// anonymous function
pub struct AnonFn {
pub:
	decl FnDecl
pub mut:
	typ table.Type // the type of anonymous fn. Both .typ and .decl.name are auto generated
}

// function or method declaration
pub struct FnDecl {
pub:
	name            string
	mod             string
	params          []table.Param
	is_deprecated   bool
	is_pub          bool
	is_variadic     bool
	is_anon         bool
	is_manualfree   bool // true, when [manualfree] is used on a fn
	receiver        Field
	receiver_pos    token.Position // `(u User)` in `fn (u User) name()` position
	is_method       bool
	method_type_pos token.Position // `User` in ` fn (u User)` position
	method_idx      int
	rec_mut         bool // is receiver mutable
	rec_share       table.ShareType
	language        table.Language
	no_body         bool // just a definition `fn C.malloc()`
	is_builtin      bool // this function is defined in builtin/strconv
	pos             token.Position // function declaration position
	body_pos        token.Position // function bodys position
	file            string
	is_generic      bool
	is_direct_arr   bool // direct array access
	attrs           []table.Attr
pub mut:
	stmts         []Stmt
	return_type   table.Type
	comments      []Comment // comments *after* the header, but *before* `{`; used for InterfaceDecl
	next_comments []Comment // coments that are one line after the decl; used for InterfaceDecl
	source_file   &File = 0
	scope         &Scope
}

// break, continue
pub struct BranchStmt {
pub:
	kind  token.Kind
	label string
	pos   token.Position
}

// function or method call expr
pub struct CallExpr {
pub:
	pos  token.Position
	left Expr // `user` in `user.register()`
	mod  string
pub mut:
	name               string // left.name()
	is_method          bool
	is_field           bool // temp hack, remove ASAP when re-impl CallExpr / Selector (joe)
	args               []CallArg
	expected_arg_types []table.Type
	language           table.Language
	or_block           OrExpr
	left_type          table.Type // type of `user`
	receiver_type      table.Type // User
	return_type        table.Type
	should_be_skipped  bool
	generic_type       table.Type // TODO array, to support multiple types
	generic_list_pos   token.Position
	free_receiver      bool // true if the receiver expression needs to be freed
	scope              &Scope
	from_embed_type    table.Type // holds the type of the embed that the method is called from
	comments           []Comment
}

/*
pub struct AutofreeArgVar {
	name string
	idx  int
}
*/
// function call argument: `f(callarg)`
pub struct CallArg {
pub:
	is_mut   bool
	share    table.ShareType
	expr     Expr
	comments []Comment
pub mut:
	typ             table.Type
	is_tmp_autofree bool // this tells cgen that a tmp variable has to be used for the arg expression in order to free it after the call
	pos             token.Position
	// tmp_name        string // for autofree
}

// function return statement
pub struct Return {
pub:
	pos      token.Position
	exprs    []Expr
	comments []Comment
pub mut:
	types []table.Type
}

/*
pub enum Expr {
	Binary(InfixExpr)
	If(IfExpr)
	Integer(IntegerExpr)
}
*/
/*
pub struct Stmt {
	pos int
	//end int
}
*/
pub struct Var {
pub:
	name            string
	expr            Expr
	share           table.ShareType
	is_mut          bool
	is_autofree_tmp bool
	is_arg          bool // fn args should not be autofreed
pub mut:
	typ            table.Type
	sum_type_casts []table.Type // nested sum types require nested smart casting, for that a list of types is needed
	pos            token.Position
	is_used        bool
	is_changed     bool // to detect mutable vars that are never changed
	//
	// (for setting the position after the or block for autofree)
	is_or  bool // `x := foo() or { ... }`
	is_tmp bool // for tmp for loop vars, so that autofree can skip them
}

// used for smartcasting only
// struct fields change type in scopes
pub struct ScopeStructField {
pub:
	struct_type    table.Type // type of struct
	name           string
	pos            token.Position
	typ            table.Type
	sum_type_casts []table.Type // nested sum types require nested smart casting, for that a list of types is needed
}

pub struct GlobalField {
pub:
	name     string
	expr     Expr
	has_expr bool
	pos      token.Position
pub mut:
	typ      table.Type
	comments []Comment
}

pub struct GlobalDecl {
pub:
	pos token.Position
pub mut:
	fields       []GlobalField
	end_comments []Comment
}

pub struct EmbeddedFile {
pub:
	rpath string // used in the source code, as an ID/key to the embed
	apath string // absolute path during compilation to the resource
}

// Each V source file is represented by one ast.File structure.
// When the V compiler runs, the parser will fill an []ast.File.
// That array is then passed to V's checker.
pub struct File {
pub:
	path         string // absolute path of the source file - '/projects/v/file.v'
	path_base    string // file name - 'file.v' (useful for tracing)
	mod          Module // the module of the source file (from `module xyz` at the top)
	global_scope &Scope
pub mut:
	scope            &Scope
	stmts            []Stmt            // all the statements in the source file
	imports          []Import          // all the imports
	auto_imports     []string          // imports that were implicitely added
	embedded_files   []EmbeddedFile    // list of files to embed in the binary
	imported_symbols map[string]string // used for `import {symbol}`, it maps symbol => module.symbol
	errors           []errors.Error    // all the checker errors in the file
	warnings         []errors.Warning  // all the checker warings in the file
	generic_fns      []&FnDecl
}

pub struct IdentFn {
pub mut:
	typ table.Type
}

// TODO: (joe) remove completely, use ident.obj
// instead which points to the scope object
pub struct IdentVar {
pub mut:
	typ         table.Type
	is_mut      bool
	is_static   bool
	is_optional bool
	share       table.ShareType
}

pub type IdentInfo = IdentFn | IdentVar

pub enum IdentKind {
	unresolved
	blank_ident
	variable
	constant
	global
	function
}

// A single identifier
pub struct Ident {
pub:
	language table.Language
	tok_kind token.Kind
	pos      token.Position
	mut_pos  token.Position
pub mut:
	scope  &Scope
	obj    ScopeObject
	mod    string
	name   string
	kind   IdentKind
	info   IdentInfo
	is_mut bool
}

pub fn (i &Ident) var_info() IdentVar {
	match mut i.info {
		IdentVar {
			return i.info
		}
		else {
			// return IdentVar{}
			panic('Ident.var_info(): info is not IdentVar variant')
		}
	}
}

// left op right
// See: token.Kind.is_infix
pub struct InfixExpr {
pub:
	op  token.Kind
	pos token.Position
pub mut:
	left        Expr
	right       Expr
	left_type   table.Type
	right_type  table.Type
	auto_locked string
	or_block    OrExpr
}

// ++, --
pub struct PostfixExpr {
pub:
	op   token.Kind
	expr Expr
	pos  token.Position
pub mut:
	auto_locked string
}

// See: token.Kind.is_prefix
pub struct PrefixExpr {
pub:
	op    token.Kind
	right Expr
	pos   token.Position
pub mut:
	right_type table.Type
	or_block   OrExpr
}

pub struct IndexExpr {
pub:
	pos     token.Position
	left    Expr
	index   Expr // [0], RangeExpr [start..end] or map[key]
	or_expr OrExpr
pub mut:
	left_type table.Type // array, map, fixed array
	is_setter bool
}

pub struct IfExpr {
pub:
	is_comptime   bool
	tok_kind      token.Kind
	left          Expr // `a` in `a := if ...`
	pos           token.Position
	post_comments []Comment
pub mut:
	branches []IfBranch // includes all `else if` branches
	is_expr  bool
	typ      table.Type
	has_else bool
}

pub struct IfBranch {
pub:
	cond     Expr
	pos      token.Position
	body_pos token.Position
	comments []Comment
pub mut:
	stmts     []Stmt
	smartcast bool // true when cond is `x is SumType`, set in checker.if_expr // no longer needed with union sum types TODO: remove
	scope     &Scope
}

pub struct UnsafeExpr {
pub:
	expr Expr
	pos  token.Position
}

pub struct LockExpr {
pub:
	stmts    []Stmt
	is_rlock bool
	pos      token.Position
pub mut:
	lockeds []Ident // `x`, `y` in `lock x, y {`
	is_expr bool
	typ     table.Type
}

pub struct MatchExpr {
pub:
	tok_kind token.Kind
	cond     Expr
	branches []MatchBranch
	pos      token.Position
pub mut:
	is_expr       bool // returns a value
	return_type   table.Type
	cond_type     table.Type // type of `x` in `match x {`
	expected_type table.Type // for debugging only
	is_sum_type   bool
}

pub struct MatchBranch {
pub:
	exprs         []Expr      // left side
	ecmnts        [][]Comment // inline comments for each left side expr
	stmts         []Stmt      // right side
	pos           token.Position
	comments      []Comment // comment above `xxx {`
	is_else       bool
	post_comments []Comment
pub mut:
	scope &Scope
}

pub struct SelectExpr {
pub:
	branches      []SelectBranch
	pos           token.Position
	has_exception bool
pub mut:
	is_expr       bool       // returns a value
	expected_type table.Type // for debugging only
}

pub struct SelectBranch {
pub:
	stmt          Stmt   // `a := <-ch` or `ch <- a`
	stmts         []Stmt // right side
	pos           token.Position
	comment       Comment // comment above `select {`
	is_else       bool
	is_timeout    bool
	post_comments []Comment
}

pub enum CompForKind {
	methods
	fields
}

pub struct CompFor {
pub:
	val_var string
	stmts   []Stmt
	kind    CompForKind
	pos     token.Position
pub mut:
	// expr    Expr
	typ table.Type
}

pub struct ForStmt {
pub:
	cond   Expr
	stmts  []Stmt
	is_inf bool // `for {}`
	pos    token.Position
pub mut:
	label string // `label: for {`
	scope &Scope
}

pub struct ForInStmt {
pub:
	key_var    string
	val_var    string
	cond       Expr
	is_range   bool
	high       Expr // `10` in `for i in 0..10 {`
	stmts      []Stmt
	pos        token.Position
	val_is_mut bool // `for mut val in vals {` means that modifying `val` will modify the array
	// and the array cannot be indexed inside the loop
pub mut:
	key_type  table.Type
	val_type  table.Type
	cond_type table.Type
	kind      table.Kind // array/map/string
	label     string     // `label: for {`
	scope     &Scope
}

pub struct ForCStmt {
pub:
	init     Stmt // i := 0;
	has_init bool
	cond     Expr // i < 10;
	has_cond bool
	inc      Stmt // i++; i += 2
	has_inc  bool
	stmts    []Stmt
	pos      token.Position
pub mut:
	label string // `label: for {`
	scope &Scope
}

// #include etc
pub struct HashStmt {
pub:
	mod string
	pos token.Position
pub mut:
	val  string // example: 'include <openssl/rand.h> # please install openssl // comment'
	kind string // : 'include'
	main string // : '<openssl/rand.h>'
	msg  string // : 'please install openssl'
}

/*
// filter(), map(), sort()
pub struct Lambda {
pub:
	name string
}
*/
// variable assign statement
pub struct AssignStmt {
pub:
	right        []Expr
	op           token.Kind // include: =,:=,+=,-=,*=,/= and so on; for a list of all the assign operators, see vlib/token/token.v
	pos          token.Position
	comments     []Comment
	end_comments []Comment
pub mut:
	left          []Expr
	left_types    []table.Type
	right_types   []table.Type
	is_static     bool // for translated code only
	is_simple     bool // `x+=2` in `for x:=1; ; x+=2`
	has_cross_var bool
}

pub struct AsCast {
pub:
	expr Expr
	typ  table.Type
	pos  token.Position
pub mut:
	expr_type table.Type
}

// an enum value, like OS.macos or .macos
pub struct EnumVal {
pub:
	enum_name string
	val       string
	mod       string // for full path `mod_Enum_val`
	pos       token.Position
pub mut:
	typ table.Type
}

// enum field in enum declaration
pub struct EnumField {
pub:
	name          string
	pos           token.Position
	comments      []Comment // comment after Enumfield in the same line
	next_comments []Comment // comments between current EnumField and next EnumField
	expr          Expr      // the value of current EnumField; 123 in `ename = 123`
	has_expr      bool      // true, when .expr has a value
}

// enum declaration
pub struct EnumDecl {
pub:
	name             string
	is_pub           bool
	is_flag          bool         // true when the enum has [flag] tag,for bit field enum
	is_multi_allowed bool         // true when the enum has [_allow_multiple_values] tag
	comments         []Comment    // comments before the first EnumField
	fields           []EnumField  // all the enum fields
	attrs            []table.Attr // attributes of enum declaration
	pos              token.Position
}

pub struct AliasTypeDecl {
pub:
	name        string
	is_pub      bool
	parent_type table.Type
	pos         token.Position
	comments    []Comment
}

// New implementation of sum types
pub struct SumTypeDecl {
pub:
	name     string
	is_pub   bool
	pos      token.Position
	comments []Comment
pub mut:
	variants []SumTypeVariant
}

pub struct SumTypeVariant {
pub:
	typ table.Type
	pos token.Position
}

pub struct FnTypeDecl {
pub:
	name     string
	is_pub   bool
	typ      table.Type
	pos      token.Position
	comments []Comment
}

// TODO: handle this differently
// v1 excludes non current os ifdefs so
// the defer's never get added in the first place
pub struct DeferStmt {
pub:
	stmts []Stmt
	pos   token.Position
pub mut:
	ifdef string
}

// `(3+4)`
pub struct ParExpr {
pub:
	expr Expr
	pos  token.Position
}

pub struct GoStmt {
pub:
	pos token.Position
pub mut:
	call_expr CallExpr
}

pub struct GoExpr {
pub:
	pos token.Position
pub mut:
	go_stmt GoStmt
mut:
	return_type table.Type
}

pub struct GotoLabel {
pub:
	name string
	pos  token.Position
}

pub struct GotoStmt {
pub:
	name string
	pos  token.Position
}

pub struct ArrayInit {
pub:
	pos           token.Position // `[]` in []Type{} position
	elem_type_pos token.Position // `Type` in []Type{} position
	exprs         []Expr      // `[expr, expr]` or `[expr]Type{}` for fixed array
	ecmnts        [][]Comment // optional iembed comments after each expr
	is_fixed      bool
	has_val       bool // fixed size literal `[expr, expr]!`
	mod           string
	len_expr      Expr // len: expr
	cap_expr      Expr // cap: expr
	default_expr  Expr // init: expr
	has_len       bool
	has_cap       bool
	has_default   bool
pub mut:
	expr_types     []table.Type // [Dog, Cat] // also used for interface_types
	is_interface   bool       // array of interfaces e.g. `[]Animal` `[Dog{}, Cat{}]`
	interface_type table.Type // Animal
	elem_type      table.Type // element type
	typ            table.Type // array type
}

pub struct ArrayDecompose {
pub:
	expr Expr
	pos  token.Position
pub mut:
	expr_type table.Type
	arg_type  table.Type
}

pub struct ChanInit {
pub:
	pos      token.Position
	cap_expr Expr
	has_cap  bool
pub mut:
	typ       table.Type
	elem_type table.Type
}

pub struct MapInit {
pub:
	pos  token.Position
	keys []Expr
	vals []Expr
pub mut:
	typ        table.Type
	key_type   table.Type
	value_type table.Type
}

// s[10..20]
pub struct RangeExpr {
pub:
	low      Expr
	high     Expr
	has_high bool
	has_low  bool
	pos      token.Position
}

// NB: &string(x) gets parsed as ast.PrefixExpr{ right: ast.CastExpr{...} }
// TODO: that is very likely a parsing bug. It should get parsed as just
// ast.CastExpr{...}, where .typname is '&string' instead.
// The current situation leads to special cases in vfmt and cgen
// (see prefix_expr_cast_expr in fmt.v, and .is_amp in cgen.v)
// .in_prexpr is also needed because of that, because the checker needs to
// show warnings about the deprecated C->V conversions `string(x)` and
// `string(x,y)`, while skipping the real pointer casts like `&string(x)`.
pub struct CastExpr {
pub:
	expr Expr       // `buf` in `string(buf, n)`
	arg  Expr       // `n` in `string(buf, n)`
	typ  table.Type // `string` TODO rename to `type_to_cast_to`
	pos  token.Position
pub mut:
	typname   string     // TypeSymbol.name
	expr_type table.Type // `byteptr`
	has_arg   bool
	in_prexpr bool // is the parent node an ast.PrefixExpr
}

pub struct AssertStmt {
pub:
	pos token.Position
pub mut:
	expr Expr
}

// `if [x := opt()] {`
pub struct IfGuardExpr {
pub:
	var_name string
	expr     Expr
	pos      token.Position
pub mut:
	expr_type table.Type
}

pub enum OrKind {
	absent
	block
	propagate
}

// `or { ... }`
pub struct OrExpr {
pub:
	stmts []Stmt
	kind  OrKind
	pos   token.Position
}

/*
// `or { ... }`
pub struct OrExpr2 {
pub:
	call_expr CallExpr
	stmts     []Stmt // inside `or { }`
	kind      OrKind
	pos       token.Position
}
*/
pub struct Assoc {
pub:
	var_name string
	fields   []string
	exprs    []Expr
	pos      token.Position
pub mut:
	typ   table.Type
	scope &Scope
}

pub struct SizeOf {
pub:
	is_type bool
	expr    Expr // checker uses this to set typ
	pos     token.Position
pub mut:
	typ table.Type
}

pub struct Likely {
pub:
	expr      Expr
	pos       token.Position
	is_likely bool // false for _unlikely_
}

pub struct TypeOf {
pub:
	expr Expr
	pos  token.Position
pub mut:
	expr_type table.Type
}

pub struct Comment {
pub:
	text     string
	is_multi bool
	line_nr  int
	pos      token.Position
}

pub struct ConcatExpr {
pub:
	vals []Expr
	pos  token.Position
pub mut:
	return_type table.Type
}

// @FN, @STRUCT, @MOD etc. See full list in token.valid_at_tokens
pub struct AtExpr {
pub:
	name string
	pos  token.Position
	kind token.AtKind
pub mut:
	val string
}

pub struct ComptimeSelector {
pub:
	has_parens bool // if $() is used, for vfmt
	left       Expr
	field_expr Expr
pub mut:
	left_type table.Type
	typ       table.Type
}

pub struct ComptimeCall {
pub:
	has_parens  bool // if $() is used, for vfmt
	method_name string
	left        Expr
	is_vweb     bool
	vweb_tmpl   File
	args_var    string
	is_embed    bool
	embed_file  EmbeddedFile
pub mut:
	sym table.TypeSymbol
}

pub struct None {
pub:
	pos token.Position
	foo int // todo
}

/*
pub enum SqlExprKind {
	select_
	insert
	update
}
*/
pub enum SqlStmtKind {
	insert
	update
	delete
}

pub struct SqlStmt {
pub:
	kind            SqlStmtKind
	db_expr         Expr   // `db` in `sql db {`
	object_var_name string // `user`
	table_type      table.Type
	pos             token.Position
	where_expr      Expr
	updated_columns []string // for `update set x=y`
	update_exprs    []Expr   // for `update`
pub mut:
	table_name string
	fields     []table.Field
}

pub struct SqlExpr {
pub:
	typ         table.Type
	is_count    bool
	db_expr     Expr // `db` in `sql db {`
	where_expr  Expr
	has_where   bool
	has_offset  bool
	offset_expr Expr
	has_order   bool
	order_expr  Expr
	has_desc    bool
	is_array    bool
	table_type  table.Type
	pos         token.Position
	has_limit   bool
	limit_expr  Expr
pub mut:
	table_name string
	fields     []table.Field
}

[inline]
pub fn (expr Expr) is_blank_ident() bool {
	match expr {
		Ident { return expr.kind == .blank_ident }
		else { return false }
	}
}

pub fn (expr Expr) position() token.Position {
	// all uncommented have to be implemented
	match expr {
		// KEKW2
		AnonFn {
			return expr.decl.pos
		}
		ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, CastExpr, ChanInit, CharLiteral,
		ConcatExpr, Comment, EnumVal, FloatLiteral, GoExpr, Ident, IfExpr, IndexExpr, IntegerLiteral,
		Likely, LockExpr, MapInit, MatchExpr, None, OrExpr, ParExpr, PostfixExpr, PrefixExpr,
		RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral,
		StructInit, Type, TypeOf, UnsafeExpr {
			return expr.pos
		}
		ArrayDecompose {
			return expr.pos
		}
		IfGuardExpr {
			return expr.expr.position()
		}
		ComptimeCall, ComptimeSelector {
			return expr.left.position()
		}
		InfixExpr {
			left_pos := expr.left.position()
			right_pos := expr.right.position()
			return token.Position{
				line_nr: expr.pos.line_nr
				pos: left_pos.pos
				len: right_pos.pos - left_pos.pos + right_pos.len
				last_line: right_pos.last_line
			}
		}
		CTempVar {
			return token.Position{}
		}
		// Please, do NOT use else{} here.
		// This match is exhaustive *on purpose*, to help force
		// maintaining/implementing proper .pos fields.
	}
}

pub fn (expr Expr) is_lvalue() bool {
	match expr {
		Ident { return true }
		CTempVar { return true }
		IndexExpr { return expr.left.is_lvalue() }
		SelectorExpr { return expr.expr.is_lvalue() }
		else {}
	}
	return false
}

pub fn (expr Expr) is_expr() bool {
	match expr {
		IfExpr { return expr.is_expr }
		MatchExpr { return expr.is_expr }
		else {}
	}
	return true
}

pub fn (expr Expr) is_lit() bool {
	return match expr {
		BoolLiteral, StringLiteral, IntegerLiteral { true }
		else { false }
	}
}

// check if stmt can be an expression in C
pub fn (stmt Stmt) check_c_expr() ? {
	match stmt {
		AssignStmt {
			return
		}
		ExprStmt {
			if stmt.expr.is_expr() {
				return
			}
			return error('unsupported statement (`$stmt.expr.type_name()`)')
		}
		else {}
	}
	return error('unsupported statement (`$stmt.type_name()`)')
}

// CTempVar is used in cgen only, to hold nodes for temporary variables
pub struct CTempVar {
pub:
	name   string     // the name of the C temporary variable; used by g.expr(x)
	orig   Expr       // the original expression, which produced the C temp variable; used by x.str()
	typ    table.Type // the type of the original expression
	is_ptr bool       // whether the type is a pointer
}

pub fn (stmt Stmt) position() token.Position {
	match stmt {
		AssertStmt, AssignStmt, Block, BranchStmt, CompFor, ConstDecl, DeferStmt, EnumDecl, ExprStmt,
		FnDecl, ForCStmt, ForInStmt, ForStmt, GotoLabel, GotoStmt, Import, Return, StructDecl,
		GlobalDecl, HashStmt, InterfaceDecl, Module, SqlStmt {
			return stmt.pos
		}
		GoStmt {
			return stmt.call_expr.pos
		}
		TypeDecl {
			match stmt {
				AliasTypeDecl, FnTypeDecl, SumTypeDecl { return stmt.pos }
			}
		}
		// Please, do NOT use else{} here.
		// This match is exhaustive *on purpose*, to help force
		// maintaining/implementing proper .pos fields.
	}
}

pub fn (node Node) position() token.Position {
	match node {
		Stmt {
			mut pos := node.position()
			if node is Import {
				for sym in node.syms {
					pos = pos.extend(sym.pos)
				}
			}
			return pos
		}
		Expr {
			return node.position()
		}
		StructField {
			return node.pos.extend(node.type_pos)
		}
		MatchBranch, SelectBranch, Field, EnumField, ConstField, StructInitField, GlobalField,
		table.Param {
			return node.pos
		}
		IfBranch {
			return node.pos.extend(node.body_pos)
		}
		ScopeObject {
			match node {
				ConstField, GlobalField, Var { return node.pos }
			}
		}
		File {
			mut pos := token.Position{}
			if node.stmts.len > 0 {
				first_pos := node.stmts.first().position()
				last_pos := node.stmts.last().position()
				pos = first_pos.extend_with_last_line(last_pos, last_pos.line_nr)
			}
			return pos
		}
	}
}

pub fn (node Node) children() []Node {
	mut children := []Node{}
	if node is Expr {
		match node {
			StringInterLiteral, Assoc, ArrayInit {
				return node.exprs.map(Node(it))
			}
			SelectorExpr, PostfixExpr, UnsafeExpr, AsCast, ParExpr, IfGuardExpr, SizeOf, Likely,
			TypeOf, ArrayDecompose {
				children << node.expr
			}
			LockExpr, OrExpr {
				return node.stmts.map(Node(it))
			}
			StructInit {
				return node.fields.map(Node(it))
			}
			AnonFn {
				children << Stmt(node.decl)
			}
			CallExpr {
				children << node.left
				children << Expr(node.or_block)
			}
			InfixExpr {
				children << node.left
				children << node.right
			}
			PrefixExpr {
				children << node.right
			}
			IndexExpr {
				children << node.left
				children << node.index
			}
			IfExpr {
				children << node.left
				children << node.branches.map(Node(it))
			}
			MatchExpr {
				children << node.cond
				children << node.branches.map(Node(it))
			}
			SelectExpr {
				return node.branches.map(Node(it))
			}
			ChanInit {
				children << node.cap_expr
			}
			MapInit {
				children << node.keys.map(Node(it))
				children << node.vals.map(Node(it))
			}
			RangeExpr {
				children << node.low
				children << node.high
			}
			CastExpr {
				children << node.expr
				children << node.arg
			}
			ConcatExpr {
				return node.vals.map(Node(it))
			}
			ComptimeCall, ComptimeSelector {
				children << node.left
			}
			else {}
		}
	} else if node is Stmt {
		match node {
			Block, DeferStmt, ForCStmt, ForInStmt, ForStmt, CompFor {
				return node.stmts.map(Node(it))
			}
			ExprStmt, AssertStmt {
				children << node.expr
			}
			InterfaceDecl {
				return node.methods.map(Node(Stmt(it)))
			}
			AssignStmt {
				children << node.left.map(Node(it))
				children << node.right.map(Node(it))
			}
			Return {
				return node.exprs.map(Node(it))
			}
			// NB: these four decl nodes cannot be merged as one branch
			StructDecl {
				return node.fields.map(Node(it))
			}
			GlobalDecl {
				return node.fields.map(Node(it))
			}
			ConstDecl {
				return node.fields.map(Node(it))
			}
			EnumDecl {
				return node.fields.map(Node(it))
			}
			FnDecl {
				if node.is_method {
					children << Node(node.receiver)
				}
				children << node.params.map(Node(it))
				children << node.stmts.map(Node(it))
			}
			else {}
		}
	} else if node is ScopeObject {
		match node {
			GlobalField, ConstField, Var { children << node.expr }
		}
	} else {
		match node {
			GlobalField, ConstField, EnumField, StructInitField {
				children << node.expr
			}
			SelectBranch {
				children << node.stmt
				children << node.stmts.map(Node(it))
			}
			IfBranch, File {
				return node.stmts.map(Node(it))
			}
			MatchBranch {
				children << node.stmts.map(Node(it))
				children << node.exprs.map(Node(it))
			}
			else {}
		}
	}
	return children
}

// TODO: remove this fugly hack :-|
// fe2ex/1 and ex2fe/1 are used to convert back and forth from
// table.FExpr to ast.Expr , which in turn is needed to break
// a dependency cycle between v.ast and v.table, for the single
// field table.Field.default_expr, which should be ast.Expr
pub fn fe2ex(x table.FExpr) Expr {
	res := Expr{}
	unsafe { C.memcpy(&res, &x, sizeof(Expr)) }
	return res
}

pub fn ex2fe(x Expr) table.FExpr {
	res := table.FExpr{}
	unsafe { C.memcpy(&res, &x, sizeof(table.FExpr)) }
	return res
}
