diff --git a/ast/ast.go b/ast/ast.go index 037afb2..69978d8 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -391,7 +391,6 @@ type AssignmentStatement struct { func (as AssignmentStatement) TokenLiteral() string { return as.Token.Literal } - func (as AssignmentStatement) String() string { var out bytes.Buffer @@ -403,5 +402,25 @@ func (as AssignmentStatement) String() string { return out.String() } - func (as AssignmentStatement) statementNode() {} + +// Comment a comment +type Comment struct { + Token token.Token // the token.COMMENT token + Value string +} + +func (c *Comment) statementNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (c *Comment) TokenLiteral() string { return c.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (c *Comment) String() string { + var out bytes.Buffer + + out.WriteString(c.TokenLiteral() + " ") + out.WriteString(c.Value) + + return out.String() +} diff --git a/lexer/lexer.go b/lexer/lexer.go index 1554b08..56c09c7 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -7,6 +7,7 @@ type Lexer struct { position int // current position in input (point to current char) readPosition int // current reading position in input (after current char) ch byte // current char under exclamation + prevCh byte // previous char read } func New(input string) *Lexer { @@ -16,6 +17,7 @@ func New(input string) *Lexer { } func (l *Lexer) readChar() { + l.prevCh = l.ch if l.readPosition >= len(l.input) { l.ch = 0 } else { @@ -39,6 +41,9 @@ func (l *Lexer) NextToken() token.Token { l.skipWhitespace() switch l.ch { + case '#': + tok.Type = token.COMMENT + tok.Literal = l.readLine() case '=': if l.peekChar() == '=' { ch := l.ch @@ -69,7 +74,13 @@ func (l *Lexer) NextToken() token.Token { } case '/': - tok = newToken(token.SLASH, l.ch) + if l.peekChar() == '/' { + l.readChar() + tok.Type = token.COMMENT + tok.Literal = l.readLine() + } else { + tok = newToken(token.SLASH, l.ch) + } case '*': tok = newToken(token.ASTERISK, l.ch) case '<': @@ -165,3 +176,14 @@ func (l *Lexer) readString() string { } return l.input[position:l.position] } + +func (l *Lexer) readLine() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '\r' || l.ch == '\n' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index f86a028..7bf104c 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -6,13 +6,15 @@ import ( ) func TestNextToken(t *testing.T) { - input := `let five = 5; + input := `#!./monkey-lang + let five = 5; let ten = 10; let add = fn(x, y) { x + y; }; + # this is a comment let result = add(five, ten); !-/*5; 5 < 10 > 5; @@ -23,6 +25,7 @@ func TestNextToken(t *testing.T) { return false; } + // this is another comment 10 == 10; 10 != 9; "foobar" @@ -35,6 +38,7 @@ func TestNextToken(t *testing.T) { expectedType token.TokenType expectedLiteral string }{ + {token.COMMENT, "!./monkey-lang"}, {token.LET, "let"}, {token.IDENT, "five"}, {token.ASSIGN, "="}, @@ -61,6 +65,7 @@ func TestNextToken(t *testing.T) { {token.SEMICOLON, ";"}, {token.RBRACE, "}"}, {token.SEMICOLON, ";"}, + {token.COMMENT, " this is a comment"}, {token.LET, "let"}, {token.IDENT, "result"}, {token.ASSIGN, "="}, @@ -106,7 +111,7 @@ func TestNextToken(t *testing.T) { {token.SEMICOLON, ";"}, {token.RBRACE, "}"}, - + {token.COMMENT, " this is another comment"}, {token.INT, "10"}, {token.EQ, "=="}, {token.INT, "10"}, diff --git a/object/object.go b/object/object.go index 59a8cbf..51c7958 100644 --- a/object/object.go +++ b/object/object.go @@ -26,7 +26,7 @@ const ( CLOSURE_OBJ = "CLOSURE" ) -// Mutable is the interface for all immutable objects which must implement +// Immutable is the interface for all immutable objects which must implement // the Clone() method used by binding names to values. type Immutable interface { Clone() Object diff --git a/parser/parser.go b/parser/parser.go index cebd535..c6a269c 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -153,6 +153,8 @@ func (p *Parser) parseStatement() ast.Statement { } switch p.curToken.Type { + case token.COMMENT: + return p.parseComment() case token.LET: return p.parseLetStatement() case token.RETURN: @@ -542,3 +544,7 @@ func (p *Parser) parseAssignmentStatement() ast.Statement { return stmt } + +func (p *Parser) parseComment() ast.Statement { + return &ast.Comment{Token: p.curToken, Value: p.curToken.Literal} +} diff --git a/parser/parser_test.go b/parser/parser_test.go index f340e22..0a96c13 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -1126,3 +1126,48 @@ func testAssignmentStatement(t *testing.T, s ast.Statement, name string) bool { return true } + +func TestComments(t *testing.T) { + tests := []struct { + input string + expectedComment string + }{ + {"// foo", " foo"}, + {"#!monkey", "!monkey"}, + {"# foo", " foo"}, + {" # foo", " foo"}, + {" // let x = 1", " let x = 1"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + comment := program.Statements[0] + if !testComment(t, comment, tt.expectedComment) { + return + } + } +} + +func testComment(t *testing.T, s ast.Statement, expected string) bool { + comment, ok := s.(*ast.Comment) + if !ok { + t.Errorf("s not *ast.Comment. got=%T", s) + return false + } + + if comment.Value != expected { + t.Errorf("comment.Value not '%s'. got=%s", expected, comment.Value) + return false + } + + return true +} diff --git a/token/token.go b/token/token.go index aa33aab..256ec7c 100644 --- a/token/token.go +++ b/token/token.go @@ -11,6 +11,9 @@ const ( ILLEGAL = "ILLEGAL" EOF = "EOF" + // COMMENT a line comment, e.g: # this is a comment + COMMENT = "COMMENT" + // Identifiers + literals IDENT = "IDENT" // add, foobar, x, y INT = "INT" // 123456