// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module ast import v2.token pub const empty_expr = Expr(EmptyExpr(0)) pub const empty_stmt = Stmt(EmptyStmt(0)) type EmptyExpr = u8 type EmptyStmt = u8 pub type Expr = ArrayInitExpr | AsCastExpr | AssocExpr | BasicLiteral | CallExpr | CallOrCastExpr | CastExpr | ComptimeExpr | EmptyExpr | FieldInit | FnLiteral | GenericArgOrIndexExpr | GenericArgs | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | InitExpr | Keyword | KeywordOperator | LambdaExpr | LockExpr | MapInitExpr | MatchExpr | ModifierExpr | OrExpr | ParenExpr | PostfixExpr | PrefixExpr | RangeExpr | SelectExpr | SelectorExpr | SqlExpr | StringInterLiteral | StringLiteral | Tuple | Type | UnsafeExpr // TODO: decide if this going to be done like this (FieldInit) pub type Stmt = AsmStmt | AssertStmt | AssignStmt | BlockStmt | ComptimeStmt | ConstDecl | DeferStmt | Directive | EmptyStmt | EnumDecl | ExprStmt | FlowControlStmt | FnDecl | ForInStmt | ForStmt | GlobalDecl | ImportStmt | InterfaceDecl | LabelStmt | ModuleStmt | ReturnStmt | StructDecl | TypeDecl | []Attribute // pub type Decl = ConstDecl | EnumDecl | FnDecl | GlobalDecl // | InterfaceDecl | StructDecl | TypeDecl // TODO: (re)implement nested sumtype like TS (was removed from v) // currently need to cast to type in parser.type. Should I leave like // this or add these directly to Expr until nesting is implemented? pub type Type = AnonStructType | ArrayFixedType | ArrayType | ChannelType | FnType | GenericType | MapType | NilType | NoneType | OptionType | ResultType | ThreadType | TupleType pub fn (exprs []Expr) name_list() string { mut name_list := '' for i, expr in exprs { name_list += expr.name() if i < exprs.len - 1 { name_list += ',' } } return name_list } pub fn (t Type) name() string { return match t { GenericType { '${t.name.name()}[${t.params.name_list()}]' } else { panic('Type.name(): unsupported expr `${t.type_name()}`, currently only supports `ast.Ident` & `ast.SelectorExpr`') // expr.str() } } } // TODO: fix this, should be only what is needed pub fn (expr Expr) name() string { return match expr { AsCastExpr { '${expr.expr.name()} as ${expr.typ.name()}' } BasicLiteral { expr.value } CallExpr { // TODO: '${expr.lhs.name()}()' } CallOrCastExpr { '${expr.lhs.name()}(${expr.expr.name()})' } EmptyExpr { // TODO: 'EmptyExpr' } GenericArgs { '${expr.lhs.name()}[${expr.args.name_list()}]' } Ident { expr.name } IndexExpr { '${expr.lhs.name()}[${expr.expr.name()}]' } InfixExpr { '${expr.lhs.name()} ${expr.op} ${expr.rhs.name()}' } Keyword { expr.tok.str() } ModifierExpr { '${expr.kind} ${expr.expr.name()}' } ParenExpr { '(${expr.expr.name()})' } PrefixExpr { '${expr.op}${expr.expr.name()}' } SelectorExpr { expr.name() } StringLiteral { "'${expr.value}'" } Type { expr.name() } UnsafeExpr { // TODO: 'UnsafeExpr' } else { panic('Expr.name(): unsupported expr `${expr.type_name()}`, add it?') // expr.str() } } } pub fn (expr Expr) pos() token.Pos { return match expr { Ident { expr.pos } SelectorExpr { expr.pos // NOTE: should we remove `pos` from `SelectorExpr` and use `expr.lhs.pos()` instead? // which would always get the position of the left most part of the `SelectorExpr` // expr.lhs.pos() } else { panic('Expr.pos(): TODO: add ${expr.type_name()}') } } } // File (AST container) pub struct File { pub: attributes []Attribute mod string name string stmts []Stmt imports []ImportStmt } pub enum Language { v c js } pub fn (lang Language) str() string { return match lang { .v { 'V' } .c { 'C' } .js { 'JS' } } } // Expressions pub struct ArrayInitExpr { pub: typ Expr = empty_expr exprs []Expr init Expr = empty_expr cap Expr = empty_expr len Expr = empty_expr pos token.Pos } pub struct AsCastExpr { pub: expr Expr typ Expr pos token.Pos } pub struct AssocExpr { pub: typ Expr expr Expr fields []FieldInit } pub struct BasicLiteral { pub: kind token.Token value string } pub struct CallExpr { pub: lhs Expr args []Expr pos token.Pos } pub struct CallOrCastExpr { pub: lhs Expr expr Expr pos token.Pos } pub struct CastExpr { pub: typ Expr expr Expr pos token.Pos } pub struct ComptimeExpr { pub: expr Expr pos token.Pos } pub struct FieldDecl { pub: name string typ Expr = empty_expr // can be empty as used for const (unless we use something else) value Expr = empty_expr attributes []Attribute } pub struct FieldInit { pub: name string value Expr } // anon fn / closure pub struct FnLiteral { pub: typ FnType captured_vars []Expr stmts []Stmt } pub struct GenericArgs { pub: lhs Expr args []Expr // concrete types } pub struct GenericArgOrIndexExpr { pub: lhs Expr expr Expr } pub struct Ident { pub: pos token.Pos name string } pub struct IfExpr { pub: cond Expr = empty_expr else_expr Expr = empty_expr stmts []Stmt } pub struct IfGuardExpr { pub: stmt AssignStmt } pub struct InfixExpr { pub: op token.Token lhs Expr rhs Expr pos token.Pos } pub struct IndexExpr { pub: lhs Expr expr Expr is_gated bool } pub struct InitExpr { pub: typ Expr fields []FieldInit } pub struct Keyword { pub: tok token.Token } pub struct KeywordOperator { pub: op token.Token exprs []Expr } pub struct Tuple { pub: exprs []Expr } pub struct LambdaExpr { pub: args []Ident expr Expr } pub struct LockExpr { pub: lock_exprs []Expr rlock_exprs []Expr stmts []Stmt } pub struct MapInitExpr { pub: typ Expr = empty_expr keys []Expr vals []Expr pos token.Pos } pub struct MatchBranch { pub: cond []Expr stmts []Stmt pos token.Pos } pub struct MatchExpr { pub: expr Expr branches []MatchBranch pos token.Pos } // TODO: possibly merge modifiers into a bitfield where // multiple modifiers are used. this could also be used // for struct decl `mut:`, `pub mut:` etc. consider this // [flag] // pub enum Modifier { // .atomic // .global // .mutable // .public // .shared // .static // .volatile // } pub struct ModifierExpr { pub: kind token.Token expr Expr } // pub fn (expr ModifierExpr) unwrap() Expr { // return expr.expr // } pub struct OrExpr { pub: expr Expr stmts []Stmt pos token.Pos } pub struct Parameter { pub: name string typ Expr is_mut bool pos token.Pos } pub struct ParenExpr { pub: expr Expr } pub struct PostfixExpr { pub: op token.Token expr Expr } pub struct PrefixExpr { pub: op token.Token expr Expr pos token.Pos } pub struct RangeExpr { pub: op token.Token // `..` exclusive | `...` inclusive start Expr end Expr } pub struct SelectExpr { pub: pos token.Pos stmt Stmt stmts []Stmt next Expr = empty_expr } pub struct SelectorExpr { pub: lhs Expr rhs Ident pos token.Pos } pub fn (se SelectorExpr) leftmost() Expr { if se.lhs is SelectorExpr { return se.lhs.leftmost() } return se.lhs } // pub fn (expr Expr) str() string { // return 'Expr.str() - $expr.type_name()' // } pub fn (se SelectorExpr) name() string { return se.lhs.name() + '.' + se.rhs.name } pub enum StringLiteralKind { c js raw v } pub fn (s StringLiteralKind) str() string { return match s { .c { 'c' } .js { 'js' } .raw { 'r' } .v { 'v' } } } // TODO: allow overriding this method in main v compiler // that is why this method was renamed from `from_string` @[direct_array_access] pub fn StringLiteralKind.from_string_tinyv(s string) !StringLiteralKind { match s[0] { `c` { return .c } `j` { if s[1] == `s` { return .js } } `r` { return .raw } else {} } return error('invalid string prefix `${s}`') } // NOTE: I'm using two nodes StringLiteral & StringInterLiteral // to avoid the extra array allocations when not needed. pub struct StringLiteral { pub: kind StringLiteralKind value string } pub struct StringInterLiteral { pub: kind StringLiteralKind values []string inters []StringInter } pub struct StringInter { pub: format StringInterFormat width int precision int expr Expr // TEMP: prob removed once individual // fields are set, precision etc format_expr Expr = empty_expr } pub enum StringInterFormat { unformatted binary character decimal exponent exponent_short float hex octal pointer_address string } pub fn StringInterFormat.from_u8(c u8) !StringInterFormat { return match c { `b` { .binary } `c` { .character } `d` { .decimal } `e`, `E` { .exponent } `g`, `G` { .exponent_short } `f`, `F` { .float } `x`, `X` { .hex } `o` { .octal } `p` { .pointer_address } `s` { .string } else { error('unknown formatter `${c.ascii_str()}`') } } } pub fn (sif StringInterFormat) str() string { return match sif { .unformatted { '' } .binary { 'b' } .character { 'c' } .decimal { 'd' } .exponent { 'e' } .exponent_short { 'g' } .float { 'f' } .hex { 'x' } .octal { 'o' } .pointer_address { 'p' } .string { 's' } } } pub struct SqlExpr { pub: expr Expr } pub struct UnsafeExpr { pub: stmts []Stmt } // Statements pub struct AsmStmt { pub: arch string } pub struct AssertStmt { pub: expr Expr extra Expr = empty_expr } pub struct AssignStmt { pub: op token.Token lhs []Expr rhs []Expr pos token.Pos } pub fn (attributes []Attribute) has(name string) bool { for attribute in attributes { if attribute.name == name { return true } } return false } pub struct Attribute { pub: name string value Expr comptime_cond Expr } pub struct BlockStmt { pub: stmts []Stmt } pub struct ComptimeStmt { pub: stmt Stmt } pub struct ConstDecl { pub: is_public bool fields []FieldInit } pub struct DeferStmt { pub: stmts []Stmt } // #flag / #include pub struct Directive { pub: name string value string } pub struct EnumDecl { pub: attributes []Attribute is_public bool name string as_type Expr = empty_expr fields []FieldDecl } pub struct ExprStmt { pub: expr Expr } pub struct FlowControlStmt { pub: op token.Token label string } // enum FnArributes { // method // static // } pub struct FnDecl { pub: attributes []Attribute is_public bool is_method bool is_static bool receiver Parameter language Language = .v name string typ FnType stmts []Stmt pos token.Pos } pub struct ForStmt { pub: init Stmt = empty_stmt // initialization cond Expr = empty_expr // condition post Stmt = empty_stmt // post iteration (afterthought) stmts []Stmt } // NOTE: used as the initializer for ForStmt pub struct ForInStmt { pub: // key string // value string // value_is_mut bool // expr Expr // TODO: key Expr = empty_expr value Expr expr Expr } pub struct GlobalDecl { pub: attributes []Attribute fields []FieldDecl } pub struct ImportStmt { pub: name string alias string is_aliased bool symbols []Expr } pub struct InterfaceDecl { pub: is_public bool attributes []Attribute name string generic_params []Expr embedded []Expr fields []FieldDecl } pub struct LabelStmt { pub: name string stmt Stmt = empty_stmt } pub struct ModuleStmt { pub: name string } pub struct ReturnStmt { pub: exprs []Expr } pub struct StructDecl { pub: attributes []Attribute is_public bool embedded []Expr language Language = .v name string generic_params []Expr fields []FieldDecl pos token.Pos } pub struct TypeDecl { pub: is_public bool language Language name string generic_params []Expr base_type Expr = empty_expr variants []Expr } // Type Nodes pub struct ArrayType { pub: elem_type Expr } pub struct ArrayFixedType { pub: len Expr elem_type Expr } pub struct ChannelType { pub: cap Expr elem_type Expr } pub struct ThreadType { pub: elem_type Expr = empty_expr } pub struct FnType { pub: generic_params []Expr params []Parameter return_type Expr = empty_expr } pub fn (ft &FnType) str() string { mut s := 'fn(' for param in ft.params { s += param.name + param.typ.str() } s += ') ' + ft.return_type.str() return s } pub fn (ident &Ident) str() string { return ident.name.clone() } // pub fn (expr Expr) str() string { // return match expr { // Ident { // expr.name // } // SelectorExpr { // expr.lhs.str() + '.' + expr.rhs.name // } // else { // 'missing Expr.str() for ${expr.type_name()}' // // expr.str() // } // } // } pub struct AnonStructType { pub: generic_params []Expr embedded []Expr fields []FieldDecl } pub struct GenericType { pub: name Expr params []Expr } pub struct MapType { pub: key_type Expr value_type Expr } pub struct NilType {} pub struct NoneType {} pub struct OptionType { pub: base_type Expr = empty_expr } pub struct ResultType { pub: base_type Expr = empty_expr } pub struct TupleType { pub: types []Expr }