3173 lines
72 KiB
Go
3173 lines
72 KiB
Go
// 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 main
|
|
|
|
import rand
|
|
|
|
struct Var {
|
|
mut:
|
|
typ string
|
|
name string
|
|
is_arg bool
|
|
is_const bool
|
|
is_import_const bool // TODO remove import consts entirely
|
|
args []Var // function args
|
|
attr string // [json] etc
|
|
is_mut bool
|
|
ptr bool
|
|
ref bool
|
|
parent_fn string // Variables can only be defined in functions
|
|
pkg string // module where this var is stored TODO rename to `mod`
|
|
line_nr int
|
|
access_mod AccessMod
|
|
is_global bool // __global (translated from C only)
|
|
is_used bool
|
|
scope_level int
|
|
}
|
|
|
|
struct Parser {
|
|
file_path string // "/home/user/hello.v"
|
|
file_name string // "hello.v"
|
|
mut:
|
|
scanner *Scanner
|
|
// tokens []Token // TODO cache all tokens, right now they have to be scanned twice
|
|
token_idx int
|
|
tok Token
|
|
prev_tok Token
|
|
prev_tok2 Token // TODO remove these once the tokens are cached
|
|
lit string
|
|
cgen *CGen
|
|
table *Table
|
|
run Pass // TODO rename `run` to `pass`
|
|
os Os
|
|
pkg string
|
|
inside_const bool
|
|
expr_var Var
|
|
assigned_type string
|
|
tmp_cnt int
|
|
// TODO all these options are copy-pasted from the V struct. Create a Settings struct instead?
|
|
is_test bool
|
|
is_script bool
|
|
is_live bool
|
|
is_so bool
|
|
is_prof bool
|
|
translated bool
|
|
is_prod bool
|
|
is_verbose bool
|
|
obfuscate bool
|
|
is_play bool
|
|
is_repl bool
|
|
builtin_pkg bool
|
|
build_mode BuildMode
|
|
vh_lines []string
|
|
inside_if_expr bool
|
|
is_struct_init 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
|
|
returns bool
|
|
}
|
|
|
|
const (
|
|
EmptyFn = &Fn { }
|
|
)
|
|
|
|
fn (c mut V) new_parser(path string, run Pass) Parser {
|
|
c.log('new_parser("$path")')
|
|
c.cgen.run = run
|
|
mut p := Parser {
|
|
file_path: path
|
|
file_name: path.all_after('/')
|
|
scanner: new_scanner(path)
|
|
table: c.table
|
|
cur_fn: EmptyFn
|
|
cgen: c.cgen
|
|
is_test: c.is_test
|
|
is_script: (c.is_script && path == c.dir)
|
|
is_so: c.is_so
|
|
os: c.os
|
|
is_prof: c.is_prof
|
|
is_prod: c.is_prod
|
|
is_play: c.is_play
|
|
translated: c.translated
|
|
obfuscate: c.obfuscate
|
|
is_verbose: c.is_verbose
|
|
build_mode: c.build_mode
|
|
is_repl: c.is_repl
|
|
run: run
|
|
}
|
|
p.next()
|
|
// p.scanner.debug_tokens()
|
|
return p
|
|
}
|
|
|
|
fn (p mut Parser) next() {
|
|
p.prev_tok2 = p.prev_tok
|
|
p.prev_tok = p.tok
|
|
res := p.scanner.scan()
|
|
p.tok = res.tok
|
|
p.lit = res.lit
|
|
}
|
|
|
|
fn (p &Parser) log(s string) {
|
|
if !p.is_verbose {
|
|
return
|
|
}
|
|
println(s)
|
|
}
|
|
|
|
fn (p mut Parser) parse() {
|
|
p.log('\nparse() run=$p.run 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.is_script || p.is_test {
|
|
p.pkg = 'main'
|
|
// User may still specify `module main`
|
|
if p.tok == PACKAGE {
|
|
p.next()
|
|
p.fgen('module ')
|
|
p.pkg = p.check_name()
|
|
}
|
|
}
|
|
else {
|
|
p.check(PACKAGE)
|
|
p.pkg = p.check_name()
|
|
}
|
|
p.fgenln('\n')
|
|
p.builtin_pkg = p.pkg == 'builtin'
|
|
// Import pass - the first and the smallest pass that only analyzes imports
|
|
p.table.register_package(p.pkg)
|
|
if p.run == RUN_IMPORTS {
|
|
for p.tok == IMPORT && p.peek() != CONST {
|
|
p.import_statement()
|
|
}
|
|
return
|
|
}
|
|
// Go through every top level token or throw a compilation error if a non-top level token is met
|
|
for {
|
|
switch p.tok {
|
|
case IMPORT:
|
|
if p.peek() == CONST {
|
|
p.const_decl()
|
|
}
|
|
else {
|
|
// TODO remove imported consts from the language
|
|
p.import_statement()
|
|
}
|
|
case AT:
|
|
p.at()
|
|
case ENUM:
|
|
p.next()
|
|
if p.tok == NAME {
|
|
p.fgen('enum ')
|
|
name := p.check_name()
|
|
p.fgen(' ')
|
|
p.enum_decl(name)
|
|
}
|
|
// 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
|
|
else if p.translated {
|
|
p.enum_decl('int')
|
|
}
|
|
else {
|
|
p.check(NAME)
|
|
}
|
|
case PUB:
|
|
if p.peek() == FUNC {
|
|
p.fn_decl()
|
|
}
|
|
// TODO public structs
|
|
case FUNC:
|
|
p.fn_decl()
|
|
case TIP:
|
|
p.type_decl()
|
|
case STRUCT, INTERFACE, UNION, LSBR:// `[` can only mean an [attribute] before the struct definition
|
|
p.struct_decl()
|
|
case CONST:
|
|
p.const_decl()
|
|
case HASH:
|
|
// insert C code, TODO this is going to be removed ASAP
|
|
// some libraries (like UI) still have lots of C code
|
|
// # puts("hello");
|
|
p.chash()
|
|
case DOLLAR:
|
|
// $if, $else
|
|
p.comp_time()
|
|
case GLOBAL:
|
|
if !p.translated {
|
|
p.error('__global is only allowed in translated code')
|
|
}
|
|
p.next()
|
|
name := p.check_name()
|
|
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()
|
|
// p.gen(' = ')
|
|
g += ' = '
|
|
p.cgen.start_tmp()
|
|
p.bool_expression()
|
|
// g += '<<< ' + p.cgen.end_tmp() + '>>>'
|
|
g += p.cgen.end_tmp()
|
|
}
|
|
// p.genln('; // global')
|
|
g += ('; // global')
|
|
p.cgen.consts << g
|
|
case EOF:
|
|
p.log('end of parse()')
|
|
if true && !p.first_run() && p.fileis('test') {
|
|
out := os.create('/var/tmp/fmt.v')
|
|
out.appendln(p.scanner.fmt_out.str())
|
|
out.close()
|
|
}
|
|
return
|
|
default:
|
|
// no `fn main`, add this "global" statement to cgen.fn_main
|
|
if p.is_script && !p.is_test {
|
|
if p.cur_fn.scope_level == 0 {
|
|
// p.cur_fn.scope_level++
|
|
}
|
|
// println('is script')
|
|
p.print_tok()
|
|
start := p.cgen.lines.len
|
|
p.statement(true)
|
|
end := p.cgen.lines.len
|
|
lines := p.cgen.lines.slice(start, end)
|
|
// p.cgen.fn_main << p.cgen.prev_line
|
|
// println('fn line:')
|
|
// println(p.cgen.fn_main + lines.join('\n'))
|
|
p.cgen.fn_main = p.cgen.fn_main + lines.join('\n')
|
|
p.cgen.cur_line = ''
|
|
for i := start; i < end; i++ {
|
|
// p.cgen.lines[p.cgen.lines.len - 1] = ''
|
|
p.cgen.lines[i] = ''
|
|
}
|
|
// exit('')
|
|
}
|
|
else {
|
|
p.error('unexpected token `${p.strtok()}`')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (p mut Parser) import_statement() {
|
|
p.check(IMPORT)
|
|
// `import ()`
|
|
if p.tok == LPAR {
|
|
p.check(LPAR)
|
|
for p.tok != RPAR && p.tok != EOF {
|
|
pkg := p.lit.trim_space()
|
|
p.next()
|
|
if p.table.imports.contains(pkg) {
|
|
continue
|
|
}
|
|
p.table.imports << pkg
|
|
p.table.register_package(pkg)
|
|
}
|
|
p.check(RPAR)
|
|
return
|
|
}
|
|
// `import foo`
|
|
if p.tok != NAME {
|
|
p.error('bad import format')
|
|
}
|
|
pkg := p.lit.trim_space()
|
|
p.next()
|
|
p.fgenln(' ' + pkg)
|
|
// Make sure there are no duplicate imports
|
|
if p.table.imports.contains(pkg) {
|
|
return
|
|
}
|
|
p.log('adding import $pkg')
|
|
p.table.imports << pkg
|
|
p.table.register_package(pkg)
|
|
}
|
|
|
|
fn (p mut Parser) const_decl() {
|
|
is_import := p.tok == IMPORT
|
|
p.inside_const = true
|
|
if is_import {
|
|
p.next()
|
|
}
|
|
p.check(CONST)
|
|
p.fspace()
|
|
p.check(LPAR)
|
|
p.fgenln('')
|
|
p.scanner.fmt_indent++
|
|
for p.tok == NAME {
|
|
// `Age = 20`
|
|
mut name := p.check_name()
|
|
if p.is_play && ! (name[0] >= `A` && name[0] <= `Z`) {
|
|
p.error('const name must be capitalized')
|
|
}
|
|
// Imported consts (like GL_TRIANGLES) dont need pkg prepended (gl__GL_TRIANGLES)
|
|
if !is_import {
|
|
name = p.prepend_pkg(name)
|
|
}
|
|
mut typ := 'int'
|
|
if !is_import {
|
|
p.check_space(ASSIGN)
|
|
typ = p.expression()
|
|
}
|
|
if p.first_run() && !is_import && p.table.known_const(name) {
|
|
p.error('redefinition of `$name`')
|
|
}
|
|
p.table.register_const(name, typ, p.pkg, is_import)
|
|
if p.run == RUN_MAIN && !is_import {
|
|
// 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
|
|
if is_compile_time_const(p.cgen.cur_line) {
|
|
p.cgen.consts << '#define $name $p.cgen.cur_line'
|
|
p.cgen.cur_line = ''
|
|
p.fgenln('')
|
|
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) + ';'
|
|
p.cgen.consts_init << '$name = $p.cgen.cur_line;'
|
|
}
|
|
p.cgen.cur_line = ''
|
|
}
|
|
p.fgenln('')
|
|
}
|
|
p.scanner.fmt_indent--
|
|
p.check(RPAR)
|
|
p.fgenln('\n')
|
|
p.inside_const = false
|
|
}
|
|
|
|
// `type myint int`
|
|
// `type onclickfn fn(voidptr) int`
|
|
fn (p mut Parser) type_decl() {
|
|
p.check(TIP)
|
|
name := p.check_name()
|
|
parent := p.get_type()
|
|
nt_pair := p.table.cgen_name_type_pair(name, parent)
|
|
// 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.contains('[') && !parent.starts_with('fn ') && !p.table.known_type(parent){'struct'} else { ''}
|
|
p.gen_typedef('typedef $_struct $nt_pair; // type alias name="$name" parent="$parent"')
|
|
p.table.register_type_with_parent(name, parent)
|
|
}
|
|
|
|
// also unions and interfaces
|
|
fn (p mut Parser) struct_decl() {
|
|
// Attribute before type?
|
|
mut objc_parent := ''
|
|
mut is_objc := false// V can generate Objective C for integration with Cocoa
|
|
// [attr]
|
|
if p.tok == LSBR {
|
|
p.check(LSBR)
|
|
// `[interface:ParentInterface]`
|
|
is_objc = p.tok == INTERFACE
|
|
p.next()
|
|
if is_objc {
|
|
p.check(COLON)
|
|
objc_parent = p.check_name()
|
|
}
|
|
p.check(RSBR)
|
|
}
|
|
is_interface := p.tok == INTERFACE
|
|
is_union := p.tok == UNION
|
|
is_struct := p.tok == STRUCT
|
|
p.fgen(p.tok.str() + ' ')
|
|
// Get type name
|
|
p.next()
|
|
mut name := p.check_name()
|
|
if name.contains('_') && !p.translated {
|
|
p.error('type names cannot contain `_`')
|
|
}
|
|
if is_interface && !name.ends_with('er') {
|
|
p.error('interface names temporarily have to end with `er` (e.g. `Speaker`, `Reader`)')
|
|
}
|
|
is_c := name == 'C' && p.tok == DOT
|
|
if is_c {
|
|
p.check(DOT)
|
|
name = p.check_name()
|
|
}
|
|
// Specify full type name
|
|
if !is_c && !p.builtin_pkg && p.pkg != 'main' {
|
|
name = p.prepend_pkg(name)
|
|
}
|
|
if p.run == RUN_DECLS && p.table.known_type(name) {
|
|
p.error('`$name` redeclared')
|
|
}
|
|
// Generate type definitions
|
|
if is_objc {
|
|
p.gen_type('@interface $name : $objc_parent { @public')
|
|
}
|
|
else {
|
|
// type alias is generated later
|
|
if !is_c {
|
|
kind := if is_union{'union'} else { 'struct'}
|
|
p.gen_typedef('typedef $kind $name $name;')
|
|
p.gen_type('$kind $name {')
|
|
}
|
|
}
|
|
// V used to have 'type Foo struct', many Go users might use this syntax
|
|
if p.tok == STRUCT {
|
|
p.error('use `struct $name {` instead of `type $name struct {`')
|
|
}
|
|
// Register the type
|
|
mut typ := p.table.find_type(name)
|
|
mut is_ph := false
|
|
if typ.is_placeholder {
|
|
is_ph = true
|
|
typ.name = name
|
|
typ.pkg = p.pkg
|
|
typ.is_c = is_c
|
|
typ.is_placeholder = false
|
|
}
|
|
else {
|
|
typ = &Type {
|
|
name: name
|
|
pkg: p.pkg
|
|
is_c: is_c
|
|
is_interface: is_interface
|
|
}
|
|
}
|
|
// Struct `C.Foo` declaration, no body
|
|
// println('EEEE $is_c $is_struct')
|
|
if is_c && is_struct && p.tok != LCBR {
|
|
// println('skipping struct header $name')
|
|
p.table.register_type2(typ)
|
|
return
|
|
}
|
|
p.fgen(' ')
|
|
p.check(LCBR)
|
|
// Struct fields
|
|
mut is_pub := false
|
|
mut is_mut := false
|
|
mut names := []string// to avoid dup names TODO alloc perf
|
|
// mut is_mut_mut := false
|
|
for p.tok != RCBR {
|
|
if p.tok == PUB {
|
|
if is_pub {
|
|
p.error('structs can only have one `pub:`, all public fields have to be grouped')
|
|
}
|
|
is_pub = true
|
|
is_mut = false
|
|
p.scanner.fmt_indent--
|
|
p.check(PUB)
|
|
p.check(COLON)
|
|
p.scanner.fmt_indent++
|
|
p.fgenln('')
|
|
}
|
|
if p.tok == MUT {
|
|
if is_mut {
|
|
p.error('structs can only have one `mut:`, all private mutable fields have to be grouped')
|
|
}
|
|
is_mut = true
|
|
is_pub = false
|
|
p.scanner.fmt_indent--
|
|
p.check(MUT)
|
|
p.check(COLON)
|
|
p.scanner.fmt_indent++
|
|
p.fgenln('')
|
|
}
|
|
// if is_pub {
|
|
// }
|
|
// (mut) user *User
|
|
// if p.tok == PLUS {
|
|
// p.next()
|
|
// }
|
|
// Check dups
|
|
field_name := p.check_name()
|
|
if field_name in names {
|
|
p.error('duplicate field `$field_name`')
|
|
}
|
|
names << field_name
|
|
// We are in an interface?
|
|
// `run() string` => run is a method, not a struct field
|
|
if is_interface {
|
|
mut interface_method := &Fn {
|
|
name: field_name
|
|
is_interface: true
|
|
is_method: true
|
|
receiver_typ: name
|
|
}
|
|
println('is interfaace. field=$field_name run=$p.run')
|
|
p.fn_args(mut interface_method)
|
|
p.fspace()
|
|
interface_method.typ = p.get_type()// method return type
|
|
typ.add_method(interface_method)
|
|
p.fgenln('')
|
|
continue
|
|
}
|
|
// `pub` access mod
|
|
access_mod := if is_pub{PUBLIC} else { PRIVATE}
|
|
if typ.name == 'Userf' {
|
|
println('$field_name $access_mod mut=$is_mut')
|
|
}
|
|
p.fgen(' ')
|
|
field_type := p.get_type()
|
|
is_atomic := p.tok == ATOMIC
|
|
if is_atomic {
|
|
p.next()
|
|
p.gen_type('_Atomic ')
|
|
}
|
|
if !is_c {
|
|
p.gen_type(p.table.cgen_name_type_pair(field_name, field_type) + ';')
|
|
}
|
|
// [ATTR]
|
|
mut attr := ''
|
|
if p.tok == LSBR {
|
|
p.next()
|
|
attr = p.check_name()
|
|
p.check(RSBR)
|
|
}
|
|
typ.add_field(field_name, field_type, is_mut, attr, access_mod)
|
|
p.fgenln('')
|
|
}
|
|
if !is_ph && p.first_run() {
|
|
p.table.register_type2(typ)
|
|
}
|
|
p.check(RCBR)
|
|
if !is_c {
|
|
p.gen_type('}; ')
|
|
}
|
|
if is_objc {
|
|
p.gen_type('@end')
|
|
}
|
|
p.fgenln('\n')
|
|
}
|
|
|
|
fn (p mut Parser) enum_decl(_enum_name string) {
|
|
mut enum_name := _enum_name
|
|
// Specify full type name
|
|
if !p.builtin_pkg && p.pkg != 'main' {
|
|
enum_name = p.prepend_pkg(enum_name)
|
|
}
|
|
p.table.register_type2(&Type {
|
|
name: enum_name
|
|
pkg: p.pkg
|
|
parent: 'int'
|
|
is_enum: true
|
|
})
|
|
// Skip empty enums
|
|
if enum_name != 'int' {
|
|
p.cgen.typedefs << 'typedef int $enum_name ;\n'
|
|
}
|
|
p.check(LCBR)
|
|
mut val := 0
|
|
for p.tok == NAME {
|
|
field := p.check_name()
|
|
// name := '${p.pkg}__${enum_name}_$field'
|
|
// name := '${enum_name}_$field'
|
|
name := '$field'
|
|
p.fgenln('')
|
|
if p.run == RUN_MAIN {
|
|
p.cgen.consts << '#define $name $val \n'
|
|
}
|
|
if p.tok == COMMA {
|
|
p.next()
|
|
}
|
|
p.table.register_const(name, enum_name, p.pkg, false)
|
|
val++
|
|
}
|
|
p.check(RCBR)
|
|
p.fgenln('\n')
|
|
}
|
|
|
|
// 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(STRING)
|
|
return s
|
|
}
|
|
|
|
fn (p &Parser) strtok() string {
|
|
if p.tok == NAME {
|
|
return p.lit
|
|
}
|
|
if p.tok == STRING {
|
|
return '"$p.lit"'
|
|
}
|
|
res := p.tok.str()
|
|
if res == '' {
|
|
n := int(p.tok)
|
|
return n.str()
|
|
}
|
|
return res
|
|
}
|
|
|
|
// same as check(), but addes a space to the formatter output
|
|
// TODO bad name
|
|
fn (p mut Parser) check_space(expected Token) {
|
|
p.fspace()
|
|
p.check(expected)
|
|
p.fspace()
|
|
}
|
|
|
|
fn (p mut Parser) check(expected Token) {
|
|
if p.tok != expected {
|
|
println('check()')
|
|
mut s := 'expected `${expected.str()}` but got `${p.strtok()}`'
|
|
p.next()
|
|
println('next token = `${p.strtok()}`')
|
|
print_backtrace()
|
|
p.error(s)
|
|
}
|
|
if expected == RCBR {
|
|
p.scanner.fmt_indent--
|
|
}
|
|
p.fgen(p.strtok())
|
|
// vfmt: increase indentation on `{` unless it's `{}`
|
|
if expected == LCBR && p.scanner.text[p.scanner.pos + 1] != `}` {
|
|
p.fgenln('')
|
|
p.scanner.fmt_indent++
|
|
}
|
|
p.next()
|
|
}
|
|
|
|
fn (p mut Parser) error(s string) {
|
|
// Dump all vars and types for debugging
|
|
if false {
|
|
file_types := os.create_file('$TmpPath/types')
|
|
file_vars := os.create_file('$TmpPath/vars')
|
|
// ////debug("ALL T", q.J(p.table.types))
|
|
// os.write_to_file('/var/tmp/lang.types', '')//pes(p.table.types))
|
|
// //debug("ALL V", q.J(p.table.vars))
|
|
// os.write_to_file('/var/tmp/lang.vars', q.J(p.table.vars))
|
|
file_types.close()
|
|
file_vars.close()
|
|
}
|
|
if !p.is_repl {
|
|
println('pass=$p.run fn=`$p.cur_fn.name`')
|
|
}
|
|
p.cgen.save()
|
|
// p.scanner.debug_tokens()
|
|
// Print `[]int` instead of `array_int` in errors
|
|
p.scanner.error(s.replace('array_', '[]').replace('__', '.'))
|
|
}
|
|
|
|
fn (p &Parser) first_run() bool {
|
|
return p.run == RUN_DECLS
|
|
}
|
|
|
|
// TODO return Type instead of string?
|
|
fn (p mut Parser) get_type() string {
|
|
debug := p.fileis('fn_test') && false
|
|
mut mul := false
|
|
mut nr_muls := 0
|
|
mut typ = ''
|
|
// fn type
|
|
if p.tok == FUNC {
|
|
if debug {
|
|
println('\nget_type() GOT FN TYP line=$p.scanner.line_nr')
|
|
}
|
|
mut f := Fn{name: '_', pkg: p.pkg}
|
|
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 debug {
|
|
println('same line getting type')
|
|
}
|
|
f.typ = p.get_type()
|
|
// 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'
|
|
pkg: p.pkg
|
|
func: f
|
|
}
|
|
p.table.register_type2(fn_typ)
|
|
return f.typ_str()
|
|
}
|
|
// arrays ([]int)
|
|
mut is_arr := false
|
|
mut is_arr2 := false// [][]int TODO remove this and allow unlimited levels of arrays
|
|
is_question := p.tok == QUESTION
|
|
if is_question {
|
|
p.check(QUESTION)
|
|
}
|
|
if p.tok == LSBR {
|
|
p.check(LSBR)
|
|
// [10]int
|
|
if p.tok == INT {
|
|
typ = '[$p.lit]'
|
|
p.next()
|
|
}
|
|
else {
|
|
is_arr = true
|
|
}
|
|
p.check(RSBR)
|
|
// [10][3]int
|
|
if p.tok == LSBR {
|
|
p.next()
|
|
if p.tok == INT {
|
|
typ += '[$p.lit]'
|
|
p.check(INT)
|
|
}
|
|
else {
|
|
is_arr2 = true
|
|
}
|
|
p.check(RSBR)
|
|
}
|
|
}
|
|
for p.tok == MUL {
|
|
mul = true
|
|
nr_muls++
|
|
p.next()
|
|
}
|
|
if p.tok == AMP {
|
|
mul = true
|
|
nr_muls++
|
|
p.next()
|
|
}
|
|
typ += p.lit
|
|
if !p.is_struct_init {
|
|
// Otherwise we get `foo := FooFoo{` because `Foo` was already generated in name_expr()
|
|
p.fgen(p.lit)
|
|
}
|
|
// C.Struct import
|
|
if p.lit == 'C' && p.peek() == DOT {
|
|
p.next()
|
|
p.check(DOT)
|
|
typ = p.lit
|
|
}
|
|
else {
|
|
// Package specified? (e.g. gx.Image)
|
|
if p.peek() == DOT {
|
|
p.next()
|
|
p.check(DOT)
|
|
typ += '__$p.lit'
|
|
}
|
|
mut t := p.table.find_type(typ)
|
|
// "typ" not found? try "pkg__typ"
|
|
if t.name == '' && !p.builtin_pkg {
|
|
// && !p.first_run() {
|
|
if !typ.contains('array_') && p.pkg != 'main' && !typ.contains('__') {
|
|
typ = p.prepend_pkg(typ)
|
|
}
|
|
t = p.table.find_type(typ)
|
|
if t.name == '' && !p.translated && !p.first_run() && !typ.starts_with('[') {
|
|
println('get_type() bad type')
|
|
// println('all registered types:')
|
|
// for q in p.table.types {
|
|
// println(q.name)
|
|
// }
|
|
p.error('unknown type `$typ`')
|
|
}
|
|
}
|
|
}
|
|
if typ == 'void' {
|
|
p.error('unknown type `$typ`')
|
|
}
|
|
if mul {
|
|
typ += repeat_char(`*`, nr_muls)
|
|
}
|
|
// Register an []array type
|
|
if is_arr2 {
|
|
typ = 'array_array_$typ'
|
|
p.register_array(typ)
|
|
}
|
|
else if is_arr {
|
|
typ = 'array_$typ'
|
|
// p.log('ARR TYPE="$typ" run=$p.run')
|
|
// We come across "[]User" etc ?
|
|
p.register_array(typ)
|
|
}
|
|
p.next()
|
|
if p.tok == QUESTION || is_question {
|
|
typ = 'Option_$typ'
|
|
p.table.register_type_with_parent(typ, 'Option')
|
|
if p.tok == QUESTION {
|
|
p.next()
|
|
}
|
|
}
|
|
// Because the code uses * to see if it's a pointer
|
|
if typ == 'byteptr' {
|
|
return 'byte*'
|
|
}
|
|
if typ == 'voidptr' {
|
|
//if !p.builtin_pkg && p.pkg != 'os' && p.pkg != 'gx' && p.pkg != 'gg' && !p.translated {
|
|
//p.error('voidptr can only be used in unsafe code')
|
|
//}
|
|
return 'void*'
|
|
}
|
|
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 == STRING {
|
|
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_curly_end()
|
|
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_curly_end() string {
|
|
p.cur_fn.open_scope()
|
|
if !p.inside_if_expr {
|
|
p.genln('')
|
|
}
|
|
mut i := 0
|
|
mut last_st_typ := ''
|
|
for p.tok != RCBR && p.tok != EOF && p.tok != CASE && p.tok != DEFAULT {
|
|
// println(p.tok.str())
|
|
// p.print_tok()
|
|
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('')
|
|
}
|
|
i++
|
|
if i > 50000 {
|
|
p.cgen.save()
|
|
p.error('more than 50 000 statements in function `$p.cur_fn.name`')
|
|
}
|
|
}
|
|
if p.tok != CASE && p.tok != DEFAULT {
|
|
// p.next()
|
|
p.check(RCBR)
|
|
}
|
|
else {
|
|
// p.check(RCBR)
|
|
}
|
|
p.scanner.fmt_indent--
|
|
// println('close scope line=$p.scanner.line_nr')
|
|
p.cur_fn.close_scope()
|
|
return last_st_typ
|
|
}
|
|
|
|
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) vh_genln(s string) {
|
|
p.vh_lines << s
|
|
}
|
|
|
|
fn (p mut Parser) fmt_inc() {
|
|
p.scanner.fmt_indent++
|
|
}
|
|
|
|
fn (p mut Parser) fmt_dec() {
|
|
p.scanner.fmt_indent--
|
|
}
|
|
|
|
fn (p mut Parser) statement(add_semi bool) string {
|
|
p.cgen.is_tmp = false
|
|
tok := p.tok
|
|
mut q := ''
|
|
switch tok {
|
|
case NAME:
|
|
next := p.peek()
|
|
if p.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 ''
|
|
}
|
|
else if p.peek() == DECL_ASSIGN {
|
|
p.log('var decl')
|
|
p.var_decl()
|
|
}
|
|
else if p.lit == 'jsdecode' {
|
|
p.js_decode()
|
|
}
|
|
else {
|
|
// "a + 3", "a(7)" or maybe just "a"
|
|
q = p.bool_expression()
|
|
}
|
|
case GOTO:
|
|
p.check(GOTO)
|
|
p.fgen(' ')
|
|
label := p.check_name()
|
|
p.genln('goto $label;')
|
|
return ''
|
|
case HASH:
|
|
p.chash()
|
|
return ''
|
|
case DOLLAR:
|
|
p.comp_time()
|
|
case IF:
|
|
p.if_st(false)
|
|
case FOR:
|
|
p.for_st()
|
|
case SWITCH, MATCH:
|
|
p.switch_statement()
|
|
case MUT, STATIC:
|
|
p.var_decl()
|
|
case RETURN:
|
|
p.return_st()
|
|
case LCBR:// {} block
|
|
p.next()
|
|
p.genln('{')
|
|
p.statements()
|
|
return ''
|
|
case CONTINUE:
|
|
if p.for_expr_cnt == 0 {
|
|
p.error('`continue` statement outside `for`')
|
|
}
|
|
p.genln('continue')
|
|
p.next()
|
|
case BREAK:
|
|
if p.for_expr_cnt == 0 {
|
|
p.error('`break` statement outside `for`')
|
|
}
|
|
p.genln('break')
|
|
p.next()
|
|
case GO:
|
|
p.go_statement()
|
|
case ASSERT:
|
|
p.assert_statement()
|
|
default:
|
|
// 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) {
|
|
p.log('assign_statement() name=$v.name tok=')
|
|
// p.print_tok()
|
|
// p.gen(name)
|
|
// p.assigned_var = name
|
|
tok := p.tok
|
|
if !v.is_mut && !v.is_arg && !p.translated {
|
|
p.error('`$v.name` is immutable')
|
|
}
|
|
if !v.is_mut && p.is_play && !p.builtin_pkg && !p.translated {
|
|
// no mutable args in play
|
|
p.error('`$v.name` is immutable')
|
|
}
|
|
is_str := v.typ == 'string'
|
|
switch tok {
|
|
case ASSIGN:
|
|
if !is_map {
|
|
p.gen(' = ')
|
|
}
|
|
case PLUS_ASSIGN:
|
|
if is_str {
|
|
p.gen('= string_add($v.name, ')// TODO can't do `foo.bar += '!'`
|
|
}
|
|
else {
|
|
p.gen(' += ')
|
|
}
|
|
default: p.gen(' ' + p.tok.str() + ' ')
|
|
}
|
|
p.fgen(' ' + p.tok.str() + ' ')
|
|
p.next()
|
|
pos := p.cgen.cur_line.len
|
|
expr_type := p.bool_expression()
|
|
// Allow `num = 4` where `num` is an `?int`
|
|
if p.assigned_type.starts_with('Option_') && expr_type == p.assigned_type.right('Option_'.len) {
|
|
println('allowing option asss')
|
|
expr := p.cgen.cur_line.right(pos)
|
|
left := p.cgen.cur_line.left(pos)
|
|
p.cgen.cur_line = left + 'opt_ok($expr)'
|
|
}
|
|
else if !p.builtin_pkg && !p.check_types_no_throw(expr_type, p.assigned_type) {
|
|
p.scanner.line_nr--
|
|
p.error('cannot use type `$expr_type` as type `$p.assigned_type` in assignment')
|
|
}
|
|
if is_str && tok == PLUS_ASSIGN {
|
|
p.gen(')')
|
|
}
|
|
// p.assigned_var = ''
|
|
p.assigned_type = ''
|
|
if !v.is_used {
|
|
p.cur_fn.mark_var_used(v)
|
|
}
|
|
}
|
|
|
|
fn (p mut Parser) var_decl() {
|
|
is_mut := p.tok == MUT || p.prev_tok == FOR
|
|
is_static := p.tok == STATIC
|
|
if p.tok == MUT {
|
|
p.check(MUT)
|
|
p.fspace()
|
|
}
|
|
if p.tok == STATIC {
|
|
p.check(STATIC)
|
|
p.fspace()
|
|
}
|
|
// println('var decl tok=${p.strtok()} ismut=$is_mut')
|
|
name := p.check_name()
|
|
p.fgen(' := ')
|
|
// Don't allow declaring a variable with the same name. Even in a child scope
|
|
// (shadowing is not allowed)
|
|
if !p.builtin_pkg && p.cur_fn.known_var(name) {
|
|
v := p.cur_fn.find_var(name)
|
|
p.error('redefinition of `$name`')
|
|
// Check if this variable has already been declared only in the first run.
|
|
// Otherwise the is_script code outside main will run in the first run
|
|
// since we can't skip the function body since there's no function.
|
|
// And the variable will be registered twice.
|
|
if p.is_play && p.first_run() && !p.builtin_pkg {
|
|
p.error('variable `$name` is already declared.')
|
|
}
|
|
}
|
|
// println('var_decl $name')
|
|
// p.assigned_var = name
|
|
p.next()// :=
|
|
// Generate expression to tmp because we need its type first
|
|
// [TYP NAME =] bool_expression()
|
|
pos := p.cgen.add_placeholder()
|
|
// p.gen('typ $name = ')
|
|
// p.gen('/*^^^*/')
|
|
mut typ := p.bool_expression()
|
|
// p.gen('/*VVV*/')
|
|
// Option check ? or {
|
|
or_else := p.tok == OR_ELSE
|
|
tmp := p.get_tmp()
|
|
// assigned_var_copy := p.assigned_var
|
|
if or_else {
|
|
// Option_User tmp = get_user(1);
|
|
// if (!tmp.ok) { or_statement }
|
|
// User user = *(User*)tmp.data;
|
|
// p.assigned_var = ''
|
|
p.cgen.set_placeholder(pos, '$typ $tmp = ')
|
|
p.gen(';')
|
|
typ = typ.replace('Option_', '')
|
|
p.next()
|
|
p.check(LCBR)
|
|
p.genln('if (!$tmp .ok) {')
|
|
p.statements()
|
|
p.genln('$typ $name = *($typ*) $tmp . data;')
|
|
if !p.returns && p.prev_tok2 != CONTINUE && p.prev_tok2 != BREAK {
|
|
println(p.prev_tok2)
|
|
p.error('`or` statement must return/continue/break')
|
|
}
|
|
// p.assigned_var = assigned_var_copy
|
|
}
|
|
p.register_var(Var {
|
|
name: name
|
|
typ: typ
|
|
is_mut: is_mut
|
|
})
|
|
mut cgen_typ := typ
|
|
if !or_else {
|
|
gen_name := p.table.var_cgen_name(name)
|
|
// p.cgen.set_placeholder(pos, '$cgen_typ $gen_name = ')
|
|
mut nt_gen := p.table.cgen_name_type_pair(gen_name, cgen_typ) + '='
|
|
if is_static {
|
|
nt_gen = 'static $nt_gen'
|
|
// p.gen('static ')
|
|
}
|
|
p.cgen.set_placeholder(pos, nt_gen)
|
|
}
|
|
}
|
|
|
|
fn (p mut Parser) bool_expression() string {
|
|
tok := p.tok
|
|
typ := p.bterm()
|
|
for p.tok == AND || p.tok == OR {
|
|
p.gen(' ${p.tok.str()} ')
|
|
p.next()
|
|
p.check_types(p.bterm(), typ)
|
|
}
|
|
if typ == '' {
|
|
println('curline:')
|
|
println(p.cgen.cur_line)
|
|
println(tok.str())
|
|
p.error('expr() returns empty type')
|
|
}
|
|
return typ
|
|
}
|
|
|
|
fn (p mut Parser) bterm() string {
|
|
ph := p.cgen.add_placeholder()
|
|
mut typ = p.expression()
|
|
is_str := typ.eq('string')
|
|
tok := p.tok
|
|
// if tok in [ EQ, GT, LT, LE, GE, NE] {
|
|
if tok == EQ || tok == GT || tok == LT || tok == LE || tok == GE || tok == NE {
|
|
p.fgen(' ${p.tok.str()} ')
|
|
if is_str {
|
|
p.gen(',')
|
|
}
|
|
else {
|
|
p.gen(tok.str())
|
|
}
|
|
p.next()
|
|
p.check_types(p.expression(), typ)
|
|
typ = 'bool'
|
|
if is_str {
|
|
p.gen(')')
|
|
switch tok {
|
|
case EQ: p.cgen.set_placeholder(ph, 'string_eq(')
|
|
case NE: p.cgen.set_placeholder(ph, 'string_ne(')
|
|
case LE: p.cgen.set_placeholder(ph, 'string_le(')
|
|
case GE: p.cgen.set_placeholder(ph, 'string_ge(')
|
|
case GT: p.cgen.set_placeholder(ph, 'string_gt(')
|
|
case LT: p.cgen.set_placeholder(ph, 'string_lt(')
|
|
}
|
|
}
|
|
}
|
|
return typ
|
|
}
|
|
|
|
// also called on *, &
|
|
fn (p mut Parser) name_expr() string {
|
|
p.log('\nname expr() pass=$p.run tok=${p.tok.str()} $p.lit')
|
|
// print('known type:')
|
|
// println(p.table.known_type(p.lit))
|
|
// hack for struct_init TODO
|
|
hack_pos := p.scanner.pos
|
|
hack_tok := p.tok
|
|
hack_lit := p.lit
|
|
// amp
|
|
ptr := p.tok == AMP
|
|
deref := p.tok == MUL
|
|
if ptr || deref {
|
|
p.next()
|
|
}
|
|
if deref {
|
|
if p.is_play && !p.builtin_pkg {
|
|
p.error('dereferencing is temporarily disabled on the playground, will be fixed soon')
|
|
}
|
|
}
|
|
mut name := p.lit
|
|
p.fgen(name)
|
|
// known_type := p.table.known_type(name)
|
|
orig_name := name
|
|
is_c := name == 'C' && p.peek() == DOT
|
|
mut is_c_struct_init := is_c && ptr// a := &C.mycstruct{}
|
|
if is_c {
|
|
p.next()
|
|
p.check(DOT)
|
|
name = p.lit
|
|
p.fgen(name)
|
|
// Currently struct init is set to true only we have `&C.Foo{}`, handle `C.Foo{}`:
|
|
if !is_c_struct_init && p.peek() == LCBR {
|
|
is_c_struct_init = true
|
|
}
|
|
}
|
|
// //////////////////////////
|
|
// module ?
|
|
// Allow shadowing (gg = gg.newcontext(); gg.draw_triangle())
|
|
if p.table.known_pkg(name) && !p.cur_fn.known_var(name) {
|
|
// println('"$name" is a known pkg')
|
|
pkg := name
|
|
p.next()
|
|
p.check(DOT)
|
|
name = p.lit
|
|
p.fgen(name)
|
|
name = prepend_pkg(pkg, name)
|
|
}
|
|
else if !p.table.known_type(name) && !p.cur_fn.known_var(name) &&
|
|
!p.table.known_fn(name) && !p.table.known_const(name) && !is_c {
|
|
name = p.prepend_pkg(name)
|
|
}
|
|
// Variable
|
|
v := p.cur_fn.find_var(name)
|
|
if v.name.len != 0 {
|
|
if ptr {
|
|
p.gen('& /*vvar*/ ')
|
|
}
|
|
else if deref {
|
|
p.gen('*')
|
|
}
|
|
mut typ := p.var_expr(v)
|
|
// *var
|
|
if deref {
|
|
if !typ.contains('*') && !typ.ends_with('ptr') {
|
|
println('name="$name", t=$v.typ')
|
|
p.error('dereferencing requires a pointer, but got `$typ`')
|
|
}
|
|
typ = typ.replace('ptr', '')// TODO
|
|
typ = typ.replace('*', '')// TODO
|
|
}
|
|
// &var
|
|
else if ptr {
|
|
typ += '*'
|
|
}
|
|
return typ
|
|
}
|
|
// if known_type || is_c_struct_init || (p.first_run() && p.peek() == LCBR) {
|
|
// known type? int(4.5) or Color.green (enum)
|
|
if p.table.known_type(name) {
|
|
// float(5), byte(0), (*int)(ptr) etc
|
|
if p.peek() == LPAR || (deref && p.peek() == RPAR) {
|
|
// println('CASTT $name')
|
|
if deref {
|
|
// p.check(RPAR)
|
|
// p.next()
|
|
name += '*'
|
|
}
|
|
else if ptr {
|
|
name += '*'
|
|
}
|
|
p.gen('(/*casttt*/')
|
|
mut typ := p.cast(name)
|
|
p.gen(')')
|
|
for p.tok == DOT {
|
|
typ = p.dot(typ, 0)
|
|
}
|
|
return typ
|
|
}
|
|
// Color.green
|
|
else if p.peek() == DOT {
|
|
enum_type := p.table.find_type(name)
|
|
if !enum_type.is_enum {
|
|
p.error('`$name` is not an enum')
|
|
}
|
|
p.next()
|
|
p.check(DOT)
|
|
val := p.lit
|
|
// println('enum val $val')
|
|
p.gen(p.pkg + '__' + enum_type.name + '_' + val)// `color = main__Color_green`
|
|
p.next()
|
|
return enum_type.name
|
|
}
|
|
else {
|
|
// go back to name start (pkg.name)
|
|
p.scanner.pos = hack_pos
|
|
p.tok = hack_tok
|
|
p.lit = hack_lit
|
|
// TODO hack. If it's a C type, we may need to add struct before declaration:
|
|
// a := &C.A{} ==> struct A* a = malloc(sizeof(struct A));
|
|
if is_c_struct_init && name != 'tm' {
|
|
p.cgen.insert_before('struct ')
|
|
}
|
|
return p.struct_init(is_c_struct_init)
|
|
}
|
|
}
|
|
// C fn
|
|
if is_c {
|
|
f := Fn {
|
|
name: name// .replace('c_', '')
|
|
is_c: true
|
|
}
|
|
p.fn_call(f, 0, '', '')
|
|
// Try looking it up. Maybe its defined with "C.fn_name() fn_type",
|
|
// then we know what type it returns
|
|
cfn := p.table.find_fn(name)
|
|
// Not Found? Return 'void*'
|
|
if cfn.name == '' {
|
|
return 'void*'
|
|
}
|
|
return cfn.typ
|
|
}
|
|
// Constant
|
|
mut c := p.table.find_const(name)
|
|
if c.name != '' && ptr && !c.is_global {
|
|
p.error('cannot take the address of constant `$c.name`')
|
|
}
|
|
if c.name.len != 0 {
|
|
if ptr {
|
|
// c.ptr = true
|
|
p.gen('& /*const*/ ')
|
|
}
|
|
p.log('calling var expr')
|
|
mut typ := p.var_expr(c)
|
|
if ptr {
|
|
typ += '*'
|
|
}
|
|
return typ
|
|
}
|
|
// Function (not method btw, methods are handled in dot())
|
|
f := p.table.find_fn(name)
|
|
if f.name == '' {
|
|
println(p.cur_fn.name)
|
|
println(p.cur_fn.args.len)
|
|
// if !p.first_run() && !p.translated {
|
|
if !p.first_run() {
|
|
// println('name_expr():')
|
|
// If orig_name is a pkg, then printing undefined: `pkg` tells us nothing
|
|
if p.table.known_pkg(orig_name) {
|
|
name = name.replace('__', '.')
|
|
p.error('undefined: `$name`')
|
|
}
|
|
else {
|
|
p.error('undefined: `$orig_name`')
|
|
}
|
|
}
|
|
p.next()
|
|
return 'void'
|
|
}
|
|
// no () after func, so func is an argument, just gen its name
|
|
// TODO verify this and handle errors
|
|
if p.peek() != LPAR {
|
|
p.gen(p.table.cgen_name(f))
|
|
p.next()
|
|
return 'void*'
|
|
}
|
|
// TODO bring back
|
|
if f.typ == 'void' && !p.inside_if_expr {
|
|
// p.error('`$f.name` used as value')
|
|
}
|
|
p.log('calling function')
|
|
p.fn_call(f, 0, '', '')
|
|
// dot after a function call: `get_user().age`
|
|
if p.tok == DOT {
|
|
mut typ := ''
|
|
for p.tok == DOT {
|
|
// println('dot #$dc')
|
|
typ = p.dot(f.typ, 0)
|
|
}
|
|
return typ
|
|
}
|
|
p.log('end of name_expr')
|
|
return f.typ
|
|
}
|
|
|
|
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')
|
|
// p.gen('VAR EXPR ')
|
|
p.cur_fn.mark_var_used(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
|
|
// fn_pointer()
|
|
if typ.starts_with('fn ') {
|
|
println('CALLING FN PTR')
|
|
p.print_tok()
|
|
T := p.table.find_type(typ)
|
|
p.gen('(')
|
|
p.fn_call_args(T.func)
|
|
p.gen(')')
|
|
typ = T.func.typ
|
|
}
|
|
// users[0] before dot so that we can have
|
|
// users[0].name
|
|
if p.tok == LSBR {
|
|
typ = p.index_expr(typ, fn_ph)
|
|
// ////println('QQQQ KEK $typ')
|
|
}
|
|
// a.b.c().d chain
|
|
// mut dc := 0
|
|
for p.tok == DOT {
|
|
// 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, v)
|
|
}
|
|
}
|
|
// a++ and a--
|
|
if p.tok == INC || p.tok == DEC {
|
|
if !v.is_mut && !v.is_arg && !p.translated {
|
|
p.error('`$v.name` is immutable')
|
|
}
|
|
if typ != 'int' {
|
|
if !p.translated && !is_number_type(typ) {
|
|
// if T.parent != 'int' {
|
|
p.error('cannot ++/-- value of type `$typ`')
|
|
}
|
|
}
|
|
p.gen(p.tok.str())
|
|
p.fgen(p.tok.str())
|
|
p.next()// ++
|
|
// allow a := c++ in translated
|
|
if p.translated {
|
|
return p.index_expr(typ, fn_ph)
|
|
// return typ
|
|
}
|
|
else {
|
|
return 'void'
|
|
}
|
|
}
|
|
typ = p.index_expr(typ, fn_ph)
|
|
return typ
|
|
}
|
|
|
|
fn (p &Parser) fileis(s string) bool {
|
|
return p.scanner.file_path.contains(s)
|
|
}
|
|
|
|
// 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 {
|
|
p.check(DOT)
|
|
field_name := p.lit
|
|
p.fgen(field_name)
|
|
p.log('dot() field_name=$field_name typ=$str_typ')
|
|
if p.fileis('hi_test') {
|
|
println('dot() field_name=$field_name typ=$str_typ')
|
|
}
|
|
typ := p.find_type(str_typ)
|
|
if typ.name.len == 0 {
|
|
p.error('dot(): cannot find type `$str_typ`')
|
|
}
|
|
has_field := p.table.type_has_field(typ, field_name)
|
|
has_method := p.table.type_has_method(typ, field_name)
|
|
if !typ.is_c && !has_field && !has_method && !p.first_run() {
|
|
// println(typ.str())
|
|
if typ.name.starts_with('Option_') {
|
|
opt_type := typ.name.substr(7, typ.name.len)
|
|
p.error('unhandled option type: $opt_type?')
|
|
}
|
|
println('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('type `$typ.name` has no field or method `$field_name`')
|
|
}
|
|
mut dot := '.'
|
|
if str_typ.contains('*') {
|
|
dot = '->'
|
|
}
|
|
// field
|
|
if has_field {
|
|
field := p.table.find_field(typ, field_name)
|
|
// Is the next token `=`, `+=` etc? (Are we modifying the field?)
|
|
next := p.peek()
|
|
modifying := next.is_assign() || next == INC || next == DEC
|
|
is_vi := p.fileis('vi')
|
|
if !p.builtin_pkg && !p.translated && modifying && !field.is_mut && !is_vi {
|
|
p.error('cannot modify immutable field `$field_name` (type `$typ.name`)')
|
|
}
|
|
if !p.builtin_pkg && p.pkg != typ.pkg {
|
|
}
|
|
// if p.is_play && field.access_mod == PRIVATE && !p.builtin_pkg && p.pkg != typ.pkg {
|
|
// Don't allow `arr.data`
|
|
if field.access_mod == PRIVATE && !p.builtin_pkg && !p.translated && p.pkg != typ.pkg {
|
|
// println('$typ.name :: $field.name ')
|
|
// println(field.access_mod)
|
|
p.error('cannot refer to unexported field `$field_name` (type `$typ.name`)')
|
|
}
|
|
// if field.access_mod == PUBLIC && p.peek() == ASSIGN && !p.builtin_pkg && p.pkg != typ.pkg {
|
|
// Don't allow `str.len = 0`
|
|
if field.access_mod == PUBLIC && !p.builtin_pkg && p.pkg != typ.pkg {
|
|
// if field.name == 'age' {
|
|
// println('HOHOH')
|
|
// println(next.str())
|
|
// }
|
|
if !field.is_mut && !p.translated && modifying {
|
|
p.error('cannot modify public immutable field `$field_name` (type `$typ.name`)')
|
|
}
|
|
}
|
|
p.gen('${dot}${field_name}')
|
|
// p.gen(dot + field_name)
|
|
p.next()
|
|
return field.typ
|
|
}
|
|
// method
|
|
// mut method := typ.find_method(field_name)
|
|
mut method := p.table.find_method(typ, field_name)
|
|
p.fn_call(method, method_ph, '', str_typ)
|
|
// Methods returning "array" (like slice_fast) should return "array_string"
|
|
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 typ.name.replace('array_', '')
|
|
return typ.name.right(6)
|
|
}
|
|
if false && p.tok == LSBR {
|
|
// if is_indexer {
|
|
return p.index_expr(method.typ, method_ph)
|
|
}
|
|
return method.typ
|
|
}
|
|
|
|
fn (p mut Parser) index_expr(typ string, fn_ph int) string {
|
|
if p.fileis('int_test') {
|
|
println('index expr typ=$typ')
|
|
}
|
|
// a[0]
|
|
v := p.expr_var
|
|
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('*')
|
|
is_indexer := p.tok == LSBR
|
|
mut close_bracket := false
|
|
if is_indexer {
|
|
is_fixed_arr := typ[0] == `[`
|
|
if !is_str && !is_arr && !is_map && !is_ptr && !is_fixed_arr {
|
|
p.error('Cant [] non-array/string/map. Got type "$typ"')
|
|
}
|
|
p.check(LSBR)
|
|
// Get element type (set `typ` to it)
|
|
if is_str {
|
|
typ = 'byte'
|
|
p.fgen('[')
|
|
// Direct faster access to .str[i] in builtin package
|
|
if p.builtin_pkg {
|
|
p.gen('.str[')
|
|
close_bracket = true
|
|
}
|
|
else {
|
|
// Bounds check everywhere else
|
|
p.gen(',')
|
|
}
|
|
}
|
|
if is_fixed_arr {
|
|
// `[10]int` => `int`, `[10][3]int` => `[3]int`
|
|
if typ.contains('][') {
|
|
pos := typ.index_after('[', 1)
|
|
typ = typ.right(pos)
|
|
}
|
|
else {
|
|
typ = typ.all_after(']')
|
|
}
|
|
p.gen('[')
|
|
close_bracket = true
|
|
}
|
|
else if is_ptr {
|
|
// typ = 'byte'
|
|
typ = typ.replace('*', '')
|
|
// modify(mut []string) fix
|
|
if !is_arr {
|
|
p.gen('[/*ptr*/')
|
|
close_bracket = true
|
|
}
|
|
}
|
|
if is_arr {
|
|
p.fgen('[')
|
|
// array_int a; a[0]
|
|
// type is "array_int", need "int"
|
|
// typ = typ.replace('array_', '')
|
|
if is_arr0 {
|
|
if p.fileis('int_test') {
|
|
println('\nRRRR0 $typ')
|
|
}
|
|
typ = typ.right(6)
|
|
if p.fileis('int_test') {
|
|
println('RRRR $typ')
|
|
}
|
|
}
|
|
// array a; a.first() voidptr
|
|
// type is "array", need "void*"
|
|
if typ == 'array' {
|
|
typ = 'void*'
|
|
}
|
|
// No bounds check in translated from C code
|
|
if p.translated {
|
|
// Cast void* to typ*: add (typ*) to the beginning of the assignment :
|
|
// ((int*)a.data = ...
|
|
p.cgen.set_placeholder(fn_ph, '(($typ*)(')
|
|
p.gen('.data))[')
|
|
}
|
|
else {
|
|
p.gen(',')
|
|
}
|
|
}
|
|
// 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_', '')
|
|
if typ == 'map' {
|
|
typ = 'void*'
|
|
}
|
|
p.gen(',')
|
|
}
|
|
// expression inside [ ]
|
|
if is_arr {
|
|
T := p.table.find_type(p.expression())
|
|
if T.parent != 'int' {
|
|
p.check_types(T.name, 'int')
|
|
}
|
|
}
|
|
else {
|
|
p.expression()
|
|
}
|
|
p.check(RSBR)
|
|
// if (is_str && p.builtin_pkg) || is_ptr || is_fixed_arr && ! (is_ptr && is_arr) {
|
|
if close_bracket {
|
|
p.gen(']/*r$typ $v.is_mut*/')
|
|
}
|
|
}
|
|
// TODO if p.tok in ...
|
|
// if p.tok in [ASSIGN, PLUS_ASSIGN, MINUS_ASSIGN]
|
|
if p.tok == ASSIGN || p.tok == PLUS_ASSIGN || p.tok == MINUS_ASSIGN ||
|
|
p.tok == MULT_ASSIGN || p.tok == DIV_ASSIGN || p.tok == XOR_ASSIGN || p.tok == MOD_ASSIGN ||
|
|
p.tok == OR_ASSIGN || p.tok == AND_ASSIGN || p.tok == RIGHT_SHIFT_ASSIGN ||
|
|
p.tok == LEFT_SHIFT_ASSIGN {
|
|
if is_map || is_arr {
|
|
// Don't generate indexer right away, but assign it to tmp
|
|
// p.cgen.start_tmp()
|
|
}
|
|
if is_indexer && is_str && !p.builtin_pkg {
|
|
p.error('strings are immutable')
|
|
}
|
|
// println('111 "$p.cgen.cur_line"')
|
|
assign_pos := p.cgen.cur_line.len
|
|
p.assigned_type = typ
|
|
p.assign_statement(v, fn_ph, is_indexer && (is_map || is_arr))
|
|
// m[key] = val
|
|
if is_indexer && (is_map || is_arr) {
|
|
// a[0] = 7
|
|
// curline right now: "a , 0 = 7"
|
|
// println('222 "$p.cgen.cur_line"')
|
|
// Cant have &7, so need a tmp
|
|
tmp := p.get_tmp()
|
|
tmp_val := p.cgen.cur_line.right(assign_pos)
|
|
p.cgen.cur_line = p.cgen.cur_line.left(assign_pos)
|
|
// val := p.cgen.end_tmp()
|
|
if is_map {
|
|
p.cgen.set_placeholder(fn_ph, 'map__set(&')
|
|
}
|
|
else {
|
|
if is_ptr {
|
|
p.cgen.set_placeholder(fn_ph, 'array_set(')
|
|
}
|
|
else {
|
|
p.cgen.set_placeholder(fn_ph, 'array_set(&/*q*/')
|
|
}
|
|
}
|
|
p.gen(', & $tmp)')
|
|
p.cgen.insert_before('$typ $tmp = $tmp_val;')
|
|
}
|
|
return typ
|
|
return 'void'
|
|
}
|
|
// else if p.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_pkg)) && is_indexer {
|
|
// Erase var name we generated earlier: "int a = m, 0"
|
|
// "m, 0" gets killed since we need to start from scratch. It's messy.
|
|
// "m, 0" is an index expression, save it before deleting and insert later in map_get()
|
|
index_expr := p.cgen.cur_line.right(fn_ph)
|
|
p.cgen.cur_line = p.cgen.cur_line.left(fn_ph)
|
|
// Can't pass integer literal, because map_get() requires a void*
|
|
tmp := p.get_tmp()
|
|
tmp_ok := p.get_tmp()
|
|
if is_map {
|
|
p.gen('$tmp')
|
|
def := type_default(typ)
|
|
p.cgen.insert_before('$typ $tmp = $def; bool $tmp_ok = map_get($index_expr, & $tmp);')
|
|
}
|
|
else if is_arr {
|
|
if p.translated {
|
|
p.gen('$index_expr ]')
|
|
}
|
|
else {
|
|
p.gen('( *($typ*) array__get($index_expr) )')
|
|
}
|
|
}
|
|
else if is_str && !p.builtin_pkg {
|
|
p.gen('string_at($index_expr)')
|
|
}
|
|
// Zero the string after map_get() if it's nil, numbers are automatically 0
|
|
// This is ugly, but what can I do without generics?
|
|
// TODO what about user types?
|
|
if is_map && typ == 'string' {
|
|
// p.cgen.insert_before('if (!${tmp}.str) $tmp = tos("", 0);')
|
|
p.cgen.insert_before('if (!$tmp_ok) $tmp = tos("", 0);')
|
|
}
|
|
}
|
|
// else if is_arr && is_indexer{}
|
|
return typ
|
|
}
|
|
|
|
// returns resulting type
|
|
fn (p mut Parser) expression() string {
|
|
if p.scanner.file_path.contains('test_test') {
|
|
println('epxression() pass=$p.run tok=')
|
|
p.print_tok()
|
|
}
|
|
p.cgen('/* expr start*/')
|
|
ph := p.cgen.add_placeholder()
|
|
mut typ := p.term()
|
|
is_str := typ.eq('string')
|
|
// a << b ==> array2_push(&a, b)
|
|
if p.tok == LEFT_SHIFT {
|
|
if typ.contains('array_') {
|
|
// Can't pass integer literal, because push requires a void*
|
|
// a << 7 => int tmp = 7; array_push(&a, &tmp);
|
|
// _PUSH(&a, expression(), tmp, string)
|
|
tmp := p.get_tmp()
|
|
tmp_typ := typ.right(6)// skip "array_"
|
|
p.next()
|
|
// Get the value we are pushing
|
|
p.gen(', (')
|
|
// Immutable? Can we push?
|
|
if !p.expr_var.is_mut && !p.translated {
|
|
p.error('`$p.expr_var.name` is immutable (can\'t <<)')
|
|
}
|
|
p.check_types(p.expression(), tmp_typ)
|
|
// Pass tmp var info to the _PUSH macro
|
|
p.gen('), $tmp, $tmp_typ)')
|
|
// Prepend tmp initialisation and push call
|
|
// Don't dereference if it's already a mutable array argument (`fn foo(mut []int)`)
|
|
push_call := if typ.contains('*'){'_PUSH('} else { '_PUSH(&'} // p.cgen.set_placeholder(ph, '_PUSH(&')
|
|
p.cgen.set_placeholder(ph, push_call)
|
|
return 'void'
|
|
}
|
|
else {
|
|
p.next()
|
|
p.gen(' << ')
|
|
p.check_types(p.expression(), typ)
|
|
return 'int'
|
|
}
|
|
}
|
|
// a in [1,2,3]
|
|
if p.tok == IN {
|
|
p.fgen(' ')
|
|
p.check(IN)
|
|
p.fgen(' ')
|
|
p.gen(', ')
|
|
arr_typ := p.expression()
|
|
if !arr_typ.starts_with('array_') {
|
|
p.error('`in` requires an array')
|
|
}
|
|
T := p.table.find_type(arr_typ)
|
|
if !T.has_method('contains') {
|
|
p.error('$arr_typ has no method `contains`')
|
|
}
|
|
// `typ` is element type
|
|
p.cgen.set_placeholder(ph, '_IN($typ, ')
|
|
p.gen(')')
|
|
return 'bool'
|
|
}
|
|
if p.tok == RIGHT_SHIFT {
|
|
p.next()
|
|
p.gen(' >> ')
|
|
p.check_types(p.expression(), typ)
|
|
return 'int'
|
|
}
|
|
if p.tok == DOT {
|
|
for p.tok == DOT {
|
|
typ = p.dot(typ, 0)
|
|
}
|
|
}
|
|
// + - |
|
|
for p.tok == PLUS || p.tok == MINUS || p.tok == PIPE || p.tok == AMP || p.tok == XOR {
|
|
// for p.tok in [PLUS, MINUS, PIPE, AMP, XOR] {
|
|
tok_op := p.tok
|
|
is_num := typ == 'void*' || typ == 'byte*' || is_number_type(typ)
|
|
p.next()
|
|
if is_str && tok_op == PLUS {
|
|
p.cgen.set_placeholder(ph, 'string_add(')
|
|
p.gen(',')
|
|
}
|
|
// 3 + 4
|
|
else if is_num {
|
|
p.gen(tok_op.str())
|
|
}
|
|
// Vec + Vec
|
|
else {
|
|
if p.translated {
|
|
p.gen(tok_op.str() + ' /*doom hack*/')// TODO hack to fix DOOM's angle_t
|
|
}
|
|
else {
|
|
p.gen(',')
|
|
}
|
|
}
|
|
p.check_types(p.term(), typ)
|
|
if is_str && tok_op == PLUS {
|
|
p.gen(')')
|
|
}
|
|
// Make sure operators are used with correct types
|
|
if !p.translated && !is_str && !is_num {
|
|
T := p.table.find_type(typ)
|
|
if tok_op == PLUS {
|
|
if T.has_method('+') {
|
|
p.cgen.set_placeholder(ph, typ + '_plus(')
|
|
p.gen(')')
|
|
}
|
|
else {
|
|
p.error('operator + not defined on `$typ`')
|
|
}
|
|
}
|
|
else if tok_op == MINUS {
|
|
if T.has_method('-') {
|
|
p.cgen.set_placeholder(ph, '${typ}_minus(')
|
|
p.gen(')')
|
|
}
|
|
else {
|
|
p.error('operator - not defined on `$typ`')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return typ
|
|
}
|
|
|
|
fn (p mut Parser) term() string {
|
|
line_nr := p.scanner.line_nr
|
|
if p.fileis('fn_test') {
|
|
println('\nterm() $line_nr')
|
|
}
|
|
typ := p.unary()
|
|
if p.fileis('fn_test') {
|
|
println('2: $line_nr')
|
|
}
|
|
// `*` on a newline? Can't be multiplication, only dereference
|
|
if p.tok == MUL && line_nr != p.scanner.line_nr {
|
|
return typ
|
|
}
|
|
for p.tok == MUL || p.tok == DIV || p.tok == MOD {
|
|
tok := p.tok
|
|
is_div := tok == DIV
|
|
is_mod := tok == MOD
|
|
// is_mul := tok == MOD
|
|
p.next()
|
|
p.gen(tok.str())// + ' /*op2*/ ')
|
|
p.fgen(' ' + tok.str() + ' ')
|
|
if is_div && p.tok == INT && p.lit == '0' {
|
|
p.error('division by zero')
|
|
}
|
|
if is_mod && (is_float_type(typ) || !is_number_type(typ)) {
|
|
p.error('operator MOD requires integer types')
|
|
}
|
|
p.check_types(p.unary(), typ)
|
|
}
|
|
return typ
|
|
}
|
|
|
|
fn (p mut Parser) unary() string {
|
|
mut typ := ''
|
|
tok := p.tok
|
|
switch tok {
|
|
case NOT:
|
|
p.gen('!')
|
|
p.next()
|
|
typ = 'bool'
|
|
p.bool_expression()
|
|
case BIT_NOT:
|
|
p.gen('~')
|
|
p.next()
|
|
typ = p.bool_expression()
|
|
default:
|
|
typ = p.factor()
|
|
}
|
|
return typ
|
|
}
|
|
|
|
fn (p mut Parser) factor() string {
|
|
mut typ := ''
|
|
tok := p.tok
|
|
switch tok {
|
|
case INT:
|
|
p.gen(p.lit)
|
|
p.fgen(p.lit)
|
|
typ = 'int'
|
|
// typ = 'number'
|
|
if p.lit.starts_with('u') {
|
|
typ = 'long'
|
|
}
|
|
if p.lit.contains('.') || p.lit.contains('e') {
|
|
// typ = 'f64'
|
|
typ = 'float'
|
|
}
|
|
case FLOAT:
|
|
// TODO remove float
|
|
typ = 'float'
|
|
// typ = 'f64'
|
|
// p.gen('(f64)$p.lit')
|
|
p.gen('$p.lit')
|
|
p.fgen(p.lit)
|
|
case MINUS:
|
|
p.gen('-')
|
|
p.fgen('-')
|
|
p.next()
|
|
return p.factor()
|
|
// Variable
|
|
case SIZEOF:
|
|
p.gen('sizeof(')
|
|
p.fgen('sizeof(')
|
|
p.next()
|
|
p.check(LPAR)
|
|
mut sizeof_typ := p.get_type()
|
|
if sizeof_typ.ends_with('*') {
|
|
// Move * from the end to the beginning, as C requires
|
|
sizeof_typ = '*' + sizeof_typ.left(sizeof_typ.len - 1)
|
|
}
|
|
p.check(RPAR)
|
|
p.gen('$sizeof_typ)')
|
|
p.fgen('$sizeof_typ)')
|
|
return 'int'
|
|
case AMP:
|
|
return p.name_expr()
|
|
case DOT:
|
|
return p.name_expr()// `.green` (enum)
|
|
case MUL:
|
|
return p.name_expr()
|
|
case NAME:
|
|
// map[string]int
|
|
if p.lit == 'map' && p.peek() == LSBR {
|
|
return p.map_init()
|
|
}
|
|
if p.lit == 'json' && p.peek() == DOT {
|
|
return p.js_decode()
|
|
}
|
|
typ = p.name_expr()
|
|
return typ
|
|
case DEFAULT:
|
|
p.next()
|
|
p.next()
|
|
name := p.check_name()
|
|
if name != 'T' {
|
|
p.error('default needs T')
|
|
}
|
|
p.gen('default(T)')
|
|
p.next()
|
|
return 'T'
|
|
case LPAR:
|
|
p.gen('(/*lpar*/')
|
|
p.next()// (
|
|
typ = p.bool_expression()
|
|
// Hack. If this `)` referes to a ptr cast `(*int__)__`, it was already checked
|
|
// TODO: fix parser so that it doesn't think it's a par expression when it sees `(` in
|
|
// __(__*int)(
|
|
if !p.ptr_cast {
|
|
p.check(RPAR)
|
|
}
|
|
p.ptr_cast = false
|
|
p.gen(')')
|
|
return typ
|
|
case CHAR:
|
|
p.char_expr()
|
|
typ = 'byte'
|
|
return typ
|
|
case STRING:
|
|
p.string_expr()
|
|
typ = 'string'
|
|
return typ
|
|
case FALSE:
|
|
typ = 'bool'
|
|
p.gen('0')
|
|
p.fgen('false')
|
|
case TRUE:
|
|
typ = 'bool'
|
|
p.gen('1')
|
|
p.fgen('true')
|
|
case LSBR:
|
|
// `[1,2,3]` or `[]` or `[20]byte`
|
|
// TODO have to return because arrayInit does next()
|
|
// everything should do next()
|
|
return p.array_init()
|
|
case LCBR:
|
|
// { user | name :'new name' }
|
|
return p.assoc()
|
|
case IF:
|
|
typ = p.if_st(true)
|
|
return typ
|
|
default:
|
|
next := p.peek()
|
|
println('PREV=${p.prev_tok.str()}')
|
|
println('NEXT=${next.str()}')
|
|
p.error('unexpected token: `${p.tok.str()}`')
|
|
}
|
|
p.next()// TODO everything should next()
|
|
return typ
|
|
}
|
|
|
|
// { user | name: 'new name' }
|
|
fn (p mut Parser) assoc() string {
|
|
// println('assoc()')
|
|
p.next()
|
|
name := p.check_name()
|
|
if !p.cur_fn.known_var(name) {
|
|
p.error('unknown variable `$name`')
|
|
}
|
|
var := p.cur_fn.find_var(name)
|
|
p.check(PIPE)
|
|
p.gen('($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()
|
|
fields << field
|
|
p.gen('.$field = ')
|
|
p.check(COLON)
|
|
p.bool_expression()
|
|
p.gen(',')
|
|
if p.tok != RCBR {
|
|
p.check(COMMA)
|
|
}
|
|
}
|
|
// Copy the rest of the fields
|
|
T := p.table.find_type(var.typ)
|
|
for ffield in T.fields {
|
|
f := ffield.name
|
|
if f in fields {
|
|
continue
|
|
}
|
|
p.gen('.$f = $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 {
|
|
str = str.replace('"', '\\"')
|
|
str = str.replace('\n', '\\n')
|
|
return str
|
|
}
|
|
|
|
fn (p mut Parser) typ_to_fmt(typ string) string {
|
|
t := p.table.find_type(typ)
|
|
if t.parent == 'int' {
|
|
return '%d'
|
|
}
|
|
switch typ {
|
|
case 'string': return '%.*s'
|
|
case 'ustring': return '%.*s'
|
|
case 'long': return '%ld'
|
|
case 'byte': return '%d'
|
|
case 'int': return '%d'
|
|
case 'char': return '%d'
|
|
case 'byte': return '%d'
|
|
case 'bool': return '%d'
|
|
case 'u32': return '%d'
|
|
case 'float': return '%f'
|
|
case 'double', 'f64': return '%f'
|
|
case 'i64': return '%lld'
|
|
case 'byte*': return '%s'
|
|
// case 'array_string': return '%s'
|
|
// case 'array_int': return '%s'
|
|
case 'void': p.error('cannot interpolate this value')
|
|
default:
|
|
p.error('unhandled sprintf format "$typ" ')
|
|
}
|
|
return ''
|
|
}
|
|
|
|
fn (p mut Parser) string_expr() {
|
|
// println('STRING EXPR')
|
|
str := p.lit
|
|
p.fgen('\'$str\'')
|
|
// No ${}, just return simple string
|
|
if p.peek() != DOLLAR {
|
|
// println('before format: "$str"')
|
|
f := format_str(str)
|
|
// println('after format: "$str"')
|
|
if p.calling_c || p.translated {
|
|
p.gen('"$f"')
|
|
}
|
|
else {
|
|
p.gen('tos2("$f")')// TODO dont call strlen here
|
|
}
|
|
p.next()
|
|
return
|
|
}
|
|
// tmp := p.get_tmp()
|
|
mut args := '"'
|
|
mut format := '"'
|
|
for p.tok == STRING {
|
|
// Add the string between %d's
|
|
format += format_str(p.lit)
|
|
p.next()// skip $
|
|
if p.tok != DOLLAR {
|
|
continue
|
|
}
|
|
// Handle DOLLAR
|
|
p.next()
|
|
// Get bool expr inside a temp var
|
|
p.cgen.start_tmp()
|
|
typ := p.bool_expression()
|
|
mut val := p.cgen.end_tmp()
|
|
val = val.trim_space()
|
|
args += ', $val'
|
|
if typ == 'string' {
|
|
// args += '.str'
|
|
// printf("%.*s", a.len, a.str) syntax
|
|
args += '.len, ${val}.str'
|
|
}
|
|
if typ == 'ustring' {
|
|
args += '.len, ${val}.s.str'
|
|
}
|
|
// Custom format? ${t.hour:02d}
|
|
custom := p.tok == COLON
|
|
if custom {
|
|
format += '%'
|
|
p.next()
|
|
if p.tok == DOT {
|
|
format += '.'
|
|
p.next()
|
|
}
|
|
format += p.lit// 02
|
|
p.next()
|
|
format += p.lit// f
|
|
// println('custom str F=$format')
|
|
p.next()
|
|
}
|
|
else {
|
|
format += p.typ_to_fmt(typ)
|
|
}
|
|
}
|
|
// println("hello %d", num) optimization.
|
|
if p.cgen.nogen {
|
|
return
|
|
}
|
|
// Don't allocate a new string, just print it. TODO HACK PRINT OPT
|
|
cur_line := p.cgen.cur_line.trim_space()
|
|
if cur_line.contains('println(') && p.tok != PLUS && !p.is_prod && !cur_line.contains('string_add') {
|
|
p.cgen.cur_line = cur_line.replace('println(', 'printf(')
|
|
p.gen('$format\\n$args')
|
|
return
|
|
}
|
|
// '$age'! means the user wants this to be a tmp string (uses global buffer, no allocation,
|
|
// won't be used again)
|
|
if p.tok == NOT {
|
|
p.next()
|
|
p.gen('_STR_TMP($format$args)')
|
|
}
|
|
else {
|
|
// Otherwise do ugly len counting + allocation + sprintf
|
|
p.gen('_STR($format$args)')
|
|
}
|
|
}
|
|
|
|
// m := map[string]int{}
|
|
fn (p mut Parser) map_init() string {
|
|
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.check_name()
|
|
if !p.table.known_type(val_type) {
|
|
p.error('map init unknown type "$val_type"')
|
|
}
|
|
p.gen('new_map(1, sizeof($val_type))')
|
|
p.check(LCBR)
|
|
p.check(RCBR)
|
|
return 'map_$val_type'
|
|
}
|
|
|
|
// [1,2,3]
|
|
fn (p mut Parser) array_init() string {
|
|
p.check(LSBR)
|
|
is_integer := p.tok == INT
|
|
lit := p.lit
|
|
mut typ := ''
|
|
new_arr_ph := p.cgen.add_placeholder()
|
|
mut i := 0
|
|
pos := p.cgen.cur_line.len// remember cur line to fetch first number in cgen for [0; 10]
|
|
for p.tok != RSBR {
|
|
val_typ := p.bool_expression()
|
|
// Get 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 {
|
|
nextc := p.scanner.text[p.scanner.pos + 1]
|
|
// TODO whitespace hack
|
|
// Make sure there's no space in `[10]byte`
|
|
if !nextc.is_space() {
|
|
p.check(RSBR)
|
|
name := p.check_name()
|
|
if p.table.known_type(name) {
|
|
p.cgen.cur_line = ''
|
|
p.gen('{} /* arkek init*/')
|
|
return '[$lit]$name'
|
|
}
|
|
else {
|
|
p.error('bad type `$name`')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if val_typ != typ {
|
|
if !p.check_types_no_throw(val_typ, typ) {
|
|
p.error('bad array element type `$val_typ` instead of `$typ`')
|
|
}
|
|
}
|
|
if p.tok != RSBR && p.tok != SEMICOLON {
|
|
p.gen(',')
|
|
p.check(COMMA)
|
|
}
|
|
i++
|
|
// Repeat (a = [0;5] )
|
|
if i == 1 && p.tok == SEMICOLON {
|
|
p.check_space(SEMICOLON)
|
|
val := p.cgen.cur_line.right(pos)
|
|
// p.cgen.cur_line = ''
|
|
p.cgen.cur_line = p.cgen.cur_line.left(pos)
|
|
// Special case for zero
|
|
if false && val.trim_space() == '0' {
|
|
p.gen('array_repeat( & V_ZERO, ')
|
|
}
|
|
else {
|
|
tmp := p.get_tmp()
|
|
p.cgen.insert_before('$typ $tmp = $val;')
|
|
p.gen('array_repeat(&$tmp, ')
|
|
}
|
|
p.check_types(p.bool_expression(), 'int')
|
|
p.gen(', sizeof($typ) )')
|
|
p.check(RSBR)
|
|
return 'array_$typ'
|
|
}
|
|
}
|
|
p.check(RSBR)
|
|
// type after `]`? (e.g. "[]string")
|
|
if p.tok != NAME && i == 0 {
|
|
p.error('specify array type: `[]typ` instead of `[]`')
|
|
}
|
|
if p.tok == NAME && i == 0 {
|
|
// vals.len == 0 {
|
|
typ = p.get_type()
|
|
// println('GOT TYP after [] $typ')
|
|
}
|
|
// ! 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_run() {
|
|
// 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"
|
|
// }
|
|
mut new_arr := 'new_array_from_c_array'
|
|
if no_alloc {
|
|
new_arr += '_no_alloc'
|
|
}
|
|
p.gen(' })')
|
|
// p.gen('$new_arr($vals.len, $vals.len, sizeof($typ), ($typ[]) $c_arr );')
|
|
// TODO why need !first_run()?? Otherwise it goes to the very top of the out.c file
|
|
if !p.first_run() {
|
|
p.cgen.set_placeholder(new_arr_ph, '$new_arr($i, $i, sizeof($typ), ($typ[]) { ')
|
|
}
|
|
typ = 'array_$typ'
|
|
p.register_array(typ)
|
|
return typ
|
|
}
|
|
|
|
fn (p mut Parser) register_array(typ string) {
|
|
if typ.contains('*') {
|
|
println('bad arr $typ')
|
|
return
|
|
}
|
|
if !p.table.known_type(typ) {
|
|
p.register_type_with_parent(typ, 'array')
|
|
p.cgen.typedefs << 'typedef array $typ;'
|
|
}
|
|
}
|
|
|
|
// name == 'User'
|
|
fn (p mut Parser) struct_init(is_c_struct_init bool) string {
|
|
p.is_struct_init = true
|
|
mut typ := p.get_type()
|
|
p.scanner.fmt_out.cut(typ.len)
|
|
ptr := typ.contains('*')
|
|
p.check(LCBR)
|
|
// tmp := p.get_tmp()
|
|
if !ptr {
|
|
if typ == 'tm' {
|
|
p.gen('(struct tm) {')// TODO struct tm hack, handle all C structs
|
|
}
|
|
else {
|
|
p.gen('($typ){')
|
|
}
|
|
}
|
|
else {
|
|
// TODO tmp hack for 0 pointers init
|
|
// &User{!} ==> 0
|
|
if p.tok == NOT {
|
|
p.next()
|
|
p.gen('0')
|
|
p.check(RCBR)
|
|
return typ
|
|
}
|
|
mut type_gen := typ.replace('*', '')
|
|
// All V types are typedef'ed, C structs aren't, so we need to prepend "struct "
|
|
if is_c_struct_init {
|
|
type_gen = 'struct $type_gen'
|
|
}
|
|
// p.gen('malloc(sizeof($type_gen)); \n')
|
|
no_star := typ.replace('*', '')
|
|
p.gen('ALLOC_INIT($no_star, {')
|
|
}
|
|
// Loop thru all struct init keys and assign values
|
|
// u := User{age:20, name:'bob'}
|
|
// Remember which fields were set, so that we dont have to zero them later
|
|
mut inited_fields := []string
|
|
peek := p.peek()
|
|
if peek == COLON || p.tok == RCBR {
|
|
t := p.table.find_type(typ)
|
|
for p.tok != RCBR {
|
|
field := p.check_name()
|
|
if !t.has_field(field) {
|
|
p.error('`$t.name` has no field `$field`')
|
|
}
|
|
inited_fields << field
|
|
p.gen('.$field = ')
|
|
p.check(COLON)
|
|
p.fspace()
|
|
p.expression()
|
|
if p.tok == COMMA {
|
|
p.next()
|
|
}
|
|
if p.tok != RCBR {
|
|
p.gen(',')
|
|
}
|
|
p.fgenln('')
|
|
}
|
|
// If we already set some fields, need to prepend a comma
|
|
if t.fields.len != inited_fields.len && inited_fields.len > 0 {
|
|
p.gen(',')
|
|
}
|
|
// Zero values: init all fields (ints to 0, strings to '' etc)
|
|
for i, field in t.fields {
|
|
// println('### field.name')
|
|
// Skip if this field has already been assigned to
|
|
if inited_fields.contains(field.name) {
|
|
continue
|
|
}
|
|
field_typ := field.typ
|
|
if !p.builtin_pkg && field_typ.ends_with('*') && field_typ.contains('Cfg') {
|
|
p.error('pointer field `${typ}.${field.name}` must be initialized')
|
|
}
|
|
def_val := type_default(field_typ)
|
|
if def_val != '' {
|
|
p.gen('.$field.name = $def_val')
|
|
if i != t.fields.len - 1 {
|
|
p.gen(',')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Point{3,4} syntax
|
|
else {
|
|
mut T := p.table.find_type(typ)
|
|
// Aliases (TODO Hack, implement proper aliases)
|
|
if T.fields.len == 0 && T.parent != '' {
|
|
T = p.table.find_type(T.parent)
|
|
}
|
|
for i, ffield in T.fields {
|
|
expr_typ := p.bool_expression()
|
|
if !p.check_types_no_throw(expr_typ, ffield.typ) {
|
|
p.error('field value #${i+1} `$ffield.name` has type `$ffield.typ`, got `$expr_typ` ')
|
|
}
|
|
if i < T.fields.len - 1 {
|
|
if p.tok != COMMA {
|
|
p.error('too few values in `$typ` literal (${i+1} instead of $T.fields.len)')
|
|
}
|
|
p.gen(',')
|
|
p.next()
|
|
}
|
|
}
|
|
// Allow `user := User{1,2,3,}`
|
|
// The final comma will be removed by vfmt, since we are not calling `p.fgen()`
|
|
if p.tok == COMMA {
|
|
p.next()
|
|
}
|
|
if p.tok != RCBR {
|
|
p.error('too many fields initialized: `$typ` has $T.fields.len field(s)')
|
|
}
|
|
}
|
|
p.gen('}')
|
|
if ptr {
|
|
p.gen(')')
|
|
}
|
|
p.check(RCBR)
|
|
p.is_struct_init = false
|
|
// println('struct init typ=$typ')
|
|
return typ
|
|
}
|
|
|
|
// `f32(3)`
|
|
// tok is `f32` or `)` if `(*int)(ptr)`
|
|
fn (p mut Parser) cast(typ string) string {
|
|
// typ := p.lit
|
|
if p.file_path.contains('test') {
|
|
println('CAST TYP=$typ tok=')
|
|
p.print_tok()
|
|
}
|
|
p.gen('($typ)(')
|
|
// p.fgen(typ)
|
|
p.next()
|
|
if p.tok == RPAR {
|
|
// skip `)` if it's `(*int)(ptr)`, not `int(a)`
|
|
p.ptr_cast = true
|
|
p.next()
|
|
}
|
|
p.check(LPAR)
|
|
p.gen('/*77*/')
|
|
expr_typ := p.bool_expression()
|
|
p.check(RPAR)
|
|
p.gen(')')
|
|
if typ == 'string' && expr_typ == 'int' {
|
|
p.error('cannot convert `$expr_typ` to `$typ`')
|
|
}
|
|
return typ
|
|
}
|
|
|
|
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) comp_time() {
|
|
p.next()
|
|
if p.tok == IF {
|
|
p.next()
|
|
not := p.tok == NOT
|
|
if not {
|
|
p.next()
|
|
}
|
|
name := p.check_name()
|
|
if name in SupportedPlatforms {
|
|
if not {
|
|
p.genln('#ifndef $name')
|
|
}
|
|
else {
|
|
p.genln('#ifdef $name')
|
|
}
|
|
p.check(LCBR)
|
|
p.statements_no_curly_end()
|
|
if ! (p.tok == DOLLAR && p.peek() == ELSE) {
|
|
p.genln('#endif')
|
|
}
|
|
}
|
|
else {
|
|
println('Supported platforms:')
|
|
println(SupportedPlatforms)
|
|
p.error('unknown platform `$name`')
|
|
}
|
|
}
|
|
else if p.tok == FOR {
|
|
p.next()
|
|
name := p.check_name()
|
|
if name != 'field' {
|
|
p.error('for field only')
|
|
}
|
|
p.check(IN)
|
|
p.check_name()
|
|
p.check(DOT)
|
|
p.check_name()// fields
|
|
p.check(LCBR)
|
|
// for p.tok != RCBR && p.tok != EOF {
|
|
res_name := p.check_name()
|
|
println(res_name)
|
|
p.check(DOT)
|
|
p.check(DOLLAR)
|
|
p.check(NAME)
|
|
p.check(ASSIGN)
|
|
p.cgen.start_tmp()
|
|
p.bool_expression()
|
|
val := p.cgen.end_tmp()
|
|
println(val)
|
|
p.check(RCBR)
|
|
// }
|
|
}
|
|
else if p.tok == ELSE {
|
|
p.next()
|
|
p.check(LCBR)
|
|
p.genln('#else')
|
|
p.statements_no_curly_end()
|
|
p.genln('#endif')
|
|
}
|
|
else {
|
|
p.error('bad comptime expr')
|
|
}
|
|
}
|
|
|
|
fn (p mut Parser) chash() {
|
|
hash := p.lit.trim_space()
|
|
// println('chsh() file=$p.file is_sig=${p.is_sig()} hash="$hash"')
|
|
p.next()
|
|
is_sig := p.is_sig()
|
|
if is_sig {
|
|
// p.cgen.nogen = true
|
|
}
|
|
if hash == 'live' {
|
|
if p.is_so {
|
|
return
|
|
}
|
|
p.is_live = true
|
|
return
|
|
}
|
|
if hash.starts_with('flag ') {
|
|
mut flag := hash.right(5)
|
|
// No the right os? Skip!
|
|
// mut ok := true
|
|
if hash.contains('linux') && p.os != LINUX {
|
|
return
|
|
}
|
|
else if hash.contains('darwin') && p.os != MAC {
|
|
return
|
|
}
|
|
else if hash.contains('windows') && p.os != WINDOWS {
|
|
return
|
|
}
|
|
// Remove "linux" etc from flag
|
|
if flag.contains('linux') || flag.contains('darwin') || flag.contains('windows') {
|
|
pos := flag.index(' ')
|
|
flag = flag.right(pos)
|
|
}
|
|
flag = flag.trim_space()
|
|
if p.table.flags.contains(flag) {
|
|
return
|
|
}
|
|
p.log('adding flag "$flag"')
|
|
p.table.flags << flag// .all_after(' '))
|
|
// }
|
|
return
|
|
}
|
|
if hash.starts_with('include') {
|
|
if p.first_run() && !is_sig {
|
|
p.cgen.includes << '#$hash'
|
|
return
|
|
}
|
|
}
|
|
else if hash.starts_with('typedef') {
|
|
if p.first_run() {
|
|
p.cgen.typedefs << '$hash'
|
|
}
|
|
}
|
|
// TODO remove after ui_mac.m is removed
|
|
else if hash.contains('embed') {
|
|
pos := hash.index('embed') + 5
|
|
file := hash.right(pos)
|
|
if p.build_mode != DEFAULT_MODE {
|
|
p.genln('#include $file')
|
|
}
|
|
}
|
|
else if is_c_pre(hash) {
|
|
// Skip not current OS hack
|
|
if hash.starts_with('ifdef') {
|
|
os := hash.right(6).trim_space()
|
|
// println('ifdef "$os" $p.scanner.line_nr')
|
|
if os == 'linux' && p.os != LINUX {
|
|
// println('linux ifdef skip')
|
|
for p.tok != EOF {
|
|
if p.tok == HASH && p.lit.contains('else') || p.lit.contains('endif') {
|
|
break
|
|
}
|
|
// println('skipping')
|
|
p.next()
|
|
}
|
|
}
|
|
}
|
|
// Move defines on top (like old gdefine)
|
|
if hash.contains('define') {
|
|
p.cgen.includes << '#$hash'
|
|
}
|
|
else {
|
|
p.genln('#$hash')
|
|
}
|
|
}
|
|
else {
|
|
if p.cur_fn.name == '' {
|
|
// p.error('# outside of fn')
|
|
}
|
|
p.genln(hash)
|
|
}
|
|
// p.cgen.nogen = false
|
|
// println('HASH=$hash')
|
|
}
|
|
|
|
fn is_c_pre(hash string) bool {
|
|
return hash.contains('ifdef') || hash.contains('define') ||
|
|
hash.contains('endif') || hash.contains('elif') ||
|
|
hash.contains('ifndef') || (hash.contains('else') && !hash.contains('{'))
|
|
}
|
|
|
|
fn (p mut Parser) if_st(is_expr bool) string {
|
|
if is_expr {
|
|
if p.fileis('if_expr') {
|
|
println('IF EXPR')
|
|
}
|
|
p.inside_if_expr = true
|
|
p.gen('(')
|
|
}
|
|
else {
|
|
p.gen('if (')
|
|
p.fgen('if ')
|
|
}
|
|
p.next()
|
|
p.check_types(p.bool_expression(), 'bool')
|
|
if is_expr {
|
|
p.gen(') ? (')
|
|
}
|
|
else {
|
|
p.genln(') {')
|
|
p.fgenln('{')
|
|
p.genln('/*if*/')
|
|
}
|
|
p.fgen(' ')
|
|
p.check(LCBR)
|
|
mut typ := ''
|
|
// if { if hack
|
|
if p.tok == IF && p.inside_if_expr {
|
|
println('AAAWWFAFAF')
|
|
typ = p.factor()
|
|
println('QWEWQE typ=$typ')
|
|
p.next()
|
|
}
|
|
else {
|
|
typ = p.statements()
|
|
}
|
|
// println('IF TYp=$typ')
|
|
if p.tok == ELSE {
|
|
p.next()
|
|
if p.tok == IF {
|
|
p.gen(' else ')
|
|
return p.if_st(is_expr)
|
|
// return ''
|
|
}
|
|
if is_expr {
|
|
p.gen(') : (')
|
|
}
|
|
else {
|
|
p.genln(' else { ')
|
|
p.genln('/*else if*/')
|
|
}
|
|
p.check(LCBR)
|
|
// statements() returns the type of the last statement
|
|
typ = p.statements()
|
|
p.inside_if_expr = false
|
|
if is_expr {
|
|
p.gen(')')
|
|
}
|
|
return typ
|
|
}
|
|
p.inside_if_expr = false
|
|
if p.fileis('test_test') {
|
|
println('if ret typ="$typ" line=$p.scanner.line_nr')
|
|
}
|
|
return typ
|
|
}
|
|
|
|
fn (p mut Parser) for_st() {
|
|
p.check(FOR)
|
|
p.fgen(' ')
|
|
p.for_expr_cnt++
|
|
next_tok := p.peek()
|
|
debug := p.scanner.file_path.contains('r_draw')
|
|
if debug {
|
|
println('\n\nFOR {')
|
|
}
|
|
p.cur_fn.open_scope()
|
|
if p.tok == LCBR {
|
|
// Infinite loop
|
|
p.gen('while (1) {')
|
|
}
|
|
else if p.tok == MUT {
|
|
p.error('`mut` is not required in for loops')
|
|
}
|
|
// for i := 0; i < 10; i++ {
|
|
else if next_tok == DECL_ASSIGN || next_tok == ASSIGN || p.tok == SEMICOLON {
|
|
if debug {
|
|
println('for 1')
|
|
}
|
|
p.genln('for (')
|
|
if next_tok == DECL_ASSIGN {
|
|
p.var_decl()
|
|
}
|
|
else if p.tok != SEMICOLON {
|
|
// allow `for ;; i++ {`
|
|
// Allow `for i = 0; i < ...`
|
|
p.statement(false)
|
|
}
|
|
if debug {
|
|
println('for 2')
|
|
}
|
|
p.check(SEMICOLON)
|
|
p.gen(' ; ')
|
|
p.fgen(' ')
|
|
if p.tok != SEMICOLON {
|
|
p.bool_expression()
|
|
}
|
|
if debug {
|
|
println('for 3')
|
|
}
|
|
p.check(SEMICOLON)
|
|
p.gen(' ; ')
|
|
p.fgen(' ')
|
|
if p.tok != LCBR {
|
|
p.statement(false)
|
|
}
|
|
if debug {
|
|
println('for 4')
|
|
}
|
|
p.fgen(' ')
|
|
p.genln(') { ')
|
|
}
|
|
// for i, val in array
|
|
else if p.peek() == COMMA {
|
|
// for i, val in array { ==>
|
|
//
|
|
// array_int tmp = array;
|
|
// for (int i = 0; i < tmp.len; i++) {
|
|
// int val = tmp[i];
|
|
i := p.check_name()
|
|
p.check(COMMA)
|
|
val := p.check_name()
|
|
p.fgen(' ')
|
|
p.check(IN)
|
|
p.fgen(' ')
|
|
tmp := p.get_tmp()
|
|
p.cgen.start_tmp()
|
|
typ := p.bool_expression()
|
|
expr := p.cgen.end_tmp()
|
|
p.genln('$typ $tmp = $expr ;')
|
|
var_typ := typ.right(6)
|
|
// typ = strings.Replace(typ, "_ptr", "*", -1)
|
|
// Register temp var
|
|
val_var := Var {
|
|
name: val
|
|
typ: var_typ
|
|
// parent_fn: p.cur_fn
|
|
ptr: typ.contains('*')
|
|
}
|
|
p.register_var(val_var)
|
|
i_var := Var {
|
|
name: i
|
|
typ: 'int'
|
|
// parent_fn: p.cur_fn
|
|
is_mut: true
|
|
}
|
|
p.register_var(i_var)
|
|
p.genln(';\nfor (int $i = 0; $i < $tmp .len; $i ++) {')
|
|
p.genln('$var_typ $val = (($var_typ *) $tmp . data)[$i];')
|
|
}
|
|
// `for val in vals`
|
|
else if p.peek() == IN {
|
|
val := p.check_name()
|
|
p.fgen(' ')
|
|
p.check(IN)
|
|
p.fspace()
|
|
tmp := p.get_tmp()
|
|
p.cgen.start_tmp()
|
|
typ := p.bool_expression()
|
|
expr := p.cgen.end_tmp()
|
|
// println('if in:')
|
|
// println(p.strtok())
|
|
is_range := p.tok == DOTDOT
|
|
mut range_end := ''
|
|
if is_range {
|
|
p.check_types(typ, 'int')
|
|
p.check_space(DOTDOT)
|
|
p.cgen.start_tmp()
|
|
p.check_types(p.bool_expression(), 'int')
|
|
range_end = p.cgen.end_tmp()
|
|
}
|
|
is_arr := typ.contains('array')
|
|
is_str := typ == 'string'
|
|
// ////if !typ.contains('array') && typ != 'string' {
|
|
if !is_arr && !is_str && !is_range {
|
|
p.error('`for in` requires an array or a string but got `$typ`')
|
|
}
|
|
p.genln('$typ $tmp = $expr;')
|
|
// TODO var_type := if...
|
|
mut var_type := ''
|
|
if is_arr {
|
|
var_type = typ.right(6)// all after `array_`
|
|
}
|
|
else if is_str {
|
|
var_type = 'byte'
|
|
}
|
|
else if is_range {
|
|
var_type = 'int'
|
|
}
|
|
// println('for typ=$typ vartyp=$var_typ')
|
|
// Register temp var
|
|
val_var := Var {
|
|
name: val
|
|
typ: var_type
|
|
ptr: typ.contains('*')
|
|
}
|
|
p.register_var(val_var)
|
|
i := p.get_tmp()
|
|
if is_range {
|
|
p.genln(';\nfor (int $i = $tmp; $i < $range_end; $i++) {')
|
|
}
|
|
else {
|
|
p.genln(';\nfor (int $i = 0; $i < $tmp .len; $i ++) {')
|
|
}
|
|
if is_arr {
|
|
p.genln('$var_type $val = (($var_type *) ${tmp}.data)[$i];')
|
|
}
|
|
else if is_str {
|
|
p.genln('$var_type $val = (($var_type *) ${tmp}.str)[$i];')
|
|
}
|
|
else if is_range {
|
|
p.genln('$var_type $val = $i;')
|
|
}
|
|
}
|
|
else {
|
|
// `for a < b {`
|
|
p.gen('while (')
|
|
p.check_types(p.bool_expression(), 'bool')
|
|
p.genln(') {')
|
|
}
|
|
p.check(LCBR)
|
|
p.statements()
|
|
p.cur_fn.close_scope()
|
|
p.for_expr_cnt--
|
|
}
|
|
|
|
fn (p mut Parser) switch_statement() {
|
|
p.next()
|
|
p.cgen.start_tmp()
|
|
typ := p.bool_expression()
|
|
expr := p.cgen.end_tmp()
|
|
p.check(LCBR)
|
|
mut i := 0
|
|
for p.tok == CASE || p.tok == DEFAULT {
|
|
if p.tok == DEFAULT {
|
|
p.genln('else { // default:')
|
|
p.next()
|
|
p.check(COLON)
|
|
p.statements()
|
|
break
|
|
}
|
|
if i > 0 {
|
|
p.gen('else ')
|
|
}
|
|
p.gen('if (')
|
|
// Multiple checks separated by comma
|
|
mut got_comma := false
|
|
for {
|
|
if got_comma {
|
|
p.gen(') || ')
|
|
}
|
|
if typ == 'string' {
|
|
p.gen('string_eq($expr, ')
|
|
}
|
|
else {
|
|
p.gen('($expr == ')
|
|
}
|
|
if p.tok == CASE || p.tok == DEFAULT {
|
|
p.next()
|
|
}
|
|
p.bool_expression()
|
|
if p.tok != COMMA {
|
|
break
|
|
}
|
|
p.check(COMMA)
|
|
got_comma = true
|
|
}
|
|
p.check(COLON)
|
|
p.gen(')) {')
|
|
p.genln('/* case */')
|
|
p.statements()
|
|
i++
|
|
}
|
|
}
|
|
|
|
fn (p mut Parser) assert_statement() {
|
|
p.check(ASSERT)
|
|
p.fspace()
|
|
tmp := p.get_tmp()
|
|
p.gen('bool $tmp = ')
|
|
p.check_types(p.bool_expression(), 'bool')
|
|
// TODO print "expected: got" for failed tests
|
|
filename := p.file_path
|
|
p.genln(';\n
|
|
if (!$tmp) {
|
|
puts("\\x1B[31mFAILED: $p.cur_fn.name() in $filename:$p.scanner.line_nr\\x1B[0m");
|
|
g_test_ok = 0 ;
|
|
// TODO
|
|
// Maybe print all vars in a test function if it fails?
|
|
}
|
|
else {
|
|
puts("\\x1B[32mPASSED: $p.cur_fn.name()\\x1B[0m");
|
|
}')
|
|
}
|
|
|
|
fn (p mut Parser) return_st() {
|
|
p.cgen.insert_before(p.cur_fn.defer)
|
|
p.gen('return ')
|
|
if p.cur_fn.name == 'main' {
|
|
p.gen(' 0')
|
|
}
|
|
p.check(RETURN)
|
|
p.fgen(' ')
|
|
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`')
|
|
}
|
|
else {
|
|
ph := p.cgen.add_placeholder()
|
|
expr_type := p.bool_expression()
|
|
// Automatically wrap an object inside an option if the function returns an option
|
|
if p.cur_fn.typ.ends_with(expr_type) && p.cur_fn.typ.starts_with('Option_') {
|
|
p.cgen.set_placeholder(ph, 'opt_ok(& ')
|
|
p.gen(')')
|
|
}
|
|
p.check_types(expr_type, p.cur_fn.typ)
|
|
}
|
|
}
|
|
else {
|
|
// Don't allow `return val` in functions that don't return anything
|
|
// if p.tok != RCBR && p.tok != HASH {
|
|
if false && p.tok == NAME || p.tok == INT {
|
|
p.error('function `$p.cur_fn.name` does not return a value')
|
|
}
|
|
}
|
|
p.returns = true
|
|
}
|
|
|
|
fn (p mut Parser) at() {
|
|
vals := p.lit.split(' ')
|
|
if vals.len != 2 {
|
|
p.error('Bad @ syntax (type name)')
|
|
}
|
|
typ := vals[0]
|
|
// name := vals[1]
|
|
if !p.table.known_type(typ) {
|
|
p.table.register_type(typ)
|
|
}
|
|
// if p.fn == "" {
|
|
// p.table.registerVar(&Var{
|
|
// Name: name,
|
|
// Type: typ,
|
|
// Cat: CatFunc,
|
|
// })
|
|
// } else {
|
|
//
|
|
// fn := p.table.findVar(p.fn)
|
|
// p.table.registerFnVar(&Var{
|
|
// Name: name,
|
|
// Type: typ,
|
|
// Cat: CatVar,
|
|
// }, fn)
|
|
// }
|
|
p.next()
|
|
}
|
|
|
|
fn prepend_pkg(pkg, name string) string {
|
|
return '${pkg}__${name}'
|
|
}
|
|
|
|
fn (p &Parser) prepend_pkg(name string) string {
|
|
return prepend_pkg(p.pkg, name)
|
|
}
|
|
|
|
fn (p mut Parser) go_statement() {
|
|
p.check(GO)
|
|
// TODO copypasta of name_expr() ?
|
|
// Method
|
|
if p.peek() == DOT {
|
|
var_name := p.lit
|
|
v := p.cur_fn.find_var(var_name)
|
|
p.cur_fn.mark_var_used(v)
|
|
p.next()
|
|
p.check(DOT)
|
|
typ := p.table.find_type(v.typ)
|
|
mut method := p.table.find_method(typ, p.lit)
|
|
p.async_fn_call(method, 0, var_name, v.typ)
|
|
}
|
|
// Normal function
|
|
else {
|
|
f := p.table.find_fn(p.lit)
|
|
if f.name == 'println' {
|
|
p.error('`go` cannot be used with `println`')
|
|
}
|
|
// println(f.name)
|
|
p.async_fn_call(f, 0, '', '')
|
|
}
|
|
}
|
|
|
|
fn (p mut Parser) register_var(v Var) {
|
|
if v.line_nr == 0 {
|
|
v.line_nr = p.scanner.line_nr
|
|
}
|
|
p.cur_fn.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()
|
|
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)
|
|
p.cgen.start_tmp()
|
|
p.check_types(p.bool_expression(), 'string')
|
|
expr := p.cgen.end_tmp()
|
|
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 = $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_type(opt_type)
|
|
return opt_type
|
|
}
|
|
else if op == 'encode' {
|
|
p.check(LPAR)
|
|
p.cgen.start_tmp()
|
|
typ := p.bool_expression()
|
|
T := p.table.find_type(typ)
|
|
p.gen_json_for_type(T)
|
|
expr := p.cgen.end_tmp()
|
|
p.check(RPAR)
|
|
p.gen('json__json_print(json__jsencode_$typ($expr))')
|
|
return 'string'
|
|
}
|
|
else {
|
|
p.error('bad json op "$op"')
|
|
}
|
|
return ''
|
|
}
|
|
|
|
fn is_compile_time_const(s string) bool {
|
|
s = s.trim_space()
|
|
if s == '' {
|
|
return false
|
|
}
|
|
if s.contains('\'') {
|
|
return true
|
|
}
|
|
for c in s {
|
|
if ! ((c >= `0` && c <= `9`) || c == `.`) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// fmt helpers
|
|
fn (scanner mut Scanner) fgen(s string) {
|
|
if scanner.fmt_line_empty {
|
|
s = repeat_char(`\t`, scanner.fmt_indent) + s
|
|
}
|
|
scanner.fmt_out.write(s)
|
|
scanner.fmt_line_empty = false
|
|
}
|
|
|
|
fn (scanner mut Scanner) fgenln(s string) {
|
|
if scanner.fmt_line_empty {
|
|
s = repeat_char(`\t`, scanner.fmt_indent) + s
|
|
}
|
|
scanner.fmt_out.writeln(s)
|
|
scanner.fmt_line_empty = true
|
|
}
|
|
|
|
fn (p mut Parser) fgen(s string) {
|
|
p.scanner.fgen(s)
|
|
}
|
|
|
|
fn (p mut Parser) fspace() {
|
|
p.fgen(' ')
|
|
}
|
|
|
|
fn (p mut Parser) fgenln(s string) {
|
|
p.scanner.fgenln(s)
|
|
}
|
|
|