diff --git a/cmd/lsp/completion.go b/cmd/lsp/completion.go new file mode 100644 index 0000000..a44e116 --- /dev/null +++ b/cmd/lsp/completion.go @@ -0,0 +1,324 @@ +package lsp + +import ( + "fireball/core" + "fireball/core/ast" + "fireball/core/fuckoff" + "fireball/core/scanner" + "fireball/core/utils" + "github.com/MineGame159/protocol" + "strconv" +) + +func getCompletions(resolver fuckoff.Resolver, node ast.Node, pos core.Pos) *protocol.CompletionList { + c := completions{} + + // Leaf + leaf := ast.GetLeaf(node, pos) + + if leaf != nil { + if isInFunctionBody(pos, leaf) { + switch parent := leaf.Parent().(type) { + case *ast.Member: + if isAfterNode(pos, parent.Value) { + getMemberCompletions(resolver, &c, parent) + } else { + getIdentifierCompletions(resolver, &c, pos, leaf) + } + + default: + getIdentifierCompletions(resolver, &c, pos, leaf) + } + } else { + getTypeCompletions(resolver, &c, pos, leaf.Parent()) + } + } else { + // Non leaf + node = ast.Get(node, pos) + + if isInFunctionBody(pos, node) { + switch node := node.(type) { + case *ast.Member: + if isAfterNode(pos, node.Value) { + getMemberCompletions(resolver, &c, node) + } else { + getIdentifierCompletions(resolver, &c, pos, leaf) + } + + case *ast.StructInitializer: + if isAfterCst(pos, node, scanner.LeftBrace, false) { + if s, ok := ast.As[*ast.Struct](node.Type); ok { + for _, field := range s.Fields { + c.addNode(protocol.CompletionItemKindField, field.Name, printType(field.Type)) + } + } + } + + default: + getIdentifierCompletions(resolver, &c, pos, node) + } + } else { + getTypeCompletions(resolver, &c, pos, node) + } + } + + // Return + return c.get() +} + +func getTypeCompletions(resolver fuckoff.Resolver, c *completions, pos core.Pos, node ast.Node) { + switch node := node.(type) { + case *ast.Struct: + for _, field := range node.Fields { + if field.Type == nil && isAfterNode(pos, field.Name) { + getGlobalCompletions(resolver, c, false) + } + } + + case *ast.Impl: + if isAfterCst(pos, node, scanner.Impl, true) { + getGlobalCompletions(resolver, c, false) + } + + case *ast.Enum: + if isAfterCst(pos, node, scanner.Colon, true) { + getGlobalCompletions(resolver, c, false) + } + + case *ast.Func: + for _, param := range node.Params { + if param.Type == nil && isAfterNode(pos, param.Name) { + getGlobalCompletions(resolver, c, false) + } + } + + if isAfterCst(pos, node, scanner.RightParen, true) { + getGlobalCompletions(resolver, c, false) + } + + case *ast.Field: + if isAfterNode(pos, node.Name) { + getGlobalCompletions(resolver, c, false) + } + + case *ast.Param: + if isAfterNode(pos, node.Name) { + getGlobalCompletions(resolver, c, false) + } + + case ast.Type: + if !isComplexType(node) { + getGlobalCompletions(resolver, c, false) + } + } +} + +func getMemberCompletions(resolver fuckoff.Resolver, c *completions, member *ast.Member) { + if s, ok := asThroughPointer[*ast.Struct](member.Value.Result().Type); ok { + fields := s.Fields + static := false + + if member.Value.Result().Kind == ast.TypeResultKind { + fields = s.StaticFields + static = true + } + + for _, field := range fields { + c.addNode(protocol.CompletionItemKindField, field.Name, printType(field.Type)) + } + + for _, method := range resolver.GetMethods(s, static) { + c.addNode(protocol.CompletionItemKindMethod, method.Name, printType(method)) + } + } else if e, ok := asThroughPointer[*ast.Enum](member.Value.Result().Type); ok { + for _, case_ := range e.Cases { + c.addNode(protocol.CompletionItemKindEnumMember, case_.Name, strconv.FormatInt(case_.ActualValue, 10)) + } + } +} + +func getIdentifierCompletions(resolver fuckoff.Resolver, c *completions, pos core.Pos, node ast.Node) { + // Types and global functions + getGlobalCompletions(resolver, c, true) + + // Variables + function := ast.GetParent[*ast.Func](node) + + if function != nil { + names := utils.NewSet[string]() + + // This + if s := function.Method(); s != nil { + c.add(protocol.CompletionItemKindVariable, "this", printType(s)) + } + + // Parameters + for _, param := range function.Params { + if param.Name != nil && !names.Contains(param.Name.String()) { + c.addNode(protocol.CompletionItemKindVariable, param.Name, printType(param.Type)) + names.Add(param.Name.String()) + } + } + + // Variables + varResolver := variableResolver{target: pos} + if !node.Token().IsEmpty() { + varResolver.targetVariableName = node + } + + varResolver.VisitNode(function) + + for i := len(varResolver.variables) - 1; i >= 0; i-- { + variable := varResolver.variables[i] + add := true + + if parent, ok := node.Parent().(*ast.Var); ok { + add = parent != variable + } + + if add && variable.Name != nil && !names.Contains(variable.Name.String()) { + c.addNode(protocol.CompletionItemKindVariable, variable.Name, printType(variable.ActualType)) + names.Add(variable.Name.String()) + } + } + } +} + +func getGlobalCompletions(resolver fuckoff.Resolver, c *completions, functions bool) { + // Primitive types + c.add(protocol.CompletionItemKindStruct, "void", "") + c.add(protocol.CompletionItemKindStruct, "bool", "") + + c.add(protocol.CompletionItemKindStruct, "u8", "") + c.add(protocol.CompletionItemKindStruct, "u16", "") + c.add(protocol.CompletionItemKindStruct, "u32", "") + c.add(protocol.CompletionItemKindStruct, "u64", "") + + c.add(protocol.CompletionItemKindStruct, "i8", "") + c.add(protocol.CompletionItemKindStruct, "i16", "") + c.add(protocol.CompletionItemKindStruct, "i32", "") + c.add(protocol.CompletionItemKindStruct, "i64", "") + + c.add(protocol.CompletionItemKindStruct, "f32", "") + c.add(protocol.CompletionItemKindStruct, "f64", "") + + // True, false + c.add(protocol.CompletionItemKindKeyword, "true", "bool") + c.add(protocol.CompletionItemKindKeyword, "false", "bool") + + // Language defined types and functions + for _, file := range resolver.GetFileNodes() { + for _, decl := range file.Decls { + switch decl := decl.(type) { + case *ast.Struct: + c.addNode(protocol.CompletionItemKindStruct, decl.Name, "") + + case *ast.Enum: + c.addNode(protocol.CompletionItemKindEnum, decl.Name, "") + + case *ast.Func: + if functions { + c.addNode(protocol.CompletionItemKindFunction, decl.Name, printType(decl)) + } + } + } + } +} + +// Utils + +func isComplexType(type_ ast.Type) bool { + switch type_.(type) { + case *ast.Struct, *ast.Enum, *ast.Func: + return true + + default: + return false + } +} + +func isAfterNode(pos core.Pos, node ast.Node) bool { + return !ast.IsNil(node) && node.Cst() != nil && pos.Line == node.Cst().Range.End.Line && pos.Column > node.Cst().Range.End.Column +} + +func isAfterCst(pos core.Pos, node ast.Node, kind scanner.TokenKind, sameLine bool) bool { + if node.Cst() == nil { + return false + } + + child := node.Cst().Get(kind) + if child == nil { + return false + } + + after := child.Range.End + + if sameLine { + return pos.Line == after.Line && pos.Column > after.Column + } + + return pos.IsAfter(after) +} + +func isInFunctionBody(pos core.Pos, node ast.Node) bool { + function := ast.GetParent[*ast.Func](node) + if function == nil || function.Cst() == nil { + return false + } + + left := function.Cst().Get(scanner.LeftBrace) + if left == nil { + return false + } + + right := function.Cst().Get(scanner.RightBrace) + if right == nil { + return false + } + + return core.Range{Start: left.Range.Start, End: right.Range.End}.Contains(pos) +} + +func printType(type_ ast.Type) string { + return ast.PrintTypeOptions(type_, ast.TypePrintOptions{ParamNames: true}) +} + +// Completions + +type completions struct { + items []protocol.CompletionItem +} + +var commitCharacters = []string{".", ";"} +var commitCharactersFunction = []string{".", ";", "("} + +func (c *completions) addNode(kind protocol.CompletionItemKind, name ast.Node, detail string) { + if !ast.IsNil(name) { + c.add(kind, name.String(), detail) + } +} + +func (c *completions) add(kind protocol.CompletionItemKind, name, detail string) { + characters := commitCharacters + if kind == protocol.CompletionItemKindFunction || kind == protocol.CompletionItemKindMethod { + characters = commitCharactersFunction + } + + c.items = append(c.items, protocol.CompletionItem{ + Kind: kind, + Label: name, + Detail: detail, + CommitCharacters: characters, + }) +} + +func (c *completions) get() *protocol.CompletionList { + if len(c.items) == 0 { + return nil + } + + return &protocol.CompletionList{ + IsIncomplete: false, + Items: c.items, + } +} diff --git a/cmd/lsp/definition.go b/cmd/lsp/definition.go index fd41e4d..d3cc1f8 100644 --- a/cmd/lsp/definition.go +++ b/cmd/lsp/definition.go @@ -15,7 +15,7 @@ func getDefinition(resolver fuckoff.Resolver, node ast.Node, pos core.Pos) []pro // Get definition based on the leaf node switch node := node.(type) { case *ast.Identifier: - return getDefinitionIdentifier(node) + return getDefinitionIdentifier(pos, node) case *ast.Resolvable: return newDefinition(node.Resolved()) @@ -27,7 +27,7 @@ func getDefinition(resolver fuckoff.Resolver, node ast.Node, pos core.Pos) []pro return nil } -func getDefinitionIdentifier(identifier *ast.Identifier) []protocol.Location { +func getDefinitionIdentifier(pos core.Pos, identifier *ast.Identifier) []protocol.Location { switch identifier.Kind { case ast.StructKind, ast.EnumKind: return newDefinition(identifier.Result().Type) @@ -53,7 +53,7 @@ func getDefinitionIdentifier(identifier *ast.Identifier) []protocol.Location { return nil } - resolver := variableResolver{target: identifier} + resolver := variableResolver{target: pos, targetVariableName: identifier} resolver.VisitNode(function) if resolver.variable != nil { diff --git a/cmd/lsp/handler.go b/cmd/lsp/handler.go index 1d33858..dc0f7f1 100644 --- a/cmd/lsp/handler.go +++ b/cmd/lsp/handler.go @@ -113,6 +113,10 @@ func (h *handler) Initialize(_ context.Context, params *protocol.InitializeParam InlayHintProvider: true, WorkspaceSymbolProvider: true, DefinitionProvider: true, + CompletionProvider: &protocol.CompletionOptions{ + ResolveProvider: false, + TriggerCharacters: []string{" ", ".", ":", "=", "*", "&", ">", "<", "!"}, + }, Workspace: &protocol.ServerCapabilitiesWorkspace{ FileOperations: &protocol.ServerCapabilitiesWorkspaceFileOperations{ @@ -425,6 +429,27 @@ func (h *handler) Definition(_ context.Context, params *protocol.DefinitionParam return getDefinition(file.Project, file.Ast, pos), nil } +func (h *handler) Completion(ctx context.Context, params *protocol.CompletionParams) (result *protocol.CompletionList, err error) { + defer stop(start(h, "Completion")) + + // Get document + file := h.getFile(params.TextDocument.URI) + if file == nil { + return nil, nil + } + + file.EnsureChecked() + + // Get position + pos := core.Pos{ + Line: uint16(params.Position.Line + 1), + Column: uint16(params.Position.Character), + } + + // Get completions + return getCompletions(file.Project, file.Ast, pos), nil +} + // Utils type request struct { diff --git a/cmd/lsp/helpers.go b/cmd/lsp/helpers.go index 936bfd5..c3a3258 100644 --- a/cmd/lsp/helpers.go +++ b/cmd/lsp/helpers.go @@ -1,6 +1,7 @@ package lsp import ( + "fireball/core" "fireball/core/ast" "fireball/core/cst" "fireball/core/scanner" @@ -42,7 +43,8 @@ type scope struct { } type variableResolver struct { - target ast.Node + target core.Pos + targetVariableName ast.Node scopes []scope variables []*ast.Var @@ -58,7 +60,7 @@ func (v *variableResolver) VisitNode(node ast.Node) { } // Check target - if node == v.target { + if node == v.targetVariableName || (node.Cst() != nil && node.Cst().Range.Start.IsAfter(v.target)) { v.done = true v.checkScope() return @@ -88,7 +90,7 @@ func (v *variableResolver) VisitNode(node ast.Node) { node.AcceptChildren(v) - if pop { + if pop && !v.done { v.popScope() } } @@ -97,7 +99,7 @@ func (v *variableResolver) checkScope() { for i := len(v.variables) - 1; i >= 0; i-- { variable := v.variables[i] - if variable.Name.String() == v.target.String() { + if v.targetVariableName != nil && v.variable == nil && variable.Name.String() == v.targetVariableName.String() { v.variable = variable break } diff --git a/cmd/lsp/unused.go b/cmd/lsp/unused.go index e120e36..d5bd5f0 100644 --- a/cmd/lsp/unused.go +++ b/cmd/lsp/unused.go @@ -41,11 +41,6 @@ func (h *handler) ColorPresentation(ctx context.Context, params *protocol.ColorP return nil, errors.New("not implemented") } -//goland:noinspection GoUnusedParameter -func (h *handler) Completion(ctx context.Context, params *protocol.CompletionParams) (result *protocol.CompletionList, err error) { - return nil, errors.New("not implemented") -} - //goland:noinspection GoUnusedParameter func (h *handler) CompletionResolve(ctx context.Context, params *protocol.CompletionItem) (result *protocol.CompletionItem, err error) { return nil, errors.New("not implemented") diff --git a/core/ast/ast.go b/core/ast/ast.go index f137f8c..979d4af 100644 --- a/core/ast/ast.go +++ b/core/ast/ast.go @@ -27,7 +27,7 @@ type Visitor interface { // GetLeaf() func GetLeaf(node Node, pos core.Pos) Node { - g := get{ + g := getLeaf{ pos: pos, } @@ -35,23 +35,21 @@ func GetLeaf(node Node, pos core.Pos) Node { return g.node } -type get struct { +type getLeaf struct { pos core.Pos node Node } -func (g *get) VisitNode(node Node) { +func (g *getLeaf) VisitNode(node Node) { // Propagate node up the tree if g.node != nil { return } // Check if node contains target position - contains := node.Cst().Range.Contains(g.pos) - - if node.Cst() == nil || contains { + if node.Cst() == nil || node.Cst().Range.Contains(g.pos) { // Check if node is a leaf node - if !node.Token().IsEmpty() && contains { + if !node.Token().IsEmpty() && node.Cst() != nil && node.Cst().Range.Contains(g.pos) { g.node = node return } @@ -66,6 +64,99 @@ func (g *get) VisitNode(node Node) { } } +// Get() + +func Get(node Node, pos core.Pos) Node { + g := get{ + pos: pos, + } + + g.VisitNode(node) + return g.node +} + +type get struct { + pos core.Pos + node Node + + hasChild bool +} + +func (g *get) VisitNode(node Node) { + // Propagate node up the tree + if g.node != nil { + return + } + + // Check if node contains target position + if node.Cst() == nil || g.contains(node) { + // Children + node.AcceptChildren(g) + + // Propagate node up the tree + if g.node != nil { + return + } + + // Set node if we are going back up the tree without a found node and the pos is inside the current range + if g.contains(node) { + g.node = node + return + } + } +} + +func (g *get) contains(node Node) bool { + if node.Cst() == nil { + return false + } + range_ := node.Cst().Range + + if g.pos.Line == range_.End.Line && g.pos.Column >= range_.End.Column && node.Token().IsEmpty() { + if node.Parent().Cst() != nil && (node.Parent().Cst().Range.End.Line > range_.End.Line || node.Parent().Cst().Range.End.Column == range_.End.Column) { + next := GetNextSibling(node) + + if next == nil || (next.Cst() != nil && next.Cst().Range.Start.Line > g.pos.Line) { + return true + } + } + } + + return range_.Contains(g.pos) +} + +// GetNextSibling() + +func GetNextSibling(node Node) Node { + for node.Parent() != nil { + g := getNextSibling{target: node} + node.Parent().AcceptChildren(&g) + + if g.next != nil { + return g.next + } + + node = node.Parent() + } + + return nil +} + +type getNextSibling struct { + target Node + + seenTarget bool + next Node +} + +func (g *getNextSibling) VisitNode(node Node) { + if node == g.target { + g.seenTarget = true + } else if g.seenTarget && g.next == nil { + g.next = node + } +} + // GetParent() func GetParent[T Node](node Node) T { diff --git a/core/ast/cst2ast/statements.go b/core/ast/cst2ast/statements.go index e4719bb..ea937a4 100644 --- a/core/ast/cst2ast/statements.go +++ b/core/ast/cst2ast/statements.go @@ -72,7 +72,11 @@ func (c *converter) convertVarStmt(node cst.Node) ast.Stmt { for _, child := range node.Children { if child.Kind == cst.IdentifierNode { - name = c.convertToken(child) + if name == nil { + name = c.convertToken(child) + } else { + value = c.convertExpr(child) + } } else if child.Kind.IsType() { type_ = c.convertType(child) } else if child.Kind.IsExpr() { diff --git a/core/ast/types_manual.go b/core/ast/types_manual.go index 49570c9..5dd8f4b 100644 --- a/core/ast/types_manual.go +++ b/core/ast/types_manual.go @@ -70,7 +70,7 @@ func (p *Pointer) Align() uint32 { func (p *Pointer) Equals(other Type) bool { if p2, ok := As[*Pointer](other); ok { - return p.Pointee.Equals(p2.Pointee) + return typesEquals(p.Pointee, p2.Pointee) } return false @@ -78,7 +78,7 @@ func (p *Pointer) Equals(other Type) bool { func (p *Pointer) CanAssignTo(other Type) bool { if p2, ok := As[*Pointer](other); ok { - return IsPrimitive(p2.Pointee, Void) || p.Pointee.Equals(p2.Pointee) + return IsPrimitive(p2.Pointee, Void) || typesEquals(p.Pointee, p2.Pointee) } return false @@ -96,7 +96,7 @@ func (a *Array) Align() uint32 { func (a *Array) Equals(other Type) bool { if a2, ok := As[*Array](other); ok { - return a.Base.Equals(a2.Base) && a.Count == a2.Count + return typesEquals(a.Base, a2.Base) && a.Count == a2.Count } return false @@ -171,7 +171,7 @@ func (s *Struct) Equals(other Type) bool { } func fieldEquals(v1, v2 *Field) bool { - return v1.Name.Token().Lexeme == v2.Name.Token().Lexeme && v1.Type.Equals(v2.Type) + return v1.Name.String() == v2.Name.String() && typesEquals(v1.Type, v2.Type) } func (s *Struct) CanAssignTo(other Type) bool { @@ -198,14 +198,14 @@ func (e *Enum) Align() uint32 { func (e *Enum) Equals(other Type) bool { if e2, ok := As[*Enum](other); ok { - return e.ActualType.Equals(e2.ActualType) && slices.EqualFunc(e.Cases, e2.Cases, enumCaseEquals) + return typesEquals(e.ActualType, e2.ActualType) && slices.EqualFunc(e.Cases, e2.Cases, enumCaseEquals) } return false } func enumCaseEquals(v1, v2 *EnumCase) bool { - return v1.Name.Token().Lexeme == v2.Name.Token().Lexeme && v1.ActualValue == v2.ActualValue + return v1.Name.String() == v2.Name.String() && v1.ActualValue == v2.ActualValue } func (e *Enum) CanAssignTo(other Type) bool { @@ -242,19 +242,19 @@ func (f *Func) Equals(other Type) bool { f2Name = f2.Name.String() } - return fName == f2Name && f.Returns.Equals(f2.Returns) && slices.EqualFunc(f.Params, f2.Params, paramEquals) + return fName == f2Name && typesEquals(f.Returns, f2.Returns) && slices.EqualFunc(f.Params, f2.Params, paramEquals) } return false } func paramEquals(v1, v2 *Param) bool { - return v1.Type.Equals(v2.Type) + return typesEquals(v1.Type, v2.Type) } func (f *Func) CanAssignTo(other Type) bool { if f2, ok := As[*Func](other); ok { - return f.Returns.Equals(f2.Returns) && slices.EqualFunc(f.Params, f2.Params, paramEquals) + return typesEquals(f.Returns, f2.Returns) && slices.EqualFunc(f.Params, f2.Params, paramEquals) } return false @@ -325,3 +325,9 @@ func (t *typePrinter) VisitNode(node Node) { type_.AcceptType(t) } } + +// Utils + +func typesEquals(t1, t2 Type) bool { + return (t1 == nil && t2 == nil) || (t1 != nil && t2 != nil && t1.Equals(t2)) +} diff --git a/core/checker/expressions.go b/core/checker/expressions.go index 618a50f..1c47453 100644 --- a/core/checker/expressions.go +++ b/core/checker/expressions.go @@ -712,7 +712,7 @@ func (c *checker) VisitCall(expr *ast.Call) { func (c *checker) VisitIndex(expr *ast.Index) { expr.AcceptChildren(c) - if expr.Value == nil || expr.Value.Result().Kind == ast.InvalidResultKind || expr.Index.Result().Kind == ast.InvalidResultKind { + if expr.Value == nil || expr.Index == nil || expr.Value.Result().Kind == ast.InvalidResultKind || expr.Index.Result().Kind == ast.InvalidResultKind { return // Do not cascade errors } diff --git a/core/cst/node.go b/core/cst/node.go index afce463..7961e19 100644 --- a/core/cst/node.go +++ b/core/cst/node.go @@ -40,6 +40,18 @@ func (n Node) ContainsAny(kinds []scanner.TokenKind) bool { return false } +func (n Node) Get(kind scanner.TokenKind) *Node { + for i := range n.Children { + child := &n.Children[i] + + if child.Token.Kind == kind { + return child + } + } + + return nil +} + const ( UnknownNode NodeKind = iota FileNode diff --git a/core/cst/statements.go b/core/cst/statements.go index e353aed..a65ad17 100644 --- a/core/cst/statements.go +++ b/core/cst/statements.go @@ -61,7 +61,7 @@ func parseBlockStmt(p *parser) Node { if p.consume(scanner.LeftBrace) { return p.end() } - if p.repeat(parseStmt, canStartStmt...) { + if p.repeatSync(parseStmt, scanner.RightBrace, canStartStmt...) { return p.end() } if p.consume(scanner.RightBrace) { diff --git a/core/fuckoff/resolver.go b/core/fuckoff/resolver.go index 6f4dbdf..b909db1 100644 --- a/core/fuckoff/resolver.go +++ b/core/fuckoff/resolver.go @@ -10,4 +10,7 @@ type Resolver interface { GetFunction(name string) (*ast.Func, string) GetMethod(type_ ast.Type, name string, static bool) (*ast.Func, string) + GetMethods(type_ ast.Type, static bool) []*ast.Func + + GetFileNodes() []*ast.File } diff --git a/core/pos.go b/core/pos.go index f3c16a8..f4f01e8 100644 --- a/core/pos.go +++ b/core/pos.go @@ -1,15 +1,23 @@ package core -type Range struct { - Start Pos - End Pos -} - type Pos struct { Line uint16 Column uint16 } +func (p Pos) IsAfter(after Pos) bool { + if p.Line == after.Line { + return p.Column > after.Column + } + + return p.Line > after.Line +} + +type Range struct { + Start Pos + End Pos +} + func (r Range) Valid() bool { return r.End.Line > 0 && r.End.Column > 0 } diff --git a/core/workspace/project.go b/core/workspace/project.go index 6c674e2..f3fc75e 100644 --- a/core/workspace/project.go +++ b/core/workspace/project.go @@ -173,6 +173,39 @@ func (p *Project) GetMethod(type_ ast.Type, name string, static bool) (*ast.Func return nil, "" } +func (p *Project) GetMethods(type_ ast.Type, static bool) []*ast.Func { + var methods []*ast.Func + + staticValue := ast.FuncFlags(0) + if static { + staticValue = 1 + } + + for _, file := range p.Files { + for _, decl := range file.Ast.Decls { + if impl, ok := decl.(*ast.Impl); ok && impl.Type != nil && impl.Type.Equals(type_) { + for _, method := range impl.Methods { + if method.Flags&ast.Static == staticValue { + methods = append(methods, method) + } + } + } + } + } + + return methods +} + +func (p *Project) GetFileNodes() []*ast.File { + nodes := make([]*ast.File, 0, len(p.Files)) + + for _, file := range p.Files { + nodes = append(nodes, file.Ast) + } + + return nodes +} + func (p *Project) GetOrCreateFile(path string) *File { if file, ok := p.Files[path]; ok { return file diff --git a/example/src/main.fb b/example/src/main.fb index b05d0e6..1d98b62 100644 --- a/example/src/main.fb +++ b/example/src/main.fb @@ -27,7 +27,13 @@ func main() { LibC.printf("Align: %d\n", alignof(Foo)); LibC.printf("\n"); - var animal = Animal.Dog; + var animal = Animal.Cat; + + if (true) { + var animal = 5; + + LibC.printf("Number: %d\n\n", animal); + } LibC.printf("Foo.bar: %d\n", Foo.bar); Foo.setBar(5);