Implemented assert keyword
This commit is contained in:
parent
f0bc5039ee
commit
f4dd9004be
@ -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
10
src/build/ast/Assert.go
Normal 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
|
||||
}
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
29
src/build/core/CompileAssert.go
Normal file
29
src/build/core/CompileAssert.go
Normal 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
6
src/build/core/Defer.go
Normal 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)
|
||||
}
|
@ -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
|
||||
|
@ -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"}
|
||||
|
@ -20,6 +20,7 @@ const (
|
||||
ArrayStart // [
|
||||
ArrayEnd // ]
|
||||
_keywords // <keywords>
|
||||
Assert // assert
|
||||
If // if
|
||||
Import // import
|
||||
Loop // loop
|
||||
|
@ -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
6
tests/programs/assert.q
Normal file
@ -0,0 +1,6 @@
|
||||
main() {
|
||||
assert 1 != 0
|
||||
assert 1 == 0 || 1 != 0
|
||||
assert 1 != 0 && 2 != 0
|
||||
assert 1 == 0
|
||||
}
|
@ -40,6 +40,7 @@ var programs = []struct {
|
||||
{"jump-near", "", "", 0},
|
||||
{"loop", "", "", 0},
|
||||
{"loop-lifetime", "", "", 0},
|
||||
{"assert", "", "", 1},
|
||||
}
|
||||
|
||||
func TestPrograms(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user