Moved AST generation to its own package
This commit is contained in:
parent
f479b5a03a
commit
115f46c12d
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,7 @@ package build
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"git.akyoto.dev/cli/q/src/build/ast"
|
||||||
"git.akyoto.dev/cli/q/src/build/config"
|
"git.akyoto.dev/cli/q/src/build/config"
|
||||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||||
"git.akyoto.dev/cli/q/src/build/expression"
|
"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
|
var temporary cpu.Register
|
||||||
|
|
||||||
if isFunctionCall(value) {
|
if ast.IsFunctionCall(value) {
|
||||||
temporary = f.cpu.Return[0]
|
temporary = f.cpu.Return[0]
|
||||||
} else {
|
} else {
|
||||||
found := false
|
found := false
|
||||||
|
@ -2,6 +2,7 @@ package build
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.akyoto.dev/cli/q/src/build/asm"
|
"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/config"
|
||||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||||
"git.akyoto.dev/cli/q/src/build/expression"
|
"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)
|
return f.TokenToRegister(operation, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isFunctionCall(root) {
|
if ast.IsFunctionCall(root) {
|
||||||
err := f.CompileFunctionCall(root)
|
err := f.CompileFunctionCall(root)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -5,9 +5,9 @@ import (
|
|||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/ast"
|
"git.akyoto.dev/cli/q/src/build/ast"
|
||||||
"git.akyoto.dev/cli/q/src/build/config"
|
"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/fs"
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Function represents a function.
|
// Function represents a function.
|
||||||
@ -28,9 +28,10 @@ func (f *Function) Compile() {
|
|||||||
|
|
||||||
// CompileTokens compiles a token list.
|
// CompileTokens compiles a token list.
|
||||||
func (f *Function) CompileTokens(tokens token.List) error {
|
func (f *Function) CompileTokens(tokens token.List) error {
|
||||||
tree, err := f.toAST(tokens)
|
tree, err := ast.Parse(tokens)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
err.(*errors.Error).File = f.File
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,36 +96,3 @@ func (f *Function) String() string {
|
|||||||
func (f *Function) Wait() {
|
func (f *Function) Wait() {
|
||||||
<-f.finished
|
<-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() == ":="
|
|
||||||
}
|
|
||||||
|
@ -1,48 +1 @@
|
|||||||
package build
|
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
|
|
||||||
}
|
|
||||||
|
@ -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 {
|
func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error {
|
||||||
reg, exists := f.cpu.FindFree(f.cpu.General)
|
reg, exists := f.cpu.FindFree(f.cpu.General)
|
||||||
|
|
||||||
|
46
src/build/ast/EachInstruction.go
Normal file
46
src/build/ast/EachInstruction.go
Normal file
@ -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
|
||||||
|
}
|
101
src/build/ast/Parse.go
Normal file
101
src/build/ast/Parse.go
Normal file
@ -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() == ":="
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user