diff --git a/src/build/AST.go b/src/build/AST.go deleted file mode 100644 index b12cc5e..0000000 --- a/src/build/AST.go +++ /dev/null @@ -1,87 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/keyword" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" -) - -// toAST generates an AST from a list of tokens. -func (f *Function) toAST(tokens token.List) (ast.AST, error) { - tree := make(ast.AST, 0, len(tokens)/64) - - err := EachInstruction(tokens, func(instruction token.List) error { - node, err := f.toASTNode(instruction) - - if err == nil && node != nil { - tree = append(tree, node) - } - - return err - }) - - return tree, err -} - -// toASTNode generates an AST node from an instruction. -func (f *Function) toASTNode(tokens token.List) (ast.Node, error) { - if tokens[0].Kind == token.Keyword { - switch tokens[0].Text() { - case keyword.Return: - value := expression.Parse(tokens[1:]) - return &ast.Return{Value: value}, nil - - case keyword.Loop: - blockStart := tokens.IndexKind(token.BlockStart) + 1 - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, f.File, tokens[0].End()) - } - - if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End()) - } - - tree, err := f.toAST(tokens[blockStart:blockEnd]) - return &ast.Loop{Body: tree}, err - - default: - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position) - } - } - - expr := expression.Parse(tokens) - - if expr == nil { - return nil, nil - } - - switch { - case isVariableDefinition(expr): - if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token - value := expr.Children[1] - return &ast.Define{Name: name, Value: value}, nil - - case isAssignment(expr): - if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token - value := expr.Children[1] - return &ast.Assign{Name: name, Value: value}, nil - - case isFunctionCall(expr): - return &ast.Call{Expression: expr}, nil - - default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) - } -} diff --git a/src/build/Execute.go b/src/build/Execute.go index 6a96ce8..e85cd1e 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -3,6 +3,7 @@ package build import ( "strconv" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -22,7 +23,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * var temporary cpu.Register - if isFunctionCall(value) { + if ast.IsFunctionCall(value) { temporary = f.cpu.Return[0] } else { found := false diff --git a/src/build/ExpressionToRegister.go b/src/build/ExpressionToRegister.go index 9a6f09d..cf0a04c 100644 --- a/src/build/ExpressionToRegister.go +++ b/src/build/ExpressionToRegister.go @@ -2,6 +2,7 @@ package build import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -19,7 +20,7 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return f.TokenToRegister(operation, register) } - if isFunctionCall(root) { + if ast.IsFunctionCall(root) { err := f.CompileFunctionCall(root) if err != nil { diff --git a/src/build/Function.go b/src/build/Function.go index 39bf0c3..de87e2f 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -5,9 +5,9 @@ import ( "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" ) // Function represents a function. @@ -28,9 +28,10 @@ func (f *Function) Compile() { // CompileTokens compiles a token list. func (f *Function) CompileTokens(tokens token.List) error { - tree, err := f.toAST(tokens) + tree, err := ast.Parse(tokens) if err != nil { + err.(*errors.Error).File = f.File return err } @@ -95,36 +96,3 @@ func (f *Function) String() string { func (f *Function) Wait() { <-f.finished } - -// identifierExists returns true if the identifier has been defined. -func (f *Function) identifierExists(name string) bool { - _, exists := f.variables[name] - - if exists { - return true - } - - _, exists = f.definitions[name] - - if exists { - return true - } - - _, exists = f.functions[name] - return exists -} - -// isAssignment returns true if the expression is an assignment. -func isAssignment(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' -} - -// isFunctionCall returns true if the expression is a function call. -func isFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" -} - -// isVariableDefinition returns true if the expression is a variable definition. -func isVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" -} diff --git a/src/build/Instruction.go b/src/build/Instruction.go index 944ab92..5c93a93 100644 --- a/src/build/Instruction.go +++ b/src/build/Instruction.go @@ -1,48 +1 @@ package build - -import ( - "git.akyoto.dev/cli/q/src/build/token" -) - -// EachInstruction calls the function on each instruction. -func EachInstruction(body token.List, call func(token.List) error) error { - start := 0 - groupLevel := 0 - blockLevel := 0 - - for i, t := range body { - if start == i && t.Kind == token.NewLine { - start = i + 1 - continue - } - - switch t.Kind { - case token.NewLine: - if groupLevel > 0 || blockLevel > 0 { - continue - } - - err := call(body[start:i]) - - if err != nil { - return err - } - - start = i + 1 - - case token.GroupStart: - groupLevel++ - - case token.GroupEnd: - groupLevel-- - - case token.BlockStart: - blockLevel++ - - case token.BlockEnd: - blockLevel-- - } - } - - return nil -} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 917f12f..f385c24 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -77,6 +77,24 @@ func (f *Function) useVariable(variable *Variable) { } } +// identifierExists returns true if the identifier has been defined. +func (f *Function) identifierExists(name string) bool { + _, exists := f.variables[name] + + if exists { + return true + } + + _, exists = f.definitions[name] + + if exists { + return true + } + + _, exists = f.functions[name] + return exists +} + func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { reg, exists := f.cpu.FindFree(f.cpu.General) diff --git a/src/build/ast/EachInstruction.go b/src/build/ast/EachInstruction.go new file mode 100644 index 0000000..f11bedc --- /dev/null +++ b/src/build/ast/EachInstruction.go @@ -0,0 +1,46 @@ +package ast + +import "git.akyoto.dev/cli/q/src/build/token" + +// EachInstruction calls the function on each instruction. +func EachInstruction(body token.List, call func(token.List) error) error { + start := 0 + groupLevel := 0 + blockLevel := 0 + + for i, t := range body { + if start == i && t.Kind == token.NewLine { + start = i + 1 + continue + } + + switch t.Kind { + case token.NewLine: + if groupLevel > 0 || blockLevel > 0 { + continue + } + + err := call(body[start:i]) + + if err != nil { + return err + } + + start = i + 1 + + case token.GroupStart: + groupLevel++ + + case token.GroupEnd: + groupLevel-- + + case token.BlockStart: + blockLevel++ + + case token.BlockEnd: + blockLevel-- + } + } + + return nil +} diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go new file mode 100644 index 0000000..b977704 --- /dev/null +++ b/src/build/ast/Parse.go @@ -0,0 +1,101 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/keyword" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// Parse generates an AST from a list of tokens. +func Parse(tokens token.List) (AST, error) { + tree := make(AST, 0, len(tokens)/64) + + err := EachInstruction(tokens, func(instruction token.List) error { + node, err := toASTNode(instruction) + + if err == nil && node != nil { + tree = append(tree, node) + } + + return err + }) + + return tree, err +} + +// toASTNode generates an AST node from an instruction. +func toASTNode(tokens token.List) (Node, error) { + if tokens[0].Kind == token.Keyword { + switch tokens[0].Text() { + case keyword.Return: + value := expression.Parse(tokens[1:]) + return &Return{Value: value}, nil + + case keyword.Loop: + blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + } + + tree, err := Parse(tokens[blockStart:blockEnd]) + return &Loop{Body: tree}, err + + default: + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position) + } + } + + expr := expression.Parse(tokens) + + if expr == nil { + return nil, nil + } + + switch { + case IsVariableDefinition(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingAssignValue, nil, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token + value := expr.Children[1] + return &Define{Name: name, Value: value}, nil + + case IsAssignment(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingAssignValue, nil, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token + value := expr.Children[1] + return &Assign{Name: name, Value: value}, nil + + case IsFunctionCall(expr): + return &Call{Expression: expr}, nil + + default: + return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, nil, expr.Token.Position) + } +} + +// IsAssignment returns true if the expression is an assignment. +func IsAssignment(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' +} + +// IsFunctionCall returns true if the expression is a function call. +func IsFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} + +// IsVariableDefinition returns true if the expression is a variable definition. +func IsVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +}