diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index adbc0d4..d0dda24 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -33,6 +33,12 @@ func Compile(build *config.Build) (*core.Environment, error) { env.Main = main + // Parse struct field types and calculate the size of all structs. + // We couldn't do that during the scan phase because it's possible + // that a field references a type that will only be known after the + // full scan is finished. + parseFieldTypes(env.Structs()) + // Parse input and output types so we have type information // ready for all functions before parallel compilation starts. // This ensures that the function compilers have access to diff --git a/src/compiler/parseFieldTypes.go b/src/compiler/parseFieldTypes.go new file mode 100644 index 0000000..5359826 --- /dev/null +++ b/src/compiler/parseFieldTypes.go @@ -0,0 +1,21 @@ +package compiler + +import ( + "iter" + + "git.urbach.dev/cli/q/src/types" +) + +// parseFieldTypes parses the tokens of the struct field types. +func parseFieldTypes(structs iter.Seq[*types.Struct]) { + for structure := range structs { + offset := 0 + + for i, field := range structure.Fields { + field.Type = types.Parse(field.Tokens[1:], structure.File.Bytes) + field.Index = uint8(i) + field.Offset = uint8(offset) + offset += field.Type.Size() + } + } +} \ No newline at end of file diff --git a/src/core/Environment.go b/src/core/Environment.go index 4076455..a30563e 100644 --- a/src/core/Environment.go +++ b/src/core/Environment.go @@ -5,6 +5,7 @@ import ( "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/types" ) // Environment holds information about the entire build. @@ -29,6 +30,7 @@ func (env *Environment) AddPackage(name string, isExtern bool) *Package { Name: name, Constants: make(map[string]*Constant), Functions: make(map[string]*Function, 8), + Structs: make(map[string]*types.Struct), IsExtern: isExtern, } @@ -82,4 +84,17 @@ func (env *Environment) LiveFunctions() iter.Seq[*Function] { running = yield(f) }) } +} + +// Structs returns an iterator over all structs. +func (env *Environment) Structs() iter.Seq[*types.Struct] { + return func(yield func(*types.Struct) bool) { + for _, pkg := range env.Packages { + for _, structure := range pkg.Structs { + if !yield(structure) { + return + } + } + } + } } \ No newline at end of file diff --git a/src/core/Package.go b/src/core/Package.go index 026f166..66b73a0 100644 --- a/src/core/Package.go +++ b/src/core/Package.go @@ -1,9 +1,12 @@ package core +import "git.urbach.dev/cli/q/src/types" + // Package represents a directory of functions. type Package struct { Constants map[string]*Constant Functions map[string]*Function + Structs map[string]*types.Struct Name string IsExtern bool } \ No newline at end of file diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index df953bc..f14d76b 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -7,6 +7,7 @@ import ( "git.urbach.dev/cli/q/src/core" "git.urbach.dev/cli/q/src/fs" "git.urbach.dev/cli/q/src/global" + "git.urbach.dev/cli/q/src/types" ) // Scan scans all the files included in the build. @@ -15,6 +16,7 @@ func Scan(build *config.Build) (*core.Environment, error) { constants: make(chan *core.Constant), functions: make(chan *core.Function), files: make(chan *fs.File), + structs: make(chan *types.Struct), errors: make(chan error), build: build, } @@ -26,6 +28,7 @@ func Scan(build *config.Build) (*core.Environment, error) { close(s.constants) close(s.functions) close(s.files) + close(s.structs) close(s.errors) }() @@ -35,7 +38,7 @@ func Scan(build *config.Build) (*core.Environment, error) { Packages: make(map[string]*core.Package, 8), } - for s.functions != nil || s.files != nil || s.constants != nil || s.errors != nil { + for s.functions != nil || s.files != nil || s.structs != nil || s.constants != nil || s.errors != nil { select { case f, ok := <-s.functions: if !ok { @@ -56,6 +59,15 @@ func Scan(build *config.Build) (*core.Environment, error) { env.Files = append(env.Files, file) + case structure, ok := <-s.structs: + if !ok { + s.structs = nil + continue + } + + pkg := env.AddPackage(structure.Package, false) + pkg.Structs[structure.Package] = structure + case constant, ok := <-s.constants: if !ok { s.constants = nil diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index fd74964..583a7b5 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -33,7 +33,26 @@ func (s *scanner) scanFile(path string, pkg string) error { case token.NewLine: case token.Comment: case token.Identifier: - i, err = s.scanFunction(file, tokens, i) + if i+1 >= len(tokens) { + return errors.New(InvalidFunctionDefinition, file, tokens[i].End()) + } + + next := tokens[i+1] + + switch next.Kind { + case token.GroupStart: + i, err = s.scanFunction(file, tokens, i) + case token.BlockStart: + i, err = s.scanStruct(file, tokens, i) + case token.GroupEnd: + return errors.New(MissingGroupStart, file, next.Position) + case token.BlockEnd: + return errors.New(MissingBlockStart, file, next.Position) + case token.Invalid: + return errors.New(&InvalidCharacter{Character: next.String(file.Bytes)}, file, next.Position) + default: + return errors.New(InvalidFunctionDefinition, file, next.Position) + } case token.Const: i, err = s.scanConst(file, tokens, i) case token.Extern: diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go new file mode 100644 index 0000000..f7a8455 --- /dev/null +++ b/src/scanner/scanStruct.go @@ -0,0 +1,47 @@ +package scanner + +import ( + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" +) + +// scanStruct scans a struct. +func (s *scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, error) { + structName := tokens[i].String(file.Bytes) + structure := types.NewStruct(file, file.Package, structName) + i += 2 + start := -1 + + for i < len(tokens) { + switch tokens[i].Kind { + case token.Identifier: + if start == -1 { + start = i + } + + case token.NewLine, token.BlockEnd: + if start != -1 { + name := tokens[start].String(file.Bytes) + + structure.AddField(&types.Field{ + Tokens: tokens[start:i], + Name: name, + Position: token.Position(start), + }) + } + + if tokens[i].Kind == token.BlockEnd { + s.structs <- structure + return i, nil + } + + start = -1 + } + + i++ + } + + return i, errors.New(MissingBlockEnd, file, tokens[i].Position) +} \ No newline at end of file diff --git a/src/scanner/scanner.go b/src/scanner/scanner.go index 1601399..6ace1a1 100644 --- a/src/scanner/scanner.go +++ b/src/scanner/scanner.go @@ -6,6 +6,7 @@ import ( "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/core" "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/types" ) // scanner is used to scan files before the actual compilation step. @@ -13,6 +14,7 @@ type scanner struct { constants chan *core.Constant functions chan *core.Function files chan *fs.File + structs chan *types.Struct errors chan error build *config.Build queued sync.Map diff --git a/src/types/Field.go b/src/types/Field.go index 77cd00e..aae4cd7 100644 --- a/src/types/Field.go +++ b/src/types/Field.go @@ -6,6 +6,7 @@ import "git.urbach.dev/cli/q/src/token" type Field struct { Type Type Name string + Tokens token.List Position token.Position Index uint8 Offset uint8 diff --git a/src/types/Struct.go b/src/types/Struct.go index bedacb0..1a6bc13 100644 --- a/src/types/Struct.go +++ b/src/types/Struct.go @@ -1,7 +1,10 @@ package types +import "git.urbach.dev/cli/q/src/fs" + // Struct is a structure in memory whose regions are addressable with named fields. type Struct struct { + File *fs.File Package string UniqueName string name string @@ -9,8 +12,9 @@ type Struct struct { } // NewStruct creates a new struct. -func NewStruct(pkg string, name string) *Struct { +func NewStruct(file *fs.File, pkg string, name string) *Struct { return &Struct{ + File: file, Package: pkg, UniqueName: pkg + "." + name, name: name, diff --git a/src/types/Struct_test.go b/src/types/Struct_test.go index 9e927f8..9ed141c 100644 --- a/src/types/Struct_test.go +++ b/src/types/Struct_test.go @@ -8,7 +8,7 @@ import ( ) func TestStruct(t *testing.T) { - point := types.NewStruct("math", "Point") + point := types.NewStruct(nil, "math", "Point") x := &types.Field{ Name: "x", diff --git a/tests/point.q b/tests/point.q new file mode 100644 index 0000000..1e11600 --- /dev/null +++ b/tests/point.q @@ -0,0 +1,6 @@ +Point { + x int + y int +} + +main() {} \ No newline at end of file diff --git a/tests/tests_test.go b/tests/tests_test.go index 4bfef1b..ddcfa2b 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -52,6 +52,7 @@ var tests = []run{ {"loop-keepalive", "", "", 0}, {"memory", "", "Hello\n", 0}, {"out-of-memory", "", "", 1}, + {"point", "", "", 0}, {"hello", "", "Hello\nHello\nHello\n", 0}, {"script", "", "Hello\n", 0}, }