// 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.errors
import v.pref

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
	| DumpExpr
	| EmptyExpr
	| EnumVal
	| FloatLiteral
	| GoExpr
	| Ident
	| IfExpr
	| IfGuardExpr
	| IndexExpr
	| InfixExpr
	| IntegerLiteral
	| IsRefType
	| Likely
	| LockExpr
	| MapInit
	| MatchExpr
	| NodeError
	| None
	| OffsetOf
	| OrExpr
	| ParExpr
	| PostfixExpr
	| PrefixExpr
	| RangeExpr
	| SelectExpr
	| SelectorExpr
	| SizeOf
	| SqlExpr
	| StringInterLiteral
	| StringLiteral
	| StructInit
	| TypeNode
	| TypeOf
	| UnsafeExpr

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

pub type ScopeObject = AsmRegister | ConstField | GlobalField | Var

// TODO: replace Param
pub type Node = CallArg
	| ConstField
	| EmptyNode
	| EnumField
	| Expr
	| File
	| GlobalField
	| IfBranch
	| MatchBranch
	| NodeError
	| Param
	| ScopeObject
	| SelectBranch
	| Stmt
	| StructField
	| StructInitField

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

pub struct EmptyExpr {
	x int
}

pub fn empty_expr() Expr {
	return EmptyExpr{}
}

pub struct EmptyStmt {
pub:
	pos token.Position
}

pub fn empty_stmt() Stmt {
	return EmptyStmt{}
}

pub struct EmptyNode {
	x int
}

pub fn empty_node() Node {
	return EmptyNode{}
}

// `{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
pub mut:
	is_expr bool
	typ     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 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 []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
}

pub enum GenericKindField {
	unknown
	name
	typ
}

// `foo.bar`
pub struct SelectorExpr {
pub:
	pos        token.Position
	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            Expr // expr.field_name
	expr_type       Type // type of `Foo` in `Foo.bar`
	typ             Type // type of the entire thing (`Foo.bar`)
	name_type       Type // T in `T.name` or typeof in `typeof(expr).name`
	gkind_field     GenericKindField // `T.name` => ast.GenericKindField.name, `T.typ` => ast.GenericKindField.typ, or .unknown
	scope           &Scope
	from_embed_type 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 mut root is SelectorExpr {
		root = root.expr
	}
	if root is Ident {
		return root as Ident
	}

	return none
}

// module declaration
pub struct Module {
pub:
	name       string // encoding.base64
	short_name string // base64
	attrs      []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
	has_default_expr bool
	attrs            []Attr
	is_pub           bool
	default_val      string
	is_mut           bool
	is_global        bool
pub mut:
	default_expr     Expr
	default_expr_typ Type
	name             string
	typ              Type
}

