diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 2ff6dfb..9773db9 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,16 +1,13 @@ package core import ( - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" ) // CompileAssign compiles an assign statement. func (f *Function) CompileAssign(node *ast.Assign) error { - operator := node.Expression.Token left := node.Expression.Children[0] right := node.Expression.Children[1] @@ -23,7 +20,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { } defer f.UseVariable(variable) - return f.Execute(operator, variable.Register, right) + return f.Execute(node.Expression.Token, variable.Register, right) } if left.Token.Kind == token.Period { @@ -42,24 +39,5 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } - count := 0 - _, err := f.CompileCall(right) - - if err != nil { - return err - } - - return left.EachLeaf(func(leaf *expression.Expression) error { - name := leaf.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) - } - - f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) - f.UseVariable(variable) - count++ - return nil - }) + return f.MultiAssign(left, right) } diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index ab379e4..740c42b 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -1,11 +1,13 @@ package core import ( + "fmt" "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/types" ) @@ -13,7 +15,6 @@ import ( func (f *Function) CompileAssignArray(node *ast.Assign) error { left := node.Expression.Children[0] right := node.Expression.Children[1] - name := left.Children[0].Token.Text(f.File.Bytes) variable := f.VariableByName(name) @@ -31,30 +32,26 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { } indexExpr := left.Children[1] + index, err := f.Evaluate(indexExpr) - if indexExpr.Token.IsNumeric() { - index, err := f.ToNumber(indexExpr.Token) - - if err != nil { - return err - } - - memory.Offset = int8(index) - } else { - index, err := f.Evaluate(indexExpr) - - if err != nil { - return err - } - - if !types.Is(index.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) - } - - memory.OffsetRegister = index.Register - defer f.FreeRegister(index.Register) + if err != nil { + return err } - _, err := f.ExpressionToMemory(right, memory) + if !types.Is(index.Type, types.AnyInt) { + return 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)) + } + + _, err = f.ExpressionToMemory(right, memory) return err } diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 619e19b..fd16573 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -1,8 +1,11 @@ package core import ( + "fmt" + "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/scope" "git.urbach.dev/cli/q/src/token" @@ -13,21 +16,21 @@ import ( // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. func (f *Function) CompileAssignDivision(expr *expression.Expression) error { var ( - left = expr.Children[0] - right = expr.Children[1] + variables = expr.Children[0] + division = expr.Children[1] quotientVariable *scope.Variable remainderVariable *scope.Variable err error ) if expr.Token.Kind == token.Define { - quotientVariable, err = f.Define(left.Children[0]) + quotientVariable, err = f.Define(variables.Children[0]) if err != nil { return err } - remainderVariable, err = f.Define(left.Children[1]) + remainderVariable, err = f.Define(variables.Children[1]) if err != nil { return err @@ -38,7 +41,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { f.AddVariable(quotientVariable) f.AddVariable(remainderVariable) } else { - quotient := left.Children[0] + quotient := variables.Children[0] name := quotient.Token.Text(f.File.Bytes) quotientVariable = f.VariableByName(name) @@ -46,7 +49,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position) } - remainder := left.Children[1] + remainder := variables.Children[1] name = remainder.Token.Text(f.File.Bytes) remainderVariable = f.VariableByName(name) @@ -58,7 +61,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { defer f.UseVariable(remainderVariable) } - dividendExpr := right.Children[0] + dividendExpr := division.Children[0] dividend, err := f.Evaluate(dividendExpr) if err != nil { @@ -69,10 +72,21 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { return errors.New(&errors.TypeMismatch{Encountered: dividend.Type.Name(), Expected: types.AnyInt.Name()}, f.File, dividendExpr.Token.Position) } - divisor := right.Children[1] - err = f.Execute(right.Token, dividend.Register, divisor) + divisor := division.Children[1] + + switch dividend.Kind { + case eval.Number: + f.SaveRegister(x86.RAX) + f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) + err = f.Execute(division.Token, x86.RAX, divisor) + case eval.Register: + err = f.Execute(division.Token, dividend.Register, divisor) + defer f.FreeRegister(dividend.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, dividend.Kind)) + } + f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) - f.FreeRegister(dividend.Register) return err } diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 0e41f82..087731e 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -1,8 +1,11 @@ package core import ( + "fmt" + "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" @@ -66,22 +69,29 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err case token.Call: - typ, err := f.CompileCall(condition) + value, err := f.Evaluate(condition) if err != nil { return err } - if len(typ) == 0 { - return errors.New(errors.UntypedExpression, f.File, condition.Token.Position) + if !types.Is(value.Type, types.Bool) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) } - if !types.Is(typ[0], types.Bool) { - return errors.New(&errors.TypeMismatch{Encountered: typ[0].Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) + switch value.Kind { + case eval.Number: + if value.Number == 0 { + f.Jump(asm.JUMP, failLabel) + } + case eval.Register: + f.RegisterNumber(asm.COMPARE, value.Register, 0) + f.FreeRegister(value.Register) + f.Jump(asm.JE, failLabel) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) } - f.RegisterNumber(asm.COMPARE, f.CPU.Output[0], 0) - f.Jump(asm.JE, failLabel) return nil case token.Equal, token.NotEqual, token.Greater, token.Less, token.GreaterEqual, token.LessEqual: diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 98a63d9..1eafd68 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -1,10 +1,13 @@ package core import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/cpu" "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" @@ -58,29 +61,26 @@ func (f *Function) CompileFor(loop *ast.For) error { return err } - if to.Token.IsNumeric() { - number, err := f.ToNumber(to.Token) + value, err := f.Evaluate(to) - if err != nil { - return err - } + if err != nil { + return err + } - f.AddLabel(label) - f.RegisterNumber(asm.COMPARE, counter, number) - } else { - value, err := f.Evaluate(to) + if !types.Is(value.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) + } - if err != nil { - return err - } + f.AddLabel(label) - if !types.Is(value.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) - } - - f.AddLabel(label) + switch value.Kind { + case eval.Number: + f.RegisterNumber(asm.COMPARE, counter, value.Number) + case eval.Register: f.RegisterRegister(asm.COMPARE, counter, value.Register) defer f.FreeRegister(value.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) } f.Jump(asm.JGE, labelEnd) diff --git a/src/core/Define.go b/src/core/Define.go index d56208d..16ccdd6 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -2,6 +2,7 @@ package core import ( "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/scope" "git.urbach.dev/cli/q/src/token" @@ -24,7 +25,8 @@ func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) variable = &scope.Variable{ Name: name, - Value: scope.Value{ + Value: eval.Value{ + Kind: eval.Register, Register: f.NewRegister(), Alive: uses, }, diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 44adb18..a2af299 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,37 +1,88 @@ package core import ( - "git.urbach.dev/cli/q/src/ast" + "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/scope" "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. -func (f *Function) Evaluate(expr *expression.Expression) (scope.Value, error) { - if expr.Token.Kind == token.Identifier { - name := expr.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) +func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { + if expr.IsLeaf() { + if expr.Token.IsNumeric() { + number, err := f.ToNumber(expr.Token) - if variable == nil { - return scope.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + Alive: 1, + } + + return value, nil } - if variable.Alive == 1 { - f.UseVariable(variable) - return variable.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 ast.IsFunctionCall(expr) { + if expr.Token.Kind == token.Call { types, err := f.CompileCall(expr) if err != nil { - return scope.Value{}, err + return eval.Value{}, err } - value := scope.Value{ + 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], Alive: 1, @@ -40,10 +91,56 @@ func (f *Function) Evaluate(expr *expression.Expression) (scope.Value, error) { 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 variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + + 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()), + }, + } + + 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 + } + } + tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - value := scope.Value{ + value := eval.Value{ + Kind: eval.Register, Type: typ, Register: tmp, Alive: 1, diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index d807371..be9811f 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,64 +1,38 @@ package core import ( + "fmt" + "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" ) // ExpressionToMemory puts the result of an expression into the specified memory address. func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) { - if node.IsLeaf() { - if node.Token.Kind == token.Identifier { - name := node.Token.Text(f.File.Bytes) - variable, function := f.Identifier(name) - - if variable != nil { - f.MemoryRegister(asm.STORE, memory, variable.Register) - f.UseVariable(variable) - return types.AnyPointer, nil - } - - if function != nil { - f.MemoryLabel(asm.STORE, memory, function.UniqueName) - return types.AnyPointer, nil - } - - if name == "_exit" { - f.MemoryLabel(asm.STORE, memory, "_exit") - return types.AnyPointer, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) - } - - if node.Token.IsNumeric() { - number, err := f.ToNumber(node.Token) - - if err != nil { - return nil, err - } - - // size := byte(sizeof.Signed(int64(number))) - - // if size > memory.Length { - // return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) - // } - - f.MemoryNumber(asm.STORE, memory, number) - return types.AnyInt, nil - } - } - value, err := f.Evaluate(node) if err != nil { return nil, err } - f.MemoryRegister(asm.STORE, memory, value.Register) - f.FreeRegister(value.Register) + switch value.Kind { + case eval.Number: + f.MemoryNumber(asm.STORE, memory, value.Number) + case eval.Register: + f.MemoryRegister(asm.STORE, memory, value.Register) + f.FreeRegister(value.Register) + case eval.Memory: + tmp := f.NewRegister() + f.MemoryRegister(asm.LOAD, value.Memory, tmp) + f.MemoryRegister(asm.STORE, memory, tmp) + f.FreeRegister(tmp) + case eval.Label: + f.MemoryLabel(asm.STORE, memory, value.Label) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + } + return value.Type, err } diff --git a/src/core/MultiAssign.go b/src/core/MultiAssign.go new file mode 100644 index 0000000..e6c3f13 --- /dev/null +++ b/src/core/MultiAssign.go @@ -0,0 +1,31 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" +) + +// MultiAssign assigns multiple return values to local variables. +func (f *Function) MultiAssign(left *expression.Expression, right *expression.Expression) error { + count := 0 + _, err := f.CompileCall(right) + + if err != nil { + return err + } + + return left.EachLeaf(func(leaf *expression.Expression) error { + name := leaf.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + } + + f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.UseVariable(variable) + count++ + return nil + }) +} diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index cf17430..7db5a6c 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -2,6 +2,7 @@ package core import ( "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" @@ -31,7 +32,8 @@ func (f *Function) ResolveTypes() error { f.AddVariable(&scope.Variable{ Name: param.name, - Value: scope.Value{ + Value: eval.Value{ + Kind: eval.Register, Type: param.typ, Register: x86.InputRegisters[i], Alive: uses, diff --git a/src/eval/Kind.go b/src/eval/Kind.go new file mode 100644 index 0000000..4a41f05 --- /dev/null +++ b/src/eval/Kind.go @@ -0,0 +1,11 @@ +package eval + +type Kind uint8 + +const ( + Invalid Kind = iota // Invalid is an invalid value. + Number // Number is an immediately encoded value stored together with the instruction. + Register // Register is a CPU register. + Memory // Memory is an area in the RAM. + Label // Label is a reference to a name that can only be resolved once the program is fully compiled. +) diff --git a/src/scope/Value.go b/src/eval/Value.go similarity index 80% rename from src/scope/Value.go rename to src/eval/Value.go index 93a3774..1205a98 100644 --- a/src/scope/Value.go +++ b/src/eval/Value.go @@ -1,6 +1,7 @@ -package scope +package eval import ( + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/types" ) @@ -8,8 +9,12 @@ import ( // Value combines a register with its data type. type Value struct { Type types.Type + Label string + Number int + Memory asm.Memory Register cpu.Register Alive uint8 + Kind Kind } // IsAlive returns true if the Value is still alive. diff --git a/src/readme.md b/src/readme.md index 3dc207b..99ce70e 100644 --- a/src/readme.md +++ b/src/readme.md @@ -14,6 +14,7 @@ - [dll](dll) - DLL support for Windows systems (w.i.p.) - [elf](elf) - ELF format for Linux executables - [errors](errors) - Error types +- [eval](eval) - Evaluates expressions - [expression](expression) - Expression parser generating trees with the `Parse` function - [fs](fs) - File system access - [macho](macho) - MachO format for Mac executables diff --git a/src/scope/Stack.go b/src/scope/Stack.go index 5037f45..c2c1e9d 100644 --- a/src/scope/Stack.go +++ b/src/scope/Stack.go @@ -3,6 +3,7 @@ package scope import ( "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" ) @@ -45,7 +46,8 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { s.Variables = append(s.Variables, &Variable{ Name: v.Name, - Value: Value{ + Value: eval.Value{ + Kind: eval.Register, Register: v.Register, Alive: count, Type: v.Type, diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 4a387ae..72ea61b 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -1,7 +1,9 @@ package scope +import "git.urbach.dev/cli/q/src/eval" + // Variable is a named value. type Variable struct { - Value Name string + eval.Value } diff --git a/tests/programs/for.q b/tests/programs/for.q index e41e08f..894751f 100644 --- a/tests/programs/for.q +++ b/tests/programs/for.q @@ -11,7 +11,7 @@ main() { total -= 1 } - assert total == 5 + assert total == 0 for i := 0..10 { assert i >= 0