diff --git a/examples/point/point.q b/examples/point/point.q index 2898895..bc21184 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -7,10 +7,19 @@ struct Point { } main() { + p := construct() + print(p) + delete(p) +} + +construct() -> *Point { p := new(Point) p.x = 1 p.y = 2 + return p +} +print(p *Point) { out := mem.alloc(8) out[0] = 'x' out[1] = ' ' @@ -21,7 +30,5 @@ main() { out[6] = '0' + p.y out[7] = '\n' sys.write(1, out, 8) - mem.free(out) - - delete(p) + mem.free(out, 8) } \ No newline at end of file diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 23960a1..2fdea38 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -14,7 +14,18 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} - allTypes := map[string]types.Type{} + + allTypes := map[string]types.Type{ + "Int": types.Int, + "Int64": types.Int64, + "Int32": types.Int32, + "Int16": types.Int16, + "Int8": types.Int8, + "Float": types.Float, + "Float64": types.Float64, + "Float32": types.Float32, + "Pointer": types.PointerAny, + } for functions != nil || files != nil || errs != nil { select { @@ -54,6 +65,15 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } } + // Resolve the types + for _, function := range allFunctions { + err := function.ResolveTypes() + + if err != nil { + return result, err + } + } + // Start parallel compilation CompileFunctions(allFunctions) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index d65baec..2b3f4fa 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -31,9 +31,11 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, f.CompileSyscall(root) case "new": return &Function{ - ReturnTypes: []types.Type{ - &types.Pointer{ - To: f.Types[root.Children[1].Token.Text(f.File.Bytes)], + Output: []*Output{ + { + Type: &types.Pointer{ + To: f.Types[root.Children[1].Token.Text(f.File.Bytes)], + }, }, }, }, f.CompileNew(root) @@ -92,16 +94,16 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, err } - if !types.Is(typ, fn.Parameters[i].Type) { + if !types.Is(typ, fn.Input[i].Type) { return nil, errors.New(&errors.TypeMismatch{ Encountered: typ.Name(), - Expected: fn.Parameters[i].Type.Name(), - ParameterName: fn.Parameters[i].Name, + Expected: fn.Input[i].Type.Name(), + ParameterName: fn.Input[i].Name, }, f.File, parameters[i].Token.Position) } } - for _, register := range f.CPU.Output[:len(fn.ReturnTypes)] { + for _, register := range f.CPU.Output[:len(fn.Output)] { f.SaveRegister(register) } diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 5782978..b50b3dd 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -28,7 +28,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { variable.Type = typ if variable.Type == nil { - return errors.New(errors.UnknownType, f.File, node.Expression.Token.End()) + return errors.New(errors.CouldNotInferType, f.File, node.Expression.Token.End()) } f.AddVariable(variable) @@ -54,7 +54,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } if called != nil { - variable.Type = called.ReturnTypes[count] + variable.Type = called.Output[count].Type } f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 9b84b58..16180a8 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -14,8 +14,8 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - if len(node.Values) != len(f.ReturnTypes) { - return errors.New(&errors.ReturnCountMismatch{Count: len(node.Values), ExpectedCount: len(f.ReturnTypes)}, f.File, node.Values[0].Token.Position) + if len(node.Values) != len(f.Output) { + return errors.New(&errors.ReturnCountMismatch{Count: len(node.Values), ExpectedCount: len(f.Output)}, f.File, node.Values[0].Token.Position) } for i := len(node.Values) - 1; i >= 0; i-- { @@ -25,10 +25,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return err } - if !types.Is(typ, f.ReturnTypes[i]) { + if !types.Is(typ, f.Output[i].Type) { return errors.New(&errors.TypeMismatch{ Encountered: typ.Name(), - Expected: f.ReturnTypes[i].Name(), + Expected: f.Output[i].Type.Name(), ParameterName: "", IsReturn: true, }, f.File, node.Values[i].Token.Position) diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index aa6ad9b..17f3feb 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -30,11 +30,11 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } - if fn == nil || len(fn.ReturnTypes) == 0 { + if fn == nil || len(fn.Output) == 0 { return nil, err } - return fn.ReturnTypes[0], err + return fn.Output[0].Type, err } if node.Token.Kind == token.Array { diff --git a/src/core/Function.go b/src/core/Function.go index 966b00c..7aeb189 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -4,7 +4,6 @@ import ( "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" ) @@ -12,19 +11,19 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - UniqueName string - File *fs.File - Body token.List - Parameters []*scope.Variable - ReturnTypes []types.Type - Functions map[string]*Function - Types map[string]types.Type - DLLs dll.List - Err error - deferred []func() - count counter + Package string + Name string + UniqueName string + File *fs.File + Body token.List + Input []*Input + Output []*Output + Functions map[string]*Function + Types map[string]types.Type + DLLs dll.List + 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/core/Input.go b/src/core/Input.go new file mode 100644 index 0000000..2b9aee2 --- /dev/null +++ b/src/core/Input.go @@ -0,0 +1,16 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +type Input struct { + Name string + Type types.Type + tokens token.List +} + +func NewInput(tokens token.List) *Input { + return &Input{tokens: tokens} +} diff --git a/src/core/Output.go b/src/core/Output.go new file mode 100644 index 0000000..3b1b434 --- /dev/null +++ b/src/core/Output.go @@ -0,0 +1,15 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +type Output struct { + Type types.Type + tokens token.List +} + +func NewOutput(tokens token.List) *Output { + return &Output{tokens: tokens} +} diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go new file mode 100644 index 0000000..c508073 --- /dev/null +++ b/src/core/ResolveTypes.go @@ -0,0 +1,45 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/x64" +) + +// ResolveTypes parses the input and output types. +func (f *Function) ResolveTypes() error { + for i, param := range f.Input { + param.Name = param.tokens[0].Text(f.File.Bytes) + typeName := param.tokens[1:].Text(f.File.Bytes) + param.Type = f.TypeByName(typeName) + + if param.Type == nil { + return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) + } + + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.Name) + + if uses == 0 && param.Name != "_" { + return errors.New(&errors.UnusedVariable{Name: param.Name}, f.File, param.tokens[0].Position) + } + + f.AddVariable(&scope.Variable{ + Name: param.Name, + Type: param.Type, + Register: x64.InputRegisters[i], + Alive: uses, + }) + } + + for _, param := range f.Output { + typeName := param.tokens.Text(f.File.Bytes) + param.Type = f.TypeByName(typeName) + + if param.Type == nil { + return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) + } + } + + return nil +} diff --git a/src/core/TypeByName.go b/src/core/TypeByName.go new file mode 100644 index 0000000..fcad711 --- /dev/null +++ b/src/core/TypeByName.go @@ -0,0 +1,17 @@ +package core + +import ( + "strings" + + "git.akyoto.dev/cli/q/src/types" +) + +// TypeByName returns the type with the given name or `nil` if it doesn't exist. +func (f *Function) TypeByName(name string) types.Type { + if strings.HasPrefix(name, "*") { + to := strings.TrimPrefix(name, "*") + return &types.Pointer{To: f.TypeByName(to)} + } + + return f.Types[name] +} diff --git a/src/errors/Common.go b/src/errors/Common.go index d395bdd..107cb3c 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -1,6 +1,7 @@ package errors var ( + CouldNotInferType = &Base{"Couldn't infer type"} EmptySwitch = &Base{"Empty switch"} ExpectedFunctionName = &Base{"Expected function name"} ExpectedFunctionParameters = &Base{"Expected function parameters"} @@ -21,5 +22,4 @@ var ( MissingParameter = &Base{"Missing parameter"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} - UnknownType = &Base{"Unknown type"} ) diff --git a/src/errors/UnknownType.go b/src/errors/UnknownType.go new file mode 100644 index 0000000..77fc270 --- /dev/null +++ b/src/errors/UnknownType.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +// UnknownType represents unknown types. +type UnknownType struct { + Name string + CorrectName string +} + +// Error generates the string representation. +func (err *UnknownType) Error() string { + if err.CorrectName != "" { + return fmt.Sprintf("Unknown type '%s', did you mean '%s'?", err.Name, err.CorrectName) + } + + return fmt.Sprintf("Unknown type '%s'", err.Name) +} diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index 30cda18..63fb880 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -4,10 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x64" ) // scanFunction scans a function. @@ -157,11 +154,19 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er typeEnd-- } - function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], file.Bytes) + outputTokens := tokens[typeStart:typeEnd] + + err := outputTokens.Split(func(tokens token.List) error { + function.Output = append(function.Output, core.NewOutput(tokens)) + return nil + }) + + if err != nil { + return i, err + } } parameters := tokens[paramsStart:paramsEnd] - count := 0 err := parameters.Split(func(tokens token.List) error { if len(tokens) == 0 { @@ -172,25 +177,7 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er return errors.New(errors.MissingType, file, tokens[0].End()) } - name := tokens[0].Text(file.Bytes) - dataType := types.Parse(tokens[1:].Text(file.Bytes)) - register := x64.InputRegisters[count] - uses := token.Count(function.Body, file.Bytes, token.Identifier, name) - - if uses == 0 && name != "_" { - return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) - } - - variable := &scope.Variable{ - Name: name, - Type: dataType, - Register: register, - Alive: uses, - } - - function.Parameters = append(function.Parameters, variable) - function.AddVariable(variable) - count++ + function.Input = append(function.Input, core.NewInput(tokens)) return nil }) diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index e99165f..f2319e9 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -33,7 +33,12 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldName := tokens[i].Text(file.Bytes) i++ fieldTypeName := tokens[i].Text(file.Bytes) - fieldType := types.Parse(fieldTypeName) + fieldType := types.Int + + if fieldTypeName != "Int" { + panic("not implemented") + } + i++ structure.AddField(&types.Field{ diff --git a/src/types/Parse.go b/src/types/Parse.go deleted file mode 100644 index 6fcc1f2..0000000 --- a/src/types/Parse.go +++ /dev/null @@ -1,27 +0,0 @@ -package types - -// Parse creates a new type from a list of tokens. -func Parse(name string) Type { - switch name { - case "Int": - return Int - case "Int64": - return Int64 - case "Int32": - return Int32 - case "Int16": - return Int16 - case "Int8": - return Int8 - case "Float": - return Float - case "Float64": - return Float64 - case "Float32": - return Float32 - case "Pointer": - return PointerAny - default: - panic("Unknown type " + name) - } -} diff --git a/src/types/ParseList.go b/src/types/ParseList.go deleted file mode 100644 index 137aec2..0000000 --- a/src/types/ParseList.go +++ /dev/null @@ -1,16 +0,0 @@ -package types - -import "git.akyoto.dev/cli/q/src/token" - -// ParseList generates a list of types from comma separated tokens. -func ParseList(tokens token.List, source []byte) []Type { - var list []Type - - tokens.Split(func(parameter token.List) error { - typ := Parse(parameter.Text(source)) - list = append(list, typ) - return nil - }) - - return list -}