Implemented an abstract syntax tree
This commit is contained in:
parent
27c707b6ff
commit
f479b5a03a
87
src/build/AST.go
Normal file
87
src/build/AST.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package build
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"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/expression"
|
"git.akyoto.dev/cli/q/src/build/expression"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,3 +46,23 @@ func (f *Function) CompileSyscall(expr *expression.Expression) error {
|
|||||||
f.sideEffects++
|
f.sideEffects++
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExpressionsToRegisters moves multiple expressions into the specified registers.
|
||||||
|
func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error {
|
||||||
|
for _, register := range registers {
|
||||||
|
f.SaveRegister(register)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(expressions) - 1; i >= 0; i-- {
|
||||||
|
expression := expressions[i]
|
||||||
|
register := registers[i]
|
||||||
|
|
||||||
|
err := f.ExpressionToRegister(expression, register)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -3,7 +3,6 @@ package build
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/asm"
|
|
||||||
"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"
|
||||||
@ -72,110 +71,3 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope
|
|||||||
|
|
||||||
return errors.New(errors.NotImplemented, f.File, operation.Position)
|
return errors.New(errors.NotImplemented, f.File, operation.Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpressionToRegister moves the result of an expression into the given register.
|
|
||||||
func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error {
|
|
||||||
if config.Verbose {
|
|
||||||
f.Logf("%s to register %s", root, register)
|
|
||||||
}
|
|
||||||
|
|
||||||
operation := root.Token
|
|
||||||
|
|
||||||
if root.IsLeaf() {
|
|
||||||
return f.TokenToRegister(operation, register)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isFunctionCall(root) {
|
|
||||||
err := f.CompileFunctionCall(root)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if register != f.cpu.Return[0] {
|
|
||||||
f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Return[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
left := root.Children[0]
|
|
||||||
right := root.Children[1]
|
|
||||||
|
|
||||||
err := f.ExpressionToRegister(left, register)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.SaveRegister(register)
|
|
||||||
return f.Execute(operation, register, right)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpressionsToRegisters moves multiple expressions into the specified registers.
|
|
||||||
func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error {
|
|
||||||
for _, register := range registers {
|
|
||||||
f.SaveRegister(register)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := len(expressions) - 1; i >= 0; i-- {
|
|
||||||
expression := expressions[i]
|
|
||||||
register := registers[i]
|
|
||||||
|
|
||||||
err := f.ExpressionToRegister(expression, register)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenToRegister moves a token into a register.
|
|
||||||
// It only works with identifiers, numbers and strings.
|
|
||||||
func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
|
|
||||||
switch t.Kind {
|
|
||||||
case token.Identifier:
|
|
||||||
name := t.Text()
|
|
||||||
constant, exists := f.definitions[name]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
if config.Verbose {
|
|
||||||
f.Logf("constant %s = %s", constant.Name, constant.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.ExpressionToRegister(constant.Value, register)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable, exists := f.variables[name]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
if register != variable.Register {
|
|
||||||
f.assembler.RegisterRegister(asm.MOVE, register, variable.Register)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.useVariable(variable)
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case token.Number:
|
|
||||||
value := t.Text()
|
|
||||||
n, err := strconv.Atoi(value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.assembler.RegisterNumber(asm.MOVE, register, n)
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case token.String:
|
|
||||||
return errors.New(errors.NotImplemented, f.File, t.Position)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errors.New(errors.InvalidExpression, f.File, t.Position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
47
src/build/ExpressionToRegister.go
Normal file
47
src/build/ExpressionToRegister.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.akyoto.dev/cli/q/src/build/asm"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/config"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/expression"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExpressionToRegister moves the result of an expression into the given register.
|
||||||
|
func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error {
|
||||||
|
if config.Verbose {
|
||||||
|
f.Logf("%s to register %s", root, register)
|
||||||
|
}
|
||||||
|
|
||||||
|
operation := root.Token
|
||||||
|
|
||||||
|
if root.IsLeaf() {
|
||||||
|
return f.TokenToRegister(operation, register)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFunctionCall(root) {
|
||||||
|
err := f.CompileFunctionCall(root)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if register != f.cpu.Return[0] {
|
||||||
|
f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Return[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
left := root.Children[0]
|
||||||
|
right := root.Children[1]
|
||||||
|
|
||||||
|
err := f.ExpressionToRegister(left, register)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.SaveRegister(register)
|
||||||
|
return f.Execute(operation, register, right)
|
||||||
|
}
|
@ -3,6 +3,7 @@ package build
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"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/expression"
|
||||||
"git.akyoto.dev/cli/q/src/build/fs"
|
"git.akyoto.dev/cli/q/src/build/fs"
|
||||||
@ -11,8 +12,8 @@ import (
|
|||||||
|
|
||||||
// Function represents a function.
|
// Function represents a function.
|
||||||
type Function struct {
|
type Function struct {
|
||||||
Name string
|
|
||||||
File *fs.File
|
File *fs.File
|
||||||
|
Name string
|
||||||
Body token.List
|
Body token.List
|
||||||
compiler
|
compiler
|
||||||
}
|
}
|
||||||
@ -26,61 +27,60 @@ func (f *Function) Compile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CompileTokens compiles a token list.
|
// CompileTokens compiles a token list.
|
||||||
func (f *Function) CompileTokens(body token.List) error {
|
func (f *Function) CompileTokens(tokens token.List) error {
|
||||||
start := 0
|
tree, err := f.toAST(tokens)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
instruction := body[start:i]
|
|
||||||
|
|
||||||
if config.Verbose {
|
|
||||||
f.Logf("compiling: %s", instruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Assembler {
|
|
||||||
f.debug = append(f.debug, debug{
|
|
||||||
position: len(f.assembler.Instructions),
|
|
||||||
source: instruction,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
err := f.CompileInstruction(instruction)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
start = i + 1
|
return f.CompileAST(tree)
|
||||||
|
}
|
||||||
|
|
||||||
case token.GroupStart:
|
// CompileAST compiles an abstract syntax tree.
|
||||||
groupLevel++
|
func (f *Function) CompileAST(tree ast.AST) error {
|
||||||
|
for _, node := range tree {
|
||||||
|
if config.Verbose {
|
||||||
|
f.Logf("%T %s", node, node)
|
||||||
|
}
|
||||||
|
|
||||||
case token.GroupEnd:
|
if config.Assembler {
|
||||||
groupLevel--
|
f.debug = append(f.debug, debug{
|
||||||
|
position: len(f.assembler.Instructions),
|
||||||
|
source: node,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
case token.BlockStart:
|
err := f.CompileNode(node)
|
||||||
blockLevel++
|
|
||||||
|
|
||||||
case token.BlockEnd:
|
if err != nil {
|
||||||
blockLevel--
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompileNode compiles a node in the AST.
|
||||||
|
func (f *Function) CompileNode(node ast.Node) error {
|
||||||
|
switch node := node.(type) {
|
||||||
|
case *ast.Call:
|
||||||
|
return f.CompileFunctionCall(node.Expression)
|
||||||
|
|
||||||
|
case *ast.Define:
|
||||||
|
return f.CompileVariableDefinition(node)
|
||||||
|
|
||||||
|
case *ast.Return:
|
||||||
|
return f.CompileReturn(node)
|
||||||
|
|
||||||
|
case *ast.Loop:
|
||||||
|
return f.CompileLoop(node)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("Unknown AST type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Logf formats a message for verbose output.
|
// Logf formats a message for verbose output.
|
||||||
func (f *Function) Logf(format string, data ...any) {
|
func (f *Function) Logf(format string, data ...any) {
|
||||||
fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...))
|
fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...))
|
||||||
|
@ -1,36 +1,48 @@
|
|||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.akyoto.dev/cli/q/src/build/expression"
|
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
"git.akyoto.dev/cli/q/src/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompileInstruction compiles a single instruction.
|
// EachInstruction calls the function on each instruction.
|
||||||
func (f *Function) CompileInstruction(instruction token.List) error {
|
func EachInstruction(body token.List, call func(token.List) error) error {
|
||||||
if instruction[0].Kind == token.Keyword {
|
start := 0
|
||||||
return f.CompileKeyword(instruction)
|
groupLevel := 0
|
||||||
|
blockLevel := 0
|
||||||
|
|
||||||
|
for i, t := range body {
|
||||||
|
if start == i && t.Kind == token.NewLine {
|
||||||
|
start = i + 1
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
expr := expression.Parse(instruction)
|
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--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if expr == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
defer expr.Close()
|
|
||||||
|
|
||||||
switch true {
|
|
||||||
case isVariableDefinition(expr):
|
|
||||||
return f.CompileVariableDefinition(expr)
|
|
||||||
|
|
||||||
case isAssignment(expr):
|
|
||||||
return f.CompileAssignment(expr)
|
|
||||||
|
|
||||||
case isFunctionCall(expr):
|
|
||||||
return f.CompileFunctionCall(expr)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
|
||||||
"git.akyoto.dev/cli/q/src/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CompileKeyword compiles an instruction that starts with a keyword.
|
|
||||||
func (f *Function) CompileKeyword(tokens token.List) error {
|
|
||||||
switch tokens[0].Text() {
|
|
||||||
case "return":
|
|
||||||
return f.CompileReturn(tokens)
|
|
||||||
|
|
||||||
case "loop":
|
|
||||||
return f.CompileLoop(tokens)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position)
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,26 +3,14 @@ package build
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"git.akyoto.dev/cli/q/src/build/ast"
|
||||||
"git.akyoto.dev/cli/q/src/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompileLoop compiles a loop instruction.
|
// CompileLoop compiles a loop instruction.
|
||||||
func (f *Function) CompileLoop(tokens token.List) error {
|
func (f *Function) CompileLoop(loop *ast.Loop) error {
|
||||||
blockStart := tokens.IndexKind(token.BlockStart) + 1
|
label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
|
||||||
blockEnd := tokens.LastIndexKind(token.BlockEnd)
|
f.assembler.Label(label)
|
||||||
|
defer f.assembler.Jump(label)
|
||||||
if blockStart == -1 {
|
|
||||||
return errors.New(errors.MissingBlockStart, f.File, tokens[0].End())
|
|
||||||
}
|
|
||||||
|
|
||||||
if blockEnd == -1 {
|
|
||||||
return errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End())
|
|
||||||
}
|
|
||||||
|
|
||||||
loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
|
|
||||||
f.assembler.Label(loop)
|
|
||||||
defer f.assembler.Jump(loop)
|
|
||||||
f.count.loop++
|
f.count.loop++
|
||||||
return f.CompileTokens(tokens[blockStart:blockEnd])
|
return f.CompileAST(loop.Body)
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,16 @@
|
|||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.akyoto.dev/cli/q/src/build/expression"
|
"git.akyoto.dev/cli/q/src/build/ast"
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompileReturn compiles a return instruction.
|
// CompileReturn compiles a return instruction.
|
||||||
func (f *Function) CompileReturn(tokens token.List) error {
|
func (f *Function) CompileReturn(node *ast.Return) error {
|
||||||
defer f.assembler.Return()
|
defer f.assembler.Return()
|
||||||
|
|
||||||
if len(tokens) == 1 {
|
if node.Value == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
value := expression.Parse(tokens[1:])
|
return f.ExpressionToRegister(node.Value, f.cpu.Return[0])
|
||||||
|
|
||||||
if value == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer value.Close()
|
|
||||||
return f.ExpressionToRegister(value, f.cpu.Return[0])
|
|
||||||
}
|
}
|
||||||
|
59
src/build/TokenToRegister.go
Normal file
59
src/build/TokenToRegister.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.akyoto.dev/cli/q/src/build/asm"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/config"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TokenToRegister moves a token into a register.
|
||||||
|
// It only works with identifiers, numbers and strings.
|
||||||
|
func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
|
||||||
|
switch t.Kind {
|
||||||
|
case token.Identifier:
|
||||||
|
name := t.Text()
|
||||||
|
constant, exists := f.definitions[name]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
if config.Verbose {
|
||||||
|
f.Logf("constant %s = %s", constant.Name, constant.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.ExpressionToRegister(constant.Value, register)
|
||||||
|
}
|
||||||
|
|
||||||
|
variable, exists := f.variables[name]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if register != variable.Register {
|
||||||
|
f.assembler.RegisterRegister(asm.MOVE, register, variable.Register)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.useVariable(variable)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case token.Number:
|
||||||
|
value := t.Text()
|
||||||
|
n, err := strconv.Atoi(value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.assembler.RegisterNumber(asm.MOVE, register, n)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case token.String:
|
||||||
|
return errors.New(errors.NotImplemented, f.File, t.Position)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.New(errors.InvalidExpression, f.File, t.Position)
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,6 @@ type Variable struct {
|
|||||||
|
|
||||||
// Definitions are single use expressions that don't reside in a register yet.
|
// Definitions are single use expressions that don't reside in a register yet.
|
||||||
type Definition struct {
|
type Definition struct {
|
||||||
Name string
|
|
||||||
Value *expression.Expression
|
Value *expression.Expression
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"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/expression"
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
@ -8,24 +9,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CompileVariableDefinition compiles a variable definition.
|
// CompileVariableDefinition compiles a variable definition.
|
||||||
func (f *Function) CompileVariableDefinition(expr *expression.Expression) error {
|
func (f *Function) CompileVariableDefinition(node *ast.Define) error {
|
||||||
if len(expr.Children) < 2 {
|
name := node.Name.Text()
|
||||||
return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End())
|
|
||||||
}
|
|
||||||
|
|
||||||
name := expr.Children[0].Token.Text()
|
|
||||||
|
|
||||||
if f.identifierExists(name) {
|
if f.identifierExists(name) {
|
||||||
return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position)
|
return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
uses := countIdentifier(f.Body, name) - 1
|
uses := countIdentifier(f.Body, name) - 1
|
||||||
|
|
||||||
if uses == 0 {
|
if uses == 0 {
|
||||||
return errors.New(&errors.UnusedVariable{Name: name}, f.File, expr.Children[0].Token.Position)
|
return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := expr.Children[1]
|
value := node.Value
|
||||||
|
|
||||||
err := value.EachLeaf(func(leaf *expression.Expression) error {
|
err := value.EachLeaf(func(leaf *expression.Expression) error {
|
||||||
if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) {
|
if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) {
|
||||||
@ -40,8 +37,6 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
if uses == 1 {
|
if uses == 1 {
|
||||||
expr.RemoveChild(value)
|
|
||||||
|
|
||||||
f.definitions[name] = &Definition{
|
f.definitions[name] = &Definition{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: value,
|
Value: value,
|
||||||
|
@ -4,6 +4,6 @@ import "fmt"
|
|||||||
|
|
||||||
// Instruction represents a single instruction which can be converted to machine code.
|
// Instruction represents a single instruction which can be converted to machine code.
|
||||||
type Instruction struct {
|
type Instruction struct {
|
||||||
Mnemonic Mnemonic
|
|
||||||
Data fmt.Stringer
|
Data fmt.Stringer
|
||||||
|
Mnemonic Mnemonic
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ type Address = uint32
|
|||||||
// Position: The machine code offset where the address was inserted.
|
// Position: The machine code offset where the address was inserted.
|
||||||
// Resolve: The function that will return the final address.
|
// Resolve: The function that will return the final address.
|
||||||
type Pointer struct {
|
type Pointer struct {
|
||||||
|
Resolve func() Address
|
||||||
Position Address
|
Position Address
|
||||||
Size uint8
|
Size uint8
|
||||||
Resolve func() Address
|
|
||||||
}
|
}
|
||||||
|
36
src/build/ast/AST.go
Normal file
36
src/build/ast/AST.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.akyoto.dev/cli/q/src/build/expression"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Node interface{}
|
||||||
|
type AST []Node
|
||||||
|
|
||||||
|
type Assign struct {
|
||||||
|
Value *expression.Expression
|
||||||
|
Name token.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type Call struct {
|
||||||
|
Expression *expression.Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
type Define struct {
|
||||||
|
Value *expression.Expression
|
||||||
|
Name token.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type If struct {
|
||||||
|
Condition *expression.Expression
|
||||||
|
Body AST
|
||||||
|
}
|
||||||
|
|
||||||
|
type Loop struct {
|
||||||
|
Body AST
|
||||||
|
}
|
||||||
|
|
||||||
|
type Return struct {
|
||||||
|
Value *expression.Expression
|
||||||
|
}
|
@ -4,23 +4,23 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"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/cpu"
|
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
|
||||||
"git.akyoto.dev/go/color/ansi"
|
"git.akyoto.dev/go/color/ansi"
|
||||||
)
|
)
|
||||||
|
|
||||||
// compiler is the data structure we embed in each function to preserve compilation state.
|
// compiler is the data structure we embed in each function to preserve compilation state.
|
||||||
type compiler struct {
|
type compiler struct {
|
||||||
assembler asm.Assembler
|
|
||||||
count counter
|
|
||||||
cpu cpu.CPU
|
|
||||||
debug []debug
|
|
||||||
err error
|
err error
|
||||||
definitions map[string]*Definition
|
definitions map[string]*Definition
|
||||||
variables map[string]*Variable
|
variables map[string]*Variable
|
||||||
functions map[string]*Function
|
functions map[string]*Function
|
||||||
sideEffects int
|
|
||||||
finished chan struct{}
|
finished chan struct{}
|
||||||
|
assembler asm.Assembler
|
||||||
|
debug []debug
|
||||||
|
cpu cpu.CPU
|
||||||
|
count counter
|
||||||
|
sideEffects int
|
||||||
}
|
}
|
||||||
|
|
||||||
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
||||||
@ -30,8 +30,8 @@ type counter struct {
|
|||||||
|
|
||||||
// debug is used to look up the source code at the given position.
|
// debug is used to look up the source code at the given position.
|
||||||
type debug struct {
|
type debug struct {
|
||||||
|
source ast.Node
|
||||||
position int
|
position int
|
||||||
source token.List
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintInstructions shows the assembly instructions.
|
// PrintInstructions shows the assembly instructions.
|
||||||
@ -66,7 +66,7 @@ func (c *compiler) PrintInstructions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sourceAt retrieves the source code at the given position or `nil`.
|
// sourceAt retrieves the source code at the given position or `nil`.
|
||||||
func (c *compiler) sourceAt(position int) token.List {
|
func (c *compiler) sourceAt(position int) ast.Node {
|
||||||
for _, record := range c.debug {
|
for _, record := range c.debug {
|
||||||
if record.position == position {
|
if record.position == position {
|
||||||
return record.source
|
return record.source
|
||||||
|
@ -8,22 +8,22 @@ import (
|
|||||||
|
|
||||||
// Expression is a binary tree with an operator on each node.
|
// Expression is a binary tree with an operator on each node.
|
||||||
type Expression struct {
|
type Expression struct {
|
||||||
Token token.Token
|
|
||||||
Parent *Expression
|
Parent *Expression
|
||||||
Children []*Expression
|
Children []*Expression
|
||||||
Precedence int
|
Token token.Token
|
||||||
|
Precedence int8
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new expression.
|
// New creates a new expression.
|
||||||
func New() *Expression {
|
func New() *Expression {
|
||||||
return pool.Get().(*Expression)
|
return &Expression{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLeaf creates a new leaf node.
|
// NewLeaf creates a new leaf node.
|
||||||
func NewLeaf(t token.Token) *Expression {
|
func NewLeaf(t token.Token) *Expression {
|
||||||
expr := New()
|
return &Expression{
|
||||||
expr.Token = t
|
Token: t,
|
||||||
return expr
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddChild adds a child to the expression.
|
// AddChild adds a child to the expression.
|
||||||
@ -32,17 +32,16 @@ func (expr *Expression) AddChild(child *Expression) {
|
|||||||
child.Parent = expr
|
child.Parent = expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close puts the expression back into the memory pool.
|
// Reset resets all values to the default.
|
||||||
func (expr *Expression) Close() {
|
func (expr *Expression) Reset() {
|
||||||
for _, child := range expr.Children {
|
for _, child := range expr.Children {
|
||||||
child.Close()
|
child.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
expr.Token.Reset()
|
expr.Token.Reset()
|
||||||
expr.Parent = nil
|
expr.Parent = nil
|
||||||
expr.Children = expr.Children[:0]
|
expr.Children = expr.Children[:0]
|
||||||
expr.Precedence = 0
|
expr.Precedence = 0
|
||||||
pool.Put(expr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EachLeaf iterates through all leaves in the tree.
|
// EachLeaf iterates through all leaves in the tree.
|
||||||
|
@ -88,7 +88,7 @@ func TestExpressionParse(t *testing.T) {
|
|||||||
src := []byte(test.Expression)
|
src := []byte(test.Expression)
|
||||||
tokens := token.Tokenize(src)
|
tokens := token.Tokenize(src)
|
||||||
expr := expression.Parse(tokens)
|
expr := expression.Parse(tokens)
|
||||||
defer expr.Close()
|
defer expr.Reset()
|
||||||
|
|
||||||
assert.NotNil(t, expr)
|
assert.NotNil(t, expr)
|
||||||
assert.Equal(t, expr.String(), test.Result)
|
assert.Equal(t, expr.String(), test.Result)
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
// Operator represents an operator for mathematical expressions.
|
// Operator represents an operator for mathematical expressions.
|
||||||
type Operator struct {
|
type Operator struct {
|
||||||
Symbol string
|
Symbol string
|
||||||
Precedence int
|
Precedence int8
|
||||||
Operands int
|
Operands int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,14 +39,14 @@ var Operators = map[string]*Operator{
|
|||||||
"&&": {"&&", 2, 2},
|
"&&": {"&&", 2, 2},
|
||||||
"||": {"||", 1, 2},
|
"||": {"||", 1, 2},
|
||||||
|
|
||||||
"=": {"=", math.MinInt, 2},
|
"=": {"=", math.MinInt8, 2},
|
||||||
":=": {":=", math.MinInt, 2},
|
":=": {":=", math.MinInt8, 2},
|
||||||
"+=": {"+=", math.MinInt, 2},
|
"+=": {"+=", math.MinInt8, 2},
|
||||||
"-=": {"-=", math.MinInt, 2},
|
"-=": {"-=", math.MinInt8, 2},
|
||||||
"*=": {"*=", math.MinInt, 2},
|
"*=": {"*=", math.MinInt8, 2},
|
||||||
"/=": {"/=", math.MinInt, 2},
|
"/=": {"/=", math.MinInt8, 2},
|
||||||
">>=": {">>=", math.MinInt, 2},
|
">>=": {">>=", math.MinInt8, 2},
|
||||||
"<<=": {"<<=", math.MinInt, 2},
|
"<<=": {"<<=", math.MinInt8, 2},
|
||||||
}
|
}
|
||||||
|
|
||||||
func isComplete(expr *Expression) bool {
|
func isComplete(expr *Expression) bool {
|
||||||
@ -75,7 +75,7 @@ func numOperands(symbol string) int {
|
|||||||
return operator.Operands
|
return operator.Operands
|
||||||
}
|
}
|
||||||
|
|
||||||
func precedence(symbol string) int {
|
func precedence(symbol string) int8 {
|
||||||
operator, exists := Operators[symbol]
|
operator, exists := Operators[symbol]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
|
@ -70,7 +70,7 @@ func Parse(tokens token.List) *Expression {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
group.Precedence = math.MaxInt
|
group.Precedence = math.MaxInt8
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = group
|
cursor = group
|
||||||
|
@ -12,7 +12,6 @@ func BenchmarkExpression(b *testing.B) {
|
|||||||
tokens := token.Tokenize(src)
|
tokens := token.Tokenize(src)
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
expr := expression.Parse(tokens)
|
expression.Parse(tokens)
|
||||||
expr.Close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package expression
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
var pool = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return &Expression{}
|
|
||||||
},
|
|
||||||
}
|
|
@ -4,6 +4,6 @@ import "git.akyoto.dev/cli/q/src/build/token"
|
|||||||
|
|
||||||
// File represents a single source file.
|
// File represents a single source file.
|
||||||
type File struct {
|
type File struct {
|
||||||
Tokens token.List
|
|
||||||
Path string
|
Path string
|
||||||
|
Tokens token.List
|
||||||
}
|
}
|
||||||
|
12
src/build/keyword/Keyword.go
Normal file
12
src/build/keyword/Keyword.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package keyword
|
||||||
|
|
||||||
|
const (
|
||||||
|
Loop = "loop"
|
||||||
|
Return = "return"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map is a map of all keywords used in the language.
|
||||||
|
var Map = map[string][]byte{
|
||||||
|
Loop: []byte(Loop),
|
||||||
|
Return: []byte(Return),
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package token
|
|
||||||
|
|
||||||
// Keywords defines the keywords used in the language.
|
|
||||||
var Keywords = map[string]bool{
|
|
||||||
"return": true,
|
|
||||||
"loop": true,
|
|
||||||
}
|
|
@ -6,9 +6,9 @@ import "fmt"
|
|||||||
// The characters that make up an identifier are grouped into a single token.
|
// The characters that make up an identifier are grouped into a single token.
|
||||||
// This makes parsing easier and allows us to do better syntax checks.
|
// This makes parsing easier and allows us to do better syntax checks.
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Kind Kind
|
|
||||||
Position int
|
|
||||||
Bytes []byte
|
Bytes []byte
|
||||||
|
Position int
|
||||||
|
Kind Kind
|
||||||
}
|
}
|
||||||
|
|
||||||
// End returns the position after the token.
|
// End returns the position after the token.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package token
|
package token
|
||||||
|
|
||||||
|
import "git.akyoto.dev/cli/q/src/build/keyword"
|
||||||
|
|
||||||
// Pre-allocate these byte buffers so we can re-use them
|
// Pre-allocate these byte buffers so we can re-use them
|
||||||
// instead of allocating a new buffer every time.
|
// instead of allocating a new buffer every time.
|
||||||
var (
|
var (
|
||||||
@ -26,35 +28,35 @@ func Tokenize(buffer []byte) List {
|
|||||||
case ' ', '\t':
|
case ' ', '\t':
|
||||||
// Separator
|
// Separator
|
||||||
case ',':
|
case ',':
|
||||||
tokens = append(tokens, Token{Separator, i, separatorBytes})
|
tokens = append(tokens, Token{Kind: Separator, Position: i, Bytes: separatorBytes})
|
||||||
|
|
||||||
// Parentheses start
|
// Parentheses start
|
||||||
case '(':
|
case '(':
|
||||||
tokens = append(tokens, Token{GroupStart, i, groupStartBytes})
|
tokens = append(tokens, Token{Kind: GroupStart, Position: i, Bytes: groupStartBytes})
|
||||||
|
|
||||||
// Parentheses end
|
// Parentheses end
|
||||||
case ')':
|
case ')':
|
||||||
tokens = append(tokens, Token{GroupEnd, i, groupEndBytes})
|
tokens = append(tokens, Token{Kind: GroupEnd, Position: i, Bytes: groupEndBytes})
|
||||||
|
|
||||||
// Block start
|
// Block start
|
||||||
case '{':
|
case '{':
|
||||||
tokens = append(tokens, Token{BlockStart, i, blockStartBytes})
|
tokens = append(tokens, Token{Kind: BlockStart, Position: i, Bytes: blockStartBytes})
|
||||||
|
|
||||||
// Block end
|
// Block end
|
||||||
case '}':
|
case '}':
|
||||||
tokens = append(tokens, Token{BlockEnd, i, blockEndBytes})
|
tokens = append(tokens, Token{Kind: BlockEnd, Position: i, Bytes: blockEndBytes})
|
||||||
|
|
||||||
// Array start
|
// Array start
|
||||||
case '[':
|
case '[':
|
||||||
tokens = append(tokens, Token{ArrayStart, i, arrayStartBytes})
|
tokens = append(tokens, Token{Kind: ArrayStart, Position: i, Bytes: arrayStartBytes})
|
||||||
|
|
||||||
// Array end
|
// Array end
|
||||||
case ']':
|
case ']':
|
||||||
tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes})
|
tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Bytes: arrayEndBytes})
|
||||||
|
|
||||||
// New line
|
// New line
|
||||||
case '\n':
|
case '\n':
|
||||||
tokens = append(tokens, Token{NewLine, i, newLineBytes})
|
tokens = append(tokens, Token{Kind: NewLine, Position: i, Bytes: newLineBytes})
|
||||||
|
|
||||||
// Comment
|
// Comment
|
||||||
case '/':
|
case '/':
|
||||||
@ -66,7 +68,7 @@ func Tokenize(buffer []byte) List {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens = append(tokens, Token{Operator, position, buffer[position:i]})
|
tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +78,7 @@ func Tokenize(buffer []byte) List {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens = append(tokens, Token{Comment, position, buffer[position:i]})
|
tokens = append(tokens, Token{Kind: Comment, Position: position, Bytes: buffer[position:i]})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
// String
|
// String
|
||||||
@ -95,7 +97,7 @@ func Tokenize(buffer []byte) List {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens = append(tokens, Token{String, start, buffer[start:end]})
|
tokens = append(tokens, Token{Kind: String, Position: start, Bytes: buffer[start:end]})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -108,13 +110,15 @@ func Tokenize(buffer []byte) List {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
token := Token{Identifier, position, buffer[position:i]}
|
identifier := buffer[position:i]
|
||||||
|
keyword, isKeyword := keyword.Map[string(identifier)]
|
||||||
|
|
||||||
if Keywords[string(token.Bytes)] {
|
if isKeyword {
|
||||||
token.Kind = Keyword
|
tokens = append(tokens, Token{Kind: Keyword, Position: position, Bytes: keyword})
|
||||||
|
} else {
|
||||||
|
tokens = append(tokens, Token{Kind: Identifier, Position: position, Bytes: identifier})
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens = append(tokens, token)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +131,7 @@ func Tokenize(buffer []byte) List {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens = append(tokens, Token{Number, position, buffer[position:i]})
|
tokens = append(tokens, Token{Kind: Number, Position: position, Bytes: buffer[position:i]})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,18 +144,18 @@ func Tokenize(buffer []byte) List {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens = append(tokens, Token{Operator, position, buffer[position:i]})
|
tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid character
|
// Invalid character
|
||||||
tokens = append(tokens, Token{Invalid, i, buffer[i : i+1]})
|
tokens = append(tokens, Token{Kind: Invalid, Position: i, Bytes: buffer[i : i+1]})
|
||||||
}
|
}
|
||||||
|
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens = append(tokens, Token{EOF, i, nil})
|
tokens = append(tokens, Token{Kind: EOF, Position: i, Bytes: nil})
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ import (
|
|||||||
type Error struct {
|
type Error struct {
|
||||||
Err error
|
Err error
|
||||||
File *fs.File
|
File *fs.File
|
||||||
Position int
|
|
||||||
Stack string
|
Stack string
|
||||||
|
Position int
|
||||||
}
|
}
|
||||||
|
|
||||||
// New generates an error message at the current token position.
|
// New generates an error message at the current token position.
|
||||||
|
Loading…
Reference in New Issue
Block a user