Skip to content

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

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
Expand Up @@ -565,6 +565,7 @@ func (me *MethodExpression) String() string {

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

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
`)
}
}

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

"github.com/NuruProgramming/Nuru/lexer"
"github.com/NuruProgramming/Nuru/object"
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Up @@ -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) {
Expand All @@ -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)
}
Expand Down
4 changes: 0 additions & 4 deletions evaluator/method.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
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
Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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 {
Expand Down
7 changes: 6 additions & 1 deletion lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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(')'):
Expand Down
Loading