/*
pub struct Field {
pub:
	name string
	pos  token.Position
pub mut:
	typ 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      Type      // the type of the const field, it can be any type in V
	comments []Comment // comments before current const field
	// the comptime_expr_value field is filled by the checker, when it has enough
	// info to evaluate the constant at compile time
	comptime_expr_value ComptTimeConstValue = empty_comptime_const_expr()
}

// 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
	generic_types []Type
	is_pub        bool
	// _pos fields for vfmt
	mut_pos      int // mut:
	pub_pos      int // pub:
	pub_mut_pos  int // pub mut:
	global_pos   int // __global:
	module_pos   int // module:
	language     Language
	is_union     bool
	attrs        []Attr
	end_comments []Comment
	embeds       []Embed
pub mut:
	fields []StructField
}

pub struct Embed {
pub:
	typ      Type
	pos      token.Position
	comments []Comment
}

pub struct InterfaceEmbedding {
pub:
	name     string
	typ      Type
	pos      token.Position
	comments []Comment
}

pub struct InterfaceDecl {
pub:
	name          string
	typ           Type
	name_pos      token.Position
	language      Language
	field_names   []string
	is_pub        bool
	mut_pos       int // mut:
	pos           token.Position
	pre_comments  []Comment
	generic_types []Type
pub mut:
	methods []FnDecl
	fields  []StructField
	//
	ifaces              []InterfaceEmbedding
	are_ifaces_expanded bool
}

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

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

pub struct StructInit {
pub:
	pos      token.Position
	name_pos token.Position
	is_short bool
pub mut:
	unresolved           bool
	pre_comments         []Comment
	typ                  Type
	update_expr          Expr
	update_expr_type     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
	syms_pos  token.Position
pub mut:
	syms          []ImportSymbol // the list of symbols in `import {symbol1, symbol2}`
	comments      []Comment
	next_comments []Comment
}

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

// anonymous function
pub struct AnonFn {
pub mut:
	decl           FnDecl
	inherited_vars []Param
	typ            Type // the type of anonymous fn. Both .typ and .decl.name are auto generated
	has_gen        bool // has been generated
}

// function or method declaration
pub struct FnDecl {
pub:
	name            string
	mod             string
	is_deprecated   bool
	is_pub          bool
	is_variadic     bool
	is_anon         bool
	is_noreturn     bool           // true, when [noreturn] is used on a fn
	is_manualfree   bool           // true, when [manualfree] is used on a fn
	is_main         bool           // true for `fn main()`
	is_test         bool           // true for `fn test_abcde`
	is_conditional  bool           // true for `[if abc] fn abc(){}`
	is_exported     bool           // true for `[export: 'exact_C_name']`
	is_keep_alive   bool           // passed memory must not be freed (by GC) before function returns
	is_unsafe       bool           // true, when [unsafe] is used on a fn
	receiver        StructField    // TODO this is not a struct 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       ShareType
	language        Language       // V, C, JS
	file_mode       Language       // whether *the file*, where a function was a '.c.v', '.js.v' etc.
	no_body         bool           // just a definition `fn C.malloc()`
	is_builtin      bool           // this function is defined in builtin/strconv
	body_pos        token.Position // function bodys position
	file            string
	generic_names   []string
	is_direct_arr   bool // direct array access
	attrs           []Attr
	ctdefine_idx    int = -1 // the index in fn.attrs of `[if xyz]`, when such attribute exists
pub mut:
	params            []Param
	stmts             []Stmt
	defer_stmts       []DeferStmt
	return_type       Type
	return_type_pos   token.Position // `string` in `fn (u User) name() string` position
	has_return        bool
	should_be_skipped bool
	//
	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
	label_names []string
	pos         token.Position // function declaration position
}

// 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
	name_pos token.Position
	mod      string
pub mut:
	name               string // left.name()
	is_method          bool
	is_field           bool // temp hack, remove ASAP when re-impl CallExpr / Selector (joe)
	is_keep_alive      bool // GC must not free arguments before fn returns
	is_noreturn        bool // whether the function/method is marked as [noreturn]
	args               []CallArg
	expected_arg_types []Type
	language           Language
	or_block           OrExpr
	left               Expr // `user` in `user.register()`
	left_type          Type // type of `user`
	receiver_type      Type // User
	return_type        Type
	should_be_skipped  bool
	concrete_types     []Type // concrete types, e.g. <int, string>
	concrete_list_pos  token.Position
	free_receiver      bool // true if the receiver expression needs to be freed
	scope              &Scope
	from_embed_type    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    ShareType
	comments []Comment
pub mut:
	expr            Expr
	typ             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
	comments []Comment
pub mut:
	exprs []Expr
	types []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           ShareType
	is_mut          bool
	is_autofree_tmp bool
	is_arg          bool // fn args should not be autofreed
	is_auto_deref   bool
	is_inherited    bool
pub mut:
	typ        Type
	orig_type  Type   // original sumtype type; 0 if it's not a sumtype
	smartcasts []Type // nested sum types require nested smart casting, for that a list of types is needed
	// TODO: move this to a real docs site later
	// 10 <- original type (orig_type)
	//   [11, 12, 13] <- cast order (smartcasts)
	//        12 <- the current casted type (typ)
	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
	is_auto_heap bool // value whoes address goes out of scope
	is_stack_obj bool // may be pointer to stack value (`mut` or `&` arg and not [heap] struct)
}

// used for smartcasting only
// struct fields change type in scopes
pub struct ScopeStructField {
pub:
	struct_type Type // type of struct
	name        string
	pos         token.Position
	typ         Type
	smartcasts  []Type // nested sum types require nested smart casting, for that a list of types is needed
	orig_type   Type   // original sumtype type; 0 if it's not a sumtype
	// TODO: move this to a real docs site later
	// 10 <- original type (orig_type)
	//   [11, 12, 13] <- cast order (smartcasts)
	//        12 <- the current casted type (typ)
}

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

pub struct GlobalDecl {
pub:
	mod      string
	pos      token.Position
	is_block bool // __global() block
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 File structure.
// When the V compiler runs, the parser will fill an []File.
// That array is then passed to V's checker.
[heap]
pub struct File {
pub:
	nr_lines     int    // number of source code lines in the file (including newlines and comments)
	nr_bytes     int    // number of processed source code bytes
	mod          Module // the module of the source file (from `module xyz` at the top)
	global_scope &Scope
	is_test      bool // true for _test.v files
pub mut:
	path             string // absolute path of the source file - '/projects/v/file.v'
	path_base        string // file name - 'file.v' (useful for tracing)
	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 warnings in the file
	notices          []errors.Notice   // all the checker notices in the file
	generic_fns      []&FnDecl
	global_labels    []string // from `asm { .globl labelname }`
}

[unsafe]
pub fn (f &File) free() {
	unsafe {
		f.path.free()
		f.path_base.free()
		f.scope.free()
		f.stmts.free()
		f.imports.free()
		f.auto_imports.free()
		f.embedded_files.free()
		f.imported_symbols.free()
		f.errors.free()
		f.warnings.free()
		f.notices.free()
		f.global_labels.free()
	}
}

pub struct IdentFn {
pub mut:
	typ Type
}

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

pub type IdentInfo = IdentFn | IdentVar

pub enum IdentKind {
	unresolved
	blank_ident
	variable
	constant
	global
	function
}

// A single identifier
pub struct Ident {
pub:
	language Language
	tok_kind token.Kind
	pos      token.Position
	mut_pos  token.Position
	comptime bool
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
	is_stmt bool
pub mut:
	left        Expr
	right       Expr
	left_type   Type
	right_type  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
	pos token.Position
pub mut:
	right_type Type
	right      Expr
	or_block   OrExpr
	is_option  bool // IfGuard
}

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

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      Type
	has_else bool
	// implements bool // comptime $if implements interface
}

pub struct IfBranch {
pub:
	cond     Expr
	pos      token.Position
	body_pos token.Position
	comments []Comment
pub mut:
	pkg_exist bool
	stmts     []Stmt
	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  []Expr // `x`, `y.z` in `lock x, y.z {`
	comments []Comment
	is_expr  bool
	typ      Type
	scope    &Scope
}

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

pub struct MatchBranch {
pub:
	ecmnts        [][]Comment // inline comments for each left side expr
	stmts         []Stmt      // right side
	pos           token.Position
	is_else       bool
	post_comments []Comment      // comments below ´... }´
	branch_pos    token.Position // for checker errors about invalid branches
pub mut:
	exprs []Expr // left side
	scope &Scope
}

pub struct SelectExpr {
pub:
	branches      []SelectBranch
	pos           token.Position
	has_exception bool
pub mut:
	is_expr       bool // returns a value
	expected_type 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
	attributes
}

pub struct CompFor {
pub:
	val_var string
	stmts   []Stmt
	kind    CompForKind
	pos     token.Position
	typ_pos token.Position
pub mut:
	// expr    Expr
	typ 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  Type
	val_type  Type
	cond_type Type
	kind      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
	is_multi bool // for a,b := 0,1; a < 10; a,b = a+b, a {...}
	stmts    []Stmt
	pos      token.Position
pub mut:
	label string // `label: for {`
	scope &Scope
}

// #include, #define etc
pub struct HashStmt {
pub:
	mod         string
	pos         token.Position
	source_file string
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'
	ct_conds []Expr // *all* comptime conditions, that must be true, for the hash to be processed
	// ct_conds is filled by the checker, based on the current nesting of `$if cond1 {}` blocks
}

/*
// filter(), map(), sort()
pub struct Lambda {
pub:
	name string
}
*/
// variable assign statement
pub struct AssignStmt {
pub:
	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:
	right         []Expr
	left          []Expr
	left_types    []Type
	right_types   []Type
	is_static     bool // for translated code only
	is_simple     bool // `x+=2` in `for x:=1; ; x+=2`
	has_cross_var bool
}

