Skip to content

Commit

Permalink
refactor: expose analyzer by compiler
Browse files Browse the repository at this point in the history
  • Loading branch information
siyul-park committed Feb 16, 2025
1 parent d2694d8 commit 2562a7e
Show file tree
Hide file tree
Showing 5 changed files with 478 additions and 185 deletions.
30 changes: 15 additions & 15 deletions ast/statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,6 @@ func (n *EmptyStatement) String() string {
func (n *EmptyStatement) statement() {
}

type ExpressionStatement struct {
Expression Expression
}

func NewExpressionStatement(expression Expression) *ExpressionStatement {
return &ExpressionStatement{Expression: expression}
}

func (n *ExpressionStatement) String() string {
return n.Expression.String() + ";"
}

func (n *ExpressionStatement) statement() {
}

type BlockStatement struct {
Statements []Statement
}
Expand All @@ -62,3 +47,18 @@ func (n *BlockStatement) String() string {

func (n *BlockStatement) statement() {
}

type ExpressionStatement struct {
Expression Expression
}

func NewExpressionStatement(expression Expression) *ExpressionStatement {
return &ExpressionStatement{Expression: expression}
}

func (n *ExpressionStatement) String() string {
return n.Expression.String() + ";"
}

func (n *ExpressionStatement) statement() {
}
144 changes: 144 additions & 0 deletions compiler/analyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package compiler

import (
"strings"

"github.com/siyul-park/minijs/ast"
"github.com/siyul-park/minijs/interpreter"
"github.com/siyul-park/minijs/token"
)

type Analyzer struct {
cache map[ast.Node]*Meta
}

type Meta struct {
Kind interpreter.Kind
}

func NewAnalyzer() *Analyzer {
return &Analyzer{cache: make(map[ast.Node]*Meta)}
}

func (a *Analyzer) Analyze(node ast.Node) *Meta {
if meta, found := a.cache[node]; found {
return meta
}

var meta *Meta
switch node := node.(type) {
case *ast.Program:
meta = a.program(node)
case *ast.EmptyStatement:
meta = a.emptyStatement(node)
case *ast.BlockStatement:
meta = a.blockStatement(node)
case *ast.ExpressionStatement:
meta = a.expressionStatement(node)
case *ast.PrefixExpression:
meta = a.prefixExpression(node)
case *ast.InfixExpression:
meta = a.infixExpression(node)
case *ast.BoolLiteral:
meta = a.boolLiteral(node)
case *ast.NumberLiteral:
meta = a.numberLiteral(node)
case *ast.StringLiteral:
meta = a.stringLiteral(node)
default:
meta = nil
}

a.cache[node] = meta
return meta
}

func (a *Analyzer) program(node *ast.Program) *Meta {
for _, stmt := range node.Statements {
a.Analyze(stmt)
}
return &Meta{Kind: interpreter.KindVoid}
}

func (a *Analyzer) emptyStatement(_ *ast.EmptyStatement) *Meta {
return &Meta{Kind: interpreter.KindVoid}
}

func (a *Analyzer) blockStatement(node *ast.BlockStatement) *Meta {
for _, stmt := range node.Statements {
a.Analyze(stmt)
}
return &Meta{Kind: interpreter.KindVoid}
}

func (a *Analyzer) expressionStatement(node *ast.ExpressionStatement) *Meta {
a.Analyze(node.Expression)
return &Meta{Kind: interpreter.KindVoid}
}

func (a *Analyzer) prefixExpression(node *ast.PrefixExpression) *Meta {
right := a.Analyze(node.Right)
if right == nil {
return nil
}

kind := interpreter.KindUnknown
switch node.Token {
case token.PLUS, token.MINUS:
switch right.Kind {
case interpreter.KindBool:
kind = interpreter.KindInt32
case interpreter.KindInt32, interpreter.KindFloat64:
kind = right.Kind
case interpreter.KindString:
kind = interpreter.KindFloat64
default:
}
default:
}
return &Meta{Kind: kind}
}

func (a *Analyzer) infixExpression(node *ast.InfixExpression) *Meta {
left := a.Analyze(node.Left)
right := a.Analyze(node.Right)

kind := interpreter.KindUnknown
switch node.Token {
case token.PLUS:
if left.Kind == interpreter.KindString || right.Kind == interpreter.KindString {
kind = interpreter.KindString
} else if left.Kind == interpreter.KindFloat64 || right.Kind == interpreter.KindFloat64 {
kind = interpreter.KindFloat64
} else {
kind = interpreter.KindInt32
}
case token.DIVIDE, token.MODULO:
kind = interpreter.KindFloat64
default:
if left.Kind == interpreter.KindInt32 && right.Kind == interpreter.KindInt32 {
kind = interpreter.KindInt32
} else {
kind = interpreter.KindFloat64
}
}
return &Meta{Kind: kind}
}

func (a *Analyzer) boolLiteral(_ *ast.BoolLiteral) *Meta {
return &Meta{Kind: interpreter.KindBool}
}

func (a *Analyzer) numberLiteral(node *ast.NumberLiteral) *Meta {
kind := interpreter.KindInt32
if strings.Contains(node.Token.Literal, ".") || strings.Contains(node.Token.Literal, "e") {
kind = interpreter.KindFloat64
} else if node.Value != float64(int32(node.Value)) {
kind = interpreter.KindFloat64
}
return &Meta{Kind: kind}
}

func (a *Analyzer) stringLiteral(_ *ast.StringLiteral) *Meta {
return &Meta{Kind: interpreter.KindString}
}
184 changes: 184 additions & 0 deletions compiler/analyzer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package compiler

import (
"testing"

"github.com/siyul-park/minijs/ast"
"github.com/siyul-park/minijs/interpreter"
"github.com/siyul-park/minijs/token"
"github.com/stretchr/testify/assert"
)

func TestAnalyzer_Analyze(t *testing.T) {
tests := []struct {
node ast.Node
meta *Meta
}{
{
node: ast.NewProgram(),
meta: &Meta{Kind: interpreter.KindVoid},
},
{
node: ast.NewEmptyStatement(),
meta: &Meta{Kind: interpreter.KindVoid},
},
{
node: ast.NewBlockStatement(),
meta: &Meta{Kind: interpreter.KindVoid},
},
{
node: ast.NewExpressionStatement(
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
),
meta: &Meta{Kind: interpreter.KindVoid},
},
{
node: ast.NewPrefixExpression(
token.PLUS,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
),
meta: &Meta{Kind: interpreter.KindInt32},
},
{
node: ast.NewPrefixExpression(
token.PLUS,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1.5"}, 1.5),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewPrefixExpression(
token.PLUS,
ast.NewStringLiteral(token.Token{Type: token.STRING, Literal: "foo"}, "foo"),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewPrefixExpression(
token.MINUS,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
),
meta: &Meta{Kind: interpreter.KindInt32},
},
{
node: ast.NewPrefixExpression(
token.MINUS,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "2.0"}, 2),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewInfixExpression(
token.PLUS,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "2"}, 2),
),
meta: &Meta{Kind: interpreter.KindInt32},
},
{
node: ast.NewInfixExpression(
token.PLUS,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "2.0"}, 2),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewInfixExpression(
token.PLUS,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewStringLiteral(token.Token{Type: token.STRING, Literal: "2"}, "2"),
),
meta: &Meta{Kind: interpreter.KindString},
},
{
node: ast.NewInfixExpression(
token.MINUS,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "2"}, 2),
),
meta: &Meta{Kind: interpreter.KindInt32},
},
{
node: ast.NewInfixExpression(
token.MULTIPLE,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "2"}, 2),
),
meta: &Meta{Kind: interpreter.KindInt32},
},
{
node: ast.NewInfixExpression(
token.MULTIPLE,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "2.0"}, 2),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewInfixExpression(
token.MULTIPLE,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewStringLiteral(token.Token{Type: token.STRING, Literal: "2"}, "2"),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewInfixExpression(
token.DIVIDE,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "2"}, 2),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewInfixExpression(
token.DIVIDE,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 2),
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "2.0"}, 2),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewInfixExpression(
token.DIVIDE,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewStringLiteral(token.Token{Type: token.STRING, Literal: "2"}, "2"),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewInfixExpression(
token.MODULO,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "2"}, 2),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewInfixExpression(
token.MODULO,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 2),
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "2.0"}, 2),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
{
node: ast.NewInfixExpression(
token.MODULO,
ast.NewNumberLiteral(token.Token{Type: token.NUMBER, Literal: "1"}, 1),
ast.NewStringLiteral(token.Token{Type: token.STRING, Literal: "2"}, "2"),
),
meta: &Meta{Kind: interpreter.KindFloat64},
},
}

analyzer := NewAnalyzer()

for _, tt := range tests {
t.Run(tt.node.String(), func(t *testing.T) {
meta := analyzer.Analyze(tt.node)
assert.Equal(t, tt.meta, meta)
})
}
}
Loading

0 comments on commit 2562a7e

Please sign in to comment.