Implemented struct parser
This commit is contained in:
parent
fc1b970f7f
commit
51e3c1ba0e
4
lib/time/timespec.q
Normal file
4
lib/time/timespec.q
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
struct timespec {
|
||||||
|
seconds Int
|
||||||
|
nanoseconds Int
|
||||||
|
}
|
@ -23,8 +23,8 @@ func New(files ...string) *Build {
|
|||||||
|
|
||||||
// Run compiles the input files.
|
// Run compiles the input files.
|
||||||
func (build *Build) Run() (compiler.Result, error) {
|
func (build *Build) Run() (compiler.Result, error) {
|
||||||
files, functions, errors := scanner.Scan(build.Files)
|
files, functions, types, errors := scanner.Scan(build.Files)
|
||||||
return compiler.Compile(files, functions, errors)
|
return compiler.Compile(files, functions, types, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executable returns the path to the executable.
|
// Executable returns the path to the executable.
|
||||||
|
@ -6,13 +6,15 @@ import (
|
|||||||
"git.akyoto.dev/cli/q/src/core"
|
"git.akyoto.dev/cli/q/src/core"
|
||||||
"git.akyoto.dev/cli/q/src/errors"
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
"git.akyoto.dev/cli/q/src/fs"
|
"git.akyoto.dev/cli/q/src/fs"
|
||||||
|
"git.akyoto.dev/cli/q/src/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Compile waits for the scan to finish and compiles all functions.
|
// Compile waits for the scan to finish and compiles all functions.
|
||||||
func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan error) (Result, error) {
|
func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Type, errs <-chan error) (Result, error) {
|
||||||
result := Result{}
|
result := Result{}
|
||||||
allFiles := make([]*fs.File, 0, 8)
|
allFiles := make([]*fs.File, 0, 8)
|
||||||
allFunctions := map[string]*core.Function{}
|
allFunctions := map[string]*core.Function{}
|
||||||
|
allTypes := map[string]*types.Type{}
|
||||||
|
|
||||||
for functions != nil || files != nil || errs != nil {
|
for functions != nil || files != nil || errs != nil {
|
||||||
select {
|
select {
|
||||||
@ -25,6 +27,14 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan
|
|||||||
function.Functions = allFunctions
|
function.Functions = allFunctions
|
||||||
allFunctions[function.UniqueName] = function
|
allFunctions[function.UniqueName] = function
|
||||||
|
|
||||||
|
case typ, ok := <-structs:
|
||||||
|
if !ok {
|
||||||
|
structs = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
allTypes[typ.Name] = typ
|
||||||
|
|
||||||
case file, ok := <-files:
|
case file, ok := <-files:
|
||||||
if !ok {
|
if !ok {
|
||||||
files = nil
|
files = nil
|
||||||
|
@ -6,6 +6,7 @@ var (
|
|||||||
ExpectedFunctionParameters = &Base{"Expected function parameters"}
|
ExpectedFunctionParameters = &Base{"Expected function parameters"}
|
||||||
ExpectedFunctionDefinition = &Base{"Expected function definition"}
|
ExpectedFunctionDefinition = &Base{"Expected function definition"}
|
||||||
ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"}
|
ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"}
|
||||||
|
ExpectedStructName = &Base{"Expected struct name"}
|
||||||
InvalidNumber = &Base{"Invalid number"}
|
InvalidNumber = &Base{"Invalid number"}
|
||||||
InvalidExpression = &Base{"Invalid expression"}
|
InvalidExpression = &Base{"Invalid expression"}
|
||||||
InvalidRune = &Base{"Invalid rune"}
|
InvalidRune = &Base{"Invalid rune"}
|
||||||
|
@ -3,13 +3,15 @@ package scanner
|
|||||||
import (
|
import (
|
||||||
"git.akyoto.dev/cli/q/src/core"
|
"git.akyoto.dev/cli/q/src/core"
|
||||||
"git.akyoto.dev/cli/q/src/fs"
|
"git.akyoto.dev/cli/q/src/fs"
|
||||||
|
"git.akyoto.dev/cli/q/src/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scan scans the list of files.
|
// Scan scans the list of files.
|
||||||
func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan error) {
|
func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types.Type, <-chan error) {
|
||||||
scanner := Scanner{
|
scanner := Scanner{
|
||||||
files: make(chan *fs.File),
|
files: make(chan *fs.File),
|
||||||
functions: make(chan *core.Function),
|
functions: make(chan *core.Function),
|
||||||
|
types: make(chan *types.Type),
|
||||||
errors: make(chan error),
|
errors: make(chan error),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,8 +20,9 @@ func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan error)
|
|||||||
scanner.group.Wait()
|
scanner.group.Wait()
|
||||||
close(scanner.files)
|
close(scanner.files)
|
||||||
close(scanner.functions)
|
close(scanner.functions)
|
||||||
|
close(scanner.types)
|
||||||
close(scanner.errors)
|
close(scanner.errors)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return scanner.files, scanner.functions, scanner.errors
|
return scanner.files, scanner.functions, scanner.types, scanner.errors
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,14 @@ import (
|
|||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/core"
|
"git.akyoto.dev/cli/q/src/core"
|
||||||
"git.akyoto.dev/cli/q/src/fs"
|
"git.akyoto.dev/cli/q/src/fs"
|
||||||
|
"git.akyoto.dev/cli/q/src/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scanner is used to scan files before the actual compilation step.
|
// Scanner is used to scan files before the actual compilation step.
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
files chan *fs.File
|
files chan *fs.File
|
||||||
functions chan *core.Function
|
functions chan *core.Function
|
||||||
|
types chan *types.Type
|
||||||
errors chan error
|
errors chan error
|
||||||
queued sync.Map
|
queued sync.Map
|
||||||
group sync.WaitGroup
|
group sync.WaitGroup
|
||||||
|
@ -2,16 +2,10 @@ package scanner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/config"
|
|
||||||
"git.akyoto.dev/cli/q/src/core"
|
|
||||||
"git.akyoto.dev/cli/q/src/errors"
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
"git.akyoto.dev/cli/q/src/fs"
|
"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/token"
|
||||||
"git.akyoto.dev/cli/q/src/types"
|
|
||||||
"git.akyoto.dev/cli/q/src/x64"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// scanFile scans a single file.
|
// scanFile scans a single file.
|
||||||
@ -32,250 +26,31 @@ func (s *Scanner) scanFile(path string, pkg string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.files <- file
|
s.files <- file
|
||||||
|
i := 0
|
||||||
|
|
||||||
var (
|
for i < len(tokens) {
|
||||||
i = 0
|
switch tokens[i].Kind {
|
||||||
groupLevel = 0
|
case token.NewLine:
|
||||||
blockLevel = 0
|
case token.Import:
|
||||||
nameStart = -1
|
i, err = s.scanImport(file, tokens, i)
|
||||||
paramsStart = -1
|
case token.Struct:
|
||||||
paramsEnd = -1
|
i, err = s.scanStruct(file, tokens, i)
|
||||||
bodyStart = -1
|
case token.Identifier:
|
||||||
typeStart = -1
|
i, err = s.scanFunction(file, tokens, i)
|
||||||
typeEnd = -1
|
case token.EOF:
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
for i < len(tokens) && tokens[i].Kind == token.Import {
|
|
||||||
i++
|
|
||||||
|
|
||||||
if tokens[i].Kind != token.Identifier {
|
|
||||||
panic("expected package name")
|
|
||||||
}
|
|
||||||
|
|
||||||
packageName := tokens[i].Text(contents)
|
|
||||||
|
|
||||||
if file.Imports == nil {
|
|
||||||
file.Imports = map[string]*fs.Import{}
|
|
||||||
}
|
|
||||||
|
|
||||||
fullPath := filepath.Join(config.Library, packageName)
|
|
||||||
|
|
||||||
file.Imports[packageName] = &fs.Import{
|
|
||||||
Path: packageName,
|
|
||||||
FullPath: fullPath,
|
|
||||||
Position: tokens[i].Position,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.queueDirectory(fullPath, packageName)
|
|
||||||
i++
|
|
||||||
|
|
||||||
if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF {
|
|
||||||
panic("expected newline or eof")
|
|
||||||
}
|
|
||||||
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function name
|
|
||||||
for i < len(tokens) {
|
|
||||||
if tokens[i].Kind == token.Identifier {
|
|
||||||
nameStart = i
|
|
||||||
i++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment {
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[i].Kind == token.Invalid {
|
|
||||||
return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[i].Kind == token.EOF {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function parameters
|
|
||||||
for i < len(tokens) {
|
|
||||||
if tokens[i].Kind == token.GroupStart {
|
|
||||||
groupLevel++
|
|
||||||
i++
|
|
||||||
|
|
||||||
if groupLevel == 1 {
|
|
||||||
paramsStart = i
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[i].Kind == token.GroupEnd {
|
|
||||||
groupLevel--
|
|
||||||
|
|
||||||
if groupLevel < 0 {
|
|
||||||
return errors.New(errors.MissingGroupStart, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
if groupLevel == 0 {
|
|
||||||
paramsEnd = i
|
|
||||||
i++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[i].Kind == token.Invalid {
|
|
||||||
return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[i].Kind == token.EOF {
|
|
||||||
if groupLevel > 0 {
|
|
||||||
return errors.New(errors.MissingGroupEnd, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
if paramsStart == -1 {
|
|
||||||
return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if groupLevel > 0 {
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
for i < len(tokens) {
|
|
||||||
if tokens[i].Kind == token.ReturnType {
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[i].Kind == token.BlockStart {
|
|
||||||
blockLevel++
|
|
||||||
i++
|
|
||||||
|
|
||||||
if blockLevel == 1 {
|
|
||||||
bodyStart = i
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[i].Kind == token.BlockEnd {
|
|
||||||
blockLevel--
|
|
||||||
|
|
||||||
if blockLevel < 0 {
|
|
||||||
return errors.New(errors.MissingBlockStart, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
if blockLevel == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[i].Kind == token.Invalid {
|
|
||||||
return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[i].Kind == token.EOF {
|
|
||||||
if blockLevel > 0 {
|
|
||||||
return errors.New(errors.MissingBlockEnd, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bodyStart == -1 {
|
|
||||||
return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if blockLevel > 0 {
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
name := tokens[nameStart].Text(contents)
|
|
||||||
body := tokens[bodyStart:i]
|
|
||||||
function := core.NewFunction(pkg, name, file, body)
|
|
||||||
|
|
||||||
if typeStart != -1 {
|
|
||||||
if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd {
|
|
||||||
typeStart++
|
|
||||||
typeEnd--
|
|
||||||
}
|
|
||||||
|
|
||||||
function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters := tokens[paramsStart:paramsEnd]
|
|
||||||
count := 0
|
|
||||||
|
|
||||||
err := parameters.Split(func(tokens token.List) error {
|
|
||||||
if len(tokens) < 2 {
|
|
||||||
return errors.New(errors.MissingType, file, tokens[0].End())
|
|
||||||
}
|
|
||||||
|
|
||||||
name := tokens[0].Text(contents)
|
|
||||||
dataType := types.Parse(tokens[1:].Text(contents))
|
|
||||||
register := x64.InputRegisters[count]
|
|
||||||
uses := token.Count(function.Body, contents, 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++
|
|
||||||
return nil
|
return nil
|
||||||
})
|
case token.Invalid:
|
||||||
|
return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position)
|
||||||
|
default:
|
||||||
|
return errors.New(&errors.InvalidInstruction{Instruction: tokens[i].Text(file.Bytes)}, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.functions <- function
|
|
||||||
nameStart = -1
|
|
||||||
paramsStart = -1
|
|
||||||
bodyStart = -1
|
|
||||||
typeStart = -1
|
|
||||||
typeEnd = -1
|
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
222
src/scanner/scanFunction.go
Normal file
222
src/scanner/scanFunction.go
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package scanner
|
||||||
|
|
||||||
|
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.
|
||||||
|
func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, error) {
|
||||||
|
var (
|
||||||
|
groupLevel = 0
|
||||||
|
blockLevel = 0
|
||||||
|
nameStart = -1
|
||||||
|
paramsStart = -1
|
||||||
|
paramsEnd = -1
|
||||||
|
bodyStart = -1
|
||||||
|
typeStart = -1
|
||||||
|
typeEnd = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Function name
|
||||||
|
for i < len(tokens) {
|
||||||
|
if tokens[i].Kind == token.Identifier {
|
||||||
|
nameStart = i
|
||||||
|
i++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.Invalid {
|
||||||
|
return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.EOF {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, errors.New(errors.ExpectedFunctionName, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function parameters
|
||||||
|
for i < len(tokens) {
|
||||||
|
if tokens[i].Kind == token.GroupStart {
|
||||||
|
groupLevel++
|
||||||
|
i++
|
||||||
|
|
||||||
|
if groupLevel == 1 {
|
||||||
|
paramsStart = i
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.GroupEnd {
|
||||||
|
groupLevel--
|
||||||
|
|
||||||
|
if groupLevel < 0 {
|
||||||
|
return i, errors.New(errors.MissingGroupStart, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupLevel == 0 {
|
||||||
|
paramsEnd = i
|
||||||
|
i++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.Invalid {
|
||||||
|
return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.EOF {
|
||||||
|
if groupLevel > 0 {
|
||||||
|
return i, errors.New(errors.MissingGroupEnd, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if paramsStart == -1 {
|
||||||
|
return i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupLevel > 0 {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
for i < len(tokens) {
|
||||||
|
if tokens[i].Kind == token.ReturnType {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.BlockStart {
|
||||||
|
blockLevel++
|
||||||
|
i++
|
||||||
|
|
||||||
|
if blockLevel == 1 {
|
||||||
|
bodyStart = i
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.BlockEnd {
|
||||||
|
blockLevel--
|
||||||
|
|
||||||
|
if blockLevel < 0 {
|
||||||
|
return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockLevel == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.Invalid {
|
||||||
|
return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.EOF {
|
||||||
|
if blockLevel > 0 {
|
||||||
|
return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bodyStart == -1 {
|
||||||
|
return i, errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockLevel > 0 {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := tokens[nameStart].Text(file.Bytes)
|
||||||
|
body := tokens[bodyStart:i]
|
||||||
|
function := core.NewFunction(file.Package, name, file, body)
|
||||||
|
|
||||||
|
if typeStart != -1 {
|
||||||
|
if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd {
|
||||||
|
typeStart++
|
||||||
|
typeEnd--
|
||||||
|
}
|
||||||
|
|
||||||
|
function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], file.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters := tokens[paramsStart:paramsEnd]
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
err := parameters.Split(func(tokens token.List) error {
|
||||||
|
if len(tokens) < 2 {
|
||||||
|
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++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.functions <- function
|
||||||
|
i++
|
||||||
|
return i, nil
|
||||||
|
}
|
41
src/scanner/scanImport.go
Normal file
41
src/scanner/scanImport.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.akyoto.dev/cli/q/src/config"
|
||||||
|
"git.akyoto.dev/cli/q/src/fs"
|
||||||
|
"git.akyoto.dev/cli/q/src/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// scanImport scans an import.
|
||||||
|
func (s *Scanner) scanImport(file *fs.File, tokens token.List, i int) (int, error) {
|
||||||
|
i++
|
||||||
|
|
||||||
|
if tokens[i].Kind != token.Identifier {
|
||||||
|
panic("expected package name")
|
||||||
|
}
|
||||||
|
|
||||||
|
packageName := tokens[i].Text(file.Bytes)
|
||||||
|
|
||||||
|
if file.Imports == nil {
|
||||||
|
file.Imports = map[string]*fs.Import{}
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := filepath.Join(config.Library, packageName)
|
||||||
|
|
||||||
|
file.Imports[packageName] = &fs.Import{
|
||||||
|
Path: packageName,
|
||||||
|
FullPath: fullPath,
|
||||||
|
Position: tokens[i].Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.queueDirectory(fullPath, packageName)
|
||||||
|
i++
|
||||||
|
|
||||||
|
if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF {
|
||||||
|
panic("expected newline or eof")
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
66
src/scanner/scanStruct.go
Normal file
66
src/scanner/scanStruct.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
|
"git.akyoto.dev/cli/q/src/fs"
|
||||||
|
"git.akyoto.dev/cli/q/src/token"
|
||||||
|
"git.akyoto.dev/cli/q/src/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// scanStruct scans a struct.
|
||||||
|
func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, error) {
|
||||||
|
i++
|
||||||
|
|
||||||
|
if tokens[i].Kind != token.Identifier {
|
||||||
|
return i, errors.New(errors.ExpectedStructName, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
structName := tokens[i].Text(file.Bytes)
|
||||||
|
|
||||||
|
typ := &types.Type{
|
||||||
|
Name: structName,
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
|
||||||
|
if tokens[i].Kind != token.BlockStart {
|
||||||
|
return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
closed := false
|
||||||
|
|
||||||
|
for i < len(tokens) {
|
||||||
|
if tokens[i].Kind == token.Identifier {
|
||||||
|
fieldPosition := i
|
||||||
|
fieldName := tokens[i].Text(file.Bytes)
|
||||||
|
i++
|
||||||
|
fieldTypeName := tokens[i].Text(file.Bytes)
|
||||||
|
fieldType := types.Parse(fieldTypeName)
|
||||||
|
i++
|
||||||
|
|
||||||
|
typ.Fields = append(typ.Fields, &types.Field{
|
||||||
|
Type: fieldType,
|
||||||
|
Name: fieldName,
|
||||||
|
Position: token.Position(fieldPosition),
|
||||||
|
Offset: typ.Size,
|
||||||
|
})
|
||||||
|
|
||||||
|
typ.Size += fieldType.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.BlockEnd {
|
||||||
|
closed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if !closed {
|
||||||
|
return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.types <- typ
|
||||||
|
return i, nil
|
||||||
|
}
|
@ -70,6 +70,7 @@ const (
|
|||||||
Import // import
|
Import // import
|
||||||
Loop // loop
|
Loop // loop
|
||||||
Return // return
|
Return // return
|
||||||
|
Struct // struct
|
||||||
Switch // switch
|
Switch // switch
|
||||||
___END_KEYWORDS___ // </keywords>
|
___END_KEYWORDS___ // </keywords>
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,7 @@ func TestFunction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyword(t *testing.T) {
|
func TestKeyword(t *testing.T) {
|
||||||
tokens := token.Tokenize([]byte("assert if import else loop return switch"))
|
tokens := token.Tokenize([]byte("assert if import else loop return struct switch"))
|
||||||
|
|
||||||
expected := []token.Kind{
|
expected := []token.Kind{
|
||||||
token.Assert,
|
token.Assert,
|
||||||
@ -34,6 +34,7 @@ func TestKeyword(t *testing.T) {
|
|||||||
token.Else,
|
token.Else,
|
||||||
token.Loop,
|
token.Loop,
|
||||||
token.Return,
|
token.Return,
|
||||||
|
token.Struct,
|
||||||
token.Switch,
|
token.Switch,
|
||||||
token.EOF,
|
token.EOF,
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) {
|
|||||||
kind = Loop
|
kind = Loop
|
||||||
case "return":
|
case "return":
|
||||||
kind = Return
|
kind = Return
|
||||||
|
case "struct":
|
||||||
|
kind = Struct
|
||||||
case "switch":
|
case "switch":
|
||||||
kind = Switch
|
kind = Switch
|
||||||
}
|
}
|
||||||
|
1
tests/errors/ExpectedStructName.q
Normal file
1
tests/errors/ExpectedStructName.q
Normal file
@ -0,0 +1 @@
|
|||||||
|
struct{}
|
3
tests/errors/InvalidInstructionString.q
Normal file
3
tests/errors/InvalidInstructionString.q
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
main() {
|
||||||
|
"Hello"
|
||||||
|
}
|
1
tests/errors/InvalidInstructionTopLevel3.q
Normal file
1
tests/errors/InvalidInstructionTopLevel3.q
Normal file
@ -0,0 +1 @@
|
|||||||
|
+
|
@ -16,14 +16,17 @@ var errs = []struct {
|
|||||||
}{
|
}{
|
||||||
{"EmptySwitch.q", errors.EmptySwitch},
|
{"EmptySwitch.q", errors.EmptySwitch},
|
||||||
{"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition},
|
{"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition},
|
||||||
{"ExpectedFunctionName.q", errors.ExpectedFunctionName},
|
|
||||||
{"ExpectedFunctionName2.q", errors.ExpectedFunctionName},
|
|
||||||
{"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters},
|
{"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters},
|
||||||
{"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse},
|
{"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse},
|
||||||
{"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse},
|
{"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse},
|
||||||
|
{"ExpectedStructName.q", errors.ExpectedStructName},
|
||||||
|
{"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}},
|
||||||
{"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}},
|
{"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}},
|
||||||
{"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}},
|
{"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}},
|
||||||
{"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}},
|
{"InvalidInstructionString.q", &errors.InvalidInstruction{Instruction: "\"Hello\""}},
|
||||||
|
{"InvalidInstructionTopLevel.q", &errors.InvalidInstruction{Instruction: "123"}},
|
||||||
|
{"InvalidInstructionTopLevel2.q", &errors.InvalidInstruction{Instruction: "\"Hello\""}},
|
||||||
|
{"InvalidInstructionTopLevel3.q", &errors.InvalidInstruction{Instruction: "+"}},
|
||||||
{"InvalidExpression.q", errors.InvalidExpression},
|
{"InvalidExpression.q", errors.InvalidExpression},
|
||||||
{"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}},
|
{"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}},
|
||||||
{"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}},
|
{"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user