q/src/scanner/scanFunction.go

201 lines
3.9 KiB
Go

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 = i
paramsStart = -1
paramsEnd = -1
bodyStart = -1
typeStart = -1
typeEnd = -1
)
i++
// 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
}