Skip to content

Commit

Permalink
feat(cli): LE-001 concept #39
Browse files Browse the repository at this point in the history
  • Loading branch information
vknabel committed Feb 3, 2023
1 parent 6fd91b5 commit cb2a160
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- feat(error): impproved error messages for missing members
- feat(stdlib): introduced `apps`
- feat(error): improved error messages for dicts
- feat(cli): whitelist external definitions #39 LE-001
- feat(cli): timeout for scripts #39 LE-001

## v0.0.18

Expand Down
7 changes: 6 additions & 1 deletion app/lithia/cmd/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ func runPrompt() {
world.Current.Env.Exit(1)
}
reader := bufio.NewReader(world.Current.Stdin)
inter := lithia.NewDefaultInterpreter(importRoot)
inter, ctx := lithia.NewDefaultInterpreter(importRoot)
for {
select {
case <-ctx.Done():
return
default:
}
fmt.Print("> ")
line, err := reader.ReadString('\n')
if err == io.EOF {
Expand Down
3 changes: 2 additions & 1 deletion app/lithia/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ func runFile(fileName string, args []string) {
fmt.Fprint(world.Current.Stderr, err)
world.Current.Env.Exit(1)
}
inter := lithia.NewDefaultInterpreter(path.Dir(fileName))

inter, _ := lithia.NewDefaultInterpreter(path.Dir(fileName))
script := string(scriptData) + "\n"
_, err = inter.Interpret(fileName, script)
if err != nil {
Expand Down
65 changes: 58 additions & 7 deletions lithia.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,69 @@
package lithia

import (
"context"
"fmt"
"strings"
"time"

extdocs "github.com/vknabel/lithia/external/docs"
extfs "github.com/vknabel/lithia/external/fs"
extos "github.com/vknabel/lithia/external/os"
extrx "github.com/vknabel/lithia/external/rx"
"github.com/vknabel/lithia/runtime"
"github.com/vknabel/lithia/world"
)

func NewDefaultInterpreter(referenceFile string, importRoots ...string) *runtime.Interpreter {
inter := runtime.NewIsolatedInterpreter(referenceFile, importRoots...)
inter.ExternalDefinitions["os"] = extos.New(inter)
inter.ExternalDefinitions["rx"] = extrx.New(inter)
inter.ExternalDefinitions["docs"] = extdocs.New(inter)
inter.ExternalDefinitions["fs"] = extfs.New(inter)
return inter
func NewDefaultInterpreter(referenceFile string, importRoots ...string) (*runtime.Interpreter, context.Context) {
ctx := context.Background()
if timeout, ok := world.Current.Env.LookupEnv("LITHIA_TIMEOUT"); ok {
duration, err := time.ParseDuration(timeout)
if err != nil {
fmt.Fprint(world.Current.Stderr, err)
world.Current.Env.Exit(1)
}
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, duration)

go func() {
<-time.After(duration)
cancel()
}()
}
inter := runtime.NewIsolatedInterpreter(ctx, referenceFile, importRoots...)

externalDefinitions := "*"
if defs, ok := world.Current.Env.LookupEnv("LITHIA_EXTERNAL_DEFINITIONS"); ok {
externalDefinitions = defs
}
var whitelistExternals []string
if externalDefinitions != "*" {
whitelistExternals = strings.Split(externalDefinitions, ",")
}

if allowsWhitelistExternal(whitelistExternals, "os") {
inter.ExternalDefinitions["os"] = extos.New(inter)
}
if allowsWhitelistExternal(whitelistExternals, "rx") {
inter.ExternalDefinitions["rx"] = extrx.New(inter)
}
if allowsWhitelistExternal(whitelistExternals, "docs") {
inter.ExternalDefinitions["docs"] = extdocs.New(inter)
}
if allowsWhitelistExternal(whitelistExternals, "fs") {
inter.ExternalDefinitions["fs"] = extfs.New(inter)
}
return inter, ctx
}

func allowsWhitelistExternal(whitelistExternals []string, name string) bool {
if whitelistExternals == nil {
return true
}
for _, allowed := range whitelistExternals {
if allowed == name {
return true
}
}
return false
}
2 changes: 1 addition & 1 deletion runtime/evaluatable-expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func MakeEvaluatableExpr(context *InterpreterContext, expr ast.Expr) Evaluatable
if context.fileDef.Path != expr.Meta().Source.FileName {
panic("Mixing files in declared evaluatable expr!")
}
return EvaluatableExpr{&copy, expr, NewLazyEvaluationCache()}
return EvaluatableExpr{&copy, expr, NewLazyEvaluationCache(context.interpreter.Context)}
}

func (e EvaluatableExpr) Evaluate() (RuntimeValue, *RuntimeError) {
Expand Down
9 changes: 7 additions & 2 deletions runtime/interpreter-context.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (inter *Interpreter) NewInterpreterContext(fileDef *ast.SourceFile, module
module: module,
path: []string{},
environment: environment,
evalCache: NewLazyEvaluationCache(),
evalCache: NewLazyEvaluationCache(inter.Context),
}
}

Expand All @@ -37,7 +37,7 @@ func (i *InterpreterContext) NestedInterpreterContext(name string) *InterpreterC
module: i.module,
path: append(i.path, name),
environment: NewEnvironment(i.environment),
evalCache: NewLazyEvaluationCache(),
evalCache: NewLazyEvaluationCache(i.interpreter.Context),
}
}

