Skip to content

Commit

Permalink
Merge pull request #21 from JunNishimura/feature/add_quote_function
Browse files Browse the repository at this point in the history
add quote function
  • Loading branch information
JunNishimura authored Jul 7, 2024
2 parents d884e3d + ec75520 commit 265ceff
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 45 deletions.
25 changes: 24 additions & 1 deletion ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,30 @@ type ConsCell struct {
}

func (cc *ConsCell) String() string {
return fmt.Sprintf("(%s . %s)", cc.CarField.String(), cc.CdrField.String())
var out bytes.Buffer

out.WriteString("(")

consCell := cc
for {
out.WriteString(consCell.Car().String())

if _, ok := consCell.Cdr().(*Nil); ok {
break
}

if cdr, ok := consCell.Cdr().(*ConsCell); ok {
out.WriteString(" ")
consCell = cdr
} else {
out.WriteString(" . ")
out.WriteString(consCell.Cdr().String())
break
}
}
out.WriteString(")")

return out.String()
}
func (cc *ConsCell) Car() SExpression { return cc.CarField }
func (cc *ConsCell) Cdr() SExpression { return cc.CdrField }
Expand Down
14 changes: 9 additions & 5 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ func evalMinusPrefix(right object.Object) object.Object {
}

func evalSymbol(symbol *ast.Symbol, env *object.Environment) object.Object {
if specialForm, ok := specialForms[symbol.Value]; ok {
return specialForm
}

if val, ok := env.Get(symbol.Value); ok {
return val
}
Expand All @@ -102,17 +106,17 @@ func evalSymbol(symbol *ast.Symbol, env *object.Environment) object.Object {

// evaluate cdr of the cons cell as arguments to the command car
func evalList(sexp *ast.ConsCell, env *object.Environment) object.Object {
// check if the car is a special form
if specialForm, ok := sexp.Car().(*ast.SpecialForm); ok {
return specialForms[specialForm.TokenLiteral()].Fn(sexp.Cdr(), env)
}

// Evaluate the car of the cons cell
car := Eval(sexp.Car(), env)
if isError(car) {
return car
}

// check if the car is a special form
if specialForm, ok := car.(*object.SpecialForm); ok {
return specialForm.Fn(sexp.Cdr(), env)
}

// Evaluate the arguments
args := evalArgs(sexp.Cdr(), env)
if len(args) == 1 && isError(args[0]) {
Expand Down
17 changes: 0 additions & 17 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,3 @@ func TestEvalIntegerExpression(t *testing.T) {
testIntegerObject(t, evaluated, tt.expected)
}
}

func TestLambdaExpression(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"((lambda () 5))", 5},
{"((lambda (x) x) 5)", 5},
{"((lambda (x y) (+ x y)) 5 5)", 10},
{"(+ ((lambda () 1)) ((lambda (x y) (+ x y)) 1 2))", 4},
}

for _, tt := range tests {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
}
}
12 changes: 12 additions & 0 deletions evaluator/specialform.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ var specialForms = map[string]*object.SpecialForm{
return &object.Function{Parameters: params, Body: body, Env: env}
},
},
"quote": {
Fn: func(sexp ast.SExpression, env *object.Environment) object.Object {
consCell, ok := sexp.(*ast.ConsCell)
if !ok {
return newError("quote must be a cons cell, got %T", sexp)
}

return &object.Quote{
SExpression: consCell.Car(),
}
},
},
}

func evalLambdaParams(sexp ast.SExpression) ([]*ast.Symbol, error) {
Expand Down
43 changes: 43 additions & 0 deletions evaluator/specialform_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package evaluator

import "testing"

func TestLambdaExpression(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"((lambda () 5))", 5},
{"((lambda (x) x) 5)", 5},
{"((lambda (x y) (+ x y)) 5 5)", 10},
{"(+ ((lambda () 1)) ((lambda (x y) (+ x y)) 1 2))", 4},
}

for _, tt := range tests {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
}
}

func TestQuote(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"'5", "5"},
{"'-5", "-5"},
{"'(+ 1 2)", "(+ 1 2)"},
{"'(+ . (1 . (2 . nil)))", "(+ 1 2)"},
{"(quote 5)", "5"},
{"(quote -5)", "-5"},
{"(quote (+ 1 2))", "(+ 1 2)"},
{"(quote (+ . (1 . (2 . nil))))", "(+ 1 2)"},
}

