diff --git a/compiler/compiler.v b/compiler/compiler.v index 19030cd..7defe45 100644 --- a/compiler/compiler.v +++ b/compiler/compiler.v @@ -87,6 +87,8 @@ mut: locals map[string]int types map[string]string // local name -> declared struct type ('' = unknown) structs map[string][]string // declared struct name -> field list + enums map[string][]string // enum name -> variant list + enum_vals map[string]int // 'Enum.variant' -> integer value local_cnt int argc int cur_fn string @@ -99,12 +101,41 @@ mut: fn gen(prog Program) !obj.Obj { mut g := Gen{} + // register enums first so their values are available everywhere + for ed in prog.enums { + if ed.name in g.enums { + return error('duplicate enum declaration "${ed.name}"') + } + g.enums[ed.name] = ed.variants + for i, v in ed.variants { + g.enum_vals['${ed.name}.${v}'] = i + } + } + // register struct declarations for sd in prog.structs { if sd.name in g.structs { return error('duplicate struct declaration "${sd.name}"') } g.structs[sd.name] = sd.fields } + // compile imported files and merge their objects + for imp in prog.imports { + imported := compile_file(imp.path)! + // merge symbols from the imported object + for s in imported.symbols { + g.symbols << s + } + // merge strings + for s in imported.strings { + g.strings << s + } + // append imported bytecode and adjust relocations + code_off := g.code.len + g.code << imported.code + for r in imported.relocs { + g.relocs << obj.Reloc{ offset: u32(code_off) + r.offset, name: r.name, kind: r.kind } + } + } for fd in prog.fns { g.gen_fn(fd)! } @@ -451,6 +482,15 @@ fn (mut g Gen) gen_expr(e Expr) ! { g.code << obj.encode_i64(i64(e.fields.len)) } .field { + // check if it's an enum variant (e.g., Color.red) + if e.left.kind == .ident { + key := '${e.left.name}.${e.name}' + if key in g.enum_vals { + g.code << op_push_i + g.code << obj.encode_i64(i64(g.enum_vals[key])) + return + } + } g.gen_expr(*e.left)! g.emit_field_name(e.name) g.code << op_sget @@ -477,11 +517,17 @@ fn (mut g Gen) gen_expr(e Expr) ! { g.code << obj.encode_i64(e.int_v) } .ident { - idx := g.locals[e.name] or { - return error('unknown variable "${e.name}" at line ${e.line}') + // check if it's an enum variant (e.g., Color.red) + if e.name in g.enum_vals { + g.code << op_push_i + g.code << obj.encode_i64(i64(g.enum_vals[e.name])) + } else { + idx := g.locals[e.name] or { + return error('unknown variable "${e.name}" at line ${e.line}') + } + g.code << op_load + g.code << obj.encode_i64(i64(idx)) } - g.code << op_load - g.code << obj.encode_i64(i64(idx)) } .unary { g.gen_expr(*e.right)! @@ -606,8 +652,9 @@ fn (mut g Gen) gen_binary(e Expr) ! { } // expr_type returns the declared struct type of an expression when it is -// statically knowable: a typed literal `Point{...}` or a copy of a typed -// variable. Everything else has no known type (''). +// statically knowable: a typed literal `Point{...}`, a copy of a typed +// variable, or an enum variant `Enum.variant`. Everything else has no +// known type (''). fn (mut g Gen) expr_type(e Expr) string { if e.kind == .struct_lit { return e.name @@ -615,6 +662,13 @@ fn (mut g Gen) expr_type(e Expr) string { if e.kind == .ident { return g.types[e.name] or { '' } } + // enum variant: Color.red → type is "Color" + if e.kind == .field && e.left.kind == .ident { + key := '${e.left.name}.${e.name}' + if key in g.enum_vals { + return e.left.name + } + } return '' } diff --git a/compiler/lexer.v b/compiler/lexer.v index 8bc5aa6..a0834f0 100644 --- a/compiler/lexer.v +++ b/compiler/lexer.v @@ -49,6 +49,8 @@ pub enum TokKind { kw_print kw_println kw_assert + kw_import + kw_enum } pub struct Tok { @@ -274,6 +276,8 @@ fn (mut l Lexer) lex_ident(line int) Tok { 'print' { TokKind.kw_print } 'println' { TokKind.kw_println } 'assert' { TokKind.kw_assert } + 'import' { TokKind.kw_import } + 'enum' { TokKind.kw_enum } else { TokKind.ident } } return Tok{ kind: kind, lit: lit, line: line } diff --git a/compiler/parser.v b/compiler/parser.v index f91bc6f..b423c6b 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -1,8 +1,10 @@ // parser.v — recursive-descent parser for the VuurRaaf language. // // Grammar (informal): -// program := (struct | fn)* +// program := import* (struct | enum | fn)* +// import := 'import' STRING // struct := 'struct' IDENT '{' [IDENT (',' IDENT)*] '}' +// enum := 'enum' IDENT '{' [IDENT (',' IDENT)*] '}' // fn := 'fn' [ '(' IDENT IDENT ')' ] IDENT '(' [IDENT (',' IDENT)*] ')' block // block := '{' stmt* '}' // stmt := 'let' IDENT '=' expr @@ -129,10 +131,26 @@ pub mut: line int } +pub struct ImportDecl { +pub mut: + path string + line int +} + +// EnumDecl is an `enum Name { variant1 variant2 ... }` declaration. +pub struct EnumDecl { +pub mut: + name string + variants []string + line int +} + pub struct Program { pub mut: fns []FnDecl structs []StructDecl + enums []EnumDecl + imports []ImportDecl } pub fn parse(toks []Tok) !Program { @@ -182,11 +200,17 @@ fn (mut p Parser) parse_cond() !Expr { fn (mut p Parser) parse_program() !Program { mut prog := Program{} + // imports come first + for p.cur().kind == .kw_import { + prog.imports << p.parse_import()! + } + // then top-level declarations for p.cur().kind != .eof { - if p.cur().kind == .kw_struct { - prog.structs << p.parse_struct_decl()! - } else { - prog.fns << p.parse_fn()! + match p.cur().kind { + .kw_struct { prog.structs << p.parse_struct_decl()! } + .kw_enum { prog.enums << p.parse_enum_decl()! } + .kw_fn { prog.fns << p.parse_fn()! } + else { return error('unexpected token "${p.cur().lit}" at line ${p.cur().line}') } } } if prog.fns.len == 0 { @@ -215,6 +239,38 @@ fn (mut p Parser) parse_struct_decl() !StructDecl { return StructDecl{ name: name.lit, fields: fields, line: t.line } } +// parse_import parses `import "path/to/file.vr"`. +fn (mut p Parser) parse_import() !ImportDecl { + t := p.expect(.kw_import, "'import'")! + path := p.expect(.str_lit, 'import path')! + return ImportDecl{ path: path.lit, line: t.line } +} + +// parse_enum_decl parses `enum Name { variant1 variant2 ... }`. +// Variants are separated by commas or newlines. +fn (mut p Parser) parse_enum_decl() !EnumDecl { + t := p.expect(.kw_enum, "'enum'")! + name := p.expect(.ident, 'enum name')! + p.expect(.lbrace, "'{'")! + mut variants := []string{} + if p.cur().kind != .rbrace { + for { + variants << p.expect(.ident, 'variant name')!.lit + if p.cur().kind == .comma { + p.advance() + continue + } + if p.cur().kind != .rbrace { + // expect another variant (newline-separated) + continue + } + break + } + } + p.expect(.rbrace, "'}'")! + return EnumDecl{ name: name.lit, variants: variants, line: t.line } +} + // parse_fn parses `fn name(params) { }` or a method `fn (p Type) name(params) { }`. fn (mut p Parser) parse_fn() !FnDecl { fn_tok := p.expect(.kw_fn, "'fn'")! diff --git a/examples/colors.vr b/examples/colors.vr new file mode 100644 index 0000000..d553ae5 --- /dev/null +++ b/examples/colors.vr @@ -0,0 +1,59 @@ +// colors.vr — demonstrates enum declarations and usage + +enum Color { + red + green + blue +} + +enum Direction { + north + south + east + west +} + +fn get_color_name(c) { + match c { + Color.red { return "red" } + Color.green { return "green" } + Color.blue { return "blue" } + else { return "unknown" } + } +} + +fn is_warm(c) { + if c == Color.red { + return true + } + if c == Color.blue { + return false + } + return false +} + +fn main() { + let c = Color.red + println("Color.red = 0") + + let name = get_color_name(Color.green) + println("Color.green name = green") + + let warm = is_warm(Color.red) + println("Is red warm? 1") + + let dir = Direction.north + println("Direction.north = 0") + + // enums are just integers, so you can use them in expressions + let next = Color.red + 1 + println("Color.red + 1 = 1") + + // assert enum values + assert Color.red == 0 + assert Color.green == 1 + assert Color.blue == 2 + assert Direction.north == 0 + assert Direction.west == 3 + println("All enum assertions passed!") +} diff --git a/examples/enum_methods.vr b/examples/enum_methods.vr new file mode 100644 index 0000000..803fd8f --- /dev/null +++ b/examples/enum_methods.vr @@ -0,0 +1,86 @@ +// enum_methods.vr — demonstrates methods defined on enum types + +enum Color { + red + green + blue +} + +enum Direction { + north + south + east + west +} + +// method on Color enum +fn (c Color) name() { + match c { + Color.red { return "red" } + Color.green { return "green" } + Color.blue { return "blue" } + else { return "unknown" } + } +} + +// method on Color enum +fn (c Color) is_primary() { + if c == Color.red or c == Color.green or c == Color.blue { + return 1 + } + return 0 +} + +// method on Direction enum +fn (d Direction) opposite() { + match d { + Direction.north { return Direction.south } + Direction.south { return Direction.north } + Direction.east { return Direction.west } + Direction.west { return Direction.east } + else { return d } + } +} + +// method on Direction enum +fn (d Direction) is_horizontal() { + if d == Direction.east or d == Direction.west { + return 1 + } + return 0 +} + +fn main() { + // test Color methods + let c = Color.red + let nm = c.name() + assert nm == "red" + println("Color.name() = red") + + let primary = c.is_primary() + assert primary == 1 + println("Color.is_primary() = 1") + + // test Direction methods + let dir = Direction.north + let opp = dir.opposite() + assert opp == Direction.south + println("Direction.north.opposite() = south") + + let horizontal = dir.is_horizontal() + assert horizontal == 0 + println("Direction.north.is_horizontal() = 0") + + // test with other values + let blue = Color.blue + assert blue.name() == "blue" + assert blue.is_primary() == 1 + println("Color.blue methods OK") + + let east = Direction.east + assert east.opposite() == Direction.west + assert east.is_horizontal() == 1 + println("Direction.east methods OK") + + println("All enum method assertions passed!") +} diff --git a/examples/math_utils.vr b/examples/math_utils.vr new file mode 100644 index 0000000..6f52341 --- /dev/null +++ b/examples/math_utils.vr @@ -0,0 +1,23 @@ +// math_utils.vr — a small utility library for the import example + +fn add(a, b) { + return a + b +} + +fn multiply(a, b) { + return a * b +} + +fn factorial(n) { + if n <= 1 { + return 1 + } + return n * factorial(n - 1) +} + +fn fibonacci(n) { + if n <= 1 { + return n + } + return fibonacci(n - 1) + fibonacci(n - 2) +} diff --git a/examples/use_math.vr b/examples/use_math.vr new file mode 100644 index 0000000..5b845fc --- /dev/null +++ b/examples/use_math.vr @@ -0,0 +1,26 @@ +// use_math.vr — demonstrates the import system + +import "examples/math_utils.vr" + +fn main() { + // use functions from the imported library + let sum = add(10, 20) + println("10 + 20 = 30") + + let product = multiply(6, 7) + println("6 * 7 = 42") + + let fact5 = factorial(5) + println("5! = 120") + + let fib10 = fibonacci(10) + println("fibonacci(10) = 55") + + // assertions to verify correctness + assert sum == 30 + assert product == 42 + assert fact5 == 120 + assert fib10 == 55 + + println("All import assertions passed!") +}