Implemented extern functions

This commit is contained in:
Eduard Urbach 2025-02-12 00:04:30 +01:00
parent 1083db6ab2
commit 3b66dae1d4
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
24 changed files with 304 additions and 169 deletions

View File

@ -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!"

View File

@ -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))
} }

View File

@ -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 {

View File

@ -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 {

View File

@ -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
} }

View File

@ -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)
} }

View File

@ -1,3 +1,7 @@
extern kernel32 {
ExitProcess(code UInt)
}
exit(code Int) { exit(code Int) {
kernel32.ExitProcess(code) kernel32.ExitProcess(code)
} }

View File

@ -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
View 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
}

View File

@ -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 {
return nil, err
}
} }
f.DLLs = f.DLLs.Append(pkg, name) if pkg != f.File.Package && !fn.IsExtern() {
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" {
continue
} }
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) registers := f.CPU.Input[:len(parameters)]
} err := f.BeforeCall(fn, parameters, registers)
if err != nil {
return nil, err
} }
f.CallSafe(fn, registers) f.CallSafe(fn, registers)

6
src/core/IsExtern.go Normal file
View 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
}

View File

@ -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)

View File

@ -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
View 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
}

View File

@ -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:

View File

@ -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
nameStart = i
paramsStart = -1
paramsEnd = -1
bodyStart = -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

View 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
}

View File

@ -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

View File

@ -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,

View File

@ -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":

View File

@ -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]

View File

@ -13,6 +13,8 @@ var (
) )
var ( var (
Bool = Int
Int = Int64 Int = Int64
Float = Float64 Float = Float64
UInt = Int
) )

View File

@ -0,0 +1 @@
extern {}

View File

@ -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},