diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 9773db9..910cda0 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -23,7 +23,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return f.Execute(node.Expression.Token, variable.Register, right) } - if left.Token.Kind == token.Period { + if left.Token.Kind == token.Dot { return f.CompileAssignField(node) } diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 076bcc1..b1d117c 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -1,10 +1,12 @@ package core import ( + "fmt" "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/types" ) @@ -23,8 +25,26 @@ func (f *Function) CompileLen(root *expression.Expression) error { return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) } - f.SaveRegister(f.CPU.Output[0]) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: value.Register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) + memory := asm.Memory{ + Offset: -8, + OffsetRegister: math.MaxUint8, + Length: 8, + } + + output := f.CPU.Output[0] + f.SaveRegister(output) + + switch value.Kind { + case eval.Register: + memory.Base = value.Register + case eval.Label: + f.RegisterLabel(asm.MOVE, output, value.Label) + memory.Base = output + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + } + + f.MemoryRegister(asm.LOAD, memory, output) f.FreeRegister(value.Register) return nil } diff --git a/src/core/PeriodToRegister.go b/src/core/DotToRegister.go similarity index 81% rename from src/core/PeriodToRegister.go rename to src/core/DotToRegister.go index aeb7675..d76ac81 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/DotToRegister.go @@ -11,8 +11,8 @@ import ( "git.urbach.dev/cli/q/src/types" ) -// PeriodToRegister moves a constant or a function address into the given register. -func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { +// DotToRegister moves a constant or a function address into the given register. +func (f *Function) DotToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { left := node.Children[0] right := node.Children[1] leftText := left.Token.Text(f.File.Bytes) @@ -56,5 +56,5 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re return types.AnyPointer, nil } - return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) } diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index a2af299..05e5a54 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,139 +1,37 @@ package core import ( - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) -// Evaluate evaluates an expression and returns a register that contains the value of the expression. +// Evaluate evaluates an expression and returns a value. func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { - if expr.IsLeaf() { - if expr.Token.IsNumeric() { - number, err := f.ToNumber(expr.Token) - - if err != nil { - return eval.Value{}, err - } - - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, - Number: number, - Alive: 1, - } - - return value, nil - } - - if expr.Token.Kind == token.Identifier { - name := expr.Token.Text(f.File.Bytes) - variable, function := f.Identifier(name) - - if variable != nil { - f.UseVariable(variable) - - if variable.Alive == 0 { - return variable.Value, nil - } - - tmp := f.NewRegister() - f.RegisterRegister(asm.MOVE, tmp, variable.Register) - - value := eval.Value{ - Kind: eval.Register, - Type: variable.Type, - Register: tmp, - Alive: 1, - } - - return value, nil - } - - if function != nil { - value := eval.Value{ - Kind: eval.Label, - Type: types.AnyPointer, - Label: function.UniqueName, - Alive: 1, - } - - return value, nil - } - - return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) - } - } - - if expr.Token.Kind == token.Call { - types, err := f.CompileCall(expr) - - if err != nil { - return eval.Value{}, err - } - - if len(types) == 0 { - return eval.Value{}, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) - } - + if expr.IsFolded { value := eval.Value{ - Kind: eval.Register, - Type: types[0], - Register: f.CPU.Output[0], - Alive: 1, + Kind: eval.Number, + Type: types.AnyInt, + Number: expr.Value, } return value, nil } - if expr.Token.Kind == token.Period { - left := expr.Children[0] - right := expr.Children[1] - leftText := left.Token.Text(f.File.Bytes) - rightText := right.Token.Text(f.File.Bytes) - variable := f.VariableByName(leftText) + if expr.IsLeaf() { + return f.EvaluateLeaf(expr) + } - if variable != nil { - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + switch expr.Token.Kind { + case token.Call: + return f.EvaluateCall(expr) - value := eval.Value{ - Kind: eval.Memory, - Type: field.Type, - Alive: 1, - Memory: asm.Memory{ - Base: variable.Register, - Offset: int8(field.Offset), - OffsetRegister: math.MaxUint8, - Length: byte(field.Type.Size()), - }, - } + case token.Dot: + return f.EvaluateDot(expr) - return value, nil - } - - constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] - - if isConst { - number, err := ToNumber(constant.Token, constant.File) - - if err != nil { - return eval.Value{}, err - } - - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, - Number: number, - Alive: 1, - } - - return value, nil - } + case token.Array: + return f.EvaluateArray(expr) } tmp := f.NewRegister() @@ -143,7 +41,6 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { Kind: eval.Register, Type: typ, Register: tmp, - Alive: 1, } return value, err diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go new file mode 100644 index 0000000..8457dcd --- /dev/null +++ b/src/core/EvaluateArray.go @@ -0,0 +1,60 @@ +package core + +import ( + "fmt" + "math" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" +) + +// EvaluateArray evaluates a function call. +func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error) { + name := expr.Children[0].Token.Text(f.File.Bytes) + array := f.VariableByName(name) + + if array == nil { + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) + } + + defer f.UseVariable(array) + + memory := asm.Memory{ + Base: array.Register, + Offset: 0, + OffsetRegister: math.MaxUint8, + Length: byte(1), + } + + indexExpr := expr.Children[1] + index, err := f.Evaluate(indexExpr) + + if err != nil { + return eval.Value{}, err + } + + if !types.Is(index.Type, types.AnyInt) { + return eval.Value{}, errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + } + + switch index.Kind { + case eval.Number: + memory.Offset = int8(index.Number) + case eval.Register: + memory.OffsetRegister = index.Register + defer f.FreeRegister(index.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, index.Kind)) + } + + value := eval.Value{ + Kind: eval.Memory, + Type: array.Type.(*types.Array).Of, + Memory: memory, + } + + return value, nil +} diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go new file mode 100644 index 0000000..e5a1eef --- /dev/null +++ b/src/core/EvaluateCall.go @@ -0,0 +1,28 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" +) + +// EvaluateCall evaluates a function call. +func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) { + types, err := f.CompileCall(expr) + + if err != nil { + return eval.Value{}, err + } + + if len(types) == 0 { + return eval.Value{}, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) + } + + value := eval.Value{ + Kind: eval.Register, + Type: types[0], + Register: f.CPU.Output[0], + } + + return value, nil +} diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go new file mode 100644 index 0000000..46b68ec --- /dev/null +++ b/src/core/EvaluateDot.go @@ -0,0 +1,73 @@ +package core + +import ( + "fmt" + "math" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" +) + +// EvaluateDot evaluates an access with the dot operator. +func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) { + left := expr.Children[0] + right := expr.Children[1] + leftText := left.Token.Text(f.File.Bytes) + rightText := right.Token.Text(f.File.Bytes) + variable := f.VariableByName(leftText) + + if variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + + value := eval.Value{ + Kind: eval.Memory, + Type: field.Type, + Memory: asm.Memory{ + Base: variable.Register, + Offset: int8(field.Offset), + OffsetRegister: math.MaxUint8, + Length: byte(field.Type.Size()), + }, + } + + return value, nil + } + + constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + number, err := ToNumber(constant.Token, constant.File) + + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + } + + return value, nil + } + + uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) + function, exists := f.All.Functions[uniqueName] + + if exists { + f.File.Imports[leftText].Used = true + + value := eval.Value{ + Kind: eval.Label, + Type: types.AnyPointer, + Label: function.UniqueName, + } + + return value, nil + } + + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) +} diff --git a/src/core/EvaluateLeaf.go b/src/core/EvaluateLeaf.go new file mode 100644 index 0000000..af9446e --- /dev/null +++ b/src/core/EvaluateLeaf.go @@ -0,0 +1,107 @@ +package core + +import ( + "encoding/binary" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" +) + +// EvaluateLeaf evaluates a leaf expression. +func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) { + switch expr.Token.Kind { + case token.Identifier: + name := expr.Token.Text(f.File.Bytes) + + if name == "true" { + value := eval.Value{ + Kind: eval.Number, + Type: types.Bool, + Number: 1, + } + + return value, nil + } + + if name == "false" { + value := eval.Value{ + Kind: eval.Number, + Type: types.Bool, + Number: 0, + } + + return value, nil + } + + variable, function := f.Identifier(name) + + if variable != nil { + f.UseVariable(variable) + + if variable.Alive == 0 { + return variable.Value, nil + } + + tmp := f.NewRegister() + f.RegisterRegister(asm.MOVE, tmp, variable.Register) + + value := eval.Value{ + Kind: eval.Register, + Type: variable.Type, + Register: tmp, + } + + return value, nil + } + + if function != nil { + value := eval.Value{ + Kind: eval.Label, + Type: types.AnyPointer, + Label: function.UniqueName, + } + + return value, nil + } + + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + + case token.Number, token.Rune: + number, err := f.ToNumber(expr.Token) + + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + } + + return value, nil + + case token.String: + data := expr.Token.Bytes(f.File.Bytes) + data = String(data) + + slice := make([]byte, len(data)+8+1) + binary.LittleEndian.PutUint64(slice, uint64(len(data))) + copy(slice[8:], data) + label := f.AddBytes(slice) + + value := eval.Value{ + Kind: eval.Label, + Type: types.String, + Label: label, + } + + return value, nil + } + + return eval.Value{}, errors.New(errors.InvalidExpression, f.File, expr.Token.Position) +} diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 54d82b2..b81d5cd 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -25,8 +25,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return f.CallToRegister(node, register) case token.Array: return f.ArrayElementToRegister(node, register) - case token.Period: - return f.PeriodToRegister(node, register) + case token.Dot: + return f.DotToRegister(node, register) } if len(node.Children) == 1 { diff --git a/src/core/Fold.go b/src/core/Fold.go index 11ae861..bb1419a 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -20,7 +20,7 @@ func (f *Function) Fold(expr *expression.Expression) error { return f.FoldLeaf(expr) } - if expr.Token.Kind == token.Period { + if expr.Token.Kind == token.Dot { return f.FoldConstant(expr) } diff --git a/src/expression/Operator.go b/src/expression/Operator.go index 63f77c1..cd626d4 100644 --- a/src/expression/Operator.go +++ b/src/expression/Operator.go @@ -16,7 +16,7 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. var Operators = [64]Operator{ - token.Period: {".", 13, 2}, + token.Dot: {".", 13, 2}, token.Call: {"λ", 12, 1}, token.Array: {"@", 12, 2}, token.Negate: {"-", 11, 1}, diff --git a/src/token/Kind.go b/src/token/Kind.go index 2e0d764..881bd18 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -33,7 +33,7 @@ const ( LogicalAnd // && LogicalOr // || Define // := - Period // . + Dot // . Range // .. Call // x() Array // [x] diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 65b8abe..19ebf16 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -216,14 +216,14 @@ func TestDefine(t *testing.T) { } } -func TestPeriod(t *testing.T) { +func TestDot(t *testing.T) { tokens := token.Tokenize([]byte(`a.b.c`)) expected := []token.Kind{ token.Identifier, - token.Period, + token.Dot, token.Identifier, - token.Period, + token.Dot, token.Identifier, token.EOF, } diff --git a/src/token/operator.go b/src/token/operator.go index 5d74059..3352f9e 100644 --- a/src/token/operator.go +++ b/src/token/operator.go @@ -35,7 +35,7 @@ func operator(tokens List, buffer []byte, i Position) (List, Position) { case "+=": kind = AddAssign case ".": - kind = Period + kind = Dot case "..": kind = Range case ":=":