Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement module system for stdlib (msingi) and local libs (hapa) #97

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion ast/ast.go
Original file line number Diff line number Diff line change
@@ -565,6 +565,7 @@ func (me *MethodExpression) String() string {

type Import struct {
Token token.Token
Filename string
Identifiers map[string]*Identifier
}

@@ -617,6 +618,14 @@ func (p *Package) String() string {
return out.String()
}

type NoOp struct {
Token token.Token
}

func (np *NoOp) expressionNode() {}
func (np *NoOp) TokenLiteral() string { return np.Token.Literal }
func (np *NoOp) String() string { return "" }

type At struct {
Token token.Token
}
@@ -644,4 +653,4 @@ type PropertyExpression struct {

func (pe *PropertyExpression) expressionNode() {}
func (pe *PropertyExpression) TokenLiteral() string { return pe.Token.Literal }
func (pe *PropertyExpression) String() string { return "Ngl I'm tired part two" }
func (pe *PropertyExpression) String() string { return "Property Expression" }
6 changes: 6 additions & 0 deletions evaluator/assign.go
Original file line number Diff line number Diff line change
@@ -6,6 +6,12 @@ import (
)

func evalAssign(node *ast.Assign, env *object.Environment) object.Object {
// Preserve the name of the Function for use in later cases
switch node.Value.(type) {
case *ast.FunctionLiteral:
node.Value.(*ast.FunctionLiteral).Name = node.Name.Value
}

val := Eval(node.Value, env)
if isError(val) {
return val
10 changes: 7 additions & 3 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
@@ -124,8 +124,6 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
// return evalForExpression(node, env)
case *ast.ForIn:
return evalForInExpression(node, env, node.Token.Line)
case *ast.Package:
return evalPackage(node, env)
case *ast.PropertyExpression:
return evalPropertyExpression(node, env)
case *ast.PropertyAssignment:
@@ -298,7 +296,13 @@ func applyFunction(fn object.Object, args []object.Object, line int) object.Obje
if fn != nil {
return newError("Mstari %d: Hiki sio kiendesha: %s", line, fn.Type())
} else {
return newError("Bro how did you even get here??? Contact language maker asap!")
// Commit a484f5ca37d3f38b72f78b00b27a341d4d09a247 introduced a way to trigger this.
// My guess is how white spaces are processed.
return newError(`Hii ni muundo ambayo programu yako haifai kuwa. Tumejaribu miundo mingine lakini programu yako haieleweki.

Tuma sehemu ya programu ilifananya hii kosa kuonyeshwa hapa:
https://github.com/nuruprogramming/Nuru/issues
`)
}
}

7 changes: 4 additions & 3 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ package evaluator
import (
"fmt"
"testing"
"time"

"github.com/NuruProgramming/Nuru/lexer"
"github.com/NuruProgramming/Nuru/object"
@@ -108,7 +107,7 @@ func TestBangOperator(t *testing.T) {

func testEval(input string) object.Object {
l := lexer.New(input)
p := parser.New(l)
p := parser.New(l, "<nuru-test>")
program := p.ParseProgram()
env := object.NewEnvironment()

@@ -1111,6 +1110,8 @@ func TestStringMethods(t *testing.T) {
}
}

// Should be updated to test new module model
/*
func TestTimeModule(t *testing.T) {
input := `
tumia muda
@@ -1127,4 +1128,4 @@ func TestTimeModule(t *testing.T) {
if err != nil {
t.Errorf("Wrong time value: got=%v", err)
}
}
} */
128 changes: 112 additions & 16 deletions evaluator/import.go
Original file line number Diff line number Diff line change
@@ -2,50 +2,138 @@ package evaluator

import (
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/NuruProgramming/Nuru/ast"
"github.com/NuruProgramming/Nuru/lexer"
"github.com/NuruProgramming/Nuru/module"

"github.com/NuruProgramming/Nuru/object"
"github.com/NuruProgramming/Nuru/parser"
)

var searchPaths []string

type maktaba int

const (
MAKTABA_YA_NURU maktaba = iota
MAKTABA_YA_HAPA
MAKTABA_YA_NJE
)

func evalImport(node *ast.Import, env *object.Environment) object.Object {
/*
Maktaba yanafuata mkondo wa:
msingi => ni maktaba ya kawaida (stdlib)
hapa => ni maktaba haya, tunaanza na faili "nuru.toml" (ama faili ya kisawa)
_ => majina mengine ni ya maktaba ya nje
*/

// Go through the Identifiers
for k, v := range node.Identifiers {
if mod, ok := module.Mapper[v.Value]; ok {
env.Set(k, mod)
} else {
return evalImportFile(k, v, env)
vs := strings.Split(v.Value, "/")
if len(vs) <= 0 {
break
}

switch vs[0] {
case "msingi":
loc := mahali_pa_maktaba(MAKTABA_YA_NURU)

ss := strings.Split(v.Value, "/")[1:]
fi := filepath.Join(loc, filepath.Join(ss...))

return evalImportFile(k, &ast.Identifier{Value: fi}, env)
case "hapa":
loc := mahali_pa_maktaba(MAKTABA_YA_HAPA, node.Filename)

ss := strings.Split(v.Value, "/")[1:]
fi := filepath.Join(loc, filepath.Join(ss...))

return evalImportFile(k, &ast.Identifier{Value: fi}, env)
default:
log.Printf("Maktaba ya nje hazijazaidiwa '%s'\n", k)
return NULL
}
}

return NULL
}

func mahali_pa_maktaba(mkt maktaba, mahali ...string) string {
switch mkt {
case MAKTABA_YA_NURU:
loc := os.Getenv("MAKTABA_YA_NURU")
if len(loc) > 0 {
return loc
}

var usr_lib string
var lib_ string

if runtime.GOOS == "windows" {
uhd, _ := os.UserHomeDir()
usr_lib = filepath.Join(filepath.Base(uhd), "nuru", "maktaba")
} else {
usr_lib = "/usr/lib/nuru/maktaba"
lib_ = "/lib/nuru/maktaba"
}
if fileExists(usr_lib) {
return usr_lib
}
if fileExists(lib_) {
return lib_
}

return usr_lib
case MAKTABA_YA_HAPA:
// Hii tunahitaji kuenda nyuma hadi mahali tutakapo pata faili "nuru.toml"
var mkt__ string = mahali[0]
var nuru_config string = "nuru.toml"

// Check if the current dir has "nuru.toml"
for {
if filepath.Dir(mkt__) == mkt__ {
break
}

if fileExists(filepath.Join(mkt__, nuru_config)) {
return mkt__
}

mkt__ = filepath.Dir(mkt__)
}

return mkt__
default:
return ""
}
}

func evalImportFile(name string, ident *ast.Identifier, env *object.Environment) object.Object {
addSearchPath("")
filename := findFile(name)
filename := findFile(ident.Value)
if filename == "" {
return newError("Moduli %s haipo", name)
}
var scope *object.Environment
scope, err := evaluateFile(filename, env)
scope, err := evaluateFile(name, filename, env)
if err != nil {
return err
}
return importFile(name, ident, env, scope)
return importFile(name, env, scope)
}

func addSearchPath(path string) {
searchPaths = append(searchPaths, path)
}

func findFile(name string) string {
basename := fmt.Sprintf("%s.nr", name)
basename := fmt.Sprintf("%s.nuru", name)
for _, path := range searchPaths {
file := filepath.Join(path, basename)
if fileExists(file) {
@@ -60,28 +148,36 @@ func fileExists(file string) bool {
return err == nil
}

func evaluateFile(file string, env *object.Environment) (*object.Environment, object.Object) {
func evaluateFile(name, file string, env *object.Environment) (*object.Environment, object.Object) {
source, err := os.ReadFile(file)
if err != nil {
return nil, &object.Error{Message: fmt.Sprintf("Tumeshindwa kufungua pakeji: %s", file)}
}
l := lexer.New(string(source))
p := parser.New(l)
p := parser.New(l, file)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
return nil, &object.Error{Message: fmt.Sprintf("Pakeji %s ina makosa yafuatayo:\n%s", file, strings.Join(p.Errors(), "\n"))}
}

scope := object.NewEnvironment()
result := Eval(program, scope)
pkg := &object.Package{
Name: &ast.Identifier{Value: name},
Env: env,
Scope: object.NewEnclosedEnvironment(env),
}

result := Eval(program, pkg.Scope)

env.Set(name, pkg)

if isError(result) {
return nil, result
}
return scope, nil
return pkg.Env, nil
}

func importFile(name string, ident *ast.Identifier, env *object.Environment, scope *object.Environment) object.Object {
value, ok := scope.Get(ident.Value)
func importFile(name string, env *object.Environment, scope *object.Environment) object.Object {
value, ok := scope.Get(name)
if !ok {
return newError("%s sio pakeji", name)
}
4 changes: 0 additions & 4 deletions evaluator/method.go
Original file line number Diff line number Diff line change
@@ -40,10 +40,6 @@ func applyMethod(obj object.Object, method ast.Expression, args []object.Object,
default:
return obj.Method(method.(*ast.Identifier).Value, args)
}
case *object.Module:
if fn, ok := obj.Functions[method.(*ast.Identifier).Value]; ok {
return fn(args, defs)
}
case *object.Instance:
if fn, ok := obj.Package.Scope.Get(method.(*ast.Identifier).Value); ok {
fn.(*object.Function).Env.Set("@", obj)
18 changes: 0 additions & 18 deletions evaluator/package.go

This file was deleted.

6 changes: 4 additions & 2 deletions evaluator/property.go
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ func evalPropertyExpression(node *ast.PropertyExpression, env *object.Environmen
if isError(left) {
return left
}

switch left.(type) {
case *object.Instance:
obj := left.(*object.Instance)
@@ -20,7 +21,8 @@ func evalPropertyExpression(node *ast.PropertyExpression, env *object.Environmen
case *object.Package:
obj := left.(*object.Package)
prop := node.Property.(*ast.Identifier).Value
if val, ok := obj.Env.Get(prop); ok {

if val, ok := obj.Scope.Get(prop); ok {
return val
}
// case *object.Module:
@@ -30,7 +32,7 @@ func evalPropertyExpression(node *ast.PropertyExpression, env *object.Environmen
// return val()
// }
}
return newError("Value %s sii sahihi kwenye %s", node.Property.(*ast.Identifier).Value, left.Inspect())
return newError("Thamani %s sii sahihi kwenye %s", node.Property.(*ast.Identifier).Value, left.Inspect())
}

func evalPropertyAssignment(name *ast.PropertyExpression, val object.Object, env *object.Environment) object.Object {
7 changes: 6 additions & 1 deletion lexer/lexer.go
Original file line number Diff line number Diff line change
@@ -33,7 +33,9 @@ func (l *Lexer) readChar() {

func (l *Lexer) NextToken() token.Token {
var tok token.Token
l.skipWhitespace()
if !(l.ch == '\n' || l.ch == '\r') {
l.skipWhitespace()
}
if l.ch == rune('/') && l.peekChar() == rune('/') {
l.skipSingleLineComment()
return l.NextToken()
@@ -54,6 +56,9 @@ func (l *Lexer) NextToken() token.Token {
}
case rune(';'):
tok = newToken(token.SEMICOLON, l.line, l.ch)
case rune('\n'), rune('\r'):
tok = newToken(token.NEWLINE, l.line, l.ch)
l.line++
case rune('('):
tok = newToken(token.LPAREN, l.line, l.ch)
case rune(')'):
Loading
Oops, something went wrong.