Add expression parser
parent
d8050c1699
commit
2aae58b670
@ -0,0 +1 @@
|
|||||||
|
5f28a0838ca9382a947da5bd52756cf3
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
# https://taskfile.dev
|
||||||
|
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
APP_NAME: golox
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- task: run
|
||||||
|
|
||||||
|
run:
|
||||||
|
desc: "Run the interpreter"
|
||||||
|
deps: [build]
|
||||||
|
cmds:
|
||||||
|
- ./bin/{{.APP_NAME}} {{.CLI_ARGS}}
|
||||||
|
|
||||||
|
build:
|
||||||
|
desc: "Build the interpreter"
|
||||||
|
deps: [astgen]
|
||||||
|
cmds:
|
||||||
|
- go build -o bin/{{.APP_NAME}} cmd/golox/main.go
|
||||||
|
|
||||||
|
test:
|
||||||
|
desc: "Run tests"
|
||||||
|
deps: [astgen]
|
||||||
|
cmds:
|
||||||
|
- go test -v ./...
|
||||||
|
|
||||||
|
astgen:
|
||||||
|
desc: "Generate AST nodes"
|
||||||
|
cmds:
|
||||||
|
- go run cmd/astgen/main.go "./parser"
|
||||||
|
sources:
|
||||||
|
- cmd/astgen/main.go
|
||||||
|
generates:
|
||||||
|
- parser/expr.go
|
||||||
|
|
||||||
|
clean:
|
||||||
|
desc: "Clean up"
|
||||||
|
cmds:
|
||||||
|
- rm -rf bin/*
|
||||||
|
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"golox/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Printer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Printer {
|
||||||
|
return &Printer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *Printer) Print(expr parser.Expr) string {
|
||||||
|
return expr.Accept(ap).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *Printer) VisitBinaryExpr(expr *parser.BinaryExpr) any {
|
||||||
|
return ap.parenthesize(expr.Operator.Lexeme, expr.Left, expr.Right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *Printer) VisitGroupingExpr(expr *parser.GroupingExpr) any {
|
||||||
|
return ap.parenthesize("group", expr.Expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *Printer) VisitLiteralExpr(expr *parser.LiteralExpr) any {
|
||||||
|
if expr.Value == nil {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
return fmt.Sprint(expr.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *Printer) VisitUnaryExpr(expr *parser.UnaryExpr) any {
|
||||||
|
return ap.parenthesize(expr.Operator.Lexeme, expr.Right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *Printer) VisitErrorExpr(expr *parser.ErrorExpr) any {
|
||||||
|
return expr.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *Printer) parenthesize(name string, exprs ...parser.Expr) string {
|
||||||
|
str := "(" + name
|
||||||
|
|
||||||
|
for _, expr := range exprs {
|
||||||
|
str += " " + expr.Accept(ap).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return str + ")"
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Println("Usage: astgen <output_directory>")
|
||||||
|
os.Exit(64)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := os.Args[1]
|
||||||
|
|
||||||
|
if _, err := os.Stat(d); os.IsNotExist(err) {
|
||||||
|
fmt.Println("Directory does not exist")
|
||||||
|
os.Exit(74)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Generating AST classes in", d)
|
||||||
|
|
||||||
|
defineAst(d, "Expr", []string{
|
||||||
|
"Error : Value string",
|
||||||
|
"Binary : Left Expr, Operator token.Token, Right Expr",
|
||||||
|
"Grouping : Expression Expr",
|
||||||
|
"Literal : Value any",
|
||||||
|
"Unary : Operator token.Token, Right Expr",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func defineAst(outputDir, baseName string, types []string) {
|
||||||
|
path := outputDir + "/" + strings.ToLower(baseName) + ".go"
|
||||||
|
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error creating file", path)
|
||||||
|
os.Exit(74)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
file.WriteString("package parser\n\n")
|
||||||
|
file.WriteString("import \"golox/token\"\n\n")
|
||||||
|
defineVisitor(file, baseName, types)
|
||||||
|
|
||||||
|
file.WriteString("type " + baseName + " interface {\n")
|
||||||
|
file.WriteString(" Accept(visitor " + baseName + "Visitor[any]) any\n")
|
||||||
|
file.WriteString("}\n\n")
|
||||||
|
|
||||||
|
for _, t := range types {
|
||||||
|
defineType(file, baseName, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defineVisitor(file *os.File, baseName string, types []string) {
|
||||||
|
file.WriteString("type " + baseName + "Visitor[T any] interface {\n")
|
||||||
|
|
||||||
|
for _, t := range types {
|
||||||
|
typeName := strings.TrimSpace(t[:strings.Index(t, ":")-1])
|
||||||
|
file.WriteString(" Visit" + typeName + baseName + "(" + strings.ToLower(typeName) + " *" + typeName + baseName + ") T\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
file.WriteString("}\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func defineType(file *os.File, baseName, typeString string) {
|
||||||
|
typeName := strings.TrimSpace(typeString[:strings.Index(typeString, ":")-1])
|
||||||
|
fields := strings.TrimSpace(typeString[strings.Index(typeString, ":")+1:])
|
||||||
|
|
||||||
|
file.WriteString("type " + typeName + baseName + " struct {\n")
|
||||||
|
|
||||||
|
for _, field := range strings.Split(fields, ", ") {
|
||||||
|
file.WriteString(" " + field + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
file.WriteString("}\n\n")
|
||||||
|
file.WriteString("func (t *" + typeName + baseName + ") Accept(visitor " + baseName + "Visitor[any]) any {\n")
|
||||||
|
file.WriteString(" return visitor.Visit" + typeName + baseName + "(t)\n")
|
||||||
|
file.WriteString("}\n\n")
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import "golox/token"
|
||||||
|
|
||||||
|
type Logger interface {
|
||||||
|
Error(line int, message string)
|
||||||
|
ErrorAtToken(t token.Token, message string)
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import "golox/token"
|
||||||
|
|
||||||
|
type ExprVisitor[T any] interface {
|
||||||
|
VisitErrorExpr(error *ErrorExpr) T
|
||||||
|
VisitBinaryExpr(binary *BinaryExpr) T
|
||||||
|
VisitGroupingExpr(grouping *GroupingExpr) T
|
||||||
|
VisitLiteralExpr(literal *LiteralExpr) T
|
||||||
|
VisitUnaryExpr(unary *UnaryExpr) T
|
||||||
|
}
|
||||||
|
|
||||||
|
type Expr interface {
|
||||||
|
Accept(visitor ExprVisitor[any]) any
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorExpr struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorExpr) Accept(visitor ExprVisitor[any]) any {
|
||||||
|
return visitor.VisitErrorExpr(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BinaryExpr struct {
|
||||||
|
Left Expr
|
||||||
|
Operator token.Token
|
||||||
|
Right Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BinaryExpr) Accept(visitor ExprVisitor[any]) any {
|
||||||
|
return visitor.VisitBinaryExpr(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupingExpr struct {
|
||||||
|
Expression Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *GroupingExpr) Accept(visitor ExprVisitor[any]) any {
|
||||||
|
return visitor.VisitGroupingExpr(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LiteralExpr struct {
|
||||||
|
Value any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *LiteralExpr) Accept(visitor ExprVisitor[any]) any {
|
||||||
|
return visitor.VisitLiteralExpr(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnaryExpr struct {
|
||||||
|
Operator token.Token
|
||||||
|
Right Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *UnaryExpr) Accept(visitor ExprVisitor[any]) any {
|
||||||
|
return visitor.VisitUnaryExpr(t)
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,207 @@
|
|||||||
|
/* Description: This file contains the recursivde descent parser implementation.
|
||||||
|
* The parser is responsible for parsing the tokens generated by the scanner.
|
||||||
|
*
|
||||||
|
* The grammar is as follows:
|
||||||
|
* expression → equality ;
|
||||||
|
* equality → comparison ( ( "!=" | "==" ) comparison )* ;
|
||||||
|
* comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
|
||||||
|
* term → factor ( ( "-" | "+" ) factor )* ;
|
||||||
|
* factor → unary ( ( "/" | "*" ) unary )* ;
|
||||||
|
* unary → ( "!" | "-" ) unary
|
||||||
|
* | primary ;
|
||||||
|
* primary → NUMBER | STRING | "true" | "false" | "nil"
|
||||||
|
* | "(" expression ")" ;
|
||||||
|
*/
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golox/errors"
|
||||||
|
"golox/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parser is a recursive descent parser.
|
||||||
|
type Parser struct {
|
||||||
|
tokens []token.Token
|
||||||
|
current int
|
||||||
|
errLogger errors.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Parser.
|
||||||
|
func New(tokens []token.Token, el errors.Logger) *Parser {
|
||||||
|
return &Parser{tokens: tokens, current: 0, errLogger: el}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the tokens and returns the AST.
|
||||||
|
func (p *Parser) Parse() Expr {
|
||||||
|
return p.expression()
|
||||||
|
}
|
||||||
|
|
||||||
|
// expression → equality ;
|
||||||
|
func (p *Parser) expression() Expr {
|
||||||
|
return p.equality()
|
||||||
|
}
|
||||||
|
|
||||||
|
// equality → comparison ( ( "!=" | "==" ) comparison )* ;
|
||||||
|
func (p *Parser) equality() Expr {
|
||||||
|
expr := p.comparison()
|
||||||
|
|
||||||
|
for p.match(token.BANG_EQUAL, token.EQUAL_EQUAL) {
|
||||||
|
operator := p.previous()
|
||||||
|
right := p.comparison()
|
||||||
|
expr = &BinaryExpr{Left: expr, Operator: operator, Right: right}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
|
||||||
|
func (p *Parser) comparison() Expr {
|
||||||
|
expr := p.term()
|
||||||
|
|
||||||
|
for p.match(token.GREATER, token.GREATER_EQUAL, token.LESS, token.LESS_EQUAL) {
|
||||||
|
operator := p.previous()
|
||||||
|
right := p.term()
|
||||||
|
expr = &BinaryExpr{Left: expr, Operator: operator, Right: right}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// term → factor ( ( "-" | "+" ) factor )* ;
|
||||||
|
func (p *Parser) term() Expr {
|
||||||
|
expr := p.factor()
|
||||||
|
|
||||||
|
for p.match(token.MINUS, token.PLUS) {
|
||||||
|
operator := p.previous()
|
||||||
|
right := p.factor()
|
||||||
|
expr = &BinaryExpr{Left: expr, Operator: operator, Right: right}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// factor → unary ( ( "/" | "*" ) unary )* ;
|
||||||
|
func (p *Parser) factor() Expr {
|
||||||
|
expr := p.unary()
|
||||||
|
|
||||||
|
for p.match(token.SLASH, token.STAR) {
|
||||||
|
operator := p.previous()
|
||||||
|
right := p.unary()
|
||||||
|
expr = &BinaryExpr{Left: expr, Operator: operator, Right: right}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// unary → ( "!" | "-" ) unary | primary ;
|
||||||
|
func (p *Parser) unary() Expr {
|
||||||
|
if p.match(token.BANG, token.MINUS) {
|
||||||
|
operator := p.previous()
|
||||||
|
right := p.unary()
|
||||||
|
return &UnaryExpr{Operator: operator, Right: right}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.primary()
|
||||||
|
}
|
||||||
|
|
||||||
|
// primary → NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" ;
|
||||||
|
func (p *Parser) primary() Expr {
|
||||||
|
switch {
|
||||||
|
case p.match(token.FALSE):
|
||||||
|
return &LiteralExpr{Value: false}
|
||||||
|
case p.match(token.TRUE):
|
||||||
|
return &LiteralExpr{Value: true}
|
||||||
|
case p.match(token.NIL):
|
||||||
|
return &LiteralExpr{Value: nil}
|
||||||
|
case p.match(token.NUMBER, token.STRING):
|
||||||
|
return &LiteralExpr{Value: p.previous().Literal}
|
||||||
|
case p.match(token.LEFT_PAREN):
|
||||||
|
expr := p.expression()
|
||||||
|
err := p.consume(token.RIGHT_PAREN, "Expect ')' after expression.")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return &GroupingExpr{Expression: expr}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.newErrorExpr(p.peek(), "Expect expression.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// match checks if the current token is any of the given types.
|
||||||
|
func (p *Parser) match(types ...token.TokenType) bool {
|
||||||
|
for _, t := range types {
|
||||||
|
if p.check(t) {
|
||||||
|
p.advance()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume consumes the current token if it is of the given type.
|
||||||
|
func (p *Parser) consume(tt token.TokenType, message string) *ErrorExpr {
|
||||||
|
if p.check(tt) {
|
||||||
|
p.advance()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.newErrorExpr(p.peek(), message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check checks if the current token is of the given type.
|
||||||
|
func (p *Parser) check(tt token.TokenType) bool {
|
||||||
|
if p.isAtEnd() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.peek().Type == tt
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance advances the current token and returns the previous token.
|
||||||
|
func (p *Parser) advance() token.Token {
|
||||||
|
if !p.isAtEnd() {
|
||||||
|
p.current++
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.previous()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAtEnd checks if the parser has reached the end of the tokens.
|
||||||
|
func (p *Parser) isAtEnd() bool {
|
||||||
|
return p.peek().Type == token.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns the current token.
|
||||||
|
func (p *Parser) peek() token.Token {
|
||||||
|
return p.tokens[p.current]
|
||||||
|
}
|
||||||
|
|
||||||
|
// previous returns the previous token.
|
||||||
|
func (p *Parser) previous() token.Token {
|
||||||
|
return p.tokens[p.current-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// newErrorExpr creates a new ErrorExpr and reports the error.
|
||||||
|
func (p *Parser) newErrorExpr(t token.Token, message string) *ErrorExpr {
|
||||||
|
p.errLogger.ErrorAtToken(t, message)
|
||||||
|
return &ErrorExpr{Value: message}
|
||||||
|
}
|
||||||
|
|
||||||
|
// synchronize synchronizes the parser after an error.
|
||||||
|
func (p *Parser) synchronize() {
|
||||||
|
p.advance()
|
||||||
|
|
||||||
|
for !p.isAtEnd() {
|
||||||
|
if p.previous().Type == token.SEMICOLON {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.peek().Type {
|
||||||
|
case token.CLASS, token.FUN, token.VAR, token.FOR, token.IF, token.WHILE, token.PRINT, token.RETURN:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.advance()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue