Skip to content

Commit

Permalink
macro done??
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonak-Adipta-Kalita committed May 7, 2023
1 parent 7474e1f commit d29f495
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 2 deletions.
22 changes: 22 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,3 +441,25 @@ func (fes *ForeachStatement) String() string {
out.WriteString(fes.Body.String())
return out.String()
}

type MacroLiteral struct {
Token token.Token
Parameters []*Identifier
Body *BlockStatement
}

func (ml *MacroLiteral) expressionNode() {}
func (ml *MacroLiteral) TokenLiteral() string { return ml.Token.Literal }
func (ml *MacroLiteral) String() string {
var out bytes.Buffer
params := []string{}
for _, p := range ml.Parameters {
params = append(params, p.String())
}
out.WriteString(ml.TokenLiteral())
out.WriteString("(")
out.WriteString(strings.Join(params, ", "))
out.WriteString(") ")
out.WriteString(ml.Body.String())
return out.String()
}
101 changes: 101 additions & 0 deletions evaluator/macro_expansion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package evaluator

import (
"github.com/Jonak-Adipta-Kalita/JAK-Programming-Language/ast"
"github.com/Jonak-Adipta-Kalita/JAK-Programming-Language/object"
)

func DefineMacros(program *ast.Program, env *object.Environment) {
definitions := []int{}
for i, statement := range program.Statements {
if isMacroDefinition(statement) {
addMacro(statement, env)
definitions = append(definitions, i)
}
}
for i := len(definitions) - 1; i >= 0; i = i - 1 {
definitionIndex := definitions[i]
program.Statements = append(
program.Statements[:definitionIndex],
program.Statements[definitionIndex+1:]...,
)
}
}

func isMacroDefinition(node ast.Statement) bool {
assignStatement, ok := node.(*ast.AssignStatement)
if !ok {
return false
}
_, ok = assignStatement.Value.(*ast.MacroLiteral)
return ok
}

func addMacro(stmt ast.Statement, env *object.Environment) {
assignStatement, _ := stmt.(*ast.AssignStatement)
macroLiteral, _ := assignStatement.Value.(*ast.MacroLiteral)
macro := &object.Macro{
Parameters: macroLiteral.Parameters,
Env: env,
Body: macroLiteral.Body,
}
env.Set(assignStatement.Name.Value, macro)
}

func ExpandMacros(program ast.Node, env *object.Environment) ast.Node {
return ast.Modify(program, func(node ast.Node) ast.Node {
callExpression, ok := node.(*ast.CallExpression)
if !ok {
return node
}
macro, ok := isMacroCall(callExpression, env)
if !ok {
return node
}
args := quoteArgs(callExpression)
evalEnv := extendMacroEnv(macro, args)
evaluated := Eval(macro.Body, evalEnv)
quote, ok := evaluated.(*object.Quote)
if !ok {
panic("we only support returning AST-nodes from macros")
}
return quote.Node
})
}
func isMacroCall(
exp *ast.CallExpression,
env *object.Environment,
) (*object.Macro, bool) {
identifier, ok := exp.Function.(*ast.Identifier)
if !ok {
return nil, false
}
obj, ok := env.Get(identifier.Value)
if !ok {
return nil, false
}
macro, ok := obj.(*object.Macro)
if !ok {
return nil, false
}
return macro, true
}

func quoteArgs(exp *ast.CallExpression) []*object.Quote {
args := []*object.Quote{}
for _, a := range exp.Arguments {
args = append(args, &object.Quote{Node: a})
}
return args
}

func extendMacroEnv(
macro *object.Macro,
args []*object.Quote,
) *object.Environment {
extended := object.NewEnclosedEnvironment(macro.Env)
for paramIdx, param := range macro.Parameters {
extended.Set(param.Value, args[paramIdx])
}
return extended
}
3 changes: 3 additions & 0 deletions examples/macro_unless.jak
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var unless = macro(condition, consequence, alternative) { quote(if (!(unquote(condition))) { unquote(consequence); } else { unquote(alternative); }); };

unless(10 > 5, println("not greater"), println("greater"));
6 changes: 5 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func main() {
}

env := object.NewEnvironment()
macroEnv := object.NewEnvironment()

l := lexer.New(string(contents))
p := parser.New(l)
Expand All @@ -38,6 +39,9 @@ func main() {
return
}

evaluator.Eval(program, env)
evaluator.DefineMacros(program, macroEnv)
expanded := evaluator.ExpandMacros(program, macroEnv)

evaluator.Eval(expanded, env)
}
}
23 changes: 23 additions & 0 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (

BUILTIN_OBJ = "BUILTIN"
QUOTE_OBJ = "QUOTE"
MACRO_OBJ = "MACRO"
)

type Boolean struct {
Expand Down Expand Up @@ -233,3 +234,25 @@ func (q *Quote) Type() ObjectType { return QUOTE_OBJ }
func (q *Quote) Inspect() string {
return "QUOTE(" + q.Node.String() + ")"
}

type Macro struct {
Parameters []*ast.Identifier
Body *ast.BlockStatement
Env *Environment
}

func (m *Macro) Type() ObjectType { return MACRO_OBJ }
func (m *Macro) Inspect() string {
var out bytes.Buffer
params := []string{}
for _, p := range m.Parameters {
params = append(params, p.String())
}
out.WriteString("macro")
out.WriteString("(")
out.WriteString(strings.Join(params, ", "))
out.WriteString(") {\n")
out.WriteString(m.Body.String())
out.WriteString("\n}")
return out.String()
}
14 changes: 14 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(token.FOR, p.parseForLoopExpression)
p.registerPrefix(token.FOREACH, p.parseForEach)
p.registerPrefix(token.SWITCH, p.parseSwitchStatement)
p.registerPrefix(token.MACRO, p.parseMacroLiteral)

p.infixParseFns = make(map[token.TokenType]infixParseFn)
p.registerInfix(token.PLUS, p.parseInfixExpression)
Expand Down Expand Up @@ -669,3 +670,16 @@ func (p *Parser) parseForEach() ast.Expression {

return expression
}

func (p *Parser) parseMacroLiteral() ast.Expression {
lit := &ast.MacroLiteral{Token: p.curToken}
if !p.expectPeek(token.LPAREN) {
return nil
}
lit.Parameters = p.parseFunctionParameters()
if !p.expectPeek(token.LBRACE) {
return nil
}
lit.Body = p.parseBlockStatement()
return lit
}
6 changes: 5 additions & 1 deletion repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func Start(in io.Reader, out io.Writer) {
file.SetFileName("STDIN")
scanner := bufio.NewScanner(in)
env := object.NewEnvironment()
macroEnv := object.NewEnvironment()

for {
fmt.Print(PROMPT)
Expand All @@ -37,6 +38,9 @@ func Start(in io.Reader, out io.Writer) {
continue
}

evaluator.Eval(program, env)
evaluator.DefineMacros(program, macroEnv)
expanded := evaluator.ExpandMacros(program, macroEnv)

evaluator.Eval(expanded, env)
}
}
2 changes: 2 additions & 0 deletions token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const (
SWITCH = "SWITCH"
CASE = "CASE"
DEFAULT = "DEFAULT"
MACRO = "MACRO"
)

var keywords = map[string]TokenType{
Expand All @@ -81,6 +82,7 @@ var keywords = map[string]TokenType{
"switch": SWITCH,
"case": CASE,
"default": DEFAULT,
"macro": MACRO,
}

func LookupIdent(ident string) TokenType {
Expand Down

0 comments on commit d29f495

Please sign in to comment.