// `expr as Ident`
pub struct AsCast {
pub:
	expr Expr // from expr: `expr` in `expr as Ident`
	typ  Type // to type
	pos  token.Position
pub mut:
	expr_type Type // from 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 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            []Attr      // attributes of enum declaration
	pos              token.Position
}

pub struct AliasTypeDecl {
pub:
	name        string
	is_pub      bool
	parent_type Type
	pos         token.Position
	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
	typ           Type
	generic_types []Type
pub mut:
	variants []TypeNode
}

pub struct FnTypeDecl {
pub:
	name     string
	is_pub   bool
	typ      Type
	pos      token.Position
	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:
	defer_vars []Ident
	ifdef      string
	idx_in_fn  int = -1 // index in FnDecl.defer_stmts
}

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

pub struct GoExpr {
pub:
	pos token.Position
pub mut:
	call_expr CallExpr
	is_expr   bool
}

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
	pre_cmnts     []Comment
	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 []Type // [Dog, Cat] // also used for interface_types
	elem_type  Type   // element type
	typ        Type   // array type
}

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

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

pub struct MapInit {
pub:
	pos       token.Position
	keys      []Expr
	vals      []Expr
	comments  [][]Comment // comments after key-value pairs
	pre_cmnts []Comment   // comments before the first key-value pair
pub mut:
	typ        Type
	key_type   Type
	value_type Type
}

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

