Skip to content

Commit 76060ac

Browse files
committed
Bump forthic-ts to v0.18.0
Handle quadrulple quote case for nested quotes
1 parent a559a40 commit 76060ac

File tree

4 files changed

+126
-4
lines changed

4 files changed

+126
-4
lines changed

forthic-ts/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@forthic/interp",
3-
"version": "0.17.0",
3+
"version": "0.18.0",
44
"description": "Forthic interpreter",
55
"main": "dist/cjs/index.js",
66
"module": "dist/esm/index.js",

forthic-ts/src/forthic/tests/streamingRun.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,11 @@ test("Nested string issue", async () => {
358358
const gen = interp.streamingRun(`"""Reply saying "Thanks`, false);
359359
await gen.next();
360360

361-
// Should throw an error because we're done
361+
// Should now handle nested quotes correctly (no longer throws error)
362362
interp = new Interpreter();
363363
const gen2 = interp.streamingRun(`"""Reply saying "Thanks""""`, true);
364-
await expect(gen2.next()).rejects.toThrow(UnterminatedStringError);
364+
const result = await gen2.next();
365+
expect(result.done).toBe(true);
365366
});
366367

367368
class SampleModule extends Module {

forthic-ts/src/forthic/tests/tokenizer.test.ts

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,111 @@ test("Unterminated string", () => {
235235
expect(e).toBeInstanceOf(UnterminatedStringError);
236236
console.log(e.message);
237237
}
238-
})
238+
})
239+
240+
describe("Triple quote string with nested quotes", () => {
241+
const reference_location = new CodeLocation({
242+
screen_name: "test",
243+
line: 1,
244+
column: 1,
245+
start_pos: 0,
246+
});
247+
248+
test("Basic case: '''I said 'Hello''''", () => {
249+
const input = "'''I said 'Hello''''";
250+
const tokenizer = new Tokenizer(input, reference_location);
251+
const token = tokenizer.next_token();
252+
253+
expect(token.string).toEqual("I said 'Hello'");
254+
});
255+
256+
test("Normal triple quote behavior (no 4+ consecutive quotes)", () => {
257+
const input = "'''Hello'''";
258+
const tokenizer = new Tokenizer(input, reference_location);
259+
const token = tokenizer.next_token();
260+
261+
expect(token.string).toEqual("Hello");
262+
});
263+
264+
test("Double quotes with greedy mode", () => {
265+
const input = '"""I said "Hello""""';
266+
const tokenizer = new Tokenizer(input, reference_location);
267+
const token = tokenizer.next_token();
268+
269+
expect(token.string).toEqual('I said "Hello"');
270+
});
271+
272+
test("Six consecutive quotes (empty string case)", () => {
273+
const input = "''''''";
274+
const tokenizer = new Tokenizer(input, reference_location);
275+
const token = tokenizer.next_token();
276+
277+
expect(token.string).toEqual("");
278+
});
279+
280+
test("Eight consecutive quotes (two quote content)", () => {
281+
const input = "''''''''";
282+
const tokenizer = new Tokenizer(input, reference_location);
283+
const token = tokenizer.next_token();
284+
285+
expect(token.string).toEqual("''");
286+
});
287+
288+
test("Multiple nested quotes", () => {
289+
const input = `"""He said "I said 'Hello' to you""""`;
290+
const tokenizer = new Tokenizer(input, reference_location);
291+
const token = tokenizer.next_token();
292+
293+
expect(token.string).toEqual(`He said "I said 'Hello' to you"`);
294+
});
295+
296+
test("No greedy mode when triple quote not followed by quote", () => {
297+
const input = "'''Hello''' world'''";
298+
const tokenizer = new Tokenizer(input, reference_location);
299+
const token = tokenizer.next_token();
300+
301+
// Should close at first ''' since it's not followed by another quote
302+
expect(token.string).toEqual("Hello");
303+
});
304+
305+
test("Content with apostrophes (contractions)", () => {
306+
const input = "'''It's a beautiful day, isn't it?''''";
307+
const tokenizer = new Tokenizer(input, reference_location);
308+
const token = tokenizer.next_token();
309+
310+
expect(token.string).toEqual("It's a beautiful day, isn't it?'");
311+
});
312+
313+
test("Mixed quote types don't trigger greedy mode", () => {
314+
const input = "'''Hello\"\"\"";
315+
const tokenizer = new Tokenizer(input, reference_location);
316+
317+
try {
318+
tokenizer.next_token();
319+
} catch (e) {
320+
expect(e).toBeInstanceOf(UnterminatedStringError);
321+
}
322+
});
323+
324+
test("Backward compatibility: normal strings unchanged", () => {
325+
const inputs = [
326+
"'''simple'''",
327+
"'''multi\nline\nstring'''",
328+
"'''string with \"double quotes\"'''",
329+
"'''string with 'single quotes'''''"
330+
];
331+
332+
const expected = [
333+
"simple",
334+
"multi\nline\nstring",
335+
'string with "double quotes"',
336+
"string with 'single quotes''"
337+
];
338+
339+
inputs.forEach((input, i) => {
340+
const tokenizer = new Tokenizer(input, reference_location);
341+
const token = tokenizer.next_token();
342+
expect(token.string).toEqual(expected[i]);
343+
});
344+
});
345+
});

forthic-ts/src/forthic/tokenizer.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,19 @@ export class Tokenizer {
364364
char === string_delimiter &&
365365
this.is_triple_quote(this.input_pos, char)
366366
) {
367+
// Check if this triple quote is followed by at least one more quote (greedy mode trigger)
368+
if (
369+
this.input_pos + 3 < this.input_string.length &&
370+
this.input_string[this.input_pos + 3] === string_delimiter
371+
) {
372+
// Greedy mode: include this quote as content and continue looking for the end
373+
this.advance_position(1); // Advance by 1 to catch overlapping sequences
374+
this.token_string += string_delimiter;
375+
this.string_delta.end = this.input_pos;
376+
continue;
377+
}
378+
379+
// Normal behavior: close at first triple quote
367380
this.advance_position(3);
368381
const token = new Token(
369382
TokenType.STRING,
@@ -422,6 +435,7 @@ export class Tokenizer {
422435
);
423436
}
424437

438+
425439
transition_from_GATHER_WORD(): Token {
426440
this.note_start_token();
427441
while (this.input_pos < this.input_string.length) {

0 commit comments

Comments
 (0)