// Copyright (c) 2019 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 compiler

import (
	os
	strings
	filepath
	compiler.x64
	// time
)

struct Parser {
	file_path              string // "/home/user/hello.v"
	file_name              string // "hello.v"
	file_platform          string // ".v", "_windows.v", "_nix.v", "_darwin.v", "_linux.v" ...
	// When p.file_pcguard != '', it contains a
	// C ifdef guard clause that must be put before
	// the #include directives in the parsed .v file
	file_pcguard           string
	v                      &V
	pref                   &Preferences // Preferences shared from V struct
mut:
	scanner                &Scanner
	tokens                 []Token
	token_idx              int
	tok                    TokenKind
	prev_tok               TokenKind
	prev_tok2              TokenKind // TODO remove these once the tokens are cached
	lit                    string
	cgen                   &CGen
	x64                    &x64.Gen
	table                  &Table
	import_table           ImportTable // Holds imports for just the file being parsed
	pass                   Pass
	os                     OS
	inside_const           bool
	expr_var               Var
	has_immutable_field    bool
	first_immutable_field  Var
	assigned_type          string // non-empty if we are in an assignment expression
	expected_type          string
	tmp_cnt                int
	builtin_mod            bool
	inside_if_expr         bool
	// inside_unwrapping_match bool
	inside_return_expr     bool
	inside_unsafe          bool
	is_struct_init         bool
	is_var_decl            bool
	if_expr_cnt            int
	for_expr_cnt           int // to detect whether `continue` can be used
	ptr_cast               bool
	calling_c              bool
	cur_fn                 Fn
	local_vars             []Var // local function variables
	global_vars            []Var // only for "script" programs without "fn main"
	var_idx                int
	returns                bool
	vroot                  string
	is_c_struct_init       bool
	is_empty_c_struct_init bool // for `foo := C.Foo{}` => `Foo foo;`
	is_c_fn_call           bool
	can_chash              bool
	attr                   string
	v_script               bool // "V bash", import all os functions into global space
	var_decl_name          string // To allow declaring the variable so that it can be used in the struct initialization
	is_alloc               bool // Whether current expression resulted in an allocation
	is_const_literal       bool // `1`, `2.0` etc, so that `u64_var == 0` works
	in_dispatch            bool // dispatching generic instance?
	is_vgen                bool
	is_sql                 bool
	is_js                  bool
	sql_i                  int // $1 $2 $3
	sql_params             []string // ("select * from users where id = $1", ***"100"***)
	sql_types              []string // int, string and so on; see sql_params
	is_vh                  bool // parsing .vh file (for example `const (a int)` is allowed)
	generic_dispatch       TypeInst
pub mut:
	mod                    string
}

const (
	max_module_depth = 5
	reserved_types = {
		'i8': true,
		'i16': true,
		'int': true,
		'i64': true,
		'i128': true,
		'byte': true,
		'u16': true,
		'u32': true,
		'u64': true,
		'u128': true,
		'f32': true,
		'f64': true,
		'rune': true,
		'byteptr': true,
		'voidptr': true
	}
)

struct ParserState {
	scanner_line_nr   int
	scanner_text      string
	scanner_pos       int
	scanner_line_ends []int
	scanner_nlines    int
	cgen_lines        []string
	cgen_cur_line     string
	cgen_tmp_line     string
	cgen_is_tmp       bool
	tokens            []Token
	token_idx         int
	tok               TokenKind
	prev_tok          TokenKind
	prev_tok2         TokenKind
	lit               string
}

// new parser from string. unique id specified in `id`.
// tip: use a hashing function to auto generate `id` from `text` eg. sha1.hexhash(text)
fn (v mut V) new_parser_from_string(text string) Parser {
	// line comment 1
	mut p := v.new_parser(new_scanner(text))
	p.scan_tokens() // same line comment
	return p
	// final comment
}

fn (v mut V) reset_cgen_file_line_parameters() {
	v.cgen.line = 0
	v.cgen.file = ''
	v.cgen.line_directives = v.pref.is_vlines
}

fn (v mut V) new_parser_from_file(path string) Parser {
	v.reset_cgen_file_line_parameters()
	// println('new_parser("$path")')
	mut path_pcguard := ''
	mut path_platform := '.v'
	for path_ending in ['_lin.v', '_mac.v', '_win.v', '_nix.v', '_linux.v', '_darwin.v', '_windows.v'] {
		if path.ends_with(path_ending) {
			if path_ending == '_mac.v' {
				p := path_ending.replace('_mac.v', '_darwin.v')
				println('warning: use "$p" file name instead of "$path"')
			}
			if path_ending == '_lin.v' {
				p := path_ending.replace('_lin.v', '_linux.v')
				println('warning: use "$p" file name instead of "$path"')
			}
			if path_ending == '_win.v' {
				p := path_ending.replace('_win.v', '_windows.v')
				println('warning: use "$p" file name instead of "$path"')
			}
			path_platform = path_ending
			path_pcguard = v.platform_postfix_to_ifdefguard(path_ending)
			break
		}
	}

	if v.compile_defines.len > 0 {
		for cdefine in v.compile_defines {
			custom_path_ending := '_d_${cdefine}.v'
			if path.ends_with(custom_path_ending){
				path_platform = custom_path_ending
				path_pcguard = v.platform_postfix_to_ifdefguard('custom $cdefine')
				break
			}
		}
	}

	mut p := v.new_parser(new_scanner_file(path))
	p = {
		p |
		file_path:path,
		file_name:path.all_after(os.path_separator),
		file_platform:path_platform,
		file_pcguard:path_pcguard,
		is_vh:path.ends_with('.vh'),
		v_script:path.ends_with('.vsh')
	}
	if p.v_script {
		println('new_parser: V script')
	}
	if p.pref.building_v {
		p.scanner.print_rel_paths_on_error = true
	}
	// if p.pref.generating_vh {
	// Keep newlines
	// p.scanner.is_vh = true
	// }
	p.scan_tokens()
	// p.scanner.debug_tokens()
	return p
}

// creates a new parser. most likely you will want to use
// `new_parser_file` or `new_parser_string` instead.
fn (v mut V) new_parser(scanner &Scanner) Parser {
	v.reset_cgen_file_line_parameters()
	mut p := Parser{
		scanner: scanner
		v: v
		table: v.table
		cur_fn: EmptyFn
		cgen: v.cgen
		x64: v.x64
		pref: v.pref
		os: v.os
		vroot: v.vroot
		local_vars: [Var{
		}].repeat(MaxLocalVars)
		import_table: new_import_table()
	}
	$if js {
		p.is_js = true
	}
	if p.pref.is_repl {
		p.scanner.print_line_on_error = false
		p.scanner.print_colored_error = false
		p.scanner.print_rel_paths_on_error = true
	}
	return p
}

// __global scan_time i64
fn (p mut Parser) scan_tokens() {
	// t := time.ticks()
	for {
		res := p.scanner.scan()
		p.tokens << Token{
			tok: res.tok
			lit: res.lit
			line_nr: p.scanner.line_nr
			pos: p.scanner.pos
		}
		if res.tok == .eof {
			break
		}
	}
	// scan_time += time.ticks() - t
	// println('scan tokens $p.file_name $scan_time ')
}

fn (p mut Parser) set_current_fn(f Fn) {
	p.cur_fn = f
	// p.cur_fn = p.table.fns[f.name]
	p.scanner.fn_name = '${f.mod}.${f.name}'
}

fn (p mut Parser) next() {
	// Generate a formatted version of this token
	// (only when vfmt compile time flag is enabled, otherwise this function
	// is not even generated)
	p.fnext()
	p.prev_tok2 = p.prev_tok
	p.prev_tok = p.tok
	p.scanner.prev_tok = p.tok
	if p.token_idx >= p.tokens.len {
		p.tok = .eof
		p.lit = ''
		return
	}
	res := p.tokens[p.token_idx]
	p.token_idx++
	p.tok = res.tok
	p.lit = res.lit
	p.scanner.line_nr = res.line_nr
	p.cgen.line = res.line_nr
}

fn (p &Parser) peek() TokenKind {
	if p.token_idx >= p.tokens.len - 2 {
		return .eof
	}
	return p.tokens[p.token_idx].tok
	/*
	mut i := p.token_idx
	for i < p.tokens.len  {
		tok := p.tokens[i]
		if tok.tok != .mline_comment && tok.tok != .line_comment {
			return tok.tok
		}
		i++
	}
	return .eof
	*/

}

// TODO remove dups
[inline]
fn (p &Parser) prev_token() Token {
	return p.tokens[p.token_idx - 2]
}

[inline]
fn (p &Parser) cur_tok() Token {
	return p.tokens[p.token_idx - 1]
}

[inline]
fn (p &Parser) peek_token() Token {
	if p.token_idx >= p.tokens.len - 2 {
		return Token{
			tok: .eof
		}
	}
	return p.tokens[p.token_idx]
}

fn (p &Parser) log(s string) {
	/*
	if !p.pref.is_verbose {
		return
	}
	println(s)
*/
}

pub fn (p &Parser) save_state() ParserState {
	return ParserState{
		scanner_line_nr: p.scanner.line_nr
		scanner_text: p.scanner.text
		scanner_pos: p.scanner.pos
		scanner_line_ends: p.scanner.line_ends
		scanner_nlines: p.scanner.nlines
		cgen_lines: p.cgen.lines
		cgen_cur_line: p.cgen.cur_line
		cgen_tmp_line: p.cgen.tmp_line
		cgen_is_tmp: p.cgen.is_tmp
		tokens: p.tokens
		token_idx: p.token_idx
		tok: p.tok
		prev_tok: p.prev_tok
		prev_tok2: p.prev_tok2
		lit: p.lit
	}
}

pub fn (p mut Parser) restore_state(state ParserState, scanner bool, cgen bool) {
	if scanner {
		p.scanner.line_nr = state.scanner_line_nr
		p.scanner.text = state.scanner_text
		p.scanner.pos = state.scanner_pos
		p.scanner.line_ends = state.scanner_line_ends
		p.scanner.nlines = state.scanner_nlines
	}
	if cgen {
		p.cgen.lines = state.cgen_lines
		p.cgen.cur_line = state.cgen_cur_line
		p.cgen.tmp_line = state.cgen_tmp_line
		p.cgen.is_tmp = state.cgen_is_tmp
	}
	p.tokens = state.tokens
	p.token_idx = state.token_idx
	p.tok = state.tok
	p.prev_tok = state.prev_tok
	p.prev_tok2 = state.prev_tok2
	p.lit = state.lit
}

fn (p mut Parser) clear_state(scanner bool, cgen bool) {
	if scanner {
		p.scanner.line_nr = 0
		p.scanner.text = ''
		p.scanner.pos = 0
		p.scanner.line_ends = []
		p.scanner.nlines = 0
	}
	if cgen {
		p.cgen.lines = []
		p.cgen.cur_line = ''
		p.cgen.tmp_line = ''
		p.cgen.is_tmp = false
	}
	p.tokens = []
	p.token_idx = 0
	p.lit = ''
}

pub fn (p mut Parser) add_text(text string) {
	if p.tokens.len > 1 && p.tokens[p.tokens.len - 1].tok == .eof {
		p.tokens.delete(p.tokens.len - 1)
	}
	p.scanner.text = p.scanner.text + '\n' + text
	p.scan_tokens()
}

fn (p mut Parser) statements_from_text(text string, rcbr bool) {
	saved_state := p.save_state()
	p.clear_state(true, false)
	p.add_text(text)
	p.next()
	if rcbr {
		p.statements()
	}
	else {
		p.statements_no_rcbr()
	}
	p.restore_state(saved_state, true, false)
}

