Skip to content

Commit 3f927c0

Browse files
committed
stdlib: futures
1 parent a90cdca commit 3f927c0

23 files changed

+411
-37
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
- compiler: dictionary literals `[:]` and `["a": "b"]` #9
77
- stdlib: new type `prelude.Dict` #9
88
- stdlib: new type `prelude.Pair`
9+
- stdlib: moved `results.Result` to `prelude.Result`
910
- stdlib: Float-support! #24
1011
- stdlib: `!` operator #22
12+
- stdlib: only the first leading space will be trimmed in docs
13+
- stdlib: new type `rx.Future` and `rx.Async` and associated functions
1114

1215
## v0.0.16
1316

ast/docs.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ func MakeDocs(comments []string) *Docs {
1212
docsStrings := make([]string, 0)
1313
for _, comment := range comments {
1414
if strings.HasPrefix(comment, "///") {
15-
docsStrings = append(docsStrings, strings.TrimSpace(strings.TrimPrefix(comment, "///")))
15+
docsStrings = append(docsStrings, strings.TrimPrefix(strings.TrimPrefix(strings.TrimSpace(comment), "///"), " "))
1616
} else if strings.HasPrefix(comment, "/**") {
1717
trimmed := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(comment, "/**"), "*/"))
1818
lines := strings.Split(trimmed, "\n")
1919
for _, line := range lines {
20-
docsStrings = append(docsStrings, strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "*")))
20+
docsStrings = append(docsStrings, strings.TrimPrefix(strings.TrimPrefix(strings.TrimSpace(line), "*"), " "))
2121
}
2222
}
2323
}

runtime/environment-data.go

+11
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,14 @@ func (env *Environment) MakePair(key Evaluatable, value Evaluatable) (DataRuntim
6060
"value": value,
6161
})
6262
}
63+
64+
func (env *Environment) MakeSuccess(value Evaluatable) (DataRuntimeValue, *RuntimeError) {
65+
return env.MakeDataRuntimeValue("Success", map[string]Evaluatable{
66+
"value": value,
67+
})
68+
}
69+
func (env *Environment) MakeFailure(err Evaluatable) (DataRuntimeValue, *RuntimeError) {
70+
return env.MakeDataRuntimeValue("Failure", map[string]Evaluatable{
71+
"error": err,
72+
})
73+
}

runtime/extern-rx-future-type.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package runtime
2+
3+
import (
4+
"github.com/vknabel/lithia/ast"
5+
)
6+
7+
var _ RuntimeValue = RxFutureType{}
8+
var _ DeclRuntimeValue = RxFutureType{}
9+
var _ CallableRuntimeValue = RxFutureType{}
10+
11+
var RxFutureTypeRef = MakeRuntimeTypeRef("Future", "rx")
12+
13+
type RxFutureType struct {
14+
ast.DeclExternType
15+
}
16+
17+
func (RxFutureType) RuntimeType() RuntimeTypeRef {
18+
return PreludeAnyTypeRef
19+
}
20+
21+
func (RxFutureType) String() string {
22+
return RxVariableTypeRef.String()
23+
}
24+
25+
func (t RxFutureType) Declaration(*Interpreter) (ast.Decl, *RuntimeError) {
26+
return t.DeclExternType, nil
27+
}
28+
29+
func (d RxFutureType) HasInstance(value RuntimeValue) (bool, *RuntimeError) {
30+
if _, ok := value.(RxVariable); ok {
31+
return true, nil
32+
} else {
33+
return false, nil
34+
}
35+
}
36+
37+
func (RxFutureType) Lookup(member string) (Evaluatable, *RuntimeError) {
38+
return nil, NewRuntimeErrorf("%s is not a member of %s", member, RxVariableTypeRef.String())
39+
}
40+
41+
func (RxFutureType) Arity() int {
42+
return 1
43+
}
44+
45+
func (t RxFutureType) Call(arguments []Evaluatable, fromExpr ast.Expr) (RuntimeValue, *RuntimeError) {
46+
if len(arguments) != 1 {
47+
return nil, NewRuntimeErrorf("too many arguments for variable type %s", t)
48+
}
49+
receive, err := arguments[0].Evaluate()
50+
if err != nil {
51+
return nil, err.CascadeDecl(t.DeclExternType)
52+
}
53+
if receive, ok := receive.(CallableRuntimeValue); ok {
54+
return MakeRxFuture(&t, receive), nil
55+
} else {
56+
return nil, NewRuntimeErrorf("%s is not callable", receive)
57+
}
58+
}
59+
60+
func (t RxFutureType) Source() *ast.Source {
61+
return t.Meta().Source
62+
}

