Monkey言語:loop文の実装

これで実装してるMonkey言語を拡張します.


loop文とは

while文の簡略版みたいなもので,whileでは条件文を入れていたところにIntを入れてその回数分繰り返させようというものです.

作ろうとした動機は,LiveCoding言語の実装を考えるときに,細かい条件文を入れるよりは単に繰り返す数を入力すぐ方が頻度が多くなりそうだと考えたからである.実際にSonic Piなどでもこれがある.

loop(Int){
    本文
}


Lexer

token/token.goのconstに

LOOP = “LOOP”

を加え,ただの変数と認識されないようにkeywordsのmapに

loop" = LOOP,

を追加する.


lexer/lexer_test.go
Loopを字句解析で捉えられているかを確認するために次のようにinputとexpectedにそれぞれ追加する.

loop(4){
        return true;
    }

        {token.LOOP, "loop"},
        {token.LPAREN, "("},
        {token.INT, "4"},
        {token.RPAREN, ")"},
        {token.LBRACE, "{"},
        {token.RETURN, "return"},
        {token.TRUE, "true"},
        {token.SEMICOLON, ";"},
        {token.RBRACE, "}"},


おそらくテストは通過するであろう.lexerは分岐で指定している文字でない場合はisLetterでまとめて読んで, keywordsにその文字列が紐付けてあればそのtokenを返すようになっているので,構文を増やす分には特にいじる必要はない.



Parser

ast/ast.go

type LoopExpression struct {
    Token       token.Token
    Condition   Expression
    Consequence *BlockStatement
}
func (le *LoopExpression) expressionNode() {}
func (le *LoopExpression) TokenLiteral() string {return le.Token.Literal}
func (le *LoopExpression) String() string {
    var out bytes.Buffer
    out.WriteString("loop")
    out.WriteString(le.Token.Literal)
    out.WriteString(" ")
    out.WriteString(le.Consequence.String())
    return out.String()
}



parser/parser_test.go

func TestLoopExpression(t *testing.T){
    input := `loop(4) {return true;}`

    l := lexer.New(input)
    p := New(l)
    program := p.ParseProgram()
    checkParserErrors(t, p)

    if len(program.Statements) != 1{
        t.Fatalf("program.Statements[0] does not contain %d statements. got=%d\n", 1, len(program.Statements))
    }
    stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
    if !ok {
        t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", stmt.Expression)
    }
    exp, ok := stmt.Expression.(*ast.LoopExpression)
    if !ok {
        t.Fatalf("stmt.Expression is not ast.LoopExpression. got=%T", stmt.Expression)
    }
    if !testIntegerLiteral(t, exp.Condition, 4){
        return 
    }
    if len(exp.Consequence.Statements) != 1{
        t.Errorf("Consequence is not 1 statements. got=%d\n", len(exp.Consequence.Statements))
    }
    consequence, ok := exp.Consequence.Statements[0].(*ast.ReturnStatement)
    if !ok{
        t.Fatalf("Statements[0] is not ast.ReturnStatement. got=%T", exp.Consequence.Statements[0])
    }
    if !testReturnStatement(t, consequence, "true"){
        return
    }
}

func testReturnStatement(t *testing.T, s ast.Statement, returnvalue string) bool{
    if s.TokenLiteral() != "return"{
        t.Errorf("s.TokenLiteral not 'return'. got=%q", s.TokenLiteral())
        return false
    }
    retStmt, ok := s.(*ast.ReturnStatement)
    if !ok{
        t.Errorf("s not *ast.ReturnStatement. got=%T", s)
        return false
    }
    if retStmt.ReturnValue.TokenLiteral() != returnvalue{
        t.Errorf("retStmt.ReturnValue.TokenLiteral() not '%s'. got=%s", returnvalue, retStmt.ReturnValue.TokenLiteral())
        return false
    }
    return true
}


InputでConsequenceの部分にセミコロンを忘れるとテストしたときに終わらなくなるので注意.


parser/parser.go
If関数の時のように,まずPrefixのmapにloopを次のように追加する.

p.registerPrefix(token.LOOP, p.parseLoopExpression)


射影先の関数は以下のようにする.

func (p *Parser) parseLoopExpression() ast.Expression {
    expression := &ast.LoopExpression{Token: p.curToken}
    if !p.expectPeek(token.LPAREN){
        return nil
    }
    p.nextToken()
    expression.Condition = p.parseExpression(LOWEST)
    if !p.expectPeek(token.RPAREN) {
        return nil
    }
    if !p.expectPeek(token.LBRACE) {
        return nil
    }
    expression.Consequence = p.parseBlockStatement()
    return expression
}



