Skip to content

Commit

Permalink
Merge pull request #34 from JunNishimura/feature/add_setq
Browse files Browse the repository at this point in the history
add setq special form
  • Loading branch information
JunNishimura authored Jul 25, 2024
2 parents 5956d14 + 61bc6d6 commit fa7f6f4
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 9 deletions.
47 changes: 46 additions & 1 deletion evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,11 @@ func evalValueList(consCell *ast.ConsCell, env *object.Environment) []object.Obj
if isError(car) {
return []object.Object{car}
}
list = append(list, car)
if symbol, ok := car.(*object.Symbol); ok {
list = append(list, symbol.Value)
} else {
list = append(list, car)
}

// move to the next cons cell or return the list if the cdr is nil
switch cdr := consCell.Cdr().(type) {
Expand Down Expand Up @@ -232,6 +236,8 @@ func evalSpecialForm(sexp *ast.ConsCell, env *object.Environment) object.Object
return evalBackquote(sexp, env)
case "if":
return evalIf(sexp, env)
case "setq":
return evalSetq(sexp, env)
}

return newError("unknown special form: %s", spForm.Value)
Expand Down Expand Up @@ -428,3 +434,42 @@ func isTruthy(obj object.Object) bool {
return true
}
}

func evalSetq(consCell *ast.ConsCell, env *object.Environment) object.Object {
spForm, ok := consCell.Car().(*ast.SpecialForm)
if !ok {
return newError("expect special form, got %T", consCell.Car())
}
if spForm.Token.Type != token.SETQ {
return newError("expect special form setq, got %s", spForm.Token.Type)
}

cdr, ok := consCell.Cdr().(*ast.ConsCell)
if !ok {
return newError("not defined name of symbol")
}

symbolName, ok := cdr.Car().(*ast.Symbol)
if !ok {
return newError("expect symbol, got %T", cdr.Car())
}

cddr, ok := cdr.Cdr().(*ast.ConsCell)
if !ok {
return newError("not defined value of symbol")
}

value := Eval(cddr.Car(), env)
if isError(value) {
return value
}

symbol := &object.Symbol{
Name: symbolName.Value,
Value: value,
}

env.Set(symbolName.Value, symbol)

return symbol
}
46 changes: 39 additions & 7 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,30 @@ func testEval(input string) object.Object {

func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
result, ok := obj.(*object.Integer)
if !ok {
t.Errorf("object is not Integer. got=%T (%+v)", obj, obj)
return false
if ok {
if result.Value != expected {
t.Errorf("object has wrong value. got=%d, want=%d", result.Value, expected)
return false
}
return true
}
if result.Value != expected {
t.Errorf("object has wrong value. got=%d, want=%d", result.Value, expected)
return false

symbol, ok := obj.(*object.Symbol)
if ok {
integer, ok := symbol.Value.(*object.Integer)
if !ok {
t.Errorf("object is not Integer. got=%T (%+v)", result.Value, result.Value)
return false
}
if integer.Value != expected {
t.Errorf("object has wrong value. got=%d, want=%d", integer.Value, expected)
return false
}
return true
}
return true

t.Errorf("object is not Integer. got=%T (%+v)", obj, obj)
return false
}

func TestEvalIntegerExpression(t *testing.T) {
Expand Down Expand Up @@ -201,3 +216,20 @@ func TestIfExpression(t *testing.T) {
}
}
}

func TestSetqExpression(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"(setq x 10) x", 10},
{"(setq x 10) (setq x 20) x", 20},
{"(setq x 10) (setq y 20) (+ x y)", 30},
{"(setq x (+ 1 1)) x", 2},
}

for _, tt := range tests {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
}
}
12 changes: 12 additions & 0 deletions lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,18 @@ func TestList(t *testing.T) {
{Type: token.EOF, Literal: ""},
},
},
{
name: "setq special form",
input: "(setq x 1)",
expected: []token.Token{
{Type: token.LPAREN, Literal: "("},
{Type: token.SETQ, Literal: "setq"},
{Type: token.SYMBOL, Literal: "x"},
{Type: token.INT, Literal: "1"},
{Type: token.RPAREN, Literal: ")"},
{Type: token.EOF, Literal: ""},
},
},
}

