@harmoniclabs/pebble-cli 0.1.3-dev8 → 0.1.4-dev0

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.
@@ -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(trimmed)],
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-dev8";
2
- export declare const PEBBLE_LIB_VERSION = "0.1.3-dev8";
3
- export declare const PEBBLE_COMMIT_HASH = "b4f09962fed52022b74db16ea95b21895fdbb416";
1
+ export declare const PEBBLE_VERSION = "0.1.4-dev0";
2
+ export declare const PEBBLE_LIB_VERSION = "0.1.4-dev0";
3
+ export declare const PEBBLE_COMMIT_HASH = "17215e1c18bf631771d171cf30476276fa80a31a";
@@ -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-dev8";
3
- export const PEBBLE_LIB_VERSION = "0.1.3-dev8";
4
- export const PEBBLE_COMMIT_HASH = "b4f09962fed52022b74db16ea95b21895fdbb416";
2
+ export const PEBBLE_VERSION = "0.1.4-dev0";
3
+ export const PEBBLE_LIB_VERSION = "0.1.4-dev0";
4
+ export const PEBBLE_COMMIT_HASH = "17215e1c18bf631771d171cf30476276fa80a31a";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harmoniclabs/pebble-cli",
3
- "version": "0.1.3-dev8",
3
+ "version": "0.1.4-dev0",
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-dev8",
53
+ "@harmoniclabs/pebble": "0.1.4-dev0",
54
54
  "@harmoniclabs/plutus-machine": "^3.0.0",
55
55
  "@harmoniclabs/uint8array-utils": "^1.0.4",
56
56
  "@harmoniclabs/uplc": "^2.0.5",