Evaluator

evaluator/evaluator.go
Eval関数の分岐に次のようにloopのものを追加する.

func evalLoopExpression(le *ast.LoopExpression, env *object.Environment) object.Object {
    condition := Eval(le.Condition, env)
    fmt.Printf("condition.Type():%s\n", condition.Type())
    fmt.Printf("condition.Inspect():%s\n", condition.Inspect())
    if isError(condition) {
        return condition
    }else if condition.Type() != object.INTEGER_OBJ{
        return NULL
    }
    var res object.Object
    res = NULL
    condition_int, _ := strconv.Atoi(condition.Inspect())
    for isPlus(condition_int) {
        res = Eval(le.Consequence, env)
        fmt.Printf("condition_int: %d\n", condition_int)
        condition_int = condition_int - 1
    }
    return res
}

func isPlus(cnd_i int)bool{
    if cnd_i > 0{
        return true
    }else{
        return false
    }
}


>> while(i < 5){puts("Hello"); let i = i + 1;}
Hello
Hello
Hello
Hello
Hello
>> loop(5){puts("Hello");}
Hello
Hello
Hello
Hello
Hello
res: null
null
>> let a = ["a", "ai", "aiu"];
>> let i = 0;
>> loop(3){len(a[i]); let i = i + 1;}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x18 pc=0x102d20cb4]

goroutine 1 [running]:
monkey/evaluator.evalLoopExpression(0x14000144240, 0x102cca250?)
        /Users/usrname/Go/grad_thesis/01/src/halo/evaluator/evaluator.go:432 +0x214
monkey/evaluator.Eval({0x102d66208?, 0x14000144240?}, 0x1400011a310)
        /Users/usrname/Go/grad_thesis/01/src/halo/evaluator/evaluator.go:88 +0x8e0
monkey/evaluator.Eval({0x102d66078?, 0x1400011cb40?}, 0x1400011a310)
        /Users/usrname/Go/grad_thesis/01/src/halo/evaluator/evaluator.go:21 +0x938
monkey/evaluator.evalProgram(0x103064a68?, 0x30?)
        /Users/usrname/Go/grad_thesis/01/src/halo/evaluator/evaluator.go:96 +0x88
monkey/evaluator.Eval({0x102d66258?, 0x140001280d8?}, 0x1400011a310)
        /Users/usrname/Go/grad_thesis/01/src/halo/evaluator/evaluator.go:19 +0x98
monkey/repl.Start({0x102d65eb8?, 0x14000130000?}, {0x102d65ed8, 0x14000130008})
        /Users/usrname/Go/grad_thesis/01/src/halo/repl/repl.go:34 +0x1c4
main.main()
        /Users/usrname/Go/grad_thesis/01/src/halo/main.go:18 +0xd8
exit status 2


この段階で引数のintの数だけ命令を繰り返すということは一部の命令ではできている,ただし,返り値であるresがNULLになってしまっている.loop中のEvalで渡された結果でさえNULLである.同じ繰り返しの構文であり,正しく動作しているように見えるwhile文でも同様の構造があるので,resの中身を見ようと次のようにしてみたが,

panic: runtime error: invalid memory address or nil pointer dereference

のように表示されREPLが終了してしまう.


func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object {
    condition := Eval(we.Condition, env)
    if isError(condition) {
        return condition
    }
    var res object.Object
    res = NULL
    for isTruethy(condition) {
        res = Eval(we.Consequence, env)
        fmt.Printf("res: %s\n", Eval(we.Consequence, env).Inspect())
        //上のを追加するとエラーが出る.
        condition = Eval(we.Condition, env)
    }
    //fmt.Printf("res: %s\n", res.Inspect())
    //これも同様
    return res
}


対処するためにエラー文で検索を掛けると次のような記事がでてくる


  • nilに何かしらのデータを渡す。

  • nilであるスライス、マップにデータを代入する。

  • nilポインタのデリファレンス(参照)

原因はこの3つらしい.今回の場合は標準出力をしないときはエラーが出ないことを考えると一番下になるのだろうか.しかし「*」は使ってはいない.

type Null struct{}なのにInspect()なんてないということなのだろうか.わからん.

なにか原因がわかる人がいたらコメントか何かで教えて欲しい.

この記事が気に入ったらサポートをしてみませんか?