diff --git a/compiler/compiler/alloc.go b/compiler/compiler/alloc.go index 747cbcf..2906c58 100644 --- a/compiler/compiler/alloc.go +++ b/compiler/compiler/alloc.go @@ -20,6 +20,11 @@ func (c *Compiler) compileAllocNode(v *parser.AllocNode) { c.contextAlloc = c.contextAlloc[0 : len(c.contextAlloc)-1] }() + if v.IsConst { + c.compileAllocConstNode(v) + return + } + // Allocate from type if len(v.Val) == 0 && v.Type != nil { treType := c.parserTypeToType(v.Type) @@ -125,6 +130,16 @@ func (c *Compiler) compileAllocNode(v *parser.AllocNode) { return } +func (c *Compiler) compileAllocConstNode(v *parser.AllocNode) { + for i, varName := range v.Name { + cnst := v.Val[i].(*parser.ConstantNode) + c.setVar(varName, value.Value{ + Type: &types.UntypedConstantNumber{}, + Value: constant.NewInt(i64.LLVM().(*irTypes.IntType), cnst.Value), + }) + } +} + func (c *Compiler) compileAssignNode(v *parser.AssignNode) { // Allocate from type if typeNode, ok := v.Val[0].(parser.TypeNode); ok { @@ -143,6 +158,9 @@ func (c *Compiler) compileAssignNode(v *parser.AssignNode) { // Skip temporary variables if we're assigning to one single var if len(v.Target) == 1 { dst := c.compileValue(v.Target[0]) + if !dst.IsVariable { + compilePanic("Can only assign to variable") + } s := c.compileSingleAssign(dst.Type, dst, v.Val[0]) c.contextBlock.NewStore(s, dst.Value) return diff --git a/compiler/compiler/condition.go b/compiler/compiler/condition.go index 6f7d14b..3736620 100644 --- a/compiler/compiler/condition.go +++ b/compiler/compiler/condition.go @@ -35,9 +35,19 @@ func getConditionLLVMpred(operator parser.Operator) enum.IPred { func (c *Compiler) compileOperatorNode(v *parser.OperatorNode) value.Value { left := c.compileValue(v.Left) - leftLLVM := left.Value - right := c.compileValue(v.Right) + + _, rightIsUntyped := right.Type.(*types.UntypedConstantNumber) + _, leftIsUntyped := left.Type.(*types.UntypedConstantNumber) + + if rightIsUntyped && !leftIsUntyped { + right = value.UntypedConstAs(right, left) + } + if leftIsUntyped && !rightIsUntyped { + left = value.UntypedConstAs(left, right) + } + + leftLLVM := left.Value rightLLVM := right.Value if left.IsVariable { @@ -48,7 +58,7 @@ func (c *Compiler) compileOperatorNode(v *parser.OperatorNode) value.Value { rightLLVM = c.contextBlock.NewLoad(pointer.ElemType(rightLLVM), rightLLVM) } - if !leftLLVM.Type().Equal(rightLLVM.Type()) { + if !leftLLVM.Type().Equal(rightLLVM.Type()) && !rightIsUntyped && !leftIsUntyped { panic(fmt.Sprintf("Different types in operation: %T and %T (%+v and %+v)", left.Type, right.Type, leftLLVM.Type(), rightLLVM.Type())) } diff --git a/compiler/compiler/constants.go b/compiler/compiler/constants.go index c01d131..bba9d74 100644 --- a/compiler/compiler/constants.go +++ b/compiler/compiler/constants.go @@ -31,7 +31,7 @@ func (c *Compiler) compileConstantNode(v *parser.ConstantNode) value.Value { return value.Value{ Value: constant.NewInt(intType.Type, v.Value), - Type: i64, + Type: intType, IsVariable: false, } diff --git a/compiler/compiler/func.go b/compiler/compiler/func.go index 7122033..dd6fa41 100644 --- a/compiler/compiler/func.go +++ b/compiler/compiler/func.go @@ -186,15 +186,16 @@ func (c *Compiler) compileDefineFuncNode(v *parser.DefineFuncNode) value.Value { } // Save all parameters in the block mapping - // Return value arguments are ignored for i, param := range llvmParams { var paramName string var dataType types.Type + var isVariable bool // Named return values if i < argumentReturnValuesCount { paramName = v.ReturnValues[i].Name dataType = treReturnTypes[i] + isVariable = true } else { paramName = v.Arguments[i-argumentReturnValuesCount].Name dataType = treParams[i-argumentReturnValuesCount] @@ -218,7 +219,7 @@ func (c *Compiler) compileDefineFuncNode(v *parser.DefineFuncNode) value.Value { c.setVar(paramName, value.Value{ Value: param, Type: dataType, - IsVariable: false, + IsVariable: isVariable, }) } diff --git a/compiler/compiler/types/type.go b/compiler/compiler/types/type.go index 134e167..54e611f 100644 --- a/compiler/compiler/types/type.go +++ b/compiler/compiler/types/type.go @@ -2,11 +2,12 @@ package types import ( "fmt" + "math/big" + "github.com/llir/llvm/ir" "github.com/llir/llvm/ir/constant" "github.com/llir/llvm/ir/types" llvmValue "github.com/llir/llvm/ir/value" - "math/big" "github.com/zegl/tre/compiler/compiler/internal/pointer" "github.com/zegl/tre/compiler/compiler/name" @@ -342,3 +343,15 @@ func (m MultiValue) Name() string { func (m MultiValue) LLVM() types.Type { panic("MutliValue has no LLVM type") } + +type UntypedConstantNumber struct { + backingType +} + +func (m UntypedConstantNumber) Name() string { + return "UntypedConstantNumber" +} + +func (m UntypedConstantNumber) LLVM() types.Type { + panic("UntypedConstantNumber has no LLVM type") +} diff --git a/compiler/compiler/value/value.go b/compiler/compiler/value/value.go index a067177..71b575e 100644 --- a/compiler/compiler/value/value.go +++ b/compiler/compiler/value/value.go @@ -1,6 +1,7 @@ package value import ( + "github.com/llir/llvm/ir/constant" llvmValue "github.com/llir/llvm/ir/value" "github.com/zegl/tre/compiler/compiler/types" @@ -21,3 +22,21 @@ type Value struct { // type information MultiValues []Value } + +func UntypedConstAs(val Value, context Value) Value { + switch val.Type.(type) { + case *types.UntypedConstantNumber: + if contextInt, ok := context.Type.(*types.Int); ok { + return Value{ + Type: contextInt, + Value: &constant.Int{ + Typ: contextInt.Type, + X: val.Value.(*constant.Int).X, + }, + } + } + panic("unexpected type in UntypedConstAs") + default: + panic("unexpected type in UntypedConstAs") + } +} diff --git a/compiler/lexer/keywords.go b/compiler/lexer/keywords.go index a78e1a7..cb41a57 100644 --- a/compiler/lexer/keywords.go +++ b/compiler/lexer/keywords.go @@ -8,6 +8,7 @@ var keywords = map[string]struct{}{ "type": {}, "struct": {}, "var": {}, + "const": {}, "package": {}, "for": {}, "break": {}, diff --git a/compiler/parser/node.go b/compiler/parser/node.go index fbaedcc..238f2b4 100644 --- a/compiler/parser/node.go +++ b/compiler/parser/node.go @@ -237,6 +237,8 @@ type AllocNode struct { Val []Node Type TypeNode // Is set when allocating on the format "var ident int" and "var ident, ident int = expr, expr" + + IsConst bool // Is true when in a const expression. } func (an AllocNode) String() string { diff --git a/compiler/parser/parser.go b/compiler/parser/parser.go index bb1cfc2..b98d476 100644 --- a/compiler/parser/parser.go +++ b/compiler/parser/parser.go @@ -527,9 +527,11 @@ func (p *parser) parseOneWithOptions(withAheadParse, withArithAhead, withIdentif } // New instance of type - if current.Val == "var" { + if current.Val == "var" || current.Val == "const" { p.i++ + isConst := current.Val == "const" + isGroup := p.lookAhead(0) if isGroup.Val == "(" { p.i++ @@ -544,12 +546,12 @@ func (p *parser) parseOneWithOptions(withAheadParse, withArithAhead, withIdentif p.i++ continue } - allocs = append(allocs, p.parseVarDecl()) + allocs = append(allocs, p.parseVarDecl(isConst)) } return &AllocGroup{Allocs: allocs} } - return p.parseVarDecl() + return p.parseVarDecl(isConst) } if current.Val == "package" { @@ -612,15 +614,15 @@ func (p *parser) parseOneWithOptions(withAheadParse, withArithAhead, withIdentif panic("") } -func (p *parser) parseVarDecl() *AllocNode { - allocNode := &AllocNode{Name: p.identifierList()} +func (p *parser) parseVarDecl(isConst bool) *AllocNode { + allocNode := &AllocNode{Name: p.identifierList(), IsConst: isConst} isEq := p.lookAhead(0) - if isEq.Type == lexer.OPERATOR && isEq.Val == "=" { - p.i++ - allocNode.Val = p.expressionList() - return allocNode - } else { + if isEq.Type != lexer.OPERATOR || isEq.Val != "=" { + if isConst { + panic("unexpected type in const declaration") + } + tp, err := p.parseOneType() if err != nil { panic(err) diff --git a/compiler/parser/var_alloc_test.go b/compiler/parser/var_alloc_test.go index d9cead8..d1f44d2 100644 --- a/compiler/parser/var_alloc_test.go +++ b/compiler/parser/var_alloc_test.go @@ -98,3 +98,52 @@ func TestAllocGroup(t *testing.T) { assert.Equal(t, expected, Parse(lexed, false)) } + +func TestConstAlloc(t *testing.T) { + lexed := lexer.Lex(`const a = 30`) + expected := FileNode{ + Instructions: []Node{ + &AllocNode{ + IsConst: true, + Name: []string{"a"}, + Val: []Node{&ConstantNode{Type: NUMBER, Value: 30}}, + }, + }, + } + + assert.Equal(t, expected, Parse(lexed, false)) +} + +func TestAllocConstGroup(t *testing.T) { + lexed := lexer.Lex(`const ( + a = 10 + b, c = "bbb", "ccc" + d = 20 +)`) + + expected := FileNode{ + Instructions: []Node{ + &AllocGroup{ + Allocs: []*AllocNode{ + { + IsConst: true, + Name: []string{"a"}, + Val: []Node{&ConstantNode{Type: NUMBER, Value: 10}}, + }, + { + Name: []string{"b", "c"}, + Val: []Node{&ConstantNode{Type: STRING, ValueStr: "bbb"}, &ConstantNode{Type: STRING, ValueStr: "ccc"}}, + IsConst: true, + }, + { + Name: []string{"d"}, + Val: []Node{&ConstantNode{Type: NUMBER, Value: 20}}, + IsConst: true, + }, + }, + }, + }, + } + + assert.Equal(t, expected, Parse(lexed, false)) +} diff --git a/compiler/testdata/const.go b/compiler/testdata/const.go new file mode 100644 index 0000000..462b51e --- /dev/null +++ b/compiler/testdata/const.go @@ -0,0 +1,26 @@ +package main + +import "external" + +const ( + a = 10 + c = 12 + + big = 100000000 +) + +func main() { + external.Printf("%d\n", a) // 10 + + var b uint8 + b = 5 + external.Printf("%d\n", b+a) // 15 + external.Printf("%d\n", a+b) // 15 + + external.Printf("%d\n", a+c) // 22 + + var b32 int32 + b32 = 222288822 + external.Printf("%d\n", b32+a) // 222288832 + external.Printf("%d\n", a+b32) // 222288832 +} diff --git a/compiler/testdata/const_no_modify.go b/compiler/testdata/const_no_modify.go new file mode 100644 index 0000000..37880e6 --- /dev/null +++ b/compiler/testdata/const_no_modify.go @@ -0,0 +1,10 @@ +package main + +const ( + c = 12 +) + +func main() { + // compile panic: Can only assign to variable + c = 40 +} diff --git a/compiler/testdata/tour-named-return.go b/compiler/testdata/tour-named-return.go index 7f8830e..a5da3b0 100644 --- a/compiler/testdata/tour-named-return.go +++ b/compiler/testdata/tour-named-return.go @@ -18,6 +18,12 @@ func namedReturnNotUsed(inp int) (res int) { return 500 } +func multiNamedReturnRef(inp int) (x int, y int) { + x = inp * 2 + y = x + 2 + return +} + func main() { // 34 8 a, b := multiNamedReturn(17) @@ -28,4 +34,8 @@ func main() { // 500 external.Printf("%d\n", namedReturnNotUsed(18)) + + // 36 38 + c, d := multiNamedReturnRef(18) + external.Printf("%d %d\n", c, d) }