Implemented const keyword

This commit is contained in:
Eduard Urbach 2025-02-15 14:38:01 +01:00
parent be028a52d1
commit 0a1a8f741d
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
21 changed files with 164 additions and 26 deletions

View File

@ -1,15 +1,21 @@
import sys
const std {
in 0
out 1
err 2
}
in(buffer []Int8) -> Int {
return sys.read(0, buffer, len(buffer))
return sys.read(std.in, buffer, len(buffer))
}
out(buffer []Int8) -> Int {
return sys.write(1, buffer, len(buffer))
return sys.write(std.out, buffer, len(buffer))
}
error(buffer []Int8) -> Int {
return sys.write(2, buffer, len(buffer))
return sys.write(std.err, buffer, len(buffer))
}
read(fd Int, buffer []Int8) -> Int {

View File

@ -1,7 +1,17 @@
import sys
const prot {
read 0x1
write 0x2
}
const map {
private 0x02
anonymous 0x20
}
alloc(length Int) -> []Int8 {
x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x20)
x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous)
if x < 0x1000 {
return x

View File

@ -1,7 +1,17 @@
import sys
const prot {
read 0x1
write 0x2
}
const map {
private 0x02
anonymous 0x1000
}
alloc(length Int) -> []Int8 {
x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x1000)
x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous)
if x < 0x1000 {
return x

View File

@ -1,7 +1,16 @@
import sys
const page {
readwrite 0x0004
}
const mem {
commit 0x1000
reserve 0x2000
}
alloc(length Int) -> []Int8 {
x := sys.mmap(0, length+8, 0x0004, 0x3000)
x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve)
if x < 0x1000 {
return x

View File

@ -3,10 +3,14 @@ extern kernel32 {
VirtualFree(address *Any, length Int, type Int) -> Bool
}
const mem {
decommit 0x4000
}
mmap(address Int, length Int, protection Int, flags Int) -> *Any {
return kernel32.VirtualAlloc(address, length, flags, protection)
}
munmap(address *Any, length Int) -> Int {
return kernel32.VirtualFree(address, length, 0x4000)
return kernel32.VirtualFree(address, length, mem.decommit)
}

View File

@ -23,8 +23,8 @@ func New(files ...string) *Build {
// Run compiles the input files.
func (build *Build) Run() (compiler.Result, error) {
files, functions, structs, errors := scanner.Scan(build.Files)
return compiler.Compile(files, functions, structs, errors)
constants, files, functions, structs, errors := scanner.Scan(build.Files)
return compiler.Compile(constants, files, functions, structs, errors)
}
// Executable returns the path to the executable.

View File

@ -8,13 +8,14 @@ import (
)
// Compile waits for the scan to finish and compiles all functions.
func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) {
func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) {
result := Result{}
allFiles := make([]*fs.File, 0, 8)
allFunctions := make(map[string]*core.Function, 32)
allStructs := make(map[string]*types.Struct, 8)
allConstants := make(map[string]*core.Constant, 8)
for functions != nil || structs != nil || files != nil || errs != nil {
for constants != nil || files != nil || functions != nil || structs != nil || errs != nil {
select {
case function, ok := <-functions:
if !ok {
@ -24,6 +25,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c
function.Functions = allFunctions
function.Structs = allStructs
function.Constants = allConstants
allFunctions[function.UniqueName] = function
case structure, ok := <-structs:
@ -42,6 +44,14 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c
allFiles = append(allFiles, file)
case constant, ok := <-constants:
if !ok {
constants = nil
continue
}
allConstants[constant.Name] = constant
case err, ok := <-errs:
if !ok {
errs = nil

13
src/core/Constant.go Normal file
View File

@ -0,0 +1,13 @@
package core
import (
"git.akyoto.dev/cli/q/src/fs"
"git.akyoto.dev/cli/q/src/token"
)
// Constant registers a single value to be accessible under a descriptive name.
type Constant struct {
Name string
Value token.Token
File *fs.File
}

View File

@ -86,11 +86,22 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
if node.Token.Kind == token.Period {
left := node.Children[0]
name := left.Token.Text(f.File.Bytes)
variable := f.VariableByName(name)
leftText := left.Token.Text(f.File.Bytes)
right := node.Children[1]
name = right.Token.Text(f.File.Bytes)
field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(name)
rightText := right.Token.Text(f.File.Bytes)
variable := f.VariableByName(leftText)
if variable == nil {
constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText]
if isConst {
return f.TokenToRegister(constant.Value, register)
}
return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position)
}
field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText)
f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register)
return field.Type, nil
}

View File

@ -21,6 +21,7 @@ type Function struct {
OutputTypes []types.Type
Functions map[string]*Function
Structs map[string]*types.Struct
Constants map[string]*Constant
DLLs dll.List
Err error
deferred []func()

View File

@ -2,6 +2,7 @@ package errors
var (
EmptySwitch = &Base{"Empty switch"}
ExpectedConstName = &Base{"Expected a name for the const group"}
ExpectedFunctionParameters = &Base{"Expected function parameters"}
ExpectedFunctionDefinition = &Base{"Expected function definition"}
ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"}

View File

@ -10,8 +10,9 @@ import (
)
// Scan scans the list of files.
func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types.Struct, <-chan error) {
func Scan(files []string) (chan *core.Constant, <-chan *fs.File, <-chan *core.Function, <-chan *types.Struct, <-chan error) {
scanner := Scanner{
constants: make(chan *core.Constant),
files: make(chan *fs.File),
functions: make(chan *core.Function),
structs: make(chan *types.Struct),
@ -22,11 +23,12 @@ func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types
scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem")
scanner.queue(files...)
scanner.group.Wait()
close(scanner.constants)
close(scanner.files)
close(scanner.functions)
close(scanner.structs)
close(scanner.errors)
}()
return scanner.files, scanner.functions, scanner.structs, scanner.errors
return scanner.constants, scanner.files, scanner.functions, scanner.structs, scanner.errors
}

View File

@ -10,6 +10,7 @@ import (
// Scanner is used to scan files before the actual compilation step.
type Scanner struct {
constants chan *core.Constant
files chan *fs.File
functions chan *core.Function
structs chan *types.Struct

47
src/scanner/scanConst.go Normal file
View File

@ -0,0 +1,47 @@
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"
)
// scanConst scans a block of constants.
func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error) {
i++
if tokens[i].Kind != token.Identifier {
return i, errors.New(errors.ExpectedConstName, file, tokens[i].Position)
}
groupName := tokens[i].Text(file.Bytes)
i++
if tokens[i].Kind != token.BlockStart {
return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position)
}
i++
for i < len(tokens) {
if tokens[i].Kind == token.Identifier {
name := tokens[i].Text(file.Bytes)
i++
s.constants <- &core.Constant{
Name: file.Package + "." + groupName + "." + name,
Value: tokens[i],
File: file,
}
}
if tokens[i].Kind == token.BlockEnd {
return i, nil
}
i++
}
return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position)
}

View File

@ -22,7 +22,6 @@ func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, erro
}
i++
closed := false
for i < len(tokens) {
if tokens[i].Kind == token.Identifier {
@ -39,16 +38,11 @@ func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, erro
}
if tokens[i].Kind == token.BlockEnd {
closed = true
break
return i, nil
}
i++
}
if !closed {
return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position)
}
return i, nil
}

View File

@ -39,6 +39,8 @@ func (s *Scanner) scanFile(path string, pkg string) error {
i, err = s.scanFunction(file, tokens, i)
case token.Extern:
i, err = s.scanExtern(file, tokens, i)
case token.Const:
i, err = s.scanConst(file, tokens, i)
case token.EOF:
return nil
case token.Invalid:

View File

@ -65,6 +65,7 @@ const (
___END_OPERATORS___ // </operators>
___KEYWORDS___ // <keywords>
Assert // assert
Const // const
Else // else
Extern // extern
If // if

View File

@ -25,10 +25,11 @@ func TestFunction(t *testing.T) {
}
func TestKeyword(t *testing.T) {
tokens := token.Tokenize([]byte("assert if import else extern loop return struct switch"))
tokens := token.Tokenize([]byte("assert const if import else extern loop return struct switch"))
expected := []token.Kind{
token.Assert,
token.Const,
token.If,
token.Import,
token.Else,

View File

@ -15,6 +15,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) {
switch string(identifier) {
case "assert":
kind = Assert
case "const":
kind = Const
case "if":
kind = If
case "else":

12
tests/programs/const.q Normal file
View File

@ -0,0 +1,12 @@
const num {
one 1
two 2
three 3
}
main() {
assert num.one == 1
assert num.two == 2
assert num.three == 3
assert num.one + num.two == num.three
}

View File

@ -31,6 +31,7 @@ var programs = []struct {
{"binary", "", "", 0},
{"octal", "", "", 0},
{"hexadecimal", "", "", 0},
{"const", "", "", 0},
{"array", "", "", 0},
{"escape-rune", "", "", 0},
{"escape-string", "", "", 0},