5037 lines
106 KiB
V
5037 lines
106 KiB
V
module parser
|
|
|
|
import os
|
|
import strings
|
|
import v3.flat
|
|
import v3.pref
|
|
import v3.scanner
|
|
import v3.token
|
|
|
|
fn C.open(charptr, int, int) int
|
|
fn C.read(int, voidptr, int) int
|
|
fn C.close(int) int
|
|
fn C.malloc(int) &u8
|
|
|
|
const max_source_file_size = 8388608
|
|
|
|
pub struct Parser {
|
|
prefs &pref.Preferences
|
|
mut:
|
|
s scanner.Scanner
|
|
tok token.Token
|
|
lit string
|
|
tok_pos int
|
|
tok_is_str_tail bool
|
|
prev_tok token.Token
|
|
peek_tok token.Token = .eof
|
|
peek_lit string
|
|
peek_pos int
|
|
peek_is_str_tail bool
|
|
has_peek bool
|
|
cur_file string
|
|
cur_module string
|
|
cur_fn string
|
|
pending_flag bool
|
|
skip_next_decl bool
|
|
disable_fn_body bool
|
|
in_for_container bool
|
|
pub mut:
|
|
a &flat.FlatAst = unsafe { nil }
|
|
parsed_v_files int
|
|
parsed_v_lines int
|
|
}
|
|
|
|
pub fn Parser.new(prefs &pref.Preferences) &Parser {
|
|
return &Parser{
|
|
prefs: unsafe { prefs }
|
|
s: scanner.new_scanner(prefs, .normal)
|
|
a: &flat.FlatAst{
|
|
nodes: []flat.Node{cap: 256}
|
|
children: []flat.NodeId{cap: 512}
|
|
disabled_fns: map[string]bool{}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn (mut p Parser) parse_file(path string) &flat.FlatAst {
|
|
p.parse_into(path)
|
|
return p.a
|
|
}
|
|
|
|
pub fn (mut p Parser) parse_files(paths []string) &flat.FlatAst {
|
|
for path in paths {
|
|
p.parse_into(path)
|
|
}
|
|
return p.a
|
|
}
|
|
|
|
pub fn (mut p Parser) parse_into(path string) {
|
|
p.cur_file = path
|
|
// File marker before content so import resolver can track source files
|
|
p.a.add_node(flat.Node{
|
|
kind: .file
|
|
value: path
|
|
})
|
|
src := read_source_file_raw(path)
|
|
if src.len == 0 {
|
|
eprintln('error reading ${path}')
|
|
return
|
|
}
|
|
if path.ends_with('.v') {
|
|
p.parsed_v_files++
|
|
p.parsed_v_lines += count_source_lines(src)
|
|
}
|
|
mut file_set := token.FileSet.new()
|
|
file := file_set.add_file(path, -1, src.len)
|
|
p.s.init(file, src)
|
|
p.next()
|
|
|
|
mut ids := []flat.NodeId{}
|
|
for p.tok != .eof {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
if p.tok == .key_module {
|
|
p.next()
|
|
p.cur_module = p.lit
|
|
mod_id := p.a.add_node(flat.Node{
|
|
kind: .module_decl
|
|
value: p.lit
|
|
})
|
|
ids << mod_id
|
|
p.next()
|
|
if p.tok == .semicolon || p.lit == '' {
|
|
p.next()
|
|
}
|
|
continue
|
|
}
|
|
id := p.top_level_stmt()
|
|
if int(id) >= 0 {
|
|
ids << id
|
|
}
|
|
}
|
|
start := p.add_children(ids)
|
|
p.a.add_node(flat.Node{
|
|
kind: .file
|
|
value: path
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn count_source_lines(src string) int {
|
|
if src.len == 0 {
|
|
return 0
|
|
}
|
|
mut lines := 1
|
|
for i in 0 .. src.len {
|
|
if src[i] == `\n` && i + 1 < src.len {
|
|
lines++
|
|
}
|
|
}
|
|
return lines
|
|
}
|
|
|
|
fn read_source_file_raw(path string) string {
|
|
cpath := cstring_from_vstring(path)
|
|
fd := C.open(cpath, 0, 0)
|
|
if fd < 0 {
|
|
return ''
|
|
}
|
|
buf := C.malloc(max_source_file_size + 1)
|
|
mut total := 0
|
|
for total < max_source_file_size {
|
|
nread := C.read(fd, unsafe { buf + total }, max_source_file_size - total)
|
|
if nread <= 0 {
|
|
break
|
|
}
|
|
total += nread
|
|
}
|
|
C.close(fd)
|
|
if total <= 0 {
|
|
return ''
|
|
}
|
|
unsafe {
|
|
return tos(buf, total)
|
|
}
|
|
}
|
|
|
|
fn cstring_from_vstring(s string) &u8 {
|
|
buf := C.malloc(s.len + 1)
|
|
unsafe {
|
|
for i in 0 .. s.len {
|
|
buf[i] = s[i]
|
|
}
|
|
buf[s.len] = 0
|
|
}
|
|
return buf
|
|
}
|
|
|
|
fn vmod_root_for_file(path string) string {
|
|
mut dir := if path.len > 0 { os.dir(path) } else { os.getwd() }
|
|
if dir.len == 0 {
|
|
dir = os.getwd()
|
|
}
|
|
original_dir := dir
|
|
for {
|
|
if os.exists(os.join_path(dir, 'v.mod')) {
|
|
return dir
|
|
}
|
|
parent := os.dir(dir)
|
|
if parent == dir || parent.len == 0 {
|
|
return original_dir
|
|
}
|
|
dir = parent
|
|
}
|
|
return dir
|
|
}
|
|
|
|
fn (mut p Parser) next() {
|
|
p.prev_tok = p.tok
|
|
if p.has_peek {
|
|
p.tok = p.peek_tok
|
|
p.lit = p.peek_lit
|
|
p.tok_pos = p.peek_pos
|
|
p.tok_is_str_tail = p.peek_is_str_tail
|
|
p.has_peek = false
|
|
p.normalize_current_token()
|
|
return
|
|
}
|
|
p.tok_is_str_tail = p.s.in_str_incomplete
|
|
p.tok = p.s.scan()
|
|
p.lit = p.s.lit
|
|
p.tok_pos = p.s.pos
|
|
p.normalize_current_token()
|
|
for p.tok == .comment {
|
|
p.tok_is_str_tail = p.s.in_str_incomplete
|
|
p.tok = p.s.scan()
|
|
p.lit = p.s.lit
|
|
p.tok_pos = p.s.pos
|
|
p.normalize_current_token()
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) peek() token.Token {
|
|
if !p.has_peek {
|
|
p.peek_is_str_tail = p.s.in_str_incomplete
|
|
p.peek_tok = p.s.scan()
|
|
p.peek_lit = p.s.lit
|
|
p.peek_pos = p.s.pos
|
|
p.normalize_peek_token()
|
|
for p.peek_tok == .comment {
|
|
p.peek_is_str_tail = p.s.in_str_incomplete
|
|
p.peek_tok = p.s.scan()
|
|
p.peek_lit = p.s.lit
|
|
p.peek_pos = p.s.pos
|
|
p.normalize_peek_token()
|
|
}
|
|
p.has_peek = true
|
|
}
|
|
return p.peek_tok
|
|
}
|
|
|
|
fn (mut p Parser) peek_is(tok token.Token) bool {
|
|
return p.peek() == tok
|
|
}
|
|
|
|
fn (mut p Parser) normalize_current_token() {
|
|
p.tok = normalize_scanned_token(p.tok, p.lit, p.s.src, p.tok_pos, p.tok_is_str_tail)
|
|
}
|
|
|
|
fn (mut p Parser) normalize_peek_token() {
|
|
p.peek_tok = normalize_scanned_token(p.peek_tok, p.peek_lit, p.s.src, p.peek_pos,
|
|
p.peek_is_str_tail)
|
|
}
|
|
|
|
@[inline]
|
|
fn token_from_id(id int) token.Token {
|
|
return unsafe { token.Token(id) }
|
|
}
|
|
|
|
fn keyword_token_id(lit string) int {
|
|
return match lit {
|
|
'as' { 25 }
|
|
'asm' { 26 }
|
|
'assert' { 27 }
|
|
'atomic' { 28 }
|
|
'break' { 29 }
|
|
'const' { 30 }
|
|
'continue' { 31 }
|
|
'defer' { 32 }
|
|
'dump' { 33 }
|
|
'else' { 34 }
|
|
'enum' { 35 }
|
|
'false' { 36 }
|
|
'fn' { 37 }
|
|
'for' { 38 }
|
|
'__global' { 39 }
|
|
'go' { 40 }
|
|
'goto' { 41 }
|
|
'if' { 42 }
|
|
'import' { 43 }
|
|
'in' { 44 }
|
|
'interface' { 45 }
|
|
'is' { 46 }
|
|
'isreftype' { 47 }
|
|
'_likely_' { 48 }
|
|
'lock' { 49 }
|
|
'match' { 50 }
|
|
'module' { 51 }
|
|
'mut' { 52 }
|
|
'nil' { 53 }
|
|
'none' { 54 }
|
|
'__offsetof' { 55 }
|
|
'or' { 56 }
|
|
'pub' { 57 }
|
|
'return' { 58 }
|
|
'rlock' { 59 }
|
|
'select' { 60 }
|
|
'shared' { 61 }
|
|
'sizeof' { 62 }
|
|
'spawn' { 63 }
|
|
'static' { 64 }
|
|
'struct' { 65 }
|
|
'true' { 66 }
|
|
'type' { 67 }
|
|
'typeof' { 68 }
|
|
'union' { 69 }
|
|
'_unlikely_' { 70 }
|
|
'unsafe' { 71 }
|
|
'volatile' { 72 }
|
|
else { -1 }
|
|
}
|
|
}
|
|
|
|
fn normalize_scanned_token(tok token.Token, lit string, src string, pos int, is_str_tail bool) token.Token {
|
|
if int(tok) == 19 || int(tok) == 105 || int(tok) == 106 {
|
|
return tok
|
|
}
|
|
if lit.len > 0 {
|
|
first := lit[0]
|
|
if int(tok) == 87 || int(tok) == 0 {
|
|
if is_str_tail && int(tok) == 0 {
|
|
return token_from_id(107)
|
|
}
|
|
if first >= `0` && first <= `9` {
|
|
return token_from_id(92)
|
|
}
|
|
if first == `.` && lit.len > 1 && lit[1] >= `0` && lit[1] <= `9` {
|
|
return token_from_id(92)
|
|
}
|
|
if first == `'` || first == `"`
|
|
|| (first == `r` && lit.len > 1 && (lit[1] == `'` || lit[1] == `"`)) {
|
|
return token_from_id(107)
|
|
}
|
|
if first == `\`` || lit.starts_with('c:') {
|
|
return token_from_id(7)
|
|
}
|
|
}
|
|
if first != `@` {
|
|
keyword_id := keyword_token_id(lit)
|
|
if keyword_id >= 0 {
|
|
return token_from_id(keyword_id)
|
|
}
|
|
}
|
|
if int(tok) == 0 {
|
|
return token_from_id(87)
|
|
}
|
|
return tok
|
|
}
|
|
if pos < 0 || pos >= src.len {
|
|
return tok
|
|
}
|
|
c := src[pos]
|
|
if c == `.` {
|
|
if pos + 1 < src.len && src[pos + 1] == `.` {
|
|
if pos + 2 < src.len && src[pos + 2] == `.` {
|
|
return token_from_id(18)
|
|
}
|
|
return token_from_id(17)
|
|
}
|
|
return token_from_id(16)
|
|
}
|
|
if c == `:` {
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(12)
|
|
}
|
|
return token_from_id(8)
|
|
}
|
|
if int(tok) != 108 && int(tok) != 0 {
|
|
return tok
|
|
}
|
|
if c == `{` {
|
|
return token_from_id(73)
|
|
}
|
|
if c == `}` {
|
|
return token_from_id(98)
|
|
}
|
|
if c == `(` {
|
|
return token_from_id(78)
|
|
}
|
|
if c == `)` {
|
|
return token_from_id(103)
|
|
}
|
|
if c == `[` {
|
|
return token_from_id(79)
|
|
}
|
|
if c == `]` {
|
|
return token_from_id(104)
|
|
}
|
|
if c == `,` {
|
|
return token_from_id(9)
|
|
}
|
|
if c == `;` {
|
|
return token_from_id(105)
|
|
}
|
|
if c == `!` {
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(88)
|
|
}
|
|
if pos + 3 < src.len && src[pos + 1] == `i` && src[pos + 2] == `n`
|
|
&& (src[pos + 3] == ` ` || src[pos + 3] == `\t`) {
|
|
return token_from_id(90)
|
|
}
|
|
if pos + 3 < src.len && src[pos + 1] == `i` && src[pos + 2] == `s`
|
|
&& (src[pos + 3] == ` ` || src[pos + 3] == `\t`) {
|
|
return token_from_id(91)
|
|
}
|
|
return token_from_id(89)
|
|
}
|
|
if c == `=` {
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(20)
|
|
}
|
|
return token_from_id(4)
|
|
}
|
|
if c == `&` {
|
|
if pos + 1 < src.len && src[pos + 1] == `&` {
|
|
return token_from_id(1)
|
|
}
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(2)
|
|
}
|
|
return token_from_id(0)
|
|
}
|
|
if c == `|` {
|
|
if pos + 1 < src.len && src[pos + 1] == `|` {
|
|
return token_from_id(77)
|
|
}
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(93)
|
|
}
|
|
return token_from_id(94)
|
|
}
|
|
if c == `<` {
|
|
if pos + 1 < src.len && src[pos + 1] == `<` {
|
|
if pos + 2 < src.len && src[pos + 2] == `=` {
|
|
return token_from_id(76)
|
|
}
|
|
return token_from_id(75)
|
|
}
|
|
if pos + 1 < src.len && src[pos + 1] == `-` {
|
|
return token_from_id(3)
|
|
}
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(74)
|
|
}
|
|
return token_from_id(80)
|
|
}
|
|
if c == `>` {
|
|
if pos + 1 < src.len && src[pos + 1] == `>` {
|
|
if pos + 2 < src.len && src[pos + 2] == `>` {
|
|
if pos + 3 < src.len && src[pos + 3] == `=` {
|
|
return token_from_id(102)
|
|
}
|
|
return token_from_id(101)
|
|
}
|
|
if pos + 2 < src.len && src[pos + 2] == `=` {
|
|
return token_from_id(100)
|
|
}
|
|
return token_from_id(99)
|
|
}
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(21)
|
|
}
|
|
return token_from_id(22)
|
|
}
|
|
if c == `+` {
|
|
if pos + 1 < src.len && src[pos + 1] == `+` {
|
|
return token_from_id(24)
|
|
}
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(96)
|
|
}
|
|
return token_from_id(95)
|
|
}
|
|
if c == `-` {
|
|
if pos + 1 < src.len && src[pos + 1] == `-` {
|
|
return token_from_id(11)
|
|
}
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(82)
|
|
}
|
|
return token_from_id(81)
|
|
}
|
|
if c == `*` {
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(86)
|
|
}
|
|
return token_from_id(85)
|
|
}
|
|
if c == `/` {
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(14)
|
|
}
|
|
return token_from_id(13)
|
|
}
|
|
if c == `%` {
|
|
if pos + 1 < src.len && src[pos + 1] == `=` {
|
|
return token_from_id(84)
|
|
}
|
|
return token_from_id(83)
|
|
}
|
|
if c == `~` {
|
|
return token_from_id(6)
|
|
}
|
|
if c == `$` {
|
|
return token_from_id(15)
|
|
}
|
|
if c == `#` {
|
|
if pos + 1 < src.len && src[pos + 1] == `[` {
|
|
return token_from_id(79)
|
|
}
|
|
return token_from_id(23)
|
|
}
|
|
if c == `?` {
|
|
return token_from_id(97)
|
|
}
|
|
return tok
|
|
}
|
|
|
|
fn (mut p Parser) check(expected token.Token) {
|
|
if p.tok == expected {
|
|
p.next()
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) expect(expected token.Token) string {
|
|
lit := p.lit
|
|
if p.tok != expected {
|
|
eprintln('expected ${expected}, got ${p.tok} "${p.lit}"')
|
|
}
|
|
p.next()
|
|
return lit
|
|
}
|
|
|
|
fn (mut p Parser) expect_name() string {
|
|
name := p.lit
|
|
if p.tok != .name && p.lit.len == 0 {
|
|
eprintln('expected name, got ${p.tok} "${p.lit}"')
|
|
}
|
|
p.next()
|
|
return name
|
|
}
|
|
|
|
fn (mut p Parser) expect_name_or_keyword() string {
|
|
name := p.lit
|
|
p.next()
|
|
return name
|
|
}
|
|
|
|
fn token_id_is_infix(tv int) bool {
|
|
return tv == 0 || tv == 1 || tv == 3 || tv == 13 || tv == 20 || tv == 21 || tv == 22 || tv == 44
|
|
|| tv == 46 || tv == 74 || tv == 75 || tv == 77 || tv == 80 || tv == 81 || tv == 83
|
|
|| tv == 85 || tv == 88 || tv == 90 || tv == 91 || tv == 94 || tv == 95 || tv == 99
|
|
|| tv == 101 || tv == 109
|
|
}
|
|
|
|
fn token_is_infix(tok token.Token) bool {
|
|
return token_id_is_infix(int(tok))
|
|
}
|
|
|
|
fn token_id_is_postfix(tv int) bool {
|
|
return tv == 11 || tv == 24
|
|
}
|
|
|
|
fn token_is_postfix(tok token.Token) bool {
|
|
return token_id_is_postfix(int(tok))
|
|
}
|
|
|
|
fn token_id_is_assignment(tv int) bool {
|
|
return tv == 2 || tv == 4 || tv == 12 || tv == 14 || tv == 76 || tv == 82 || tv == 84
|
|
|| tv == 86 || tv == 93 || tv == 96 || tv == 100 || tv == 102 || tv == 110
|
|
}
|
|
|
|
fn token_is_assignment(tok token.Token) bool {
|
|
return token_id_is_assignment(int(tok))
|
|
}
|
|
|
|
fn token_id_left_binding_power(tv int) token.BindingPower {
|
|
if tv == 77 {
|
|
return token.BindingPower.logical_or
|
|
}
|
|
if tv == 1 {
|
|
return token.BindingPower.logical_and
|
|
}
|
|
if tv == 20 || tv == 88 || tv == 80 || tv == 74 || tv == 22 || tv == 21 || tv == 44 || tv == 90
|
|
|| tv == 46 || tv == 91 {
|
|
return token.BindingPower.compare
|
|
}
|
|
if tv == 94 {
|
|
return token.BindingPower.bit_or
|
|
}
|
|
if tv == 109 {
|
|
return token.BindingPower.bit_xor
|
|
}
|
|
if tv == 75 || tv == 99 || tv == 101 {
|
|
return token.BindingPower.shift
|
|
}
|
|
if tv == 95 || tv == 81 {
|
|
return token.BindingPower.add
|
|
}
|
|
if tv == 85 || tv == 13 || tv == 83 || tv == 0 {
|
|
return token.BindingPower.product
|
|
}
|
|
return token.BindingPower.lowest
|
|
}
|
|
|
|
fn token_left_binding_power(tok token.Token) token.BindingPower {
|
|
return token_id_left_binding_power(int(tok))
|
|
}
|
|
|
|
fn token_id_right_binding_power(tv int) token.BindingPower {
|
|
bp := token_id_left_binding_power(tv)
|
|
if bp == .logical_or {
|
|
return token.BindingPower.logical_and
|
|
}
|
|
if bp == .logical_and {
|
|
return token.BindingPower.compare
|
|
}
|
|
if bp == .compare {
|
|
return token.BindingPower.bit_or
|
|
}
|
|
if bp == .bit_or {
|
|
return token.BindingPower.bit_xor
|
|
}
|
|
if bp == .bit_xor {
|
|
return token.BindingPower.shift
|
|
}
|
|
if bp == .shift {
|
|
return token.BindingPower.add
|
|
}
|
|
if bp == .add {
|
|
return token.BindingPower.product
|
|
}
|
|
if bp == .product {
|
|
return token.BindingPower.highest
|
|
}
|
|
return token.BindingPower.lowest
|
|
}
|
|
|
|
fn token_right_binding_power(tok token.Token) token.BindingPower {
|
|
return token_id_right_binding_power(int(tok))
|
|
}
|
|
|
|
fn token_id_to_op(tv int) flat.Op {
|
|
if tv == 95 {
|
|
return flat.Op.plus
|
|
}
|
|
if tv == 81 {
|
|
return flat.Op.minus
|
|
}
|
|
if tv == 85 {
|
|
return flat.Op.mul
|
|
}
|
|
if tv == 13 {
|
|
return flat.Op.div
|
|
}
|
|
if tv == 83 {
|
|
return flat.Op.mod
|
|
}
|
|
if tv == 20 {
|
|
return flat.Op.eq
|
|
}
|
|
if tv == 88 {
|
|
return flat.Op.ne
|
|
}
|
|
if tv == 80 {
|
|
return flat.Op.lt
|
|
}
|
|
if tv == 22 {
|
|
return flat.Op.gt
|
|
}
|
|
if tv == 74 {
|
|
return flat.Op.le
|
|
}
|
|
if tv == 21 {
|
|
return flat.Op.ge
|
|
}
|
|
if tv == 0 {
|
|
return flat.Op.amp
|
|
}
|
|
if tv == 94 {
|
|
return flat.Op.pipe
|
|
}
|
|
if tv == 109 {
|
|
return flat.Op.xor
|
|
}
|
|
if tv == 75 {
|
|
return flat.Op.left_shift
|
|
}
|
|
if tv == 99 {
|
|
return flat.Op.right_shift
|
|
}
|
|
if tv == 101 {
|
|
return flat.Op.right_shift_unsigned
|
|
}
|
|
if tv == 1 {
|
|
return flat.Op.logical_and
|
|
}
|
|
if tv == 77 {
|
|
return flat.Op.logical_or
|
|
}
|
|
if tv == 89 {
|
|
return flat.Op.not
|
|
}
|
|
if tv == 6 {
|
|
return flat.Op.bit_not
|
|
}
|
|
if tv == 4 {
|
|
return flat.Op.assign
|
|
}
|
|
if tv == 96 {
|
|
return flat.Op.plus_assign
|
|
}
|
|
if tv == 82 {
|
|
return flat.Op.minus_assign
|
|
}
|
|
if tv == 86 {
|
|
return flat.Op.mul_assign
|
|
}
|
|
if tv == 14 {
|
|
return flat.Op.div_assign
|
|
}
|
|
if tv == 84 {
|
|
return flat.Op.mod_assign
|
|
}
|
|
if tv == 2 {
|
|
return flat.Op.amp_assign
|
|
}
|
|
if tv == 93 {
|
|
return flat.Op.pipe_assign
|
|
}
|
|
if tv == 110 {
|
|
return flat.Op.xor_assign
|
|
}
|
|
if tv == 76 {
|
|
return flat.Op.left_shift_assign
|
|
}
|
|
if tv == 100 {
|
|
return flat.Op.right_shift_assign
|
|
}
|
|
if tv == 102 {
|
|
return flat.Op.right_shift_unsigned_assign
|
|
}
|
|
if tv == 24 {
|
|
return flat.Op.inc
|
|
}
|
|
if tv == 11 {
|
|
return flat.Op.dec
|
|
}
|
|
if tv == 16 {
|
|
return flat.Op.dot
|
|
}
|
|
if tv == 3 {
|
|
return flat.Op.arrow
|
|
}
|
|
return flat.Op.none
|
|
}
|
|
|
|
fn (p &Parser) tok_can_be_decl_name() bool {
|
|
return p.tok == .name || (int(p.tok) >= int(token.Token.key_as)
|
|
&& int(p.tok) <= int(token.Token.key_unsafe))
|
|
}
|
|
|
|
fn (mut p Parser) add_children(ids []flat.NodeId) int {
|
|
start := p.a.children.len
|
|
for id in ids {
|
|
p.a.children << id
|
|
}
|
|
return start
|
|
}
|
|
|
|
fn (mut p Parser) add_child(id flat.NodeId) int {
|
|
start := p.a.children.len
|
|
p.a.children << id
|
|
return start
|
|
}
|
|
|
|
fn (mut p Parser) add_children2(a flat.NodeId, b flat.NodeId) int {
|
|
start := p.a.children.len
|
|
p.a.children << a
|
|
p.a.children << b
|
|
return start
|
|
}
|
|
|
|
// ==================== top-level ====================
|
|
|
|
fn (mut p Parser) top_level_stmt() flat.NodeId {
|
|
match p.tok {
|
|
.key_fn {
|
|
return p.fn_decl()
|
|
}
|
|
.key_pub {
|
|
p.next()
|
|
return p.top_level_stmt()
|
|
}
|
|
.key_struct, .key_union {
|
|
return p.struct_decl()
|
|
}
|
|
.key_global {
|
|
return p.global_decl()
|
|
}
|
|
.key_const {
|
|
return p.const_decl()
|
|
}
|
|
.key_enum {
|
|
return p.enum_decl()
|
|
}
|
|
.key_type {
|
|
return p.type_decl()
|
|
}
|
|
.key_interface {
|
|
return p.interface_decl()
|
|
}
|
|
.key_import {
|
|
return p.import_stmt()
|
|
}
|
|
.key_module {
|
|
return p.module_stmt()
|
|
}
|
|
.attribute {
|
|
p.skip_attrs()
|
|
return p.parse_decl_after_attrs()
|
|
}
|
|
.lsbr {
|
|
p.skip_attrs()
|
|
return p.parse_decl_after_attrs()
|
|
}
|
|
.dollar {
|
|
return p.parse_comptime_if()
|
|
}
|
|
.hash {
|
|
return p.directive()
|
|
}
|
|
.key_assert {
|
|
return p.assert_stmt()
|
|
}
|
|
.semicolon {
|
|
p.next()
|
|
return flat.empty_node
|
|
}
|
|
else {
|
|
return p.stmt()
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse_decl_after_attrs parses the declaration that follows an attribute group.
|
|
// A newline after `@[...]` yields an auto-inserted semicolon, which is consumed
|
|
// here so the declaration itself is parsed next. When the attribute group is a
|
|
// disabled `@[if flag ?]` (skip_next_decl set), functions are emitted as no-op
|
|
// stubs (empty body): the signature must remain so call sites still type-check
|
|
// and link, but the body is elided. Other disabled declarations are skipped.
|
|
fn (mut p Parser) parse_decl_after_attrs() flat.NodeId {
|
|
p.consume_decl_prefix_after_attrs()
|
|
if p.skip_next_decl {
|
|
p.skip_next_decl = false
|
|
if p.cur_decl_is_fn() {
|
|
p.disable_fn_body = true
|
|
res := p.top_level_stmt()
|
|
p.disable_fn_body = false
|
|
p.skip_next_decl = false
|
|
return res
|
|
}
|
|
p.skip_top_level_stmt()
|
|
p.skip_next_decl = false
|
|
return flat.empty_node
|
|
}
|
|
return p.top_level_stmt()
|
|
}
|
|
|
|
fn (mut p Parser) consume_decl_prefix_after_attrs() {
|
|
for p.tok == .semicolon || p.tok == .attribute || p.tok == .lsbr {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
p.skip_attrs()
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) cur_decl_is_fn() bool {
|
|
if p.tok == .key_fn {
|
|
return true
|
|
}
|
|
return p.tok == .key_pub && p.peek() == .key_fn
|
|
}
|
|
|
|
fn (mut p Parser) fn_decl() flat.NodeId {
|
|
p.check(.key_fn)
|
|
mut name := ''
|
|
mut receiver_name := ''
|
|
mut receiver_type := ''
|
|
mut is_method := false
|
|
|
|
// method receiver: fn (mut r Type) name()
|
|
if p.tok == .lpar {
|
|
is_method = true
|
|
p.next()
|
|
mut is_mut := false
|
|
if p.tok == .key_mut || p.tok == .key_shared {
|
|
is_mut = p.tok == .key_mut
|
|
p.next()
|
|
}
|
|
receiver_name = p.expect_name()
|
|
receiver_type = p.parse_type_name()
|
|
if is_mut {
|
|
receiver_type = '&' + receiver_type
|
|
}
|
|
p.check(.rpar)
|
|
|
|
// operator overload: fn (r Type) + (other Type) RetType { }
|
|
if p.tok != .name && p.tok != .eof {
|
|
if p.tok.is_overloadable() {
|
|
op_name := overload_token_name(p.tok)
|
|
p.next()
|
|
return p.fn_operator_overload(receiver_name, receiver_type, op_name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// function name
|
|
if p.tok_can_be_decl_name() {
|
|
name = p.expect_name_or_keyword()
|
|
if p.tok == .dot {
|
|
p.next()
|
|
if name == 'C' || name == 'JS' {
|
|
// C.func or JS.func
|
|
name = p.expect_name_or_keyword()
|
|
for p.tok == .dot {
|
|
p.next()
|
|
name += '.' + p.expect_name_or_keyword()
|
|
}
|
|
if is_method {
|
|
clean_type := receiver_type.trim_left('&')
|
|
name = '${clean_type}.${name}'
|
|
}
|
|
return p.fn_decl_body(name, receiver_name, receiver_type, is_method, true)
|
|
}
|
|
// module.func or Type.static_method
|
|
second := p.expect_name_or_keyword()
|
|
if is_method {
|
|
// This shouldn't happen for methods
|
|
name = name + '.' + second
|
|
} else {
|
|
// Static method: Type.name
|
|
receiver_type = name
|
|
name = second
|
|
is_method = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if is_method && receiver_type.len > 0 {
|
|
clean_type := receiver_type.trim_left('&')
|
|
name = '${clean_type}.${name}'
|
|
}
|
|
|
|
return p.fn_decl_body(name, receiver_name, receiver_type, is_method, false)
|
|
}
|
|
|
|
fn (mut p Parser) fn_operator_overload(receiver_name string, receiver_type string, op_name string) flat.NodeId {
|
|
disable_body := p.disable_fn_body
|
|
p.disable_fn_body = false
|
|
// parse parameter
|
|
p.check(.lpar)
|
|
mut param_ids := []flat.NodeId{}
|
|
// receiver
|
|
param_ids << p.a.add_node(flat.Node{
|
|
kind: .param
|
|
value: receiver_name
|
|
typ: receiver_type
|
|
})
|
|
// operator param
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
}
|
|
for p.tok != .rpar && p.tok != .eof {
|
|
start_offset := p.s.offset
|
|
param_ids << p.parse_param_group()
|
|
if p.s.offset == start_offset && p.tok != .rpar && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rpar)
|
|
|
|
mut ret_type := 'void'
|
|
if p.tok != .lcbr && p.tok != .semicolon && p.tok != .eof {
|
|
ret_type = p.parse_type_name()
|
|
}
|
|
|
|
clean_type := receiver_type.trim_left('&')
|
|
name := '${clean_type}.${op_name}'
|
|
|
|
mut body_ids := []flat.NodeId{}
|
|
if p.tok == .lcbr {
|
|
prev_fn := p.cur_fn
|
|
p.cur_fn = name
|
|
if disable_body {
|
|
p.mark_disabled_fn(name)
|
|
p.skip_block()
|
|
} else {
|
|
p.check(.lcbr)
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
id := p.stmt()
|
|
if int(id) >= 0 {
|
|
body_ids << id
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
}
|
|
p.cur_fn = prev_fn
|
|
}
|
|
|
|
mut all_ids := []flat.NodeId{cap: param_ids.len + body_ids.len}
|
|
for id in param_ids {
|
|
all_ids << id
|
|
}
|
|
for id in body_ids {
|
|
all_ids << id
|
|
}
|
|
start := p.add_children(all_ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .fn_decl
|
|
value: name
|
|
typ: ret_type
|
|
children_start: start
|
|
children_count: flat.child_count(all_ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) fn_decl_body(name string, receiver_name string, receiver_type string, is_method bool, _ bool) flat.NodeId {
|
|
// Capture & clear here so it applies only to this function (not nested closures
|
|
// or a following declaration), and is cleared even on the extern/no-body path.
|
|
disable_body := p.disable_fn_body
|
|
p.disable_fn_body = false
|
|
// generic params — skip
|
|
if p.tok == .lsbr {
|
|
p.skip_brackets()
|
|
}
|
|
|
|
// params
|
|
p.check(.lpar)
|
|
mut param_ids := []flat.NodeId{}
|
|
if is_method && receiver_name.len > 0 {
|
|
param_ids << p.a.add_node(flat.Node{
|
|
kind: .param
|
|
value: receiver_name
|
|
typ: receiver_type
|
|
})
|
|
}
|
|
for p.tok != .rpar && p.tok != .eof {
|
|
start_offset := p.s.offset
|
|
param_ids << p.parse_param_group()
|
|
if p.s.offset == start_offset && p.tok != .rpar && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rpar)
|
|
|
|
// return type
|
|
mut ret_type := 'void'
|
|
if p.tok == .name || p.tok == .amp || p.tok == .question || p.tok == .not || p.tok == .lsbr
|
|
|| p.tok == .lpar || p.tok == .key_fn || p.tok == .ellipsis {
|
|
ret_type = p.parse_type_name()
|
|
}
|
|
// no body — extern/C declaration
|
|
if p.tok != .lcbr {
|
|
for p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
start := p.add_children(param_ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .c_fn_decl
|
|
value: name
|
|
typ: ret_type
|
|
children_start: start
|
|
children_count: flat.child_count(param_ids.len)
|
|
})
|
|
}
|
|
|
|
// body
|
|
mut body_ids := []flat.NodeId{}
|
|
prev_fn := p.cur_fn
|
|
p.cur_fn = name
|
|
// A disabled `@[if flag ?]` function keeps its signature but gets an empty body
|
|
// (a no-op stub), so callers still resolve while the body is compiled out.
|
|
if disable_body {
|
|
p.mark_disabled_fn(name)
|
|
p.skip_block()
|
|
} else {
|
|
p.check(.lcbr)
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
id := p.stmt()
|
|
if int(id) >= 0 {
|
|
body_ids << id
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
}
|
|
p.cur_fn = prev_fn
|
|
|
|
mut all_ids := []flat.NodeId{cap: param_ids.len + body_ids.len}
|
|
for id in param_ids {
|
|
all_ids << id
|
|
}
|
|
for id in body_ids {
|
|
all_ids << id
|
|
}
|
|
start := p.add_children(all_ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .fn_decl
|
|
value: name
|
|
typ: ret_type
|
|
children_start: start
|
|
children_count: flat.child_count(all_ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) mark_disabled_fn(name string) {
|
|
if name.len == 0 {
|
|
return
|
|
}
|
|
if p.cur_module.len > 0 && p.cur_module != 'main' && p.cur_module != 'builtin' {
|
|
qname := if name.starts_with('${p.cur_module}.') { name } else { '${p.cur_module}.${name}' }
|
|
p.a.disabled_fns[qname] = true
|
|
}
|
|
p.a.disabled_fns[name] = true
|
|
}
|
|
|
|
fn (mut p Parser) parse_param_group() []flat.NodeId {
|
|
mut ids := []flat.NodeId{}
|
|
mut names := []string{}
|
|
mut is_mut := false
|
|
if p.tok == .key_mut {
|
|
is_mut = true
|
|
p.next()
|
|
}
|
|
if p.tok == .key_shared {
|
|
p.next()
|
|
}
|
|
// variadic ...Type (no param name)
|
|
if p.tok == .ellipsis {
|
|
typ := p.parse_type_name()
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .param
|
|
value: ''
|
|
typ: typ
|
|
})
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
return ids
|
|
}
|
|
names << p.lit
|
|
p.next()
|
|
for p.tok == .comma {
|
|
p.next()
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
}
|
|
if p.tok == .name {
|
|
names << p.lit
|
|
p.next()
|
|
}
|
|
}
|
|
mut typ := p.parse_type_name()
|
|
if is_mut {
|
|
typ = '&' + typ
|
|
}
|
|
for name in names {
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .param
|
|
value: name
|
|
typ: typ
|
|
})
|
|
}
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
return ids
|
|
}
|
|
|
|
fn (mut p Parser) struct_decl() flat.NodeId {
|
|
is_union := p.tok == .key_union
|
|
p.next() // skip 'struct' or 'union'
|
|
mut name := p.expect(.name)
|
|
if (name == 'C' || name == 'JS') && p.tok == .dot {
|
|
for p.tok == .dot {
|
|
p.next()
|
|
name += '.' + p.expect_name_or_keyword()
|
|
}
|
|
}
|
|
// generic params — skip
|
|
mut is_generic := false
|
|
if p.tok == .lsbr {
|
|
is_generic = true
|
|
p.skip_brackets()
|
|
}
|
|
// implements clause
|
|
if p.tok == .name && p.lit == 'implements' {
|
|
p.next()
|
|
p.parse_type_name()
|
|
for p.tok == .comma {
|
|
p.next()
|
|
p.parse_type_name()
|
|
}
|
|
}
|
|
// no body (C struct forward decl)
|
|
if p.tok != .lcbr {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
return p.a.add_node(flat.Node{
|
|
kind: .struct_decl
|
|
value: name
|
|
typ: struct_decl_typ(is_union, is_generic)
|
|
})
|
|
}
|
|
p.check(.lcbr)
|
|
mut ids := []flat.NodeId{}
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
// access modifiers
|
|
if p.tok == .key_pub {
|
|
p.next()
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
}
|
|
if p.tok == .name && p.lit == 'module_mut' {
|
|
p.next()
|
|
}
|
|
if p.tok == .colon {
|
|
p.next()
|
|
}
|
|
continue
|
|
}
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
if p.tok == .colon {
|
|
p.next()
|
|
}
|
|
continue
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
// comptime $if in struct fields
|
|
if p.tok == .dollar {
|
|
id := p.parse_comptime_if()
|
|
if int(id) >= 0 {
|
|
ids << id
|
|
}
|
|
continue
|
|
}
|
|
// attributes — skip
|
|
if p.tok == .attribute {
|
|
p.skip_attrs()
|
|
continue
|
|
}
|
|
// field: name type [= default] [@[attrs]]
|
|
if p.tok == .name || p.tok.is_keyword() {
|
|
field_name := p.expect_name_or_keyword()
|
|
if p.tok == .dot {
|
|
p.next()
|
|
second := p.expect_name_or_keyword()
|
|
full_type := '${field_name}.${second}'
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .field_decl
|
|
value: full_type
|
|
typ: full_type
|
|
})
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
continue
|
|
}
|
|
// embedded struct (type on its own line, followed by semicolon)
|
|
if p.tok == .semicolon || p.tok == .rcbr {
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .field_decl
|
|
value: field_name
|
|
typ: field_name
|
|
})
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
continue
|
|
}
|
|
// grouped fields: x, y int
|
|
if p.tok == .comma {
|
|
mut names := []string{}
|
|
names << field_name
|
|
for p.tok == .comma {
|
|
p.next()
|
|
names << p.expect_name_or_keyword()
|
|
}
|
|
field_type := p.parse_type_name()
|
|
for n in names {
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .field_decl
|
|
value: n
|
|
typ: field_type
|
|
})
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
continue
|
|
}
|
|
// For embedded structs followed by access modifier or another field,
|
|
// check if the next token could be a type
|
|
field_type := p.parse_type_name()
|
|
mut default_id := flat.empty_node
|
|
// default value
|
|
if p.tok == .assign {
|
|
p.next()
|
|
default_id = p.expr(.lowest)
|
|
}
|
|
mut children_start := 0
|
|
mut children_count := 0
|
|
if int(default_id) >= 0 {
|
|
children_start = p.add_child(default_id)
|
|
children_count = 1
|
|
}
|
|
// trailing field attributes — skip
|
|
if p.tok == .attribute || p.tok == .lsbr {
|
|
p.skip_attrs()
|
|
}
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .field_decl
|
|
value: field_name
|
|
typ: field_type
|
|
children_start: children_start
|
|
children_count: flat.child_count(children_count)
|
|
})
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
} else {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .struct_decl
|
|
value: name
|
|
typ: struct_decl_typ(is_union, is_generic)
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn struct_decl_typ(is_union bool, is_generic bool) string {
|
|
mut parts := []string{}
|
|
if is_union {
|
|
parts << 'union'
|
|
}
|
|
if is_generic {
|
|
parts << 'generic'
|
|
}
|
|
return parts.join(',')
|
|
}
|
|
|
|
fn (mut p Parser) global_decl() flat.NodeId {
|
|
p.next() // skip '__global'
|
|
is_grouped := p.tok == .lpar
|
|
if is_grouped {
|
|
p.next()
|
|
}
|
|
mut ids := []flat.NodeId{}
|
|
for {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
if !is_grouped {
|
|
break
|
|
}
|
|
if p.tok == .rpar {
|
|
p.next()
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
if p.tok == .key_pub {
|
|
p.next()
|
|
}
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
}
|
|
if p.tok == .name || p.tok.is_keyword() {
|
|
gname := p.expect_name_or_keyword()
|
|
// handle qualified names: C.errno
|
|
mut full_name := gname
|
|
for p.tok == .dot {
|
|
p.next()
|
|
full_name += '.' + p.expect_name_or_keyword()
|
|
}
|
|
if p.tok == .assign {
|
|
// global with initializer: __global name = expr
|
|
p.next()
|
|
val_id := p.expr(.lowest)
|
|
vstart := p.add_child(val_id)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .field_decl
|
|
value: full_name
|
|
typ: ''
|
|
children_start: vstart
|
|
children_count: 1
|
|
})
|
|
} else {
|
|
gtype := p.parse_type_name()
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .field_decl
|
|
value: full_name
|
|
typ: gtype
|
|
})
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
if !is_grouped {
|
|
break
|
|
}
|
|
if is_grouped && p.tok == .rpar {
|
|
p.next()
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
break
|
|
}
|
|
} else if p.tok == .rpar {
|
|
p.next()
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
break
|
|
} else {
|
|
p.next()
|
|
if !is_grouped {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .global_decl
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) const_decl() flat.NodeId {
|
|
p.next() // skip 'const'
|
|
is_grouped := p.tok == .lpar
|
|
if is_grouped {
|
|
p.next()
|
|
}
|
|
mut ids := []flat.NodeId{}
|
|
for {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
if !is_grouped {
|
|
break
|
|
}
|
|
if p.tok == .rpar {
|
|
p.next()
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
if p.tok == .name {
|
|
cname := p.expect_name()
|
|
mut full_name := cname
|
|
// C.NAME
|
|
if full_name == 'C' && p.tok == .dot {
|
|
p.next()
|
|
full_name = 'C.' + p.expect_name_or_keyword()
|
|
}
|
|
if p.tok == .assign {
|
|
p.next()
|
|
val_id := p.expr(.lowest)
|
|
vstart := p.add_child(val_id)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .const_field
|
|
value: full_name
|
|
children_start: vstart
|
|
children_count: 1
|
|
})
|
|
} else {
|
|
// const with type only (header files)
|
|
ctype := p.parse_type_name()
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .const_field
|
|
value: full_name
|
|
typ: ctype
|
|
})
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
if !is_grouped {
|
|
break
|
|
}
|
|
if is_grouped && p.tok == .rpar {
|
|
p.next()
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
break
|
|
}
|
|
} else if p.tok == .rpar {
|
|
p.next()
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
break
|
|
} else {
|
|
p.next()
|
|
if !is_grouped {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .const_decl
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) enum_decl() flat.NodeId {
|
|
p.next() // skip 'enum'
|
|
name := p.expect(.name)
|
|
// `as` type
|
|
if p.tok == .key_as {
|
|
p.next()
|
|
p.parse_type_name()
|
|
}
|
|
p.check(.lcbr)
|
|
mut ids := []flat.NodeId{}
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
// attributes on enum fields — skip
|
|
if p.tok == .attribute || p.tok == .lsbr {
|
|
p.skip_attrs()
|
|
continue
|
|
}
|
|
field_name := p.expect_name_or_keyword()
|
|
if p.tok == .assign {
|
|
p.next()
|
|
val_id := p.expr(.lowest)
|
|
vstart := p.add_child(val_id)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .enum_field
|
|
value: field_name
|
|
children_start: vstart
|
|
children_count: 1
|
|
})
|
|
} else {
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .enum_field
|
|
value: field_name
|
|
})
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
start := p.add_children(ids)
|
|
mut typ := ''
|
|
if p.pending_flag {
|
|
typ = 'flag'
|
|
p.pending_flag = false
|
|
}
|
|
return p.a.add_node(flat.Node{
|
|
kind: .enum_decl
|
|
value: name
|
|
typ: typ
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) type_decl() flat.NodeId {
|
|
p.next() // skip 'type'
|
|
// C. or JS. prefix
|
|
if p.tok == .name && (p.lit == 'C' || p.lit == 'JS') {
|
|
p.next()
|
|
if p.tok == .dot {
|
|
p.next()
|
|
}
|
|
}
|
|
name := p.expect_name()
|
|
// generic params
|
|
if p.tok == .lsbr {
|
|
p.skip_brackets()
|
|
}
|
|
p.expect(.assign)
|
|
first_type := p.parse_type_name()
|
|
// check for sum type: type T = A | B | C
|
|
// skip auto-semicolon before pipe
|
|
if p.tok == .pipe || (p.tok == .semicolon && p.peek_is(token.Token.pipe)) {
|
|
mut variants := []flat.NodeId{}
|
|
variants << p.a.add_val(.ident, first_type)
|
|
for p.tok == .pipe || (p.tok == .semicolon && p.peek_is(token.Token.pipe)) {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
p.next() // skip |
|
|
variant_type := p.parse_type_name()
|
|
variants << p.a.add_val(.ident, variant_type)
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
start := p.add_children(variants)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .type_decl
|
|
value: name
|
|
children_start: start
|
|
children_count: flat.child_count(variants.len)
|
|
})
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
// type alias
|
|
return p.a.add_node(flat.Node{
|
|
kind: .type_decl
|
|
value: name
|
|
typ: first_type
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) interface_decl() flat.NodeId {
|
|
p.next() // skip 'interface'
|
|
mut name := p.expect(.name)
|
|
for p.tok == .dot {
|
|
p.next()
|
|
name += '.' + p.expect(.name)
|
|
}
|
|
// generic params
|
|
if p.tok == .lsbr {
|
|
p.skip_brackets()
|
|
}
|
|
p.check(.lcbr)
|
|
mut ids := []flat.NodeId{}
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
if p.tok == .colon {
|
|
p.next()
|
|
}
|
|
continue
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
field_name := p.expect_name_or_keyword()
|
|
if p.tok == .lpar {
|
|
// method: name(params) ret_type
|
|
p.next() // skip (
|
|
mut params := []flat.NodeId{}
|
|
for p.tok != .rpar && p.tok != .eof {
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
}
|
|
// Interface method params may be named (e.g. `seed_data []u32`,
|
|
// `node &ast.Node`) or type-only (`[]u32`). When the current token
|
|
// is a plain identifier and the next token starts a type, that
|
|
// identifier is the param name; consume it first so the array/pointer
|
|
// prefix of the real type is not mis-parsed onto the name
|
|
// (e.g. `seed_data[]`). `map`/`chan` are type heads, not names.
|
|
if p.tok == .name && p.lit != 'map' && p.lit != 'chan' {
|
|
nt := p.peek()
|
|
if nt == .name || nt == .amp || nt == .question || nt == .not
|
|
|| nt == .key_fn || nt == .ellipsis
|
|
|| (nt == .lsbr && p.peek_lbr_starts_array_type()) {
|
|
p.next()
|
|
}
|
|
}
|
|
ptype := p.parse_type_name()
|
|
params << p.a.add_node(flat.Node{
|
|
kind: .param
|
|
typ: ptype
|
|
})
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rpar) // skip )
|
|
mut ret_type := ''
|
|
if p.tok != .semicolon && p.tok != .rcbr && p.tok != .eof {
|
|
ret_type = p.parse_type_name()
|
|
}
|
|
start := p.add_children(params)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .interface_field
|
|
op: .dot
|
|
value: field_name
|
|
typ: ret_type
|
|
children_start: start
|
|
children_count: flat.child_count(params.len)
|
|
})
|
|
} else if p.tok == .semicolon || p.tok == .rcbr {
|
|
// embedded type or field without explicit type
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .interface_field
|
|
value: field_name
|
|
})
|
|
} else {
|
|
// field: name type
|
|
ftype := p.parse_type_name()
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .interface_field
|
|
value: field_name
|
|
typ: ftype
|
|
})
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .interface_decl
|
|
value: name
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) import_stmt() flat.NodeId {
|
|
p.next() // skip 'import'
|
|
mut name := p.expect_name()
|
|
mut alias := name
|
|
for p.tok == .dot {
|
|
p.next()
|
|
alias = p.expect_name()
|
|
name += '.' + alias
|
|
}
|
|
if p.tok == .key_as {
|
|
p.next()
|
|
alias = p.expect_name()
|
|
}
|
|
// selective import: import mod { sym1, sym2 }
|
|
if p.tok == .lcbr {
|
|
p.next()
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
p.expect_name_or_keyword()
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
return p.a.add_node(flat.Node{
|
|
kind: .import_decl
|
|
value: name
|
|
typ: alias
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) module_stmt() flat.NodeId {
|
|
p.next() // skip 'module'
|
|
name := p.expect_name()
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
return p.a.add_node(flat.Node{
|
|
kind: .module_decl
|
|
value: name
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) directive() flat.NodeId {
|
|
full := p.lit
|
|
p.next() // skip '#' (lit already contains the full line)
|
|
mut name := full
|
|
mut value := ''
|
|
space_idx := full.index_u8(` `)
|
|
if space_idx > 0 {
|
|
name = full[..space_idx]
|
|
value = full[space_idx + 1..].trim_space()
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
return p.a.add_node(flat.Node{
|
|
kind: .directive
|
|
value: name
|
|
typ: value
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) skip_attrs() {
|
|
if p.tok == .attribute {
|
|
p.next()
|
|
if p.tok == .name && p.lit == 'flag' {
|
|
p.pending_flag = true
|
|
}
|
|
if p.tok == .key_if {
|
|
p.next()
|
|
mut cond := strings.new_builder(32)
|
|
for p.tok != .rsbr && p.tok != .eof {
|
|
tok_str := p.comptime_cond_token_text()
|
|
if cond.len > 0 && tok_str != '?' {
|
|
cond.write_string(' ')
|
|
}
|
|
cond.write_string(tok_str)
|
|
p.next()
|
|
}
|
|
if !eval_comptime_cond(p.prefs, cond.str()) {
|
|
p.skip_next_decl = true
|
|
}
|
|
}
|
|
for p.tok != .rsbr && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
p.check(.rsbr)
|
|
return
|
|
}
|
|
if p.tok == .lsbr {
|
|
p.skip_brackets()
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) skip_top_level_stmt() {
|
|
// A newline after the attribute group (`@[if flag ?]`) yields an auto-inserted
|
|
// semicolon before the declaration; skip it (and any further attributes) so the
|
|
// declaration itself — not just the empty statement — is skipped.
|
|
for p.tok == .attribute || p.tok == .lsbr || p.tok == .semicolon {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
p.skip_attrs()
|
|
}
|
|
if p.tok == .key_pub {
|
|
p.next()
|
|
}
|
|
match p.tok {
|
|
.key_fn {
|
|
p.next()
|
|
for p.tok != .lcbr && p.tok != .semicolon && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
if p.tok == .lcbr {
|
|
p.skip_block()
|
|
} else if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
}
|
|
.key_struct, .key_union, .key_enum, .key_interface, .key_const, .key_global {
|
|
for p.tok != .lcbr && p.tok != .semicolon && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
if p.tok == .lcbr {
|
|
p.skip_block()
|
|
} else if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
}
|
|
else {
|
|
for p.tok != .semicolon && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) parse_comptime_if() flat.NodeId {
|
|
p.next() // skip $
|
|
if p.tok != .key_if {
|
|
// $for or other comptime — skip
|
|
if p.tok == .key_for {
|
|
p.next()
|
|
for p.tok != .lcbr && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
p.skip_block()
|
|
} else {
|
|
for p.tok != .semicolon && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
}
|
|
return flat.empty_node
|
|
}
|
|
p.next() // skip 'if'
|
|
cond := p.parse_comptime_cond()
|
|
if comptime_cond_has_type_test(cond) {
|
|
p.skip_block()
|
|
p.skip_comptime_else()
|
|
return flat.empty_node
|
|
}
|
|
taken := eval_comptime_cond(p.prefs, cond)
|
|
if taken {
|
|
result := p.block_stmt()
|
|
p.skip_comptime_else()
|
|
return result
|
|
} else {
|
|
p.skip_block()
|
|
return p.parse_comptime_else()
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) parse_comptime_cond() string {
|
|
mut cond := strings.new_builder(64)
|
|
for p.tok != .lcbr && p.tok != .eof {
|
|
tok_str := p.comptime_cond_token_text()
|
|
if cond.len > 0 && tok_str != '?' {
|
|
cond.write_string(' ')
|
|
}
|
|
cond.write_string(tok_str)
|
|
p.next()
|
|
}
|
|
return cond.str()
|
|
}
|
|
|
|
fn (p &Parser) comptime_cond_token_text() string {
|
|
if p.lit.len > 0 {
|
|
return p.lit
|
|
}
|
|
if p.s.pos >= 0 && p.s.pos < p.s.src.len {
|
|
c := p.s.src[p.s.pos]
|
|
if c == `&` && p.s.pos + 1 < p.s.src.len && p.s.src[p.s.pos + 1] == `&` {
|
|
return '&&'
|
|
}
|
|
if c == `|` && p.s.pos + 1 < p.s.src.len && p.s.src[p.s.pos + 1] == `|` {
|
|
return '||'
|
|
}
|
|
if c == `!` {
|
|
return '!'
|
|
}
|
|
if c == `?` {
|
|
return '?'
|
|
}
|
|
}
|
|
tok := p.tok
|
|
if tok == .and {
|
|
return '&&'
|
|
}
|
|
if tok == .logical_or {
|
|
return '||'
|
|
}
|
|
if tok == .not {
|
|
return '!'
|
|
}
|
|
if tok == .question {
|
|
return '?'
|
|
}
|
|
if tok == .key_true {
|
|
return 'true'
|
|
}
|
|
if tok == .key_false {
|
|
return 'false'
|
|
}
|
|
return ''
|
|
}
|
|
|
|
fn comptime_cond_has_type_test(cond string) bool {
|
|
return cond.contains(' is ') || cond.contains(' !is ')
|
|
}
|
|
|
|
fn (mut p Parser) skip_comptime_else() {
|
|
for {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
if p.tok != .dollar || p.peek() != .key_else {
|
|
return
|
|
}
|
|
p.next() // skip $
|
|
p.next() // skip else
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
if p.tok != .dollar || p.peek() != .key_if {
|
|
p.skip_block()
|
|
return
|
|
}
|
|
p.next() // skip $
|
|
p.next() // skip if
|
|
for p.tok != .lcbr && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
p.skip_block()
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) parse_comptime_else() flat.NodeId {
|
|
// Skip auto-semicolons before $else
|
|
if p.tok == .semicolon && p.peek() == .dollar {
|
|
p.next()
|
|
}
|
|
if p.tok != .dollar {
|
|
return flat.empty_node
|
|
}
|
|
if p.peek() != .key_else {
|
|
return flat.empty_node
|
|
}
|
|
p.next() // skip $
|
|
p.next() // skip else
|
|
// $else $if — recurse
|
|
if p.tok == .dollar || (p.tok == .semicolon && p.peek() == .dollar) {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
return p.parse_comptime_if()
|
|
}
|
|
return p.block_stmt()
|
|
}
|
|
|
|
fn eval_comptime_cond(prefs &pref.Preferences, cond string) bool {
|
|
c := cond.trim_space()
|
|
if c.starts_with('!') {
|
|
return !eval_comptime_cond(prefs, c[1..])
|
|
}
|
|
if c.contains('&&') {
|
|
left := c.all_before('&&')
|
|
right := c.all_after('&&')
|
|
return eval_comptime_cond(prefs, left) && eval_comptime_cond(prefs, right)
|
|
}
|
|
if c.contains('||') {
|
|
left := c.all_before('||')
|
|
right := c.all_after('||')
|
|
return eval_comptime_cond(prefs, left) || eval_comptime_cond(prefs, right)
|
|
}
|
|
flag := c.trim_space().trim_right('? ')
|
|
return pref.comptime_flag_value(prefs, flag)
|
|
}
|
|
|
|
fn (mut p Parser) skip_block() {
|
|
if p.tok != .lcbr {
|
|
return
|
|
}
|
|
mut depth := 1
|
|
p.next()
|
|
for depth > 0 && p.tok != .eof {
|
|
if p.tok == .lcbr {
|
|
depth++
|
|
} else if p.tok == .rcbr {
|
|
depth--
|
|
}
|
|
p.next()
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) skip_brackets() {
|
|
if p.tok != .lsbr {
|
|
return
|
|
}
|
|
mut depth := 1
|
|
p.next()
|
|
for depth > 0 && p.tok != .eof {
|
|
if p.tok == .lsbr {
|
|
depth++
|
|
} else if p.tok == .rsbr {
|
|
depth--
|
|
}
|
|
p.next()
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) skip_parens() {
|
|
if p.tok != .lpar {
|
|
return
|
|
}
|
|
mut depth := 1
|
|
p.next()
|
|
for depth > 0 && p.tok != .eof {
|
|
if p.tok == .lpar {
|
|
depth++
|
|
} else if p.tok == .rpar {
|
|
depth--
|
|
}
|
|
p.next()
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) parse_comptime_expr() flat.NodeId {
|
|
if p.peek() == .key_for {
|
|
return p.parse_comptime_if()
|
|
}
|
|
p.next() // skip $
|
|
if p.tok == .key_if || (p.tok == .name && p.lit == 'if') {
|
|
return p.parse_comptime_if_expr_after_if()
|
|
}
|
|
if p.tok == .name && p.lit == 'd' {
|
|
p.next()
|
|
if p.tok != .lpar {
|
|
return flat.empty_node
|
|
}
|
|
p.next()
|
|
// First argument is the compile-time define name. v3 currently only
|
|
// uses the default expression, so consume the name expression.
|
|
if p.tok != .comma && p.tok != .rpar && p.tok != .eof {
|
|
p.expr(.lowest)
|
|
}
|
|
if p.tok == .comma {
|
|
p.next()
|
|
default_expr := p.expr(.lowest)
|
|
for p.tok != .rpar && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
p.check(.rpar)
|
|
return default_expr
|
|
}
|
|
p.check(.rpar)
|
|
return flat.empty_node
|
|
}
|
|
if p.tok == .name && p.lit == 'res' {
|
|
p.next()
|
|
if p.tok == .lpar {
|
|
p.skip_parens()
|
|
}
|
|
return p.a.add_val_id(3, 'false')
|
|
}
|
|
for p.tok != .semicolon && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
return flat.empty_node
|
|
}
|
|
|
|
fn (mut p Parser) parse_comptime_if_expr() flat.NodeId {
|
|
p.next() // skip $
|
|
if p.tok != .key_if && !(p.tok == .name && p.lit == 'if') {
|
|
return flat.empty_node
|
|
}
|
|
return p.parse_comptime_if_expr_after_if()
|
|
}
|
|
|
|
fn (mut p Parser) parse_comptime_if_expr_after_if() flat.NodeId {
|
|
p.next() // skip if
|
|
cond := p.parse_comptime_cond()
|
|
taken := eval_comptime_cond(p.prefs, cond)
|
|
if taken {
|
|
result := p.parse_comptime_expr_block()
|
|
p.skip_comptime_else()
|
|
return result
|
|
}
|
|
p.skip_block()
|
|
return p.parse_comptime_else_expr()
|
|
}
|
|
|
|
fn (mut p Parser) parse_comptime_else_expr() flat.NodeId {
|
|
if p.tok == .semicolon && p.peek() == .dollar {
|
|
p.next()
|
|
}
|
|
if p.tok != .dollar || p.peek() != .key_else {
|
|
return flat.empty_node
|
|
}
|
|
p.next() // skip $
|
|
p.next() // skip else
|
|
if p.tok == .semicolon && p.peek() == .dollar {
|
|
p.next()
|
|
}
|
|
if p.tok == .dollar {
|
|
return p.parse_comptime_if_expr()
|
|
}
|
|
return p.parse_comptime_expr_block()
|
|
}
|
|
|
|
fn (mut p Parser) parse_comptime_expr_block() flat.NodeId {
|
|
if p.tok != .lcbr {
|
|
return flat.empty_node
|
|
}
|
|
p.next()
|
|
if p.tok == .rcbr {
|
|
p.next()
|
|
return flat.empty_node
|
|
}
|
|
result := p.expr(.lowest)
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
if p.tok == .rcbr {
|
|
p.next()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ==================== statements ====================
|
|
|
|
fn (mut p Parser) stmt() flat.NodeId {
|
|
match p.tok {
|
|
.key_return {
|
|
return p.return_stmt()
|
|
}
|
|
.key_if {
|
|
return p.if_stmt()
|
|
}
|
|
.key_for {
|
|
return p.for_stmt()
|
|
}
|
|
.key_fn {
|
|
return p.fn_decl()
|
|
}
|
|
.key_match {
|
|
return p.match_stmt()
|
|
}
|
|
.key_break {
|
|
p.next()
|
|
mut label := ''
|
|
if p.tok == .name {
|
|
label = p.lit
|
|
p.next()
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
return p.a.add_val(.break_stmt, label)
|
|
}
|
|
.key_continue {
|
|
p.next()
|
|
mut label := ''
|
|
if p.tok == .name {
|
|
label = p.lit
|
|
p.next()
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
return p.a.add_val(.continue_stmt, label)
|
|
}
|
|
.key_mut {
|
|
p.next()
|
|
if p.tok == .key_static {
|
|
return p.static_decl_stmt()
|
|
}
|
|
return p.assign_or_expr_stmt()
|
|
}
|
|
.key_static {
|
|
return p.static_decl_stmt()
|
|
}
|
|
.key_pub {
|
|
p.next()
|
|
if p.tok == .key_fn {
|
|
return p.fn_decl()
|
|
}
|
|
return p.assign_or_expr_stmt()
|
|
}
|
|
.attribute {
|
|
p.skip_attrs()
|
|
return p.stmt()
|
|
}
|
|
.key_unsafe {
|
|
p.next()
|
|
return p.block_stmt()
|
|
}
|
|
.key_defer {
|
|
return p.defer_stmt()
|
|
}
|
|
.key_assert {
|
|
return p.assert_stmt()
|
|
}
|
|
.key_goto {
|
|
return p.goto_stmt()
|
|
}
|
|
.key_go, .key_spawn {
|
|
p.next()
|
|
spawn_expr := p.expr(.lowest)
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
sstart := p.add_child(spawn_expr)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .expr_stmt
|
|
children_start: sstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
.key_asm {
|
|
return p.asm_stmt()
|
|
}
|
|
.dollar {
|
|
return p.parse_comptime_if()
|
|
}
|
|
.hash {
|
|
return p.directive()
|
|
}
|
|
.lcbr {
|
|
return p.block_stmt()
|
|
}
|
|
.semicolon {
|
|
p.next()
|
|
return flat.empty_node
|
|
}
|
|
.name {
|
|
// label: name followed by ':'
|
|
if p.peek() == .colon {
|
|
label_name := p.lit
|
|
p.next() // skip name
|
|
p.next() // skip :
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
return p.a.add_val(.label_stmt, label_name)
|
|
}
|
|
return p.assign_or_expr_stmt()
|
|
}
|
|
else {
|
|
return p.assign_or_expr_stmt()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) static_decl_stmt() flat.NodeId {
|
|
p.next() // skip `static`
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
}
|
|
lhs := p.expr(.lowest)
|
|
if p.tok == .decl_assign {
|
|
p.next()
|
|
rhs := p.expr(.lowest)
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
istart := p.add_children2(lhs, rhs)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .decl_assign
|
|
op: .assign
|
|
value: 'static'
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
estart := p.add_child(lhs)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .expr_stmt
|
|
children_start: estart
|
|
children_count: 1
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) return_stmt() flat.NodeId {
|
|
p.next() // skip 'return'
|
|
mut ids := []flat.NodeId{}
|
|
if p.tok != .semicolon && p.tok != .rcbr && p.tok != .eof {
|
|
ids << p.expr(.lowest)
|
|
for p.tok == .comma {
|
|
p.next()
|
|
ids << p.expr(.lowest)
|
|
}
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .return_stmt
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) if_stmt() flat.NodeId {
|
|
p.next() // skip 'if'
|
|
cond := p.expr(.lowest)
|
|
|
|
// if-guard: if a, b := expr { ... } or if val := expr { ... }
|
|
mut guard_cond := cond
|
|
if p.tok == .comma || p.tok == .decl_assign {
|
|
// Simple if-guard; treat as regular condition for flat AST
|
|
if p.tok == .decl_assign {
|
|
p.next()
|
|
rhs := p.expr(.lowest)
|
|
istart := p.add_children2(guard_cond, rhs)
|
|
guard_cond = p.a.add_node(flat.Node{
|
|
kind: .decl_assign
|
|
op: .assign
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
} else {
|
|
// comma case: if a, b := expr
|
|
mut lhs_ids := []flat.NodeId{}
|
|
lhs_ids << guard_cond
|
|
for p.tok == .comma {
|
|
p.next()
|
|
lhs_ids << p.expr(.lowest)
|
|
}
|
|
if p.tok == .decl_assign {
|
|
p.next()
|
|
rhs := p.expr(.lowest)
|
|
mut all_ids := []flat.NodeId{cap: lhs_ids.len + 1}
|
|
all_ids << lhs_ids[0]
|
|
all_ids << rhs
|
|
for i in 1 .. lhs_ids.len {
|
|
all_ids << lhs_ids[i]
|
|
}
|
|
istart := p.add_children(all_ids)
|
|
guard_cond = p.a.add_node(flat.Node{
|
|
kind: .decl_assign
|
|
op: .assign
|
|
children_start: istart
|
|
children_count: flat.child_count(all_ids.len)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// skip auto-semicolon before {
|
|
if p.tok == .semicolon && p.peek() == .lcbr {
|
|
p.next()
|
|
}
|
|
body := p.block_stmt()
|
|
mut ids := []flat.NodeId{}
|
|
ids << guard_cond
|
|
ids << body
|
|
|
|
// skip auto-semicolon between } and else
|
|
if p.tok == .semicolon && p.peek() == .key_else {
|
|
p.next()
|
|
}
|
|
if p.tok == .key_else {
|
|
p.next()
|
|
if p.tok == .key_if {
|
|
ids << p.if_stmt()
|
|
} else {
|
|
ids << p.block_stmt()
|
|
}
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .if_expr
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) for_stmt() flat.NodeId {
|
|
p.next() // skip 'for'
|
|
if p.tok == .lcbr {
|
|
// infinite loop: for { ... }
|
|
body_ids := p.parse_block_body()
|
|
empty1 := p.a.add(flat.NodeKind.empty)
|
|
empty2 := p.a.add(flat.NodeKind.empty)
|
|
empty3 := p.a.add(flat.NodeKind.empty)
|
|
mut ids := []flat.NodeId{}
|
|
ids << empty1
|
|
ids << empty2
|
|
ids << empty3
|
|
for id in body_ids {
|
|
ids << id
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .for_stmt
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
// Check for for-in: `for x in ...` or `for i, x in ...` or `for mut x in ...`
|
|
if p.tok == .name && (p.peek() == .key_in || p.peek() == .comma) {
|
|
first_expr := p.expr(.bit_or)
|
|
return p.for_in(first_expr, false)
|
|
}
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
mut first_expr := flat.empty_node
|
|
if p.tok == .name {
|
|
ident := p.a.add_val(.ident, p.expect_name())
|
|
if p.tok == .key_in || p.tok == .comma {
|
|
first_expr = ident
|
|
} else {
|
|
first_expr = p.expr_with_lhs(ident, .lowest)
|
|
}
|
|
} else {
|
|
first_expr = p.expr(.lowest)
|
|
}
|
|
if p.tok == .key_in || p.tok == .comma {
|
|
return p.for_in(first_expr, true)
|
|
}
|
|
if p.tok == .lcbr {
|
|
body_ids := p.parse_block_body()
|
|
init_empty := p.a.add(flat.NodeKind.empty)
|
|
post_empty := p.a.add(flat.NodeKind.empty)
|
|
mut ids := []flat.NodeId{}
|
|
ids << init_empty
|
|
ids << first_expr
|
|
ids << post_empty
|
|
for id in body_ids {
|
|
ids << id
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .for_stmt
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
}
|
|
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
cond := if p.tok == .semicolon {
|
|
p.a.add(flat.NodeKind.empty)
|
|
} else {
|
|
p.expr(.lowest)
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
post := if p.tok != .lcbr && p.tok != .eof {
|
|
p.assign_or_expr_inline()
|
|
} else {
|
|
p.a.add(flat.NodeKind.empty)
|
|
}
|
|
body_ids := p.parse_block_body()
|
|
mut ids := []flat.NodeId{}
|
|
ids << p.a.add(flat.NodeKind.empty)
|
|
ids << cond
|
|
ids << post
|
|
for id in body_ids {
|
|
ids << id
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .for_stmt
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
first_expr := p.expr(.lowest)
|
|
|
|
// for-in: `for x in expr` or `for i, x in expr`
|
|
if p.tok == .key_in || p.tok == .comma {
|
|
return p.for_in(first_expr, false)
|
|
}
|
|
|
|
// C-style: `for i := 0; ...`
|
|
if p.tok == .decl_assign || token_is_assignment(p.tok) {
|
|
return p.for_c_style(first_expr)
|
|
}
|
|
|
|
if p.tok == .semicolon {
|
|
// could be C-style with expression init, or just a separator
|
|
// peek to check
|
|
pk := p.peek()
|
|
if pk == .semicolon || pk == .lcbr {
|
|
// condition-only: `for cond { }`
|
|
// fall through
|
|
} else if pk != .rcbr && pk != .eof {
|
|
// might be C-style: `for expr; cond; post`
|
|
p.next() // skip ;
|
|
cond := p.expr(.lowest)
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
post := if p.tok != .lcbr && p.tok != .eof {
|
|
p.assign_or_expr_inline()
|
|
} else {
|
|
p.a.add(flat.NodeKind.empty)
|
|
}
|
|
body_ids := p.parse_block_body()
|
|
// first_expr becomes init as expr_stmt
|
|
init_start := p.add_child(first_expr)
|
|
init_id := p.a.add_node(flat.Node{
|
|
kind: .expr_stmt
|
|
children_start: init_start
|
|
children_count: 1
|
|
})
|
|
mut ids := []flat.NodeId{}
|
|
ids << init_id
|
|
ids << cond
|
|
ids << post
|
|
for id in body_ids {
|
|
ids << id
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .for_stmt
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
}
|
|
|
|
// condition-only: `for cond { ... }`
|
|
if p.tok == .lcbr {
|
|
body_ids := p.parse_block_body()
|
|
init_empty := p.a.add(flat.NodeKind.empty)
|
|
post_empty := p.a.add(flat.NodeKind.empty)
|
|
mut ids := []flat.NodeId{}
|
|
ids << init_empty
|
|
ids << first_expr
|
|
ids << post_empty
|
|
for id in body_ids {
|
|
ids << id
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .for_stmt
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
return flat.empty_node
|
|
}
|
|
|
|
fn (mut p Parser) for_c_style(lhs_expr flat.NodeId) flat.NodeId {
|
|
op_id := int(p.tok)
|
|
p.next()
|
|
rhs := p.expr(.lowest)
|
|
|
|
mut init_id := flat.empty_node
|
|
if op_id == 12 {
|
|
istart := p.add_children2(lhs_expr, rhs)
|
|
init_id = p.a.add_node(flat.Node{
|
|
kind: .decl_assign
|
|
op: .assign
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
} else {
|
|
istart := p.add_children2(lhs_expr, rhs)
|
|
init_id = p.a.add_node(flat.Node{
|
|
kind: .assign
|
|
op: token_id_to_op(op_id)
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
}
|
|
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
|
|
cond := p.expr(.lowest)
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
|
|
post := if p.tok != .lcbr && p.tok != .eof {
|
|
p.assign_or_expr_inline()
|
|
} else {
|
|
p.a.add(flat.NodeKind.empty)
|
|
}
|
|
|
|
body_ids := p.parse_block_body()
|
|
|
|
mut ids := []flat.NodeId{}
|
|
ids << init_id
|
|
ids << cond
|
|
ids << post
|
|
for id in body_ids {
|
|
ids << id
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .for_stmt
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) for_in(first_expr flat.NodeId, first_is_mut bool) flat.NodeId {
|
|
// first_expr is either the key var or the only var
|
|
mut key_id := first_expr
|
|
mut val_id := flat.empty_node
|
|
mut value_is_mut := first_is_mut
|
|
|
|
if p.tok == .comma {
|
|
p.next()
|
|
// second variable
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
value_is_mut = true
|
|
}
|
|
val_id = p.a.add_val(.ident, p.expect_name())
|
|
}
|
|
|
|
p.check(.key_in)
|
|
was_in_for_container := p.in_for_container
|
|
p.in_for_container = true
|
|
container := p.expr(.lowest)
|
|
p.in_for_container = was_in_for_container
|
|
|
|
// optional range: `for i in 0 .. n`
|
|
mut range_end := flat.empty_node
|
|
if p.tok == .dotdot {
|
|
p.next()
|
|
range_end = p.expr(.lowest)
|
|
}
|
|
|
|
body_ids := p.parse_block_body()
|
|
|
|
mut ids := []flat.NodeId{cap: 4 + body_ids.len}
|
|
ids << key_id
|
|
ids << val_id
|
|
ids << container
|
|
if int(range_end) >= 0 {
|
|
ids << range_end
|
|
}
|
|
for id in body_ids {
|
|
ids << id
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .for_in_stmt
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
// value field stores the count of header elements (key, val, container, [range_end])
|
|
// so gen knows where body starts
|
|
value: if int(range_end) >= 0 { '4' } else { '3' }
|
|
op: if value_is_mut { .amp } else { .none }
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) match_stmt() flat.NodeId {
|
|
p.next() // skip 'match'
|
|
match_expr := p.expr(.lowest)
|
|
p.check(.lcbr)
|
|
|
|
mut ids := []flat.NodeId{cap: 8}
|
|
ids << match_expr
|
|
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
ids << p.match_branch()
|
|
}
|
|
p.check(.rcbr)
|
|
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .match_stmt
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) match_branch_cond() flat.NodeId {
|
|
if p.tok == .name && p.peek() == .dot {
|
|
mod_name := p.lit
|
|
p.next()
|
|
p.next()
|
|
if p.tok == .name && p.lit.len > 0 && p.lit[0] >= `A` && p.lit[0] <= `Z` {
|
|
type_name := p.lit
|
|
p.next()
|
|
mod_id := p.a.add_val(.ident, mod_name)
|
|
start := p.add_child(mod_id)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .selector
|
|
value: type_name
|
|
children_start: start
|
|
children_count: 1
|
|
})
|
|
}
|
|
base_id := p.a.add_val(.ident, mod_name)
|
|
start := p.add_child(base_id)
|
|
sel := p.a.add_node(flat.Node{
|
|
kind: .selector
|
|
value: p.lit
|
|
children_start: start
|
|
children_count: 1
|
|
})
|
|
if p.tok == .name {
|
|
p.next()
|
|
}
|
|
if p.tok != .lcbr && p.tok != .comma {
|
|
return p.expr_with_lhs(sel, .lowest)
|
|
}
|
|
return sel
|
|
}
|
|
if p.tok == .name && p.lit.len > 0 && p.lit[0] >= `A` && p.lit[0] <= `Z` {
|
|
name := p.lit
|
|
p.next()
|
|
return p.a.add_val(.ident, name)
|
|
}
|
|
if p.tok == .name && is_builtin_type(p.lit) && p.peek() == .lcbr {
|
|
name := p.lit
|
|
p.next()
|
|
return p.a.add_val(.ident, name)
|
|
}
|
|
cond := p.expr(.lowest)
|
|
if p.tok == .ellipsis {
|
|
p.next()
|
|
rhs := p.expr(.lowest)
|
|
rstart := p.add_children2(cond, rhs)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .range
|
|
children_start: rstart
|
|
children_count: 2
|
|
})
|
|
}
|
|
return cond
|
|
}
|
|
|
|
fn (mut p Parser) match_branch() flat.NodeId {
|
|
mut branch_ids := []flat.NodeId{}
|
|
mut is_else := false
|
|
mut n_conds := 0
|
|
|
|
if p.tok == .key_else {
|
|
is_else = true
|
|
p.next()
|
|
} else {
|
|
branch_ids << p.match_branch_cond()
|
|
n_conds = 1
|
|
for p.tok == .comma {
|
|
p.next()
|
|
branch_ids << p.match_branch_cond()
|
|
n_conds++
|
|
}
|
|
}
|
|
|
|
p.check(.lcbr)
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.looks_like_match_branch_start() {
|
|
break
|
|
}
|
|
id := p.stmt()
|
|
if int(id) >= 0 {
|
|
branch_ids << id
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
|
|
bstart := p.add_children(branch_ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .match_branch
|
|
value: if is_else { 'else' } else { '${n_conds}' }
|
|
children_start: bstart
|
|
children_count: flat.child_count(branch_ids.len)
|
|
})
|
|
}
|
|
|
|
fn (p &Parser) looks_like_match_branch_start() bool {
|
|
if p.tok == .key_else {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
fn (mut p Parser) block_stmt() flat.NodeId {
|
|
ids := p.parse_block_body()
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .block
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) parse_block_body() []flat.NodeId {
|
|
p.check(.lcbr)
|
|
mut ids := []flat.NodeId{}
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
id := p.stmt()
|
|
if int(id) >= 0 {
|
|
ids << id
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
return ids
|
|
}
|
|
|
|
fn (mut p Parser) assign_or_expr_stmt() flat.NodeId {
|
|
lhs := p.expr(.lowest)
|
|
|
|
// multi-assign: a, b := expr1, expr2
|
|
if p.tok == .comma {
|
|
mut lhs_ids := []flat.NodeId{}
|
|
lhs_ids << lhs
|
|
for p.tok == .comma {
|
|
p.next()
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
}
|
|
lhs_ids << p.expr(.lowest)
|
|
}
|
|
if p.tok == .decl_assign || token_is_assignment(p.tok) {
|
|
op_id := int(p.tok)
|
|
p.next()
|
|
mut rhs_ids := []flat.NodeId{}
|
|
rhs_ids << p.expr(.lowest)
|
|
for p.tok == .comma {
|
|
p.next()
|
|
rhs_ids << p.expr(.lowest)
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
mut all_ids := []flat.NodeId{cap: lhs_ids.len * 2}
|
|
for i in 0 .. lhs_ids.len {
|
|
all_ids << lhs_ids[i]
|
|
if i < rhs_ids.len {
|
|
all_ids << rhs_ids[i]
|
|
}
|
|
}
|
|
istart := p.add_children(all_ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: if op_id == 12 {
|
|
flat.NodeKind.decl_assign
|
|
} else {
|
|
flat.NodeKind.assign
|
|
}
|
|
op: token_id_to_op(op_id)
|
|
children_start: istart
|
|
children_count: flat.child_count(all_ids.len)
|
|
})
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
mut stmt_ids := []flat.NodeId{cap: lhs_ids.len}
|
|
for expr_id in lhs_ids {
|
|
estart := p.add_child(expr_id)
|
|
stmt_ids << p.a.add_node(flat.Node{
|
|
kind: .expr_stmt
|
|
children_start: estart
|
|
children_count: 1
|
|
})
|
|
}
|
|
bstart := p.add_children(stmt_ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .block
|
|
children_start: bstart
|
|
children_count: flat.child_count(stmt_ids.len)
|
|
})
|
|
}
|
|
|
|
if p.tok == .decl_assign {
|
|
p.next()
|
|
rhs := p.expr(.lowest)
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
istart := p.add_children2(lhs, rhs)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .decl_assign
|
|
op: .assign
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
}
|
|
|
|
if token_is_assignment(p.tok) {
|
|
op_id := int(p.tok)
|
|
p.next()
|
|
rhs := p.expr(.lowest)
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
lhs_node := p.a.nodes[int(lhs)]
|
|
kind := if lhs_node.kind == .selector {
|
|
flat.NodeKind.selector_assign
|
|
} else if lhs_node.kind == .index {
|
|
flat.NodeKind.index_assign
|
|
} else {
|
|
flat.NodeKind.assign
|
|
}
|
|
istart := p.add_children2(lhs, rhs)
|
|
return p.a.add_node(flat.Node{
|
|
kind: kind
|
|
op: token_id_to_op(op_id)
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
}
|
|
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
|
|
estart := p.add_child(lhs)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .expr_stmt
|
|
children_start: estart
|
|
children_count: 1
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) assign_or_expr_inline() flat.NodeId {
|
|
lhs := p.expr(.lowest)
|
|
|
|
if token_is_assignment(p.tok) {
|
|
op_id := int(p.tok)
|
|
p.next()
|
|
rhs := p.expr(.lowest)
|
|
lhs_node := p.a.nodes[int(lhs)]
|
|
kind := if lhs_node.kind == .selector {
|
|
flat.NodeKind.selector_assign
|
|
} else if lhs_node.kind == .index {
|
|
flat.NodeKind.index_assign
|
|
} else {
|
|
flat.NodeKind.assign
|
|
}
|
|
istart := p.add_children2(lhs, rhs)
|
|
return p.a.add_node(flat.Node{
|
|
kind: kind
|
|
op: token_id_to_op(op_id)
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
}
|
|
|
|
estart := p.add_child(lhs)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .expr_stmt
|
|
children_start: estart
|
|
children_count: 1
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) defer_stmt() flat.NodeId {
|
|
p.next() // skip 'defer'
|
|
mut mode := ''
|
|
if p.tok == .lpar {
|
|
p.next()
|
|
if p.tok == .key_fn {
|
|
mode = 'function'
|
|
p.next()
|
|
}
|
|
p.check(.rpar)
|
|
}
|
|
body := p.block_stmt()
|
|
dstart := p.add_child(body)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .defer_stmt
|
|
children_start: dstart
|
|
children_count: 1
|
|
value: mode
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) assert_stmt() flat.NodeId {
|
|
p.next() // skip 'assert'
|
|
cond := p.expr(.lowest)
|
|
mut ids := []flat.NodeId{}
|
|
ids << cond
|
|
// optional message: assert cond, 'message'
|
|
if p.tok == .comma {
|
|
p.next()
|
|
ids << p.expr(.lowest)
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
astart := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .assert_stmt
|
|
children_start: astart
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) goto_stmt() flat.NodeId {
|
|
p.next() // skip 'goto'
|
|
label := p.expect_name()
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
return p.a.add_val(.goto_stmt, label)
|
|
}
|
|
|
|
fn (mut p Parser) asm_stmt() flat.NodeId {
|
|
p.next() // skip 'asm'
|
|
// consume optional volatile keyword
|
|
if p.tok == .name && p.lit == 'volatile' {
|
|
p.next()
|
|
}
|
|
// consume the asm block
|
|
if p.tok == .lcbr {
|
|
p.skip_block()
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
return p.a.add(flat.NodeKind.asm_stmt)
|
|
}
|
|
|
|
// ==================== expressions (Pratt parser) ====================
|
|
|
|
fn (mut p Parser) expr(min_bp token.BindingPower) flat.NodeId {
|
|
lhs := p.prefix_expr()
|
|
return p.expr_with_lhs(lhs, min_bp)
|
|
}
|
|
|
|
fn (mut p Parser) expr_with_lhs(first flat.NodeId, min_bp token.BindingPower) flat.NodeId {
|
|
mut lhs := first
|
|
for {
|
|
// selector / method call
|
|
if p.tok == .dot {
|
|
lhs = p.selector_or_method(lhs)
|
|
continue
|
|
}
|
|
// module-qualified struct init: module.Type{} or module.Type{field: val, ...}
|
|
if p.tok == .lcbr {
|
|
lhs_node := p.a.nodes[int(lhs)]
|
|
if lhs_node.kind == .selector && lhs_node.value.len > 0
|
|
&& (p.peek() == .rcbr || p.peek() == .name || p.peek() == .ellipsis) {
|
|
base := p.a.child_node(&lhs_node, 0)
|
|
is_c_struct := base.kind == .ident && base.value == 'C'
|
|
&& !is_all_upper_ident(lhs_node.value)
|
|
is_v_struct := base.kind == .ident && base.value != 'C' && lhs_node.value[0] >= `A`
|
|
&& lhs_node.value[0] <= `Z`
|
|
if is_c_struct || is_v_struct {
|
|
full_name := '${base.value}.${lhs_node.value}'
|
|
lhs = p.struct_init(full_name)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
// function call
|
|
if p.tok == .lpar {
|
|
lhs_node := p.a.nodes[int(lhs)]
|
|
if lhs_node.kind == .selector && lhs_node.children_count > 0 && lhs_node.value.len > 0
|
|
&& lhs_node.value[0] >= `A` && lhs_node.value[0] <= `Z` {
|
|
base := p.a.child_node(&lhs_node, 0)
|
|
if base.kind == .ident && base.value != 'C' {
|
|
full_name := '${base.value}.${lhs_node.value}'
|
|
p.next() // skip (
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
cstart := p.add_child(inner)
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .cast_expr
|
|
value: full_name
|
|
children_start: cstart
|
|
children_count: 1
|
|
})
|
|
continue
|
|
}
|
|
}
|
|
lhs = p.call_args(lhs)
|
|
continue
|
|
}
|
|
// index / generic
|
|
if p.tok == .lsbr {
|
|
lhs = p.index_expr(lhs)
|
|
continue
|
|
}
|
|
// postfix: ++ -- ? !
|
|
if token_is_postfix(p.tok) {
|
|
op_id := int(p.tok)
|
|
p.next()
|
|
pstart := p.add_child(lhs)
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .postfix
|
|
op: token_id_to_op(op_id)
|
|
children_start: pstart
|
|
children_count: 1
|
|
})
|
|
continue
|
|
}
|
|
// postfix `!` error propagation: expr!
|
|
if p.tok == .not {
|
|
p.next()
|
|
ostart := p.add_children2(lhs, p.a.add(flat.NodeKind.empty))
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .or_expr
|
|
value: '!'
|
|
children_start: ostart
|
|
children_count: 2
|
|
})
|
|
continue
|
|
}
|
|
// postfix `?` optional propagation: expr?
|
|
if p.tok == .question {
|
|
p.next()
|
|
ostart := p.add_children2(lhs, p.a.add(flat.NodeKind.empty))
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .or_expr
|
|
value: '?'
|
|
children_start: ostart
|
|
children_count: 2
|
|
})
|
|
continue
|
|
}
|
|
// `as` cast: expr as Type
|
|
if p.tok == .key_as {
|
|
p.next()
|
|
type_name := p.parse_type_name()
|
|
astart := p.add_child(lhs)
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .as_expr
|
|
value: type_name
|
|
children_start: astart
|
|
children_count: 1
|
|
})
|
|
continue
|
|
}
|
|
// `or` block: expr or { ... }
|
|
if p.tok == .key_or {
|
|
p.next()
|
|
or_body := p.block_stmt()
|
|
ostart := p.add_children2(lhs, or_body)
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .or_expr
|
|
children_start: ostart
|
|
children_count: 2
|
|
})
|
|
continue
|
|
}
|
|
// range: expr .. expr
|
|
if p.tok == .dotdot {
|
|
if int(min_bp) > int(token.BindingPower.lowest) {
|
|
break
|
|
}
|
|
p.next()
|
|
rhs := p.expr(.lowest)
|
|
rstart := p.add_children2(lhs, rhs)
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .range
|
|
children_start: rstart
|
|
children_count: 2
|
|
})
|
|
continue
|
|
}
|
|
// `is` / `!is` / `not_is` type check
|
|
if p.tok == .key_is || p.tok == .not_is {
|
|
bp := token.BindingPower.compare
|
|
if int(bp) < int(min_bp) {
|
|
break
|
|
}
|
|
is_negated := p.tok == .not_is
|
|
p.next()
|
|
type_name := p.parse_type_name()
|
|
istart := p.add_child(lhs)
|
|
is_node := p.a.add_node(flat.Node{
|
|
kind: .is_expr
|
|
value: type_name
|
|
children_start: istart
|
|
children_count: 1
|
|
})
|
|
if is_negated {
|
|
nstart := p.add_child(is_node)
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .not
|
|
children_start: nstart
|
|
children_count: 1
|
|
})
|
|
} else {
|
|
lhs = is_node
|
|
}
|
|
continue
|
|
}
|
|
if p.tok == .not && p.peek() == .key_is {
|
|
bp := token.BindingPower.compare
|
|
if int(bp) < int(min_bp) {
|
|
break
|
|
}
|
|
p.next() // skip !
|
|
p.next() // skip is
|
|
type_name := p.parse_type_name()
|
|
istart := p.add_child(lhs)
|
|
is_node := p.a.add_node(flat.Node{
|
|
kind: .is_expr
|
|
value: type_name
|
|
children_start: istart
|
|
children_count: 1
|
|
})
|
|
nstart := p.add_child(is_node)
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .not
|
|
children_start: nstart
|
|
children_count: 1
|
|
})
|
|
continue
|
|
}
|
|
// `in` / `!in` / `not_in`
|
|
if p.tok == .key_in || p.tok == .not_in {
|
|
is_negated := p.tok == .not_in
|
|
bp := token.BindingPower.compare
|
|
if int(bp) < int(min_bp) {
|
|
break
|
|
}
|
|
p.next()
|
|
mut rhs := p.expr(token.BindingPower.bit_or)
|
|
if p.tok == .dotdot {
|
|
p.next()
|
|
range_rhs := p.expr(.lowest)
|
|
rstart := p.add_children2(rhs, range_rhs)
|
|
rhs = p.a.add_node(flat.Node{
|
|
kind: .range
|
|
children_start: rstart
|
|
children_count: 2
|
|
})
|
|
}
|
|
istart := p.add_children2(lhs, rhs)
|
|
in_node := p.a.add_node(flat.Node{
|
|
kind: .in_expr
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
if is_negated {
|
|
nstart := p.add_child(in_node)
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .not
|
|
children_start: nstart
|
|
children_count: 1
|
|
})
|
|
} else {
|
|
lhs = in_node
|
|
}
|
|
continue
|
|
}
|
|
if p.tok == .not && p.peek() == .key_in {
|
|
bp := token.BindingPower.compare
|
|
if int(bp) < int(min_bp) {
|
|
break
|
|
}
|
|
p.next() // skip !
|
|
p.next() // skip in
|
|
mut rhs := p.expr(token.BindingPower.bit_or)
|
|
if p.tok == .dotdot {
|
|
p.next()
|
|
range_rhs := p.expr(.lowest)
|
|
rstart := p.add_children2(rhs, range_rhs)
|
|
rhs = p.a.add_node(flat.Node{
|
|
kind: .range
|
|
children_start: rstart
|
|
children_count: 2
|
|
})
|
|
}
|
|
istart := p.add_children2(lhs, rhs)
|
|
in_node := p.a.add_node(flat.Node{
|
|
kind: .in_expr
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
nstart := p.add_child(in_node)
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .not
|
|
children_start: nstart
|
|
children_count: 1
|
|
})
|
|
continue
|
|
}
|
|
// skip auto-semicolons before infix operators (multi-line expressions)
|
|
if p.tok == .semicolon {
|
|
peek_tok := p.peek()
|
|
if token_is_infix(peek_tok) && int(peek_tok) != 85 && int(peek_tok) != 0 {
|
|
p.next()
|
|
}
|
|
}
|
|
if token_is_assignment(p.tok) {
|
|
break
|
|
}
|
|
// infix operators
|
|
if !token_is_infix(p.tok) {
|
|
break
|
|
}
|
|
op_id := int(p.tok)
|
|
bp := token_id_left_binding_power(op_id)
|
|
if int(bp) < int(min_bp) {
|
|
break
|
|
}
|
|
p.next()
|
|
rhs := p.expr(token_id_right_binding_power(op_id))
|
|
istart := p.add_children2(lhs, rhs)
|
|
lhs = p.a.add_node(flat.Node{
|
|
kind: .infix
|
|
op: token_id_to_op(op_id)
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
}
|
|
|
|
return lhs
|
|
}
|
|
|
|
fn is_float_number_literal(val string) bool {
|
|
if val.len > 2 && val[0] == `0` {
|
|
second := val[1]
|
|
if second == `x` || second == `X` || second == `b` || second == `B` || second == `o`
|
|
|| second == `O` {
|
|
return false
|
|
}
|
|
}
|
|
return val.contains('.') || val.contains('e') || val.contains('E')
|
|
}
|
|
|
|
fn (mut p Parser) prefix_expr() flat.NodeId {
|
|
tok_id := int(p.tok)
|
|
if tok_id == 92 {
|
|
val := p.lit
|
|
p.next()
|
|
kind_id := if is_float_number_literal(val) {
|
|
2
|
|
} else {
|
|
1
|
|
}
|
|
return p.a.add_val_id(kind_id, val)
|
|
}
|
|
if tok_id == 107 {
|
|
return p.string_literal()
|
|
}
|
|
if tok_id == 7 {
|
|
val := p.lit
|
|
p.next()
|
|
return p.a.add_val_id(4, val)
|
|
}
|
|
if tok_id == 66 {
|
|
p.next()
|
|
return p.a.add_val_id(3, 'true')
|
|
}
|
|
if tok_id == 36 {
|
|
p.next()
|
|
return p.a.add_val_id(3, 'false')
|
|
}
|
|
if tok_id == 53 {
|
|
p.next()
|
|
return p.a.add_id(28)
|
|
}
|
|
if tok_id == 54 {
|
|
p.next()
|
|
return p.a.add_id(29)
|
|
}
|
|
if tok_id == 3 {
|
|
p.next()
|
|
inner := p.expr(.highest)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .arrow
|
|
children_start: p.add_child(inner)
|
|
children_count: 1
|
|
})
|
|
}
|
|
if tok_id == 6 || tok_id == 81 || tok_id == 85 || tok_id == 89 {
|
|
p.next()
|
|
operand := p.expr(.highest)
|
|
pstart := p.add_child(operand)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: token_id_to_op(tok_id)
|
|
children_start: pstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
match p.tok {
|
|
.number {
|
|
val := p.lit
|
|
p.next()
|
|
kind_id := if is_float_number_literal(val) {
|
|
2
|
|
} else {
|
|
1
|
|
}
|
|
return p.a.add_val_id(kind_id, val)
|
|
}
|
|
.string {
|
|
return p.string_literal()
|
|
}
|
|
.char {
|
|
val := p.lit
|
|
p.next()
|
|
return p.a.add_val_id(4, val)
|
|
}
|
|
.key_true {
|
|
p.next()
|
|
return p.a.add_val_id(3, 'true')
|
|
}
|
|
.key_false {
|
|
p.next()
|
|
return p.a.add_val_id(3, 'false')
|
|
}
|
|
.key_nil {
|
|
p.next()
|
|
return p.a.add_id(28)
|
|
}
|
|
.key_none {
|
|
p.next()
|
|
return p.a.add_id(29)
|
|
}
|
|
.arrow {
|
|
p.next()
|
|
inner := p.expr(.highest)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .arrow
|
|
children_start: p.add_child(inner)
|
|
children_count: 1
|
|
})
|
|
}
|
|
.key_mut, .key_shared {
|
|
p.next()
|
|
return p.prefix_expr()
|
|
}
|
|
.name, .key_module {
|
|
name := p.lit
|
|
p.next()
|
|
if name == '@FILE' {
|
|
return p.a.add_val_id(5, p.cur_file)
|
|
}
|
|
if name == '@VMODROOT' {
|
|
return p.a.add_val_id(5, vmod_root_for_file(p.cur_file))
|
|
}
|
|
if name == '@VEXEROOT' {
|
|
return p.a.add_val_id(5, p.prefs.vroot)
|
|
}
|
|
if name == '@VEXE' {
|
|
return p.a.add_val_id(5, p.prefs.vroot + '/v')
|
|
}
|
|
if name == '@LINE' {
|
|
return p.a.add_val_id(1, '0')
|
|
}
|
|
if name == '@FN' || name == '@METHOD' {
|
|
return p.a.add_val_id(5, p.cur_fn)
|
|
}
|
|
if name == '@LOCATION' {
|
|
return p.a.add_val_id(5, '${p.cur_file}:0: ${p.cur_fn}')
|
|
}
|
|
if name == '@VCURRENTHASH' || name == '@VHASH' {
|
|
return p.a.add_val_id(5, '')
|
|
}
|
|
if name == 'chan' && p.can_start_type_name() {
|
|
elem_type := p.parse_type_name()
|
|
chan_type := 'chan ${elem_type}'
|
|
if p.tok == .lcbr {
|
|
return p.struct_init(chan_type)
|
|
}
|
|
return p.a.add_val(.ident, chan_type)
|
|
}
|
|
// map init: map[K]V{} or map[K]V{k1: v1, ...}
|
|
if name == 'map' && p.tok == .lsbr {
|
|
p.next() // skip [
|
|
key_type := p.parse_type_name()
|
|
p.check(.rsbr)
|
|
val_type := p.parse_type_name()
|
|
map_type := 'map[${key_type}]${val_type}'
|
|
if p.tok == .lcbr {
|
|
p.next() // skip {
|
|
mut ids := []flat.NodeId{}
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
k := p.expr(.lowest)
|
|
p.check(.colon)
|
|
v := p.expr(.lowest)
|
|
ids << k
|
|
ids << v
|
|
if p.tok == .comma || p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
if ids.len > 0 {
|
|
istart := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .map_init
|
|
value: map_type
|
|
children_start: istart
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
}
|
|
return p.a.add_val(.map_init, map_type)
|
|
}
|
|
// struct init: Name{...}; vlib/builtin also uses concrete lowercase
|
|
// runtime structs like array{} and string{}.
|
|
if !p.in_for_container && p.tok == .lcbr && name.len > 0
|
|
&& ((name[0] >= `A` && name[0] <= `Z`)
|
|
|| name in ['array', 'string', 'map', 'mapnode', '_result', '_option']) {
|
|
return p.struct_init(name)
|
|
}
|
|
// type cast: TypeName(expr) or builtin_type(expr)
|
|
if p.tok == .lpar && name.len > 0 && ((name[0] >= `A` && name[0] <= `Z`)
|
|
|| is_builtin_type(name)) {
|
|
p.next() // skip (
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
cstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .cast_expr
|
|
value: name
|
|
children_start: cstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
return p.a.add_val(.ident, name)
|
|
}
|
|
.lpar {
|
|
p.next()
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
pstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .paren
|
|
children_start: pstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
.lcbr {
|
|
p.next()
|
|
mut ids := []flat.NodeId{}
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.tok == .semicolon || p.tok == .comma {
|
|
p.next()
|
|
continue
|
|
}
|
|
k := p.expr(.lowest)
|
|
p.check(.colon)
|
|
v := p.expr(.lowest)
|
|
ids << k
|
|
ids << v
|
|
if p.tok == .comma || p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .map_init
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
.amp {
|
|
p.next()
|
|
if p.tok == .amp {
|
|
p.next()
|
|
if p.tok == .name && is_builtin_type(p.lit) {
|
|
type_name := '&&${p.lit}'
|
|
p.next()
|
|
if p.tok == .lpar {
|
|
p.next()
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
cstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .cast_expr
|
|
value: type_name
|
|
children_start: cstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
id := p.a.add_val(.ident, type_name[2..])
|
|
first := p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .amp
|
|
children_start: p.add_child(id)
|
|
children_count: 1
|
|
})
|
|
return p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .amp
|
|
children_start: p.add_child(first)
|
|
children_count: 1
|
|
})
|
|
}
|
|
} else if p.tok == .name && is_builtin_type(p.lit) {
|
|
name := p.lit
|
|
type_name := '&${name}'
|
|
p.next()
|
|
if p.tok == .lpar {
|
|
p.next()
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
cstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .cast_expr
|
|
value: type_name
|
|
children_start: cstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
if p.tok == .lcbr {
|
|
inner := p.struct_init(name)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .amp
|
|
children_start: p.add_child(inner)
|
|
children_count: 1
|
|
})
|
|
}
|
|
id := p.a.add_val(.ident, name)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .amp
|
|
children_start: p.add_child(id)
|
|
children_count: 1
|
|
})
|
|
} else if p.tok == .lsbr && p.peek() == .rsbr {
|
|
type_name := '&' + p.parse_type_name()
|
|
if p.tok == .lpar {
|
|
p.next()
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
cstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .cast_expr
|
|
value: type_name
|
|
children_start: cstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
}
|
|
operand := p.expr(.highest)
|
|
pstart := p.add_child(operand)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .amp
|
|
children_start: pstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
.and {
|
|
p.next()
|
|
if p.tok == .name && is_builtin_type(p.lit) {
|
|
type_name := '&&${p.lit}'
|
|
p.next()
|
|
if p.tok == .lpar {
|
|
p.next()
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
cstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .cast_expr
|
|
value: type_name
|
|
children_start: cstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
}
|
|
return p.a.add(.empty)
|
|
}
|
|
.minus, .not, .bit_not, .mul {
|
|
op_id := int(p.tok)
|
|
p.next()
|
|
operand := p.expr(.highest)
|
|
pstart := p.add_child(operand)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: token_id_to_op(op_id)
|
|
children_start: pstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
.question {
|
|
p.next()
|
|
inner_type := p.parse_type_name()
|
|
type_name := if inner_type.len > 0 { '?${inner_type}' } else { '?' }
|
|
if p.tok == .lpar {
|
|
p.next()
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
cstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .cast_expr
|
|
value: type_name
|
|
children_start: cstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
return p.a.add(flat.NodeKind.empty)
|
|
}
|
|
.key_if {
|
|
return p.if_stmt()
|
|
}
|
|
.key_match {
|
|
return p.match_stmt()
|
|
}
|
|
.key_fn {
|
|
return p.fn_literal()
|
|
}
|
|
.key_go, .key_spawn {
|
|
p.next()
|
|
inner := p.expr(.lowest)
|
|
sstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .spawn_expr
|
|
children_start: sstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
.key_lock, .key_rlock {
|
|
return p.lock_expr()
|
|
}
|
|
.key_select {
|
|
return p.select_expr()
|
|
}
|
|
.key_sizeof {
|
|
return p.sizeof_expr()
|
|
}
|
|
.key_typeof {
|
|
return p.typeof_expr()
|
|
}
|
|
.key_offsetof {
|
|
return p.offsetof_expr()
|
|
}
|
|
.key_dump {
|
|
return p.dump_expr()
|
|
}
|
|
.key_likely, .key_unlikely {
|
|
p.next()
|
|
p.check(.lpar)
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
return inner
|
|
}
|
|
.key_isreftype {
|
|
p.next()
|
|
p.check(.lpar)
|
|
p.parse_type_name()
|
|
p.check(.rpar)
|
|
return p.a.add_val_id(3, 'false')
|
|
}
|
|
.dot {
|
|
// enum value: .member
|
|
p.next()
|
|
member := p.expect_name_or_keyword()
|
|
return p.a.add_val(.enum_val, member)
|
|
}
|
|
.lsbr {
|
|
return p.array_literal()
|
|
}
|
|
.key_unsafe {
|
|
p.next()
|
|
return p.block_stmt()
|
|
}
|
|
.ellipsis {
|
|
// spread: ...expr
|
|
p.next()
|
|
inner := p.expr(.lowest)
|
|
pstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .none
|
|
value: '...'
|
|
children_start: pstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
.dollar {
|
|
return p.parse_comptime_expr()
|
|
}
|
|
else {
|
|
p.next()
|
|
return p.a.add(flat.NodeKind.empty)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) selector_or_method(lhs flat.NodeId) flat.NodeId {
|
|
p.next() // skip '.'
|
|
field_name := p.expect_name_or_keyword()
|
|
sel_start := p.add_child(lhs)
|
|
sel := p.a.add_node(flat.Node{
|
|
kind: .selector
|
|
value: field_name
|
|
children_start: sel_start
|
|
children_count: 1
|
|
})
|
|
if p.tok == .lpar {
|
|
lhs_node := p.a.nodes[int(lhs)]
|
|
if lhs_node.kind == .ident && lhs_node.value != 'C' && field_name.len > 0
|
|
&& field_name[0] >= `A` && field_name[0] <= `Z` {
|
|
full_name := '${lhs_node.value}.${field_name}'
|
|
p.next() // skip (
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
cstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .cast_expr
|
|
value: full_name
|
|
children_start: cstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
return p.call_args(sel)
|
|
}
|
|
return sel
|
|
}
|
|
|
|
fn (mut p Parser) call_args(fn_expr flat.NodeId) flat.NodeId {
|
|
p.check(.lpar)
|
|
mut ids := []flat.NodeId{}
|
|
ids << fn_expr
|
|
for p.tok != .rpar && p.tok != .eof {
|
|
prev_offset := p.s.offset
|
|
prev_tok := p.tok
|
|
if p.tok == .key_mut || p.tok == .key_shared {
|
|
p.next()
|
|
}
|
|
// vararg spread: ...expr
|
|
if p.tok == .ellipsis {
|
|
p.next()
|
|
inner := p.expr(.lowest)
|
|
sstart := p.add_child(inner)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .prefix
|
|
op: .none
|
|
value: '...'
|
|
children_start: sstart
|
|
children_count: 1
|
|
})
|
|
} else if p.tok == .logical_or {
|
|
// lambda no args: || expr
|
|
p.next()
|
|
lambda_body := p.expr(.lowest)
|
|
lstart := p.add_child(lambda_body)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .lambda_expr
|
|
children_start: lstart
|
|
children_count: 1
|
|
})
|
|
} else if p.tok == .pipe {
|
|
// lambda with args: |a, b| expr
|
|
p.next()
|
|
mut lambda_params := []flat.NodeId{}
|
|
lambda_params << p.a.add_val(.ident, p.expect_name())
|
|
for p.tok == .comma {
|
|
p.next()
|
|
lambda_params << p.a.add_val(.ident, p.expect_name())
|
|
}
|
|
p.check(.pipe)
|
|
lambda_body := p.expr(.lowest)
|
|
mut lids := lambda_params.clone()
|
|
lids << lambda_body
|
|
lstart := p.add_children(lids)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .lambda_expr
|
|
children_start: lstart
|
|
children_count: flat.child_count(lids.len)
|
|
})
|
|
} else {
|
|
arg := p.expr(.lowest)
|
|
// struct config syntax: name: value
|
|
if p.tok == .colon {
|
|
p.next()
|
|
val := p.expr(.lowest)
|
|
arg_node := p.a.nodes[int(arg)]
|
|
vstart := p.add_child(val)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .field_init
|
|
value: arg_node.value
|
|
children_start: vstart
|
|
children_count: 1
|
|
})
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
} else {
|
|
ids << arg
|
|
}
|
|
}
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
if p.s.offset == prev_offset && p.tok == prev_tok {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rpar)
|
|
cstart := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .call
|
|
children_start: cstart
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) index_expr(lhs flat.NodeId) flat.NodeId {
|
|
p.check(.lsbr)
|
|
// range index: arr[..b]
|
|
if p.tok == .dotdot {
|
|
p.next()
|
|
mut end_id := flat.empty_node
|
|
if p.tok != .rsbr {
|
|
end_id = p.expr(.lowest)
|
|
}
|
|
p.check(.rsbr)
|
|
start_id := p.a.add(flat.NodeKind.empty)
|
|
mut ids := []flat.NodeId{}
|
|
ids << lhs
|
|
ids << start_id
|
|
if int(end_id) >= 0 {
|
|
ids << end_id
|
|
}
|
|
istart := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .index
|
|
value: 'range'
|
|
children_start: istart
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
idx := p.expr(.logical_or)
|
|
// range index: arr[a..b]
|
|
if p.tok == .dotdot {
|
|
p.next()
|
|
mut end_id := flat.empty_node
|
|
if p.tok != .rsbr {
|
|
end_id = p.expr(.lowest)
|
|
}
|
|
p.check(.rsbr)
|
|
mut ids := []flat.NodeId{}
|
|
ids << lhs
|
|
ids << idx
|
|
if int(end_id) >= 0 {
|
|
ids << end_id
|
|
}
|
|
istart := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .index
|
|
value: 'range'
|
|
children_start: istart
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
p.check(.rsbr)
|
|
istart := p.add_children2(lhs, idx)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .index
|
|
children_start: istart
|
|
children_count: 2
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) struct_init(name string) flat.NodeId {
|
|
p.check(.lcbr)
|
|
mut ids := []flat.NodeId{}
|
|
// assoc syntax: Type{...base, field: val}
|
|
if p.tok == .ellipsis {
|
|
p.next()
|
|
base_expr := p.expr(.lowest)
|
|
if p.tok == .comma || p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
mut field_ids := []flat.NodeId{}
|
|
field_ids << base_expr
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
fname := p.expect_name_or_keyword()
|
|
p.check(.colon)
|
|
val := p.expr(.lowest)
|
|
vstart := p.add_child(val)
|
|
field_ids << p.a.add_node(flat.Node{
|
|
kind: .field_init
|
|
value: fname
|
|
children_start: vstart
|
|
children_count: 1
|
|
})
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
start := p.add_children(field_ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .assoc
|
|
value: name
|
|
children_start: start
|
|
children_count: flat.child_count(field_ids.len)
|
|
})
|
|
}
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
// named field: name: expr
|
|
if (p.tok == .name || p.tok.is_keyword()) && p.peek() == .colon {
|
|
fname := p.expect_name_or_keyword()
|
|
p.check(.colon)
|
|
val := p.expr(.lowest)
|
|
vstart := p.add_child(val)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .field_init
|
|
value: fname
|
|
children_start: vstart
|
|
children_count: 1
|
|
})
|
|
} else {
|
|
// positional value (unnamed)
|
|
val := p.expr(.lowest)
|
|
vstart := p.add_child(val)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .field_init
|
|
children_start: vstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .struct_init
|
|
value: name
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) string_literal() flat.NodeId {
|
|
mut q := u8(`'`)
|
|
lit := p.lit
|
|
if lit.len > 0 {
|
|
if lit[0] == `r` && lit.len > 1 {
|
|
q = p.lit[1]
|
|
} else if lit[0] == `'` || lit[0] == `"` {
|
|
q = lit[0]
|
|
}
|
|
}
|
|
p.next()
|
|
if int(p.tok) != 106 {
|
|
val := strip_quotes(lit)
|
|
return p.a.add_val_id(5, val)
|
|
}
|
|
// string interpolation
|
|
val := strip_interp_start_quotes(lit)
|
|
return p.string_interp(val, q)
|
|
}
|
|
|
|
fn (mut p Parser) string_interp(first_part string, quote u8) flat.NodeId {
|
|
mut ids := []flat.NodeId{}
|
|
if first_part.len > 0 {
|
|
ids << p.a.add_val_id(5, first_part)
|
|
}
|
|
for int(p.tok) == 106 {
|
|
p.next() // skip $
|
|
p.check(.lcbr) // skip {
|
|
ids << p.expr(.lowest)
|
|
// format spec: :fmt
|
|
if p.tok == .colon {
|
|
p.next()
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr) // skip }
|
|
if int(p.tok) == 107 {
|
|
part := strip_interp_quotes(p.lit, quote)
|
|
p.next()
|
|
if part.len > 0 {
|
|
ids << p.a.add_val_id(5, part)
|
|
}
|
|
// check for more interpolation after this string part
|
|
}
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .string_interp
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) array_literal() flat.NodeId {
|
|
p.next() // skip '['
|
|
// empty array or fixed array type: []Type{} or [N]Type{}
|
|
if p.tok == .rsbr {
|
|
p.next()
|
|
elem_type := p.parse_type_name()
|
|
if p.tok == .lpar {
|
|
p.next()
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
cstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .cast_expr
|
|
value: '[]${elem_type}'
|
|
children_start: cstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
// array init: []Type{len: n, cap: c, init: v}
|
|
if p.tok == .lcbr {
|
|
p.next()
|
|
mut ids := []flat.NodeId{}
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
if p.tok == .name && p.peek() == .colon {
|
|
fname := p.expect_name()
|
|
p.check(.colon)
|
|
val := p.expr(.lowest)
|
|
vstart := p.add_child(val)
|
|
ids << p.a.add_node(flat.Node{
|
|
kind: .field_init
|
|
value: fname
|
|
children_start: vstart
|
|
children_count: 1
|
|
})
|
|
} else {
|
|
ids << p.expr(.lowest)
|
|
}
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .array_init
|
|
value: elem_type
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
return p.a.add_val(.array_init, elem_type)
|
|
}
|
|
// array literal: [1, 2, 3]
|
|
// or fixed array type: [3]int
|
|
mut ids := []flat.NodeId{}
|
|
ids << p.expr(.lowest)
|
|
// check if it's [N]Type (fixed array type)
|
|
if p.tok == .rsbr {
|
|
p.next()
|
|
if p.tok == .name || p.tok == .amp || p.tok == .lsbr || p.tok == .question {
|
|
// fixed array type: [N]Type
|
|
size_str := p.a.nodes[int(ids[0])].value
|
|
elem_type := p.parse_type_name()
|
|
fixed_type := '[${size_str}]${elem_type}'
|
|
// may have init
|
|
if p.tok == .lcbr {
|
|
p.next()
|
|
mut init_ids := []flat.NodeId{}
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
if p.tok == .name && p.peek() == .colon {
|
|
fname := p.expect_name()
|
|
p.check(.colon)
|
|
val := p.expr(.lowest)
|
|
vstart := p.add_child(val)
|
|
init_ids << p.a.add_node(flat.Node{
|
|
kind: .field_init
|
|
value: fname
|
|
children_start: vstart
|
|
children_count: 1
|
|
})
|
|
} else {
|
|
init_ids << p.expr(.lowest)
|
|
}
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
start := p.add_children(init_ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .array_init
|
|
value: fixed_type
|
|
children_start: start
|
|
children_count: flat.child_count(init_ids.len)
|
|
})
|
|
}
|
|
return p.a.add_val(.array_init, fixed_type)
|
|
}
|
|
// single-element array: [expr]
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .array_literal
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
// multi-element array: [a, b, c]
|
|
for p.tok == .comma {
|
|
p.next()
|
|
if p.tok == .rsbr {
|
|
break
|
|
}
|
|
ids << p.expr(.lowest)
|
|
}
|
|
p.check(.rsbr)
|
|
// check for `!` (fixed array with values)
|
|
if p.tok == .not {
|
|
p.next()
|
|
}
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .array_literal
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) fn_literal() flat.NodeId {
|
|
p.next() // skip 'fn'
|
|
// capture list: fn [a, b] (params) ret { }
|
|
mut capture_ids := []flat.NodeId{}
|
|
if p.tok == .lsbr {
|
|
p.next()
|
|
for p.tok != .rsbr && p.tok != .eof {
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
}
|
|
capture_ids << p.a.add_val(.ident, p.expect_name())
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rsbr)
|
|
}
|
|
// params
|
|
p.check(.lpar)
|
|
mut param_ids := []flat.NodeId{}
|
|
for p.tok != .rpar && p.tok != .eof {
|
|
start_offset := p.s.offset
|
|
param_ids << p.parse_param_group()
|
|
if p.s.offset == start_offset && p.tok != .rpar && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rpar)
|
|
// return type
|
|
mut ret_type := 'void'
|
|
if p.tok != .lcbr && p.tok != .semicolon && p.tok != .eof {
|
|
if p.tok == .name || p.tok == .amp || p.tok == .question || p.tok == .not || p.tok == .lsbr
|
|
|| p.tok == .lpar || p.tok == .key_fn {
|
|
ret_type = p.parse_type_name()
|
|
}
|
|
}
|
|
// body
|
|
mut body_ids := []flat.NodeId{}
|
|
if p.tok == .lcbr {
|
|
p.check(.lcbr)
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
id := p.stmt()
|
|
if int(id) >= 0 {
|
|
body_ids << id
|
|
}
|
|
}
|
|
p.check(.rcbr)
|
|
}
|
|
mut all_ids := []flat.NodeId{cap: capture_ids.len + param_ids.len + body_ids.len}
|
|
for id in capture_ids {
|
|
all_ids << id
|
|
}
|
|
for id in param_ids {
|
|
all_ids << id
|
|
}
|
|
for id in body_ids {
|
|
all_ids << id
|
|
}
|
|
start := p.add_children(all_ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .fn_literal
|
|
typ: ret_type
|
|
children_start: start
|
|
children_count: flat.child_count(all_ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) lock_expr() flat.NodeId {
|
|
is_rlock := p.tok == .key_rlock
|
|
p.next() // skip 'lock' or 'rlock'
|
|
mut obj_ids := []flat.NodeId{}
|
|
// lock objects
|
|
if p.tok != .lcbr {
|
|
obj_ids << p.expr(.lowest)
|
|
for p.tok == .comma {
|
|
p.next()
|
|
obj_ids << p.expr(.lowest)
|
|
}
|
|
}
|
|
body := p.block_stmt()
|
|
mut ids := obj_ids.clone()
|
|
ids << body
|
|
lstart := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .lock_expr
|
|
value: if is_rlock { 'rlock' } else { 'lock' }
|
|
children_start: lstart
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) select_expr() flat.NodeId {
|
|
p.next() // skip 'select'
|
|
p.check(.lcbr)
|
|
mut ids := []flat.NodeId{}
|
|
for p.tok != .rcbr && p.tok != .eof {
|
|
if p.tok == .semicolon {
|
|
p.next()
|
|
continue
|
|
}
|
|
ids << p.select_branch()
|
|
}
|
|
p.check(.rcbr)
|
|
start := p.add_children(ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .select_stmt
|
|
children_start: start
|
|
children_count: flat.child_count(ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) select_branch() flat.NodeId {
|
|
mut is_else := false
|
|
mut cond_ids := []flat.NodeId{}
|
|
if p.tok == .key_else {
|
|
is_else = true
|
|
p.next()
|
|
} else {
|
|
cond_ids << p.expr(.lowest)
|
|
// could be assignment: ch <- val or val := <-ch
|
|
if token_is_assignment(p.tok) || p.tok == .decl_assign {
|
|
op := p.tok
|
|
p.next()
|
|
cond_ids << p.expr(.lowest)
|
|
_ = op
|
|
}
|
|
}
|
|
mut body_ids := []flat.NodeId{}
|
|
if p.tok == .lcbr {
|
|
body_ids = p.parse_block_body()
|
|
}
|
|
mut all_ids := cond_ids.clone()
|
|
for id in body_ids {
|
|
all_ids << id
|
|
}
|
|
start := p.add_children(all_ids)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .select_branch
|
|
value: if is_else { 'else' } else { '' }
|
|
children_start: start
|
|
children_count: flat.child_count(all_ids.len)
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) sizeof_expr() flat.NodeId {
|
|
p.next() // skip 'sizeof'
|
|
p.check(.lpar)
|
|
type_name := p.parse_type_name()
|
|
p.check(.rpar)
|
|
return p.a.add_val(.sizeof_expr, type_name)
|
|
}
|
|
|
|
fn (mut p Parser) typeof_expr() flat.NodeId {
|
|
p.next() // skip 'typeof'
|
|
p.check(.lpar)
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
tstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .typeof_expr
|
|
children_start: tstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) dump_expr() flat.NodeId {
|
|
p.next() // skip 'dump'
|
|
p.check(.lpar)
|
|
inner := p.expr(.lowest)
|
|
p.check(.rpar)
|
|
dstart := p.add_child(inner)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .dump_expr
|
|
children_start: dstart
|
|
children_count: 1
|
|
})
|
|
}
|
|
|
|
fn (mut p Parser) offsetof_expr() flat.NodeId {
|
|
p.next() // skip '__offsetof'
|
|
p.check(.lpar)
|
|
type_name := p.parse_type_name()
|
|
p.check(.comma)
|
|
field_name := p.expect_name()
|
|
p.check(.rpar)
|
|
return p.a.add_node(flat.Node{
|
|
kind: .offsetof_expr
|
|
value: type_name
|
|
typ: field_name
|
|
})
|
|
}
|
|
|
|
// ==================== types ====================
|
|
|
|
fn (mut p Parser) parse_type_name_progress() string {
|
|
start_offset := p.s.offset
|
|
typ := p.parse_type_name()
|
|
if p.s.offset == start_offset && p.tok != .eof {
|
|
p.next()
|
|
}
|
|
return typ
|
|
}
|
|
|
|
fn (mut p Parser) peek_lbr_starts_array_type() bool {
|
|
if p.peek() != .lsbr {
|
|
return false
|
|
}
|
|
mut idx := p.s.offset
|
|
for idx < p.s.src.len && (p.s.src[idx] == ` ` || p.s.src[idx] == `\t`
|
|
|| p.s.src[idx] == `\n` || p.s.src[idx] == `\r`) {
|
|
idx++
|
|
}
|
|
mut depth := 1
|
|
for idx < p.s.src.len {
|
|
c := p.s.src[idx]
|
|
if c == `[` {
|
|
depth++
|
|
} else if c == `]` {
|
|
depth--
|
|
if depth == 0 {
|
|
idx++
|
|
for idx < p.s.src.len && (p.s.src[idx] == ` ` || p.s.src[idx] == `\t`
|
|
|| p.s.src[idx] == `\n` || p.s.src[idx] == `\r`) {
|
|
idx++
|
|
}
|
|
if idx >= p.s.src.len {
|
|
return false
|
|
}
|
|
next := p.s.src[idx]
|
|
return (next >= `a` && next <= `z`) || (next >= `A` && next <= `Z`)
|
|
|| next == `_` || next == `&` || next == `?` || next == `!`
|
|
|| next == `[` || next == `(`
|
|
}
|
|
}
|
|
idx++
|
|
}
|
|
return false
|
|
}
|
|
|
|
fn (p &Parser) can_start_type_name() bool {
|
|
return p.tok == .name || p.tok == .amp || p.tok == .question || p.tok == .not || p.tok == .lsbr
|
|
|| p.tok == .lpar || p.tok == .key_fn || p.tok == .ellipsis || p.tok == .key_mut
|
|
|| p.tok == .key_shared || p.tok == .key_atomic
|
|
}
|
|
|
|
fn fn_type_param_with_mut(typ string, is_mut bool) string {
|
|
if !is_mut || typ.len == 0 || typ.starts_with('&') {
|
|
return typ
|
|
}
|
|
return '&' + typ
|
|
}
|
|
|
|
fn (mut p Parser) parse_fn_type_param() string {
|
|
mut is_mut := false
|
|
if p.tok == .key_mut {
|
|
is_mut = true
|
|
p.next()
|
|
}
|
|
first := p.parse_type_name_progress()
|
|
if first.len == 0 {
|
|
return first
|
|
}
|
|
if p.tok != .comma && p.tok != .rpar && p.tok != .eof && p.can_start_type_name() {
|
|
second := p.parse_type_name_progress()
|
|
if second.len > 0 {
|
|
return fn_type_param_with_mut(second, is_mut)
|
|
}
|
|
}
|
|
return fn_type_param_with_mut(first, is_mut)
|
|
}
|
|
|
|
fn (mut p Parser) parse_type_name() string {
|
|
if p.tok == .name && p.lit == '&' {
|
|
p.next()
|
|
return '&' + p.parse_type_name()
|
|
}
|
|
if p.tok == .key_mut {
|
|
p.next()
|
|
return fn_type_param_with_mut(p.parse_type_name(), true)
|
|
}
|
|
// option ?T
|
|
if p.tok == .question {
|
|
p.next()
|
|
inner := p.parse_type_name()
|
|
if inner.len > 0 {
|
|
return '?' + inner
|
|
}
|
|
return '?'
|
|
}
|
|
// result !T
|
|
if p.tok == .not {
|
|
p.next()
|
|
inner := p.parse_type_name()
|
|
if inner.len > 0 {
|
|
return '!' + inner
|
|
}
|
|
return '!'
|
|
}
|
|
// pointer &T
|
|
if p.tok == .amp {
|
|
p.next()
|
|
return '&' + p.parse_type_name()
|
|
}
|
|
if p.tok == .and {
|
|
p.next()
|
|
return '&&' + p.parse_type_name()
|
|
}
|
|
// variadic ...T
|
|
if p.tok == .ellipsis {
|
|
p.next()
|
|
return '...' + p.parse_type_name()
|
|
}
|
|
// array []T or fixed [N]T
|
|
if p.tok == .lsbr {
|
|
p.next()
|
|
if p.tok == .rsbr {
|
|
p.next()
|
|
return '[]' + p.parse_type_name()
|
|
}
|
|
// fixed array [N]T
|
|
mut len_lit := p.lit
|
|
p.next()
|
|
p.check(.rsbr)
|
|
return '[${len_lit}]' + p.parse_type_name()
|
|
}
|
|
// multi-return (T, U)
|
|
if p.tok == .lpar {
|
|
p.next()
|
|
mut type_list := []string{}
|
|
type_list << p.parse_type_name()
|
|
for p.tok == .comma {
|
|
p.next()
|
|
type_list << p.parse_type_name()
|
|
}
|
|
p.check(.rpar)
|
|
return '(' + type_list.join(', ') + ')'
|
|
}
|
|
// function type fn(T) U
|
|
if p.tok == .key_fn {
|
|
p.next()
|
|
if p.tok != .lpar {
|
|
return 'fn'
|
|
}
|
|
p.next()
|
|
mut ptypes := []string{}
|
|
for p.tok != .rpar && p.tok != .eof {
|
|
typ := p.parse_fn_type_param()
|
|
if typ.len > 0 {
|
|
ptypes << typ
|
|
}
|
|
if p.tok == .comma {
|
|
p.next()
|
|
}
|
|
}
|
|
p.check(.rpar)
|
|
mut ret := ''
|
|
if p.can_start_type_name() {
|
|
ret = p.parse_type_name()
|
|
}
|
|
if ret.len > 0 {
|
|
return 'fn(${ptypes.join(', ')}) ${ret}'
|
|
}
|
|
return 'fn(${ptypes.join(', ')})'
|
|
}
|
|
// atomic / shared modifier
|
|
if p.tok == .key_atomic || p.tok == .key_shared {
|
|
modifier := p.lit
|
|
p.next()
|
|
return '${modifier} ' + p.parse_type_name()
|
|
}
|
|
// nil type
|
|
if p.tok == .key_nil {
|
|
p.next()
|
|
return 'nil'
|
|
}
|
|
// none type
|
|
if p.tok == .key_none {
|
|
p.next()
|
|
return 'none'
|
|
}
|
|
// struct type (inline/anonymous)
|
|
if p.tok == .key_struct {
|
|
p.next()
|
|
if p.tok == .lcbr {
|
|
p.skip_block()
|
|
}
|
|
return 'struct'
|
|
}
|
|
// name
|
|
mut name := ''
|
|
if p.tok == .name {
|
|
name = p.lit
|
|
p.next()
|
|
// map[K]V
|
|
if name == 'map' && p.tok == .lsbr {
|
|
p.next()
|
|
key := p.parse_type_name()
|
|
p.check(.rsbr)
|
|
val := p.parse_type_name()
|
|
return 'map[${key}]${val}'
|
|
}
|
|
// chan T
|
|
if name == 'chan' {
|
|
if p.tok == .name || p.tok == .amp || p.tok == .lsbr || p.tok == .question {
|
|
elem := p.parse_type_name()
|
|
return 'chan ${elem}'
|
|
}
|
|
return 'chan'
|
|
}
|
|
// thread T
|
|
if name == 'thread' {
|
|
if p.tok == .name || p.tok == .amp || p.tok == .lsbr || p.tok == .question {
|
|
elem := p.parse_type_name()
|
|
return 'thread ${elem}'
|
|
}
|
|
return 'thread'
|
|
}
|
|
// qualified: mod.Type
|
|
if p.tok == .dot {
|
|
p.next()
|
|
if p.tok == .name {
|
|
name += '.' + p.lit
|
|
p.next()
|
|
}
|
|
}
|
|
// generic: Type[T, U]
|
|
if p.tok == .lsbr {
|
|
// peek ahead to distinguish generic from index
|
|
pk := p.peek()
|
|
if pk == .name || pk == .amp || pk == .lsbr || pk == .question || pk == .rsbr
|
|
|| pk == .key_fn {
|
|
p.next() // skip [
|
|
mut params := []string{}
|
|
params << p.parse_type_name()
|
|
for p.tok == .comma {
|
|
p.next()
|
|
params << p.parse_type_name()
|
|
}
|
|
p.check(.rsbr)
|
|
name += '[' + params.join(', ') + ']'
|
|
}
|
|
}
|
|
}
|
|
return name
|
|
}
|
|
|
|
// ==================== helpers ====================
|
|
|
|
fn strip_quotes(s string) string {
|
|
mut raw := s
|
|
is_raw := s.len >= 3 && s[0] == `r`
|
|
if is_raw {
|
|
raw = s[1..]
|
|
}
|
|
if raw.len >= 2 && ((raw[0] == `'` && raw[raw.len - 1] == `'`)
|
|
|| (raw[0] == `"` && raw[raw.len - 1] == `"`)) {
|
|
raw = raw[1..raw.len - 1]
|
|
} else if raw.len >= 1 && (raw[0] == `'` || raw[0] == `"`) {
|
|
raw = raw[1..]
|
|
}
|
|
if is_raw {
|
|
return raw
|
|
}
|
|
return unescape_string(raw)
|
|
}
|
|
|
|
fn strip_interp_start_quotes(s string) string {
|
|
mut raw := s
|
|
is_raw := s.len >= 3 && s[0] == `r`
|
|
if is_raw {
|
|
raw = s[1..]
|
|
}
|
|
if raw.len >= 1 && (raw[0] == `'` || raw[0] == `"`) {
|
|
raw = raw[1..]
|
|
}
|
|
if is_raw {
|
|
return raw
|
|
}
|
|
return unescape_string(raw)
|
|
}
|
|
|
|
fn strip_interp_quotes(s string, quote u8) string {
|
|
mut raw := s
|
|
if raw.len >= 1 && raw[raw.len - 1] == quote {
|
|
raw = raw[..raw.len - 1]
|
|
}
|
|
return unescape_string(raw)
|
|
}
|
|
|
|
fn unescape_string(s string) string {
|
|
if !s.contains('\\') {
|
|
return s
|
|
}
|
|
mut buf := unsafe { malloc(s.len + 1) }
|
|
mut j := 0
|
|
mut i := 0
|
|
for i < s.len {
|
|
if s[i] == `\\` && i + 1 < s.len {
|
|
c := match s[i + 1] {
|
|
`n` { u8(`\n`) }
|
|
`t` { u8(`\t`) }
|
|
`r` { u8(`\r`) }
|
|
`\\` { u8(`\\`) }
|
|
`'` { u8(`'`) }
|
|
`"` { u8(`"`) }
|
|
`$` { u8(`$`) }
|
|
`0` { u8(0) }
|
|
`a` { u8(7) }
|
|
`b` { u8(8) }
|
|
`f` { u8(12) }
|
|
`v` { u8(11) }
|
|
else { u8(0xff) }
|
|
}
|
|
|
|
if c != 0xff {
|
|
unsafe {
|
|
buf[j] = c
|
|
}
|
|
j++
|
|
} else {
|
|
unsafe {
|
|
buf[j] = s[i]
|
|
buf[j + 1] = s[i + 1]
|
|
}
|
|
j += 2
|
|
}
|
|
i += 2
|
|
} else {
|
|
unsafe {
|
|
buf[j] = s[i]
|
|
}
|
|
j++
|
|
i++
|
|
}
|
|
}
|
|
unsafe {
|
|
buf[j] = 0
|
|
return tos(buf, j)
|
|
}
|
|
}
|
|
|
|
fn is_builtin_type(name string) bool {
|
|
return name in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64',
|
|
'byte', 'bool', 'string', 'rune', 'char', 'voidptr', 'charptr', 'byteptr', 'usize', 'isize',
|
|
'array', 'map', 'mapnode', '_result', '_option']
|
|
}
|
|
|
|
fn is_all_upper_ident(s string) bool {
|
|
if s.len == 0 {
|
|
return false
|
|
}
|
|
mut has_letter := false
|
|
for ch in s {
|
|
if ch >= `a` && ch <= `z` {
|
|
return false
|
|
}
|
|
if ch >= `A` && ch <= `Z` {
|
|
has_letter = true
|
|
}
|
|
}
|
|
return has_letter
|
|
}
|
|
|
|
fn token_to_op(tok token.Token) flat.Op {
|
|
tv := int(tok)
|
|
if tv == 95 {
|
|
return flat.Op.plus
|
|
}
|
|
if tv == 81 {
|
|
return flat.Op.minus
|
|
}
|
|
if tv == 85 {
|
|
return flat.Op.mul
|
|
}
|
|
if tv == 13 {
|
|
return flat.Op.div
|
|
}
|
|
if tv == 83 {
|
|
return flat.Op.mod
|
|
}
|
|
if tv == 20 {
|
|
return flat.Op.eq
|
|
}
|
|
if tv == 88 {
|
|
return flat.Op.ne
|
|
}
|
|
if tv == 80 {
|
|
return flat.Op.lt
|
|
}
|
|
if tv == 22 {
|
|
return flat.Op.gt
|
|
}
|
|
if tv == 74 {
|
|
return flat.Op.le
|
|
}
|
|
if tv == 21 {
|
|
return flat.Op.ge
|
|
}
|
|
if tv == 0 {
|
|
return flat.Op.amp
|
|
}
|
|
if tv == 94 {
|
|
return flat.Op.pipe
|
|
}
|
|
if tv == 109 {
|
|
return flat.Op.xor
|
|
}
|
|
if tv == 75 {
|
|
return flat.Op.left_shift
|
|
}
|
|
if tv == 99 {
|
|
return flat.Op.right_shift
|
|
}
|
|
if tv == 101 {
|
|
return flat.Op.right_shift_unsigned
|
|
}
|
|
if tv == 1 {
|
|
return flat.Op.logical_and
|
|
}
|
|
if tv == 77 {
|
|
return flat.Op.logical_or
|
|
}
|
|
if tv == 89 {
|
|
return flat.Op.not
|
|
}
|
|
if tv == 6 {
|
|
return flat.Op.bit_not
|
|
}
|
|
if tv == 4 {
|
|
return flat.Op.assign
|
|
}
|
|
if tv == 96 {
|
|
return flat.Op.plus_assign
|
|
}
|
|
if tv == 82 {
|
|
return flat.Op.minus_assign
|
|
}
|
|
if tv == 86 {
|
|
return flat.Op.mul_assign
|
|
}
|
|
if tv == 14 {
|
|
return flat.Op.div_assign
|
|
}
|
|
if tv == 84 {
|
|
return flat.Op.mod_assign
|
|
}
|
|
if tv == 2 {
|
|
return flat.Op.amp_assign
|
|
}
|
|
if tv == 93 {
|
|
return flat.Op.pipe_assign
|
|
}
|
|
if tv == 110 {
|
|
return flat.Op.xor_assign
|
|
}
|
|
if tv == 76 {
|
|
return flat.Op.left_shift_assign
|
|
}
|
|
if tv == 100 {
|
|
return flat.Op.right_shift_assign
|
|
}
|
|
if tv == 102 {
|
|
return flat.Op.right_shift_unsigned_assign
|
|
}
|
|
if tv == 24 {
|
|
return flat.Op.inc
|
|
}
|
|
if tv == 11 {
|
|
return flat.Op.dec
|
|
}
|
|
if tv == 3 {
|
|
return flat.Op.arrow
|
|
}
|
|
if tv == 12 {
|
|
return flat.Op.assign
|
|
}
|
|
return flat.Op.none
|
|
}
|
|
|
|
fn overload_token_name(tok token.Token) string {
|
|
tv := int(tok)
|
|
if tv == 95 {
|
|
return '+'
|
|
}
|
|
if tv == 81 {
|
|
return '-'
|
|
}
|
|
if tv == 85 {
|
|
return '*'
|
|
}
|
|
if tv == 13 {
|
|
return '/'
|
|
}
|
|
if tv == 83 {
|
|
return '%'
|
|
}
|
|
if tv == 20 {
|
|
return '=='
|
|
}
|
|
if tv == 88 {
|
|
return '!='
|
|
}
|
|
if tv == 80 {
|
|
return '<'
|
|
}
|
|
if tv == 22 {
|
|
return '>'
|
|
}
|
|
if tv == 74 {
|
|
return '<='
|
|
}
|
|
if tv == 21 {
|
|
return '>='
|
|
}
|
|
if tv == 94 {
|
|
return '|'
|
|
}
|
|
if tv == 109 {
|
|
return '^'
|
|
}
|
|
return ''
|
|
}
|