feat: improve error rendering on code snippets

This commit is contained in:
Araozu 2024-08-12 20:31:19 -05:00
parent a476fffe2c
commit 25a5b20d5f
6 changed files with 58 additions and 13 deletions

View File

@ -4,5 +4,5 @@ const { error_type = "Unknown", error_message } = Astro.props;
<div class="px-4 py-2 rounded bg-red-200 dark:bg-red-950"> <div class="px-4 py-2 rounded bg-red-200 dark:bg-red-950">
<span class="inline-block font-bold">{error_type} error:</span> <span class="inline-block font-bold">{error_type} error:</span>
<span class="inline-block whitespace-pre-wrap">{error_message}</span> <span class="whitespace-pre-wrap">{error_message}</span>
</div> </div>

View File

@ -1,6 +1,12 @@
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
import { leftTrimDedent } from "../components/utils"; import { leftTrimDedent } from "../components/utils";
export type ReferenceItem = {
symbol_start: number
symbol_end: number
reference: string
}
export interface Token { export interface Token {
token_type: TokenType token_type: TokenType
value: string value: string
@ -55,13 +61,13 @@ export interface SemanticError {
} }
export interface TokenizeResult { export interface TokenizeResult {
Ok?: Token[], Ok?: [Array<Token>, Array<ReferenceItem>],
SyntaxOnly?: [Token[], Err], SyntaxOnly?: [Token[], Err],
TokensOnly?: [Token[], Err], TokensOnly?: [Token[], Err],
Err?: Err, Err?: Err,
} }
const error_classes = "underline decoration-wavy decoration-red-500"; const error_classes = "underline underline-offset-4 decoration-wavy decoration-red-500";
export async function native_highlighter(code: string): Promise<[string, string, string | null]> { export async function native_highlighter(code: string): Promise<[string, string, string | null]> {
let formatted_code = leftTrimDedent(code).join("\n"); let formatted_code = leftTrimDedent(code).join("\n");
@ -85,7 +91,12 @@ export async function native_highlighter(code: string): Promise<[string, string,
return semantic_error_highlighter(formatted_code, tokens, error.Semantic!); return semantic_error_highlighter(formatted_code, tokens, error.Semantic!);
} }
const tokens = result.Ok!; const tokens = result.Ok! as unknown as Array<Token>;
// TODO: this is disable because the compiler has not
// implemented this feature yet
// const [tokens, references] = result.Ok!;
// console.log("refs:");
// console.log(references);
const output = highlight_tokens(formatted_code, tokens); const output = highlight_tokens(formatted_code, tokens);
@ -107,22 +118,25 @@ function lex_error_highlighter(code: string, error: LexError): [string, string,
const token = `<span class="token ${error_classes}">${err_str}</span>`; const token = `<span class="token ${error_classes}">${err_str}</span>`;
const all = `${before_err}${token}${after_err}`; const all = `${before_err}${token}${after_err}`;
const [error_line, error_column] = absolute_to_line_column(code, error.position);
// TODO: Transform absolute posijion (error.position) into line:column // TODO: Transform absolute posijion (error.position) into line:column
return [all, "Lexical", error.reason + " at position " + error.position] return [all, "Lexical", error.reason + ` at line ${error_line}:${error_column} `]
} }
function syntax_error_highlighter(code: string, tokens: Array<Token>, error: SyntaxError): [string, string, string] { function syntax_error_highlighter(code: string, tokens: Array<Token>, error: SyntaxError): [string, string, string] {
const highlighted = highlight_tokens(code, tokens, error.error_start, error.error_end); const highlighted = highlight_tokens(code, tokens, error.error_start, error.error_end);
const [error_line, error_column] = absolute_to_line_column(code, error.error_start);
const error_message = `${error.reason} from position ${error.error_start} to ${error.error_end}`; const error_message = `${error.reason} at line ${error_line}:${error_column}`;
return [highlighted, "Syntax", error_message]; return [highlighted, "Syntax", error_message];
} }
function semantic_error_highlighter(code: string, tokens: Array<Token>, error: SyntaxError): [string, string, string] { function semantic_error_highlighter(code: string, tokens: Array<Token>, error: SyntaxError): [string, string, string] {
const highlighted = highlight_tokens(code, tokens, error.error_start, error.error_end); const highlighted = highlight_tokens(code, tokens, error.error_start, error.error_end);
const [error_line, error_column] = absolute_to_line_column(code, error.error_start);
const error_message = `${error.reason} from position ${error.error_start} to ${error.error_end}`; const error_message = `${error.reason} at line ${error_line}:${error_column}`;
return [highlighted, "Semantic", error_message]; return [highlighted, "Semantic", error_message];
} }
@ -149,7 +163,7 @@ function highlight_tokens(input: string, tokens: Array<Token>, error_start = -1,
const token_start = t.position; const token_start = t.position;
const token_end = t.position + t.value.length; const token_end = t.position + t.value.length;
let is_errored = (token_start == error_start); let is_errored = (token_start >= error_start && token_end <= error_end);
// Some tokens require processing (like multiline comments) // Some tokens require processing (like multiline comments)
@ -172,6 +186,33 @@ function highlight_tokens(input: string, tokens: Array<Token>, error_start = -1,
return output; return output;
} }
/**
* Transform an absolute position in source code to a line:column combination.
*
* Both line and column are 1-based
*
* @param input the source code
* @param absolute the absolute position
*/
function absolute_to_line_column(input: string, absolute: number): [number, number] {
let line_count = 1;
let last_newline_pos = 0;
// Count lines
for (let i = 0; i < input.length; i += 1) {
if (i === absolute) {
break;
}
if (input[i] === "\n") {
line_count += 1;
last_newline_pos = i;
}
}
return [line_count, absolute - last_newline_pos];
}
/** /**
* Certain tokens store values that differ from the source code representation. * Certain tokens store values that differ from the source code representation.
* For example, the multiline comment token stores the content of the comment * For example, the multiline comment token stores the content of the comment

View File

@ -30,6 +30,9 @@ Int char_code = 0b01000110
Int not_octal = 032 // This is 32, not 26 Int not_octal = 032 // This is 32, not 26
`} /> `} />
// TODO: Make it a compile error to have leading zeroes,
and force users to use `0o` for octal
## Float ## Float

View File

@ -27,7 +27,8 @@ number /= 1
number %= 2 number %= 2
`} /> `} />
**There are no prefix/postfix increment**, use `+=` or `-=`. **There are no prefix/postfix increment operators** (`++`, `--`),
use `+=` or `-=` instead.
<Code thpcode={` <Code thpcode={`
// Use // Use

View File

@ -64,9 +64,9 @@ val first = get_first_item(numbers)
<Code thpcode={` <Code thpcode={`
() -> () () -> ()
() -> Int () -> (Int)
(Int, Int) -> Int (Int, Int) -> (Int)
[T](Array[T]) -> T [T](Array[T]) -> (T)
`} /> `} />

View File

@ -10,7 +10,7 @@ import Code from "../../../components/Code.astro"
## Function as parameter ## Function as parameter
<Code thpcode={` <Code thpcode={`
fun map[A, B](Array[A] input, (A) -> B function) -> Array[B] fun map[A, B](Array[A] input, (A) -> (B) function) -> Array[B]
{ {
// implementation // implementation
} }