Skip to content

Commit

Permalink
for stmt
Browse files Browse the repository at this point in the history
  • Loading branch information
srliao committed Jan 2, 2024
1 parent eb837ab commit 141c91f
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 2 deletions.
3 changes: 1 addition & 2 deletions pkg/gcs/eval/stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ func evalFromStmt(n ast.Stmt, env *Env) evalNode {
case *ast.WhileStmt:
return whileStmtEval(v, env)
case *ast.ForStmt:
//TODO: for stmt
return nil
return forStmtEval(v, env)
case *ast.AssignStmt:
return assignStmtEval(v, env)
case *ast.SwitchStmt:
Expand Down
129 changes: 129 additions & 0 deletions pkg/gcs/eval/stmt_for.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package eval

import "github.com/genshinsim/gcsim/pkg/gcs/ast"

type forStmtEvalNode struct {
*ast.ForStmt
state nodeStateFn
env *Env

lastRes Obj

initNode evalNode
condNode evalNode
postNode evalNode
bodyNode evalNode
}

type nodeStateFn func() (nodeStateFn, Obj, error)

func forStmtEval(n *ast.ForStmt, env *Env) evalNode {
f := &forStmtEvalNode{
ForStmt: n,
env: NewEnv(env), // for has it's own scope in order to handle init
lastRes: &null{},
}
// start state is init
f.state = f.init
f.initNode = evalFromStmt(f.Init, f.env)
f.reset()

return f
}

func (f *forStmtEvalNode) nextAction() (Obj, bool, error) {
for {
next, res, err := f.state()
// we're done if any of the following is true:
// - err is not nil
// - next is nil
// - res is an action
if err != nil {
return nil, false, err
}
if next == nil {
// here the res could be an action, or may not be, it doesn't matter
// regardless, we are done with this node
return res, true, nil
}
f.state = next
if res.Typ() == typAction {
// this will effectively pause the execution
return res, false, nil
}
}
}

func (f *forStmtEvalNode) reset() {
f.condNode = evalFromExpr(f.Cond, f.env)
f.postNode = evalFromStmt(f.Post, f.env)
f.bodyNode = evalFromStmt(f.Body, f.env)
}

func (f *forStmtEvalNode) init() (nodeStateFn, Obj, error) {
// init is simply a stmt that must be evaluated before anything happens
res, done, err := f.initNode.nextAction()
if done {
return f.cond, res, err
}
return f.init, res, err
}

func (f *forStmtEvalNode) cond() (nodeStateFn, Obj, error) {
// if condition evaluates to false then we should simply exit
res, done, err := f.condNode.nextAction()
// if not done we really don't care what the res or err is at this step
if !done {
return f.cond, res, err
}
// otherwise we want to make sure no error before checking results
if err != nil {
return nil, nil, err
}
switch v := res.(type) {
case *number:
if v.fval == 0 && v.ival == 0 {
return nil, f.lastRes, nil
}
case *strval:
if v.str == "" {
return nil, f.lastRes, nil
}
default:
// default is always false
return nil, f.lastRes, nil
}
// at this point condition is true so we should loop
return f.loop, res, nil
}

func (f *forStmtEvalNode) loop() (nodeStateFn, Obj, error) {
res, done, err := f.bodyNode.nextAction()
f.lastRes = res
if !done {
return f.loop, res, err
}
if err != nil {
return nil, nil, err
}
if res.Typ() == typRet {
return nil, res, nil
}
if r, ok := res.(*ctrl); ok && r.typ == ast.CtrlBreak {
return nil, &null{}, nil
}
return f.post, res, err
}

func (f *forStmtEvalNode) post() (nodeStateFn, Obj, error) {
res, done, err := f.postNode.nextAction()
if !done {
return f.post, res, err
}
if err != nil {
return nil, nil, err
}
// reset states
f.reset()
return f.cond, res, nil
}
119 changes: 119 additions & 0 deletions pkg/gcs/eval/stmt_for_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package eval

import (
"fmt"
"testing"

"github.com/genshinsim/gcsim/pkg/gcs/ast"
)

func TestEvalForStmt(t *testing.T) {
prog := `
for let i = 0; i < 5; i = i + 1 {
print(i);
i;
}
`
p := ast.New(prog)
_, gcsl, err := p.Parse()
if err != nil {
t.Fatal(err)
}
fmt.Println("program:")
fmt.Println(gcsl.String())
eval, err := NewEvaluator(gcsl, nil)
if err != nil {
t.Error(err)
t.FailNow()
}
res, _, err := eval.base.nextAction()
if err != nil {
t.Error(err)
t.FailNow()
}
val, ok := res.(*number)
if !ok {
t.Errorf("res is not number, got %v", res.Typ())
t.FailNow()
}
if val.ival != 4 {
t.Errorf("expecting res to be 4, got %v", val.ival)
}
}

func TestEvalForStmtBreak(t *testing.T) {
prog := `
let i = 0;
for i = 0; i < 5; i = i + 1 {
print(i);
if i > 1 {
break;
}
}
i;
`
p := ast.New(prog)
_, gcsl, err := p.Parse()
if err != nil {
t.Fatal(err)
}
fmt.Println("program:")
fmt.Println(gcsl.String())
eval, err := NewEvaluator(gcsl, nil)
if err != nil {
t.Error(err)
t.FailNow()
}
res, _, err := eval.base.nextAction()
if err != nil {
t.Error(err)
t.FailNow()
}
val, ok := res.(*number)
if !ok {
t.Errorf("res is not number, got %v", res.Typ())
t.FailNow()
}
if val.ival != 2 {
t.Errorf("expecting res to be 2, got %v", val.ival)
}
}

func TestEvalForStmtContinue(t *testing.T) {
prog := `
let x = 0;
for let i = 0; i < 5; i = i + 1 {
if i < 2 {
continue;
}
x = x + 1;
print(x);
}
x;
`
p := ast.New(prog)
_, gcsl, err := p.Parse()
if err != nil {
t.Fatal(err)
}
fmt.Println("program:")
fmt.Println(gcsl.String())
eval, err := NewEvaluator(gcsl, nil)
if err != nil {
t.Error(err)
t.FailNow()
}
res, _, err := eval.base.nextAction()
if err != nil {
t.Error(err)
t.FailNow()
}
val, ok := res.(*number)
if !ok {
t.Errorf("res is not number, got %v", res.Typ())
t.FailNow()
}
if val.ival != 3 {
t.Errorf("expecting res to be 3, got %v", val.ival)
}
}

0 comments on commit 141c91f

Please sign in to comment.