Implemented assert keyword
This commit is contained in:
parent
f0bc5039ee
commit
f4dd9004be
@ -123,6 +123,7 @@ This is what generates expressions from tokens.
|
|||||||
|
|
||||||
### Keywords
|
### Keywords
|
||||||
|
|
||||||
|
- [x] `assert`
|
||||||
- [ ] `for`
|
- [ ] `for`
|
||||||
- [x] `if`
|
- [x] `if`
|
||||||
- [x] `import`
|
- [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
|
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) {
|
if keywordHasBlock(tokens[0].Kind) {
|
||||||
blockStart := tokens.IndexKind(token.BlockStart)
|
blockStart := tokens.IndexKind(token.BlockStart)
|
||||||
blockEnd := tokens.LastIndexKind(token.BlockEnd)
|
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
|
// 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`.
|
// a return address on the stack, which allows return statements in `main`.
|
||||||
final := asm.Assembler{
|
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),
|
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.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0)
|
||||||
final.Syscall()
|
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
|
// This will place the main function immediately after the entry point
|
||||||
// and also add everything the main function calls recursively.
|
// and also add everything the main function calls recursively.
|
||||||
r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) {
|
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.AddLabel(f.Name)
|
||||||
f.Err = f.CompileTokens(f.Body)
|
f.Err = f.CompileTokens(f.Body)
|
||||||
f.Return()
|
f.Return()
|
||||||
|
|
||||||
|
for _, call := range f.deferred {
|
||||||
|
call()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,9 @@ import (
|
|||||||
// CompileASTNode compiles a node in the AST.
|
// CompileASTNode compiles a node in the AST.
|
||||||
func (f *Function) CompileASTNode(node ast.Node) error {
|
func (f *Function) CompileASTNode(node ast.Node) error {
|
||||||
switch node := node.(type) {
|
switch node := node.(type) {
|
||||||
|
case *ast.Assert:
|
||||||
|
return f.CompileAssert(node)
|
||||||
|
|
||||||
case *ast.Assign:
|
case *ast.Assign:
|
||||||
return f.CompileAssign(node)
|
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
|
Body []token.Token
|
||||||
Functions map[string]*Function
|
Functions map[string]*Function
|
||||||
Err error
|
Err error
|
||||||
|
deferred []func()
|
||||||
count counter
|
count counter
|
||||||
}
|
}
|
||||||
|
|
||||||
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
||||||
type counter struct {
|
type counter struct {
|
||||||
|
assert int
|
||||||
branch int
|
branch int
|
||||||
data int
|
data int
|
||||||
loop int
|
loop int
|
||||||
|
@ -5,6 +5,7 @@ var (
|
|||||||
InvalidExpression = &Base{"Invalid expression"}
|
InvalidExpression = &Base{"Invalid expression"}
|
||||||
InvalidRune = &Base{"Invalid rune"}
|
InvalidRune = &Base{"Invalid rune"}
|
||||||
InvalidStatement = &Base{"Invalid statement"}
|
InvalidStatement = &Base{"Invalid statement"}
|
||||||
|
MissingExpression = &Base{"Missing expression"}
|
||||||
MissingMainFunction = &Base{"Missing main function"}
|
MissingMainFunction = &Base{"Missing main function"}
|
||||||
MissingOperand = &Base{"Missing operand"}
|
MissingOperand = &Base{"Missing operand"}
|
||||||
NotImplemented = &Base{"Not implemented"}
|
NotImplemented = &Base{"Not implemented"}
|
||||||
|
@ -20,6 +20,7 @@ const (
|
|||||||
ArrayStart // [
|
ArrayStart // [
|
||||||
ArrayEnd // ]
|
ArrayEnd // ]
|
||||||
_keywords // <keywords>
|
_keywords // <keywords>
|
||||||
|
Assert // assert
|
||||||
If // if
|
If // if
|
||||||
Import // import
|
Import // import
|
||||||
Loop // loop
|
Loop // loop
|
||||||
|
@ -95,6 +95,8 @@ func Tokenize(buffer []byte) List {
|
|||||||
kind := Identifier
|
kind := Identifier
|
||||||
|
|
||||||
switch string(identifier) {
|
switch string(identifier) {
|
||||||
|
case "assert":
|
||||||
|
kind = Assert
|
||||||
case "if":
|
case "if":
|
||||||
kind = If
|
kind = If
|
||||||
case "import":
|
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},
|
{"jump-near", "", "", 0},
|
||||||
{"loop", "", "", 0},
|
{"loop", "", "", 0},
|
||||||
{"loop-lifetime", "", "", 0},
|
{"loop-lifetime", "", "", 0},
|
||||||
|
{"assert", "", "", 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrograms(t *testing.T) {
|
func TestPrograms(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user