fn (p mut Parser) parse(pass Pass) {
	p.cgen.line = 0
	p.cgen.file = cescaped_path(os.realpath(p.file_path))
	// ///////////////////////////////////
	p.pass = pass
	p.token_idx = 0
	p.next()
	// p.log('\nparse() run=$p.pass file=$p.file_name tok=${p.strtok()}')// , "script_file=", script_file)
	// `module main` is not required if it's a single file program
	if p.pref.is_script || p.pref.is_test {
		// User may still specify `module main`
		if p.tok == .key_module {
			p.next()
			p.fspace()
			p.mod = p.check_name()
		}
		else {
			p.mod = 'main'
		}
	}
	else {
		p.check(.key_module)
		p.fspace()
		p.mod = p.check_name()
	}
	//
	p.fgen_nl()
	p.cgen.nogen = false
	if p.pref.build_mode == .build_module && p.mod != p.v.mod {
		// println('skipping $p.mod (v.mod = $p.v.mod)')
		p.cgen.nogen = true
		// defer { p.cgen.nogen = false }
	}
	p.fgen_nl()
	p.builtin_mod = p.mod == 'builtin'
	p.can_chash = p.mod in ['ui', 'darwin', 'clipboard', 'webview'] // TODO tmp remove
	// Import pass - the first and the smallest pass that only analyzes imports
	// if we are a building module get the full module name from v.mod
	fq_mod := if p.pref.build_mode == .build_module && p.v.mod.ends_with(p.mod) { p.v.mod }
	// fully qualify the module name, eg base64 to encoding.base64
	else { p.table.qualify_module(p.mod, p.file_path) }
	p.table.register_module(fq_mod)
	p.mod = fq_mod
	if p.pass == .imports {
		for p.tok == .key_import && p.peek() != .key_const {
			p.imports()
		}
		if 'builtin' in p.table.imports {
			p.error('module `builtin` cannot be imported')
		}
		return
	}
	// Go through every top level token or throw a compilation error if a non-top level token is met
	for {
		match p.tok {
			.key_import {
				p.imports()
			}
			.key_enum {
				next := p.peek()
				if next == .name {
					p.enum_decl(false)
				}
				else if next == .lcbr && p.pref.translated {
					// enum without a name, only allowed in code,
					// translated from C. it's a very bad practice
					// in C as well, but is used unfortunately
					// (for example, by DOOM). such fields are
					// basically int consts
					p.enum_decl(true)
				}
			}
			.key_pub {
				next := p.peek()
				match next {
					.key_fn {
						p.fn_decl()
					}
					.key_const {
						p.const_decl()
					}
					.key_struct, .key_union, .key_interface {
						p.struct_decl([])
					}
					.key_enum {
						p.enum_decl(false)
					}
					.key_type {
						p.type_decl()
					}
					else {
						p.error('wrong pub keyword usage')
					}}
			}
			.key_fn {
				p.fn_decl()
			}
			.key_type {
				p.type_decl()
			}
			.lsbr {
				// `[` can only mean an [attribute] before a function
				// or a struct definition
				p.attribute()
			}
			.key_struct, .key_interface, .key_union, .lsbr {
				p.struct_decl([])
			}
			.key_const {
				p.const_decl()
			}
			.hash {
				// insert C code (only for ui module)
				// # puts("hello");
				p.chash()
			}
			.dollar {
				// $if, $else
				p.comp_time()
			}
			.key_global {
				if !p.pref.translated && !p.pref.is_live && !p.builtin_mod && !p.pref.building_v && p.mod != 'ui' && !os.getwd().contains('/volt') && !p.pref.enable_globals {
					p.error('use `v --enable-globals ...` to enable globals')
					// p.error('__global is only allowed in translated code')
				}
				p.next()
				p.fspace()
				name := p.check_name()
				p.fspace()
				typ := p.get_type()
				p.register_global(name, typ)
				// p.genln(p.table.cgen_name_type_pair(name, typ))
				mut g := p.table.cgen_name_type_pair(name, typ)
				if p.tok == .assign {
					p.next()
					g += ' = '
					_,expr := p.tmp_expr()
					g += expr
				}
				// p.genln('; // global')
				g += '; // global'
				if !p.cgen.nogen {
					p.cgen.consts << g
				}
				p.fgen_nl()
				if p.tok != .key_global {
					// An extra empty line to separate a block of globals
					p.fgen_nl()
				}
			}
			.eof {
				// p.log('end of parse()')
				// TODO: check why this was added? everything seems to work
				// without it, and it's already happening in fn_decl
				// if p.pref.is_script && !p.pref.is_test {
				// p.set_current_fn( MainFn )
				// p.check_unused_variables()
				// }
				if !p.first_pass() && !p.pref.is_repl {
					p.check_unused_imports()
				}
				p.gen_fmt() // not generated unless `-d vfmt` is provided
				return
			}
			else {
				// no `fn main`, add this "global" statement to cgen.fn_main
				if p.pref.is_script && !p.pref.is_test {
					// cur_fn is empty since there was no fn main declared
					// we need to set it to save and find variables
					if p.cur_fn.name == '' {
						p.set_current_fn(MainFn)
						if p.pref.is_repl {
							if p.first_pass() {
								return
							}
							p.clear_vars()
						}
					}
					start := p.cgen.lines.len
					p.statement(true)
					// if start > 0 && p.cgen.lines[start - 1] != '' &&
					// p.cgen.fn_main != '' {
					// start--
					// }
					p.genln('')
					end := p.cgen.lines.len
					lines := p.cgen.lines[start..end]
					// println('adding "' + lines.join('\n') + '"\n')
					// mut line := p.cgen.fn_main + lines.join('\n')
					// line = line.trim_space()
					p.cgen.fn_main += lines.join('\n')
					p.cgen.resetln('')
					for i := start; i < end; i++ {
						p.cgen.lines[i] = ''
					}
					p.fgen_nl()
				}
				else {
					p.error('unexpected token `${p.strtok()}`')
				}
			}}
	}
}

fn (p mut Parser) imports() {
	p.check(.key_import)
	// `import ()`
	if p.tok == .lpar {
		p.fspace()
		p.check(.lpar)
		p.fmt_inc()
		p.fgen_nl()
		for p.tok != .rpar && p.tok != .eof {
			p.import_statement()
			p.fgen_nl()
		}
		p.fmt_dec()
		p.check(.rpar)
		p.fgenln('\n')
		return
	}
	// `import foo`
	p.import_statement()
	p.fgen_nl()
	if p.tok != .key_import {
		p.fgen_nl()
	}
}

fn (p mut Parser) import_statement() {
	p.fspace()
	if p.tok != .name {
		p.error('bad import format')
	}
	if p.peek() == .number {
		p.error('bad import format: module/submodule names cannot begin with a number')
	}
	import_tok_idx := p.token_idx - 1
	mut mod := p.check_name().trim_space()
	mut mod_alias := mod
	// submodule support
	mut depth := 1
	for p.tok == .dot {
		p.check(.dot)
		submodule := p.check_name()
		mod_alias = submodule
		mod += '.' + submodule
		depth++
		if depth > max_module_depth {
			p.error('module depth of $max_module_depth exceeded: $mod')
		}
	}
	// aliasing (import encoding.base64 as b64)
	if p.tok == .key_as && p.peek() == .name {
		p.check(.key_as)
		mod_alias = p.check_name()
	}
	// add import to file scope import table
	p.register_import_alias(mod_alias, mod, import_tok_idx)
	// Make sure there are no duplicate imports
	if mod in p.table.imports {
		return
	}
	// p.log('adding import $mod')
	p.table.imports << mod
	p.table.register_module(mod)
}

fn (p mut Parser) const_decl() {
	// println('const decl $p.file_path')
	is_pub := p.tok == .key_pub
	if is_pub {
		p.next()
		p.fspace()
	}
	p.inside_const = true
	p.check(.key_const)
	p.fspace()
	p.check(.lpar)
	p.fgen_nl()
	p.fmt_inc()
	for p.tok == .name {
		if p.lit == '_' && p.peek() == .assign && !p.cgen.nogen {
			p.gen_blank_identifier_assign()
			// if !p.cgen.nogen {
			p.cgen.consts_init << p.cgen.cur_line.trim_space()
			p.cgen.resetln('')
			// }
			continue
		}
		mut name := p.check_name() // `Age = 20`
		// if !p.pref.building_v && p.mod != 'os' && contains_capital(name) {
		// p.warn('const names cannot contain uppercase letters, use snake_case instead')
		// }
		name = p.prepend_mod(name)
		mut typ := ''
		if p.is_vh {
			// println('CONST VH $p.file_path')
			// .vh files may not have const values, just types: `const (a int)`
			if p.tok == .assign {
				p.next()
				// Otherwise parse the expression to get its type,
				// but don't generate it. Const's value is generated
				// in "module.o".
				p.cgen.nogen = true
				typ = p.expression()
				p.cgen.nogen = false
			}
			else {
				typ = p.get_type()
			}
			p.table.register_const(name, typ, p.mod, is_pub)
			p.cgen.consts << ('extern ' + p.table.cgen_name_type_pair(name, typ)) + ';'
			continue // Don't generate C code when building a .vh file
		}
		else {
			p.check_space(.assign)
			typ = p.expression()
		}
		if p.first_pass() && p.table.known_const(name) {
			p.error('redefinition of `$name`')
		}
		if p.first_pass() {
			p.table.register_const(name, typ, p.mod, is_pub)
		}
		// Check to see if this constant exists, and is void. If so, try and get the type again:
		if my_const:=p.v.table.find_const(name){
			if my_const.typ == 'void' {
				for i, v in p.v.table.consts {
					if v.name == name {
						p.v.table.consts[i].typ = typ
						break
					}
				}
			}
		}
		if p.pass == .main && p.cgen.nogen && p.pref.build_mode == .build_module {
			// We are building module `ui`, but are parsing `gx` right now
			// (because of nogen). We need to import gx constants with `extern`.
			// println('extern const mod=$p.mod name=$name')
			p.cgen.consts << ('extern ' + p.table.cgen_name_type_pair(name, typ)) + ';'
		}
		if p.pass == .main && !p.cgen.nogen {
			// TODO hack
			// cur_line has const's value right now. if it's just a number, then optimize generation:
			// output a #define so that we don't pollute the binary with unnecessary global vars
			// Do not do this when building a module, otherwise the consts
			// will not be accessible.
			if p.pref.build_mode != .build_module && is_compile_time_const(p.cgen.cur_line) {
				p.cgen.const_defines << '#define $name $p.cgen.cur_line'
				p.cgen.resetln('')
				p.fgen_nl()
				continue
			}
			if typ.starts_with('[') {
				p.cgen.consts << p.table.cgen_name_type_pair(name, typ) + ' = $p.cgen.cur_line;'
			}
			else {
				p.cgen.consts << p.table.cgen_name_type_pair(name, typ) + ';'
				// println('adding to init "$name"')
				p.cgen.consts_init << '$name = $p.cgen.cur_line;'
			}
			p.cgen.resetln('')
		}
		p.fgen_nl()
	}
	p.fmt_dec()
	p.check(.rpar)
	p.inside_const = false
	p.fgen_nl()
	p.fgen_nl()
}

