-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: expose analyzer by compiler
- Loading branch information
1 parent
d2694d8
commit 2562a7e
Showing
5 changed files
with
478 additions
and
185 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
Oops, something went wrong.