Browse Source

§2.5 parsing return statements. One pass of golint cleanup.

Frederic G. MARAND 5 years ago
parent
commit
3f83d07e0a
13 changed files with 268 additions and 134 deletions
  1. 9 26
      ast/ast.go
  2. 17 0
      ast/identifier.go
  3. 17 0
      ast/let.go
  4. 17 0
      ast/return.go
  5. 9 4
      lexer/lexer.go
  6. 13 36
      parser/parser.go
  7. 38 0
      parser/parser_let.go
  8. 73 0
      parser/parser_let_test.go
  9. 24 0
      parser/parser_return.go
  10. 43 0
      parser/parser_return_test.go
  11. 1 68
      parser/parser_test.go
  12. 3 0
      repl/repl.go
  13. 4 0
      token/token.go

+ 9 - 26
ast/ast.go

@@ -1,54 +1,37 @@
 package ast
 
-import "code.osinet.fr/fgm/waiig15/token"
-
-// Every node in the AST implements Node.
+// Node is the interface implemented by every node in the AST.
 type Node interface {
 	// TokenLiteral returns the literal value of the token it's associated to.
 	TokenLiteral() string
 }
 
+// Statement extends Node and is only a way to differentiate between expressions
+// and statements.
 type Statement interface {
-	// Expression extends Node.
 	Node
 	statementNode()
 }
 
+// Expression extends Node and is only a way to differentiate between
+// expressions and statements.
 type Expression interface {
 	// Expression extends Node.
 	Node
 	expressionNode()
 }
 
+// Program represents the outer structure of the parser program.
 type Program struct {
 	Statements []Statement
 }
 
+// TokenLiteral returns the string contents of the first statement token, which
+// can be an empty string if the program is empty.
 func (p *Program) TokenLiteral() string {
 	if len(p.Statements) > 0 {
 		return p.Statements[0].TokenLiteral()
-	} else {
-		return ""
 	}
-}
-
-type LetStatement struct {
-	Token token.Token // the token.LET token. Why do we need it ?
-	Name  *Identifier
-	Value Expression
-}
-
-func (ls *LetStatement) statementNode() {}
-func (ls *LetStatement) TokenLiteral() string {
-	return ls.Token.Literal
-}
-
-type Identifier struct {
-	Token token.Token // the token.IDENT token. Why do we need it ?
-	Value string      // The identifier string.
-}
 
-func (i *Identifier) expressionNode() {}
-func (i *Identifier) TokenLiteral() string {
-	return i.Token.Literal
+	return ""
 }

+ 17 - 0
ast/identifier.go

@@ -0,0 +1,17 @@
+package ast
+
+import "code.osinet.fr/fgm/waiig15/token"
+
+// Identifier is the Node type for identifiers.
+type Identifier struct {
+	Token token.Token // the token.IDENT token. Why do we need it ?
+	Value string      // The identifier string.
+}
+
+func (i *Identifier) expressionNode() {}
+
+// TokenLiteral satisfies the Node interface.
+func (i *Identifier) TokenLiteral() string {
+	return i.Token.Literal
+}
+

+ 17 - 0
ast/let.go

@@ -0,0 +1,17 @@
+package ast
+
+import "code.osinet.fr/fgm/waiig15/token"
+
+// LetStatement is the Node type for Let statements.
+type LetStatement struct {
+	Token token.Token // the token.LET token. Why do we need it ?
+	Name  *Identifier
+	Value Expression
+}
+
+func (ls *LetStatement) statementNode() {}
+
+// TokenLiteral satisfies the Node interface.
+func (ls *LetStatement) TokenLiteral() string {
+	return ls.Token.Literal
+}

+ 17 - 0
ast/return.go

@@ -0,0 +1,17 @@
+package ast
+
+import "code.osinet.fr/fgm/waiig15/token"
+
+// ReturnStatement fulfills the Node and Statement interfaces.
+type ReturnStatement struct {
+	Token       token.Token // the token.RETURN token. Why do we need it ?
+	Name        *Identifier
+	ReturnValue Expression
+}
+
+func (rs *ReturnStatement) statementNode() {}
+
+// TokenLiteral satisfies the Node interface.
+func (rs *ReturnStatement) TokenLiteral() string {
+	return rs.Token.Literal
+}

+ 9 - 4
lexer/lexer.go

@@ -1,5 +1,5 @@
 /*
-Lexer only supports single-byte character sets like ASCII.
+Package lexer only supports single-byte character sets like ASCII.
 
 @TODO convert to Unicode / UTF-8.
 */
@@ -9,6 +9,7 @@ import (
 	"code.osinet.fr/fgm/waiig15/token"
 )
 
+// Lexer implements the lexing mechanism.
 type Lexer struct {
 	input        string
 	position     int  // current position in input (points to current char)
@@ -16,6 +17,8 @@ type Lexer struct {
 	ch           byte // current char under examination
 }
 
+// New returns a new Lexer instance with the first character in the input
+// already read.
 func New(input string) *Lexer {
 	l := &Lexer{input: input}
 	l.readChar()
@@ -30,9 +33,11 @@ func (l *Lexer) readChar() {
 		l.ch = l.input[l.readPosition]
 	}
 	l.position = l.readPosition
-	l.readPosition += 1
+	l.readPosition++
 }
 
+// NextToken advances in the input by one token, skipping all whitespace. It
+// returns that token. In case of a lexing error it return an ILLEGAL token.
 func (l *Lexer) NextToken() token.Token {
 	var tok token.Token
 
@@ -111,9 +116,9 @@ func newToken(tokenType token.TokenType, ch byte) token.Token {
 func (l *Lexer) peekChar() byte {
 	if l.readPosition >= len(l.input) {
 		return 0
-	} else {
-		return l.input[l.readPosition]
 	}
+
+	return l.input[l.readPosition]
 }
 
 func (l *Lexer) readIdentifier() string {

+ 13 - 36
parser/parser.go

@@ -1,12 +1,14 @@
 package parser
 
 import (
+	"fmt"
+
 	"code.osinet.fr/fgm/waiig15/ast"
 	"code.osinet.fr/fgm/waiig15/lexer"
 	"code.osinet.fr/fgm/waiig15/token"
-	"fmt"
 )
 
+// Parser implements the parsing mechanism top-level layer.
 type Parser struct {
 	errors []string
 	l      *lexer.Lexer
@@ -15,6 +17,8 @@ type Parser struct {
 	peekToken token.Token
 }
 
+// New returns a new Parser instance with the first two parser tokens already
+// loaded.
 func New(l *lexer.Lexer) *Parser {
 	p := &Parser{
 		l:      l,
@@ -28,10 +32,13 @@ func New(l *lexer.Lexer) *Parser {
 	return p
 }
 
+// Errors is a getter for Parser.errors.
 func (p *Parser) Errors() []string {
 	return p.errors
 }
 
+// ParseProgram is the outermost parsing logic, accumulating statements in a
+// Program instance and returning that instance once parsing is done.
 func (p *Parser) ParseProgram() *ast.Program {
 	program := &ast.Program{
 		Statements: []ast.Statement{},
@@ -58,10 +65,10 @@ func (p *Parser) expectPeek(t token.TokenType) bool {
 	if p.peekTokenIs(t) {
 		p.nextToken()
 		return true
-	} else {
-		p.peekError(t)
-		return false
 	}
+
+	p.peekError(t)
+	return false
 }
 
 func (p *Parser) nextToken() {
@@ -69,41 +76,12 @@ func (p *Parser) nextToken() {
 	p.peekToken = p.l.NextToken()
 }
 
-func (p *Parser) parseLetStatement() *ast.LetStatement {
-	stmt := &ast.LetStatement{
-		Token: p.curToken,
-	}
-
-	// Let statement starts with an IDENT token, so if next token is not an
-	// IDENT, the next statement cannot be a Let statement.
-	if !p.expectPeek(token.IDENT) {
-		return nil
-	}
-
-	stmt.Name = &ast.Identifier{
-		Token: p.curToken,
-		Value: p.curToken.Literal,
-	}
-
-	// The previous expectPeek() call fetched the next token, so we should now
-	// be on the assignment.
-	if !p.expectPeek(token.ASSIGN) {
-		return nil
-	}
-
-	// Skip the expression for now, progress to the semicolon terminating the
-	// statement.
-	for !p.curTokenIs(token.SEMICOLON) {
-		p.nextToken()
-	}
-
-	return stmt
-}
-
 func (p *Parser) parseStatement() ast.Statement {
 	switch p.curToken.Type {
 	case token.LET:
 		return p.parseLetStatement()
+	case token.RETURN:
+		return p.parseReturnStatement()
 	default:
 		return nil
 	}
@@ -122,4 +100,3 @@ func (p *Parser) peekError(t token.TokenType) {
 func (p *Parser) peekTokenIs(t token.TokenType) bool {
 	return p.peekToken.Type == t
 }
-

+ 38 - 0
parser/parser_let.go

@@ -0,0 +1,38 @@
+package parser
+
+import (
+	"code.osinet.fr/fgm/waiig15/ast"
+	"code.osinet.fr/fgm/waiig15/token"
+)
+
+func (p *Parser) parseLetStatement() *ast.LetStatement {
+	stmt := &ast.LetStatement{
+		Token: p.curToken,
+	}
+
+	// Let statement starts with an IDENT token, so if next token is not an
+	// IDENT, the next statement cannot be a Let statement.
+	if !p.expectPeek(token.IDENT) {
+		return nil
+	}
+
+	stmt.Name = &ast.Identifier{
+		Token: p.curToken,
+		Value: p.curToken.Literal,
+	}
+
+	// The previous expectPeek() call fetched the next token, so we should now
+	// be on the assignment.
+	if !p.expectPeek(token.ASSIGN) {
+		return nil
+	}
+
+	// Skip the expression for now, progress to the semicolon terminating the
+	// statement.
+	for !p.curTokenIs(token.SEMICOLON) {
+		p.nextToken()
+	}
+
+	return stmt
+}
+

+ 73 - 0
parser/parser_let_test.go

@@ -0,0 +1,73 @@
+package parser
+
+import (
+	"testing"
+	"code.osinet.fr/fgm/waiig15/lexer"
+	"code.osinet.fr/fgm/waiig15/ast"
+)
+
+func TestLetStatements(t *testing.T) {
+	// Try removing the ident, the =, or both, to get human-readable errors.
+	input := `
+let x = 5;
+let y = 10;
+let foobar = 838383;
+`
+	l := lexer.New(input)
+	p := New(l)
+
+	program := p.ParseProgram()
+	checkParserErrors(t, p)
+	if program == nil {
+		t.Fatalf("ParseProgram() returned nil.")
+	}
+
+	if len(program.Statements) != 3 {
+		t.Fatalf("program.Statements does not contain 3 statements, got=%d",
+			len(program.Statements))
+	}
+
+	tests := []struct {
+		expectedIdentifier string
+	}{
+		{"x"},
+		{"y"},
+		{"foobar"},
+	}
+
+	for i, tt := range tests {
+		stmt := program.Statements[i]
+		if !testLetStatement(t, stmt, tt.expectedIdentifier) {
+			return
+		}
+	}
+}
+
+func testLetStatement(t *testing.T, s ast.Statement, name string) bool {
+	if s.TokenLiteral() != "let" {
+		t.Errorf("s.TokenLiteral not 'let', got=%q", s.TokenLiteral())
+		return false
+	}
+
+	// Statement is an interface, we need a concrete type for the value, and we
+	// just determined this looked like a LetStatement.
+	letStmt, ok := s.(*ast.LetStatement)
+	if !ok {
+		t.Errorf("s not *ast.LetStatement{}, got=%T", s)
+	}
+
+	if letStmt.Name.Value != name {
+		t.Errorf("letStmt.Name.Value not %s, got=%s",
+			name, letStmt.Name.Value)
+		return false
+	}
+
+	if letStmt.Name.TokenLiteral() != name {
+		t.Errorf("letStmt.Name.TokenLiteral not %s, got=%s",
+			name, letStmt.Name.TokenLiteral())
+		return false
+	}
+
+	return true
+}
+

+ 24 - 0
parser/parser_return.go

@@ -0,0 +1,24 @@
+package parser
+
+import (
+	"code.osinet.fr/fgm/waiig15/ast"
+	"code.osinet.fr/fgm/waiig15/token"
+)
+
+func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
+	stmt := &ast.ReturnStatement{
+		Token: p.curToken,
+	}
+
+	// There should be an expression to consume here.
+	p.nextToken()
+
+	// Skip the expression for now, progress to the semicolon terminating the
+	// statement.
+	for !p.curTokenIs(token.SEMICOLON) {
+		p.nextToken()
+	}
+
+	return stmt
+}
+

+ 43 - 0
parser/parser_return_test.go

@@ -0,0 +1,43 @@
+package parser
+
+import (
+	"testing"
+	"code.osinet.fr/fgm/waiig15/lexer"
+	"code.osinet.fr/fgm/waiig15/ast"
+)
+
+func TestReturnStatements(t *testing.T) {
+	// Try removing the ident, the =, or both, to get human-readable errors.
+	input := `
+return 5;
+return 10;
+return 993322;
+`
+	l := lexer.New(input)
+	p := New(l)
+
+	program := p.ParseProgram()
+	checkParserErrors(t, p)
+	if program == nil {
+		t.Fatalf("ParseProgram() returned nil.")
+	}
+
+	if len(program.Statements) != 3 {
+		t.Fatalf("program.Statements does not contain 3 statements, got=%d",
+			len(program.Statements))
+	}
+
+	for _, stmt := range program.Statements {
+		// Statement is an interface, we need a concrete type for the value, and
+		// our test input only contains Let statements.
+		returnStmt, ok := stmt.(*ast.ReturnStatement)
+		if !ok {
+			t.Errorf("s not *ast.ReturnStatement{}, got=%T", stmt)
+			continue
+		}
+		if returnStmt.TokenLiteral() != "return" {
+			t.Errorf("s.TokenLiteral not 'return', got=%q",
+				stmt.TokenLiteral())
+		}
+	}
+}

+ 1 - 68
parser/parser_test.go

@@ -1,9 +1,7 @@
 package parser
 
 import (
-	"code.osinet.fr/fgm/waiig15/ast"
-	"code.osinet.fr/fgm/waiig15/lexer"
-	"testing"
+			"testing"
 )
 
 func checkParserErrors(t *testing.T, p *Parser) {
@@ -18,68 +16,3 @@ func checkParserErrors(t *testing.T, p *Parser) {
 	}
 	t.FailNow()
 }
-
-func TestLetStatements(t *testing.T) {
-	// Try removing the ident, the =, or both, to get human-readable errors.
-	input := `
-let x = 5;
-let y = 10;
-let foobar = 838383;
-`
-	l := lexer.New(input)
-	p := New(l)
-
-	program := p.ParseProgram()
-	checkParserErrors(t, p)
-	if program == nil {
-		t.Fatalf("ParseProgram() returned nil.")
-	}
-
-	if len(program.Statements) != 3 {
-		t.Fatalf("program.Statements does not contain 3 statements, got=%d",
-			len(program.Statements))
-	}
-
-	tests := []struct {
-		expectedIdentifier string
-	}{
-		{"x"},
-		{"y"},
-		{"foobar"},
-	}
-
-	for i, tt := range tests {
-		stmt := program.Statements[i]
-		if !testLetStatement(t, stmt, tt.expectedIdentifier) {
-			return
-		}
-	}
-}
-
-func testLetStatement(t *testing.T, s ast.Statement, name string) bool {
-	if s.TokenLiteral() != "let" {
-		t.Errorf("s.TokenLiteral not 'let', got=%q", s.TokenLiteral())
-		return false
-	}
-
-	// Statement is an interface, we need a concrete type for the value, and we
-	// just determined this looked like a LetStatement.
-	letStmt, ok := s.(*ast.LetStatement)
-	if !ok {
-		t.Errorf("s not *ast.LetStatement{}, got=%T", s)
-	}
-
-	if letStmt.Name.Value != name {
-		t.Errorf("letStmt.Name.Value not %s, got=%s",
-			name, letStmt.Name.Value)
-		return false
-	}
-
-	if letStmt.Name.TokenLiteral() != name {
-		t.Errorf("letStmt.Name.TokenLiteral not %s, got=%s",
-			name, letStmt.Name.TokenLiteral())
-		return false
-	}
-
-	return true
-}

+ 3 - 0
repl/repl.go

@@ -8,8 +8,11 @@ import (
 	"io"
 )
 
+// PROMPT is the prompt shown to the user of the REPL to invite them to type
+// some code.
 const PROMPT = ">> "
 
+// Start implements the Read-Eval-Print Loop.
 func Start(in io.Reader, out io.Writer) {
 	scanner := bufio.NewScanner(in)
 

+ 4 - 0
token/token.go

@@ -1,12 +1,16 @@
 package token
 
+// TokenType is the string representation of the Token types.
 type TokenType string
 
+
+// Token represents a Parser token.
 type Token struct {
 	Type    TokenType
 	Literal string
 }
 
+// The TokenType values.
 const (
 	ILLEGAL = "ILLEGAL"
 	EOF     = "EOF"