pub struct CastExpr {
pub:
	arg Expr // `n` in `string(buf, n)`
pub mut:
	typ       Type   // `string`
	expr      Expr   // `buf` in `string(buf, n)` and `&Type(buf)`
	typname   string // `&Type` in `&Type(buf)`
	expr_type Type   // `byteptr`, the type of the `buf` expression
	has_arg   bool   // true for `string(buf, n)`, false for `&Type(buf)`
	pos       token.Position
}

pub struct AsmStmt {
pub:
	arch        pref.Arch
	is_basic    bool
	is_volatile bool
	is_goto     bool
	clobbered   []AsmClobbered
	pos         token.Position
pub mut:
	templates     []AsmTemplate
	scope         &Scope
	output        []AsmIO
	input         []AsmIO
	global_labels []string // labels defined in assembly block, exported with `.globl`
	local_labels  []string // local to the assembly block
}

pub struct AsmTemplate {
pub mut:
	name         string
	is_label     bool // `example_label:`
	is_directive bool // .globl assembly_function
	args         []AsmArg
	comments     []Comment
	pos          token.Position
}

// [eax+5] | j | displacement literal (e.g. 123 in [rax + 123] ) | eax | true | `a` | 0.594 | 123 | label_name
pub type AsmArg = AsmAddressing
	| AsmAlias
	| AsmDisp
	| AsmRegister
	| BoolLiteral
	| CharLiteral
	| FloatLiteral
	| IntegerLiteral
	| string

pub struct AsmRegister {
pub mut:
	name string // eax or r12d etc.
	typ  Type
	size int
}

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

pub struct AsmAlias {
pub:
	pos token.Position
pub mut:
	name string // a
}

pub struct AsmAddressing {
pub:
	scale int = -1 // 1, 2, 4, or 8 literal
	mode  AddressingMode
	pos   token.Position
pub mut:
	displacement AsmArg // 8, 16 or 32 bit literal value
	base         AsmArg // gpr
	index        AsmArg // gpr
}

// adressing modes:
pub enum AddressingMode {
	invalid
	displacement // displacement
	base // base
	base_plus_displacement // base + displacement
	index_times_scale_plus_displacement // (index ∗ scale) + displacement
	base_plus_index_plus_displacement // base + (index ∗ scale) + displacement
	base_plus_index_times_scale_plus_displacement // base + index + displacement
	rip_plus_displacement // rip + displacement
}

pub struct AsmClobbered {
pub mut:
	reg      AsmRegister
	comments []Comment
}

// : [alias_a] '=r' (a) // this is a comment
pub struct AsmIO {
pub:
	alias      string    // [alias_a]
	constraint string    // '=r' TODO: allow all backends to easily use this with a struct
	expr       Expr      // (a)
	comments   []Comment // // this is a comment
	typ        Type
	pos        token.Position
}

