Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/codegen/c/analysis/visit_stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func (a *Analyzer) visitStmt(current string, stmt ast.Stmt) {
a.visitVar(current, n)
case *ast.IfStmt:
a.visitIf(current, n)
case *ast.WhileStmt:
a.visitWhile(current, n)
case *ast.LoopStmt:
a.visitLoop(current, n)
case *ast.AssignmentStmt:
Expand Down
11 changes: 11 additions & 0 deletions internal/codegen/c/analysis/visit_while.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package analysis

import "github.com/azin-lang/Azin/pkg/ast"

func (a *Analyzer) visitWhile(fn string, stmt *ast.WhileStmt) {
a.visitExpr(fn, stmt.Condition)

for _, child := range stmt.Body {
a.visitStmt(fn, child)
}
}
24 changes: 24 additions & 0 deletions internal/codegen/c/emit_statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ func (t *Transpiler) emitStatement(
case *ast.IfStmt:
t.emitIf(n)

case *ast.WhileStmt:
t.emitWhile(n)

case *ast.LoopStmt:
t.emitLoop(n)

Expand Down Expand Up @@ -173,6 +176,27 @@ func (t *Transpiler) emitIf(
t.newline()
}

func (t *Transpiler) emitWhile(
stmt *ast.WhileStmt,
) {
t.indentLine()

t.write("while (")

t.emitExpression(
stmt.Condition,
)

t.write(") {\n")

t.emitBlock(
stmt.Body,
)

t.indentLine()
t.write("}\n")
}

func (t *Transpiler) emitLoop(
stmt *ast.LoopStmt,
) {
Expand Down
28 changes: 28 additions & 0 deletions internal/optimizer/statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func (o *Optimizer) optimizeStatement(stmt ast.Stmt) []ast.Stmt {
return o.optimizeIf(n)
case *ast.LoopStmt:
return o.optimizeLoop(n)
case *ast.WhileStmt:
return o.optimizeWhile(n)
case *ast.ExpressionStmt:
return o.optimizeExpressionStmt(n)
case *ast.FuncStmt:
Expand All @@ -58,6 +60,32 @@ func (o *Optimizer) optimizeStatement(stmt ast.Stmt) []ast.Stmt {
return []ast.Stmt{stmt}
}

func (o *Optimizer) optimizeWhile(n *ast.WhileStmt) []ast.Stmt {
// Optimization for while loops can be implemented here, but for now, we will just optimize the body of the loop.
if len(n.Body) == 0 {
return nil
}

o.currentScope.ClearAll()

o.Enter()
n.Body = o.optimizeStatements(n.Body)
o.Leave()

if !canUnwrapLoop(n.Body) {
return []ast.Stmt{n}
}

last := n.Body[len(n.Body)-1]
switch last.(type) {
case *ast.ReturnStmt:
return n.Body
case *ast.StopStmt:
return n.Body[:len(n.Body)-1]
}
return []ast.Stmt{n}
}

func (o *Optimizer) optimizeLoop(n *ast.LoopStmt) []ast.Stmt {
if len(n.Body) == 0 {
return nil
Expand Down
13 changes: 13 additions & 0 deletions pkg/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,19 @@ func (*IfStmt) Label() string {
return "if"
}

type WhileStmt struct {
Token token2.Token // while
Condition Expr
Body []Stmt
}

func (*WhileStmt) stmtNode() {}
func (w *WhileStmt) TokenLiteral() string { return w.Token.Kind.String() }
func (w *WhileStmt) Pos() token2.Position { return w.Token.Position }
func (*WhileStmt) Label() string {
return "while"
}

type LoopStmt struct {
Token token2.Token // loop
Body []Stmt
Expand Down
12 changes: 12 additions & 0 deletions pkg/ast/ast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ func TestIfStmt(t *testing.T) {
}
}

func TestWhileStmt(t *testing.T) {
s := &ast.WhileStmt{
Token: tok(token.KwWhile, 0, 5),
Condition: ident("true"),
Body: []ast.Stmt{},
}

if s.Label() != "while" {
t.Errorf("Label = %q", s.Label())
}
}

func TestLoopStmt(t *testing.T) {
s := &ast.LoopStmt{
Token: tok(token.KwLoop, 0, 4),
Expand Down
2 changes: 1 addition & 1 deletion pkg/lexer/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func FuzzLexer(f *testing.F) {
"+ - * / % = == ! !=",
"< <= > >= += ++ -= -- -> && ||",
"( ) { } [ ] , ; : .",
"fn do var mut return end char int bool unit string float if then else struct is importc loop null",
"fn do var mut return end char int bool unit string float if then else struct is importc loop while null",
"@",
"'\n'",
"",
Expand Down
4 changes: 2 additions & 2 deletions pkg/lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ func joinKinds(tokens []token2.Token) string {
}

func TestLexerKeywords(t *testing.T) {
input := "fn do var mut return end char int bool unit string float if then else struct is import importc loop null"
input := "fn do var mut return end char int bool unit string float if then else struct is import importc loop while null"
tokens, diag := lex(input)

if diag.HasErrors() {
t.Fatalf("unexpected errors: %v", diag.Err())
}

got := joinKinds(tokens)
want := "kw_fn kw_do kw_var kw_mut kw_return kw_end kw_char kw_int kw_bool kw_unit kw_string kw_float kw_if kw_then kw_else kw_struct kw_is kw_import kw_importc kw_loop kw_null eof"
want := "kw_fn kw_do kw_var kw_mut kw_return kw_end kw_char kw_int kw_bool kw_unit kw_string kw_float kw_if kw_then kw_else kw_struct kw_is kw_import kw_importc kw_loop kw_while kw_null eof"

if got != want {
t.Errorf("keywords\ngot: %s\nwant: %s", got, want)
Expand Down
1 change: 1 addition & 0 deletions pkg/parser/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func FuzzParser(f *testing.F) {
"if true then return 1; end",
"if true then return 1; else return 2; end",
"loop return 0; end",
"while true do return 0; end",
"struct Point is x: int; y: int; end",
"importc \"stdio.h\"",
"x = 42;",
Expand Down
14 changes: 14 additions & 0 deletions pkg/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ func TestParserIfElse(t *testing.T) {
}
}

func TestParserWhile(t *testing.T) {
program, diag := parseProgram(t, `
while true loop
return 1
end
`)
if diag.HasErrors() {
t.Fatalf("unexpected errors: %v", diag.Err())
}
if _, ok := program.Statements[0].(*ast2.WhileStmt); !ok {
t.Fatalf("expected WhileStmt, got %T", program.Statements[0])
}
}

func TestParserLoop(t *testing.T) {
program, diag := parseProgram(t, "loop\n return 1;\nend\n")
if diag.HasErrors() {
Expand Down
18 changes: 18 additions & 0 deletions pkg/parser/statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ func (p *Parser) parseStatement() ast.Stmt {
stmt = p.parseImportC()
case p.check(token.KwImport):
stmt = p.parseImport()
case p.check(token.KwWhile):
stmt = p.parseWhile()
case p.check(token.KwLoop):
stmt = p.parseLoop()
case p.check(token.KwStop):
Expand Down Expand Up @@ -479,6 +481,22 @@ func (p *Parser) parseStop() ast.Stmt {
}
}

func (p *Parser) parseWhile() ast.Stmt {
tok := p.advance()
condition := p.parseExpression(PrecLowest)

p.expect(token.KwLoop, "after while condition")
body := p.parseBlock(token.KwEnd)

p.expect(token.KwEnd, "to close while")

return &ast.WhileStmt{
Token: tok,
Condition: condition,
Body: body,
}
}

func (p *Parser) parseLoop() ast.Stmt {
tok := p.advance()

Expand Down
16 changes: 16 additions & 0 deletions pkg/sema/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,22 @@ func (a *Analyzer) visitStatement(stmt ast.Stmt) {

a.popScope()

case *ast.WhileStmt:
a.loopDepth++
defer func() { a.loopDepth-- }()

a.pushScope()
defer a.popScope()

cond := a.inferExprType(n.Condition)
if !types2.IsAssignable(cond, types2.BoolType()) {
a.errorf(n.Condition, "while condition must be bool, got %s", cond.Name)
}

for _, stmt := range n.Body {
a.visitStatement(stmt)
}

case *ast.LoopStmt:
a.loopDepth++
defer func() { a.loopDepth-- }()
Expand Down
59 changes: 59 additions & 0 deletions pkg/sema/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ end`
_ = mustHaveError(t, input)
}

func TestSemanticWhileLoopConditionTypeMismatch(t *testing.T) {
input := `fn main: int do
var mut x: int = 0;
while x loop
x = x + 1;
end
return x;
end`
_ = mustHaveError(t, input)
}

func TestSemanticImmutableAssign(t *testing.T) {
input := `fn main: int do
var x: int = 42;
Expand Down Expand Up @@ -206,6 +217,30 @@ end`
validProgram(t, input)
}

func TestSemanticWhileLoop(t *testing.T) {
input := `
fn main: int do
var mut x: int = 0;
while x < 10 loop
x = x + 1;
end
return x;
end`
validProgram(t, input)
}

func TestSemanticWhileLoopBreak(t *testing.T) {
input := `fn main: int do
var mut x: int = 0
while x < 10 loop
stop
end

return x
end`
validProgram(t, input)
}

func TestSemanticLoopBreak(t *testing.T) {
input := `fn main: int do
loop
Expand Down Expand Up @@ -337,6 +372,18 @@ end`
mustNotHaveWarning(t, input)
}

func TestSemanticUnusedVarInWhileLoop(t *testing.T) {
input := `fn main: int do
var mut x: int = 0;
while x < 10 loop
var y: int = 42;
x = x + 1;
end
return 0;
end`
mustHaveWarning(t, input, "unused variable: y")
}

func TestSemanticUnusedVarInLoop(t *testing.T) {
input := `fn main: int do
loop
Expand All @@ -346,6 +393,18 @@ end`
mustHaveWarning(t, input, "unused variable: x")
}

func TestSemanticUsedVarInWhileLoop(t *testing.T) {
input := `fn main: int do
var mut x: int = 0;
while x < 10 loop
var mut y: int = 42;
x = x + y + 1;
end
return x;
end`
mustNotHaveWarning(t, input)
}

func TestSemanticUsedVarInLoop(t *testing.T) {
input := `fn main: int do
loop
Expand Down
1 change: 1 addition & 0 deletions pkg/token/keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var Keywords = map[string]Kind{
"is": KwIs,
"import": KwImport,
"importc": KwImportC,
"while": KwWhile,
"loop": KwLoop,
"stop": KwStop,
"defer": KwDefer,
Expand Down
3 changes: 2 additions & 1 deletion pkg/token/keywords_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func TestKeywordsContainAllRegistered(t *testing.T) {
"import": tok.KwImport,
"importc": tok.KwImportC,
"loop": tok.KwLoop,
"while": tok.KwWhile,
"stop": tok.KwStop,
"null": tok.KwNull,
"enum": tok.KwEnum,
Expand All @@ -52,7 +53,7 @@ func TestKeywordsNoExtraEntries(t *testing.T) {
"return": true, "end": true, "char": true, "int": true,
"bool": true, "unit": true, "string": true, "float": true,
"if": true, "then": true, "else": true, "struct": true,
"is": true, "import": true, "importc": true, "loop": true, "stop": true,
"is": true, "import": true, "importc": true, "loop": true, "while": true, "stop": true,
"null": true, "enum": true, "defer": true,
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/token/kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const (
KwIs // kw_is
KwImportC // kw_importc
KwImport // kw_import
KwWhile // kw_while
KwLoop // kw_loop
KwStop // kw_stop
KwDefer // kw_defer
Expand Down Expand Up @@ -132,6 +133,8 @@ func (k Kind) DisplayName() string {
return "'importC'"
case KwImport:
return "'import'"
case KwWhile:
return "'while'"
case KwEnum:
return "'enum'"
case KwDefer:
Expand Down
Loading
Loading