@harmoniclabs/pebble-cli 0.1.3-dev8 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/repl/pebbleRepl.js
CHANGED
|
@@ -26,11 +26,164 @@ function formatCEKValue(val) {
|
|
|
26
26
|
}
|
|
27
27
|
return "(lambda)";
|
|
28
28
|
}
|
|
29
|
+
// keywords that produce declarations to accumulate across REPL lines
|
|
30
|
+
const _accumKeywords = new Set([
|
|
31
|
+
"const", "let", "var",
|
|
32
|
+
"function",
|
|
33
|
+
"struct", "enum", "type", "interface",
|
|
34
|
+
"data", "contract", "runtime",
|
|
35
|
+
"import", "export",
|
|
36
|
+
]);
|
|
37
|
+
// all keywords that start statements (accumulated or not)
|
|
38
|
+
const _allKeywords = new Set([
|
|
39
|
+
..._accumKeywords,
|
|
40
|
+
"for", "while", "if", "match",
|
|
41
|
+
"return", "break", "continue", "using",
|
|
42
|
+
"trace", "assert", "fail", "test",
|
|
43
|
+
]);
|
|
44
|
+
function _readFirstWord(text) {
|
|
45
|
+
const m = text.match(/^([a-zA-Z_]\w*)/);
|
|
46
|
+
return m ? m[1] : "";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* extracts the declared name from a declaration statement
|
|
50
|
+
* to use as the context key. returns undefined if not detectable.
|
|
51
|
+
*/
|
|
52
|
+
function _extractDeclKey(firstWord, input) {
|
|
53
|
+
let m;
|
|
54
|
+
switch (firstWord) {
|
|
55
|
+
case "const":
|
|
56
|
+
case "let":
|
|
57
|
+
case "var":
|
|
58
|
+
m = input.match(/^(?:const|let|var)\s+(\w+)/);
|
|
59
|
+
return m ? "var:" + m[1] : undefined;
|
|
60
|
+
case "function":
|
|
61
|
+
m = input.match(/^function\s+(\w+)/);
|
|
62
|
+
return m ? "fn:" + m[1] : undefined;
|
|
63
|
+
case "struct":
|
|
64
|
+
case "enum":
|
|
65
|
+
case "type":
|
|
66
|
+
case "interface":
|
|
67
|
+
m = input.match(/^(?:struct|enum|type|interface)\s+(\w+)/);
|
|
68
|
+
return m ? "type:" + m[1] : undefined;
|
|
69
|
+
case "data":
|
|
70
|
+
m = input.match(/^data\s+(?:struct|enum)?\s*(\w+)/);
|
|
71
|
+
return m ? "type:" + m[1] : undefined;
|
|
72
|
+
case "contract":
|
|
73
|
+
case "runtime":
|
|
74
|
+
m = input.match(/^(?:contract|runtime)\s+(\w+)/);
|
|
75
|
+
return m ? "type:" + m[1] : undefined;
|
|
76
|
+
case "import":
|
|
77
|
+
return "import:" + input;
|
|
78
|
+
case "export":
|
|
79
|
+
// export function foo / export struct Foo / etc.
|
|
80
|
+
m = input.match(/^export\s+(?:function|struct|enum|type|interface|data|contract|runtime)\s+(\w+)/);
|
|
81
|
+
if (m)
|
|
82
|
+
return "decl:" + m[1];
|
|
83
|
+
// export const / let / var
|
|
84
|
+
m = input.match(/^export\s+(?:const|let|var)\s+(\w+)/);
|
|
85
|
+
if (m)
|
|
86
|
+
return "var:" + m[1];
|
|
87
|
+
return "export:" + input;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* ensures a statement source ends with a semicolon
|
|
93
|
+
* (unless it ends with a closing brace, which is self-terminating).
|
|
94
|
+
*/
|
|
95
|
+
function _ensureSemicolon(src) {
|
|
96
|
+
const trimmed = src.trimEnd();
|
|
97
|
+
if (trimmed.endsWith(";") || trimmed.endsWith("}"))
|
|
98
|
+
return trimmed;
|
|
99
|
+
return trimmed + ";";
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* counts unmatched braces, parens, and brackets in text,
|
|
103
|
+
* respecting string literals and comments.
|
|
104
|
+
* returns > 0 if there are unclosed delimiters.
|
|
105
|
+
*/
|
|
106
|
+
function _unclosedDepth(text) {
|
|
107
|
+
let depth = 0;
|
|
108
|
+
const len = text.length;
|
|
109
|
+
let pos = 0;
|
|
110
|
+
while (pos < len) {
|
|
111
|
+
const ch = text.charCodeAt(pos);
|
|
112
|
+
// single-line comment
|
|
113
|
+
if (ch === 0x2F && pos + 1 < len && text.charCodeAt(pos + 1) === 0x2F) {
|
|
114
|
+
pos += 2;
|
|
115
|
+
while (pos < len && text.charCodeAt(pos) !== 0x0A)
|
|
116
|
+
pos++;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// multi-line comment
|
|
120
|
+
if (ch === 0x2F && pos + 1 < len && text.charCodeAt(pos + 1) === 0x2A) {
|
|
121
|
+
pos += 2;
|
|
122
|
+
while (pos < len && !(text.charCodeAt(pos) === 0x2A && pos + 1 < len && text.charCodeAt(pos + 1) === 0x2F))
|
|
123
|
+
pos++;
|
|
124
|
+
pos += 2;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
// string literals
|
|
128
|
+
if (ch === 0x22 || ch === 0x27 || ch === 0x60) {
|
|
129
|
+
pos++;
|
|
130
|
+
while (pos < len) {
|
|
131
|
+
const sc = text.charCodeAt(pos);
|
|
132
|
+
if (sc === 0x5C) {
|
|
133
|
+
pos += 2;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (sc === ch) {
|
|
137
|
+
pos++;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
pos++;
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (ch === 0x28 || ch === 0x5B || ch === 0x7B)
|
|
145
|
+
depth++;
|
|
146
|
+
else if (ch === 0x29 || ch === 0x5D || ch === 0x7D)
|
|
147
|
+
depth--;
|
|
148
|
+
pos++;
|
|
149
|
+
}
|
|
150
|
+
return depth;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* ordered context: entries preserve insertion order,
|
|
154
|
+
* and re-declarations replace at the original position.
|
|
155
|
+
*/
|
|
156
|
+
class ReplContext {
|
|
157
|
+
entries = [];
|
|
158
|
+
set(key, src) {
|
|
159
|
+
const idx = this.entries.findIndex(e => e.key === key);
|
|
160
|
+
if (idx >= 0)
|
|
161
|
+
this.entries[idx] = { key, src };
|
|
162
|
+
else
|
|
163
|
+
this.entries.push({ key, src });
|
|
164
|
+
}
|
|
165
|
+
get(key) {
|
|
166
|
+
return this.entries.find(e => e.key === key)?.src;
|
|
167
|
+
}
|
|
168
|
+
has(key) {
|
|
169
|
+
return this.entries.some(e => e.key === key);
|
|
170
|
+
}
|
|
171
|
+
toSource() {
|
|
172
|
+
return this.entries.map(e => e.src).join("\n");
|
|
173
|
+
}
|
|
174
|
+
snapshot() {
|
|
175
|
+
return this.entries.map(e => ({ ...e }));
|
|
176
|
+
}
|
|
177
|
+
restoreSnapshot(snap) {
|
|
178
|
+
this.entries = snap;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
29
181
|
export async function pebbleRepl() {
|
|
30
182
|
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
31
183
|
console.log(chalk.bold("Pebble REPL"));
|
|
32
184
|
console.log("Type pebble expressions to evaluate them. Use :quit to exit.\n");
|
|
33
185
|
const entryFile = "__repl__.pebble";
|
|
186
|
+
const context = new ReplContext();
|
|
34
187
|
while (true) {
|
|
35
188
|
let input;
|
|
36
189
|
try {
|
|
@@ -46,9 +199,66 @@ export async function pebbleRepl() {
|
|
|
46
199
|
if (trimmed === ":quit" || trimmed === ":q" || trimmed === ":exit") {
|
|
47
200
|
break;
|
|
48
201
|
}
|
|
202
|
+
// multi-line: keep reading if delimiters are unclosed
|
|
203
|
+
let fullInput = trimmed;
|
|
204
|
+
while (_unclosedDepth(fullInput) > 0) {
|
|
205
|
+
let cont;
|
|
206
|
+
try {
|
|
207
|
+
cont = await rl.question(chalk.blue(" ...> "));
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
fullInput += "\n" + cont;
|
|
213
|
+
}
|
|
214
|
+
// classify: keyword-first → statement, otherwise → expression
|
|
215
|
+
const firstWord = _readFirstWord(fullInput);
|
|
216
|
+
const isAccum = _accumKeywords.has(firstWord);
|
|
217
|
+
// detect reassignment: <name> = <expr> where <name> is a known variable
|
|
218
|
+
let isReassignment = false;
|
|
219
|
+
let reassignName = "";
|
|
220
|
+
if (!isAccum) {
|
|
221
|
+
const reassignMatch = fullInput.match(/^(\w+)\s*=\s/);
|
|
222
|
+
if (reassignMatch && context.has("var:" + reassignMatch[1])) {
|
|
223
|
+
isReassignment = true;
|
|
224
|
+
reassignName = reassignMatch[1];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// save a snapshot so we can restore on failure
|
|
228
|
+
const snapshot = context.snapshot();
|
|
229
|
+
if (isAccum) {
|
|
230
|
+
// it's a declaration — accumulate in context
|
|
231
|
+
const key = _extractDeclKey(firstWord, fullInput);
|
|
232
|
+
if (key) {
|
|
233
|
+
context.set(key, _ensureSemicolon(fullInput));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (isReassignment) {
|
|
237
|
+
// append the assignment to the variable's context entry
|
|
238
|
+
const key = "var:" + reassignName;
|
|
239
|
+
const existing = context.get(key);
|
|
240
|
+
context.set(key, existing + "\n" + _ensureSemicolon(fullInput));
|
|
241
|
+
}
|
|
242
|
+
// build the full source
|
|
243
|
+
const contextSrc = context.toSource();
|
|
244
|
+
let fullSrc;
|
|
245
|
+
if (isAccum) {
|
|
246
|
+
// declarations are already in context; no extra body needed
|
|
247
|
+
fullSrc = contextSrc;
|
|
248
|
+
}
|
|
249
|
+
else if (isReassignment) {
|
|
250
|
+
// assignment is in context; append variable name to return new value
|
|
251
|
+
fullSrc = contextSrc + "\n" + reassignName;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// expression or transient statement — append after context
|
|
255
|
+
fullSrc = contextSrc
|
|
256
|
+
? contextSrc + "\n" + fullInput
|
|
257
|
+
: fullInput;
|
|
258
|
+
}
|
|
49
259
|
const ioApi = createMemoryCompilerIoApi({
|
|
50
260
|
sources: new Map([
|
|
51
|
-
[entryFile, fromUtf8(
|
|
261
|
+
[entryFile, fromUtf8(fullSrc)],
|
|
52
262
|
]),
|
|
53
263
|
useConsoleAsOutput: true,
|
|
54
264
|
});
|
|
@@ -61,15 +271,15 @@ export async function pebbleRepl() {
|
|
|
61
271
|
entry: entryFile,
|
|
62
272
|
root: "/",
|
|
63
273
|
});
|
|
64
|
-
// print trace logs
|
|
65
274
|
for (const log of logs) {
|
|
66
275
|
console.log(chalk.magenta("trace:"), log);
|
|
67
276
|
}
|
|
68
|
-
// print result
|
|
69
277
|
console.log(formatCEKValue(result));
|
|
70
278
|
console.log(chalk.dim(`cpu: ${budgetSpent.cpu} | mem: ${budgetSpent.mem}`));
|
|
71
279
|
}
|
|
72
280
|
catch (e) {
|
|
281
|
+
// restore context on failure
|
|
282
|
+
context.restoreSnapshot(snapshot);
|
|
73
283
|
const diagnostics = compiler.diagnostics;
|
|
74
284
|
if (diagnostics.length > 0) {
|
|
75
285
|
for (const d of diagnostics) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const PEBBLE_VERSION = "0.1.3
|
|
2
|
-
export declare const PEBBLE_LIB_VERSION = "0.1.3
|
|
3
|
-
export declare const PEBBLE_COMMIT_HASH = "
|
|
1
|
+
export declare const PEBBLE_VERSION = "0.1.3";
|
|
2
|
+
export declare const PEBBLE_LIB_VERSION = "0.1.3";
|
|
3
|
+
export declare const PEBBLE_COMMIT_HASH = "166d04dd786bb34749efe5a644c06c27edb0835a";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// This file is auto-generated by scripts/genVersions.js. Do not edit.
|
|
2
|
-
export const PEBBLE_VERSION = "0.1.3
|
|
3
|
-
export const PEBBLE_LIB_VERSION = "0.1.3
|
|
4
|
-
export const PEBBLE_COMMIT_HASH = "
|
|
2
|
+
export const PEBBLE_VERSION = "0.1.3";
|
|
3
|
+
export const PEBBLE_LIB_VERSION = "0.1.3";
|
|
4
|
+
export const PEBBLE_COMMIT_HASH = "166d04dd786bb34749efe5a644c06c27edb0835a";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harmoniclabs/pebble-cli",
|
|
3
|
-
"version": "0.1.3
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A simple, yet rock solid, functional language with an imperative bias, targeting UPLC",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pebble": "./dist/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@harmoniclabs/crypto": "^0.3.0",
|
|
52
52
|
"@harmoniclabs/obj-utils": "^1.0.0",
|
|
53
|
-
"@harmoniclabs/pebble": "0.1.3
|
|
53
|
+
"@harmoniclabs/pebble": "0.1.3",
|
|
54
54
|
"@harmoniclabs/plutus-machine": "^3.0.0",
|
|
55
55
|
"@harmoniclabs/uint8array-utils": "^1.0.4",
|
|
56
56
|
"@harmoniclabs/uplc": "^2.0.5",
|