-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrepl.orb
More file actions
177 lines (150 loc) · 4.99 KB
/
Copy pathrepl.orb
File metadata and controls
177 lines (150 loc) · 4.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# This source file is part of the Orbit project.
#
# Licensed under the Apache License v2.0
import "io"
import "readline"
/*!
* @brief Interactive read-eval-print loop (REPL).
*
* Drives an interactive session: reads a line (with editing and history via
* `readline`), evaluates it with `eval` in a persistent `Context`, and prints
* the result. Bindings made on one line stay in that context, so they are
* visible on the next.
*
* `default_session` is a ready-to-use singleton — the interpreter's interactive
* mode simply runs `repl.default_session.run()`. Construct your own `Repl` for a
* custom prompt or a private context.
*
* @example
* import "repl"
* repl.default_session.run() # start the default REPL
*
* let r = new repl.Repl(ps1="orbit> ") # custom prompt
* r.run()
*/
/**
* @brief A single interactive session.
*
* Bundles the prompts, the line reader, and the `Context` that evaluated lines
* accumulate their bindings in. Each `Repl` is an independent session with its
* own namespace.
*/
pub class Repl {
pub var ps1 = ">>> "
pub var ps2 = "... "
var context
var rd = new readline.Readline(auto_history=false)
/**
* @brief Create a session.
*
* @param ps1? Primary prompt. Defaults to ">>> ".
* @param ps2? Continuation prompt (for multi-line input). Defaults to "... ".
* @param context? Context to evaluate in. Defaults to a clone of the current
* scope, so the session starts with everything currently in
* scope without ever mutating it.
*/
pub init(ps1=, ps2=, context=) {
if context {
self.context = context
} else {
self.context = Context(clone=true)
}
if ps1 {
self.ps1 = ps1
}
if ps2 {
self.ps2 = ps2
}
}
/**
* @brief Run the read-eval-print loop until the session ends.
*
* Repeatedly reads a line, evaluates it in the session's context, and prints
* the result — or, on failure, the error (the `eval` is trapped, so a runtime
* error reports instead of terminating the loop). Blank lines are skipped;
* `exit` ends the session.
*/
pub func run() {
loop {
data := self.read_input()
if data == ":exit" {
break
}
res := trap await eval("<repl>", data, context = self.context, module=false)
if res {
value := res.unwrap()
if value != nil {
io.print(value)
}
} else {
io.perror(res.unwrap_err())
}
}
}
pub func read_input() {
p_open := [0, 0, 0]
prompt := self.ps1
buffer := ""
loop {
data := self.rd.read(prompt=prompt)
if data == nil {
return ":exit"
}
if data.length() == 0 {
continue
}
loop i := 0; i < data.length(); i++ {
sub := data.at(i)
switch sub {
case "(":
p_open[0]++
case ")":
p_open[0]--
case "[":
p_open[1]++
case "]":
p_open[1]--
case "{":
p_open[2]++
case "}":
p_open[2]--
}
}
# Check for continuation
continuation := data.ends_with("\\") || data.ends_with("|>")
# A negative counter is a closer with no matching opener: the input
# can't be completed by typing more, so submit it and let eval report
# the syntax error instead of waiting for a bracket that never comes.
unbalanced := p_open[0] < 0 || p_open[1] < 0 || p_open[2] < 0
pending := p_open[0] > 0 || p_open[1] > 0 || p_open[2] > 0
if !unbalanced && (continuation || pending) {
tabs := " " * (p_open[0] + p_open[1] + p_open[2])
prompt = self.ps2 + tabs
if buffer {
buffer += "%s%s\n" % (tabs, data)
} else {
buffer += "%s\n" % data
}
continue
}
if buffer {
buffer += data
self.rd.add(buffer)
return buffer
}
self.rd.add(data)
return data
}
}
/**
* @brief Return the session's evaluation context.
*
* @return The Context that evaluated lines accumulate their bindings in;
* can be inspected or handed to another `eval`.
*/
pub func get_context() {
return self.context
}
}
# Ready-to-use default session; the interpreter's interactive mode runs this one.
pub let default_session = new Repl()