runtime/extern-rx-future.go

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package runtime
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
)
7+
8+
var _ RuntimeValue = &RxFuture{}
9+
10+
type internalPromise struct {
11+
lock *sync.RWMutex
12+
result *promiseResult
13+
channel chan promiseResult
14+
}
15+
16+
type promiseResult struct {
17+
value *RuntimeValue
18+
err *RuntimeError
19+
}
20+
21+
type RxFuture struct {
22+
futureType *RxFutureType
23+
storage *internalPromise
24+
}
25+
26+
func MakeRxFuture(futureType *RxFutureType, configure CallableRuntimeValue) RxFuture {
27+
future := RxFuture{
28+
futureType: futureType,
29+
storage: &internalPromise{
30+
lock: &sync.RWMutex{},
31+
result: nil,
32+
channel: make(chan promiseResult, 1),
33+
},
34+
}
35+
go func() {
36+
receive := MakeAnonymousFunction("receive", []string{"event"}, func(args []Evaluatable) (RuntimeValue, *RuntimeError) {
37+
value, err := args[0].Evaluate()
38+
if err != nil {
39+
return nil, err.CascadeDecl(futureType.DeclExternType)
40+
}
41+
resultTypeRef := MakeRuntimeTypeRef("Result", "results")
42+
isResult, err := resultTypeRef.HasInstance(value)
43+
if err != nil {
44+
return nil, err.CascadeDecl(futureType.DeclExternType)
45+
} else if !isResult {
46+
return nil, NewRuntimeErrorf("future %s received non-result %s", fmt.Sprint(future), fmt.Sprint(value)).CascadeDecl(futureType.DeclExternType)
47+
}
48+
future.accept(value)
49+
return future, nil
50+
})
51+
_, err := configure.Call([]Evaluatable{
52+
NewConstantRuntimeValue(receive),
53+
}, nil)
54+
if err != nil {
55+
future.fail(*err)
56+
}
57+
}()
58+
return future
59+
}
60+
61+
func (RxFuture) RuntimeType() RuntimeTypeRef {
62+
return RxVariableTypeRef
63+
}
64+
65+
func (v RxFuture) String() string {
66+
v.storage.lock.RLock()
67+
defer v.storage.lock.RUnlock()
68+
if v.storage.result.err != nil {
69+
return fmt.Sprintf("(%s %s)", v.RuntimeType().Name, *v.storage.result.err)
70+
} else if v.storage.result.value != nil {
71+
return fmt.Sprintf("(%s %s)", v.RuntimeType().Name, *v.storage.result.value)
72+
} else {
73+
return "Future"
74+
}
75+
}
76+
77+
func (v RxFuture) Lookup(member string) (Evaluatable, *RuntimeError) {
78+
switch member {
79+
case "await":
80+
return NewLazyRuntimeValue(func() (RuntimeValue, *RuntimeError) {
81+
return v.Await()
82+
}), nil
83+
default:
84+
return nil, NewRuntimeErrorf("future %s has no member %s", fmt.Sprint(v), member)
85+
}
86+
}
87+
88+
func (v RxFuture) Await() (RuntimeValue, *RuntimeError) {
89+
v.storage.lock.RLock()
90+
91+
if v.storage.result != nil {
92+
defer v.storage.lock.RUnlock()
93+
if v.storage.result.err != nil {
94+
return nil, v.storage.result.err
95+
} else {
96+
return *v.storage.result.value, nil
97+
}
98+
} else {
99+
v.storage.lock.RUnlock()
100+
result := <-v.storage.channel
101+
v.storage.lock.Lock()
102+
defer v.storage.lock.Unlock()
103+
104+
v.storage.result = &result
105+
if result.err != nil {
106+
return nil, result.err
107+
} else {
108+
return *result.value, nil
109+
}
110+
}
111+
}
112+
113+
func (v RxFuture) accept(value RuntimeValue) (RuntimeValue, *RuntimeError) {
114+
v.storage.lock.RLock()
115+
if v.storage.result != nil {
116+
defer v.storage.lock.RUnlock()
117+
if v.storage.result.err != nil {
118+
return nil, v.storage.result.err
119+
} else {
120+
return *v.storage.result.value, nil
121+
}
122+
} else {
123+
v.storage.lock.RUnlock()
124+
v.storage.lock.Lock()
125+
126+
v.storage.result = &promiseResult{value: &value}
127+
v.storage.lock.Unlock()
128+
v.storage.channel <- promiseResult{value: &value}
129+
close(v.storage.channel)
130+
return value, nil
131+
}
132+
}
133+
134+
func (v RxFuture) fail(err RuntimeError) (RuntimeValue, *RuntimeError) {
135+
v.storage.lock.Lock()
136+
defer v.storage.lock.Unlock()
137+
if v.storage.result != nil {
138+
if v.storage.result.err != nil {
139+
return nil, v.storage.result.err
140+
} else {
141+
return *v.storage.result.value, nil
142+
}
143+
} else {
144+
v.storage.result = &promiseResult{err: &err}
145+
v.storage.lock.Unlock()
146+
147+
v.storage.channel <- promiseResult{err: &err}
148+
close(v.storage.channel)
149+
return nil, &err
150+
}
151+
}