Expand All @@ -48,6 +48,11 @@ func (i *InterpreterContext) Evaluate() (RuntimeValue, *RuntimeError) {
}
var result RuntimeValue
for _, stmt := range i.fileDef.Statements {
select {
case <-i.interpreter.Context.Done():
return nil, NewRuntimeError(i.interpreter.Context.Err())
default:
}
expr := MakeEvaluatableExpr(i, stmt)
value, err := expr.Evaluate()
if err != nil {
Expand Down
6 changes: 5 additions & 1 deletion runtime/interpreter.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
package runtime

import (
"context"

"github.com/vknabel/lithia/ast"
"github.com/vknabel/lithia/parser"
"github.com/vknabel/lithia/resolution"
"github.com/vknabel/lithia/world"
)

type Interpreter struct {
Context context.Context
Resolver resolution.ModuleResolver
Parser *parser.Parser
Modules map[ast.ModuleName]*RuntimeModule
ExternalDefinitions map[ast.ModuleName]ExternalDefinition
Prelude *Environment
}

func NewIsolatedInterpreter(referenceFile string, importRoots ...string) *Interpreter {
func NewIsolatedInterpreter(ctx context.Context, referenceFile string, importRoots ...string) *Interpreter {
inter := &Interpreter{
Context: ctx,
Resolver: resolution.NewDefaultModuleResolver(importRoots...),
Parser: parser.NewParser(),
Modules: make(map[ast.ModuleName]*RuntimeModule),
Expand Down
21 changes: 17 additions & 4 deletions runtime/lazy-evaluation-cache.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
package runtime

import "sync"
import (
"context"
"sync"
)

type LazyEvaluationCache struct {
ctx context.Context
once *sync.Once
value RuntimeValue
err *RuntimeError
}

func NewLazyEvaluationCache() *LazyEvaluationCache {
return &LazyEvaluationCache{once: &sync.Once{}}
func NewLazyEvaluationCache(ctx context.Context) *LazyEvaluationCache {
return &LazyEvaluationCache{ctx: ctx, once: &sync.Once{}}
}

func (c *LazyEvaluationCache) Evaluate(f func() (RuntimeValue, *RuntimeError)) (RuntimeValue, *RuntimeError) {
c.once.Do(func() {
c.value, c.err = f()
done := make(chan struct{})
go func() {
c.value, c.err = f()
close(done)
}()
select {
case <-c.ctx.Done():
c.err = NewRuntimeError(c.ctx.Err())
case <-done:
}
})
return c.value, c.err
}
3 changes: 2 additions & 1 deletion runtime/runtime-stdlib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import (

func TestStdlib(t *testing.T) {
pathToStdlib := "../stdlib"
inter := l.NewDefaultInterpreter(pathToStdlib, "../stdlib")
inter, ctx := l.NewDefaultInterpreter(pathToStdlib, "../stdlib")
defer ctx.Done()
mockOS := &mockExternalOS{
calledExitCode: -1,
env: map[string]string{"LITHIA_TESTS": "1"},
Expand Down

0 comments on commit cb2a160

Please sign in to comment.