diff --git a/src/lang/debug/repl.ts b/src/lang/debug/repl.ts index 4ff8b85..5e6b7d9 100644 --- a/src/lang/debug/repl.ts +++ b/src/lang/debug/repl.ts @@ -2,7 +2,7 @@ import * as readline from 'readline'; import * as fs from 'fs'; import { parseExpr, ParseError } from '../parser/parser'; -import { SourceText, renderSpan, sourceText } from 'source-text'; +import { SourceRegion, SourceText, renderSpan, sourceText } from 'source-text'; import { exprToString } from '../debug/expr_show'; import { valueToString } from '../debug/value_show'; import { Program } from '../program'; @@ -27,7 +27,7 @@ function runSource(inputRaw: string, isRepl: boolean): boolean { try { // Wrap in SourceText - const text = sourceText(input); + const text = sourceText(input).fullRegion(); // === Parse === const parseResult = parseExpr(text); @@ -184,7 +184,7 @@ function getErrorMessage(err: ParseError): string { } -function printPrettyError(text: SourceText, err: ParseError) { +function printPrettyError(text: SourceRegion, err: ParseError) { const msg = getErrorMessage(err); console.log(`\n${C.Red}${C.Bold}Parse Error:${C.Reset} ${C.Bold}${msg}${C.Reset}`); @@ -213,3 +213,4 @@ function printPrettyError(text: SourceText, err: ParseError) { } } } + diff --git a/src/lang/parser/cursor.test.ts b/src/lang/parser/cursor.test.ts index 0ea509d..908a90e 100644 --- a/src/lang/parser/cursor.test.ts +++ b/src/lang/parser/cursor.test.ts @@ -1,5 +1,5 @@ // AI GENERATED -import { SourceText } from "source-text"; +import { SourceText, sourceText } from "source-text"; import { Cursor, scanString, scanNumber } from "./cursor"; import { Result } from "../result"; @@ -53,13 +53,13 @@ function assertError(result: Result, expectedTag: string, expectedReas // === Number Tests === function test_integers() { - const src = new SourceText("123"); + const src = sourceText("123").fullRegion(); const cursor = new Cursor(src); const result = scanNumber(cursor); assertOk(result, 123); - const src2 = new SourceText("-500"); + const src2 = sourceText("-500").fullRegion(); const cursor2 = new Cursor(src2); const result2 = scanNumber(cursor2); @@ -69,12 +69,12 @@ function test_integers() { } function test_floats() { - const src = new SourceText("3.14159"); + const src = sourceText("3.14159").fullRegion(); const cursor = new Cursor(src); const result = scanNumber(cursor); assertOk(result, 3.14159); - const src2 = new SourceText("-0.001"); + const src2 = sourceText("-0.001").fullRegion(); const cursor2 = new Cursor(src2); const result2 = scanNumber(cursor2); assertOk(result2, -0.001); @@ -84,14 +84,14 @@ function test_floats() { function test_number_errors() { // 1. Trailing Dot - const c1 = new Cursor(new SourceText("1.")); + const c1 = new Cursor(sourceText("1.").fullRegion()); const r1 = scanNumber(c1); assertError(r1, "InvalidNumber", "MissingFractionalDigits"); // 2. No leading digit (.5) // Let's test "Saw Sign but no digits" which is a hard error - const c2 = new Cursor(new SourceText("-")); // Just a minus + const c2 = new Cursor(sourceText("-").fullRegion()); // Just a minus const r2 = scanNumber(c2); assertError(r2, "ExpectedNumber"); @@ -101,13 +101,13 @@ function test_number_errors() { // === String Tests === function test_basic_strings() { - const src = new SourceText('"hello world"'); + const src = sourceText('"hello world"').fullRegion(); const cursor = new Cursor(src); const result = scanString(cursor); assertOk(result, "hello world"); - const src2 = new SourceText('""'); // Empty string + const src2 = sourceText('""').fullRegion(); // Empty string const cursor2 = new Cursor(src2); const result2 = scanString(cursor2); @@ -117,26 +117,26 @@ function test_basic_strings() { } function test_string_escapes() { - const src = new SourceText('"line1\\nline2"'); + const src = sourceText('"line1\\nline2"').fullRegion(); const cursor = new Cursor(src); const result = scanString(cursor); assertOk(result, "line1\nline2"); - const src2 = new SourceText('"col1\\tcol2"'); + const src2 = sourceText('"col1\\tcol2"').fullRegion(); const cursor2 = new Cursor(src2); const result2 = scanString(cursor2); assertOk(result2, "col1\tcol2"); - const src3 = new SourceText('"quote: \\" slash: \\\\"'); + const src3 = sourceText('"quote: \\" slash: \\\\"').fullRegion(); const cursor3 = new Cursor(src3); const result3 = scanString(cursor3); assertOk(result3, 'quote: " slash: \\'); // Null byte test - const src4 = new SourceText('"null\\0byte"'); + const src4 = sourceText('"null\\0byte"').fullRegion(); const cursor4 = new Cursor(src4); const result4 = scanString(cursor4); assertOk(result4, "null\0byte"); @@ -146,23 +146,23 @@ function test_string_escapes() { function test_unicode_escapes() { // Rocket emoji: 🚀 (U+1F680) - const c1 = new Cursor(new SourceText('"\\u{1F680}"')); + const c1 = new Cursor(sourceText('"\\u{1F680}"').fullRegion()); assertOk(scanString(c1), "🚀"); // Two escapes - const c2 = new Cursor(new SourceText('"\\u{41}\\u{42}"')); + const c2 = new Cursor(sourceText('"\\u{41}\\u{42}"').fullRegion()); assertOk(scanString(c2), "AB"); // Error: Missing Brace - const c3 = new Cursor(new SourceText('"\\u1F680"')); + const c3 = new Cursor(sourceText('"\\u1F680"').fullRegion()); assertError(scanString(c3), "InvalidEscape", { tag: "UnicodeMissingBrace" }); // Error: Empty - const c4 = new Cursor(new SourceText('"\\u{}"')); + const c4 = new Cursor(sourceText('"\\u{}"').fullRegion()); assertError(scanString(c4), "InvalidEscape", { tag: "UnicodeNoDigits" }); // Error: Overflow - const c5 = new Cursor(new SourceText('"\\u{110000}"')); + const c5 = new Cursor(sourceText('"\\u{110000}"').fullRegion()); const res5 = scanString(c5); // Need to check the value inside the reason for overflow if (res5.tag === 'ok') throw new Error("Should have failed overflow"); @@ -180,7 +180,7 @@ function test_cursor_tracking() { // Line 2: 456 (LF) // Line 3: "foo" const code = "123\r\n456\n\"foo\""; - const src = new SourceText(code); + const src = sourceText(code).fullRegion(); const cursor = new Cursor(src); // 1. Scan 123 diff --git a/src/lang/parser/cursor.ts b/src/lang/parser/cursor.ts index 02f96b7..1bb72c1 100644 --- a/src/lang/parser/cursor.ts +++ b/src/lang/parser/cursor.ts @@ -1,5 +1,5 @@ import { char, NEW_LINE, CARRIAGE_RETURN, DOT, DIGIT_0, DIGIT_9, LOWERCASE_a, LOWERCASE_f, UPPERCASE_A, UPPERCASE_F, SPACE, TAB } from 'source-text'; -import type { SourceText, Span, SourceLocation, CodePoint, StringIndex, CodePointIndex } from 'source-text'; +import type { SourceRegion, SourceText, Span, SourceLocation, CodePoint, StringIndex, CodePointIndex } from 'source-text'; import { Result } from '../result'; export type CursorState = { @@ -10,13 +10,21 @@ export type CursorState = { } export class Cursor { - private index: CodePointIndex = 0; - private line: number = 1; - private column: number = 1; + private index: CodePointIndex; + private line: number; + private column: number; // Track previous char to handle \r\n correctly private lastCharWasCR: boolean = false; - constructor(readonly text: SourceText) {} + constructor(readonly region: SourceRegion) { + this.index = region.span.start.index; + this.line = region.span.start.line; + this.column = region.span.start.column; + } + + get text(): SourceText { + return this.region.source; + } save(): CursorState { return { index: this.index, line: this.line, column: this.column, lastCharWasCR: this.lastCharWasCR }; @@ -30,14 +38,16 @@ export class Cursor { } eof(): boolean { - return this.index >= this.text.length; + return this.index >= this.region.span.end.index; } peek(n: number = 0): CodePoint | undefined { + if (this.index + n >= this.region.span.end.index) return undefined; return this.text.chars[this.index + n]?.char; } next(): CodePoint | undefined { + if (this.eof()) return undefined; const ref = this.text.chars[this.index]; if (!ref) return undefined; diff --git a/src/lang/parser/parser.ts b/src/lang/parser/parser.ts index 3b0313b..7c024fa 100644 --- a/src/lang/parser/parser.ts +++ b/src/lang/parser/parser.ts @@ -1,6 +1,6 @@ import { Cursor } from './cursor'; import { ExprScanError, exprStart, ExprStartToken, IdentifierKind, identifierScanner, isNextTokenExprStart, isNextTokenProductPatternStart, patternStart, PatternStartToken, signalExprStart, SignalExprStartToken, skipWhitespaceAndComments } from './scanner'; -import { char, CodePoint, SourceText, Span } from 'source-text'; +import { char, CodePoint, SourceRegion, SourceText, Span } from 'source-text'; import { Result } from '../result'; import { Expr, ExprBinding, FieldAssignment, FieldPattern, FunctionName, MatchBranch, Pattern, ProductPattern, SignalExpr, SignalExprBinding } from '../expr'; @@ -531,7 +531,7 @@ function recordPatternField(cursor: Cursor): FieldPattern { } -export function parseExpr(source: SourceText): Result { +export function parseExpr(source: SourceRegion): Result { const cursor = new Cursor(source); try { @@ -553,7 +553,7 @@ export function parseExpr(source: SourceText): Result { } } -export function parseSignalExpr(source: SourceText): Result { +export function parseSignalExpr(source: SourceRegion): Result { const cursor = new Cursor(source); try { @@ -581,7 +581,7 @@ function functionParameters(cursor: Cursor): ProductPattern[] { return parameters; } -export function parseFunctionParameters(source: SourceText): Result { +export function parseFunctionParameters(source: SourceRegion): Result { const cursor = new Cursor(source); try { skipWhitespaceAndComments(cursor); @@ -603,7 +603,7 @@ export function parseFunctionParameters(source: SourceText): Result { +export function parseFunctionName(source: SourceRegion): Result { const cursor = new Cursor(source); try { skipWhitespaceAndComments(cursor); diff --git a/src/ui/Component/ParseError.tsx b/src/ui/Component/ParseError.tsx index 164d49a..dd251ab 100644 --- a/src/ui/Component/ParseError.tsx +++ b/src/ui/Component/ParseError.tsx @@ -1,5 +1,5 @@ import { ParseError } from "src/lang/parser/parser"; -import { renderSpan, SourceText } from "source-text"; +import { renderSpan, SourceRegion } from "source-text"; import { DisplayLineViews } from "./LineView"; export function formatErrorMesage(err: ParseError): string { @@ -118,7 +118,7 @@ function formatChar(cp: number | undefined): string { return `'${s}'`; } -export function ShowParseError(props: { text: SourceText, err: ParseError }) { +export function ShowParseError(props: { text: SourceRegion, err: ParseError }) { const msg = () => formatErrorMesage(props.err); const views = () => renderSpan(props.text, props.err.span, 3); diff --git a/src/ui/Digith/DigithError.tsx b/src/ui/Digith/DigithError.tsx index e75d8ef..266b691 100644 --- a/src/ui/Digith/DigithError.tsx +++ b/src/ui/Digith/DigithError.tsx @@ -1,6 +1,6 @@ import { For, Match, Show, Switch } from "solid-js"; import { ParseError } from "src/lang/parser/parser"; -import { SourceText } from "source-text"; +import { SourceRegion } from "source-text"; import { ShowParseError } from 'src/ui/Component/ParseError'; import { Program } from "src/lang/program"; @@ -13,7 +13,7 @@ export type DigithError = { export namespace DigithError { export type Payload = - | { tag: "Parse", err: ParseError, src: SourceText } + | { tag: "Parse", err: ParseError, src: SourceRegion } | { tag: "Program", err: Program.Error }; export type Id = string; diff --git a/src/ui/Digith/Function/FunctionDigith.tsx b/src/ui/Digith/Function/FunctionDigith.tsx index a699ee8..3c59570 100644 --- a/src/ui/Digith/Function/FunctionDigith.tsx +++ b/src/ui/Digith/Function/FunctionDigith.tsx @@ -17,13 +17,13 @@ type Input = { const validator: Validation = letValidate( (input) => ({ parameters: V.elseErr(validateParamsRaw(input.raw_params), err => ({ - payload: { tag: "Parse", field: "params", err, src: sourceText(input.raw_params) }, + payload: { tag: "Parse", field: "params", err, src: sourceText(input.raw_params).fullRegion() }, ids: ["params"], tags: ["footer"], config: { title: "Parameters" }, })), body: V.elseErr(validateExprRaw(input.raw_body), err => ({ - payload: { tag: "Parse", field: "body", err, src: sourceText(input.raw_body) }, + payload: { tag: "Parse", field: "body", err, src: sourceText(input.raw_body).fullRegion() }, ids: ["body"], tags: ["footer"], config: { title: "Function Body" }, diff --git a/src/ui/Digith/Function/NewFunctionDraftDigith.tsx b/src/ui/Digith/Function/NewFunctionDraftDigith.tsx index c35a199..8742c6c 100644 --- a/src/ui/Digith/Function/NewFunctionDraftDigith.tsx +++ b/src/ui/Digith/Function/NewFunctionDraftDigith.tsx @@ -18,19 +18,19 @@ type Input = { const validator: Validation = letValidate( (input) =>({ name: V.elseErr(validateNameRaw(input.raw_name), err =>({ - payload: { tag: "Parse", err, src: sourceText(input.raw_name) }, + payload: { tag: "Parse", err, src: sourceText(input.raw_name).fullRegion() }, ids: ["name"], tags: ["footer"], config: { title: "Function Name", display: "flat" }, })), parameters: V.elseErr(validateParamsRaw(input.raw_params), err => ({ - payload: { tag: "Parse", err, src: sourceText(input.raw_params) }, + payload: { tag: "Parse", err, src: sourceText(input.raw_params).fullRegion() }, ids: ["params"], tags: ["footer"], config: { title: "Parameters", display: "flat" }, })), body: V.elseErr(validateExprRaw(input.raw_body), err => ({ - payload: { tag: "Parse", err, src: sourceText(input.raw_body) }, + payload: { tag: "Parse", err, src: sourceText(input.raw_body).fullRegion() }, ids: ["body"], tags: ["footer"], config: { title: "Function Body", display: "flat" }, diff --git a/src/ui/Digith/REPL.tsx b/src/ui/Digith/REPL.tsx index 2adf2fb..c10f48a 100644 --- a/src/ui/Digith/REPL.tsx +++ b/src/ui/Digith/REPL.tsx @@ -3,7 +3,7 @@ import { useProgram } from 'src/ui/ProgramProvider'; import { eval_start } from 'src/lang/eval/evaluator'; import { Value } from 'src/lang/eval/value'; import { RuntimeError } from 'src/lang/eval/error'; -import { SourceText, sourceText } from 'source-text'; +import { SourceRegion, SourceText, sourceText } from 'source-text'; import { ParseError, parseExpr } from 'src/lang/parser/parser'; import { ShowParseError } from 'src/ui/Component/ParseError'; import { Val } from 'src/ui/Component/Value'; @@ -15,7 +15,7 @@ namespace ReplResult { export type Success = { tag: "success", value: Value } export type Parse_Error = - { tag: "parse_error", text: SourceText, err: ParseError } + { tag: "parse_error", text: SourceRegion, err: ParseError } export type Runtime_Error = { tag: "runtime_error", err: RuntimeError } } @@ -39,7 +39,7 @@ export function ExprREPL() { if (input().trim() === "") { return; } - const text = sourceText(raw); + const text = sourceText(raw).fullRegion(); const parseResult = parseExpr(text); if (parseResult.tag === "error") { diff --git a/src/ui/Digith/Signal/NewSignalDraftDigith.tsx b/src/ui/Digith/Signal/NewSignalDraftDigith.tsx index 4714111..a8a3da7 100644 --- a/src/ui/Digith/Signal/NewSignalDraftDigith.tsx +++ b/src/ui/Digith/Signal/NewSignalDraftDigith.tsx @@ -17,13 +17,13 @@ type Input = { const validator: Validation = letValidate( (input) =>({ name: V.elseErr(validateNameRaw(input.raw_name), err =>({ - payload: { tag: "Parse", err, src: sourceText(input.raw_name) }, + payload: { tag: "Parse", err, src: sourceText(input.raw_name).fullRegion() }, ids: ["name"], tags: ["footer"], config: { title: "Signal Name", display: "flat" }, })), body: V.elseErr(validateSignalExprRaw(input.raw_body), err => ({ - payload: { tag: "Parse", err, src: sourceText(input.raw_body) }, + payload: { tag: "Parse", err, src: sourceText(input.raw_body).fullRegion() }, ids: ["body"], tags: ["footer"], config: { title: "Signal Body", display: "flat" }, diff --git a/src/ui/Digith/Signal/SignalDigith.tsx b/src/ui/Digith/Signal/SignalDigith.tsx index 25d1972..179b46c 100644 --- a/src/ui/Digith/Signal/SignalDigith.tsx +++ b/src/ui/Digith/Signal/SignalDigith.tsx @@ -19,7 +19,7 @@ type Input = { const validator: Validation = letValidate( (input) => ({ body: V.elseErr(validateSignalExprRaw(input.raw_body), err => ({ - payload: { tag: "Parse", field: "body", err, src: sourceText(input.raw_body) }, + payload: { tag: "Parse", field: "body", err, src: sourceText(input.raw_body).fullRegion() }, ids: ["body"], tags: ["footer"], config: { title: "Signal Body" }, diff --git a/src/ui/validation/helpers.ts b/src/ui/validation/helpers.ts index 50b355a..151805b 100644 --- a/src/ui/validation/helpers.ts +++ b/src/ui/validation/helpers.ts @@ -5,25 +5,25 @@ import { V } from "./"; // === Parser wrappers === export function validateNameRaw(input: string): V { - const src = sourceText(input); + const src = sourceText(input).fullRegion(); const res = parseFunctionName(src); return res.tag === "ok" ? V.ok(res.value) : V.errors([res.error]); }; export function validateParamsRaw(input: string): V { - const src = sourceText(input); + const src = sourceText(input).fullRegion(); const res = parseFunctionParameters(src); return res.tag === "ok" ? V.ok(res.value) : V.errors([res.error]); }; export function validateExprRaw(input: string): V { - const src = sourceText(input); + const src = sourceText(input).fullRegion(); const res = parseExpr(src); return res.tag === "ok" ? V.ok(res.value) : V.errors([res.error]); }; export function validateSignalExprRaw(input: string): V { - const src = sourceText(input); + const src = sourceText(input).fullRegion(); const res = parseSignalExpr(src); return res.tag === "ok" ? V.ok(res.value) : V.errors([res.error]); };