runtime/extern-rx-variable-type.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func (t RxVariableType) Declaration(*Interpreter) (ast.Decl, *RuntimeError) {
2626
return t.DeclExternType, nil
2727
}
2828

29-
func (d RxVariableType) HasInstance(interpreter *Interpreter, value RuntimeValue) (bool, *RuntimeError) {
29+
func (d RxVariableType) HasInstance(value RuntimeValue) (bool, *RuntimeError) {
3030
if _, ok := value.(RxVariable); ok {
3131
return true, nil
3232
} else {

runtime/external-rx.go

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ func (e ExternalRx) Lookup(name string, env *Environment, decl ast.Decl) (Runtim
1616
} else {
1717
panic("rx.Variable must be an extern type")
1818
}
19+
case "Future":
20+
if decl, ok := decl.(ast.DeclExternType); ok {
21+
return RxFutureType{decl}, true
22+
} else {
23+
panic("rx.Future must be an extern type")
24+
}
1925
default:
2026
return nil, false
2127
}

runtime/interpreter.go

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/vknabel/lithia/resolution"
99
)
1010

11+
var interpreter *Interpreter
12+
1113
type Interpreter struct {
1214
Resolver resolution.ModuleResolver
1315
Parser *parser.Parser
@@ -29,6 +31,7 @@ func NewInterpreter(referenceFile string, importRoots ...string) *Interpreter {
2931
inter.ExternalDefinitions["rx"] = ExternalRx{}
3032
inter.ExternalDefinitions["docs"] = ExternalDocs{}
3133
inter.ExternalDefinitions["fs"] = ExternalFS{}
34+
interpreter = inter
3235
return inter
3336
}
3437

runtime/operators-binary.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ func (ex *InterpreterContext) lazyLogicComparision(
256256
// // moduleName: "prelude",
257257
// // typeValue: &trueTypeValue,
258258
// // }
259-
if ok, err := boolRef.HasInstance(ex.interpreter, left); !ok || err != nil {
259+
if ok, err := boolRef.HasInstance(left); !ok || err != nil {
260260
if err != nil {
261261
return nil, err
262262
}
@@ -268,7 +268,7 @@ func (ex *InterpreterContext) lazyLogicComparision(
268268
}
269269

270270
trueRef := MakeRuntimeTypeRef("True", "prelude")
271-
isLeftTrue, err := trueRef.HasInstance(ex.interpreter, left)
271+
isLeftTrue, err := trueRef.HasInstance(left)
272272
if err != nil {
273273
return nil, NewRuntimeError(err)
274274
}
@@ -278,7 +278,7 @@ func (ex *InterpreterContext) lazyLogicComparision(
278278
if err != nil {
279279
return false, nil
280280
}
281-
isRightTrue, err := trueRef.HasInstance(ex.interpreter, right)
281+
isRightTrue, err := trueRef.HasInstance(right)
282282
if err != nil {
283283
return false, nil
284284
}

runtime/operators-unary.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ func (ex *InterpreterContext) unaryOperatorFunction(operator string) (func(Evalu
2121

2222
func (ex *InterpreterContext) boolFromRuntimeValue(value RuntimeValue) (bool, *RuntimeError) {
2323
trueRef := MakeRuntimeTypeRef("True", "prelude")
24-
isTrue, err := trueRef.HasInstance(ex.interpreter, value)
24+
isTrue, err := trueRef.HasInstance(value)
2525
if err != nil {
2626
return false, NewRuntimeError(err)
2727
} else if isTrue {
2828
return true, nil
2929
}
3030
falseRef := MakeRuntimeTypeRef("True", "prelude")
31-
_, err = falseRef.HasInstance(ex.interpreter, value)
31+
_, err = falseRef.HasInstance(value)
3232
if err != nil {
3333
return false, NewRuntimeError(err)
3434
} else {

runtime/prelude-data-decl.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (d PreludeDataDecl) String() string {
2727
return fmt.Sprintf("data %s", d.Decl.Name)
2828
}
2929

30-
func (d PreludeDataDecl) HasInstance(interpreter *Interpreter, value RuntimeValue) (bool, *RuntimeError) {
30+
func (d PreludeDataDecl) HasInstance(value RuntimeValue) (bool, *RuntimeError) {
3131
if dataVal, ok := value.(DataRuntimeValue); ok {
3232
return reflect.DeepEqual(*dataVal.TypeDecl, d), nil
3333
} else {

runtime/prelude-enum-decl.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func (e PreludeEnumDecl) String() string {
5858
return fmt.Sprint(e.Decl)
5959
}
6060

61-
func (e PreludeEnumDecl) HasInstance(interpreter *Interpreter, value RuntimeValue) (bool, *RuntimeError) {
61+
func (e PreludeEnumDecl) HasInstance(value RuntimeValue) (bool, *RuntimeError) {
6262
for identifier, evalCase := range e.caseLookups {
6363
caseValue, err := evalCase.Evaluate()
6464
if err != nil {
@@ -68,7 +68,7 @@ func (e PreludeEnumDecl) HasInstance(interpreter *Interpreter, value RuntimeValu
6868
if !ok {
6969
return false, NewRuntimeErrorf("enum case not a declaration %s, got: %s", identifier, caseDeclValue).CascadeDecl(e.Decl)
7070
}
71-
ok, err = caseDeclValue.HasInstance(interpreter, value)
71+
ok, err = caseDeclValue.HasInstance(value)
7272
if err != nil {
7373
return false, err.CascadeDecl(e.Decl)
7474
}

runtime/prelude-primitive-extern-type.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ func (t PreludePrimitiveExternType) RuntimeType() RuntimeTypeRef {
2222
return PreludeAnyTypeRef
2323
}
2424

25-
func (t PreludePrimitiveExternType) HasInstance(interpreter *Interpreter, value RuntimeValue) (bool, *RuntimeError) {
25+
func (t PreludePrimitiveExternType) HasInstance(value RuntimeValue) (bool, *RuntimeError) {
2626
return t.hasInstance(value)
2727
}

runtime/prelude-type-switch-expr.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func (t PreludeTypeSwitchExpr) Call(args []Evaluatable, fromExpr ast.Expr) (Runt
110110
).CascadeExpr(t.Decl)
111111
}
112112

113-
ok, err = runtimeDecl.HasInstance(t.context.interpreter, primaryArg)
113+
ok, err = runtimeDecl.HasInstance(primaryArg)
114114
if err != nil {
115115
return nil, err.CascadeExpr(t.Decl)
116116
}

0 commit comments

Comments
 (0)