for _, tt := range tests {
Expand Down
3 changes: 2 additions & 1 deletion parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ func (p *Parser) parseAtomByType() ast.Atom {
return &ast.Symbol{Token: p.curToken, Value: p.curToken.Literal}
case token.LAMBDA,
token.QUOTE, // this quote is string, not '
token.IF:
token.IF,
token.SETQ:
return &ast.SpecialForm{Token: p.curToken, Value: p.curToken.Literal}
case token.NIL:
return &ast.Nil{Token: p.curToken}
Expand Down
103 changes: 103 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,109 @@ func TestIf(t *testing.T) {
}
}

func TestSetq(t *testing.T) {
tests := []struct {
name string
input string
expected *ast.ConsCell
}{
{
name: "set atom to symbol",
input: "(setq x 1)",
expected: &ast.ConsCell{
CarField: &ast.SpecialForm{Token: token.Token{Type: token.SETQ, Literal: "setq"}, Value: "setq"},
CdrField: &ast.ConsCell{
CarField: &ast.Symbol{Token: token.Token{Type: token.SYMBOL, Literal: "x"}, Value: "x"},
CdrField: &ast.ConsCell{
CarField: &ast.IntegerLiteral{Token: token.Token{Type: token.INT, Literal: "1"}, Value: 1},
CdrField: &ast.Nil{Token: token.Token{Type: token.NIL, Literal: "nil"}},
},
},
},
},
{
name: "set list to symbol",
input: "(setq x (+ 1 2))",
expected: &ast.ConsCell{
CarField: &ast.SpecialForm{Token: token.Token{Type: token.SETQ, Literal: "setq"}, Value: "setq"},
CdrField: &ast.ConsCell{
CarField: &ast.Symbol{Token: token.Token{Type: token.SYMBOL, Literal: "x"}, Value: "x"},
CdrField: &ast.ConsCell{
CarField: &ast.ConsCell{
CarField: &ast.Symbol{Token: token.Token{Type: token.SYMBOL, Literal: "+"}, Value: "+"},
CdrField: &ast.ConsCell{
CarField: &ast.IntegerLiteral{Token: token.Token{Type: token.INT, Literal: "1"}, Value: 1},
CdrField: &ast.ConsCell{
CarField: &ast.IntegerLiteral{Token: token.Token{Type: token.INT, Literal: "2"}, Value: 2},
CdrField: &ast.Nil{Token: token.Token{Type: token.NIL, Literal: "nil"}},
},
},
},
CdrField: &ast.Nil{Token: token.Token{Type: token.NIL, Literal: "nil"}},
},
},
},
},
{
name: "set lambda function to symbol",
input: "(setq f (lambda (x) (+ x 1)))",
expected: &ast.ConsCell{
CarField: &ast.SpecialForm{Token: token.Token{Type: token.SETQ, Literal: "setq"}, Value: "setq"},
CdrField: &ast.ConsCell{
CarField: &ast.Symbol{Token: token.Token{Type: token.SYMBOL, Literal: "f"}, Value: "f"},
CdrField: &ast.ConsCell{
CarField: &ast.ConsCell{
CarField: &ast.SpecialForm{Token: token.Token{Type: token.LAMBDA, Literal: "lambda"}, Value: "lambda"},
CdrField: &ast.ConsCell{
CarField: &ast.ConsCell{
CarField: &ast.Symbol{Token: token.Token{Type: token.SYMBOL, Literal: "x"}, Value: "x"},
CdrField: &ast.Nil{Token: token.Token{Type: token.NIL, Literal: "nil"}},
},
CdrField: &ast.ConsCell{
CarField: &ast.ConsCell{
CarField: &ast.Symbol{Token: token.Token{Type: token.SYMBOL, Literal: "+"}, Value: "+"},
CdrField: &ast.ConsCell{
CarField: &ast.Symbol{Token: token.Token{Type: token.SYMBOL, Literal: "x"}, Value: "x"},
CdrField: &ast.ConsCell{
CarField: &ast.IntegerLiteral{Token: token.Token{Type: token.INT, Literal: "1"}, Value: 1},
CdrField: &ast.Nil{Token: token.Token{Type: token.NIL, Literal: "nil"}},
},
},
},
CdrField: &ast.Nil{Token: token.Token{Type: token.NIL, Literal: "nil"}},
},
},
},
CdrField: &ast.Nil{Token: token.Token{Type: token.NIL, Literal: "nil"}},
},
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := lexer.New(tt.input)
p := New(l)
program := p.ParseProgram()
checkParserErrors(t, p)

if len(program.Expressions) != 1 {
t.Fatalf("program.Expressions does not contain 1 expressions. got=%d", len(program.Expressions))
}

cc, ok := program.Expressions[0].(*ast.ConsCell)
if !ok {
t.Fatalf("exp not *ast.ConsCell. got=%T", program.Expressions[0])
}

if cc.String() != tt.expected.String() {
t.Fatalf("cc.String() not %s. got=%s", tt.expected.String(), cc.String())
}
})
}
}

func TestProgram(t *testing.T) {
tests := []struct {
name string
Expand Down
2 changes: 2 additions & 0 deletions token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
LAMBDA = "LAMBDA"
QUOTE = "'"
IF = "IF"
SETQ = "SETQ"

PLUS = "+"
MINUS = "-"
Expand All @@ -42,6 +43,7 @@ var keywords = map[string]TokenType{
"lambda": LAMBDA,
"quote": QUOTE,
"if": IF,
"setq": SETQ,
}

func LookupKeyword(symbol string) TokenType {
Expand Down

0 comments on commit fa7f6f4

Please sign in to comment.