// `type myint int`
// `type onclickfn fn(voidptr) int`
fn (p mut Parser) type_decl() {
	is_pub := p.tok == .key_pub
	if is_pub {
		p.next()
		p.fspace()
	}
	p.check(.key_type)
	p.fspace()
	mut name := p.check_name()
	p.fspace()
	// V used to have 'type Foo struct', many Go users might use this syntax
	if p.tok == .key_struct {
		p.error('use `struct $name {` instead of `type $name struct {`')
	}
	is_sum := p.tok == .assign
	if is_sum {
		p.next()
	}
	mut parent := Type{}
	// Sum type
	//is_sum := p.tok == .pipe
	if is_sum {
		if !p.builtin_mod && p.mod != 'main' {
			name = p.prepend_mod(name)
		}
		// Register the first child  (name we already parsed)
		/*
		p.table.register_type(Type{
			parent: name
			name: parent.name // yeah it's not a parent here
			mod: p.mod
			is_public: is_pub
		})
		*/
		// Register the rest of them
		mut idx := 0
		mut done := false
		for  {//p.tok == .pipe {
			idx++
			//p.next()
			child_type_name := p.check_name()
			//println('$idx $child_type_name')
			if p.tok != .pipe {
				done = true
			}
			if p.pass == .main {
				// Update the type's parent
				//println('child=$child_type_name parent=$name')
				mut t := p.find_type(child_type_name)
				if t.name == '' {
					p.error('qunknown type `$child_type_name`')
				}
				t.parent = name
				p.table.rewrite_type(t)
				p.cgen.consts << '#define SumType_$child_type_name $idx // DEF2'
			}
			if done {
				break
			}
			p.check(.pipe)
		}
		if p.pass == .decl {
			p.table.sum_types << name
			println(p.table.sum_types)
		}
		// Register the actual sum type
		println('registering sum $name')
		p.table.register_type(Type{
			name: name
			mod: p.mod
			cat: .alias
			is_public: is_pub
		})
		p.gen_typedef('typedef struct {
void* obj;
int typ;
} $name;
')
	}
	else {

		parent = p.get_type2()
	}
	nt_pair := p.table.cgen_name_type_pair(name, parent.name)
	// TODO dirty C typedef hacks for DOOM
	// Unknown type probably means it's a struct, and it's used before the struct is defined,
	// so specify "struct"
	_struct := if parent.cat != .array && parent.cat != .func && !p.table.known_type(parent.name) { 'struct' } else { '' }
	if !is_sum {
		p.gen_typedef('typedef $_struct $nt_pair; //type alias name="$name" parent=`$parent.name`')
		p.table.register_type(Type{
			name: name
			parent: parent.name
			mod: p.mod
			cat: .alias
			is_public: is_pub
		})
	}
	if p.tok != .key_type {
		p.fspace()
	}
}

// current token is `(`
fn (p mut Parser) interface_method(field_name, receiver string) &Fn {
	mut method := &Fn{
		name: field_name
		is_interface: true
		is_method: true
		receiver_typ: receiver
	}
	// p.log('is interface. field=$field_name run=$p.pass')
	p.fn_args(mut method)
	prev_tok := p.prev_token()
	cur_tok := p.cur_tok()
	// No type on the same line, this method doesn't return a type, process next
	if prev_tok.line_nr != cur_tok.line_nr {
		method.typ = 'void'
	}
	else {
		method.typ = p.get_type() // method return type
		// p.fspace()
		p.fgen_nl()
	}
	return method
}

fn key_to_type_cat(tok TokenKind) TypeCategory {
	match tok {
		.key_interface {
			return .interface_
		}
		.key_struct {
			return .struct_
		}
		.key_union {
			return .union_
		}
		else {
		}}
	verror('Unknown token: $tok')
	return .builtin
}

// check_name checks for a name token and returns its literal
fn (p mut Parser) check_name() string {
	name := p.lit
	p.check(.name)
	return name
}

fn (p mut Parser) check_string() string {
	s := p.lit
	p.check(.str)
	return s
}

fn (p mut Parser) check_not_reserved() {
	if reserved_types[p.lit] {
		p.error("`$p.lit` can\'t be used as name")
	}
}

fn (p &Parser) strtok() string {
	if p.tok == .name {
		return p.lit
	}
	if p.tok == .number {
		return p.lit
	}
	if p.tok == .chartoken {
		if p.lit == '`' {
			return '`\\$p.lit`'
		}
		return '`$p.lit`'
	}
	if p.tok == .str {
		if p.lit.contains("'") && !p.lit.contains('"') {
			return '"$p.lit"'
		}
		else {
			return "'$p.lit'"
		}
	}
	if p.tok == .hash {
		return '#' + p.lit
	}
	res := p.tok.str()
	if res == '' {
		n := int(p.tok)
		return n.str()
	}
	return res
}

// same as check(), but adds a space to the formatter output
// TODO bad name
fn (p mut Parser) check_space(expected TokenKind) {
	p.fspace()
	p.check(expected)
	p.fspace()
}

fn (p mut Parser) check(expected TokenKind) {
	if p.tok != expected {
		// println('check()')
		s := 'syntax error: unexpected `${p.strtok()}`, expecting `${expected.str()}`'
		p.next()
		println('next token = `${p.strtok()}`')
		if p.pref.is_debug {
			print_backtrace()
		}
		p.error(s)
	}
	/*
	if expected == .rcbr {
		p.fmt_dec()
	}
	p.fgen(p.strtok())
	// vfmt: increase indentation on `{` unless it's `{}`
	if expected == .lcbr { //&& p.scanner.pos + 1 < p.scanner.text.len && p.scanner.text[p.scanner.pos + 1] != `}` {
		p.fgen_nl()
		p.fmt_inc()
	}
	*/

	p.next()
	// if p.scanner.line_comment != '' {
	// p.fgenln('// ! "$p.scanner.line_comment"')
	// p.scanner.line_comment = ''
	// }
}

[inline]
fn (p &Parser) first_pass() bool {
	return p.pass == .decl
}

// TODO return Type instead of string?
fn (p mut Parser) get_type() string {
	mut mul := false
	mut nr_muls := 0
	mut typ := ''
	// multiple returns
	if p.tok == .lpar {
		// p.warn('`()` are no longer necessary in multiple returns' +
		// '\nuse `fn foo() int, int {` instead of `fn foo() (int, int) {`')
		// if p.inside_tuple {p.error('unexpected (')}
		// p.inside_tuple = true
		p.check(.lpar)
		mut types := []string
		for {
			types << p.get_type()
			if p.tok != .comma {
				break
			}
			p.check(.comma)
		}
		p.check(.rpar)
		// p.inside_tuple = false
		typ = p.register_multi_return_stuct(types)
		return typ
	}
	// fn type
	if p.tok == .key_fn {
		mut f := Fn{
			name: '_'
			mod: p.mod
		}
		p.next()
		line_nr := p.scanner.line_nr
		p.fn_args(mut f)
		// Same line, it's a return type
		if p.scanner.line_nr == line_nr {
			if p.tok in [.name, .mul, .amp, .lsbr, .question, .lpar] {
				f.typ = p.get_type()
			}
			else {
				f.typ = 'void'
			}
			// println('fn return typ=$f.typ')
		}
		else {
			f.typ = 'void'
		}
		// Register anon fn type
		fn_typ := Type{
			name: f.typ_str() // 'fn (int, int) string'

			mod: p.mod
			func: f
		}
		p.table.register_type(fn_typ)
		return f.typ_str()
	}
	// arrays ([]int)
	mut arr_level := 0
	is_question := p.tok == .question
	if is_question {
		p.check(.question)
	}
	for p.tok == .lsbr {
		p.check(.lsbr)
		// [10]int
		if p.tok == .number {
			typ += '[$p.lit]'
			p.next()
		}
		else {
			arr_level++
		}
		p.check(.rsbr)
	}
	// map[string]int
	if !p.builtin_mod && p.tok == .name && p.lit == 'map' {
		p.next()
		p.check(.lsbr)
		key_type := p.check_name()
		if key_type != 'string' {
			p.error('maps only support string keys for now')
		}
		p.check(.rsbr)
		val_type := p.get_type() // p.check_name()
		typ = 'map_${stringify_pointer(val_type)}'
		p.register_map(typ)
		return typ
	}
	// ptr/ref
	mut warn := false
	for p.tok == .mul {
		if p.first_pass() {
			warn = true
		}
		mul = true
		nr_muls++
		p.check(.mul)
	}
	if p.tok == .amp {
		mul = true
		nr_muls++
		p.check(.amp)
	}
	// generic type check
	ti := p.generic_dispatch.inst
	if p.lit in ti.keys() {
		typ += ti[p.lit]
	}
	else {
		typ += p.lit
	}
	// C.Struct import
	if p.lit == 'C' && p.peek() == .dot {
		p.next()
		p.check(.dot)
		typ = p.lit
	}
	else {
		if warn && p.mod != 'ui' {
			p.warn('use `&Foo` instead of `*Foo`')
		}
		// Module specified? (e.g. gx.Image)
		if p.peek() == .dot {
			// try resolve full submodule
			if !p.builtin_mod && p.import_table.known_alias(typ) {
				mod := p.import_table.resolve_alias(typ)
				if mod.contains('.') {
					typ = mod_gen_name(mod)
				}
			}
			p.next()
			p.check(.dot)
			typ += '__$p.lit'
		}
		mut t := p.table.find_type(typ)
		// "typ" not found? try "mod__typ"
		if t.name == '' && !p.builtin_mod {
			// && !p.first_pass() {
			if !typ.contains('array_') && p.mod != 'main' && !typ.contains('__') && !typ.starts_with('[') {
				typ = p.prepend_mod(typ)
			}
			t = p.table.find_type(typ)
			if t.name == '' && !p.pref.translated && !p.first_pass() && !typ.starts_with('[') {
				println('get_type() bad type')
				// println('all registered types:')
				// for q in p.table.types {
				// println(q.name)
				// }
				mut t_suggest,tc_suggest := p.table.find_misspelled_type(typ, p, 0.50)
				if t_suggest.len > 0 {
					t_suggest = '. did you mean: ($tc_suggest) `$t_suggest`'
				}
				p.error('unknown type `$typ`$t_suggest')
			}
		}
		else if !t.is_public && t.mod != p.mod && !p.is_vgen && t.name != '' && !p.first_pass() {
			p.error('type `$t.name` is private')
		}
	}
	// generic struct init
	if p.peek() == .lt {
		p.next()
		p.check(.lt)
		typ = '${typ}_T'
		for {
			type_param := p.check_name()
			if type_param in p.generic_dispatch.inst {
				typ = '${typ}_' + p.generic_dispatch.inst[type_param]
			}
			if p.tok != .comma {
				break
			}
			p.check(.comma)
		}
		p.check(.gt)
		return typ
	}
	if typ == 'void' {
		p.error('unknown type `$typ`')
	}
	if mul {
		typ += strings.repeat(`*`, nr_muls)
	}
	// Register an []array type
	if arr_level > 0 {
		// p.log('ARR TYPE="$typ" run=$p.pass')
		// We come across "[]User" etc ?
		for i := 0; i < arr_level; i++ {
			typ = 'array_${stringify_pointer(typ)}'
		}
		p.register_array(typ)
	}
	p.next()
	if is_question {
		typ = 'Option_${stringify_pointer(typ)}'
		p.table.register_type_with_parent(typ, 'Option')
	}
	// Because the code uses * to see if it's a pointer
	if typ == 'byteptr' {
		return 'byte*'
	}
	if typ == 'voidptr' {
		// if !p.builtin_mod && p.mod != 'os' && p.mod != 'gx' && p.mod != 'gg' && !p.pref.translated {
		// p.error('voidptr can only be used in unsafe code')
		// }
		return 'void*'
	}
	/*
	TODO this is not needed?
	if typ.last_index('__') > typ.index('__') {
		p.error('2 __ in gettype(): typ="$typ"')
	}
	*/

	return typ
}

fn (p &Parser) print_tok() {
	if p.tok == .name {
		println(p.lit)
		return
	}
	if p.tok == .str {
		println('"$p.lit"')
		return
	}
	println(p.tok.str())
}

// statements() returns the type of the last statement
fn (p mut Parser) statements() string {
	// p.log('statements()')
	typ := p.statements_no_rcbr()
	if !p.inside_if_expr {
		p.genln('}')
	}
	// if p.fileis('if_expr') {
	// println('statements() ret=$typ line=$p.scanner.line_nr')
	// }
	return typ
}

fn (p mut Parser) statements_no_rcbr() string {
	p.open_scope()
	if !p.inside_if_expr {
		// p.genln('')
	}
	mut i := 0
	mut last_st_typ := ''
	for p.tok != .rcbr && p.tok != .eof {
		// println('stm: '+p.tok.str()+', next: '+p.peek().str())
		last_st_typ = p.statement(true)
		// println('last st typ=$last_st_typ')
		if !p.inside_if_expr {
			// p.genln('')// // end st tok= ${p.strtok()}')
			// p.fgenln('// ST')
			p.fgen_nl()
		}
		i++
		if i > 50000 {
			p.cgen.save()
			p.error('more than 50 000 statements in function `$p.cur_fn.name`')
		}
	}
	// p.next()
	if p.inside_if_expr {
		p.fspace()
	}
	p.check(.rcbr)
	// p.fmt_dec()
	p.close_scope()
	return last_st_typ
}

