Compare commits
3 Commits
771a4b7044
...
27bfca8880
Author | SHA1 | Date | |
---|---|---|---|
27bfca8880 | |||
2fb095896b | |||
76aad9cd8a |
@ -1,10 +1,17 @@
|
|||||||
---
|
---
|
||||||
import { native_highlighter } from "../lexer/highlighter";
|
import { native_highlighter } from "../lexer/highlighter";
|
||||||
|
import CodeError from "./docs/CodeError.astro";
|
||||||
|
|
||||||
const { thpcode } = Astro.props;
|
const { thpcode } = Astro.props;
|
||||||
|
|
||||||
const native_html = await native_highlighter(thpcode);
|
const [native_html, error_type, error_message] = await native_highlighter(thpcode);
|
||||||
---
|
---
|
||||||
|
|
||||||
<pre
|
<pre
|
||||||
class="language-thp"><code class="language-thp" set:html={native_html} /><span class="absolute top-2 right-2 inline-block text-sm select-none opacity-75">thp</span></pre>
|
class="language-thp"><code class="language-thp" set:html={native_html} /><span class="absolute top-2 right-2 inline-block text-sm select-none opacity-75">thp
|
||||||
|
</span></pre>
|
||||||
|
{
|
||||||
|
error_message !== null && (
|
||||||
|
<CodeError error_type={error_type}>{error_message}</CodeError>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
8
src/components/docs/CodeError.astro
Normal file
8
src/components/docs/CodeError.astro
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
const { error_type = "Unknown" } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="px-4 py-2 rounded bg-red-200 dark:bg-red-950">
|
||||||
|
<span class="inline-block font-bold">{error_type} error:</span>
|
||||||
|
<slot />
|
||||||
|
</div>
|
@ -4,7 +4,7 @@ import PagesLayout from "./PagesLayout.astro";
|
|||||||
const { frontmatter, headings } = Astro.props;
|
const { frontmatter, headings } = Astro.props;
|
||||||
|
|
||||||
const posts = await Astro.glob("../pages/spec/**/*.{md,mdx}");
|
const posts = await Astro.glob("../pages/spec/**/*.{md,mdx}");
|
||||||
const indexSubpath = `/spec/index.md`;
|
const indexSubpath = `/spec/index.mdx`;
|
||||||
---
|
---
|
||||||
|
|
||||||
<PagesLayout
|
<PagesLayout
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
import { leftTrimDedent } from "../components/utils";
|
import { leftTrimDedent } from "../components/utils";
|
||||||
|
|
||||||
export interface LexResult {
|
|
||||||
Ok?: Token[]
|
|
||||||
Err?: Err
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Token {
|
export interface Token {
|
||||||
token_type: TokenType
|
token_type: TokenType
|
||||||
value: string
|
value: string
|
||||||
@ -36,7 +31,8 @@ type TokenType =
|
|||||||
"FUN";
|
"FUN";
|
||||||
|
|
||||||
export interface Err {
|
export interface Err {
|
||||||
Lex: LexError
|
Lex?: LexError
|
||||||
|
Syntax?: SyntaxError
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LexError {
|
export interface LexError {
|
||||||
@ -44,23 +40,72 @@ export interface LexError {
|
|||||||
reason: string
|
reason: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SyntaxError {
|
||||||
|
error_start: number
|
||||||
|
error_end: number
|
||||||
|
reason: string
|
||||||
|
}
|
||||||
|
|
||||||
export async function native_highlighter(code: string): Promise<string> {
|
export interface TokenizeResult {
|
||||||
|
Ok?: Token[],
|
||||||
|
TokensOnly?: [Token[], Err],
|
||||||
|
Err?: Err,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
const result = await native_lex(formatted_code);
|
const result = await native_lex(formatted_code);
|
||||||
|
|
||||||
if (result.Err) {
|
if (result.Err) {
|
||||||
throw new Error(JSON.stringify(result.Err.Lex) + "\n" + code);
|
return lex_error_highlighter(formatted_code, result.Err!.Lex!);
|
||||||
|
}
|
||||||
|
else if (result.TokensOnly) {
|
||||||
|
// TODO
|
||||||
|
const [tokens, error] = result.TokensOnly!;
|
||||||
|
return syntax_error_highlighter(formatted_code, tokens, error.Syntax!);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = result.Ok!;
|
const tokens = result.Ok!;
|
||||||
|
|
||||||
const input_chars = formatted_code.split("");
|
const output = highlight_tokens(formatted_code, tokens);
|
||||||
|
|
||||||
|
return [output, "", null];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlights code that has a lexic error
|
||||||
|
*/
|
||||||
|
function lex_error_highlighter(code: string, error: LexError): [string, string, string] {
|
||||||
|
// Create a single error token
|
||||||
|
|
||||||
|
const err_pos = error.position;
|
||||||
|
const before_err = code.substring(0, err_pos);
|
||||||
|
const err_str = code[err_pos];
|
||||||
|
const after_err = code.substring(err_pos + 1);
|
||||||
|
|
||||||
|
const token = `<span class="token underline decoration-wavy decoration-red-500">${err_str}</span>`;
|
||||||
|
|
||||||
|
const all = `${before_err}${token}${after_err}`;
|
||||||
|
|
||||||
|
// TODO: Transform absolute posijion (error.position) into line:column
|
||||||
|
return [all, "Lexical", error.reason + " at position " + error.position]
|
||||||
|
}
|
||||||
|
|
||||||
|
function syntax_error_highlighter(code: string, tokens: Array<Token>, error: SyntaxError): [string, string, string] {
|
||||||
|
const highlighted = highlight_tokens(code, tokens);
|
||||||
|
|
||||||
|
const error_message = `${error.reason} from position ${error.error_start} to ${error.error_end}`;
|
||||||
|
return [highlighted, "Syntax", error_message];
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlight_tokens(input: string, tokens: Array<Token>): string {
|
||||||
|
const input_chars = input.split("");
|
||||||
let output = "";
|
let output = "";
|
||||||
|
|
||||||
let current_pos = 0;
|
let current_pos = 0;
|
||||||
|
|
||||||
for (let i = 0; i < tokens.length; i += 1) {
|
for (let i = 0; i < tokens.length; i += 1) {
|
||||||
const t = tokens[i]!;
|
const t = tokens[i]!;
|
||||||
const token_start = t.position;
|
const token_start = t.position;
|
||||||
@ -85,6 +130,7 @@ export async function native_highlighter(code: string): Promise<string> {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function translate_token_type(tt: TokenType, value: string): string {
|
function translate_token_type(tt: TokenType, value: string): string {
|
||||||
const keywords = ["throws", "extends", "constructor", "case", "static", "const",
|
const keywords = ["throws", "extends", "constructor", "case", "static", "const",
|
||||||
"enum", "union", "loop", "use", "break", "catch", "continue", "as", "do",
|
"enum", "union", "loop", "use", "break", "catch", "continue", "as", "do",
|
||||||
@ -120,7 +166,7 @@ function translate_token_type(tt: TokenType, value: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const native_lex = (code: string) => new Promise<LexResult>((resolve, reject) => {
|
const native_lex = (code: string) => new Promise<TokenizeResult>((resolve, reject) => {
|
||||||
// Get binary path from .env
|
// Get binary path from .env
|
||||||
const binary = import.meta.env.THP_BINARY;
|
const binary = import.meta.env.THP_BINARY;
|
||||||
if (!binary) {
|
if (!binary) {
|
||||||
|
@ -5,3 +5,11 @@ title: Readonly
|
|||||||
import Code from "../../../components/Code.astro"
|
import Code from "../../../components/Code.astro"
|
||||||
|
|
||||||
# Readonly
|
# Readonly
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
class Caño
|
||||||
|
{
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,11 +57,11 @@ fun UserDetail(User user) -> HTML
|
|||||||
{
|
{
|
||||||
<div>
|
<div>
|
||||||
@match user.type
|
@match user.type
|
||||||
case ::Admin
|
@case ::Admin
|
||||||
{
|
{
|
||||||
<button>Delete resource</button>
|
<button>Delete resource</button>
|
||||||
}
|
}
|
||||||
case ::User
|
@case ::User
|
||||||
{
|
{
|
||||||
<button disable>Not allowed</button>
|
<button disable>Not allowed</button>
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ pagesLayout:
|
|||||||
- path: ast
|
- path: ast
|
||||||
- path: expression
|
- path: expression
|
||||||
---
|
---
|
||||||
|
import Code from "../../components/Code.astro"
|
||||||
|
|
||||||
|
|
||||||
# The THP Language Specification
|
# The THP Language Specification
|
||||||
@ -100,11 +101,11 @@ greater than before, it emits a Indent token. If it's lower, emits a Dedent toke
|
|||||||
if it's the same it does nothing.
|
if it's the same it does nothing.
|
||||||
|
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
1 + 2
|
1 + 2
|
||||||
+ 3
|
+ 3
|
||||||
+ 4
|
+ 4
|
||||||
```
|
`} />
|
||||||
|
|
||||||
The previous code would emit the following tokens: `1` `+` `2` `NewLine` `Indent` `+` `3` `NewLine`
|
The previous code would emit the following tokens: `1` `+` `2` `NewLine` `Indent` `+` `3` `NewLine`
|
||||||
`+` `4` `Dedent`
|
`+` `4` `Dedent`
|
||||||
@ -114,12 +115,12 @@ Additionaly, it is a lexical error to have wrong indentation. The lexer stores a
|
|||||||
previous indentation levels in a stack, and reports an error if a decrease in indentation
|
previous indentation levels in a stack, and reports an error if a decrease in indentation
|
||||||
doesn't match a previous level.
|
doesn't match a previous level.
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
if true { // 0 indentation
|
if true { // 0 indentation
|
||||||
print() // 4 indentation
|
// print() // 4 indentation
|
||||||
print() // 2 indentation. Error. There is no 2-indentation level
|
// print() // 2 indentation. Error. There is no 2-indentation level
|
||||||
}
|
}
|
||||||
```
|
`} />
|
||||||
|
|
||||||
All productions of the grammar ignore whitespace/indentation, except those involved in
|
All productions of the grammar ignore whitespace/indentation, except those involved in
|
||||||
semicolon inference.
|
semicolon inference.
|
||||||
@ -134,26 +135,26 @@ Statements in THP end when a new line is encountered:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
// The statement ends | here, on the newline
|
// The statement ends | here, on the newline
|
||||||
val value = (123 + 456) * 0.75
|
val value = (123 + 456) * 0.75
|
||||||
```
|
`} />
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
// Each line contains a different statement. They all end on their new lines
|
// Each line contains a different statement. They all end on their new lines
|
||||||
|
|
||||||
var a = 1 + 2 // a = 3
|
var a = 1 + 2 // a = 3
|
||||||
+ 3 // this is not part of `a`, this is a different statement
|
+ 3 // this is not part of \`a\`, this is a different statement
|
||||||
```
|
`} />
|
||||||
|
|
||||||
This is true even if the line ends with an operator:
|
This is true even if the line ends with an operator:
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
// These are still different statements
|
// These are still different statements
|
||||||
|
|
||||||
var a = 1 + 2 + // This is now a compile error, there is a hanging `+`
|
var a = 1 + 2 + // This is now a compile error, there is a hanging `+`
|
||||||
3 // This is still a different statement
|
3 // This is still a different statement
|
||||||
```
|
`} />
|
||||||
|
|
||||||
|
|
||||||
### Parenthesis
|
### Parenthesis
|
||||||
@ -161,16 +162,16 @@ var a = 1 + 2 + // This is now a compile error, there is a hanging `+`
|
|||||||
Exception 1: When a parenthesis is open, all following whitespace is ignored
|
Exception 1: When a parenthesis is open, all following whitespace is ignored
|
||||||
until the closing parenthesis.
|
until the closing parenthesis.
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
// open parenthesis found, all whitespace is ignored until the closing
|
// open parenthesis found, all whitespace is ignored until the closing
|
||||||
name.contains(
|
name.contains(
|
||||||
"weird"
|
"weird"
|
||||||
)
|
)
|
||||||
```
|
`} />
|
||||||
|
|
||||||
However, for a parenthesis to begin to act, it needs to be open on the same line.
|
However, for a parenthesis to begin to act, it needs to be open on the same line.
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
// Still 2 statements, because the parenthesis is in a new line
|
// Still 2 statements, because the parenthesis is in a new line
|
||||||
print
|
print
|
||||||
(
|
(
|
||||||
@ -181,7 +182,7 @@ print
|
|||||||
print(
|
print(
|
||||||
"hello"
|
"hello"
|
||||||
)
|
)
|
||||||
```
|
`} />
|
||||||
|
|
||||||
### Indented binary operator
|
### Indented binary operator
|
||||||
|
|
||||||
@ -189,22 +190,22 @@ Exception 2:
|
|||||||
|
|
||||||
- When a binary operator is followed by indentation:
|
- When a binary operator is followed by indentation:
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
val sum = 1 + 2 + // The line ends with a binary operator
|
val sum = 1 + 2 + // The line ends with a binary operator
|
||||||
3 // There is indentation
|
3 // There is indentation
|
||||||
```
|
`} />
|
||||||
|
|
||||||
- Or when indentation is followed by a binary operator:
|
- Or when indentation is followed by a binary operator:
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
val sum = 1 + 2
|
val sum = 1 + 2
|
||||||
+ 3 // Indentation and a binary operator
|
+ 3 // Indentation and a binary operator
|
||||||
```
|
`} />
|
||||||
|
|
||||||
In theses cases, all whitespace will be ignored
|
In theses cases, all whitespace will be ignored
|
||||||
until the indentation returns to the initial level.
|
until the indentation returns to the initial level.
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
// This method chain is a single statement because of the indentation
|
// This method chain is a single statement because of the indentation
|
||||||
val person = PersonBuilder()
|
val person = PersonBuilder()
|
||||||
.set_name("john")
|
.set_name("john")
|
||||||
@ -215,6 +216,6 @@ val person = PersonBuilder()
|
|||||||
|
|
||||||
// Here indentation returns, and a new statement begins
|
// Here indentation returns, and a new statement begins
|
||||||
print(person)
|
print(person)
|
||||||
```
|
`} />
|
||||||
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
|||||||
layout: ../../../layouts/SpecLayout.astro
|
layout: ../../../layouts/SpecLayout.astro
|
||||||
title: Comment
|
title: Comment
|
||||||
---
|
---
|
||||||
|
import Code from "../../../components/Code.astro"
|
||||||
|
|
||||||
# Comment
|
# Comment
|
||||||
|
|
||||||
@ -9,8 +10,8 @@ title: Comment
|
|||||||
Comment = "//", any_except_new_line
|
Comment = "//", any_except_new_line
|
||||||
```
|
```
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
// This is a comment
|
// This is a comment
|
||||||
//
|
//
|
||||||
// Another // comment
|
// Another // comment
|
||||||
```
|
`} />
|
@ -2,6 +2,7 @@
|
|||||||
layout: ../../../layouts/SpecLayout.astro
|
layout: ../../../layouts/SpecLayout.astro
|
||||||
title: Identifiers & Datatypes
|
title: Identifiers & Datatypes
|
||||||
---
|
---
|
||||||
|
import Code from "../../../components/Code.astro"
|
||||||
|
|
||||||
# Identifiers & Datatypes
|
# Identifiers & Datatypes
|
||||||
|
|
||||||
@ -18,13 +19,13 @@ Identifier = (underscore | lowercase_letter), identifier_letter*
|
|||||||
identifier_letter = underscore | lowercase_letter | uppercase_letter | decimal_digit
|
identifier_letter = underscore | lowercase_letter | uppercase_letter | decimal_digit
|
||||||
```
|
```
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
identifier
|
identifier
|
||||||
_identifier
|
_identifier
|
||||||
_123
|
_123
|
||||||
_many_letters
|
_many_letters
|
||||||
camelCase
|
camelCase
|
||||||
```
|
`} />
|
||||||
|
|
||||||
|
|
||||||
## Datatype
|
## Datatype
|
||||||
@ -33,20 +34,20 @@ camelCase
|
|||||||
Datatype = uppercase_letter, indentifier_letter*
|
Datatype = uppercase_letter, indentifier_letter*
|
||||||
```
|
```
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
Datatype
|
Datatype
|
||||||
PDO
|
PDO
|
||||||
WEIRD_DATATYPE
|
WEIRD_DATATYPE
|
||||||
```
|
`} />
|
||||||
|
|
||||||
|
|
||||||
## Keywords
|
## Keywords
|
||||||
|
|
||||||
The following are (currently) THP keywords:
|
The following are (currently) THP keywords:
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
val var fun
|
val var fun
|
||||||
```
|
`} />
|
||||||
|
|
||||||
Keywords are scanned first as identifiers, then transformed
|
Keywords are scanned first as identifiers, then transformed
|
||||||
to their respective tokens.
|
to their respective tokens.
|
@ -2,6 +2,7 @@
|
|||||||
layout: ../../../layouts/SpecLayout.astro
|
layout: ../../../layouts/SpecLayout.astro
|
||||||
title: Numbers
|
title: Numbers
|
||||||
---
|
---
|
||||||
|
import Code from "../../../components/Code.astro"
|
||||||
|
|
||||||
# Numbers
|
# Numbers
|
||||||
|
|
||||||
@ -15,12 +16,12 @@ hexadecimal_number = "0", ("x" | "X"), hexadecimal_digit+
|
|||||||
decimal_number = decimal_digit+
|
decimal_number = decimal_digit+
|
||||||
```
|
```
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
12345
|
12345
|
||||||
01234 // This is a decimal number, not an octal number
|
01234 // This is a decimal number, not an octal number
|
||||||
0xff25
|
0xff25
|
||||||
0XFfaA
|
0XFfaA
|
||||||
```
|
`} />
|
||||||
|
|
||||||
`TODO`: Implement octal `0o777` and binary `0b0110`.
|
`TODO`: Implement octal `0o777` and binary `0b0110`.
|
||||||
|
|
||||||
@ -36,14 +37,14 @@ Float = decimal_number, ".", decimal_number+, scientific_notation?
|
|||||||
scientific_notation = "e", ("+" | "-"), decimal_number
|
scientific_notation = "e", ("+" | "-"), decimal_number
|
||||||
```
|
```
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
123.456
|
123.456
|
||||||
123.456e+4
|
123.456e+4
|
||||||
123.456e-2
|
123.456e-2
|
||||||
|
|
||||||
123e+10
|
123e+10
|
||||||
123e-3
|
123e-3
|
||||||
```
|
`} />
|
||||||
|
|
||||||
|
|
||||||
All floating point numbers must start with at least 1 digit.
|
All floating point numbers must start with at least 1 digit.
|
@ -2,6 +2,7 @@
|
|||||||
layout: ../../../layouts/SpecLayout.astro
|
layout: ../../../layouts/SpecLayout.astro
|
||||||
title: Operator
|
title: Operator
|
||||||
---
|
---
|
||||||
|
import Code from "../../../components/Code.astro"
|
||||||
|
|
||||||
# Operator
|
# Operator
|
||||||
|
|
||||||
@ -14,9 +15,9 @@ operator_char = "+" | "-" | "=" | "*" | "!" | "/" | "|"
|
|||||||
| "<" | ">" | "^" | "." | ":"
|
| "<" | ">" | "^" | "." | ":"
|
||||||
```
|
```
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
+ - / * % < > <= >= -> =>
|
+ - / * % < > <= >= -> =>
|
||||||
```
|
`} />
|
||||||
|
|
||||||
These are all the characters that can make an operator.
|
These are all the characters that can make an operator.
|
||||||
|
|
@ -2,6 +2,7 @@
|
|||||||
layout: ../../../layouts/SpecLayout.astro
|
layout: ../../../layouts/SpecLayout.astro
|
||||||
title: String
|
title: String
|
||||||
---
|
---
|
||||||
|
import Code from "../../../components/Code.astro"
|
||||||
|
|
||||||
# String
|
# String
|
||||||
|
|
||||||
@ -19,11 +20,11 @@ escape_seq = "\n"
|
|||||||
string_char = any_unicode_except_newline_and_double_quote
|
string_char = any_unicode_except_newline_and_double_quote
|
||||||
```
|
```
|
||||||
|
|
||||||
```thp
|
<Code thpcode={`
|
||||||
"hello"
|
"hello"
|
||||||
""
|
""
|
||||||
"it's me"
|
"it's me"
|
||||||
"\"Mario\""
|
"\\"Mario\\""
|
||||||
```
|
`} />
|
||||||
|
|
||||||
`TODO`: String interpolation
|
`TODO`: String interpolation
|
Loading…
Reference in New Issue
Block a user