Implemented extern functions
This commit is contained in:
parent
1083db6ab2
commit
3b66dae1d4
@ -1,3 +1,7 @@
|
|||||||
|
extern user32 {
|
||||||
|
MessageBoxA(window *Any, text *Int8, title *Int8, type UInt)
|
||||||
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
title := "Title."
|
title := "Title."
|
||||||
text := "Hi!"
|
text := "Hi!"
|
||||||
|
12
lib/io/io.q
12
lib/io/io.q
@ -4,18 +4,18 @@ in(buffer []Int8) -> Int {
|
|||||||
return sys.read(0, buffer, len(buffer))
|
return sys.read(0, buffer, len(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
out(message []Int8) -> Int {
|
out(buffer []Int8) -> Int {
|
||||||
return sys.write(1, message, len(message))
|
return sys.write(1, buffer, len(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message []Int8) -> Int {
|
error(buffer []Int8) -> Int {
|
||||||
return sys.write(2, message, len(message))
|
return sys.write(2, buffer, len(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
read(fd Int, buffer []Int8) -> Int {
|
read(fd Int, buffer []Int8) -> Int {
|
||||||
return sys.read(fd, buffer, len(buffer))
|
return sys.read(fd, buffer, len(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
write(fd Int, message []Int8) -> Int {
|
write(fd Int, buffer []Int8) -> Int {
|
||||||
return sys.write(fd, message, len(message))
|
return sys.write(fd, buffer, len(buffer))
|
||||||
}
|
}
|
@ -1,9 +1,9 @@
|
|||||||
read(fd Int, address *Any, length Int) -> Int {
|
read(fd Int, buffer *Any, length Int) -> Int {
|
||||||
return syscall(0, fd, address, length)
|
return syscall(0, fd, buffer, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
write(fd Int, address *Any, length Int) -> Int {
|
write(fd Int, buffer *Any, length Int) -> Int {
|
||||||
return syscall(1, fd, address, length)
|
return syscall(1, fd, buffer, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
open(path *Any, flags Int, mode Int) -> Int {
|
open(path *Any, flags Int, mode Int) -> Int {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
read(fd Int, address *Any, length Int) -> Int {
|
read(fd Int, buffer *Any, length Int) -> Int {
|
||||||
return syscall(0x2000003, fd, address, length)
|
return syscall(0x2000003, fd, buffer, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
write(fd Int, address *Any, length Int) -> Int {
|
write(fd Int, buffer *Any, length Int) -> Int {
|
||||||
return syscall(0x2000004, fd, address, length)
|
return syscall(0x2000004, fd, buffer, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
open(path *Any, flags Int, mode Int) -> Int {
|
open(path *Any, flags Int, mode Int) -> Int {
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
read(fd Int, address *Any, length Int) -> Int {
|
extern kernel32 {
|
||||||
kernel32.ReadFile(fd, address, length)
|
GetStdHandle(handle Int) -> Int
|
||||||
|
WriteConsoleA(fd Int, buffer *Any, length Int, written *Int) -> Bool
|
||||||
|
ReadFile(fd Int, buffer *Any, length Int) -> Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
read(fd Int, buffer *Any, length Int) -> Int {
|
||||||
|
kernel32.ReadFile(fd, buffer, length)
|
||||||
return length
|
return length
|
||||||
}
|
}
|
||||||
|
|
||||||
write(fd Int, address *Any, length Int) -> Int {
|
write(fd Int, buffer *Any, length Int) -> Int {
|
||||||
fd = kernel32.GetStdHandle(-10 - fd)
|
fd = kernel32.GetStdHandle(-10 - fd)
|
||||||
kernel32.WriteConsoleA(fd, address, length, 0)
|
kernel32.WriteConsoleA(fd, buffer, length, 0)
|
||||||
return length
|
return length
|
||||||
}
|
}
|
@ -1,3 +1,8 @@
|
|||||||
|
extern kernel32 {
|
||||||
|
VirtualAlloc(address Int, length Int, flags Int, protection Int)
|
||||||
|
VirtualFree(address *Any, length Int, type Int) -> Bool
|
||||||
|
}
|
||||||
|
|
||||||
mmap(address Int, length Int, protection Int, flags Int) -> *Any {
|
mmap(address Int, length Int, protection Int, flags Int) -> *Any {
|
||||||
return kernel32.VirtualAlloc(address, length, flags, protection)
|
return kernel32.VirtualAlloc(address, length, flags, protection)
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
extern kernel32 {
|
||||||
|
ExitProcess(code UInt)
|
||||||
|
}
|
||||||
|
|
||||||
exit(code Int) {
|
exit(code Int) {
|
||||||
kernel32.ExitProcess(code)
|
kernel32.ExitProcess(code)
|
||||||
}
|
}
|
@ -108,6 +108,10 @@ func CompileFunctions(functions map[string]*core.Function) {
|
|||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
for _, function := range functions {
|
for _, function := range functions {
|
||||||
|
if function.IsExtern() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
32
src/core/BeforeCall.go
Normal file
32
src/core/BeforeCall.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.akyoto.dev/cli/q/src/cpu"
|
||||||
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
|
"git.akyoto.dev/cli/q/src/expression"
|
||||||
|
"git.akyoto.dev/cli/q/src/token"
|
||||||
|
"git.akyoto.dev/cli/q/src/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BeforeCall loads the registers with the parameter values and checks that the types match with the function signature.
|
||||||
|
func (f *Function) BeforeCall(fn *Function, parameters []*expression.Expression, registers []cpu.Register) error {
|
||||||
|
for i := len(parameters) - 1; i >= 0; i-- {
|
||||||
|
typ, err := f.ExpressionToRegister(parameters[i], registers[i])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !types.Is(typ, fn.Input[i].Type) {
|
||||||
|
_, expectsPointer := fn.Input[i].Type.(*types.Pointer)
|
||||||
|
|
||||||
|
if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), ParameterName: fn.Input[i].Name}, f.File, parameters[i].Token.Position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/errors"
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
"git.akyoto.dev/cli/q/src/expression"
|
"git.akyoto.dev/cli/q/src/expression"
|
||||||
"git.akyoto.dev/cli/q/src/token"
|
|
||||||
"git.akyoto.dev/cli/q/src/types"
|
"git.akyoto.dev/cli/q/src/types"
|
||||||
"git.akyoto.dev/cli/q/src/x86"
|
"git.akyoto.dev/cli/q/src/x86"
|
||||||
)
|
)
|
||||||
@ -48,22 +47,13 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error
|
|||||||
name = nameNode.Token.Text(f.File.Bytes)
|
name = nameNode.Token.Text(f.File.Bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" {
|
fn, exists = f.Functions[pkg+"."+name]
|
||||||
parameters := root.Children[1:]
|
|
||||||
registers := x86.WindowsInputRegisters[:len(parameters)]
|
|
||||||
|
|
||||||
for i := len(parameters) - 1; i >= 0; i-- {
|
if !exists {
|
||||||
_, err := f.ExpressionToRegister(parameters[i], registers[i])
|
return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if pkg != f.File.Package && !fn.IsExtern() {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.DLLs = f.DLLs.Append(pkg, name)
|
|
||||||
f.DLLCall(fmt.Sprintf("%s.%s", pkg, name))
|
|
||||||
return nil, nil
|
|
||||||
} else if pkg != f.File.Package {
|
|
||||||
if f.File.Imports == nil {
|
if f.File.Imports == nil {
|
||||||
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position)
|
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position)
|
||||||
}
|
}
|
||||||
@ -77,36 +67,30 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error
|
|||||||
imp.Used = true
|
imp.Used = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn, exists = f.Functions[pkg+"."+name]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters := root.Children[1:]
|
parameters := root.Children[1:]
|
||||||
|
|
||||||
if len(parameters) != len(fn.Input) {
|
if len(parameters) != len(fn.Input) {
|
||||||
return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, nameNode.Token.End())
|
return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, nameNode.Token.End())
|
||||||
}
|
}
|
||||||
|
|
||||||
registers := f.CPU.Input[:len(parameters)]
|
if fn.IsExtern() {
|
||||||
|
f.DLLs = f.DLLs.Append(pkg, name)
|
||||||
for i := len(parameters) - 1; i >= 0; i-- {
|
registers := x86.WindowsInputRegisters[:len(parameters)]
|
||||||
typ, err := f.ExpressionToRegister(parameters[i], registers[i])
|
err := f.BeforeCall(fn, parameters, registers)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !types.Is(typ, fn.Input[i].Type) {
|
f.DLLCall(fmt.Sprintf("%s.%s", pkg, name))
|
||||||
_, expectsPointer := fn.Input[i].Type.(*types.Pointer)
|
return fn.OutputTypes, nil
|
||||||
|
}
|
||||||
|
|
||||||
if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" {
|
registers := f.CPU.Input[:len(parameters)]
|
||||||
continue
|
err := f.BeforeCall(fn, parameters, registers)
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), ParameterName: fn.Input[i].Name}, f.File, parameters[i].Token.Position)
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
f.CallSafe(fn, registers)
|
f.CallSafe(fn, registers)
|
||||||
|
6
src/core/IsExtern.go
Normal file
6
src/core/IsExtern.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
// IsExtern returns true if the function has no body.
|
||||||
|
func (f *Function) IsExtern() bool {
|
||||||
|
return f.Body == nil
|
||||||
|
}
|
@ -19,6 +19,10 @@ func (f *Function) ResolveTypes() error {
|
|||||||
return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position)
|
return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.IsExtern() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.Name)
|
uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.Name)
|
||||||
|
|
||||||
if uses == 0 && param.Name != "_" {
|
if uses == 0 && param.Name != "_" {
|
||||||
@ -38,7 +42,7 @@ func (f *Function) ResolveTypes() error {
|
|||||||
param.Type = types.ByName(typeName, f.Package, f.Structs)
|
param.Type = types.ByName(typeName, f.Package, f.Structs)
|
||||||
|
|
||||||
if param.Type == nil {
|
if param.Type == nil {
|
||||||
return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position)
|
return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[0].Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.OutputTypes = append(f.OutputTypes, param.Type)
|
f.OutputTypes = append(f.OutputTypes, param.Type)
|
||||||
|
@ -9,6 +9,7 @@ var (
|
|||||||
ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"}
|
ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"}
|
||||||
ExpectedPackageName = &Base{"Expected package name"}
|
ExpectedPackageName = &Base{"Expected package name"}
|
||||||
ExpectedStructName = &Base{"Expected struct name"}
|
ExpectedStructName = &Base{"Expected struct name"}
|
||||||
|
ExpectedDLLName = &Base{"Expected DLL 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"}
|
||||||
|
54
src/scanner/scanExtern.go
Normal file
54
src/scanner/scanExtern.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
|
"git.akyoto.dev/cli/q/src/fs"
|
||||||
|
"git.akyoto.dev/cli/q/src/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// scanExtern scans a block of external function declarations.
|
||||||
|
func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, error) {
|
||||||
|
i++
|
||||||
|
|
||||||
|
if tokens[i].Kind != token.Identifier {
|
||||||
|
return i, errors.New(errors.ExpectedDLLName, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
dllName := tokens[i].Text(file.Bytes)
|
||||||
|
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 {
|
||||||
|
function, j, err := scanFunctionSignature(file, tokens, i, token.NewLine)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return j, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i = j
|
||||||
|
function.Package = dllName
|
||||||
|
function.UniqueName = dllName + "." + function.Name
|
||||||
|
s.functions <- function
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.BlockEnd {
|
||||||
|
closed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if !closed {
|
||||||
|
return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
@ -37,6 +37,8 @@ func (s *Scanner) scanFile(path string, pkg string) error {
|
|||||||
i, err = s.scanStruct(file, tokens, i)
|
i, err = s.scanStruct(file, tokens, i)
|
||||||
case token.Identifier:
|
case token.Identifier:
|
||||||
i, err = s.scanFunction(file, tokens, i)
|
i, err = s.scanFunction(file, tokens, i)
|
||||||
|
case token.Extern:
|
||||||
|
i, err = s.scanExtern(file, tokens, i)
|
||||||
case token.EOF:
|
case token.EOF:
|
||||||
return nil
|
return nil
|
||||||
case token.Invalid:
|
case token.Invalid:
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package scanner
|
package scanner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"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/token"
|
"git.akyoto.dev/cli/q/src/token"
|
||||||
@ -9,84 +8,17 @@ import (
|
|||||||
|
|
||||||
// scanFunction scans a function.
|
// scanFunction scans a function.
|
||||||
func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, error) {
|
func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, error) {
|
||||||
|
function, i, err := scanFunctionSignature(file, tokens, i, token.BlockStart)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
groupLevel = 0
|
blockLevel = 0
|
||||||
blockLevel = 0
|
bodyStart = -1
|
||||||
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
|
// Function definition
|
||||||
for i < len(tokens) {
|
for i < len(tokens) {
|
||||||
if tokens[i].Kind == token.ReturnType {
|
if tokens[i].Kind == token.ReturnType {
|
||||||
@ -144,47 +76,7 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er
|
|||||||
return i, errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position)
|
return i, errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
name := tokens[nameStart].Text(file.Bytes)
|
function.Body = tokens[bodyStart:i]
|
||||||
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--
|
|
||||||
}
|
|
||||||
|
|
||||||
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]
|
|
||||||
|
|
||||||
err := parameters.Split(func(tokens token.List) error {
|
|
||||||
if len(tokens) == 0 {
|
|
||||||
return errors.New(errors.MissingParameter, file, parameters[0].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tokens) == 1 {
|
|
||||||
return errors.New(errors.MissingType, file, tokens[0].End())
|
|
||||||
}
|
|
||||||
|
|
||||||
function.Input = append(function.Input, core.NewInput(tokens))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.functions <- function
|
s.functions <- function
|
||||||
i++
|
i++
|
||||||
return i, nil
|
return i, nil
|
||||||
|
125
src/scanner/scanFunctionSignature.go
Normal file
125
src/scanner/scanFunctionSignature.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
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/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// scanFunctionSignature scans a function declaration without the body.
|
||||||
|
func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter token.Kind) (*core.Function, int, error) {
|
||||||
|
var (
|
||||||
|
groupLevel = 0
|
||||||
|
nameStart = i
|
||||||
|
paramsStart = -1
|
||||||
|
paramsEnd = -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 nil, 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 nil, 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 nil, i, errors.New(errors.MissingGroupEnd, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if paramsStart == -1 {
|
||||||
|
return nil, i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupLevel > 0 {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 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 != delimiter {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
typeEnd = i
|
||||||
|
}
|
||||||
|
|
||||||
|
name := tokens[nameStart].Text(file.Bytes)
|
||||||
|
function := core.NewFunction(file.Package, name, file, nil)
|
||||||
|
|
||||||
|
if typeStart != -1 {
|
||||||
|
if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd {
|
||||||
|
typeStart++
|
||||||
|
typeEnd--
|
||||||
|
}
|
||||||
|
|
||||||
|
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 nil, i, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters := tokens[paramsStart:paramsEnd]
|
||||||
|
|
||||||
|
err := parameters.Split(func(tokens token.List) error {
|
||||||
|
if len(tokens) == 0 {
|
||||||
|
return errors.New(errors.MissingParameter, file, parameters[0].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tokens) == 1 {
|
||||||
|
return errors.New(errors.MissingType, file, tokens[0].End())
|
||||||
|
}
|
||||||
|
|
||||||
|
function.Input = append(function.Input, core.NewInput(tokens))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return function, i, err
|
||||||
|
}
|
@ -66,6 +66,7 @@ const (
|
|||||||
___KEYWORDS___ // <keywords>
|
___KEYWORDS___ // <keywords>
|
||||||
Assert // assert
|
Assert // assert
|
||||||
Else // else
|
Else // else
|
||||||
|
Extern // extern
|
||||||
If // if
|
If // if
|
||||||
Import // import
|
Import // import
|
||||||
Loop // loop
|
Loop // loop
|
||||||
|
@ -25,13 +25,14 @@ 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 struct switch"))
|
tokens := token.Tokenize([]byte("assert if import else extern loop return struct switch"))
|
||||||
|
|
||||||
expected := []token.Kind{
|
expected := []token.Kind{
|
||||||
token.Assert,
|
token.Assert,
|
||||||
token.If,
|
token.If,
|
||||||
token.Import,
|
token.Import,
|
||||||
token.Else,
|
token.Else,
|
||||||
|
token.Extern,
|
||||||
token.Loop,
|
token.Loop,
|
||||||
token.Return,
|
token.Return,
|
||||||
token.Struct,
|
token.Struct,
|
||||||
|
@ -19,6 +19,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) {
|
|||||||
kind = If
|
kind = If
|
||||||
case "else":
|
case "else":
|
||||||
kind = Else
|
kind = Else
|
||||||
|
case "extern":
|
||||||
|
kind = Extern
|
||||||
case "import":
|
case "import":
|
||||||
kind = Import
|
kind = Import
|
||||||
case "loop":
|
case "loop":
|
||||||
|
@ -31,6 +31,8 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type {
|
|||||||
switch name {
|
switch name {
|
||||||
case "Any":
|
case "Any":
|
||||||
return Any
|
return Any
|
||||||
|
case "Bool":
|
||||||
|
return Bool
|
||||||
case "Int":
|
case "Int":
|
||||||
return Int
|
return Int
|
||||||
case "Int64":
|
case "Int64":
|
||||||
@ -47,6 +49,8 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type {
|
|||||||
return Float64
|
return Float64
|
||||||
case "Float32":
|
case "Float32":
|
||||||
return Float32
|
return Float32
|
||||||
|
case "UInt":
|
||||||
|
return UInt
|
||||||
}
|
}
|
||||||
|
|
||||||
typ, exists := structs[pkg+"."+name]
|
typ, exists := structs[pkg+"."+name]
|
||||||
|
@ -13,6 +13,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
Bool = Int
|
||||||
Int = Int64
|
Int = Int64
|
||||||
Float = Float64
|
Float = Float64
|
||||||
|
UInt = Int
|
||||||
)
|
)
|
||||||
|
1
tests/errors/ExpectedDLLName.q
Normal file
1
tests/errors/ExpectedDLLName.q
Normal file
@ -0,0 +1 @@
|
|||||||
|
extern {}
|
@ -15,6 +15,7 @@ var errs = []struct {
|
|||||||
ExpectedError error
|
ExpectedError error
|
||||||
}{
|
}{
|
||||||
{"EmptySwitch.q", errors.EmptySwitch},
|
{"EmptySwitch.q", errors.EmptySwitch},
|
||||||
|
{"ExpectedDLLName.q", errors.ExpectedDLLName},
|
||||||
{"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition},
|
{"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition},
|
||||||
{"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters},
|
{"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters},
|
||||||
{"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse},
|
{"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user