Golang でプログラミング言語を作る__Part20

Tuyoshi Akiyama
Aug 23, 2017 · 8 min read

前回からの続きになります。今回は、新しいデータタイプを追加していきます。今あるタイプは、数値と論理値のみとなっています。

今回は、文字列を、今までの処理に加えていきます。


流れをおさらいします。

  1. まずは、Tokenタイプに文字を表すものを追加
  2. lexerのテストファイルに、文字列用のテストケースを作成
  3. 上のテストを失敗させたら、それが通るようなlexing処理、つまりソースコードのToken化を加える。値に(“)が来たら、文字列タイプのTokenになります。
  4. テストをpassさせる。
  5. AST内に、文字列を表すノードを設定。これで、内部的に、ソースの文字列が認識されます。
  6. parserの文字列を含んだテストケースを書いて、failさせる
  7. テストが通るparsing(解析)処理を加える。
  8. テストをpassさせる
  9. オブジェクトに、文字列を保持するオブジェクトを追加
  10. evaluatorに文字列を含んだテストケースを書いて、failさせる
  11. 文字列を評価(evaluation)する関数を設定

上の処理に加えて、文字列の連結機能も作っていきます。といっても、既にinfixタイプ(例: 1 + 4や 54 /2と言った式)の評価関数は作られているので、そこに文字列の場合の分岐を加えればOKです。


前提として、今回扱う文字列は、下のようなイメージになります

"<sequence of characters>"

“”で囲まれたものを一つのTokenとして見ていきます。つまり、一連の文字が一つのものとして扱われます。

では、実際に作っていきます。まずはTokenタイプに文字列を加えます。

token/token.go const内に次のコードを入れます。

STRING = "STRING"

次に文字列を含むテストケースを作ります。

次のテスト入力値を加えテストをfailさせます。

"foobar"
"foo bar"

そしたら、次のコードをlexerに加えます。

lexer/lexer.go

func (l *Lexer) NextToken() token.Token { // [...]
switch l.ch { // [...]
case '"':
tok.Type = token.STRING tok.Literal = l.readString()
// [...]
func (l *Lexer) readString() string {
position := l.position + 1
for {
l.readChar()
if l.ch == '"' || l.ch == 0 {
break
}
}
return l.input[position:l.position]
}

ここでlexerのテストがpassすることを確認したら、文字列ソースのToken化は成功です。次はこのTokenをparserに渡します。


ASTに文字列を表すノードを設定します。

ast/ast.go

type StringLiteral struct {
Token token.Token
Value string
}
func (sl *StringLiteral) expressNode() {}
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
func (sl *StringLiteral) String() string { return sl.Token.Literal }

次に、テストケースを書いてきます。

func TestStringLiteralExpression(t *testing.T) {
input := `"hello world";`
l := lexer.New(input)
p := New(l)
program := p.ParseProgram()
checkParserErrors(t, p)
stmt := program.Statements[0].(*ast.ExpressionStatement)
literal, ok := stmt.Expression.(*ast.StringLiteral)
if !ok {
t.Fatalf("exp not *ast.StringLiteral, Got=%T", stmt.Expression)
}
if literal.Value != "hello world" {
t.Errorf("literal.Value not %q, got=%q", "hello world", literal.Value)
}
}

このテストをfailさせたら、次のコードをparser.goに書き込みます

func (p *Parser) parseStringLiteral() ast.Expression {
return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
}

この処理を、parserの初期化関数、 New 内で、文字タイプのTokenと結べつけるのを行う必要があります。New内に、次の登録関数を追加します。

p.registerPrefix(token.STRING, p.parseStringLiteral)

ここでテストがpassしたら、parserの処理は完了です。

次は文字列の評価に移ります。


ここも、今まで通りの処理の流れになります。

以下の記事と同じ実装の仕方になります。

evaluationの処理内容としては、ast.StringLiteralが渡された時に、ストリングタイプのオブジェクトの参照を返しています。

以上の処理を経て、文字列がREPL内で使えることが、確認できると思います。

続いて、文字列の連結機能を付けていきます。まずはテストケースを書いていきます。

evaluator/evaluator_test.go

func TestStringConcatenation(t *testing.T) {
input := `"hello" + " " + "World"`
evaluated := testEval(input)
str, ok := evaluated.(*object.String)
if !ok {
t.Fatalf("object is not string, got=%T (%+v)", evaluated, evaluated)
}
if str.Value != "hello World" {
t.Errorf("string has wrong value, got=%q", str.Value)
}
}

またエラーハンドリングのテスト TestErrorHandling に次の入力値を加えます。+以外の文字列の連結には、エラーがでることのテストになります。

  {
`"hello" - "world"`,
"unknown operator: STRING - STRING",
},

上の2つのテストがfailすることを確認したら、先程言いました、infix用の処理にstring同士の時の分岐処理を加えます。

evaluator/evaluator.go

case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:   
return evalStringInfixExpression(operator, left, right)

上のケースをinfixの関数、 evalInfixExpression のswitch内に入れます。

evalStringInfixExpression は、次の処理になります。

func evalStringInfixExpression(operator string, left, right object.Object) object.Object {
if operator != "+" {
return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type())
}
leftVal := left.(*object.String).Value
rightVal := right.(*object.String).Value
return &object.String{Value: leftVal + rightVal}
}

文字列の連結に+(プラス)以外が用いられた時は、エラーが出されます。

最後にテストを走らせpassすることを確認しましたら、文字列の連結機能が実装されています。

次はBuilt-in関数の実装になります。

)
Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade