Skip to content

Commit

Permalink
adding semantic check for declaration of variables
Browse files Browse the repository at this point in the history
  • Loading branch information
gravataLonga committed Jul 9, 2022
1 parent 4992398 commit 2516d5b
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 33 deletions.
2 changes: 1 addition & 1 deletion repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var logoImage []byte

const PROMPT = ">>> "

const NINJA_LICENSE = "Ninja Language - MIT LICENSE - Version: %s"
const NINJA_LICENSE = "Ninja Language - MIT LICENSE - Version: %s\n"

type repl struct {
out io.Writer
Expand Down
70 changes: 44 additions & 26 deletions semantic/semantic.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@ package semantic
import (
"fmt"
"ninja/ast"
"ninja/parser"
)

// Semantic here is where we are going doing some semantic analyze
// using visitor pattern
type Semantic struct {
p *parser.Parser
scopeStack Stack
errors []string
scopeStack Stack
globalVariable map[string]ast.Expression
errors []string
}

func New(p *parser.Parser) *Semantic {
return &Semantic{p: p}
func New() *Semantic {
return &Semantic{}
}

func (s *Semantic) Errors() []string {
Expand All @@ -26,8 +25,8 @@ func (s *Semantic) NewError(format string, a ...interface{}) {
s.errors = append(s.errors, fmt.Sprintf(format, a...))
}

func (s *Semantic) Analyze() ast.Node {
return s.analyze(s.p.ParseProgram())
func (s *Semantic) Analyze(node *ast.Program) ast.Node {
return s.analyze(node)
}

// beginScope record scope how deep is
Expand All @@ -41,17 +40,23 @@ func (s *Semantic) endScope() {
}

// declare will keep track of declare variables
func (s *Semantic) declare(name string) {
func (s *Semantic) declare(node *ast.VarStatement) {
peek, ok := s.scopeStack.Peek()
if !ok {
return
}

name := node.Name.Value
*peek = Scope{name: false}
}

// resolve after a variable been resolve we mark it as resolved.
func (s *Semantic) resolve(name string) {
func (s *Semantic) resolve(node *ast.VarStatement) {
name := node.Name.Value
if !s.expectIdentifierDeclare(node.Name) {
return
}

peek, ok := s.scopeStack.Peek()
if !ok {
return
Expand All @@ -60,21 +65,22 @@ func (s *Semantic) resolve(name string) {
*peek = Scope{name: true}
}

func (s *Semantic) expectIdentifierDeclare(name string) bool {
func (s *Semantic) expectIdentifierDeclare(node *ast.Identifier) bool {
name := node.Value
peek, ok := s.scopeStack.Peek()
if !ok {
s.NewError("Can't read local variable %s in its own initializer", name)
s.NewError("There aren't any scope active %s", name)
return false
}

v, ok := (*peek)[name]
if !ok {
s.NewError("Identifier %s not exists on current scope", name)
s.NewError("Variable \"%s\" not declare yet %s", name, node.Token)
return false
}

if !v {
s.NewError("Can't read local variable %s in its own initializer", name)
s.NewError("Can't read local variable \"%s\" in its own initializer %s", name, node.Token)
return false
}

Expand All @@ -84,28 +90,40 @@ func (s *Semantic) expectIdentifierDeclare(name string) bool {
func (s *Semantic) analyze(node ast.Node) ast.Node {
switch node := node.(type) {
case *ast.Program:
s.beginScope()
for _, v := range node.Statements {
s.analyze(v)
}
s.endScope()
case *ast.ArrayLiteral:
for _, e := range node.Elements {
s.analyze(e)
}
case *ast.IfExpression:
s.analyze(node.Condition)
s.analyze(node.Consequence)
s.analyze(node.Alternative)
case *ast.HashLiteral:
for k, v := range node.Pairs {
s.analyze(k)
s.analyze(v)
}
case *ast.Identifier:
s.expectIdentifierDeclare(node)
case *ast.VarStatement:
s.declare(node)
s.analyze(node.Value)
s.resolve(node)
case *ast.ExpressionStatement:
s.analyze(node.Expression)
case *ast.Function:
s.declare(node.Name.Value)
s.resolve(node.Name.Value)

case *ast.FunctionLiteral:
s.analyze(node.Body)
case *ast.BlockStatement:
s.beginScope()
for _, stmt := range node.Statements {
s.analyze(stmt)
for _, b := range node.Statements {
s.analyze(b)
}
s.endScope()
case *ast.Identifier:
s.expectIdentifierDeclare(node.Value)
case *ast.VarStatement:
s.declare(node.Name.Value)
s.analyze(node.Value)
s.resolve(node.Name.Value)
}
return node
}
44 changes: 38 additions & 6 deletions semantic/semantic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,51 @@ import (
"testing"
)

func TestResolveVar(t *testing.T) {
func TestScopeVariables(t *testing.T) {
tests := []struct {
input string
erroMessage interface{}
}{
{
`var a = a;`,
"Can't read local variable a in its own initializer",
"Can't read local variable \"a\" in its own initializer IDENT at [Line: 1, Offset: 10]",
},
{
`var a = [1, b];`,
"Variable \"b\" not declare yet IDENT at [Line: 1, Offset: 14]",
},
{
`var a = {"t": b}`,
"Variable \"b\" not declare yet IDENT at [Line: 1, Offset: 16]",
},
{
`var a = {b: 1}`,
"Variable \"b\" not declare yet IDENT at [Line: 1, Offset: 11]",
},
{
`if (a) {} else {}`,
"Variable \"a\" not declare yet IDENT at [Line: 1, Offset: 6]",
},
{
`if (true) {a} else {}`,
"Variable \"a\" not declare yet IDENT at [Line: 1, Offset: 13]",
},
{
`if (true) {} else {a}`,
"Variable \"a\" not declare yet IDENT at [Line: 1, Offset: 21]",
},
{
`if (true) {var b = b;} else {}`,
"Can't read local variable \"b\" in its own initializer IDENT at [Line: 1, Offset: 21]",
},
{
`var a = "local"; function () { var a = a; }`,
"Can't read local variable \"a\" in its own initializer IDENT at [Line: 1, Offset: 6]",
},
}

for i, tt := range tests {
t.Run(fmt.Sprintf("TestResolveVar[%d]", i), func(t *testing.T) {
t.Run(fmt.Sprintf("TestScopeVariables[%d]", i), func(t *testing.T) {
s := testSemantic(tt.input, t)

if len(s.Errors()) <= 0 {
Expand Down Expand Up @@ -60,13 +92,13 @@ func checkSemanticErrors(t *testing.T, s *Semantic) {
t.FailNow()
}

// testEval execute input code and check if there are parser error
// testSemantic execute input code and check if there are parser error
// and return result object.Object
func testSemantic(input string, t *testing.T) *Semantic {
l := lexer.New(strings.NewReader(input))
p := parser.New(l)
s := New(p)
s.Analyze()
s := New()
s.Analyze(p.ParseProgram())

checkParserErrors(t, p)
return s
Expand Down

0 comments on commit 2516d5b

Please sign in to comment.