fn (p mut Parser) close_scope() {
	// println('close_scope level=$f.scope_level var_idx=$f.var_idx')
	// Move back `var_idx` (pointer to the end of the array) till we reach
	// the previous scope level.  This effectivly deletes (closes) current
	// scope.
	mut i := p.var_idx - 1
	for ; i >= 0; i-- {
		v := p.local_vars[i]
		if p.pref.autofree && (v.is_alloc || (v.is_arg && v.typ == 'string')) {
			// && !p.pref.is_test {
			p.free_var(v)
		}
		// if p.fileis('mem.v') {
		// println(v.name + ' $v.is_arg scope=$v.scope_level cur=$p.cur_fn.scope_level')}
		if v.scope_level != p.cur_fn.scope_level {
			// && !v.is_arg {
			break
		}
	}
	if p.cur_fn.defer_text.last() != '' {
		p.genln(p.cur_fn.defer_text.last())
		// p.cur_fn.defer_text[f] = ''
	}
	p.cur_fn.scope_level--
	p.cur_fn.defer_text = p.cur_fn.defer_text[..p.cur_fn.scope_level + 1]
	p.var_idx = i + 1
	// println('close_scope new var_idx=$f.var_idx\n')
}

fn (p mut Parser) free_var(v Var) {
	// Clean up memory, only do this if -autofree was passed for now
	// if p.fileis('mem.v') {println('free_var() $v.name')}
	// println(p.cur_fn.name)
	if p.cur_fn.name in ['add', 'clone', 'free'] {
		return
	}
	mut free_fn := 'free'
	if v.typ.starts_with('array_') {
		free_fn = 'v_array_free'
	}
	else if v.typ == 'string' {
		free_fn = 'v_string_free'
		// if p.fileis('str.v') {
		// println('freeing str $v.name')
		// }
		// continue
	}
	else if v.ptr || v.typ.ends_with('*') {
		free_fn = 'v_ptr_free'
		// continue
	}
	else {
		return
	}
	if p.returns {
		// Don't free a variable that's being returned
		if !v.is_returned && v.typ != 'FILE*' {
			// !v.is_c {
			// p.cgen.cur_line = '/* free */' + p.cgen.cur_line
			// p.cgen.set_placeholder(0, '/*free2*/')
			prev_line := p.cgen.lines[p.cgen.lines.len - 1]
			free := '$free_fn ($v.name); /* :) close_scope free $v.typ */'
			p.cgen.lines[p.cgen.lines.len - 1] = free + '\n' + prev_line
			// '$free_fn ($v.name); /* :) close_scope free $v.typ */\n' + prev_line
		}
	}
	else if p.mod != 'strings' {
		// && p.mod != 'builtin' {
		/*
		prev_line := p.cgen.lines[p.cgen.lines.len-1]
		free := '$free_fn ($v.name); /* :) close_scope free $v.typ */'
		p.cgen.lines[p.cgen.lines.len-1] = free + '\n' + prev_line
		*/
		// if p.fileis('mem.v') {println(v.name)}
		p.genln('$free_fn ($v.name); // close_scope free')
	}
}

fn (p mut Parser) genln(s string) {
	p.cgen.genln(s)
}

fn (p mut Parser) gen(s string) {
	p.cgen.gen(s)
}

