diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index e4a670f..a3f1dd7 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,3 +1,7 @@ +extern user32 { + MessageBoxA(window *Any, text *Int8, title *Int8, type UInt) +} + main() { title := "Title." text := "Hi!" diff --git a/lib/io/io.q b/lib/io/io.q index 545c810..5c195a4 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -4,18 +4,18 @@ in(buffer []Int8) -> Int { return sys.read(0, buffer, len(buffer)) } -out(message []Int8) -> Int { - return sys.write(1, message, len(message)) +out(buffer []Int8) -> Int { + return sys.write(1, buffer, len(buffer)) } -error(message []Int8) -> Int { - return sys.write(2, message, len(message)) +error(buffer []Int8) -> Int { + return sys.write(2, buffer, len(buffer)) } read(fd Int, buffer []Int8) -> Int { return sys.read(fd, buffer, len(buffer)) } -write(fd Int, message []Int8) -> Int { - return sys.write(fd, message, len(message)) +write(fd Int, buffer []Int8) -> Int { + return sys.write(fd, buffer, len(buffer)) } \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q index 9a3b2c5..d204bb4 100644 --- a/lib/sys/io_linux.q +++ b/lib/sys/io_linux.q @@ -1,9 +1,9 @@ -read(fd Int, address *Any, length Int) -> Int { - return syscall(0, fd, address, length) +read(fd Int, buffer *Any, length Int) -> Int { + return syscall(0, fd, buffer, length) } -write(fd Int, address *Any, length Int) -> Int { - return syscall(1, fd, address, length) +write(fd Int, buffer *Any, length Int) -> Int { + return syscall(1, fd, buffer, length) } open(path *Any, flags Int, mode Int) -> Int { diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q index 4ef451a..8fc3dbd 100644 --- a/lib/sys/io_mac.q +++ b/lib/sys/io_mac.q @@ -1,9 +1,9 @@ -read(fd Int, address *Any, length Int) -> Int { - return syscall(0x2000003, fd, address, length) +read(fd Int, buffer *Any, length Int) -> Int { + return syscall(0x2000003, fd, buffer, length) } -write(fd Int, address *Any, length Int) -> Int { - return syscall(0x2000004, fd, address, length) +write(fd Int, buffer *Any, length Int) -> Int { + return syscall(0x2000004, fd, buffer, length) } open(path *Any, flags Int, mode Int) -> Int { diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index 964a201..81351c3 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -1,10 +1,16 @@ -read(fd Int, address *Any, length Int) -> Int { - kernel32.ReadFile(fd, address, length) +extern kernel32 { + 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 } -write(fd Int, address *Any, length Int) -> Int { +write(fd Int, buffer *Any, length Int) -> Int { fd = kernel32.GetStdHandle(-10 - fd) - kernel32.WriteConsoleA(fd, address, length, 0) + kernel32.WriteConsoleA(fd, buffer, length, 0) return length } \ No newline at end of file diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q index 319e966..79b093a 100644 --- a/lib/sys/mem_windows.q +++ b/lib/sys/mem_windows.q @@ -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 { return kernel32.VirtualAlloc(address, length, flags, protection) } diff --git a/lib/sys/proc_windows.q b/lib/sys/proc_windows.q index 7819bca..c5c4cdc 100644 --- a/lib/sys/proc_windows.q +++ b/lib/sys/proc_windows.q @@ -1,3 +1,7 @@ +extern kernel32 { + ExitProcess(code UInt) +} + exit(code Int) { kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 57ccb04..df5f2dc 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -108,6 +108,10 @@ func CompileFunctions(functions map[string]*core.Function) { wg := sync.WaitGroup{} for _, function := range functions { + if function.IsExtern() { + continue + } + wg.Add(1) go func() { diff --git a/src/core/BeforeCall.go b/src/core/BeforeCall.go new file mode 100644 index 0000000..57812f8 --- /dev/null +++ b/src/core/BeforeCall.go @@ -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 +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index a6e78c9..688673a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -5,7 +5,6 @@ import ( "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" "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) } - if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { - parameters := root.Children[1:] - registers := x86.WindowsInputRegisters[:len(parameters)] + fn, exists = f.Functions[pkg+"."+name] - for i := len(parameters) - 1; i >= 0; i-- { - _, err := f.ExpressionToRegister(parameters[i], registers[i]) + if !exists { + 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) - f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) - return nil, nil - } else if pkg != f.File.Package { + if pkg != f.File.Package && !fn.IsExtern() { if f.File.Imports == nil { 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 } - 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:] 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()) } - registers := f.CPU.Input[:len(parameters)] - - for i := len(parameters) - 1; i >= 0; i-- { - typ, err := f.ExpressionToRegister(parameters[i], registers[i]) + if fn.IsExtern() { + f.DLLs = f.DLLs.Append(pkg, name) + registers := x86.WindowsInputRegisters[:len(parameters)] + err := f.BeforeCall(fn, parameters, registers) if err != nil { return nil, err } - if !types.Is(typ, fn.Input[i].Type) { - _, expectsPointer := fn.Input[i].Type.(*types.Pointer) + f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) + return fn.OutputTypes, nil + } - if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { - continue - } + registers := f.CPU.Input[:len(parameters)] + 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) diff --git a/src/core/IsExtern.go b/src/core/IsExtern.go new file mode 100644 index 0000000..6722db9 --- /dev/null +++ b/src/core/IsExtern.go @@ -0,0 +1,6 @@ +package core + +// IsExtern returns true if the function has no body. +func (f *Function) IsExtern() bool { + return f.Body == nil +} diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 1a395cc..79ce233 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -19,6 +19,10 @@ func (f *Function) ResolveTypes() error { 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) if uses == 0 && param.Name != "_" { @@ -38,7 +42,7 @@ func (f *Function) ResolveTypes() error { param.Type = types.ByName(typeName, f.Package, f.Structs) 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) diff --git a/src/errors/Common.go b/src/errors/Common.go index e78af1d..7b00ddd 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -9,6 +9,7 @@ var ( ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} ExpectedPackageName = &Base{"Expected package name"} ExpectedStructName = &Base{"Expected struct name"} + ExpectedDLLName = &Base{"Expected DLL name"} InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go new file mode 100644 index 0000000..e4dc19d --- /dev/null +++ b/src/scanner/scanExtern.go @@ -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 +} diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 3b1bff2..2aae3af 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -37,6 +37,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { i, err = s.scanStruct(file, tokens, i) case token.Identifier: i, err = s.scanFunction(file, tokens, i) + case token.Extern: + i, err = s.scanExtern(file, tokens, i) case token.EOF: return nil case token.Invalid: diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index 63fb880..6d85d83 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -1,7 +1,6 @@ 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" @@ -9,84 +8,17 @@ import ( // scanFunction scans a function. 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 ( - groupLevel = 0 - blockLevel = 0 - nameStart = i - paramsStart = -1 - paramsEnd = -1 - bodyStart = -1 - typeStart = -1 - typeEnd = -1 + blockLevel = 0 + bodyStart = -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 { @@ -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) } - 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-- - } - - 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 - } - + function.Body = tokens[bodyStart:i] s.functions <- function i++ return i, nil diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go new file mode 100644 index 0000000..f060214 --- /dev/null +++ b/src/scanner/scanFunctionSignature.go @@ -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 +} diff --git a/src/token/Kind.go b/src/token/Kind.go index 1a652b2..6e729e4 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -66,6 +66,7 @@ const ( ___KEYWORDS___ // Assert // assert Else // else + Extern // extern If // if Import // import Loop // loop diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index e755f97..022115d 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,13 +25,14 @@ func TestFunction(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{ token.Assert, token.If, token.Import, token.Else, + token.Extern, token.Loop, token.Return, token.Struct, diff --git a/src/token/identifier.go b/src/token/identifier.go index fa9cc63..6b6823b 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -19,6 +19,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { kind = If case "else": kind = Else + case "extern": + kind = Extern case "import": kind = Import case "loop": diff --git a/src/types/ByName.go b/src/types/ByName.go index 365e55b..27ac5fc 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -31,6 +31,8 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { switch name { case "Any": return Any + case "Bool": + return Bool case "Int": return Int case "Int64": @@ -47,6 +49,8 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return Float64 case "Float32": return Float32 + case "UInt": + return UInt } typ, exists := structs[pkg+"."+name] diff --git a/src/types/Common.go b/src/types/Common.go index 6f1918f..aab4a96 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -13,6 +13,8 @@ var ( ) var ( + Bool = Int Int = Int64 Float = Float64 + UInt = Int ) diff --git a/tests/errors/ExpectedDLLName.q b/tests/errors/ExpectedDLLName.q new file mode 100644 index 0000000..de11df4 --- /dev/null +++ b/tests/errors/ExpectedDLLName.q @@ -0,0 +1 @@ +extern {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index db440a2..c2b6588 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -15,6 +15,7 @@ var errs = []struct { ExpectedError error }{ {"EmptySwitch.q", errors.EmptySwitch}, + {"ExpectedDLLName.q", errors.ExpectedDLLName}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse},