Skip to content

Commit 5aee871

Browse files
committed
compiler: new concept of packages
1 parent 2e743ca commit 5aee871

12 files changed

+270
-68
lines changed

.vscode/launch.json

+13-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,19 @@
3737
}
3838
},
3939
{
40-
"name": "Launch lithia",
40+
"name": "Launch example greeter",
41+
"type": "go",
42+
"request": "launch",
43+
"mode": "auto",
44+
"program": "${workspaceFolder}/app/lithia/main.go",
45+
"args": ["${workspaceFolder}/examples/greeter/cmd/main.lithia"],
46+
"cwd": "${workspaceFolder}",
47+
"env": {
48+
"LITHIA_STDLIB": "${workspaceFolder}/stdlib"
49+
}
50+
},
51+
{
52+
"name": "Launch fib-type",
4153
"type": "go",
4254
"request": "launch",
4355
"mode": "auto",

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
## v0.0.16-next
44

55
- fix: undeclared enum case error due to file order within a module
6+
- compiler: new concept of packages containing modules, optionally marked by a `Potfile`
7+
- compiler: implicit `src` module for package imports if folder exists
8+
- compiler: `import package` imports the current package (imports `src` if folder exists)
69

710
## v0.0.15
811

examples/greeter/Potfile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import pot.cmds
2+
3+
cmds.run "test", "cmd/test.lithia"

examples/greeter/cmd/main.lithia

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import package
2+
3+
package.greet "World"

examples/greeter/cmd/test.lithia

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import tests
2+
3+
test "example", { fail =>
4+
unless True, fail "True is False and False is True 🤯"
5+
}

examples/greeter/src/greet.lithia

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import strings
2+
3+
func greet { name =>
4+
print strings.concat ["Hello ", name, "!"]
5+
}

resolution/module-resolver.go

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package resolution
2+
3+
import (
4+
"os"
5+
"path"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/vknabel/lithia/ast"
10+
)
11+
12+
type ModuleResolver struct {
13+
// each root contains multiple packages
14+
externalImportRoots []string
15+
// defaults to Potfile
16+
defaultPackageName string
17+
manifestName string
18+
manifestSearchPaths []string
19+
defaultSrcDir string
20+
}
21+
22+
func DefaultModuleResolver() ModuleResolver {
23+
return ModuleResolver{
24+
externalImportRoots: defaultImportRoots(),
25+
defaultPackageName: "package",
26+
manifestName: "Potfile",
27+
manifestSearchPaths: []string{".", "..", "../..", "../../..", "../../../.."},
28+
defaultSrcDir: "src",
29+
}
30+
}
31+
32+
type ResolvedPackage struct {
33+
Name string
34+
Path string
35+
manifest *packageManifest
36+
}
37+
38+
type ResolvedModule struct {
39+
packageRef *ResolvedPackage
40+
// all modules of this package are relative to this path
41+
// might contain the package manifest file
42+
relativeName ast.ModuleName
43+
Path string
44+
Files []string
45+
}
46+
47+
type packageManifest struct {
48+
// a Potfile-file‚
49+
// the package module path will be derived from this location
50+
path string
51+
}
52+
53+
func defaultImportRoots() []string {
54+
roots := []string{}
55+
if path, ok := os.LookupEnv("LITHIA_LOCALS"); ok {
56+
roots = append(roots, path)
57+
}
58+
if path, ok := os.LookupEnv("LITHIA_PACKAGES"); ok {
59+
roots = append(roots, path)
60+
}
61+
if path, ok := os.LookupEnv("LITHIA_STDLIB"); ok {
62+
roots = append(roots, path)
63+
} else {
64+
roots = append(roots, "/usr/local/opt/lithia/stdlib")
65+
}
66+
absoluteImportRoots := make([]string, len(roots))
67+
for i, root := range roots {
68+
absolute, err := filepath.Abs(root)
69+
if err == nil {
70+
absoluteImportRoots[i] = absolute
71+
} else {
72+
absoluteImportRoots[i] = root
73+
}
74+
}
75+
return absoluteImportRoots
76+
}
77+
78+
func (mr *ModuleResolver) ResolvePackageForReferenceFile(referenceFile string) ResolvedPackage {
79+
for _, candidates := range mr.manifestSearchPaths {
80+
manifestPath := filepath.Join(path.Dir(referenceFile), candidates, mr.manifestName)
81+
if _, err := os.Stat(manifestPath); err == nil {
82+
modulePath := path.Dir(manifestPath)
83+
return ResolvedPackage{
84+
Name: mr.defaultPackageName,
85+
Path: modulePath,
86+
manifest: &packageManifest{
87+
path: manifestPath,
88+
},
89+
}
90+
}
91+
}
92+
dir, err := os.Getwd()
93+
if err != nil {
94+
dir = path.Dir(referenceFile)
95+
}
96+
return ResolvedPackage{Name: mr.defaultPackageName, Path: dir}
97+
}
98+
99+
func (mr *ModuleResolver) AddRootImportPath(path string) {
100+
mr.externalImportRoots = append([]string{path}, mr.externalImportRoots...)
101+
}
102+
103+
func (mr *ModuleResolver) CreateSingleFileModule(pkg ResolvedPackage, file string) ResolvedModule {
104+
return ResolvedModule{
105+
packageRef: &pkg,
106+
relativeName: ast.ModuleName(strings.ReplaceAll(strings.TrimSuffix(filepath.Base(file), ".lithia"), ".", "_")),
107+
Path: file,
108+
Files: []string{file},
109+
}
110+
}
111+
112+
func (mr *ModuleResolver) ResolveModuleFromPackage(pkg ResolvedPackage, moduleName ast.ModuleName) (ResolvedModule, error) {
113+
moduleParts := strings.Split(string(moduleName), ".")
114+
if len(moduleParts) == 0 {
115+
return mr.resolveModuleWithinPackage(pkg, moduleParts)
116+
}
117+
packageName := moduleParts[0]
118+
packageLevelModuleParts := moduleParts[1:]
119+
if packageName == mr.defaultPackageName {
120+
return mr.resolveModuleWithinPackage(pkg, packageLevelModuleParts)
121+
}
122+
123+
searchPaths := append([]string{pkg.Path}, mr.externalImportRoots...)
124+
for _, searchPath := range searchPaths {
125+
packagePath := path.Join(searchPath, packageName)
126+
if info, err := os.Stat(packagePath); err == nil && info.IsDir() {
127+
var match ResolvedPackage
128+
manifestPath := path.Join(packagePath, mr.manifestName)
129+
if _, err := os.Stat(manifestPath); err == nil && !info.IsDir() {
130+
match = ResolvedPackage{
131+
Name: packageName,
132+
Path: packagePath,
133+
manifest: &packageManifest{
134+
path: manifestPath,
135+
},
136+
}
137+
} else {
138+
match = ResolvedPackage{Name: packageName, Path: packagePath}
139+
}
140+
return mr.resolveModuleWithinPackage(match, packageLevelModuleParts)
141+
}
142+
}
143+
return ResolvedModule{}, ModuleNotFoundError{ModuleParts: moduleParts, FromPackage: pkg}
144+
}
145+
146+
func (mr *ModuleResolver) resolveModuleWithinPackage(pkg ResolvedPackage, moduleParts []string) (ResolvedModule, error) {
147+
if len(moduleParts) == 0 {
148+
files, err := filepath.Glob(path.Join(pkg.Path, mr.defaultSrcDir, "*.lithia"))
149+
if len(files) > 0 {
150+
return ResolvedModule{
151+
packageRef: &pkg,
152+
Path: pkg.Path,
153+
Files: files,
154+
}, err
155+
}
156+
files, err = filepath.Glob(path.Join(pkg.Path, "*.lithia"))
157+
return ResolvedModule{
158+
packageRef: &pkg,
159+
Path: pkg.Path,
160+
Files: files,
161+
}, err
162+
}
163+
pathElems := append([]string{pkg.Path}, moduleParts...)
164+
modulePath := path.Join(pathElems...)
165+
files, err := filepath.Glob(path.Join(modulePath, "*.lithia"))
166+
return ResolvedModule{
167+
packageRef: &pkg,
168+
relativeName: ast.ModuleName(strings.Join(moduleParts, ".")),
169+
Path: pkg.Path,
170+
Files: files,
171+
}, err
172+
}
173+
174+
func (mod ResolvedModule) Package() ResolvedPackage {
175+
return *mod.packageRef
176+
}
177+
178+
func (mod ResolvedModule) AbsoluteModuleName() ast.ModuleName {
179+
if mod.relativeName == "" {
180+
return ast.ModuleName(mod.Package().Name)
181+
} else {
182+
return ast.ModuleName(mod.packageRef.Name) + "." + mod.relativeName
183+
}
184+
}

resolution/resolution-error.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package resolution
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
type ModuleNotFoundError struct {
9+
FromPackage ResolvedPackage
10+
ModuleParts []string
11+
}
12+
13+
func (e ModuleNotFoundError) Error() string {
14+
return fmt.Sprintf("module %s not found from package %s", strings.Join(e.ModuleParts, "."), e.FromPackage.Path)
15+
}

runtime/interpreter-module.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
package runtime
22

3-
import "github.com/vknabel/lithia/ast"
3+
import (
4+
"github.com/vknabel/lithia/ast"
5+
"github.com/vknabel/lithia/resolution"
6+
)
47

58
type FileName string
69

710
type RuntimeModule struct {
811
Name ast.ModuleName
912
Environment *Environment
1013
Files map[FileName]*InterpreterContext
14+
resolved resolution.ResolvedModule
1115

1216
Decl *ast.ContextModule
1317

1418
// docs can be derived from the files
1519
}
1620

17-
func (inter *Interpreter) NewModule(name ast.ModuleName) *RuntimeModule {
21+
func (inter *Interpreter) NewModule(resolvedModule resolution.ResolvedModule) *RuntimeModule {
22+
name := resolvedModule.AbsoluteModuleName()
1823
module := &RuntimeModule{
1924
Name: name,
20-
Environment: NewEnvironment(inter.NewPreludeEnvironment()),
25+
Environment: NewEnvironment(inter.NewPreludeEnvironment(resolvedModule)),
2126
Files: make(map[FileName]*InterpreterContext),
2227
Decl: ast.MakeContextModule(name),
28+
resolved: resolvedModule,
2329
}
2430
inter.Modules[name] = module
2531
return module

runtime/interpreter-prelude.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ import (
55
"os"
66

77
"github.com/vknabel/lithia/ast"
8+
"github.com/vknabel/lithia/resolution"
89
)
910

10-
func (inter *Interpreter) NewPreludeEnvironment() *Environment {
11+
func (inter *Interpreter) NewPreludeEnvironment(resolvedModule resolution.ResolvedModule) *Environment {
1112
if inter.Prelude != nil {
1213
return inter.Prelude
1314
}
1415
env := NewEnvironment(nil)
1516
inter.Prelude = env
1617

17-
module, err := inter.LoadModuleIfNeeded(ast.ModuleName("prelude"))
18+
module, err := inter.LoadModuleIfNeeded(ast.ModuleName("prelude"), resolvedModule)
1819
if err != nil {
1920
fmt.Fprintf(os.Stderr, "error: prelude not loaded\n %s\n", err)
2021
}

0 commit comments

Comments
 (0)