Skip to content

Commit cc6151e

Browse files
committed
Initial TUI
1 parent ba71877 commit cc6151e

9 files changed

Lines changed: 315 additions & 131 deletions

File tree

app/cli/model.effekt

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,41 @@
1+
import app/net
2+
13
import lib/language/ic/term
4+
import lib/language/ic/ruler
25
import lib/net/net
6+
import lib/net/netter
7+
import lib/ui/terminal
38

4-
import app/net
5-
6-
record NetState(program: Program)
7-
record Context()
9+
record NetState(net: Net, program: Program, redexes: RedexBag, terminal: Terminal)
810

9-
def empty() = NetState(box {})
11+
def empty(): Net = box {}
12+
def empty(): Program = box {}
13+
def empty(): RedexBag = box {}
14+
def empty() = NetState(empty(), empty(), empty(), empty())
1015

1116
def load(state: NetState) { prog: => Unit / emit[Constructor] }: NetState = {
17+
with on[WrongFormat].panic // impossible by now
1218
val program: Program = collect { prog() }
13-
NetState(program)
19+
val net: Net = collect { source[Constructor] { program() } { net!() } }
20+
val redexes: RedexBag = collect { program.redexes() }
21+
NetState(net, program, redexes, empty())
1422
}
1523

16-
def cli[R] { prog: => R / NetModel[NetState, Context, Unit] }: R =
24+
def step(state: NetState): NetState = {
25+
val (loaded, redexes) = returning::collect {
26+
state.load { state.program.step(state.redexes) } }
27+
NetState(loaded.net, loaded.program, redexes, state.terminal)
28+
}
29+
30+
def cli[R] { prog: => R / NetModel[NetState, Terminal, Unit] }: R =
1731
try prog()
18-
with NetModel[NetState, Context, Unit] {
32+
with NetModel[NetState, Terminal, Unit] {
1933
def empty() = resume(empty())
20-
def context(net) = resume(Context())
2134
def load(net) = resume { { prog } => net.load { prog } }
35+
def step(net) = resume { net.step }
36+
37+
// not relevant for cli
38+
def context(net) = resume(Terminal())
2239
def render(net) = resume { => net }
23-
def step(net) = resume { => net }
2440
def operation(net, op) = resume { => net }
2541
}

app/cli/view.effekt

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,47 @@
11
module cli
22

3-
def start() = <>
3+
import lib/ui/node
4+
import lib/ui/view
5+
import lib/ui/dirty
6+
import lib/ui/terminal
7+
8+
import app/cli/model
9+
import app/model
10+
11+
val MODE = LambdaCalculus()
12+
13+
def string[Ev](content: String) = String[Ev](content)
14+
def input[Ev](handlers: List[TuiHandler[Ev]], label: String) = Input[Ev](handlers, label)
15+
def rows[Ev](elements: List[Tui[Ev]]) = Rows[Ev](elements)
16+
17+
def append(text: TextStream, ch: Char): String = collect { text(); do emit(ch) }
18+
19+
type Event {
20+
StepReduction();
21+
AppendSource(ch: Char);
22+
}
23+
24+
def dispatch(msg: Event): Unit / { Model[Terminal, Unit], Redraw } = msg match {
25+
case StepReduction() => do stepReduction()
26+
case AppendSource(ch) => do setSource(do getSource(MODE).append(ch))
27+
}
28+
29+
def view(): Tui[Event] / Model[Terminal, Unit] = rows([
30+
input([OnKeyPress(box { ch => AppendSource(ch) })], "term"),
31+
string(collect { do getSource(InteractionCalculus())() })
32+
])
33+
34+
def start() = {
35+
with cli
36+
run(empty(), model::empty[NetState, Terminal, Unit](), TuiView(
37+
update = box { ev =>
38+
with cli
39+
with model[Terminal, Unit, Unit, NetState]
40+
ev.dispatch()
41+
},
42+
view = box {
43+
with cli
44+
with model[Terminal, Unit, Tui[Event], NetState]
45+
view()
46+
}))
47+
}

app/interactive/view.effekt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ import app/model
1111

1212
def text[Ev](content: String) = Text[Ev](content)
1313

14-
def button[Ev](handler: List[EventHandler[Ev]], classes: List[String], children: List[Html[Ev]]): Html[Ev] / tag =
14+
def button[Ev](handler: List[HtmlHandler[Ev]], classes: List[String], children: List[Html[Ev]]): Html[Ev] / tag =
1515
Element("button", classes, handler, children, "").tag!
1616

17-
def div[Ev](handler: List[EventHandler[Ev]], classes: List[String], children: List[Html[Ev]]): Html[Ev] / tag =
17+
def div[Ev](handler: List[HtmlHandler[Ev]], classes: List[String], children: List[Html[Ev]]): Html[Ev] / tag =
1818
Element("div", classes, handler, children, "").tag!
1919

2020
def div[Ev](classes: List[String], children: List[Html[Ev]]): Html[Ev] / tag =
2121
div([], classes, children).tag!
2222

23-
def canvas[Ev](handler: List[EventHandler[Ev]], classes: List[String], canvas: Canvas): Html[Ev] / tag =
23+
def canvas[Ev](handler: List[HtmlHandler[Ev]], classes: List[String], canvas: Canvas): Html[Ev] / tag =
2424
Canvas(classes, handler, canvas).tag!
2525

26-
def textarea[Ev](handler: List[EventHandler[Ev]], classes: List[String], text: TextStream): Html[Ev] / tag =
26+
def textarea[Ev](handler: List[HtmlHandler[Ev]], classes: List[String], text: TextStream): Html[Ev] / tag =
2727
Element("textarea", classes, handler, [], collect { text() }).tag!
2828

2929
// ---
@@ -81,10 +81,8 @@ def start() = {
8181
documentBody.appendChild(newDiv)
8282
}
8383

84-
val lastTime = ref(0)
85-
8684
with interactive
87-
run(appDiv, model::empty[NetState, Canvas, CanvasOp](), View(
85+
run(appDiv, model::empty[NetState, Canvas, CanvasOp](), HtmlView(
8886
update = box { ev =>
8987
with interactive
9088
with model[Canvas, CanvasOp, Unit, NetState]

lib/language/ic/ruler.effekt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ def apply(ctor: Constructor, substitutions: Ref[Map[Port, Port]]): Constructor =
146146
Constructor(pp, aux, ctor.kind)
147147
}
148148

149+
// TODO: emit new redexes found by interacting
149150
def step(program: Program, redexes: RedexBag): Unit / { emit[Constructor], emit[Redex] } = {
150151
val annihilated = ref[Set[Constructor]](emptyGeneric())
151152
val substitutions = ref[Map[Port, Port]](emptyGeneric())

lib/ui/canvas.effekt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ def draw(ctx: Context, triangle: Triangle): Unit = {
6666
}
6767

6868
extern def atan2(x: Double, y: Double) at io: Double =
69-
jsWeb "Math.atan2(${x}, ${y})"
69+
js "Math.atan2(${x}, ${y})"
70+
default { <> }
7071

7172
// from https://stackoverflow.com/a/6333775
7273
def draw(ctx: Context, arrow: Arrow): Unit = {

lib/ui/context.effekt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,52 @@ extern type Context
44

55
extern def getContext(node: Node, dim: String) at io: Context =
66
jsWeb "${node}.getContext(${dim})"
7+
default { <> }
78

89
extern def save(ctx: Context) at io: Unit =
910
jsWeb "${ctx}.save()"
11+
default { <> }
1012

1113
extern def restore(ctx: Context) at io: Unit =
1214
jsWeb "${ctx}.restore()"
15+
default { <> }
1316

1417
extern def fill(ctx: Context) at io: Unit =
1518
jsWeb "${ctx}.fill()"
19+
default { <> }
1620

1721
extern def scale(ctx: Context, x: Double, y: Double) at io: Unit =
1822
jsWeb "${ctx}.scale(${x}, ${y})"
23+
default { <> }
1924

2025
extern def beginPath(ctx: Context) at io: Unit =
2126
jsWeb "${ctx}.beginPath()"
27+
default { <> }
2228

2329
extern def closePath(ctx: Context) at io: Unit =
2430
jsWeb "${ctx}.closePath()"
31+
default { <> }
2532

2633
extern def stroke(ctx: Context) at io: Unit =
2734
jsWeb "${ctx}.stroke()"
35+
default { <> }
2836

2937
extern def moveTo(ctx: Context, x: Double, y: Double) at io: Unit =
3038
jsWeb "${ctx}.moveTo(${x}, ${y})"
39+
default { <> }
3140

3241
extern def lineTo(ctx: Context, x: Double, y: Double) at io: Unit =
3342
jsWeb "${ctx}.lineTo(${x}, ${y})"
43+
default { <> }
3444

3545
extern def translate(ctx: Context, x: Double, y: Double) at io: Unit =
3646
jsWeb "${ctx}.translate(${x}, ${y})"
47+
default { <> }
3748

3849
extern def setStrokeStyle(ctx: Context, style: String) at io: Unit =
3950
jsWeb "(() => (${ctx}.strokeStyle = ${style}))()"
51+
default { <> }
4052

4153
extern def setFillStyle(ctx: Context, style: String) at io: Unit =
42-
jsWeb "(() => (${ctx}.fillStyle = ${style}))()"
54+
jsWeb "(() => (${ctx}.fillStyle = ${style}))()"
55+
default { <> }

lib/ui/node.effekt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,33 @@
33

44
extern type Node
55

6-
extern def getDocumentBody() at {}: Node = jsWeb "(document.body)"
6+
extern def empty() at {}: Node =
7+
js "(null)"
8+
default { <> }
9+
10+
extern def getDocumentBody() at {}: Node =
11+
jsWeb "(document.body)"
12+
default { <> }
713

814
val documentBody: Node = getDocumentBody()
915

10-
extern def getPixelRatio() at {}: Double = jsWeb "(window.devicePixelRatio || 1)"
16+
extern def getPixelRatio() at {}: Double =
17+
jsWeb "(window.devicePixelRatio || 1)"
18+
default { <> }
1119

1220
val pixelRatio: Double = getPixelRatio()
1321

1422
extern def onClick(node: Node, handler: () => Unit at {io, global}) at io: Unit =
1523
jsWeb "${node}.addEventListener('click', () => $effekt.runToplevel(${handler}))"
24+
default { <> }
1625

1726
extern def onScroll(node: Node, handler: Double => Unit at {io, global}) at io: Unit =
1827
jsWeb """(function() {
1928
const handler = ({wheelDelta, detail}) => $effekt.runToplevel((ks, k) => ${handler}(wheelDelta ? wheelData / 40 : detail ? -detail : 0, ks, k));
2029
${node}.addEventListener('DOMMouseScroll', handler);
2130
${node}.addEventListener('mousewheel', handler);
2231
})()"""
32+
default { <> }
2333

2434
// based on https://stackoverflow.com/a/5526721
2535
extern def onDrag(node: Node, handler: (Double, Double) => Unit at {io, global}) at io: Unit =
@@ -41,9 +51,11 @@ extern def onDrag(node: Node, handler: (Double, Double) => Unit at {io, global})
4151
if (dragStart) $effekt.runToplevel((ks, k) => ${handler}(lastX - dragStart.x, lastY - dragStart.y, ks, k))
4252
});
4353
})()"""
54+
default { <> }
4455

4556
extern def onKeyUp(node: Node, handler: String => Unit at {io, global}) at io: Unit =
4657
jsWeb "${node}.addEventListener('keyup', ({key}) => $effekt.runToplevel((ks, k) => ${handler}(key, ks, k)))"
58+
default { <> }
4759

4860
// let's just pretend we're using internet explorer :)
4961
extern def onResize(node: Node, handler: (Bool, Double, Double) => Unit at {io, global}) at io: Unit =
@@ -55,45 +67,59 @@ extern def onResize(node: Node, handler: (Bool, Double, Double) => Unit at {io,
5567
return res;
5668
}))).observe(${node});
5769
})()"""
70+
default { <> }
5871

5972
extern def createElement(tag: String) at io: Node =
6073
jsWeb "document.createElement(${tag})"
74+
default { <> }
6175

6276
extern def createTextNode(text: String) at io: Node =
6377
jsWeb "document.createTextNode(${text})"
78+
default { <> }
6479

6580
extern def appendChild(node: Node, child: Node) at io: Node =
6681
jsWeb "${node}.appendChild(${child})"
82+
default { <> }
6783

6884
extern def replaceWith(node: Node, by: Node) at io: Unit =
6985
jsWeb "${node}.replaceWith(${by})"
86+
default { <> }
7087

7188
extern def getAttribute(node: Node, key: String) at io: String =
7289
jsWeb "(${node}.getAttribute(${key}) || '')"
90+
default { <> }
7391

7492
extern def setAttribute(node: Node, key: String, value: String) at io: Node =
7593
jsWeb "(function() { ${node}.setAttribute(${key}, ${value}); return ${node} })()"
94+
default { <> }
7695

7796
extern def innerHTML(node: Node, contents: String) at io: Node =
7897
jsWeb "(function() { ${node}.innerHTML = ${contents}; return ${node} })()"
98+
default { <> }
7999

80100
extern def setValue(node: Node, value: String) at io: Node =
81101
jsWeb "(function() { ${node}.value = ${value}; return ${node} })()"
102+
default { <> }
82103

83104
extern def setSize(node: Node, width: Double, height: Double) at io: Unit =
84105
jsWeb "(function() { ${node}.width = ${width}; ${node}.height = ${height} })()"
106+
default { <> }
85107

86108
extern def addClass(node: Node, class: String) at io: Node =
87109
jsWeb "(function() { ${node}.classList.add(${class}) })()"
110+
default { <> }
88111

89112
extern def unsafeQuerySelector(node: Node, query: String) at io: Node =
90113
jsWeb "${node}.querySelector(${query})"
114+
default { <> }
91115

92116
extern def unsafeGetElementById(id: String) at io: Node =
93117
jsWeb "document.getElementById(${id})"
118+
default { <> }
94119

95120
extern def value(node: Node) at io: String =
96121
jsWeb "(${node}.value)"
122+
default { <> }
97123

98124
def getElementById(id: String): Option[Node] = {
99125
val unsafeElement = unsafeGetElementById(id)

lib/ui/terminal.effekt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
record Terminal()
2+
3+
def empty() = Terminal()
4+
5+
extern def rawMode(): Unit =
6+
jsNode "(() => require('tty').setRawMode(true))()"
7+
default { <> }
8+
9+
extern def onKeyPress(handler: Char => Unit at {io, global}) at io: Unit =
10+
jsNode "process.stdin.on('keypress', (letter, key) => $effekt.runToplevel((ks, k) => ${handler}(letter, ks, k)))"
11+
default { <> }

0 commit comments

Comments
 (0)