pub const (
	// reference: https://en.wikipedia.org/wiki/X86#/media/File:Table_of_x86_Registers_svg.svg
	// map register size -> register name
	x86_no_number_register_list = {
		8:  ['al', 'ah', 'bl', 'bh', 'cl', 'ch', 'dl', 'dh', 'bpl', 'sil', 'dil', 'spl']
		16: ['ax', 'bx', 'cx', 'dx', 'bp', 'si', 'di', 'sp', /* segment registers */ 'cs', 'ss',
			'ds', 'es', 'fs', 'gs', 'flags', 'ip', /* task registers */ 'gdtr', 'idtr', 'tr', 'ldtr',
			// CSR register 'msw', /* FP core registers */ 'cw', 'sw', 'tw', 'fp_ip', 'fp_dp',
			'fp_cs', 'fp_ds', 'fp_opc']
		32: [
			'eax',
			'ebx',
			'ecx',
			'edx',
			'ebp',
			'esi',
			'edi',
			'esp',
			'eflags',
			'eip', /* CSR register */
			'mxcsr' /* 32-bit FP core registers 'fp_dp', 'fp_ip' (TODO: why are there duplicates?) */,
		]
		64: ['rax', 'rbx', 'rcx', 'rdx', 'rbp', 'rsi', 'rdi', 'rsp', 'rflags', 'rip']
	}
	// no comments because maps do not support comments
	// r#*: gp registers added in 64-bit extensions, can only be from 8-15 actually
	// *mm#: vector/simd registors
	// st#: floating point numbers
	// cr#: control/status registers
	// dr#: debug registers
	x86_with_number_register_list = {
		8:   {
			'r#b': 16
		}
		16:  {
			'r#w': 16
		}
		32:  {
			'r#d': 16
		}
		64:  {
			'r#':  16
			'mm#': 16
			'cr#': 16
			'dr#': 16
		}
		80:  {
			'st#': 16
		}
		128: {
			'xmm#': 32
		}
		256: {
			'ymm#': 32
		}
		512: {
			'zmm#': 32
		}
	}
)

// TODO: saved priviled registers for arm
pub const (
	arm_no_number_register_list   = ['fp' /* aka r11 */, /* not instruction pointer: */ 'ip' /* aka r12 */,
		'sp' /* aka r13 */, 'lr' /* aka r14 */, /* this is instruction pointer ('program counter'): */
		'pc' /* aka r15 */,
	] // 'cpsr' and 'apsr' are special flags registers, but cannot be referred to directly
	arm_with_number_register_list = {
		'r#': 16
	}
)

pub const (
	riscv_no_number_register_list   = ['zero', 'ra', 'sp', 'gp', 'tp']
	riscv_with_number_register_list = {
		'x#': 32
		't#': 3
		's#': 12
		'a#': 8
	}
)

pub struct AssertStmt {
pub:
	pos token.Position
pub mut:
	expr    Expr
	is_used bool // asserts are used in _test.v files, as well as in non -prod builds of all files
}

// `if [x := opt()] {`
pub struct IfGuardExpr {
pub:
	var_name string
	pos      token.Position
pub mut:
	expr      Expr
	expr_type 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
}
*/

// deprecated
pub struct Assoc {
pub:
	var_name string
	fields   []string
	exprs    []Expr
	pos      token.Position
pub mut:
	typ   Type
	scope &Scope
}

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

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

pub struct OffsetOf {
pub:
	struct_type Type
	field       string
	pos         token.Position
}

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 Type
}

pub struct DumpExpr {
pub:
	expr Expr
	pos  token.Position
pub mut:
	expr_type Type
	cname     string // filled in the checker
}

pub struct Comment {
pub:
	text      string
	is_multi  bool // true only for /* comment */, that use many lines
	is_inline bool // true for all /* comment */ comments
	pos       token.Position
}

pub struct ConcatExpr {
pub:
	vals []Expr
	pos  token.Position
pub mut:
	return_type 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
	pos        token.Position
pub mut:
	left_type Type
	typ       Type
}

pub struct ComptimeCall {
pub:
	pos         token.Position
	has_parens  bool // if $() is used, for vfmt
	method_name string
	method_pos  token.Position
	scope       &Scope
	left        Expr
	args_var    string
	//
	is_vweb   bool
	vweb_tmpl File
	//
	is_embed   bool
	embed_file EmbeddedFile
	//
	is_env  bool
	env_pos token.Position
	//
	is_pkgconfig bool
pub mut:
	sym         TypeSymbol
	result_type Type
	env_value   string
	args        []CallArg
}

pub struct None {
pub:
	pos token.Position
}

pub enum SqlStmtKind {
	insert
	update
	delete
	create
	drop
}

pub struct SqlStmt {
pub:
	pos     token.Position
	db_expr Expr // `db` in `sql db {`
pub mut:
	lines []SqlStmtLine
}

pub struct SqlStmtLine {
pub:
	kind         SqlStmtKind
	pos          token.Position
	where_expr   Expr
	update_exprs []Expr // for `update`
pub mut:
	object_var_name string   // `user`
	updated_columns []string // for `update set x=y`
	table_expr      TypeNode
	fields          []StructField
	sub_structs     map[int]SqlStmtLine
}