// Generate V header from V source
fn (p mut Parser) statement(add_semi bool) string {
	p.expected_type = ''
	if p.returns {
		p.error('unreachable code')
	}
	// if !p.in_dispatch {
	p.cgen.is_tmp = false
	// }
	tok := p.tok
	mut q := ''
	match tok {
		.name {
			next := p.peek()
			// if p.pref.is_verbose {
			// println(next.str())
			// }
			// goto_label:
			if p.peek() == .colon {
				p.fmt_dec()
				label := p.check_name()
				p.fmt_inc()
				p.genln(label + ': ;')
				p.check(.colon)
				return ''
			}
			// `a := 777`
			else if p.peek() == .decl_assign || p.peek() == .comma {
				p.check_not_reserved()
				// p.log('var decl')
				p.var_decl()
			}
			// `_ = 777`
			else if p.lit == '_' && p.peek() == .assign {
				p.gen_blank_identifier_assign()
			}
			else {
				// panic and exit count as returns since they stop the function
				is_panic := p.lit == 'panic' || p.lit == 'exit'
				if is_panic {
					p.returns = true
				}
				// `a + 3`, `a(7)`, or just `a`
				q = p.bool_expression()
				// Fix "control reaches end of non-void function" error
				if is_panic && p.cur_fn.typ == 'bool' {
					p.genln(';\nreturn false;')
				}
			}
		}
		.key_goto {
			p.check(.key_goto)
			p.fspace()
			label := p.check_name()
			p.genln('goto $label;')
			return ''
		}
		.key_defer {
			p.defer_st()
			return ''
		}
		.hash {
			p.chash()
			return ''
		}
		.key_unsafe {
			p.next()
			p.inside_unsafe = true
			p.check(.lcbr)
			p.genln('{')
			p.statements()
			p.inside_unsafe = false
			// p.check(.rcbr)
		}
		.dollar {
			p.comp_time()
		}
		.key_if {
			p.if_statement(false, 0)
		}
		.key_for {
			p.for_st()
		}
		.key_switch {
			p.switch_statement()
		}
		.key_match {
			p.match_statement(false)
		}
		.key_mut, .key_static {
			p.var_decl()
		}
		.key_return {
			p.return_st()
		}
		.lcbr {
			// {} block
			// Do not allow {} block to start on the same line
			// to avoid e.g. `foo() {` instead of `if foo() {`
			if p.prev_token().line_nr == p.scanner.line_nr {
				p.genln('')
				p.error('{} block has to start on a new line')
			}
			p.check(.lcbr)
			if p.tok == .rcbr {
				p.error('empty statements block')
			}
			p.genln('{')
			p.statements()
			return ''
		}
		.key_continue {
			if p.for_expr_cnt == 0 {
				p.error('`continue` statement outside `for`')
			}
			p.genln('continue')
			p.check(.key_continue)
		}
		.key_break {
			if p.for_expr_cnt == 0 {
				p.error('`break` statement outside `for`')
			}
			p.genln('break')
			p.check(.key_break)
		}
		.key_go {
			p.go_statement()
		}
		.key_assert {
			p.assert_statement()
		}
		.key_asm {
			p.inline_asm()
		}
		else {
			// An expression as a statement
			typ := p.expression()
			if p.inside_if_expr {
			}
			else {
				p.genln('; ')
			}
			return typ
		}}
	// ? : uses , as statement separators
	if p.inside_if_expr && p.tok != .rcbr {
		p.gen(', ')
	}
	if add_semi && !p.inside_if_expr {
		p.genln(';')
	}
	return q
	// p.cgen.end_statement()
}
// is_map: are we in map assignment? (m[key] = val) if yes, dont generate '='
// this can be `user = ...`  or `user.field = ...`, in both cases `v` is `user`
fn (p mut Parser) assign_statement(v Var, ph int, is_map bool) {
	errtok := p.cur_tok_index()
	is_vid := p.fileis('vid') // TODO remove
	tok := p.tok
	// if !v.is_mut && !v.is_arg && !p.pref.translated && !v.is_global{
	if !v.is_mut && !p.pref.translated && !v.is_global && !is_vid {
		if v.is_arg {
			if p.cur_fn.args.len > 0 && p.cur_fn.args[0].name == v.name {
				println('make the receiver `$v.name` mutable:
fn ($v.name mut $v.typ) ${p.cur_fn.name}(...) {
')
			}
		}
		p.error('`$v.name` is immutable')
	}
	if !v.is_changed {
		p.mark_var_changed(v)
	}
	is_str := p.expected_type == 'string'
	is_ustr := p.expected_type == 'ustring'
	match tok {
		.assign {
			if !is_map && !p.is_empty_c_struct_init {
				p.gen(' = ')
			}
		}
		.plus_assign {
			if is_str && !p.is_js {
				expr := p.cgen.cur_line
				p.gen('= string_add($expr, ')
			}
			else if is_ustr {
				p.gen('= ustring_add($v.name, ')
			}
			else {
				next := p.peek_token()
				if next.tok == .number && next.lit == '1' {
					p.error('use `++` instead of `+= 1`')
				}
				p.gen(' += ')
			}
		}
		else {
			p.gen(' ' + p.tok.str() + ' ')
		}}
	p.fspace()
	p.next()
	p.fspace()
	pos := p.cgen.cur_line.len
	p.is_var_decl = true
	expr_type := p.bool_expression()
	p.is_var_decl = false
	// if p.expected_type.starts_with('array_') {
	// p.warn('expecting array got $expr_type')
	// }
	if expr_type == 'void' {
		_,fn_name := p.is_expr_fn_call(p.token_idx - 3)
		p.error_with_token_index('${fn_name}() $err_used_as_value', p.token_idx - 2)
	}
	// Allow `num = 4` where `num` is an `?int`
	if p.assigned_type.starts_with('Option_') && expr_type == parse_pointer(p.assigned_type['Option_'.len..]) {
		expr := p.cgen.cur_line[pos..]
		left := p.cgen.cur_line[..pos]
		typ := parse_pointer(expr_type.replace('Option_', ''))
		p.cgen.resetln(left + 'opt_ok(($typ[]){ $expr }, sizeof($typ))')
	}
	else if expr_type.starts_with('Option_') && p.assigned_type == parse_pointer(expr_type['Option_'.len..]) && p.tok == .key_orelse {
		line := p.cgen.cur_line
		vname := line[..pos].replace('=', '') // TODO cgen line hack
		if idx:=line.index('='){
			p.cgen.resetln(line.replace(line[..idx + 1], ''))
			p.gen_handle_option_or_else(expr_type, vname, ph)
		}
	}
	else if expr_type[0] == `[` {
		// assignment to a fixed_array `mut a:=[3]int a=[1,2,3]!!`
		expr := p.cgen.cur_line[pos..].all_after('{').all_before('}') // TODO cgen line hack
		left := p.cgen.cur_line[..pos].all_before('=')
		cline_pos := p.cgen.cur_line[pos..]
		etype := cline_pos.all_before(' {')
		if p.assigned_type != p.expected_type {
			p.error_with_token_index('incompatible types: $p.assigned_type != $p.expected_type', errtok)
		}
		p.cgen.resetln('memcpy( (& $left), ($etype{$expr}), sizeof( $left ) );')
	}
	// check type for <<= >>= %= ^= &= |=
	else if tok in [.left_shift_assign, .righ_shift_assign, .mod_assign, .xor_assign, .and_assign, .or_assign] {
		if !is_integer_type(p.assigned_type) {
			p.error_with_token_index('cannot use ${tok.str()} assignment operator on non-integer type `$p.assigned_type`', errtok)
		}
		if !is_integer_type(expr_type) {
			p.error_with_token_index('cannot use non-integer type `$expr_type` as ${tok.str()} argument', errtok)
		}
	}
	else if !p.builtin_mod && !p.check_types_no_throw(expr_type, p.assigned_type) {
		t := p.table.find_type(p.assigned_type)
		if t.cat == .enum_ && t.is_flag {
			p.error_with_token_index(err_modify_bitfield, errtok)
		}
		p.error_with_token_index('cannot use type `$expr_type` as type `$p.assigned_type` in assignment', errtok)
	}
	if (is_str || is_ustr) && tok == .plus_assign && !p.is_js {
		p.gen(')')
	}
	// p.assigned_var = ''
	p.assigned_type = ''
	if !v.is_used {
		p.mark_var_used(v)
	}
}

fn (p mut Parser) var_decl() {
	p.is_alloc = false
	is_mut := p.tok == .key_mut || p.prev_tok == .key_for
	is_static := p.tok == .key_static
	if p.tok == .key_mut {
		p.check(.key_mut)
		p.fspace()
	}
	if p.tok == .key_static {
		p.check(.key_static)
		p.fspace()
	}
	mut var_token_idxs := [p.cur_tok_index()]
	mut var_mut := [is_mut] // add first var mut
	mut var_names := [p.check_name()] // add first variable
	p.scanner.validate_var_name(var_names[0])
	mut new_vars := 0
	if var_names[0] != '_' && !p.known_var(var_names[0]) {
		new_vars++
	}
	// more than 1 vars (multiple returns)
	for p.tok == .comma {
		p.check(.comma)
		if p.tok == .key_mut {
			p.check(.key_mut)
			p.fspace()
			var_mut << true
		}
		else {
			var_mut << false
		}
		var_token_idxs << p.cur_tok_index()
		var_name := p.check_name()
		p.scanner.validate_var_name(var_name)
		if var_name != '_' && !p.known_var(var_name) {
			new_vars++
		}
		var_names << var_name
	}
	is_assign := p.tok == .assign
	is_decl_assign := p.tok == .decl_assign
	if is_assign {
		p.check_space(.assign) // =
	}
	else if is_decl_assign {
		p.check_space(.decl_assign) // :=
	}
	else {
		p.error('expected `=` or `:=`')
	}
	// all vars on left of `:=` already defined (or `_`)
	if is_decl_assign && var_names.len == 1 && var_names[0] == '_' {
		p.error_with_token_index('use `=` instead of `:=`', var_token_idxs.last())
	}
	p.var_decl_name = if var_names.len > 1 { '_V_mret_${p.token_idx}_' + var_names.join('_') } else { var_names[0] }
	t := p.gen_var_decl(p.var_decl_name, is_static)
	if t == 'void' {
		_,fn_name := p.is_expr_fn_call(p.token_idx - 3)
		p.error_with_token_index('${fn_name}() $err_used_as_value', p.token_idx - 2)
	}
	mut var_types := [t]
	// multiple returns types
	if var_names.len > 1 {
		var_types = t.replace('_V_MulRet_', '').replace('_PTR_', '*').split('_V_')
	}
	// mismatched number of return & assignment vars
	if var_names.len != var_types.len {
		mr_fn := p.cgen.cur_line.find_between('=', '(').trim_space()
		p.error_with_token_index('assignment mismatch: ${var_names.len} variables but `$mr_fn` returns $var_types.len values', var_token_idxs.last())
	}
	for i, var_name in var_names {
		var_token_idx := var_token_idxs[i]
		var_is_mut := var_mut[i]
		var_type := var_types[i]
		known_var := p.known_var(var_name)
		if var_name == '_' {
			if var_is_mut {
				p.error_with_token_index('`mut` has no effect here', var_token_idx - 1)
			}
			continue
		}
		// println('var decl tok=${p.strtok()} name=type=$var_name type=$var_type ismut=$var_is_mut')
		// var decl, already exists (shadowing is not allowed)
		// Don't allow declaring a variable with the same name. Even in a child scope
		// if var_names.len == 1 && !p.builtin_mod && known_var {
		if is_decl_assign && known_var {
			p.error_with_token_index('redefinition of `$var_name`', var_token_idx)
		}
		// mut specified with assignment
		// if /*is_assign && implicit*/ known_var && var_is_mut {
		if known_var && var_is_mut {
			p.error_with_token_index('cannot specify mutability for existing var `$var_name`, only for new vars', var_token_idx)
		}
		// assignment, but var does not exist
		if is_assign && !known_var {
			suggested := p.find_misspelled_local_var(var_name, 50)
			if suggested != '' {
				p.error_with_token_index('undefined: `$var_name`. did you mean:$suggested', var_token_idx)
			}
			p.error_with_token_index('undefined: `$var_name`.', var_token_idx)
		}
		if var_name.len > 1 && contains_capital(var_name) {
			p.error_with_token_index('variable names cannot contain uppercase letters, use snake_case instead', var_token_idx)
		}
		// multiple return
		if var_names.len > 1 {
			p.gen(';\n')
			// assigment
			// if !p.builtin_mod && known_var {
			if known_var {
				v := p.find_var(var_name) or {
					p.error_with_token_index('cannot find `$var_name`', var_token_idx)
					break
				}
				p.check_types_with_token_index(var_type, v.typ, var_token_idx)
				if !v.is_mut {
					p.error_with_token_index('`$v.name` is immutable', var_token_idx)
				}
				p.mark_var_used(v)
				p.mark_var_changed(v)
				p.gen('$var_name = ${p.var_decl_name}.var_$i')
				continue
			}
			// declaration
			p.gen('$var_type $var_name = ${p.var_decl_name}.var_$i')
		}
		// Function bodies are always parsed once in the second pass, but
		// script programs are parsed in the first pass, need to handle that.
		if p.pass == .main {
			p.register_var(Var{
				name: var_name
				typ: var_type
				is_mut: var_is_mut
				is_alloc: p.is_alloc || var_type.starts_with('array_')
				line_nr: p.tokens[var_token_idx].line_nr
				token_idx: var_token_idx
			})
		}
		// if p.fileis('str.v') {
		// if p.is_alloc { println('REG VAR IS ALLOC $name') }
		// }
	}
	p.var_decl_name = ''
	p.is_empty_c_struct_init = false
}

fn (p mut Parser) get_struct_type(name_ string, is_c bool, is_ptr bool) string {
	mut name := name_
	if is_ptr {
		name += '*' // `&User{}` => type `User*`
	}
	if name in reserved_type_param_names {
		p.warn('name `$name` is reserved for type parameters')
	}
	p.is_c_struct_init = is_c
	return p.struct_init(name)
}

fn (p mut Parser) get_var_type(name string, is_ptr bool, deref_nr int) string {
	v := p.find_var_check_new_var(name) or {
		return ''
	}
	if is_ptr {
		p.gen('&')
	}
	else if deref_nr > 0 {
		for _ in 0 .. deref_nr {
			p.gen('*')
		}
	}
	/*
	if p.pref.autofree && v.typ == 'string' && v.is_arg &&
		p.assigned_type == 'string' {
		p.warn('setting moved ' + v.typ)
		p.mark_arg_moved(v)
	}
	*/

	mut typ := p.var_expr(v)
	// *var
	if deref_nr > 0 {
		/*
		if !p.inside_unsafe  && !p.pref.building_v && p.mod != 'os' {
			p.error('dereferencing can only be done inside an `unsafe` block')
		}
		*/
		if !typ.contains('*') && !typ.ends_with('ptr') {
			println('name="$name", t=$v.typ')
			p.error('dereferencing requires a pointer, but got `$typ`')
		}
		for _ in 0 .. deref_nr {
			typ = typ.replace_once('ptr', '') // TODO
			typ = typ.replace_once('*', '') // TODO
		}
	}
	// &var
	else if is_ptr {
		typ += '*'
	}
	if p.inside_return_expr {
		// println('marking $v.name returned')
		p.mark_var_returned(v)
		// v.is_returned = true // TODO modifying a local variable
		// that's not used afterwards, this should be a compilation
		// error
	}
	return typ
}

fn (p mut Parser) get_const_type(name string, is_ptr bool) string {
	c := p.table.find_const(name) or {
		return ''
	}
	if is_ptr && !c.is_global {
		p.error('cannot take the address of constant `$c.name`')
	}
	else if is_ptr && c.is_global {
		// c.ptr = true
		p.gen('& /*const*/ ')
	}
	if !c.is_public && c.mod != p.mod {
		p.warn('constant `$c.name` is private')
	}
	mut typ := p.var_expr(c)
	if is_ptr {
		typ += '*'
	}
	return typ
}

fn (p mut Parser) get_c_func_type(name string) string {
	f := Fn{
		name: name
		is_c: true
	}
	p.is_c_fn_call = true
	p.fn_call(mut f, 0, '', '')
	p.is_c_fn_call = false
	// C functions must be defined with `C.fn_name() fn_type`
	cfn := p.table.find_fn(name) or {
		// Is the user trying to do `var := C.foo()` or `bar(C.foo())`
		// without declaring `foo`?
		// Do not allow it.
		if !name.starts_with('gl') && !name.starts_with('glad') {
			p.error('undefined C function `$f.name`\n' + 'define it with `fn C.${name}([args]) [return_type]`')
		}
		return 'void*'
	}
	// println("C fn $name has type $cfn.typ")
	return cfn.typ
}

fn (p mut Parser) undefined_error(name string, orig_name string) {
	name_dotted := mod_gen_name_rev(name.replace('__', '.'))
	// check for misspelled function / variable / module / type
	suggested := p.identify_typo(name)
	if suggested.len != 0 {
		p.error('undefined: `$name_dotted`. did you mean:\n$suggested\n')
	}
	// If orig_name is a mod, then printing undefined: `mod` tells us nothing
	if p.table.known_mod(orig_name) || p.import_table.known_alias(orig_name) {
		p.error('undefined: `$name_dotted` (in module `$orig_name`)')
	}
	else if orig_name in reserved_type_param_names {
		p.error('the letter `$orig_name` is reserved for type parameters')
	}
	p.error('undefined: `$orig_name`')
}

fn (p mut Parser) var_expr(v Var) string {
	// p.log('\nvar_expr() v.name="$v.name" v.typ="$v.typ"')
	// println('var expr is_tmp=$p.cgen.is_tmp\n')
	if !v.is_const {
		p.mark_var_used(v)
		// `C.foo(&var)` means that `var` is changed. Mark it as changed
		// to avoid `var was declared as mutable but was never changed` errors.
		if p.calling_c && !v.is_changed {
			// println('marking C var changed: $v.name')
			p.mark_var_changed(v)
		}
	}
	fn_ph := p.cgen.add_placeholder()
	p.expr_var = v
	p.gen(p.table.var_cgen_name(v.name))
	p.next()
	mut typ := v.typ
	// Function pointer?
	if p.base_type(typ).starts_with('fn ') && p.tok == .lpar {
		T := p.table.find_type(p.base_type(typ))
		p.gen('(')
		p.fn_call_args(mut T.func, [])
		p.gen(')')
		typ = T.func.typ
	}
	// users[0].name
	if p.tok == .lsbr {
		typ = p.index_expr(typ, fn_ph)
		if p.base_type(typ).starts_with('fn ') && p.tok == .lpar {
			T := p.table.find_type(p.base_type(typ))
			p.gen('(')
			p.fn_call_args(mut T.func, [])
			p.gen(')')
			typ = T.func.typ
		}
	}
	// a.b.c().d chain
	// mut dc := 0
	for p.tok == .dot {
		if p.peek() == .key_select {
			p.next()
			return p.select_query(fn_ph)
		}
		if typ == 'pg__DB' && !p.fileis('pg.v') && p.peek() == .name {
			name := p.tokens[p.token_idx].lit
			if !name.contains('exec') && !name.starts_with('q_') {
				p.next()
				if name == 'insert' {
					p.insert_query(fn_ph)
				}
				else if name == 'update' {
					p.update_query(fn_ph)
				}
				return 'void'
			}
		}
		// println('dot #$dc')
		typ = p.dot(typ, fn_ph)
		// p.log('typ after dot=$typ')
		// print('tok after dot()')
		// p.print_tok()
		// dc++
		if p.tok == .lsbr {
			typ = p.index_expr(typ, fn_ph)
		}
	}
	// `a++` and `a--`
	if p.tok == .inc || p.tok == .dec {
		if !v.is_mut && !p.pref.translated {
			p.error('`$v.name` is immutable')
		}
		if !v.is_changed {
			p.mark_var_changed(v)
		}
		if typ != 'int' && !typ.contains('*') {
			if !p.pref.translated && !is_number_type(typ) {
				p.error('cannot ++/-- value of type `$typ`')
			}
		}
		p.gen(p.tok.str())
		p.next() // ++/--
		// allow `a := c++` in translated code TODO remove once c2v handles this
		if p.pref.translated {
			// return p.index_expr(typ, fn_ph)
		}
		else {
			return typ
		}
	}
	typ = p.index_expr(typ, fn_ph)
	// TODO hack to allow `foo.bar[0] = 2`
	if p.tok == .dot {
		for p.tok == .dot {
			typ = p.dot(typ, fn_ph)
		}
		typ = p.index_expr(typ, fn_ph)
	}
	return typ
}

// user.name => `str_typ` is `User`
// user.company.name => `str_typ` is `Company`
fn (p mut Parser) dot(str_typ_ string, method_ph int) string {
	// if p.fileis('orm_test') {
	// println('ORM dot $str_typ')
	// }
	str_typ := str_typ_
	p.check(.dot)
	is_variadic_arg := str_typ.starts_with('varg_')
	typ := p.find_type(str_typ)
	if typ.name.len == 0 {
		p.error('dot(): cannot find type `$str_typ`')
	}
	// foo.$action()
	if p.tok == .dollar {
		p.comptime_method_call(typ)
		return 'void'
	}
	field_name := p.lit
	if field_name == 'filter' && str_typ.starts_with('array_') {
		p.gen_array_filter(str_typ, method_ph)
		return str_typ
	}
	else if field_name == 'map' && str_typ.starts_with('array_') {
		return p.gen_array_map(str_typ, method_ph)
	}
	fname_tidx := p.cur_tok_index()
	// p.log('dot() field_name=$field_name typ=$str_typ')
	// if p.fileis('main.v') {
	// println('dot() field_name=$field_name typ=$str_typ prev_tok=${prev_tok.str()}')
	// }
	has_field := p.table.type_has_field(typ, p.table.var_cgen_name(field_name))
	mut has_method := p.table.type_has_method(typ, field_name)
	if is_variadic_arg && field_name == 'len' {
		p.gen('->$field_name')
		p.next()
		return 'int'
	}
	// generate `.str()`
	if !has_method && field_name == 'str' && typ.name.starts_with('array_') {
		p.gen_array_str(typ)
		has_method = true
	}
	if !typ.is_c && !p.is_c_fn_call && !has_field && !has_method && !p.first_pass() {
		if typ.name.starts_with('Option_') {
			opt_type := typ.name[7..].replace('ptr_', '&')
			p.error('unhandled option type: `?$opt_type`')
		}
		// println('error in dot():')
		// println('fields:')
		// for field in typ.fields {
		// println(field.name)
		// }
		// println('methods:')
		// for field in typ.methods {
		// println(field.name)
		// }
		// println('str_typ=="$str_typ"')
		p.error_with_token_index('type `$typ.name` has no field or method `$field_name`', fname_tidx)
	}
	mut dot := '.'
	if str_typ.ends_with('*') || str_typ == 'FT_Face' {
		// TODO fix C ptr typedefs
		dot = dot_ptr
	}
	// field
	if has_field {
		struct_field := if typ.name != 'Option' { p.table.var_cgen_name(field_name) } else { field_name }
		field := p.table.find_field(typ, struct_field) or {
			p.error_with_token_index('missing field: $struct_field in type $typ.name', fname_tidx)
			exit(1)
		}
		if !field.is_mut && !p.has_immutable_field {
			p.has_immutable_field = true
			p.first_immutable_field = field
		}
		// Is the next token `=`, `+=` etc?  (Are we modifying the field?)
		next := p.peek()
		modifying := next.is_assign() || next == .inc || next == .dec || (field.typ.starts_with('array_') && next == .left_shift)
		if !p.builtin_mod && !p.pref.translated && modifying && p.has_immutable_field {
			f := p.first_immutable_field
			p.error_with_token_index('cannot modify immutable field `$f.name` (type `$f.parent_fn`)\n' + 'declare the field with `mut:`
struct $f.parent_fn {
mut:
	$f.name $f.typ
}
', fname_tidx)
		}
		// Don't allow `arr.data`
		if field.access_mod == .private && !p.builtin_mod && !p.pref.translated && p.mod != typ.mod && !p.is_vgen {
			// println('$typ.name :: $field.name ')
			// println(field.access_mod)
			p.error_with_token_index('cannot refer to unexported field `$struct_field` (type `$typ.name`)\n' + 'declare the field with `pub:`
struct $typ.name {
pub:
	$struct_field $field.typ
}
', fname_tidx)
		}
		base := p.base_type(field.typ)
		if base.starts_with('fn ') && p.peek() == .lpar {
			tmp_typ := p.table.find_type(base)
			mut f := tmp_typ.func
			p.gen('$dot$field.name')
			p.gen('(')
			p.check(.name)
			p.fn_call_args(mut f, [])
			p.gen(')')
			return f.typ
		}
		p.gen(dot + struct_field)
		p.next()
		return field.typ
	}
	// method
	mut method := p.table.find_method(typ, field_name) or {
		p.error_with_token_index('could not find method `$field_name`', fname_tidx) // should never happen
		exit(1)
	}
	p.fn_call(mut method, method_ph, '', str_typ)
	// optional method call `a.method() or {}`, no return assignment
	is_or_else := p.tok == .key_orelse
	if is_or_else {
		p.fspace()
	}
	if p.tok == .question {
		// `files := os.ls('.')?`
		return p.gen_handle_question_suffix(method, method_ph)
	}
	else if !p.is_var_decl && is_or_else {
		method.typ = p.gen_handle_option_or_else(method.typ, '', method_ph)
	}
	else if !p.is_var_decl && !is_or_else && !p.inside_return_expr && method.typ.starts_with('Option_') {
		opt_type := method.typ[7..].replace('ptr_', '&')
		p.error('unhandled option type: `?$opt_type`')
	}
	// Methods returning `array` should return `array_string` etc
	if method.typ == 'array' && typ.name.starts_with('array_') {
		return typ.name
	}
	// Array methods returning `voidptr` (like `last()`) should return element type
	if method.typ == 'void*' && typ.name.starts_with('array_') {
		return parse_pointer(typ.name[6..])
	}
	// if false && p.tok == .lsbr {
	// if is_indexer {
	// return p.index_expr(method.typ, method_ph)
	// }
	if method.typ.ends_with('*') {
		p.is_alloc = true
	}
	return method.typ
}

enum IndexType {
	noindex
	str
	map
	array
	array0
	fixed_array
	ptr
}

fn get_index_type(typ string) IndexType {
	if typ.starts_with('map_') {
		return .map
	}
	if typ == 'string' {
		return .str
	}
	if typ.starts_with('array_') || typ == 'array' {
		return .array
	}
	if typ == 'byte*' || typ == 'byteptr' || typ.contains('*') {
		return .ptr
	}
	if typ[0] == `[` {
		return .fixed_array
	}
	return .noindex
}

fn (p mut Parser) index_expr(typ_ string, fn_ph int) string {
	mut typ := typ_
	// a[0]
	v := p.expr_var
	// if p.fileis('fn_test.v') {
	// println('index expr typ=$typ')
	// println(v.name)
	// }
	is_variadic_arg := typ.starts_with('varg_')
	is_map := typ.starts_with('map_')
	is_str := typ == 'string'
	is_arr0 := typ.starts_with('array_')
	is_arr := is_arr0 || typ == 'array'
	is_ptr := typ == 'byte*' || typ == 'byteptr' || typ.contains('*')
	mut is_slice := false
	is_indexer := p.tok == .lsbr
	mut close_bracket := false
	index_error_tok_pos := p.token_idx
	if is_indexer {
		is_fixed_arr := typ[0] == `[`
		if !is_str && !is_arr && !is_map && !is_ptr && !is_fixed_arr && !is_variadic_arg {
			p.error('invalid operation: type `$typ` does not support indexing')
		}
		p.check(.lsbr)
		// Get element type (set `typ` to it)
		if is_str {
			typ = 'byte'
			// Direct faster access to .str[i] in builtin modules
			if p.builtin_mod || p.pref.is_bare {
				p.gen('.str[')
				close_bracket = true
			}
			else {
				// Bounds check everywhere else
				p.gen(', ')
			}
		}
		if is_variadic_arg {
			typ = typ[5..]
		}
		if is_fixed_arr {
			// `[10]int` => `int`, `[10][3]int` => `[3]int`
			if typ.contains('][') {
				pos := typ.index_after('[', 1)
				typ = typ[pos..]
			}
			else {
				typ = typ.all_after(']')
			}
			p.gen('[')
			close_bracket = true
		}
		else if is_ptr && !is_variadic_arg {
			// typ = 'byte'
			typ = typ.replace('*', '')
			// modify(mut []string) fix
			if !is_arr && !is_map {
				p.gen('[/*ptr!*/')
				close_bracket = true
			}
		}
		if is_arr {
			if is_arr0 {
				typ = parse_pointer(typ[6..])
			}
			p.gen_array_at(typ, is_arr0, fn_ph)
		}
		// map is tricky
		// need to replace "m[key] = val" with "tmp = val; map_set(&m, key, &tmp)"
		// need to replace "m[key]"       with "tmp = val; map_get(&m, key, &tmp)"
		// can only do that later once we know whether there's an "=" or not
		if is_map {
			typ = typ.replace('map_', '')
			typ = parse_pointer(typ)
			if typ == 'map' {
				typ = 'void*'
			}
			p.gen(',')
		}
		// expression inside [ ]
		if is_arr || is_str {
			// [2..
			if p.tok != .dotdot {
				index_pos := p.cgen.cur_line.len
				T := p.table.find_type(p.expression())
				// Allows only i8-64 and byte-64 to be used when accessing an array
				if T.parent != 'int' && T.parent != 'u32' {
					p.check_types(T.name, 'int')
				}
				if p.cgen.cur_line[index_pos..].replace(' ', '').int() < 0 {
					p.error('cannot access negative array index')
				}
			}
			// [..
			else {
				p.gen('0')
			}
			if p.tok == .dotdot {
				if is_arr {
					typ = 'array_' + stringify_pointer(typ)
				}
				else if is_str {
					typ = 'string'
				}
				else {
					p.error('slicing is supported by arrays and strings only')
				}
				is_slice = true
				p.next()
				p.gen(',')
				// ..4]
				if p.tok != .rsbr {
					p.check_types(p.expression(), 'int')
					p.gen(', false')
				}
				// ..]
				else {
					p.gen('-1, true')
				}
			}
		}
		else {
			T := p.table.find_type(p.expression())
			// TODO: Get the key type of the map instead of only string.
			if is_map && T.parent != 'string' {
				p.check_types(T.name, 'string')
			}
		}
		p.check(.rsbr)
		// if (is_str && p.builtin_mod) || is_ptr || is_fixed_arr && ! (is_ptr && is_arr) {
		if close_bracket {
			p.gen(']/*r$typ $v.is_mut*/')
		}
		p.expr_var = v
	}
	// accessing variadiac args
	if is_variadic_arg {
		// TODO: why was this here?
		// if p.calling_c {
		// p.error('you cannot currently pass varg to a C function.')
		// }
		if is_indexer {
			l := p.cgen.cur_line.trim_space()
			idx := l.last_index(' ') or {
				panic('idx')
			}
			index_val := l[idx..].trim_space()
			p.cgen.resetln(l[..fn_ph])
			p.table.varg_access << VargAccess{
				fn_name: p.cur_fn.name
				tok_idx: index_error_tok_pos
				index: index_val.int()
			}
			p.cgen.set_placeholder(fn_ph, '${v.name}->args[$index_val]')
			return typ
		}
	}
	// `m[key] = val`
	// TODO move this from index_expr()
	if (p.tok == .assign && !p.is_sql) || p.tok.is_assign() {
		if is_indexer && is_str && !p.builtin_mod {
			p.error('strings are immutable')
		}
		p.assigned_type = typ
		p.expected_type = typ
		assign_pos := p.cgen.cur_line.len
		is_cao := p.tok != .assign
		p.assign_statement(v, fn_ph, is_indexer && (is_map || is_arr))
		// `m[key] = val`
		if is_indexer && (is_map || is_arr) {
			p.gen_array_set(typ, is_ptr, is_map, fn_ph, assign_pos, is_cao)
		}
		return typ
	}
	// else if p.pref.is_verbose && p.assigned_var != '' {
	// p.error('didnt assign')
	// }
	// `m[key]`. no =, just a getter
	else if (is_map || is_arr || (is_str && !p.builtin_mod)) && is_indexer {
		typ = parse_pointer(typ)
		p.index_get(typ, fn_ph, IndexConfig{
			is_arr: is_arr
			is_map: is_map
			is_ptr: is_ptr
			is_str: is_str
			is_slice: is_slice
		})
	}
	// else if is_arr && is_indexer{}
	return typ
}

struct IndexConfig {
	is_map   bool
	is_str   bool
	is_ptr   bool
	is_arr   bool
	is_arr0  bool
	is_slice bool
}

// for debugging only
fn (p &Parser) fileis(s string) bool {
	return filepath.filename(p.scanner.file_path).contains(s)
}

// in and dot have higher priority than `!`
fn (p mut Parser) indot_expr() string {
	ph := p.cgen.add_placeholder()
	mut typ := p.term()
	if p.tok == .dot {
		for p.tok == .dot {
			typ = p.dot(typ, ph)
		}
	}
	// `a in [1, 2, 3]`
	// `key in map`
	if p.tok == .key_in {
		p.fspace()
		p.check(.key_in)
		p.expected_type = typ // this allows short enum syntax `foo in [.val1, .val2, .val3]`
		if p.tok == .lsbr {
			// a in [1,2,3] optimization => `a == 1 || a == 2 || a == 3`
			// avoids an allocation
			p.fspace()
			p.in_optimization(typ, ph)
			return 'bool'
		}
		p.fspace()
		p.gen('), ')
		arr_typ := p.expression()
		is_map := arr_typ.starts_with('map_')
		is_arr := arr_typ.starts_with('array_')
		if !is_arr && !is_map {
			p.error('`in` requires an array/map')
		}
		if is_arr && parse_pointer(arr_typ[6..]) != typ {
			p.error('bad element type: `$typ` in `$arr_typ`')
		}
		if is_map && typ != 'string' {
			p.error('bad element type: expecting `string`')
		}
		T := p.table.find_type(arr_typ)
		if !is_map && !T.has_method('contains') {
			p.error('$arr_typ has no method `contains`')
		}
		// `typ` is element's type
		if is_map {
			p.cgen.set_placeholder(ph, '(_IN_MAP( (')
		}
		else {
			p.cgen.set_placeholder(ph, '(_IN($typ, (')
		}
		p.gen('))')
		return 'bool'
	}
	return typ
}

// { user | name: 'new name' }
fn (p mut Parser) assoc() string {
	// println('assoc()')
	p.next()
	name := p.check_name()
	p.fspace()
	var := p.find_var_or_const(name) or {
		p.error('unknown variable `$name`')
		exit(1)
	}
	if !var.is_const {
		p.mark_var_used(var)
	}
	p.check(.pipe)
	p.fgen_nl()
	p.gen('($var.typ){')
	typ := p.table.find_type(var.typ)
	mut fields := []string // track the fields user is setting, the rest will be copied from the old object
	for p.tok != .rcbr {
		field := p.check_name()
		// if !typ.has_field(field) {
		f := typ.find_field(field) or {
			p.error('`$typ.name` has no field `$field`')
			exit(1)
		}
		fields << field
		p.gen('.$field = ')
		p.check(.colon)
		p.check_types(p.bool_expression(), f.typ)
		p.gen(',')
		if p.tok != .rcbr {
			p.check(.comma)
		}
		p.fgen_nl()
	}
	// Copy the rest of the fields
	for ffield in typ.fields {
		f := ffield.name
		if f in fields {
			continue
		}
		p.gen('.$f = ${var.name}.$f,')
	}
	p.check(.rcbr)
	p.gen('}')
	return var.typ
}

fn (p mut Parser) char_expr() {
	p.gen("\'$p.lit\'")
	p.next()
}

fn format_str(_str string) string {
	// TODO don't call replace 3 times for every string, do this in scanner.v
	mut str := _str.replace('"', '\\"')
	str = str.replace('\r\n', '\\n')
	str = str.replace('\n', '\\n')
	return str
}

// m := map[string]int{}
// m := { 'one': 1 }
fn (p mut Parser) map_init() string {
	// m := { 'one': 1, 'two': 2 }
	mut keys_gen := '' // (string[]){tos2("one"), tos2("two")}
	mut vals_gen := '' // (int[]){1, 2}
	mut val_type := '' // 'int'
	if p.tok == .lcbr {
		p.check(.lcbr)
		mut i := 0
		for {
			key := p.lit
			keys_gen += 'tos3("$key"), '
			p.check(.str)
			p.check(.colon)
			p.fspace()
			t,val_expr := p.tmp_expr()
			if i == 0 {
				val_type = t
			}
			i++
			if val_type != t {
				if !p.check_types_no_throw(val_type, t) {
					p.error('bad map element type `$val_type` instead of `$t`')
				}
			}
			vals_gen += '$val_expr, '
			if p.tok == .rcbr {
				p.fgen_nl()
				p.check(.rcbr)
				break
			}
			if p.tok == .comma {
				p.check(.comma)
			}
			p.fgen_nl()
		}
		p.gen('new_map_init($i, sizeof($val_type), ' + '(string[$i]){ $keys_gen }, ($val_type [$i]){ $vals_gen } )')
		typ := 'map_${stringify_pointer(val_type)}'
		p.register_map(typ)
		return typ
	}
	p.next()
	p.check(.lsbr)
	key_type := p.check_name()
	if key_type != 'string' {
		p.error('only string key maps allowed for now')
	}
	p.check(.rsbr)
	val_type = p.get_type() // / p.check_name()
	// if !p.table.known_type(val_type) {
	// p.error('map init unknown type "$val_type"')
	// }
	typ := 'map_${stringify_pointer(val_type)}'
	p.register_map(typ)
	p.gen('new_map(1, sizeof($val_type))')
	if p.tok == .lcbr {
		p.check(.lcbr)
		p.check(.rcbr)
		println('warning: $p.file_name:$p.scanner.line_nr ' + 'initializaing maps no longer requires `{}`')
	}
	return typ
}

// `nums := [1, 2, 3]`
fn (p mut Parser) array_init() string {
	expected_array_type := p.expected_type
	// if p.fileis('interface_') {
	// println('a exp='+p.expected_type)
	// }
	p.is_alloc = true
	p.check(.lsbr)
	mut is_integer := p.tok == .number // for `[10]int`
	// fixed length arrays with a const len: `nums := [N]int`, same as `[10]int` basically
	mut is_const_len := false
	if p.tok == .name && !p.inside_const {
		const_name := p.prepend_mod(p.lit)
		if p.table.known_const(const_name) {
			c := p.table.find_const(const_name) or {
				p.error('unknown const `$const_name`')
				exit(1)
			}
			if c.typ == 'int' && p.peek() == .rsbr {
				// && !p.inside_const {
				is_integer = true
				is_const_len = true
			}
			else {
				p.error('bad fixed size array const `$p.lit`')
			}
		}
	}
	lit := p.lit
	mut typ := ''
	new_arr_ph := p.cgen.add_placeholder()
	mut i := 0
	for p.tok != .rsbr {
		val_typ := p.bool_expression()
		// Get the type of the first expression
		if i == 0 {
			typ = val_typ
			// fixed width array initialization? (`arr := [20]byte`)
			if is_integer && p.tok == .rsbr && p.peek() == .name && p.cur_tok().line_nr == p.peek_token().line_nr {
				// there is no space between `[10]` and `byte`
				// if p.cur_tok().col + p.peek_token().lit.len == p.peek_token().col {
				if p.cur_tok().pos + p.peek_token().lit.len == p.peek_token().pos {
					p.check(.rsbr)
					// `[10]C.kevent` needs `struct `
					is_c := p.tok == .name && p.lit == 'C'
					if is_c {
						p.cgen.insert_before('struct ')
					}
					array_elem_typ := p.get_type()
					if !p.table.known_type(array_elem_typ) {
						p.error('bad type `$array_elem_typ`')
					}
					p.cgen.resetln('')
					// p.gen('{0}')
					p.is_alloc = false
					if is_const_len {
						return '[${mod_gen_name(p.mod)}__$lit]$array_elem_typ'
					}
					return '[$lit]$array_elem_typ'
				}
				else {
					p.check(.rsbr)
					typ = p.get_type()
					p.error('no space allowed between [$lit] and $typ')
				}
			}
		}
		if val_typ != typ {
			if !p.check_types_no_throw(val_typ, typ) {
				mut ok := false
				// `foo([cat, dog])` where foo is `fn foo([]Animal) {`
				// `expected_type` is `[]Animaler`
				if expected_array_type.ends_with('er') {
					if p.satisfies_interface(expected_array_type, typ, false) {
						ok = true
					}
				}
				if !ok {
					p.error('bad array element type `$val_typ` instead of `$typ`')
				}
			}
		}
		if p.tok != .rsbr && p.tok != .semicolon {
			p.gen(', ')
			line_nr := p.tok
			p.check(.comma)
			p.fspace_or_newline()
		}
		i++
		// Repeat (a = [0;5] )
		if i == 1 && p.tok == .semicolon {
			p.error('`[0 ; len]` syntax was removed. Use `[0].repeat(len)` instead')
		}
	}
	p.check(.rsbr)
	// type after `]`? (e.g. "[]string")
	exp_array := p.expected_type.starts_with('array_')
	if p.tok != .name && p.tok != .mul && p.tok != .lsbr && p.tok != .amp && i == 0 && !exp_array {
		p.error('specify array type: `[]typ` instead of `[]`')
	}
	if i == 0 && (p.tok == .name || p.tok == .mul || p.tok == .amp) && p.tokens[p.token_idx - 2].line_nr == p.tokens[p.token_idx - 1].line_nr {
		// TODO
		// vals.len == 0 {
		if exp_array {
			type_expected := p.expected_type[6..].replace('ptr_', '&')
			p.error('no need to specify the full array type here, use `[]` instead of `[]$type_expected`')
		}
		typ = p.get_type()
	}
	else if exp_array && i == 0 {
		// allow `known_array = []`
		typ = p.expected_type[6..]
	}
	// ! after array => no malloc and no copy
	no_alloc := p.tok == .not
	if no_alloc {
		p.next()
	}
	// [1,2,3]!! => [3]int{1,2,3}
	is_fixed_size := p.tok == .not
	if is_fixed_size {
		p.next()
		p.gen(' }')
		if !p.first_pass() {
			// If we are defining a const array, we don't need to specify the type:
			// `a = {1,2,3}`, not `a = (int[]) {1,2,3}`
			if p.inside_const {
				p.cgen.set_placeholder(new_arr_ph, '{')
			}
			else {
				p.cgen.set_placeholder(new_arr_ph, '($typ[]) {')
			}
		}
		return '[$i]$typ'
	}
	// if ptr {
	// typ += '_ptr"
	// }
	real := parse_pointer(typ)
	p.gen_array_init(real, no_alloc, new_arr_ph, i)
	typ = 'array_${stringify_pointer(typ)}'
	p.register_array(typ)
	return typ
}

// `f32(3)`
// tok is `f32` or `)` if `(*int)(ptr)`
fn (p mut Parser) get_tmp() string {
	p.tmp_cnt++
	return 'tmp$p.tmp_cnt'
}

fn (p mut Parser) get_tmp_counter() int {
	p.tmp_cnt++
	return p.tmp_cnt
}

fn (p mut Parser) assert_statement() {
	if p.first_pass() {
		return
	}
	p.check(.key_assert)
	p.fspace()
	tmp := p.get_tmp()
	p.gen('bool $tmp = ')
	p.check_types(p.bool_expression(), 'bool')
	nline := p.scanner.line_nr
	// TODO print "expected:  got" for failed tests
	filename := cescaped_path(p.file_path)
	cfname := p.cur_fn.name.replace('main__', '')
	sourceline := p.scanner.line(nline - 1).replace('"', "\'")
	if !p.pref.is_test {
		// an assert used in a normal v program. no fancy formatting
		p.genln(';\n
/// sline: "$sourceline"
if (!$tmp) {
	g_test_fails++;
	eprintln(tos3("${filename}:${p.scanner.line_nr}: FAILED: ${cfname}()"));
	eprintln(tos3("Source: $sourceline"));
   v_panic(tos3("An assertion failed."));
   exit(1);
} else {
	g_test_oks++;
}
')
		return
	}
	p.genln(';\n
if (!$tmp) {
 g_test_fails++;
 main__cb_assertion_failed(
    tos3("$filename"),
    $p.scanner.line_nr,
    tos3("$sourceline"),
    tos3("${p.cur_fn.name}()")
 );
 exit(1);
 // TODO
 // Maybe print all vars in a test function if it fails?
} else {
 g_test_oks++;
 main__cb_assertion_ok(
    tos3("$filename"),
    $p.scanner.line_nr,
    tos3("$sourceline"),
    tos3("${p.cur_fn.name}()")
 );
}

')
}

fn (p mut Parser) return_st() {
	p.check(.key_return)
	p.fspace()
	deferred_text := p.get_deferred_text()
	fn_returns := p.cur_fn.typ != 'void'
	if fn_returns {
		if p.tok == .rcbr {
			p.error('`$p.cur_fn.name` needs to return `$p.cur_fn.typ`')
		}
		ph := p.cgen.add_placeholder()
		p.inside_return_expr = true
		is_none := p.tok == .key_none
		p.expected_type = p.cur_fn.typ
		mut expr_type := p.bool_expression()
		// println('$p.cur_fn.name returns type $expr_type, should be $p.cur_fn.typ')
		mut types := []string
		mut mr_values := [p.cgen.cur_line[ph..].trim_space()]
		types << expr_type
		for p.tok == .comma {
			p.check(.comma)
			typ,expr := p.tmp_expr()
			types << typ
			mr_values << expr.trim_space()
		}
		mut cur_fn_typ_chk := p.cur_fn.typ
		// multiple returns
		if types.len > 1 {
			expr_type = types.join(',')
			cur_fn_typ_chk = cur_fn_typ_chk.replace('_V_MulRet_', '').replace('_PTR_', '*').replace('_V_', ',')
			mut ret_fields := ''
			for ret_val_idx, ret_val in mr_values {
				if ret_val_idx > 0 {
					ret_fields += ','
				}
				ret_fields += '.var_$ret_val_idx=${ret_val}'
			}
			p.cgen.resetln('($p.cur_fn.typ){$ret_fields}')
		}
		p.inside_return_expr = false
		// Automatically wrap an object inside an option if the function
		// returns an option:
		// `return val` => `return opt_ok(val)`
		if p.cur_fn.typ.ends_with(stringify_pointer(expr_type)) && !is_none && p.cur_fn.typ.starts_with('Option_') {
			tmp := p.get_tmp()
			ret := p.cgen.cur_line[ph..]
			typ := parse_pointer(expr_type.replace('Option_', ''))
			p.cgen.resetln('$expr_type $tmp = OPTION_CAST($expr_type)($ret);')
			p.genln(deferred_text)
			p.gen('return opt_ok(&$tmp, sizeof($typ))')
		}
		else {
			ret := p.cgen.cur_line[ph..]
			if deferred_text == '' || expr_type == 'void*' {
				// no defer{} necessary?
				if expr_type == '${p.cur_fn.typ}*' {
					p.cgen.resetln('return *$ret')
				}
				else {
					p.cgen.resetln('return $ret')
				}
			}
			else {
				tmp := p.get_tmp()
				p.cgen.resetln('$expr_type $tmp = $ret;\n')
				p.genln(deferred_text)
				p.genln('return $tmp;')
			}
		}
		p.check_types(expr_type, cur_fn_typ_chk)
	}
	else {
		// Don't allow `return val` in functions that don't return anything
		if p.tok == .name || p.tok == .number || p.tok == .str {
			p.error_with_token_index('function `$p.cur_fn.name` should not return a value', p.cur_fn.fn_name_token_idx)
		}
		p.genln(deferred_text)
		if p.cur_fn.name == 'main' {
			p.gen('return 0')
		}
		else {
			p.gen('return')
		}
	}
	// p.fgenln('//ret')
	p.returns = true
}

fn (p &Parser) get_deferred_text() string {
	// @emily33901: Scoped defer
	// Check all of our defer texts to see if there is one at a higher scope level
	// The one for our current scope would be the last so any before that need to be
	// added.
	mut deferred_text := ''
	for text in p.cur_fn.defer_text {
		if text != '' {
			// In reverse order
			deferred_text = text + deferred_text
		}
	}
	return deferred_text
}

fn prepend_mod(mod, name string) string {
	return '${mod}__${name}'
}

fn (p &Parser) prepend_mod(name string) string {
	return prepend_mod(mod_gen_name(p.mod), name)
}

fn (p mut Parser) go_statement() {
	p.check(.key_go)
	p.fspace()
	mut gotoken_idx := p.cur_tok_index()
	// TODO copypasta of name_expr() ?
	if p.peek() == .dot {
		// Method
		var_name := p.lit
		v := p.find_var(var_name) or {
			return
		}
		p.mark_var_used(v)
		gotoken_idx = p.cur_tok_index()
		p.next()
		p.check(.dot)
		typ := p.table.find_type(v.typ)
		method := p.table.find_method(typ, p.lit) or {
			p.error_with_token_index('go method missing $var_name', gotoken_idx)
			return
		}
		p.async_fn_call(method, 0, var_name, v.typ)
	}
	else {
		f_name := p.lit
		// Normal function
		f := p.table.find_fn(p.prepend_mod(f_name)) or {
			println(p.table.debug_fns())
			p.error_with_token_index('can not find function $f_name', gotoken_idx)
			return
		}
		if f.name == 'println' || f.name == 'print' {
			p.error_with_token_index('`go` cannot be used with `println`', gotoken_idx)
		}
		p.async_fn_call(f, 0, '', '')
	}
}

/*
fn (p mut Parser) register_var(v Var) {
	if v.line_nr == 0 {
		spos := p.scanner.get_scanner_pos()
		p.register_var({ v | scanner_pos: spos, line_nr: spos.line_nr })
	} else {
		p.register_var(v)
	}
}
*/

// user:=jsdecode(User, user_json_string)
fn (p mut Parser) js_decode() string {
	p.check(.name) // json
	p.check(.dot)
	op := p.check_name()
	op_token_idx := p.cur_tok_index()
	if op == 'decode' {
		// User tmp2; tmp2.foo = 0; tmp2.bar = 0;// I forgot to zero vals before => huge bug
		// Option_User tmp3 =  jsdecode_User(json_parse( s), &tmp2); ;
		// if (!tmp3 .ok) {
		// return
		// }
		// User u = *(User*) tmp3 . data;  // TODO remove this (generated in or {} block handler)
		p.check(.lpar)
		typ := p.get_type()
		p.check(.comma)
		styp,expr := p.tmp_expr()
		p.check_types(styp, 'string')
		p.check(.rpar)
		tmp := p.get_tmp()
		cjson_tmp := p.get_tmp()
		mut decl := '$typ $tmp; '
		// Init the struct
		T := p.table.find_type(typ)
		for field in T.fields {
			def_val := type_default(field.typ)
			if def_val != '' {
				decl += '${tmp}.$field.name = OPTION_CAST($field.typ) $def_val;\n'
			}
		}
		p.gen_json_for_type(T)
		decl += 'cJSON* $cjson_tmp = json__json_parse($expr);'
		p.cgen.insert_before(decl)
		// p.gen('jsdecode_$typ(json_parse($expr), &$tmp);')
		p.gen('json__jsdecode_${typ}($cjson_tmp, &$tmp); cJSON_Delete($cjson_tmp);')
		opt_type := 'Option_$typ'
		p.cgen.typedefs << 'typedef Option $opt_type;'
		p.table.register_builtin(opt_type)
		return opt_type
	}
	else if op == 'encode' {
		p.check(.lpar)
		typ,expr := p.tmp_expr()
		T := p.table.find_type(typ)
		p.gen_json_for_type(T)
		p.check(.rpar)
		p.gen('json__json_print(json__jsencode_${typ}($expr))')
		return 'string'
	}
	else {
		p.error_with_token_index('bad json op "$op"', op_token_idx)
	}
	return ''
}

fn (p mut Parser) attribute() {
	p.check(.lsbr)
	if p.tok == .key_if {
		// [if vfmt]
		p.next()
		p.attr = 'if ' + p.check_name()
	}
	else {
		p.attr = p.check_name()
	}
	attr_token_idx := p.cur_tok_index()
	if p.tok == .colon {
		p.check(.colon)
		p.attr = p.attr + ':' + p.check_name()
	}
	p.check(.rsbr)
	p.fgen_nl()
	if p.tok == .key_fn || (p.tok == .key_pub && p.peek() == .key_fn) {
		p.fn_decl()
		p.attr = ''
		return
	}
	else if p.tok == .key_struct {
		p.struct_decl([])
		p.attr = ''
		return
	}
	else if p.tok == .key_enum {
		p.enum_decl(false)
		p.attr = ''
		return
	}
	p.error_with_token_index('bad attribute usage', attr_token_idx)
}

fn (p mut Parser) defer_st() {
	p.check(.key_defer)
	p.fspace()
	p.check(.lcbr)
	pos := p.cgen.lines.len
	// Save everything inside the defer block to `defer_text`.
	// It will be inserted before every `return`
	// Emily: TODO: all variables that are used in this defer statement need to be evaluated when the block
	// is defined otherwise they could change over the course of the function
	// (make temps out of them)
	p.genln('{')
	p.statements()
	p.cur_fn.defer_text.last() = p.cgen.lines[pos..].join('\n') + p.cur_fn.defer_text.last()
	// Rollback p.cgen.lines
	p.cgen.lines = p.cgen.lines[..pos]
	p.cgen.resetln('')
}

fn (p mut Parser) check_and_register_used_imported_type(typ_name string) {
	us_idx := typ_name.index('__') or {
		return
	}
	arg_mod := typ_name[..us_idx]
	if p.import_table.known_alias(arg_mod) {
		p.import_table.register_used_import(arg_mod)
	}
}

fn (p mut Parser) check_unused_imports() {
	// Don't run in the generated V file with `.str()`
	if p.is_vgen {
		return
	}
	mut output := ''
	for alias, mod in p.import_table.imports {
		if !p.import_table.is_used_import(alias) {
			mod_alias := if alias == mod { alias } else { '$alias ($mod)' }
			output += '\n * $mod_alias'
		}
	}
	if output == '' {
		return
	}
	// the imports are usually at the start of the file
	p.production_error_with_token_index('the following imports were never used: $output', 0)
}

fn (p &Parser) is_expr_fn_call(start_tok_idx int) (bool,string) {
	mut expr := p.tokens[start_tok_idx - 1].str()
	mut is_fn_call := p.tokens[start_tok_idx].tok == .lpar
	if !is_fn_call {
		mut i := start_tok_idx
		for (p.tokens[i].tok == .dot || p.tokens[i].tok == .name) && p.tokens[i].lit != '_' && i < p.tokens.len {
			expr += p.tokens[i].str()
			i++
		}
		is_fn_call = p.tokens[i].tok == .lpar
	}
	return is_fn_call,expr
}

[inline]
// skip any block of code in curley braces `{}`
fn (p mut Parser) skip_block(inside_first_lcbr bool) {
	mut cbr_depth := if inside_first_lcbr { 1 } else { 0 }
	for {
		if p.tok == .lcbr {
			cbr_depth++
		}
		if p.tok == .rcbr {
			cbr_depth--
			if cbr_depth == 0 {
				break
			}
		}
		p.next()
	}
	p.check(.rcbr)
}

fn todo_remove() {
	x64.new_gen('f')
}

