Implemented assert keyword

This commit is contained in:
Eduard Urbach 2024-07-25 16:47:25 +02:00
parent f0bc5039ee
commit f4dd9004be
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
14 changed files with 81 additions and 1 deletions

View File

@ -123,6 +123,7 @@ This is what generates expressions from tokens.
### Keywords
- [x] `assert`
- [ ] `for`
- [x] `if`
- [x] `import`

10
src/build/ast/Assert.go Normal file
View File

@ -0,0 +1,10 @@
package ast
import (
"git.akyoto.dev/cli/q/src/build/expression"
)
// Assert represents a condition that must be true, otherwise the program stops.
type Assert struct {
Condition *expression.Expression
}

View File

@ -35,6 +35,15 @@ func toASTNode(tokens token.List, buffer []byte) (Node, error) {
return &Return{Value: value}, nil
}
if tokens[0].Kind == token.Assert {
if len(tokens) == 1 {
return nil, errors.New(errors.MissingExpression, nil, tokens[0].End())
}
condition := expression.Parse(tokens[1:])
return &Assert{Condition: condition}, nil
}
if keywordHasBlock(tokens[0].Kind) {
blockStart := tokens.IndexKind(token.BlockStart)
blockEnd := tokens.LastIndexKind(token.BlockEnd)

View File

@ -26,7 +26,7 @@ func (r *Result) finalize() ([]byte, []byte) {
// The reason we call `main` instead of using `main` itself is to place
// a return address on the stack, which allows return statements in `main`.
final := asm.Assembler{
Instructions: make([]asm.Instruction, 0, r.InstructionCount+4),
Instructions: make([]asm.Instruction, 0, r.InstructionCount+8),
Data: make(map[string][]byte, r.DataCount),
}
@ -35,6 +35,11 @@ func (r *Result) finalize() ([]byte, []byte) {
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0)
final.Syscall()
final.Label(asm.LABEL, "_crash")
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit)
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 1)
final.Syscall()
// This will place the main function immediately after the entry point
// and also add everything the main function calls recursively.
r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) {

View File

@ -5,4 +5,8 @@ func (f *Function) Compile() {
f.AddLabel(f.Name)
f.Err = f.CompileTokens(f.Body)
f.Return()
for _, call := range f.deferred {
call()
}
}

View File

@ -7,6 +7,9 @@ import (
// CompileASTNode compiles a node in the AST.
func (f *Function) CompileASTNode(node ast.Node) error {
switch node := node.(type) {
case *ast.Assert:
return f.CompileAssert(node)
case *ast.Assign:
return f.CompileAssign(node)

View File

@ -0,0 +1,29 @@
package core
import (
"fmt"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast"
)
// CompileAssert compiles an assertion.
func (f *Function) CompileAssert(assert *ast.Assert) error {
f.count.assert++
success := fmt.Sprintf("%s_assert_%d_true", f.Name, f.count.assert)
fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert)
err := f.CompileCondition(assert.Condition, success, fail)
if err != nil {
return err
}
f.AddLabel(success)
f.Defer(func() {
f.AddLabel(fail)
f.Jump(asm.JUMP, "_crash")
})
return err
}

6
src/build/core/Defer.go Normal file
View File

@ -0,0 +1,6 @@
package core
// Defer executes the callback at the end of function compilation.
func (f *Function) Defer(call func()) {
f.deferred = append(f.deferred, call)
}

View File

@ -14,11 +14,13 @@ type Function struct {
Body []token.Token
Functions map[string]*Function
Err error
deferred []func()
count counter
}
// counter stores how often a certain statement appeared so we can generate a unique label from it.
type counter struct {
assert int
branch int
data int
loop int

View File

@ -5,6 +5,7 @@ var (
InvalidExpression = &Base{"Invalid expression"}
InvalidRune = &Base{"Invalid rune"}
InvalidStatement = &Base{"Invalid statement"}
MissingExpression = &Base{"Missing expression"}
MissingMainFunction = &Base{"Missing main function"}
MissingOperand = &Base{"Missing operand"}
NotImplemented = &Base{"Not implemented"}

View File

@ -20,6 +20,7 @@ const (
ArrayStart // [
ArrayEnd // ]
_keywords // <keywords>
Assert // assert
If // if
Import // import
Loop // loop

View File

@ -95,6 +95,8 @@ func Tokenize(buffer []byte) List {
kind := Identifier
switch string(identifier) {
case "assert":
kind = Assert
case "if":
kind = If
case "import":

6
tests/programs/assert.q Normal file
View File

@ -0,0 +1,6 @@
main() {
assert 1 != 0
assert 1 == 0 || 1 != 0
assert 1 != 0 && 2 != 0
assert 1 == 0
}

View File

@ -40,6 +40,7 @@ var programs = []struct {
{"jump-near", "", "", 0},
{"loop", "", "", 0},
{"loop-lifetime", "", "", 0},
{"assert", "", "", 1},
}
func TestPrograms(t *testing.T) {