Skip to content

Commit

Permalink
Merge pull request #937 from imring/for-stmt
Browse files Browse the repository at this point in the history
add for statement
  • Loading branch information
srliao authored Sep 15, 2022
2 parents 8b37029 + 4d05978 commit 20994eb
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 0 deletions.
1 change: 1 addition & 0 deletions pkg/gcs/ast/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const (
keywordContinue // continue
keywordFallthrough // fallthrough
keywordReturn // return
keywordFor // for
// Keywords after this are specific to Genshin (i.e. not generic scripting keywords)
// These are special char related keywords
keywordOptions // options
Expand Down
1 change: 1 addition & 0 deletions pkg/gcs/ast/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var key = map[string]TokenType{
"continue": keywordContinue,
"fallthrough": keywordFallthrough,
"return": keywordReturn,
"for": keywordFor,
//genshin specific keywords
"options": keywordOptions,
"add": keywordAdd,
Expand Down
31 changes: 31 additions & 0 deletions pkg/gcs/ast/lex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func TestBasicToken(t *testing.T) {
}
}
for x = 0; x < 5; x = x + 1 {
let i = y(x);
}
-1
1
-
Expand Down Expand Up @@ -126,6 +130,33 @@ func TestBasicToken(t *testing.T) {
{Typ: itemRightBrace, Val: "}"},
//end while
{Typ: itemRightBrace, Val: "}"},
//for loop
{Typ: keywordFor, Val: "for"},
{Typ: itemIdentifier, Val: "x"},
{Typ: itemAssign, Val: "="},
{Typ: itemNumber, Val: "0"},
{Typ: itemTerminateLine, Val: ";"},
{Typ: itemIdentifier, Val: "x"},
{Typ: OpLessThan, Val: "<"},
{Typ: itemNumber, Val: "5"},
{Typ: itemTerminateLine, Val: ";"},
{Typ: itemIdentifier, Val: "x"},
{Typ: itemAssign, Val: "="},
{Typ: itemIdentifier, Val: "x"},
{Typ: ItemPlus, Val: "+"},
{Typ: itemNumber, Val: "1"},
{Typ: itemLeftBrace, Val: "{"},
//body
{Typ: keywordLet, Val: "let"},
{Typ: itemIdentifier, Val: "i"},
{Typ: itemAssign, Val: "="},
{Typ: itemIdentifier, Val: "y"},
{Typ: itemLeftParen, Val: "("},
{Typ: itemIdentifier, Val: "x"},
{Typ: itemRightParen, Val: ")"},
{Typ: itemTerminateLine, Val: ";"},
//end for
{Typ: itemRightBrace, Val: "}"},
//misc tests
{Typ: itemNumber, Val: "-1"},
{Typ: itemNumber, Val: "1"},
Expand Down
64 changes: 64 additions & 0 deletions pkg/gcs/ast/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ type (
Condition Expr //TODO: this should be an expr?
WhileBlock *BlockStmt // What to execute if true
}

// ForStmt represents a for block
ForStmt struct {
Pos
Init Stmt // initialization statement; or nil
Cond Expr // condition; or nil
Post Stmt // post iteration statement; or nil
Body *BlockStmt
}
)

type CtrlTyp int
Expand All @@ -134,6 +143,7 @@ func (*SwitchStmt) stmtNode() {}
func (*CaseStmt) stmtNode() {}
func (*FnStmt) stmtNode() {}
func (*WhileStmt) stmtNode() {}
func (*ForStmt) stmtNode() {}

// BlockStmt.
func newBlockStmt(pos Pos) *BlockStmt {
Expand Down Expand Up @@ -573,6 +583,60 @@ func (w *WhileStmt) Copy() Node {
return w.CopyWhileStmt()
}

// ForStmt.

func (f *ForStmt) String() string {
var sb strings.Builder
f.writeTo(&sb)
return sb.String()
}

func (f *ForStmt) writeTo(sb *strings.Builder) {
sb.WriteString("for ")
if f.Init != nil {
f.Init.writeTo(sb)
}
sb.WriteString("; ")
if f.Cond != nil {
f.Cond.writeTo(sb)
}
sb.WriteString("; ")
if f.Post != nil {
f.Post.writeTo(sb)
}
sb.WriteString(" {\n")
f.Body.writeTo(sb)
sb.WriteString("}")
}

func (f *ForStmt) CopyForStmt() *ForStmt {
if f == nil {
return nil
}
n := &ForStmt{
Pos: f.Pos,
Body: f.Body.CopyBlock(),
}
if f.Init != nil {
n.Init = f.Init.CopyStmt()
}
if f.Cond != nil {
n.Cond = f.Cond.CopyExpr()
}
if f.Post != nil {
n.Post = f.Post.CopyStmt()
}
return n
}

func (f *ForStmt) CopyStmt() Stmt {
return f.CopyForStmt()
}

func (f *ForStmt) Copy() Node {
return f.CopyForStmt()
}

// Expr.

type Expr interface {
Expand Down
76 changes: 76 additions & 0 deletions pkg/gcs/ast/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ func (p *Parser) parseStatement() (Node, error) {
case keywordWhile:
node, err = p.parseWhile()
hasSemi = false
case keywordFor:
node, err = p.parseFor()
hasSemi = false
case itemLeftBrace:
node, err = p.parseBlock()
hasSemi = false
Expand Down Expand Up @@ -468,6 +471,79 @@ func (p *Parser) parseWhile() (Stmt, error) {
return stmt, err
}

// for <init ;> <cond> <; post> { <body> }
// for { <body > }
func (p *Parser) existVarDecl() bool {
switch n := p.peek(); n.Typ {
case keywordLet:
return true
case itemIdentifier:
p.next()
b := p.peek().Typ == itemAssign
p.backup()
return b
}
return false
}

func (p *Parser) parseFor() (Stmt, error) {
n := p.next()

stmt := &ForStmt{
Pos: n.pos,
}

var err error

if n := p.peek(); n.Typ == itemLeftBrace {
stmt.Body, err = p.parseBlock() //parse block here
return stmt, err
}

//init
if p.existVarDecl() {
if n := p.peek(); n.Typ == keywordLet {
stmt.Init, err = p.parseLet()
} else {
stmt.Init, err = p.parseAssign()
}
if err != nil {
return nil, err
}

if n := p.peek(); n.Typ != itemTerminateLine {
return nil, fmt.Errorf("ln%v: expecting ; after statement, got %v", n.line, n.Val)
}
p.next() //skip ;
}

//cond
stmt.Cond, err = p.parseExpr(Lowest)
if err != nil {
return nil, err
}

//post
if n := p.peek(); n.Typ == itemTerminateLine {
p.next() //skip ;
if n := p.peek(); n.Typ != itemLeftBrace {
stmt.Post, err = p.parseAssign()
if err != nil {
return nil, err
}
}
}

//expecting a { next
if n := p.peek(); n.Typ != itemLeftBrace {
return nil, fmt.Errorf("ln%v: expecting { after for, got %v", n.line, n.Val)
}

stmt.Body, err = p.parseBlock() //parse block here

return stmt, err
}

func (p *Parser) parseFn() (Stmt, error) {
//fn ident(...ident){ block }
//consume fn
Expand Down
3 changes: 3 additions & 0 deletions pkg/gcs/ast/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ const cfg = `
break;
}
}
for x = 0; x < 5; x = x + 1 {
let i = y(x);
}
`

func TestCfg(t *testing.T) {
Expand Down
38 changes: 38 additions & 0 deletions pkg/gcs/stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func (e *Eval) evalStmt(s ast.Stmt, env *Env) (Obj, error) {
return e.evalIfStmt(v, env)
case *ast.WhileStmt:
return e.evalWhileStmt(v, env)
case *ast.ForStmt:
return e.evalForStmt(v, env)
case *ast.AssignStmt:
return e.evalAssignStmt(v, env)
case *ast.SwitchStmt:
Expand Down Expand Up @@ -203,6 +205,42 @@ func (e *Eval) evalWhileStmt(w *ast.WhileStmt, env *Env) (Obj, error) {
return &null{}, nil
}

func (e *Eval) evalForStmt(f *ast.ForStmt, env *Env) (Obj, error) {
scope := NewEnv(env)
if f.Init != nil {
e.evalStmt(f.Init, scope)
}

for {
if f.Cond != nil {
//if condition is false, break
cond, err := e.evalExpr(f.Cond, scope)
if err != nil {
return nil, err
}
if !otob(cond) {
break
}
}

//execute block
res, err := e.evalBlock(f.Body, scope)
if err != nil {
return nil, err
}

//if result is a break stmt, stop loo
if t, ok := res.(*ctrl); ok && t.typ == ast.CtrlBreak {
break
}

if f.Post != nil {
e.evalStmt(f.Post, scope)
}
}
return &null{}, nil
}

func (e *Eval) evalSwitchStmt(swt *ast.SwitchStmt, env *Env) (Obj, error) {
cond, err := e.evalExpr(swt.Condition, env)
if err != nil {
Expand Down

0 comments on commit 20994eb

Please sign in to comment.