for _, tt := range tests {
evaluated := testEval(tt.input)
if evaluated.Inspect() != tt.expected {
t.Errorf("expected=%q, got=%q", tt.expected, evaluated.Inspect())
}
}
}
6 changes: 2 additions & 4 deletions lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,14 @@ func (l *Lexer) NextToken() token.Token {
}
case '.':
tok = newToken(token.DOT, l.curChar)
case '\'':
tok = newToken(token.QUOTE, l.curChar)
case 0:
tok.Literal = ""
tok.Type = token.EOF
default:
if isLetter(l.curChar) || isSpecialChar(l.curChar) {
tok.Literal = l.readString()
if spForm, ok := token.LookupSpecialForm(tok.Literal); ok {
tok.Type = spForm
return tok
}
tok.Type = token.LookupSymbol(tok.Literal)
return tok
} else if isDigit(l.curChar) {
Expand Down
9 changes: 8 additions & 1 deletion lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func TestNextToken(t *testing.T) {
+(+ 1 2)
-(- 3 4)
(lambda (x) (+ x 1))
'(1 2 3)
`

tests := []struct {
Expand Down Expand Up @@ -76,7 +77,7 @@ func TestNextToken(t *testing.T) {
{token.INT, "4"},
{token.RPAREN, ")"},
{token.LPAREN, "("},
{token.LAMBDA, "lambda"},
{token.SYMBOL, "lambda"},
{token.LPAREN, "("},
{token.SYMBOL, "x"},
{token.RPAREN, ")"},
Expand All @@ -86,6 +87,12 @@ func TestNextToken(t *testing.T) {
{token.INT, "1"},
{token.RPAREN, ")"},
{token.RPAREN, ")"},
{token.QUOTE, "'"},
{token.LPAREN, "("},
{token.INT, "1"},
{token.INT, "2"},
{token.INT, "3"},
{token.RPAREN, ")"},
{token.EOF, ""},
}

Expand Down
8 changes: 8 additions & 0 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
FUNCTION_OBJ = "FUNCTION"
SYMBOL_OBJ = "SYMBOL"
SPECIALFORM_OBJ = "SPECIALFORM"
QOUTE_OBJ = "QUOTE"
CONSCELL_OBJ = "CONSCELL"
LIST_OBJ = "LIST"
BUILTIN_OBJ = "BUILTIN"
Expand Down Expand Up @@ -96,6 +97,13 @@ type SpecialForm struct {
func (sf *SpecialForm) Type() ObjectType { return SPECIALFORM_OBJ }
func (sf *SpecialForm) Inspect() string { return "special form" }

type Quote struct {
SExpression ast.SExpression
}

func (q *Quote) Type() ObjectType { return QOUTE_OBJ }
func (q *Quote) Inspect() string { return q.SExpression.String() }

type ConsCell struct {
Car Object
Cdr Object
Expand Down
20 changes: 18 additions & 2 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ func (p *Parser) parseSExpression() ast.SExpression {
switch p.curToken.Type {
case token.LPAREN:
return p.parseList()
case token.QUOTE:
return p.parseQuote()
default:
return p.parseAtom()
}
Expand Down Expand Up @@ -132,6 +134,22 @@ func (p *Parser) parseList() ast.List {
return consCell
}

func (p *Parser) parseQuote() ast.SExpression {
quote := &ast.Symbol{Token: p.curToken, Value: "quote"}

p.nextToken()

sexpression := p.parseSExpression()

return &ast.ConsCell{
CarField: quote,
CdrField: &ast.ConsCell{
CarField: sexpression,
CdrField: &ast.Nil{Token: token.Token{Type: token.NIL, Literal: "nil"}},
},
}
}

func (p *Parser) parseAtom() ast.Atom {
atom := p.parseAtomByType()

Expand All @@ -148,8 +166,6 @@ func (p *Parser) parseAtomByType() ast.Atom {
return p.parseIntegerLiteral()
case token.SYMBOL:
return p.parseSymbol()
case token.LAMBDA:
return &ast.SpecialForm{Token: p.curToken}
case token.NIL:
return &ast.Nil{Token: p.curToken}
}
Expand Down
Loading

0 comments on commit 265ceff

Please sign in to comment.