pub struct SqlExpr {
pub:
	typ         Type
	is_count    bool
	db_expr     Expr // `db` in `sql db {`
	has_where   bool
	has_offset  bool
	offset_expr Expr
	has_order   bool
	order_expr  Expr
	has_desc    bool
	is_array    bool
	pos         token.Position
	has_limit   bool
	limit_expr  Expr
pub mut:
	where_expr  Expr
	table_expr  TypeNode
	fields      []StructField
	sub_structs map[int]SqlExpr
}

pub struct NodeError {
pub:
	idx int // index for referencing the related File error
	pos token.Position
}

[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
	// NB: please do not print here. the language server will hang
	// as it uses STDIO primarly to communicate ~Ned
	match expr {
		AnonFn {
			return expr.decl.pos
		}
		CTempVar, EmptyExpr {
			// println('compiler bug, unhandled EmptyExpr position()')
			return token.Position{}
		}
		NodeError, ArrayDecompose, ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr,
		CastExpr, ChanInit, CharLiteral, ConcatExpr, Comment, ComptimeCall, ComptimeSelector,
		EnumVal, DumpExpr, FloatLiteral, GoExpr, Ident, IfExpr, IntegerLiteral, IsRefType, Likely,
		LockExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr,
		RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral,
		StructInit, TypeNode, TypeOf, UnsafeExpr {
			return expr.pos
		}
		IndexExpr {
			if expr.or_expr.kind != .absent {
				return expr.or_expr.pos
			}
			return expr.pos
		}
		IfGuardExpr {
			return expr.expr.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
				col: left_pos.col
				last_line: right_pos.last_line
			}
		}
		// 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() }
		ParExpr { return expr.expr.is_lvalue() } // for var := &{...(*pointer_var)}
		PrefixExpr { return expr.right.is_lvalue() }
		else {}
	}
	return false
}

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

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

pub fn (expr Expr) is_auto_deref_var() bool {
	match expr {
		Ident {
			if expr.obj is Var {
				if expr.obj.is_auto_deref {
					return true
				}
			}
		}
		PrefixExpr {
			if expr.op == .amp && expr.right.is_auto_deref_var() {
				return true
			}
		}
		else {}
	}
	return false
}

// returns if an expression can be used in `lock x, y.z {`
pub fn (e &Expr) is_lockable() bool {
	match e {
		Ident {
			return true
		}
		SelectorExpr {
			return e.expr.is_lockable()
		}
		else {
			return 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    Type   // the type of the original expression
	is_ptr bool   // whether the type is a pointer
}

pub fn (node Node) position() token.Position {
	match node {
		NodeError {
			return token.Position{}
		}
		EmptyNode {
			return token.Position{}
		}
		Stmt {
			mut pos := node.pos
			if node is Import {
				for sym in node.syms {
					pos = pos.extend(sym.pos)
				}
			} else if node is TypeDecl {
				match node {
					FnTypeDecl, AliasTypeDecl {
						pos = pos.extend(node.type_pos)
					}
					SumTypeDecl {
						for variant in node.variants {
							pos = pos.extend(variant.pos)
						}
					}
				}
			}
			if node is AssignStmt {
				return pos.extend(node.right.last().position())
			}
			if node is AssertStmt {
				return pos.extend(node.expr.position())
			}
			return pos
		}
		Expr {
			return node.position()
		}
		StructField {
			return node.pos.extend(node.type_pos)
		}
		MatchBranch, SelectBranch, EnumField, ConstField, StructInitField, GlobalField, CallArg {
			return node.pos
		}
		Param {
			return node.pos.extend(node.type_pos)
		}
		IfBranch {
			return node.pos.extend(node.body_pos)
		}
		ScopeObject {
			match node {
				ConstField, GlobalField, Var {
					return node.pos
				}
				AsmRegister {
					return token.Position{
						len: -1
						line_nr: -1
						pos: -1
						last_line: -1
						col: -1
					}
				}
			}
		}
		File {
			mut pos := token.Position{}
			if node.stmts.len > 0 {
				first_pos := node.stmts.first().pos
				last_pos := node.stmts.last().pos
				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 << node.args.map(Node(it))
				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 {
				children << node.methods.map(Node(Stmt(it)))
				children << node.fields.map(Node(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))
			}
			TypeDecl {
				if node is SumTypeDecl {
					children << node.variants.map(Node(Expr(it)))
				}
			}
			else {}
		}
	} else if node is ScopeObject {
		match node {
			GlobalField, ConstField, Var { children << node.expr }
			AsmRegister {}
		}
	} else {
		match node {
			GlobalField, ConstField, EnumField, StructInitField, CallArg {
				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
}

// helper for dealing with `m[k1][k2][k3][k3] = value`
pub fn (mut lx IndexExpr) recursive_mapset_is_setter(val bool) {
	lx.is_setter = val
	if mut lx.left is IndexExpr {
		if lx.left.is_map {
			lx.left.recursive_mapset_is_setter(val)
		}
	}
}

// return all the registers for the given architecture
pub fn all_registers(mut t Table, arch pref.Arch) map[string]ScopeObject {
	mut res := map[string]ScopeObject{}
	match arch {
		.amd64, .i386 {
			for bit_size, array in ast.x86_no_number_register_list {
				for name in array {
					res[name] = AsmRegister{
						name: name
						typ: t.bitsize_to_type(bit_size)
						size: bit_size
					}
				}
			}
			for bit_size, array in ast.x86_with_number_register_list {
				for name, max_num in array {
					for i in 0 .. max_num {
						hash_index := name.index('#') or {
							panic('all_registers: no hashtag found')
						}
						assembled_name := '${name[..hash_index]}$i${name[hash_index + 1..]}'
						res[assembled_name] = AsmRegister{
							name: assembled_name
							typ: t.bitsize_to_type(bit_size)
							size: bit_size
						}
					}
				}
			}
		}
		.arm32 {
			arm32 := gen_all_registers(mut t, ast.arm_no_number_register_list, ast.arm_with_number_register_list,
				32)
			for k, v in arm32 {
				res[k] = v
			}
		}
		.arm64 {
			arm64 := gen_all_registers(mut t, ast.arm_no_number_register_list, ast.arm_with_number_register_list,
				64)
			for k, v in arm64 {
				res[k] = v
			}
		}
		.rv32 {
			rv32 := gen_all_registers(mut t, ast.riscv_no_number_register_list, ast.riscv_with_number_register_list,
				32)
			for k, v in rv32 {
				res[k] = v
			}
		}
		.rv64 {
			rv64 := gen_all_registers(mut t, ast.riscv_no_number_register_list, ast.riscv_with_number_register_list,
				64)
			for k, v in rv64 {
				res[k] = v
			}
		}
		else { // TODO
			panic('all_registers: unhandled arch')
		}
	}

	return res
}

// only for arm and riscv because x86 has different sized registers
fn gen_all_registers(mut t Table, without_numbers []string, with_numbers map[string]int, bit_size int) map[string]ScopeObject {
	mut res := map[string]ScopeObject{}
	for name in without_numbers {
		res[name] = AsmRegister{
			name: name
			typ: t.bitsize_to_type(bit_size)
			size: bit_size
		}
	}
	for name, max_num in with_numbers {
		for i in 0 .. max_num {
			hash_index := name.index('#') or { panic('all_registers: no hashtag found') }
			assembled_name := '${name[..hash_index]}$i${name[hash_index + 1..]}'
			res[assembled_name] = AsmRegister{
				name: assembled_name
				typ: t.bitsize_to_type(bit_size)
				size: bit_size
			}
		}
	}
	return res
}

// is `expr` a literal, i.e. it does not depend on any other declarations (C compile time constant)
pub fn (expr Expr) is_literal() bool {
	match expr {
		BoolLiteral, CharLiteral, FloatLiteral, IntegerLiteral {
			return true
		}
		PrefixExpr {
			return expr.right.is_literal()
		}
		InfixExpr {
			return expr.left.is_literal() && expr.right.is_literal()
		}
		ParExpr {
			return expr.expr.is_literal()
		}
		CastExpr {
			return !expr.has_arg && expr.expr.is_literal()
				&& (expr.typ.is_ptr() || expr.typ.is_pointer()
				|| expr.typ in [i8_type, i16_type, int_type, i64_type, byte_type, u8_type, u16_type, u32_type, u64_type, f32_type, f64_type, char_type, bool_type, rune_type])
		}
		SizeOf, IsRefType {
			return expr.is_type || expr.expr.is_literal()
		}
		else {
			return false
		}
	}
}
