diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 0ccfe3c..c884a82 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -25,7 +25,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { } if ast.IsFunctionCall(left) && right.IsLeaf() { - err := f.CompileCall(left) + _, err := f.CompileCall(left) if err != nil { return err @@ -35,7 +35,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { } tmp := f.NewRegister() - err := f.ExpressionToRegister(left, tmp) + _, err := f.ExpressionToRegister(left, tmp) if err != nil { return err diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index 7277ccc..ddbd05a 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -17,7 +17,8 @@ func (f *Function) CompileASTNode(node ast.Node) error { case *ast.Call: f.Fold(node.Expression) - return f.CompileCall(node.Expression) + _, err := f.CompileCall(node.Expression) + return err case *ast.Define: f.Fold(node.Expression) diff --git a/src/build/core/CompileAssignArray.go b/src/build/core/CompileAssignArray.go index 167d3e3..8f812d5 100644 --- a/src/build/core/CompileAssignArray.go +++ b/src/build/core/CompileAssignArray.go @@ -33,5 +33,6 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { Length: byte(1), } - return f.ExpressionToMemory(right, memory) + _, err = f.ExpressionToMemory(right, memory) + return err } diff --git a/src/build/core/CompileAssignDivision.go b/src/build/core/CompileAssignDivision.go index 177b008..9a06c61 100644 --- a/src/build/core/CompileAssignDivision.go +++ b/src/build/core/CompileAssignDivision.go @@ -29,7 +29,7 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { } dividend := right.Children[0] - dividendRegister, err := f.Evaluate(dividend) + _, dividendRegister, err := f.Evaluate(dividend) if err != nil { return err diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index c04f842..25466db 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -12,12 +12,14 @@ import ( // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileCall(root *expression.Expression) error { +func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { var ( pkg = f.Package nameRoot = root.Children[0] + fn *Function name string fullName string + exists bool ) if nameRoot.IsLeaf() { @@ -32,13 +34,13 @@ func (f *Function) CompileCall(root *expression.Expression) error { if !isSyscall { if pkg != f.File.Package { if f.File.Imports == nil { - return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) } imp, exists := f.File.Imports[pkg] if !exists { - return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) } imp.Used = true @@ -49,10 +51,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { tmp.WriteString(".") tmp.WriteString(name) fullName = tmp.String() - _, exists := f.Functions[fullName] + fn, exists = f.Functions[fullName] if !exists { - return errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) } } @@ -66,7 +68,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { err := f.ExpressionsToRegisters(parameters, registers) if err != nil { - return err + return fn, err } // TODO: Save all return value registers of the function @@ -104,5 +106,5 @@ func (f *Function) CompileCall(root *expression.Expression) error { } } - return nil + return fn, nil } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 689f020..aeed43d 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -5,6 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/types" ) // CompileDefinition compiles a variable definition. @@ -19,9 +20,20 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - err = f.ExpressionToRegister(right, variable.Register) + typ, err := f.ExpressionToRegister(right, variable.Register) + + if err != nil { + return err + } + + variable.Type = typ + + if variable.Type == types.Invalid { + return errors.New(errors.UnknownType, f.File, node.Expression.Token.End()) + } + f.AddVariable(variable) - return err + return nil } if !ast.IsFunctionCall(right) { @@ -29,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } count := 0 - err := f.CompileCall(right) + _, err := f.CompileCall(right) if err != nil { return err diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go index f7d601a..145f3b1 100644 --- a/src/build/core/Evaluate.go +++ b/src/build/core/Evaluate.go @@ -4,21 +4,22 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (cpu.Register, error) { +func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Register, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable.Alive == 1 { f.UseVariable(variable) - return variable.Register, nil + return variable.Type, variable.Register, nil } } tmp := f.NewRegister() - err := f.ExpressionToRegister(expr, tmp) - return tmp, err + typ, err := f.ExpressionToRegister(expr, tmp) + return typ, tmp, err } diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 76a54bf..e4b7010 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -14,7 +14,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * } if ast.IsFunctionCall(value) { - err := f.CompileCall(value) + _, err := f.CompileCall(value) if err != nil { return err @@ -26,7 +26,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * tmp := f.NewRegister() defer f.FreeRegister(tmp) - err := f.ExpressionToRegister(value, tmp) + _, err := f.ExpressionToRegister(value, tmp) if err != nil { return err diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index fe78ef6..2254a8a 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -31,7 +31,8 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope case token.String: if operation.Kind == token.Assign { - return f.TokenToRegister(operand, register) + _, err := f.TokenToRegister(operand, register) + return err } } diff --git a/src/build/core/ExpressionToMemory.go b/src/build/core/ExpressionToMemory.go index 781b378..204e17d 100644 --- a/src/build/core/ExpressionToMemory.go +++ b/src/build/core/ExpressionToMemory.go @@ -5,30 +5,31 @@ import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/build/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. -func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) error { +func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) { if node.IsLeaf() && node.Token.IsNumeric() { number, err := f.Number(node.Token) if err != nil { - return err + return types.Invalid, err } size := byte(sizeof.Signed(int64(number))) if size != memory.Length { - return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + return types.Invalid, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) } f.MemoryNumber(asm.STORE, memory, number) - return nil + return types.Int, nil } tmp := f.NewRegister() defer f.FreeRegister(tmp) - err := f.ExpressionToRegister(node, tmp) + typ, err := f.ExpressionToRegister(node, tmp) f.MemoryRegister(asm.STORE, memory, tmp) - return err + return typ, err } diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 29a084c..c084351 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -7,15 +7,16 @@ import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // ExpressionToRegister puts the result of an expression into the specified register. -func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { +func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { f.SaveRegister(register) if node.IsFolded { f.RegisterNumber(asm.MOVE, register, node.Value) - return nil + return types.Int, nil } if node.IsLeaf() { @@ -23,34 +24,38 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if ast.IsFunctionCall(node) { - err := f.CompileCall(node) + fn, err := f.CompileCall(node) if register != f.CPU.Output[0] { f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } - return err + if fn == nil || len(fn.ReturnTypes) == 0 { + return types.Invalid, err + } + + return fn.ReturnTypes[0], err } if node.Token.Kind == token.Array { array := f.VariableByName(node.Children[0].Token.Text(f.File.Bytes)) offset, err := f.Number(node.Children[1].Token) f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: byte(offset), Length: 1}, register) - return err + return types.Int, err } if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { - return errors.New(errors.MissingOperand, f.File, node.Token.End()) + return types.Invalid, errors.New(errors.MissingOperand, f.File, node.Token.End()) } - err := f.ExpressionToRegister(node.Children[0], register) + typ, err := f.ExpressionToRegister(node.Children[0], register) if err != nil { - return err + return typ, err } - return f.ExecuteRegister(node.Token, register) + return typ, f.ExecuteRegister(node.Token, register) } left := node.Children[0] @@ -61,10 +66,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp register = f.NewRegister() } - err := f.ExpressionToRegister(left, register) + typ, err := f.ExpressionToRegister(left, register) if err != nil { - return err + return types.Invalid, err } err = f.Execute(node.Token, register, right) @@ -74,5 +79,5 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.FreeRegister(register) } - return err + return typ, err } diff --git a/src/build/core/ExpressionsToRegisters.go b/src/build/core/ExpressionsToRegisters.go index f3a09fe..982701a 100644 --- a/src/build/core/ExpressionsToRegisters.go +++ b/src/build/core/ExpressionsToRegisters.go @@ -8,7 +8,7 @@ import ( // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { for i := len(expressions) - 1; i >= 0; i-- { - err := f.ExpressionToRegister(expressions[i], registers[i]) + _, err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { return err diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 9aeb10a..5f8c25c 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -3,21 +3,25 @@ package core import ( "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/register" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - UniqueName string - File *fs.File - Body []token.Token - Functions map[string]*Function - Err error - deferred []func() - count counter + Package string + Name string + UniqueName string + File *fs.File + Body token.List + Parameters []*scope.Variable + ReturnTypes []types.Type + Functions map[string]*Function + Err error + deferred []func() + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 3335daf..c1d38ff 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -5,35 +5,36 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // 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 { +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) { switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + return types.Invalid, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } f.UseVariable(variable) f.SaveRegister(register) f.RegisterRegister(asm.MOVE, register, variable.Register) - return nil + return variable.Type, nil case token.Number, token.Rune: number, err := f.Number(t) if err != nil { - return err + return types.Invalid, err } f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, number) - return nil + return types.Int, nil case token.String: data := t.Bytes(f.File.Bytes) @@ -41,9 +42,9 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { label := f.AddBytes(data) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) - return nil + return types.Int, nil default: - return errors.New(errors.InvalidExpression, f.File, t.Position) + return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position) } } diff --git a/src/build/errors/Base.go b/src/build/errors/Base.go index 136c9bf..0933e59 100644 --- a/src/build/errors/Base.go +++ b/src/build/errors/Base.go @@ -19,6 +19,7 @@ var ( MissingOperand = &Base{"Missing operand"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} + UnknownType = &Base{"Unknown type"} ) // Base is the base class for errors that have no parameters. diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 59260bd..b7174b0 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -1,7 +1,6 @@ package scanner import ( - "fmt" "os" "path/filepath" @@ -13,6 +12,7 @@ import ( "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // scanFile scans a single file. @@ -42,6 +42,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { paramsStart = -1 paramsEnd = -1 bodyStart = -1 + typeStart = -1 + typeEnd = -1 ) for { @@ -156,9 +158,13 @@ func (s *Scanner) scanFile(path string, pkg string) error { // Return type if i < len(tokens) && tokens[i].Kind == token.ReturnType { + typeStart = i + 1 + for i < len(tokens) && tokens[i].Kind != token.BlockStart { i++ } + + typeEnd = i } // Function definition @@ -221,6 +227,11 @@ func (s *Scanner) scanFile(path string, pkg string) error { name := tokens[nameStart].Text(contents) body := tokens[bodyStart:i] function := core.NewFunction(pkg, name, file, body) + + if typeStart != -1 { + function.ReturnTypes = append(function.ReturnTypes, types.New(tokens[typeStart:typeEnd])) + } + parameters := tokens[paramsStart:paramsEnd] count := 0 @@ -230,8 +241,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } name := tokens[0].Text(contents) - dataType := tokens[1].Text(contents) - fmt.Println(dataType) + dataType := types.New(tokens[1:]) register := x64.CallRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) @@ -241,10 +251,12 @@ func (s *Scanner) scanFile(path string, pkg string) error { variable := &scope.Variable{ Name: name, + Type: dataType, Register: register, Alive: uses, } + function.Parameters = append(function.Parameters, variable) function.AddVariable(variable) count++ return nil @@ -258,6 +270,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { nameStart = -1 paramsStart = -1 bodyStart = -1 + typeStart = -1 + typeEnd = -1 i++ } } diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go index 491bb78..0346ca1 100644 --- a/src/build/scope/Variable.go +++ b/src/build/scope/Variable.go @@ -2,11 +2,13 @@ package scope import ( "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/types" ) // Variable represents a named register. type Variable struct { Name string + Type types.Type Alive uint8 Register cpu.Register } diff --git a/src/build/types/New.go b/src/build/types/New.go new file mode 100644 index 0000000..73e7d23 --- /dev/null +++ b/src/build/types/New.go @@ -0,0 +1,8 @@ +package types + +import "git.akyoto.dev/cli/q/src/build/token" + +// New creates a new type from a list of tokens. +func New(tokens token.List) Type { + return Int +} diff --git a/src/build/types/Type.go b/src/build/types/Type.go new file mode 100644 index 0000000..07340a0 --- /dev/null +++ b/src/build/types/Type.go @@ -0,0 +1,8 @@ +package types + +type Type int + +const ( + Invalid Type = iota // Invalid is an invalid type. + Int +)