Compare commits
61 Commits
e2ce25aa24
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d6b064efcd | |||
| d2d09c770d | |||
| ff1c392dd6 | |||
| c9fd245bb3 | |||
| 272881e6cf | |||
| 382492a6fc | |||
| 5e4c7350a9 | |||
| 8d06407527 | |||
| 3a80c4e941 | |||
| d03af56e67 | |||
| 37fdff2db5 | |||
| f197c2c3d5 | |||
| dbaf01b6a3 | |||
| 37050e3e3b | |||
| 6d4a456010 | |||
| ea72c91870 | |||
| 76bf509d48 | |||
| bdf72b2bcc | |||
| 92301f3ff6 | |||
| 27c5f7bf56 | |||
| 5418dfa577 | |||
| 60fe691731 | |||
| 634c5a2f93 | |||
| 60b45869fb | |||
| 461bfbbdc5 | |||
| 5e2ebcdc9d | |||
| f5b8986681 | |||
| 24b187c23c | |||
| c55a63f46c | |||
| 791ed2491e | |||
| 3af2115e7d | |||
| 25e88a6362 | |||
| 0da0907a62 | |||
| 940f8d7311 | |||
| 8c69a300a5 | |||
| 39218485c7 | |||
| b9a1ed1bcc | |||
| ebea9f942b | |||
| 4f78450756 | |||
| afb1622b3a | |||
| 9003d49b20 | |||
| 378c28102c | |||
| 559b1b100e | |||
| 5961884219 | |||
| 80d430970a | |||
| e61009b6ef | |||
| 108e69291d | |||
| 78b1dd7667 | |||
| 693054491f | |||
| db5b7bf144 | |||
| 264ea84dfc | |||
| 0b8b7c086e | |||
| 6413d9fd1c | |||
| f62a7dda1c | |||
| 861448cb04 | |||
| 63c35605a2 | |||
| c28ffbbf45 | |||
| ad46f189c0 | |||
| 4f67b1b2dc | |||
| 7960f6e3da | |||
| c570d72b02 |
574
DOCS.md
Normal file
@@ -0,0 +1,574 @@
|
|||||||
|
# CENTVRION Language Reference
|
||||||
|
|
||||||
|
Full syntax documentation for the CENTVRION language. For the formal grammar, see [`language/main.pdf`](language/main.pdf). For installation and a minimal example, see [`README.md`](README.md).
|
||||||
|
|
||||||
|
## Example code
|
||||||
|
### Hello World
|
||||||
|

|
||||||
|
|
||||||
|
### Recursive Fibonacci number function
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Number guessing game
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Variables
|
||||||
|
Variables are set with the `DESIGNA` and `VT` keywords. Type is inferred.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Variable can consist of lower-case letters, numbers, as well as `_`.
|
||||||
|
|
||||||
|
### Compound assignment
|
||||||
|
|
||||||
|
`AVGE` (+=), `MINVE` (-=), `MVLTIPLICA` (*=) and `DIVIDE` (/=) are shorthand for updating a variable with an arithmetic operation:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> VIII
|
||||||
|
```
|
||||||
|
|
||||||
|
`x AVGE III` is equivalent to `DESIGNA x VT x + III`; `MINVE`, `MVLTIPLICA` and `DIVIDE` expand the same way with subtraction, multiplication and division.
|
||||||
|
|
||||||
|
### Destructuring
|
||||||
|
|
||||||
|
Multiple variables can be assigned at once by unpacking an array or multi-return function:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The number of targets must match the length of the array. This also works with array literals:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Data types
|
||||||
|
### NVLLVS
|
||||||
|
`NVLLVS` is a special kind of data type in `CENTVRION`, similar to the `null` value in many other languages. `NVLLVS` can be 0 if evaluated as an int or float, or an empty string if evaluated as a string. `NVLLVS` cannot be evaluated as a boolean.
|
||||||
|
|
||||||
|
### Strings
|
||||||
|
|
||||||
|
Strings are written as text in quotes (`'` or `"`).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Strings are concatenated with `&`:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
`NVLLVS` coerces to an empty string when used with `&`. Note: `+` is for arithmetic only — using it on strings raises an error.
|
||||||
|
|
||||||
|
#### String Interpolation
|
||||||
|
|
||||||
|
Double-quoted strings support interpolation with `{expression}`:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Any expression can appear inside `{}`. Values are coerced to strings the same way as with `&` (integers become Roman numerals, booleans become `VERITAS`/`FALSITAS`, etc.).
|
||||||
|
|
||||||
|
Single-quoted strings do **not** interpolate — `'{nomen}'` is the literal text `{nomen}`. Use `{{` and `}}` for literal braces in double-quoted strings: `"use {{braces}}"` → `use {braces}`.
|
||||||
|
|
||||||
|
#### String Indexing and Slicing
|
||||||
|
|
||||||
|
Strings support the same indexing and slicing syntax as arrays. Indexing is 1-based and returns a single-character string:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> S
|
||||||
|
> L
|
||||||
|
```
|
||||||
|
|
||||||
|
Slicing uses `VSQVE` with inclusive bounds, returning a substring:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> ALV
|
||||||
|
```
|
||||||
|
|
||||||
|
Integer modulo is `RELIQVVM`: `VII RELIQVVM III` evaluates to `I`. Under the `FRACTIO` module it returns a fraction, so `IIIS RELIQVVM IS` is `S` (i.e. 1/2).
|
||||||
|
|
||||||
|
### Integers
|
||||||
|
Integers must be written in roman numerals using the following symbols:
|
||||||
|
|
||||||
|
|Symbol|Value|
|
||||||
|
|------|-----|
|
||||||
|
|`I`|1|
|
||||||
|
|`V`|5|
|
||||||
|
|`X`|10|
|
||||||
|
|`L`|50|
|
||||||
|
|`C`|100|
|
||||||
|
|`D`|500|
|
||||||
|
|`M`|1000|
|
||||||
|
|
||||||
|
Each of the symbols written by themself is equal to the value of the symbol. Different symbols written from largest to smallest are equal to the sum of the symbols. Two to three of the same symbol written consecutively is equal to the sum of those symbols (only true for `I`s, `X`s, `C`s or `M`s ). A single `I` written before a `V` or `X` is equal to 1 less than the value of the second symbol. Similarly, an `X` written before a `L` or `C` is 10 less than the second symbol, and a `C` written before a `D` or `M` is 100 less than the second symbol.
|
||||||
|
|
||||||
|
Because of the restrictions of roman numerals, numbers above 3.999 are impossible to write in the base `CENTVRION` syntax. If numbers of that size are required, see the `MAGNVM` module.
|
||||||
|
|
||||||
|
The number 0 can be expressed with the keyword `NVLLVS`.
|
||||||
|
|
||||||
|
#### Negative numbers
|
||||||
|
Negative numbers can be expressed as `NVLLVS` minus the value. For an explicit definition of negative numbers, see the `SVBNVLLA` module.
|
||||||
|
|
||||||
|
### Floats
|
||||||
|
The base `CENTVRION` syntax does not allow for floats. However, the `FRACTIO` module adds a syntax for fractions.
|
||||||
|
|
||||||
|
### Booleans
|
||||||
|
Booleans are denoted with the keywords `VERITAS` for true and `FALSITAS` for false.
|
||||||
|
|
||||||
|
### Arrays
|
||||||
|
Arrays are defined using square brackets (`[]`) and commas (`,`):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
An array of integers can also be initialized with the `VSQVE` keyword. The range is inclusive on both ends:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> [I, II, III, IV, V, VI, VII, VIII, IX, X]
|
||||||
|
```
|
||||||
|
|
||||||
|
Individual elements can be accessed by index using square brackets. Indexing is 1-based, so `I` refers to the first element:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> I
|
||||||
|
```
|
||||||
|
|
||||||
|
Arrays are concatenated with `@`:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> [I, II, III, IV, V]
|
||||||
|
```
|
||||||
|
|
||||||
|
Both operands must be arrays — using `@` on non-arrays raises an error.
|
||||||
|
|
||||||
|
A sub-array can be extracted with `VSQVE` inside the index brackets. Both bounds are inclusive and 1-based:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> [XX, XXX, XL]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dicts (TABVLA)
|
||||||
|
|
||||||
|
Dicts are key-value maps created with the `TABVLA` keyword and curly braces:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Keys must be strings or integers. Values are accessed and assigned with square brackets:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Iterating over a dict with `PER` loops over its keys:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
`LONGITVDO(dict)` returns the number of entries. `CLAVES(dict)` returns the keys as an array.
|
||||||
|
|
||||||
|
## Conditionals
|
||||||
|
### SI/TVNC
|
||||||
|
If-then statements are denoted with the keywords `SI` (if) and `TVNC` (then). Thus, the code
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Will return `I` (1), as the conditional evaluates `x` to be true.
|
||||||
|
|
||||||
|
### Boolean expressions
|
||||||
|
In conditionals, `EST` functions as an equality evaluation, `DISPAR` as not-equal, and `MINVS` (<), `PLVS` (>), `HAVD_PLVS` (≤), and `HAVD_MINVS` (≥) function as inequality evaluation.
|
||||||
|
|
||||||
|
### ALIVD
|
||||||
|
|
||||||
|
When using `SI`/`TVNC` statements, you can also use `ALIVD` as an "else".
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> I
|
||||||
|
```
|
||||||
|
|
||||||
|
`SI` statements may follow immediately after `ALIVD`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> II
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boolean operators
|
||||||
|
|
||||||
|
The keyword `ET` can be used as a boolean "and". The keyword `AVT` can be used as a boolean "or".
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> II
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loops
|
||||||
|
### DONICVM loops
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> LV
|
||||||
|
```
|
||||||
|
|
||||||
|
An optional `GRADV` clause sets the stride. The step must be a nonzero
|
||||||
|
integer expression; positive values ascend, negative values descend, and
|
||||||
|
the endpoint is included only when the stride lands on it exactly.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> XXV
|
||||||
|
```
|
||||||
|
|
||||||
|
### DVM loops
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> XI
|
||||||
|
```
|
||||||
|
|
||||||
|
### AETERNVM loops
|
||||||
|
|
||||||
|
`AETERNVM FAC { ... }` is shorthand for an infinite loop — equivalent
|
||||||
|
to `DVM FALSITAS FAC { ... }` but without relying on `DVM`'s inverted
|
||||||
|
condition. Exit the loop with `ERVMPE` (or `REDI` from inside a function).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> X
|
||||||
|
```
|
||||||
|
|
||||||
|
### PER loops
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> I
|
||||||
|
> II
|
||||||
|
> III
|
||||||
|
> IV
|
||||||
|
> V
|
||||||
|
```
|
||||||
|
|
||||||
|
Variables can be unpacked in `PER` loops, similar to `DESIGNA` destructuring:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> III
|
||||||
|
> VII
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error handling
|
||||||
|
|
||||||
|
Errors can be caught using `TEMPTA` (temptare = to try) and `CAPE` (capere = to catch). The `CAPE` block binds the error message to a variable as a string.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> Division by zero
|
||||||
|
```
|
||||||
|
|
||||||
|
If the try block succeeds, the catch block is skipped. If an error occurs in the catch block, it propagates up. `TEMPTA`/`CAPE` blocks can be nested.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
Functions are defined with the `DEFINI` and `VT` keywords. The `REDI` keyword is used to return. `REDI` can also be used to end the program, if used outside of a function.
|
||||||
|
|
||||||
|
Calling a function is done with the `INVOCA` keyword.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> CXXI
|
||||||
|
```
|
||||||
|
|
||||||
|
## First-class functions
|
||||||
|
Functions are first-class values in CENTVRION. They can be assigned to variables, passed as arguments, returned from functions, and stored in arrays or dicts.
|
||||||
|
|
||||||
|
Anonymous functions are created with the `FVNCTIO` keyword:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> XIV
|
||||||
|
```
|
||||||
|
|
||||||
|
`INVOCA` accepts any expression as the callee, not just a name:
|
||||||
|
|
||||||
|

|
||||||
|
```
|
||||||
|
> VI
|
||||||
|
> VI
|
||||||
|
> XVI
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: CENTVRION does **not** have closures. When a function is called, it receives a copy of the *caller's* scope, not the scope where it was defined. Variables from a function's definition site are only available if they also exist in the caller's scope at call time.
|
||||||
|
|
||||||
|
## Built-ins
|
||||||
|
### DIC
|
||||||
|
`DIC(value, ...)`
|
||||||
|
|
||||||
|
Prints one or more values to stdout, space-separated, with integers rendered as Roman numerals. Returns the printed string.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### AVDI
|
||||||
|
`AVDI()`
|
||||||
|
|
||||||
|
Reads one line from stdin and returns it as a string.
|
||||||
|
|
||||||
|
### AVDI_NVMERVS
|
||||||
|
`AVDI_NVMERVS()`
|
||||||
|
|
||||||
|
Reads one line from stdin, parses it as a Roman numeral, and returns it as an integer. Raises an error if the input is not a valid numeral.
|
||||||
|
|
||||||
|
### CONTINVA
|
||||||
|
`CONTINVA`
|
||||||
|
|
||||||
|
Skips the rest of the current loop body and continues to the next iteration (`DVM` or `PER`). Has no meaningful return value.
|
||||||
|
|
||||||
|
### ERVMPE
|
||||||
|
`ERVMPE`
|
||||||
|
|
||||||
|
Breaks out of the current loop (`DVM` or `PER`). Has no meaningful return value.
|
||||||
|
|
||||||
|
### LONGITVDO
|
||||||
|
`LONGITVDO(array)`, `LONGITVDO(string)`, or `LONGITVDO(dict)`
|
||||||
|
|
||||||
|
Returns the length of `array` (element count), `string` (character count), or `dict` (entry count) as an integer.
|
||||||
|
|
||||||
|
### CLAVES
|
||||||
|
`CLAVES(dict)`
|
||||||
|
|
||||||
|
Returns the keys of `dict` as an array.
|
||||||
|
|
||||||
|
### ORDINA
|
||||||
|
`ORDINA(array)` or `ORDINA(array, comparator)`
|
||||||
|
|
||||||
|
Sorts an array. Returns a new sorted array; the original is unchanged.
|
||||||
|
|
||||||
|
Without a comparator, sorts in ascending order. All elements must be the same type — integers, fractions, or strings. Integers and fractions sort numerically; strings sort lexicographically.
|
||||||
|
|
||||||
|
With a comparator, the type-uniformity rule is dropped — the comparator decides ordering. The comparator must be a function of exactly two parameters and must return `VERAX`. `comparator(a, b)` returns `VERITAS` iff `a` should come **before** `b`; two elements are treated as equal when both `comparator(a, b)` and `comparator(b, a)` are `FALSITAS`.
|
||||||
|
|
||||||
|

|
||||||
|
```
|
||||||
|
> [V III II I]
|
||||||
|
```
|
||||||
|
|
||||||
|
### MVTA
|
||||||
|
`MVTA(array, fn)`
|
||||||
|
|
||||||
|
Returns a new array obtained by applying `fn` to every element of `array`. The original array is unchanged. `fn` must be a function of exactly one parameter; its return value is unrestricted, so `MVTA` may produce an array of a different element type than its input.
|
||||||
|
|
||||||
|

|
||||||
|
```
|
||||||
|
> [II IV VI VIII]
|
||||||
|
```
|
||||||
|
|
||||||
|
### CRIBRA
|
||||||
|
`CRIBRA(array, predicate)`
|
||||||
|
|
||||||
|
Returns a new array containing the elements of `array` for which `predicate` returns `VERITAS`, in their original order. The original array is unchanged. `predicate` must be a function of exactly one parameter and must return `VERAX`.
|
||||||
|
|
||||||
|

|
||||||
|
```
|
||||||
|
> [I II III]
|
||||||
|
```
|
||||||
|
|
||||||
|
### CONFLA
|
||||||
|
`CONFLA(array, initial, fn)`
|
||||||
|
|
||||||
|
Left fold: starts with `initial` as the accumulator, then for each element `e` of `array` updates the accumulator to `fn(acc, e)`, and returns the final accumulator. The original array is unchanged. `fn` must be a function of exactly two parameters. If `array` is empty, `initial` is returned unchanged.
|
||||||
|
|
||||||
|

|
||||||
|
```
|
||||||
|
> XVI
|
||||||
|
```
|
||||||
|
|
||||||
|
### ADDE
|
||||||
|
`ADDE(array, value)`
|
||||||
|
|
||||||
|
Returns a new array with `value` appended at the end. The original array is unchanged.
|
||||||
|
|
||||||
|
### TOLLE
|
||||||
|
`TOLLE(array, idx)`
|
||||||
|
|
||||||
|
Returns a new array with the element at 1-based position `idx` removed. The index must be an integer in the range `[I, LONGITVDO(array)]`; out-of-range indices raise an error.
|
||||||
|
|
||||||
|
### INSERE
|
||||||
|
`INSERE(array, idx, value)`
|
||||||
|
|
||||||
|
Returns a new array with `value` inserted at 1-based position `idx`, shifting later elements one position to the right. The index must be an integer in the range `[I, LONGITVDO(array) + I]`; passing `LONGITVDO(array) + I` is equivalent to `ADDE`.
|
||||||
|
|
||||||
|
### NECTE
|
||||||
|
`NECTE(array1, array2)`
|
||||||
|
|
||||||
|
Weaves two arrays together into a new array of two-element pair arrays. The two inputs must have equal length; mismatched lengths raise an error.
|
||||||
|
|
||||||
|
### IVNGE
|
||||||
|
`IVNGE(keys, values)`
|
||||||
|
|
||||||
|
Builds a dict by yoking two parallel arrays — the i-th element of `keys` becomes the key for the i-th element of `values`. The two arrays must have equal length. Keys must be strings or integers. If `keys` contains duplicates, the later value wins.
|
||||||
|
|
||||||
|
### SENATVS
|
||||||
|
`SENATVS(bool, ...)` or `SENATVS([bool])`
|
||||||
|
|
||||||
|
Returns VERITAS if a strict majority of the arguments are VERITAS, FALSITAS otherwise. Also accepts a single array of booleans. All values must be booleans. Ties return FALSITAS.
|
||||||
|
|
||||||
|
### NVMERVS
|
||||||
|
`NVMERVS(string)`
|
||||||
|
|
||||||
|
Parses a Roman numeral string and returns its integer value. The argument must be a string containing a valid Roman numeral. Respects the `MAGNVM` and `SVBNVLLA` modules for large and negative numbers respectively.
|
||||||
|
|
||||||
|
### TYPVS
|
||||||
|
`TYPVS(value)`
|
||||||
|
|
||||||
|
Returns the type of `value` as a string: `NVMERVS` (integer), `LITTERA` (string), `VERAX` (boolean), `CATALOGVS` (list), `FRACTIO` (fraction), `TABVLA` (dict), `FVNCTIO` (function), or `NVLLVS` (null).
|
||||||
|
|
||||||
|
### LITTERA
|
||||||
|
`LITTERA(value)`
|
||||||
|
|
||||||
|
Returns `value` formatted as the same display string `DIC` would print. Integers become Roman numerals (zero becomes `NVLLVS`), fractions use the `S`/`:`/`.`/`|` notation, booleans become `VERITAS`/`FALSITAS`, arrays are space-separated in brackets, and dicts use the `{ key VT value, ... }` form. Strings pass through unchanged. Respects `MAGNVM` and `SVBNVLLA` for large and negative numbers. Inverse of `NVMERVS` for integers: `NVMERVS(LITTERA(n)) == n`.
|
||||||
|
|
||||||
|
### DORMI
|
||||||
|
`DORMI(n)`
|
||||||
|
|
||||||
|
Sleeps for `n` seconds, where `n` is an integer, fraction, or NVLLVS (treated as 0). Returns nothing meaningful.
|
||||||
|
|
||||||
|
### QVAERE
|
||||||
|
`QVAERE(pattern, string)`
|
||||||
|
|
||||||
|
Returns an array of all non-overlapping matches of the regex `pattern` in `string`. Both arguments must be strings. Patterns use extended regular expression syntax with Roman numeral quantifiers (`{III}` for exactly 3, `{II,V}` for 2–5, `{III,}` for 3 or more). Returns an empty array if there are no matches. Raises an error if the pattern is invalid.
|
||||||
|
|
||||||
|
### SVBSTITVE
|
||||||
|
`SVBSTITVE(pattern, replacement, string)`
|
||||||
|
|
||||||
|
Replaces all non-overlapping matches of the regex `pattern` in `string` with `replacement`. All three arguments must be strings. The replacement string supports backreferences (`\I`, `\II`, etc.) to captured groups. Returns the resulting string. Raises an error if the pattern is invalid.
|
||||||
|
|
||||||
|
### SCINDE
|
||||||
|
`SCINDE(string, delimiter)`
|
||||||
|
|
||||||
|
Splits `string` by `delimiter` and returns an array of substrings. Both arguments must be strings. If the delimiter is not found, returns a single-element array containing the original string. If the delimiter is an empty string, splits into individual characters.
|
||||||
|
|
||||||
|
### MAIVSCVLA
|
||||||
|
`MAIVSCVLA(string)`
|
||||||
|
|
||||||
|
Returns a new string with every ASCII letter `a`–`z` replaced by its uppercase counterpart `A`–`Z`. All other bytes (digits, punctuation, non-ASCII) pass through unchanged.
|
||||||
|
|
||||||
|
### MINVSCVLA
|
||||||
|
`MINVSCVLA(string)`
|
||||||
|
|
||||||
|
Returns a new string with every ASCII letter `A`–`Z` replaced by its lowercase counterpart `a`–`z`. All other bytes (digits, punctuation, non-ASCII) pass through unchanged.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
Modules are additions to the base `CENTVRION` syntax. They add or change certain features. Modules are included in your code by having
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
In the beginning of your source file.
|
||||||
|
|
||||||
|
Vnlike many other programming languages with modules, the modules in `CENTVRION` are not libraries that can be "imported" from other scripts written in the language. They are features of the compiler, disabled by default.
|
||||||
|
|
||||||
|
### FORS
|
||||||
|

|
||||||
|
|
||||||
|
The `FORS` module allows you to add randomness to your `CENTVRION` program. It adds 4 new built-in functions: `FORTVITVS_NVMERVS(int, int)`, `FORTVITA_ELECTIO(['a])`, `DECIMATIO(['a])`, and `SEMEN(int)`.
|
||||||
|
|
||||||
|
`FORTVITVS_NVMERVS(int, int)` picks a random int in the (inclusive) range of the two given ints.
|
||||||
|
|
||||||
|
`FORTVITA_ELECTIO(['a])` picks a random element from the given array. `FORTVITA_ELECTIO(array)` is identical to ```array[FORTVITVS_NVMERVS(I, LONGITVDO(array))]```.
|
||||||
|
|
||||||
|
`DECIMATIO(['a])` returns a copy of the given array with a random tenth of its elements removed. Arrays with fewer than 10 elements are returned unchanged.
|
||||||
|
|
||||||
|
`SEMEN(int)` seeds the random number generator for reproducibility.
|
||||||
|
|
||||||
|
### FRACTIO
|
||||||
|

|
||||||
|
|
||||||
|
The `FRACTIO` module adds floats, in the form of base 12 fractions.
|
||||||
|
|
||||||
|
In the `FRACTIO` module, `.` represents 1/12, `:` represents 1/6 and `S` represents 1/2. The symbols must be written from highest to lowest. So 3/4 would be written as "`S:.`".
|
||||||
|
|
||||||
|
Fractions can be written as an extension of integers. So 3.5 would be "`IIIS`".
|
||||||
|
|
||||||
|
The symbol `|` can be used to denote that the following fraction symbols are 1 "level down" in base 12. So after the first `|`, the fraction symbols denote 144ths instead of 12ths. So 7 and 100/144 would be "`VIIS:|::`", as "7 + 100/144" is also "7+8/12+4/144".
|
||||||
|
|
||||||
|
A single "set" of fraction symbols can only represent up to 11/12, as 12/12 can be written as 1.
|
||||||
|
|
||||||
|
### IASON
|
||||||
|
|
||||||
|
> ⚠ **Warning.** The `IASON` module enables your program to read and write non-Roman numerals. Numbers handled by `IASON_LEGE` and `IASON_SCRIBE` use the decimal digits `0`–`9` (e.g. `42`, `1789`, `30`), not Roman numerals. This goes against the design philosophy of CENTVRION and should not be used unless absolutely necessary.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The `IASON` module adds two builtins for converting between `CENTVRION` values and JSON strings.
|
||||||
|
|
||||||
|
`IASON_LEGE(string)` parses a JSON string and returns the corresponding `CENTVRION` value. Mappings: JSON `null` → `NVLLVS`, `true`/`false` → `VERITAS`/`FALSITAS`, integer → numeral, string → string, array → array, object → `TABVLA` (string keys).
|
||||||
|
|
||||||
|
JSON floats with no fractional part (e.g. `3.0`) come back as integers. Other floats depend on whether the `FRACTIO` module is also loaded: with `FRACTIO`, `0.1` parses to the exact fraction `I:|::|::|S:.|S.|:` (1/10); without it, the value is floored to the nearest integer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> Marcus
|
||||||
|
> XXX
|
||||||
|
> [gladius scutum]
|
||||||
|
```
|
||||||
|
|
||||||
|
`IASON_SCRIBE(value)` serializes a `CENTVRION` value to a JSON string. Integers and fractions become JSON numbers (fractions via shortest-round-trip float), strings become JSON strings (with the standard escapes), arrays become arrays, dicts become objects (insertion order preserved). Functions and dicts with non-string keys raise an error.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> {"nomen": "Marcus", "anni": 30}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MAGNVM
|
||||||
|

|
||||||
|
|
||||||
|
`MAGNVM` adds the ability to write integers larger than `MMMCMXCIX` (3.999) in your code, by adding the thousands operator, "`_`".
|
||||||
|
|
||||||
|
When `_` is added _after_ a numeric symbol, the symbol becomes 1.000 times larger. The operator can be added to the same symbol multiple times. So "`V_`" is 5.000, and "`V__`" is 5.000.000. The strict rules for integers still apply, so 4.999 cannot be written as "`IV_`", but must instead be written as "`MV_CMXCIX`".
|
||||||
|
|
||||||
|
All integer symbols except `I` may be given a `_`.
|
||||||
|
|
||||||
|
### SCRIPTA
|
||||||
|

|
||||||
|
|
||||||
|
The `SCRIPTA` module adds file I/O to your `CENTVRION` program. It adds 3 new built-in functions: `LEGE`, `SCRIBE`, and `ADIVNGE`.
|
||||||
|
|
||||||
|
`LEGE(string)` reads the contents of the file at the given path and returns them as a string.
|
||||||
|
|
||||||
|
`SCRIBE(string, string)` writes the second argument to the file at the path given by the first argument, overwriting any existing content.
|
||||||
|
|
||||||
|
`ADIVNGE(string, string)` appends the second argument to the file at the path given by the first argument.
|
||||||
|
|
||||||
|
### RETE
|
||||||
|

|
||||||
|
|
||||||
|
The `RETE` module adds networking to your `CENTVRION` program.
|
||||||
|
|
||||||
|
`PETE(string)` performs an HTTP GET request to the given URL and returns the response body as a string.
|
||||||
|
|
||||||
|
`PETITVR(string, function)` registers a GET handler for the given path. The handler function receives a single argument: a dictionary with keys `"via"` (the request path), `"quaestio"` (query string), and `"methodus"` (HTTP method). The handler's return value becomes the response body (200 OK). Unmatched paths return a 404.
|
||||||
|
|
||||||
|
`AVSCVLTA(integer)` starts an HTTP server on the given port. This call blocks indefinitely, serving registered routes. Routes must be registered with `PETITVR` before calling `AVSCVLTA`. Ports above 3999 require the `MAGNVM` module.
|
||||||
|
|
||||||
|
### SVBNVLLA
|
||||||
|

|
||||||
|
|
||||||
|
The `SVBNVLLA` module adds the ability to write negative numbers as `-II` instead of `NVLLVS-II`.
|
||||||
240
README.md
@@ -1,241 +1,21 @@
|
|||||||
# About
|
# About
|
||||||
`CENTVRION` is the programming language for the modern roman.
|
`CENTVRION` is the programming language for the modern roman.
|
||||||
|
|
||||||
# Documentation
|
## Hello World
|
||||||
|
|
||||||
## Example code
|
|
||||||
### Hello World
|
|
||||||

|

|
||||||
|
|
||||||
### Recursive Fibonacci number function
|
## Running
|
||||||
|
|
||||||

|
```bash
|
||||||
|
./cent -i FILE.cent # interpret a .cent file
|
||||||
### Number guessing game
|
./cent -c FILE.cent # compile (not yet implemented)
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Variables
|
|
||||||
Variables are set with the `DESIGNA` and `VT` keywords. Type is inferred.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Variable can consist of lower-case letters, numbers, as well as `_`.
|
|
||||||
|
|
||||||
## Data types
|
|
||||||
### NVLLVS
|
|
||||||
`NVLLVS` is a special kind of data type in `CENTVRION`, similar to the `null` value in many other languages. `NVLLVS` can be 0 if evaluated as an int or float, or an empty string if evaluated as a string. `NVLLVS` cannot be evaluated as a boolean.
|
|
||||||
|
|
||||||
### Strings
|
|
||||||
|
|
||||||
Strings are written as text in quotes (`'` or `"`).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Strings are concatenated with `&`:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
`NVLLVS` coerces to an empty string when used with `&`. Note: `+` is for arithmetic only — using it on strings raises an error.
|
|
||||||
|
|
||||||
### Integers
|
|
||||||
Integers must be written in roman numerals using the following symbols:
|
|
||||||
|
|
||||||
|Symbol|Value|
|
|
||||||
|------|-----|
|
|
||||||
|`I`|1|
|
|
||||||
|`V`|5|
|
|
||||||
|`X`|10|
|
|
||||||
|`L`|50|
|
|
||||||
|`C`|100|
|
|
||||||
|`D`|500|
|
|
||||||
|`M`|1000|
|
|
||||||
|
|
||||||
Each of the symbols written by themself is equal to the value of the symbol. Different symbols written from largest to smallest are equal to the sum of the symbols. Two to three of the same symbol written consecutively is equal to the sum of those symbols (only true for `I`s, `X`s, `C`s or `M`s ). A single `I` written before a `V` or `X` is equal to 1 less than the value of the second symbol. Similarly, an `X` written before a `L` or `C` is 10 less than the second symbol, and a `C` written before a `D` or `M` is 100 less than the second symbol.
|
|
||||||
|
|
||||||
Because of the restrictions of roman numerals, numbers above 3.999 are impossible to write in the base `CENTVRION` syntax. If numbers of that size are required, see the `MAGNVM` module.
|
|
||||||
|
|
||||||
The number 0 can be expressed with the keyword `NVLLVS`.
|
|
||||||
|
|
||||||
#### Negative numbers
|
|
||||||
Negative numbers can be expressed as `NVLLVS` minus the value. For an explicit definition of negative numbers, see the `SVBNVLLA` module.
|
|
||||||
|
|
||||||
### Floats
|
|
||||||
The base `CENTVRION` syntax does not allow for floats. However, the `FRACTIO` module adds a syntax for fractions.
|
|
||||||
|
|
||||||
### Booleans
|
|
||||||
Booleans are denoted with the keywords `VERITAS` for true and `FALSITAS` for false.
|
|
||||||
|
|
||||||
### Arrays
|
|
||||||
Arrays are defined using square brackets (`[]`) and commas (`,`):
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
An array of integers can also be initialized with the `VSQVE` keyword:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Individual elements can be accessed by index using square brackets. Indexing is 1-based, so `I` refers to the first element:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```
|
|
||||||
> I
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Conditionals
|
Dependencies: `rply`, `docopt`. Install via `pip install rply docopt`.
|
||||||
### SI/TVNC
|
|
||||||
If-then statements are denoted with the keywords `SI` (if) and `TVNC` (then). Thus, the code
|
|
||||||
|
|
||||||

|
## Documentation
|
||||||
|
|
||||||
Will return `I` (1), as the conditional evaluates `x` to be true.
|
- [`DOCS.md`](DOCS.md) — full language reference: syntax, data types, built-ins, modules.
|
||||||
|
- [`language/main.pdf`](language/main.pdf) — formal grammar.
|
||||||
### Boolean expressions
|
- [`examples/`](examples/) — example programs.
|
||||||
In conditionals, `EST` functions as an equality evaluation, and `MINVS` (<) and `PLVS` (>) function as inequality evaluation.
|
|
||||||
|
|
||||||
### ALVID
|
|
||||||
|
|
||||||
When using `SI`/`TVNC` statements, you can also use `ALVID` as an "else".
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```
|
|
||||||
> I
|
|
||||||
```
|
|
||||||
|
|
||||||
`SI` statements may follow immediately after `ALVID`.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```
|
|
||||||
> II
|
|
||||||
```
|
|
||||||
|
|
||||||
### Boolean operators
|
|
||||||
|
|
||||||
The keyword `ET` can be used as a boolean "and". The keyword `AVT` can be used as a boolean "or".
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```
|
|
||||||
> II
|
|
||||||
```
|
|
||||||
|
|
||||||
## Loops
|
|
||||||
### DONICVM loops
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```
|
|
||||||
> XLV
|
|
||||||
```
|
|
||||||
|
|
||||||
### DVM loops
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```
|
|
||||||
> XI
|
|
||||||
```
|
|
||||||
|
|
||||||
### PER loops
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```
|
|
||||||
> I
|
|
||||||
> II
|
|
||||||
> III
|
|
||||||
> IV
|
|
||||||
> V
|
|
||||||
```
|
|
||||||
|
|
||||||
## Functions
|
|
||||||
Functions are defined with the `DEFINI` and `VT` keywords. The `REDI` keyword is used to return. `REDI` can also be used to end the program, if used outside of a function.
|
|
||||||
|
|
||||||
Calling a function is done with the `INVOCA` keyword.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```
|
|
||||||
> CXXI
|
|
||||||
```
|
|
||||||
|
|
||||||
## Built-ins
|
|
||||||
### DICE
|
|
||||||
`DICE value ...`
|
|
||||||
|
|
||||||
Prints one or more values to stdout, space-separated, with integers rendered as Roman numerals. Returns the printed string.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### AVDI
|
|
||||||
`AVDI()`
|
|
||||||
|
|
||||||
Reads one line from stdin and returns it as a string.
|
|
||||||
|
|
||||||
### AVDI_NVMERVS
|
|
||||||
`AVDI_NVMERVS()`
|
|
||||||
|
|
||||||
Reads one line from stdin, parses it as a Roman numeral, and returns it as an integer. Raises an error if the input is not a valid numeral.
|
|
||||||
|
|
||||||
### CONTINVA
|
|
||||||
`CONTINVA`
|
|
||||||
|
|
||||||
Skips the rest of the current loop body and continues to the next iteration (`DVM` or `PER`). Has no meaningful return value.
|
|
||||||
|
|
||||||
### ERVMPE
|
|
||||||
`ERVMPE`
|
|
||||||
|
|
||||||
Breaks out of the current loop (`DVM` or `PER`). Has no meaningful return value.
|
|
||||||
|
|
||||||
### LONGITVDO
|
|
||||||
`LONGITVDO array` or `LONGITVDO string`
|
|
||||||
|
|
||||||
Returns the length of `array` (element count) or `string` (character count) as an integer.
|
|
||||||
|
|
||||||
## Modules
|
|
||||||
Modules are additions to the base `CENTVRION` syntax. They add or change certain features. Modules are included in your code by having
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
In the beginning of your source file.
|
|
||||||
|
|
||||||
Vnlike many other programming languages with modules, the modules in `CENTVRION` are not libraries that can be "imported" from other scripts written in the language. They are features of the compiler, disabled by default.
|
|
||||||
|
|
||||||
### FORS
|
|
||||||

|
|
||||||
|
|
||||||
The `FORS` module allows you to add randomness to your `CENTVRION` program. It adds 2 new built-in functions: `FORTIS_NVMERVS int int` and `FORTIS_ELECTIONIS ['a]`.
|
|
||||||
|
|
||||||
`FORTIS_NVMERVS int int` picks a random int in the (inclusive) range of the two given ints.
|
|
||||||
|
|
||||||
`FORTIS_ELECTIONIS ['a]` picks a random element from the given array. `FORTIS_ELECTIONIS array` is identical to ```array[FORTIS_NVMERVS NVLLVS ((LONGITVDO array)-I)]```.
|
|
||||||
|
|
||||||
### FRACTIO
|
|
||||||

|
|
||||||
|
|
||||||
The `FRACTIO` module adds floats, in the form of base 12 fractions.
|
|
||||||
|
|
||||||
In the `FRACTIO` module, `.` represents 1/12, `:` represents 1/6 and `S` represents 1/2. The symbols must be written from highest to lowest. So 3/4 would be written as "`S:.`".
|
|
||||||
|
|
||||||
Fractions can be written as an extension of integers. So 3.5 would be "`IIIS`".
|
|
||||||
|
|
||||||
The symbol `|` can be used to denote that the following fraction symbols are 1 "level down" in base 12. So after the first `|`, the fraction symbols denote 144ths instead of 12ths. So 7 and 100/144 would be "`VIIS:|::`", as "7 + 100/144" is also "7+8/12+4/144".
|
|
||||||
|
|
||||||
A single "set" of fraction symbols can only represent up to 11/12, as 12/12 can be written as 1.
|
|
||||||
|
|
||||||
### MAGNVM
|
|
||||||

|
|
||||||
|
|
||||||
`MAGNVM` adds the ability to write integers larger than `MMMCMXCIX` (3.999) in your code, by adding the thousands operator, "`_`".
|
|
||||||
|
|
||||||
When `_` is added _after_ a numeric symbol, the symbol becomes 1.000 times larger. The operator can be added to the same symbol multiple times. So "`V_`" is 5.000, and "`V__`" is 5.000.000. The strict rules for integers still apply, so 4.999 cannot be written as "`IV_`", but must instead be written as "`MV_CMXCIX`".
|
|
||||||
|
|
||||||
All integer symbols except `I` may be given a `_`.
|
|
||||||
|
|
||||||
### SVBNVLLA
|
|
||||||

|
|
||||||
|
|
||||||
The `SVBNVLLA` module adds the ability to write negative numbers as `-II` instead of `NVLLVS-II`.
|
|
||||||
|
|||||||
12
cent
@@ -42,6 +42,8 @@ def main():
|
|||||||
pos = e.source_pos
|
pos = e.source_pos
|
||||||
char = program_text[pos.idx] if pos.idx < len(program_text) else "?"
|
char = program_text[pos.idx] if pos.idx < len(program_text) else "?"
|
||||||
sys.exit(f"CENTVRION error: Invalid character {char!r} at line {pos.lineno}, column {pos.colno}")
|
sys.exit(f"CENTVRION error: Invalid character {char!r} at line {pos.lineno}, column {pos.colno}")
|
||||||
|
except SyntaxError as e:
|
||||||
|
sys.exit(f"CENTVRION error: {e}")
|
||||||
|
|
||||||
if isinstance(program, Program):
|
if isinstance(program, Program):
|
||||||
if args["-i"]:
|
if args["-i"]:
|
||||||
@@ -51,17 +53,19 @@ def main():
|
|||||||
sys.exit(f"CENTVRION error: {e}")
|
sys.exit(f"CENTVRION error: {e}")
|
||||||
else:
|
else:
|
||||||
c_source = compile_program(program)
|
c_source = compile_program(program)
|
||||||
runtime_c = os.path.join(
|
runtime_dir = os.path.join(
|
||||||
os.path.dirname(__file__),
|
os.path.dirname(__file__),
|
||||||
"centvrion", "compiler", "runtime", "cent_runtime.c"
|
"centvrion", "compiler", "runtime"
|
||||||
)
|
)
|
||||||
|
runtime_c = os.path.join(runtime_dir, "cent_runtime.c")
|
||||||
|
iason_c = os.path.join(runtime_dir, "cent_iason.c")
|
||||||
out_path = os.path.splitext(file_path)[0]
|
out_path = os.path.splitext(file_path)[0]
|
||||||
if args["--keep-c"]:
|
if args["--keep-c"]:
|
||||||
tmp_path = out_path + ".c"
|
tmp_path = out_path + ".c"
|
||||||
with open(tmp_path, "w") as f:
|
with open(tmp_path, "w") as f:
|
||||||
f.write(c_source)
|
f.write(c_source)
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
["gcc", "-O2", tmp_path, runtime_c, "-o", out_path],
|
["gcc", "-O2", tmp_path, runtime_c, iason_c, "-o", out_path, "-lcurl", "-lmicrohttpd", "-lm"],
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -70,7 +74,7 @@ def main():
|
|||||||
tmp_path = tmp.name
|
tmp_path = tmp.name
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
["gcc", "-O2", tmp_path, runtime_c, "-o", out_path],
|
["gcc", "-O2", tmp_path, runtime_c, iason_c, "-o", out_path, "-lcurl", "-lmicrohttpd", "-lm"],
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ class EmitContext:
|
|||||||
self.functions = {}
|
self.functions = {}
|
||||||
# source-level name / alias → c_func_name; populated by emitter pre-pass
|
# source-level name / alias → c_func_name; populated by emitter pre-pass
|
||||||
self.func_resolve = {}
|
self.func_resolve = {}
|
||||||
|
# id(Fvnctio_node) → c_func_name; populated by lambda lifting pass
|
||||||
|
self.lambda_names = {}
|
||||||
|
# [(c_name, Fvnctio_node), ...]; populated by lambda lifting pass
|
||||||
|
self.lambdas = []
|
||||||
|
|
||||||
def fresh_tmp(self):
|
def fresh_tmp(self):
|
||||||
name = f"_t{self._tmp_counter}"
|
name = f"_t{self._tmp_counter}"
|
||||||
|
|||||||
@@ -1,21 +1,34 @@
|
|||||||
from centvrion.errors import CentvrionError
|
from centvrion.errors import CentvrionError
|
||||||
from centvrion.ast_nodes import (
|
from centvrion.ast_nodes import (
|
||||||
String, Numeral, Fractio, Bool, Nullus, ID,
|
String, InterpolatedString, Numeral, Fractio, Bool, Nullus, ID,
|
||||||
BinOp, UnaryMinus, UnaryNot,
|
BinOp, UnaryMinus, UnaryNot,
|
||||||
ArrayIndex, DataArray, DataRangeArray,
|
ArrayIndex, ArraySlice, DataArray, DataRangeArray, DataDict,
|
||||||
BuiltIn, Invoca,
|
BuiltIn, Invoca, Fvnctio,
|
||||||
num_to_int, frac_to_fraction,
|
num_to_int, frac_to_fraction,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _err(node, msg):
|
||||||
|
"""Build a CentvrionError stamped with a node's source position, if any."""
|
||||||
|
pos = getattr(node, "pos", None)
|
||||||
|
if pos is not None:
|
||||||
|
return CentvrionError(msg, pos[0], pos[1])
|
||||||
|
return CentvrionError(msg)
|
||||||
|
|
||||||
|
|
||||||
_BINOP_FN = {
|
_BINOP_FN = {
|
||||||
"SYMBOL_PLUS": "cent_add",
|
"SYMBOL_PLUS": "cent_add",
|
||||||
"SYMBOL_MINUS": "cent_sub",
|
"SYMBOL_MINUS": "cent_sub",
|
||||||
"SYMBOL_TIMES": "cent_mul",
|
"SYMBOL_TIMES": "cent_mul",
|
||||||
"SYMBOL_DIVIDE": "cent_div",
|
"SYMBOL_DIVIDE": "cent_div",
|
||||||
"SYMBOL_AMPERSAND": "cent_concat",
|
"SYMBOL_AMPERSAND": "cent_concat",
|
||||||
|
"SYMBOL_AT": "cent_array_concat",
|
||||||
|
"KEYWORD_RELIQVVM": "cent_mod",
|
||||||
"KEYWORD_EST": "cent_eq",
|
"KEYWORD_EST": "cent_eq",
|
||||||
|
"KEYWORD_DISPAR": "cent_neq",
|
||||||
"KEYWORD_MINVS": "cent_lt",
|
"KEYWORD_MINVS": "cent_lt",
|
||||||
"KEYWORD_PLVS": "cent_gt",
|
"KEYWORD_PLVS": "cent_gt",
|
||||||
|
"KEYWORD_HAVD_PLVS": "cent_lte",
|
||||||
|
"KEYWORD_HAVD_MINVS": "cent_gte",
|
||||||
"KEYWORD_ET": "cent_and",
|
"KEYWORD_ET": "cent_and",
|
||||||
"KEYWORD_AVT": "cent_or",
|
"KEYWORD_AVT": "cent_or",
|
||||||
}
|
}
|
||||||
@@ -49,6 +62,25 @@ def emit_expr(node, ctx):
|
|||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
return [f'CentValue {tmp} = cent_str("{_escape(node.value)}");'], tmp
|
return [f'CentValue {tmp} = cent_str("{_escape(node.value)}");'], tmp
|
||||||
|
|
||||||
|
if isinstance(node, InterpolatedString):
|
||||||
|
if len(node.parts) == 0:
|
||||||
|
tmp = ctx.fresh_tmp()
|
||||||
|
return [f'CentValue {tmp} = cent_str("");'], tmp
|
||||||
|
if len(node.parts) == 1:
|
||||||
|
return emit_expr(node.parts[0], ctx)
|
||||||
|
l_lines, l_var = emit_expr(node.parts[0], ctx)
|
||||||
|
r_lines, r_var = emit_expr(node.parts[1], ctx)
|
||||||
|
lines = l_lines + r_lines
|
||||||
|
acc = ctx.fresh_tmp()
|
||||||
|
lines.append(f"CentValue {acc} = cent_concat({l_var}, {r_var});")
|
||||||
|
for part in node.parts[2:]:
|
||||||
|
p_lines, p_var = emit_expr(part, ctx)
|
||||||
|
lines.extend(p_lines)
|
||||||
|
new_acc = ctx.fresh_tmp()
|
||||||
|
lines.append(f"CentValue {new_acc} = cent_concat({acc}, {p_var});")
|
||||||
|
acc = new_acc
|
||||||
|
return lines, acc
|
||||||
|
|
||||||
if isinstance(node, Bool):
|
if isinstance(node, Bool):
|
||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
v = "1" if node.value else "0"
|
v = "1" if node.value else "0"
|
||||||
@@ -60,7 +92,7 @@ def emit_expr(node, ctx):
|
|||||||
|
|
||||||
if isinstance(node, Fractio):
|
if isinstance(node, Fractio):
|
||||||
if not ctx.has_module("FRACTIO"):
|
if not ctx.has_module("FRACTIO"):
|
||||||
raise CentvrionError("Cannot use fraction literals without 'FRACTIO' module")
|
raise _err(node, "Cannot use fraction literals without 'FRACTIO' module")
|
||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
magnvm = "MAGNVM" in ctx.modules
|
magnvm = "MAGNVM" in ctx.modules
|
||||||
svbnvlla = "SVBNVLLA" in ctx.modules
|
svbnvlla = "SVBNVLLA" in ctx.modules
|
||||||
@@ -72,11 +104,32 @@ def emit_expr(node, ctx):
|
|||||||
return [f'CentValue {tmp} = cent_scope_get(&_scope, "{node.name}");'], tmp
|
return [f'CentValue {tmp} = cent_scope_get(&_scope, "{node.name}");'], tmp
|
||||||
|
|
||||||
if isinstance(node, BinOp):
|
if isinstance(node, BinOp):
|
||||||
|
# Short-circuit for logical operators
|
||||||
|
if node.op in ("KEYWORD_AVT", "KEYWORD_ET"):
|
||||||
|
l_lines, l_var = emit_expr(node.left, ctx)
|
||||||
|
r_lines, r_var = emit_expr(node.right, ctx)
|
||||||
|
tmp = ctx.fresh_tmp()
|
||||||
|
if node.op == "KEYWORD_AVT":
|
||||||
|
lines = l_lines + [f"CentValue {tmp};"]
|
||||||
|
lines += [f"if (cent_truthy({l_var})) {{ {tmp} = cent_bool(1); }} else {{"]
|
||||||
|
lines += [f" {l}" for l in r_lines]
|
||||||
|
lines += [f" {tmp} = cent_bool(cent_truthy({r_var}));"]
|
||||||
|
lines += ["}"]
|
||||||
|
else:
|
||||||
|
lines = l_lines + [f"CentValue {tmp};"]
|
||||||
|
lines += [f"if (!cent_truthy({l_var})) {{ {tmp} = cent_bool(0); }} else {{"]
|
||||||
|
lines += [f" {l}" for l in r_lines]
|
||||||
|
lines += [f" {tmp} = cent_bool(cent_truthy({r_var}));"]
|
||||||
|
lines += ["}"]
|
||||||
|
return lines, tmp
|
||||||
|
|
||||||
l_lines, l_var = emit_expr(node.left, ctx)
|
l_lines, l_var = emit_expr(node.left, ctx)
|
||||||
r_lines, r_var = emit_expr(node.right, ctx)
|
r_lines, r_var = emit_expr(node.right, ctx)
|
||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
if node.op == "SYMBOL_DIVIDE" and ctx.has_module("FRACTIO"):
|
if node.op == "SYMBOL_DIVIDE" and ctx.has_module("FRACTIO"):
|
||||||
fn = "cent_div_frac"
|
fn = "cent_div_frac"
|
||||||
|
elif node.op == "KEYWORD_RELIQVVM" and ctx.has_module("FRACTIO"):
|
||||||
|
fn = "cent_mod_frac"
|
||||||
else:
|
else:
|
||||||
fn = _BINOP_FN[node.op]
|
fn = _BINOP_FN[node.op]
|
||||||
return l_lines + r_lines + [f"CentValue {tmp} = {fn}({l_var}, {r_var});"], tmp
|
return l_lines + r_lines + [f"CentValue {tmp} = {fn}({l_var}, {r_var});"], tmp
|
||||||
@@ -84,7 +137,7 @@ def emit_expr(node, ctx):
|
|||||||
if isinstance(node, UnaryMinus):
|
if isinstance(node, UnaryMinus):
|
||||||
inner_lines, inner_var = emit_expr(node.expr, ctx)
|
inner_lines, inner_var = emit_expr(node.expr, ctx)
|
||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
return inner_lines + [f"CentValue {tmp} = cent_int(-{inner_var}.ival);"], tmp
|
return inner_lines + [f"CentValue {tmp} = cent_neg({inner_var});"], tmp
|
||||||
|
|
||||||
if isinstance(node, UnaryNot):
|
if isinstance(node, UnaryNot):
|
||||||
inner_lines, inner_var = emit_expr(node.expr, ctx)
|
inner_lines, inner_var = emit_expr(node.expr, ctx)
|
||||||
@@ -97,6 +150,15 @@ def emit_expr(node, ctx):
|
|||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
return arr_lines + idx_lines + [f"CentValue {tmp} = cent_list_index({arr_var}, {idx_var});"], tmp
|
return arr_lines + idx_lines + [f"CentValue {tmp} = cent_list_index({arr_var}, {idx_var});"], tmp
|
||||||
|
|
||||||
|
if isinstance(node, ArraySlice):
|
||||||
|
arr_lines, arr_var = emit_expr(node.array, ctx)
|
||||||
|
lo_lines, lo_var = emit_expr(node.from_index, ctx)
|
||||||
|
hi_lines, hi_var = emit_expr(node.to_index, ctx)
|
||||||
|
tmp = ctx.fresh_tmp()
|
||||||
|
return arr_lines + lo_lines + hi_lines + [
|
||||||
|
f"CentValue {tmp} = cent_list_slice({arr_var}, {lo_var}, {hi_var});"
|
||||||
|
], tmp
|
||||||
|
|
||||||
if isinstance(node, DataArray):
|
if isinstance(node, DataArray):
|
||||||
lines = []
|
lines = []
|
||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
@@ -112,21 +174,47 @@ def emit_expr(node, ctx):
|
|||||||
hi_lines, hi_var = emit_expr(node.to_value, ctx)
|
hi_lines, hi_var = emit_expr(node.to_value, ctx)
|
||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
i_var = ctx.fresh_tmp()
|
i_var = ctx.fresh_tmp()
|
||||||
cap = f"({hi_var}.ival > {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival) : 0)"
|
if node.step is None:
|
||||||
lines = lo_lines + hi_lines + [
|
cap = f"({hi_var}.ival >= {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival + 1) : 0)"
|
||||||
f"CentValue {tmp} = cent_list_new({cap});",
|
lines = lo_lines + hi_lines + [
|
||||||
f"for (long {i_var} = {lo_var}.ival; {i_var} < {hi_var}.ival; {i_var}++) {{",
|
f"CentValue {tmp} = cent_list_new({cap});",
|
||||||
|
f"for (long {i_var} = {lo_var}.ival; {i_var} <= {hi_var}.ival; {i_var}++) {{",
|
||||||
|
f" cent_list_push(&{tmp}, cent_int({i_var}));",
|
||||||
|
"}",
|
||||||
|
]
|
||||||
|
return lines, tmp
|
||||||
|
step_lines, step_var = emit_expr(node.step, ctx)
|
||||||
|
lines = lo_lines + hi_lines + step_lines + [
|
||||||
|
f'if ({step_var}.type != CENT_INT) cent_type_error("Range step must be a number");',
|
||||||
|
f'if ({step_var}.ival == 0) cent_runtime_error("Range step cannot be zero");',
|
||||||
|
f"CentValue {tmp} = cent_list_new(0);",
|
||||||
|
f"for (long {i_var} = {lo_var}.ival; ({step_var}.ival > 0 ? {i_var} <= {hi_var}.ival : {i_var} >= {hi_var}.ival); {i_var} += {step_var}.ival) {{",
|
||||||
f" cent_list_push(&{tmp}, cent_int({i_var}));",
|
f" cent_list_push(&{tmp}, cent_int({i_var}));",
|
||||||
"}",
|
"}",
|
||||||
]
|
]
|
||||||
return lines, tmp
|
return lines, tmp
|
||||||
|
|
||||||
|
if isinstance(node, DataDict):
|
||||||
|
lines = []
|
||||||
|
tmp = ctx.fresh_tmp()
|
||||||
|
lines.append(f"CentValue {tmp} = cent_dict_new({len(node.pairs)});")
|
||||||
|
for key_node, val_node in node.pairs:
|
||||||
|
k_lines, k_var = emit_expr(key_node, ctx)
|
||||||
|
v_lines, v_var = emit_expr(val_node, ctx)
|
||||||
|
lines.extend(k_lines)
|
||||||
|
lines.extend(v_lines)
|
||||||
|
lines.append(f"cent_dict_set(&{tmp}, {k_var}, {v_var});")
|
||||||
|
return lines, tmp
|
||||||
|
|
||||||
if isinstance(node, BuiltIn):
|
if isinstance(node, BuiltIn):
|
||||||
return _emit_builtin(node, ctx)
|
return _emit_builtin(node, ctx)
|
||||||
|
|
||||||
if isinstance(node, Invoca):
|
if isinstance(node, Invoca):
|
||||||
return _emit_invoca(node, ctx)
|
return _emit_invoca(node, ctx)
|
||||||
|
|
||||||
|
if isinstance(node, Fvnctio):
|
||||||
|
return _emit_fvnctio(node, ctx)
|
||||||
|
|
||||||
raise NotImplementedError(type(node).__name__)
|
raise NotImplementedError(type(node).__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -141,12 +229,12 @@ def _emit_builtin(node, ctx):
|
|||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
|
|
||||||
match node.builtin:
|
match node.builtin:
|
||||||
case "DICE":
|
case "DIC":
|
||||||
if not param_vars:
|
if not param_vars:
|
||||||
lines.append('cent_dice(cent_str(""));')
|
lines.append('cent_dic(cent_str(""));')
|
||||||
lines.append(f'CentValue {tmp} = cent_str("");')
|
lines.append(f'CentValue {tmp} = cent_str("");')
|
||||||
elif len(param_vars) == 1:
|
elif len(param_vars) == 1:
|
||||||
lines.append(f"cent_dice({param_vars[0]});")
|
lines.append(f"cent_dic({param_vars[0]});")
|
||||||
lines.append(f"CentValue {tmp} = {param_vars[0]};")
|
lines.append(f"CentValue {tmp} = {param_vars[0]};")
|
||||||
else:
|
else:
|
||||||
acc = param_vars[0]
|
acc = param_vars[0]
|
||||||
@@ -156,7 +244,7 @@ def _emit_builtin(node, ctx):
|
|||||||
lines.append(f'CentValue {space_tmp} = cent_concat({acc}, cent_str(" "));')
|
lines.append(f'CentValue {space_tmp} = cent_concat({acc}, cent_str(" "));')
|
||||||
lines.append(f"CentValue {joined_tmp} = cent_concat({space_tmp}, {pv});")
|
lines.append(f"CentValue {joined_tmp} = cent_concat({space_tmp}, {pv});")
|
||||||
acc = joined_tmp
|
acc = joined_tmp
|
||||||
lines.append(f"cent_dice({acc});")
|
lines.append(f"cent_dic({acc});")
|
||||||
lines.append(f"CentValue {tmp} = {acc};")
|
lines.append(f"CentValue {tmp} = {acc};")
|
||||||
|
|
||||||
case "AVDI":
|
case "AVDI":
|
||||||
@@ -168,29 +256,206 @@ def _emit_builtin(node, ctx):
|
|||||||
case "LONGITVDO":
|
case "LONGITVDO":
|
||||||
lines.append(f"CentValue {tmp} = cent_longitudo({param_vars[0]});")
|
lines.append(f"CentValue {tmp} = cent_longitudo({param_vars[0]});")
|
||||||
|
|
||||||
case "FORTIS_NVMERVS":
|
case "LITTERA":
|
||||||
if not ctx.has_module("FORS"):
|
lines.append(f"CentValue {tmp} = cent_littera({param_vars[0]});")
|
||||||
lines.append('cent_runtime_error("FORS module required for FORTIS_NVMERVS");')
|
|
||||||
lines.append(f"CentValue {tmp} = cent_null();")
|
|
||||||
else:
|
|
||||||
lines.append(f"CentValue {tmp} = cent_fortis_numerus({param_vars[0]}, {param_vars[1]});")
|
|
||||||
|
|
||||||
case "FORTIS_ELECTIONIS":
|
case "MAIVSCVLA":
|
||||||
if not ctx.has_module("FORS"):
|
if len(param_vars) != 1:
|
||||||
lines.append('cent_runtime_error("FORS module required for FORTIS_ELECTIONIS");')
|
lines.append(f'cent_runtime_error("MAIVSCVLA takes exactly I argument");')
|
||||||
lines.append(f"CentValue {tmp} = cent_null();")
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
else:
|
else:
|
||||||
lines.append(f"CentValue {tmp} = cent_fortis_electionis({param_vars[0]});")
|
lines.append(f"CentValue {tmp} = cent_maivscvla({param_vars[0]});")
|
||||||
|
|
||||||
|
case "MINVSCVLA":
|
||||||
|
if len(param_vars) != 1:
|
||||||
|
lines.append(f'cent_runtime_error("MINVSCVLA takes exactly I argument");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_minvscvla({param_vars[0]});")
|
||||||
|
|
||||||
|
case "FORTVITVS_NVMERVS":
|
||||||
|
if not ctx.has_module("FORS"):
|
||||||
|
lines.append('cent_runtime_error("FORS module required for FORTVITVS_NVMERVS");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_fortuitus_numerus({param_vars[0]}, {param_vars[1]});")
|
||||||
|
|
||||||
|
case "FORTVITA_ELECTIO":
|
||||||
|
if not ctx.has_module("FORS"):
|
||||||
|
lines.append('cent_runtime_error("FORS module required for FORTVITA_ELECTIO");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_fortuita_electionis({param_vars[0]});")
|
||||||
|
|
||||||
|
case "DECIMATIO":
|
||||||
|
if not ctx.has_module("FORS"):
|
||||||
|
lines.append('cent_runtime_error("FORS module required for DECIMATIO");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_decimatio({param_vars[0]});")
|
||||||
|
|
||||||
|
case "SEMEN":
|
||||||
|
if not ctx.has_module("FORS"):
|
||||||
|
lines.append('cent_runtime_error("FORS module required for SEMEN");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"cent_semen({param_vars[0]});")
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
|
||||||
|
case "SENATVS":
|
||||||
|
if param_vars:
|
||||||
|
arr_tmp = ctx.fresh_tmp() + "_arr"
|
||||||
|
lines.append(f"CentValue {arr_tmp}[] = {{{', '.join(param_vars)}}};")
|
||||||
|
lines.append(f"CentValue {tmp} = cent_senatus({arr_tmp}, {len(param_vars)});")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_senatus(NULL, 0);")
|
||||||
|
|
||||||
case "ERVMPE":
|
case "ERVMPE":
|
||||||
# break as expression (side-effecting; result is unused)
|
# break as expression (side-effecting; result is unused)
|
||||||
lines.append("break;")
|
lines.append("break;")
|
||||||
lines.append(f"CentValue {tmp} = cent_null();")
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
|
||||||
case "EVERRO":
|
case "CLAVES":
|
||||||
lines.append("cent_everro();")
|
lines.append(f"CentValue {tmp} = cent_dict_keys({param_vars[0]});")
|
||||||
|
|
||||||
|
case "ORDINA":
|
||||||
|
if len(param_vars) == 1:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_ordina({param_vars[0]});")
|
||||||
|
elif len(param_vars) == 2:
|
||||||
|
lines.append(
|
||||||
|
f"CentValue {tmp} = cent_ordina_cmp({param_vars[0]}, {param_vars[1]}, _scope);"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise _err(node, "ORDINA takes 1 or 2 arguments")
|
||||||
|
|
||||||
|
case "MVTA":
|
||||||
|
if len(param_vars) != 2:
|
||||||
|
raise _err(node, "MVTA takes II arguments")
|
||||||
|
lines.append(
|
||||||
|
f"CentValue {tmp} = cent_mvta({param_vars[0]}, {param_vars[1]}, _scope);"
|
||||||
|
)
|
||||||
|
|
||||||
|
case "CRIBRA":
|
||||||
|
if len(param_vars) != 2:
|
||||||
|
raise _err(node, "CRIBRA takes II arguments")
|
||||||
|
lines.append(
|
||||||
|
f"CentValue {tmp} = cent_cribra({param_vars[0]}, {param_vars[1]}, _scope);"
|
||||||
|
)
|
||||||
|
|
||||||
|
case "CONFLA":
|
||||||
|
if len(param_vars) != 3:
|
||||||
|
raise _err(node, "CONFLA takes III arguments")
|
||||||
|
lines.append(
|
||||||
|
f"CentValue {tmp} = cent_confla({param_vars[0]}, {param_vars[1]}, {param_vars[2]}, _scope);"
|
||||||
|
)
|
||||||
|
|
||||||
|
case "ADDE":
|
||||||
|
lines.append(f"CentValue {tmp} = cent_adde({param_vars[0]}, {param_vars[1]});")
|
||||||
|
|
||||||
|
case "TOLLE":
|
||||||
|
lines.append(f"CentValue {tmp} = cent_tolle({param_vars[0]}, {param_vars[1]});")
|
||||||
|
|
||||||
|
case "INSERE":
|
||||||
|
lines.append(f"CentValue {tmp} = cent_insere({param_vars[0]}, {param_vars[1]}, {param_vars[2]});")
|
||||||
|
|
||||||
|
case "NECTE":
|
||||||
|
lines.append(f"CentValue {tmp} = cent_necte({param_vars[0]}, {param_vars[1]});")
|
||||||
|
|
||||||
|
case "IVNGE":
|
||||||
|
lines.append(f"CentValue {tmp} = cent_ivnge({param_vars[0]}, {param_vars[1]});")
|
||||||
|
|
||||||
|
case "EVERRE":
|
||||||
|
lines.append("cent_everre();")
|
||||||
lines.append(f"CentValue {tmp} = cent_null();")
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
|
||||||
|
case "TYPVS":
|
||||||
|
lines.append(f"CentValue {tmp} = cent_typvs({param_vars[0]});")
|
||||||
|
|
||||||
|
case "DORMI":
|
||||||
|
lines.append(f"cent_dormi({param_vars[0]});")
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
|
||||||
|
case "LEGE":
|
||||||
|
if not ctx.has_module("SCRIPTA"):
|
||||||
|
lines.append('cent_runtime_error("SCRIPTA module required for LEGE");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_lege({param_vars[0]});")
|
||||||
|
|
||||||
|
case "SCRIBE":
|
||||||
|
if not ctx.has_module("SCRIPTA"):
|
||||||
|
lines.append('cent_runtime_error("SCRIPTA module required for SCRIBE");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"cent_scribe({param_vars[0]}, {param_vars[1]});")
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
|
||||||
|
case "ADIVNGE":
|
||||||
|
if not ctx.has_module("SCRIPTA"):
|
||||||
|
lines.append('cent_runtime_error("SCRIPTA module required for ADIVNGE");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"cent_adivnge({param_vars[0]}, {param_vars[1]});")
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
|
||||||
|
case "NVMERVS":
|
||||||
|
if len(param_vars) != 1:
|
||||||
|
lines.append(f'cent_runtime_error("NVMERVS takes exactly I argument");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_numerus({param_vars[0]});")
|
||||||
|
|
||||||
|
case "QVAERE":
|
||||||
|
lines.append(f"CentValue {tmp} = cent_qvaere({param_vars[0]}, {param_vars[1]});")
|
||||||
|
|
||||||
|
case "SVBSTITVE":
|
||||||
|
lines.append(f"CentValue {tmp} = cent_svbstitve({param_vars[0]}, {param_vars[1]}, {param_vars[2]});")
|
||||||
|
|
||||||
|
case "SCINDE":
|
||||||
|
lines.append(f"CentValue {tmp} = cent_scinde({param_vars[0]}, {param_vars[1]});")
|
||||||
|
|
||||||
|
case "PETE":
|
||||||
|
if not ctx.has_module("RETE"):
|
||||||
|
lines.append('cent_runtime_error("RETE module required for PETE");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_pete({param_vars[0]});")
|
||||||
|
|
||||||
|
case "PETITVR":
|
||||||
|
if not ctx.has_module("RETE"):
|
||||||
|
lines.append('cent_runtime_error("RETE module required for PETITVR");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"cent_petitvr({param_vars[0]}, {param_vars[1]}, _scope);")
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
|
||||||
|
case "AVSCVLTA":
|
||||||
|
if not ctx.has_module("RETE"):
|
||||||
|
lines.append('cent_runtime_error("RETE module required for AVSCVLTA");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"cent_avscvlta({param_vars[0]});")
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
|
||||||
|
case "IASON_LEGE":
|
||||||
|
if not ctx.has_module("IASON"):
|
||||||
|
lines.append('cent_runtime_error("IASON module required for IASON_LEGE");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
elif len(param_vars) != 1:
|
||||||
|
raise _err(node, "IASON_LEGE takes exactly I argument")
|
||||||
|
else:
|
||||||
|
fractio_flag = "1" if ctx.has_module("FRACTIO") else "0"
|
||||||
|
lines.append(f"CentValue {tmp} = cent_iason_lege({param_vars[0]}, {fractio_flag});")
|
||||||
|
|
||||||
|
case "IASON_SCRIBE":
|
||||||
|
if not ctx.has_module("IASON"):
|
||||||
|
lines.append('cent_runtime_error("IASON module required for IASON_SCRIBE");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
elif len(param_vars) != 1:
|
||||||
|
raise _err(node, "IASON_SCRIBE takes exactly I argument")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_iason_scribe({param_vars[0]});")
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
raise NotImplementedError(node.builtin)
|
raise NotImplementedError(node.builtin)
|
||||||
|
|
||||||
@@ -200,7 +465,8 @@ def _emit_builtin(node, ctx):
|
|||||||
def _emit_invoca(node, ctx):
|
def _emit_invoca(node, ctx):
|
||||||
"""
|
"""
|
||||||
Emits a user-defined function call.
|
Emits a user-defined function call.
|
||||||
Requires ctx.functions[name] = [param_names] populated by the emitter pre-pass.
|
Supports both static resolution (ID callee with known function) and
|
||||||
|
dynamic dispatch (arbitrary expression callee via CENT_FUNC values).
|
||||||
"""
|
"""
|
||||||
lines = []
|
lines = []
|
||||||
param_vars = []
|
param_vars = []
|
||||||
@@ -209,21 +475,59 @@ def _emit_invoca(node, ctx):
|
|||||||
lines.extend(p_lines)
|
lines.extend(p_lines)
|
||||||
param_vars.append(p_var)
|
param_vars.append(p_var)
|
||||||
|
|
||||||
func_name = node.name.name
|
# Try static resolution for simple ID callees
|
||||||
c_func_name = ctx.func_resolve.get(func_name)
|
if isinstance(node.callee, ID):
|
||||||
if c_func_name is None:
|
c_func_name = ctx.func_resolve.get(node.callee.name)
|
||||||
raise CentvrionError(f"Undefined function: {func_name}")
|
if c_func_name is not None:
|
||||||
|
call_scope_var = ctx.fresh_tmp() + "_sc"
|
||||||
|
lines.append(f"CentScope {call_scope_var} = cent_scope_copy(&_scope);")
|
||||||
|
param_names = ctx.functions[c_func_name]
|
||||||
|
if len(param_vars) != len(param_names):
|
||||||
|
raise _err(node,
|
||||||
|
f"Function '{node.callee.name}' expects {len(param_names)} argument(s), "
|
||||||
|
f"got {len(param_vars)}"
|
||||||
|
)
|
||||||
|
for i, pname in enumerate(param_names):
|
||||||
|
lines.append(f'cent_scope_set(&{call_scope_var}, "{pname}", {param_vars[i]});')
|
||||||
|
tmp = ctx.fresh_tmp()
|
||||||
|
lines.append(f"CentValue {tmp} = {c_func_name}({call_scope_var});")
|
||||||
|
return lines, tmp
|
||||||
|
|
||||||
|
# Dynamic dispatch: evaluate callee, call via function pointer
|
||||||
|
callee_lines, callee_var = emit_expr(node.callee, ctx)
|
||||||
|
lines.extend(callee_lines)
|
||||||
|
lines.append(f'if ({callee_var}.type != CENT_FUNC) cent_type_error("cannot call non-function");')
|
||||||
call_scope_var = ctx.fresh_tmp() + "_sc"
|
call_scope_var = ctx.fresh_tmp() + "_sc"
|
||||||
lines.append(f"CentScope {call_scope_var} = cent_scope_copy(&_scope);")
|
lines.append(f"CentScope {call_scope_var} = cent_scope_copy(&_scope);")
|
||||||
|
nargs = len(param_vars)
|
||||||
param_names = ctx.functions[c_func_name]
|
lines.append(
|
||||||
if len(param_vars) != len(param_names):
|
f"if ({callee_var}.fnval.param_count != {nargs}) "
|
||||||
raise CentvrionError(
|
f'cent_runtime_error("wrong number of arguments");'
|
||||||
f"Function '{func_name}' expects {len(param_names)} argument(s), got {len(param_vars)}"
|
)
|
||||||
|
for i, pv in enumerate(param_vars):
|
||||||
|
lines.append(
|
||||||
|
f'cent_scope_set(&{call_scope_var}, '
|
||||||
|
f'{callee_var}.fnval.param_names[{i}], {pv});'
|
||||||
)
|
)
|
||||||
for i, pname in enumerate(param_names):
|
|
||||||
lines.append(f'cent_scope_set(&{call_scope_var}, "{pname}", {param_vars[i]});')
|
|
||||||
|
|
||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
lines.append(f"CentValue {tmp} = {c_func_name}({call_scope_var});")
|
lines.append(f"CentValue {tmp} = {callee_var}.fnval.fn({call_scope_var});")
|
||||||
|
return lines, tmp
|
||||||
|
|
||||||
|
|
||||||
|
def _emit_fvnctio(node, ctx):
|
||||||
|
"""Emit a FVNCTIO lambda expression as a CENT_FUNC value."""
|
||||||
|
c_name = ctx.lambda_names[id(node)]
|
||||||
|
param_names = ctx.functions[c_name]
|
||||||
|
tmp = ctx.fresh_tmp()
|
||||||
|
lines = []
|
||||||
|
# Build static param name array
|
||||||
|
params_arr = ctx.fresh_tmp() + "_pn"
|
||||||
|
lines.append(
|
||||||
|
f"static const char *{params_arr}[] = {{"
|
||||||
|
+ ", ".join(f'"{p}"' for p in param_names)
|
||||||
|
+ "};"
|
||||||
|
)
|
||||||
|
lines.append(
|
||||||
|
f"CentValue {tmp} = cent_func_val({c_name}, {params_arr}, {len(param_names)});"
|
||||||
|
)
|
||||||
return lines, tmp
|
return lines, tmp
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from centvrion.ast_nodes import (
|
from centvrion.ast_nodes import (
|
||||||
Designa, DesignaIndex, SiStatement, DumStatement, PerStatement,
|
Designa, DesignaIndex, DesignaDestructure, SiStatement, DumStatement,
|
||||||
Defini, Redi, Erumpe, Continva, ExpressionStatement, ID,
|
PerStatement, TemptaStatement, Defini, Redi, Erumpe, Continva,
|
||||||
|
ExpressionStatement, ID,
|
||||||
)
|
)
|
||||||
from centvrion.compiler.emit_expr import emit_expr
|
from centvrion.compiler.emit_expr import emit_expr
|
||||||
|
|
||||||
@@ -10,24 +11,57 @@ def emit_stmt(node, ctx):
|
|||||||
Emit C code for a CENTVRION statement node.
|
Emit C code for a CENTVRION statement node.
|
||||||
Returns lines — list of C statements.
|
Returns lines — list of C statements.
|
||||||
"""
|
"""
|
||||||
|
body = _emit_stmt_body(node, ctx)
|
||||||
|
pos = getattr(node, "pos", None)
|
||||||
|
if pos is not None:
|
||||||
|
return [f"_cent_current_line = {pos[0]};"] + body
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
def _emit_stmt_body(node, ctx):
|
||||||
if isinstance(node, Designa):
|
if isinstance(node, Designa):
|
||||||
# Function alias: resolved at compile time, no runtime code needed
|
|
||||||
if isinstance(node.value, ID) and node.value.name in ctx.func_resolve:
|
|
||||||
return []
|
|
||||||
val_lines, val_var = emit_expr(node.value, ctx)
|
val_lines, val_var = emit_expr(node.value, ctx)
|
||||||
return val_lines + [f'cent_scope_set(&_scope, "{node.id.name}", {val_var});']
|
return val_lines + [f'cent_scope_set(&_scope, "{node.id.name}", {val_var});']
|
||||||
|
|
||||||
if isinstance(node, DesignaIndex):
|
if isinstance(node, DesignaIndex):
|
||||||
idx_lines, idx_var = emit_expr(node.index, ctx)
|
lines = []
|
||||||
|
idx_vars = []
|
||||||
|
for idx_expr in node.indices:
|
||||||
|
idx_lines, idx_var = emit_expr(idx_expr, ctx)
|
||||||
|
lines += idx_lines
|
||||||
|
idx_vars.append(idx_var)
|
||||||
val_lines, val_var = emit_expr(node.value, ctx)
|
val_lines, val_var = emit_expr(node.value, ctx)
|
||||||
arr_tmp = ctx.fresh_tmp()
|
lines += val_lines
|
||||||
return (
|
root_tmp = ctx.fresh_tmp()
|
||||||
idx_lines + val_lines + [
|
lines.append(f'CentValue {root_tmp} = cent_scope_get(&_scope, "{node.id.name}");')
|
||||||
f'CentValue {arr_tmp} = cent_scope_get(&_scope, "{node.id.name}");',
|
if len(idx_vars) == 1:
|
||||||
f"cent_list_index_set(&{arr_tmp}, {idx_var}, {val_var});",
|
lines.append(f"cent_list_index_set(&{root_tmp}, {idx_vars[0]}, {val_var});")
|
||||||
f'cent_scope_set(&_scope, "{node.id.name}", {arr_tmp});',
|
else:
|
||||||
]
|
# Walk down to collect intermediate containers
|
||||||
)
|
container_tmps = [root_tmp]
|
||||||
|
for idx_var in idx_vars[:-1]:
|
||||||
|
tmp = ctx.fresh_tmp()
|
||||||
|
lines.append(f"CentValue {tmp} = cent_list_index({container_tmps[-1]}, {idx_var});")
|
||||||
|
container_tmps.append(tmp)
|
||||||
|
# Set at deepest level
|
||||||
|
lines.append(f"cent_list_index_set(&{container_tmps[-1]}, {idx_vars[-1]}, {val_var});")
|
||||||
|
# Rebuild up the chain
|
||||||
|
for i in range(len(container_tmps) - 2, -1, -1):
|
||||||
|
lines.append(f"cent_list_index_set(&{container_tmps[i]}, {idx_vars[i]}, {container_tmps[i + 1]});")
|
||||||
|
lines.append(f'cent_scope_set(&_scope, "{node.id.name}", {root_tmp});')
|
||||||
|
return lines
|
||||||
|
|
||||||
|
if isinstance(node, DesignaDestructure):
|
||||||
|
n = len(node.ids)
|
||||||
|
val_lines, val_var = emit_expr(node.value, ctx)
|
||||||
|
lines = val_lines[:]
|
||||||
|
lines.append(f'if ({val_var}.type != CENT_LIST) cent_type_error("Cannot destructure non-array value");')
|
||||||
|
lines.append(f'if ({val_var}.lval.len != {n}) cent_runtime_error("Destructuring mismatch");')
|
||||||
|
for i, id_node in enumerate(node.ids):
|
||||||
|
tmp = ctx.fresh_tmp()
|
||||||
|
lines.append(f"CentValue {tmp} = cent_list_index({val_var}, cent_int({i + 1}));")
|
||||||
|
lines.append(f'cent_scope_set(&_scope, "{id_node.name}", {tmp});')
|
||||||
|
return lines
|
||||||
|
|
||||||
if isinstance(node, SiStatement):
|
if isinstance(node, SiStatement):
|
||||||
cond_lines, cond_var = emit_expr(node.test, ctx)
|
cond_lines, cond_var = emit_expr(node.test, ctx)
|
||||||
@@ -55,19 +89,61 @@ def emit_stmt(node, ctx):
|
|||||||
if isinstance(node, PerStatement):
|
if isinstance(node, PerStatement):
|
||||||
arr_lines, arr_var = emit_expr(node.data_list, ctx)
|
arr_lines, arr_var = emit_expr(node.data_list, ctx)
|
||||||
i_var = ctx.fresh_tmp()
|
i_var = ctx.fresh_tmp()
|
||||||
var_name = node.variable_name.name
|
|
||||||
body_lines = _emit_body(node.statements, ctx)
|
body_lines = _emit_body(node.statements, ctx)
|
||||||
lines = arr_lines + [
|
|
||||||
f'if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array");',
|
if node.destructure:
|
||||||
f"for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{",
|
# Destructuring PER — each element must be a list
|
||||||
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.lval.items[{i_var}]);',
|
elem_var = ctx.fresh_tmp()
|
||||||
]
|
assign_lines = [
|
||||||
lines += [f" {l}" for l in body_lines]
|
f"CentValue {elem_var} = {arr_var}.lval.items[{i_var}];",
|
||||||
lines += ["}"]
|
f'if ({elem_var}.type != CENT_LIST) cent_type_error("Cannot destructure non-array value in PER loop");',
|
||||||
|
f'if ({elem_var}.lval.len != {len(node.variable_name)}) cent_runtime_error("Destructuring mismatch");',
|
||||||
|
]
|
||||||
|
for j, id_node in enumerate(node.variable_name):
|
||||||
|
tmp = ctx.fresh_tmp()
|
||||||
|
assign_lines.append(f"CentValue {tmp} = cent_list_index({elem_var}, cent_int({j + 1}));")
|
||||||
|
assign_lines.append(f'cent_scope_set(&_scope, "{id_node.name}", {tmp});')
|
||||||
|
lines = arr_lines + [
|
||||||
|
f'if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array");',
|
||||||
|
f"for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{",
|
||||||
|
]
|
||||||
|
lines += [f" {l}" for l in assign_lines]
|
||||||
|
lines += [f" {l}" for l in body_lines]
|
||||||
|
lines += ["}"]
|
||||||
|
else:
|
||||||
|
var_name = node.variable_name.name
|
||||||
|
lines = arr_lines + [
|
||||||
|
f"if ({arr_var}.type == CENT_DICT) {{",
|
||||||
|
f" for (int {i_var} = 0; {i_var} < {arr_var}.dval.len; {i_var}++) {{",
|
||||||
|
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.dval.keys[{i_var}]);',
|
||||||
|
]
|
||||||
|
lines += [f" {l}" for l in body_lines]
|
||||||
|
lines += [
|
||||||
|
" }",
|
||||||
|
"} else {",
|
||||||
|
f' if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array or dict");',
|
||||||
|
f" for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{",
|
||||||
|
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.lval.items[{i_var}]);',
|
||||||
|
]
|
||||||
|
lines += [f" {l}" for l in body_lines]
|
||||||
|
lines += [" }", "}"]
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
if isinstance(node, Defini):
|
if isinstance(node, Defini):
|
||||||
# Function definitions are hoisted by emitter.py; no-op here.
|
# Top-level definitions are handled by emitter.py (hoisted + scope-set).
|
||||||
|
# Nested definitions (inside another function) need runtime scope-set.
|
||||||
|
if ctx.current_function is not None:
|
||||||
|
name = node.name.name
|
||||||
|
c_name = ctx.func_resolve[name]
|
||||||
|
param_names = ctx.functions[c_name]
|
||||||
|
pn_var = ctx.fresh_tmp() + "_pn"
|
||||||
|
return [
|
||||||
|
f"static const char *{pn_var}[] = {{"
|
||||||
|
+ ", ".join(f'"{p}"' for p in param_names)
|
||||||
|
+ "};",
|
||||||
|
f'cent_scope_set(&_scope, "{name}", '
|
||||||
|
f"cent_func_val({c_name}, {pn_var}, {len(param_names)}));",
|
||||||
|
]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if isinstance(node, Redi):
|
if isinstance(node, Redi):
|
||||||
@@ -95,6 +171,24 @@ def emit_stmt(node, ctx):
|
|||||||
if isinstance(node, Continva):
|
if isinstance(node, Continva):
|
||||||
return ["continue;"]
|
return ["continue;"]
|
||||||
|
|
||||||
|
if isinstance(node, TemptaStatement):
|
||||||
|
lines = [
|
||||||
|
"_cent_try_depth++;",
|
||||||
|
"if (setjmp(_cent_try_stack[_cent_try_depth - 1]) == 0) {",
|
||||||
|
]
|
||||||
|
try_lines = _emit_body(node.try_statements, ctx)
|
||||||
|
lines += [f" {l}" for l in try_lines]
|
||||||
|
lines += [
|
||||||
|
" _cent_try_depth--;",
|
||||||
|
"} else {",
|
||||||
|
" _cent_try_depth--;",
|
||||||
|
f' cent_scope_set(&_scope, "{node.error_var.name}", cent_str(_cent_error_msg));',
|
||||||
|
]
|
||||||
|
catch_lines = _emit_body(node.catch_statements, ctx)
|
||||||
|
lines += [f" {l}" for l in catch_lines]
|
||||||
|
lines += ["}"]
|
||||||
|
return lines
|
||||||
|
|
||||||
if isinstance(node, ExpressionStatement):
|
if isinstance(node, ExpressionStatement):
|
||||||
lines, _ = emit_expr(node.expression, ctx)
|
lines, _ = emit_expr(node.expression, ctx)
|
||||||
return lines
|
return lines
|
||||||
|
|||||||
@@ -1,11 +1,32 @@
|
|||||||
import os
|
import os
|
||||||
from centvrion.ast_nodes import Defini, Designa, ID
|
from centvrion.ast_nodes import Defini, Designa, Fvnctio, ID, Node
|
||||||
from centvrion.compiler.context import EmitContext
|
from centvrion.compiler.context import EmitContext
|
||||||
from centvrion.compiler.emit_stmt import emit_stmt, _emit_body
|
from centvrion.compiler.emit_stmt import emit_stmt, _emit_body
|
||||||
|
|
||||||
_RUNTIME_DIR = os.path.join(os.path.dirname(__file__), "runtime")
|
_RUNTIME_DIR = os.path.join(os.path.dirname(__file__), "runtime")
|
||||||
|
|
||||||
|
|
||||||
|
def _collect_lambdas(node, ctx, counter):
|
||||||
|
"""Walk AST recursively, find all Fvnctio nodes, assign C names."""
|
||||||
|
if isinstance(node, Fvnctio):
|
||||||
|
c_name = f"_cent_lambda_{counter[0]}"
|
||||||
|
counter[0] += 1
|
||||||
|
ctx.lambda_names[id(node)] = c_name
|
||||||
|
ctx.functions[c_name] = [p.name for p in node.parameters]
|
||||||
|
ctx.lambdas.append((c_name, node))
|
||||||
|
for attr in vars(node).values():
|
||||||
|
if isinstance(attr, Node):
|
||||||
|
_collect_lambdas(attr, ctx, counter)
|
||||||
|
elif isinstance(attr, list):
|
||||||
|
for item in attr:
|
||||||
|
if isinstance(item, Node):
|
||||||
|
_collect_lambdas(item, ctx, counter)
|
||||||
|
elif isinstance(item, tuple):
|
||||||
|
for elem in item:
|
||||||
|
if isinstance(elem, Node):
|
||||||
|
_collect_lambdas(elem, ctx, counter)
|
||||||
|
|
||||||
|
|
||||||
def compile_program(program):
|
def compile_program(program):
|
||||||
"""Return a complete C source string for the given Program AST node."""
|
"""Return a complete C source string for the given Program AST node."""
|
||||||
ctx = EmitContext()
|
ctx = EmitContext()
|
||||||
@@ -26,26 +47,28 @@ def compile_program(program):
|
|||||||
ctx.functions[c_name] = [p.name for p in stmt.parameters]
|
ctx.functions[c_name] = [p.name for p in stmt.parameters]
|
||||||
ctx.func_resolve[name] = c_name
|
ctx.func_resolve[name] = c_name
|
||||||
func_definitions.append((c_name, stmt))
|
func_definitions.append((c_name, stmt))
|
||||||
elif isinstance(stmt, Designa) and isinstance(stmt.value, ID):
|
|
||||||
rhs = stmt.value.name
|
# Lambda lifting: find all Fvnctio nodes in the entire AST
|
||||||
if rhs in ctx.func_resolve:
|
counter = [0]
|
||||||
ctx.func_resolve[stmt.id.name] = ctx.func_resolve[rhs]
|
for stmt in program.statements:
|
||||||
|
_collect_lambdas(stmt, ctx, counter)
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# Includes
|
# Includes
|
||||||
lines += [
|
lines += [
|
||||||
f'#include "{_RUNTIME_DIR}/cent_runtime.h"',
|
f'#include "{_RUNTIME_DIR}/cent_runtime.h"',
|
||||||
|
f'#include "{_RUNTIME_DIR}/cent_iason.h"',
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Forward declarations
|
# Forward declarations (named functions + lambdas)
|
||||||
for c_name in ctx.functions:
|
for c_name in ctx.functions:
|
||||||
lines.append(f"CentValue {c_name}(CentScope _scope);")
|
lines.append(f"CentValue {c_name}(CentScope _scope);")
|
||||||
if ctx.functions:
|
if ctx.functions:
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Hoisted function definitions
|
# Hoisted named function definitions
|
||||||
for c_name, stmt in func_definitions:
|
for c_name, stmt in func_definitions:
|
||||||
ctx.current_function = c_name
|
ctx.current_function = c_name
|
||||||
lines.append(f"CentValue {c_name}(CentScope _scope) {{")
|
lines.append(f"CentValue {c_name}(CentScope _scope) {{")
|
||||||
@@ -55,15 +78,44 @@ def compile_program(program):
|
|||||||
lines += ["_func_return:", " return _return_val;", "}", ""]
|
lines += ["_func_return:", " return _return_val;", "}", ""]
|
||||||
ctx.current_function = None
|
ctx.current_function = None
|
||||||
|
|
||||||
|
# Hoisted lambda definitions
|
||||||
|
for c_name, fvnctio_node in ctx.lambdas:
|
||||||
|
ctx.current_function = c_name
|
||||||
|
lines.append(f"CentValue {c_name}(CentScope _scope) {{")
|
||||||
|
lines.append(" CentValue _return_val = cent_null();")
|
||||||
|
for l in _emit_body(fvnctio_node.statements, ctx):
|
||||||
|
lines.append(f" {l}")
|
||||||
|
lines += ["_func_return:", " return _return_val;", "}", ""]
|
||||||
|
ctx.current_function = None
|
||||||
|
|
||||||
# main()
|
# main()
|
||||||
lines.append("int main(void) {")
|
lines.append("int main(void) {")
|
||||||
lines.append(" cent_init();")
|
lines.append(" cent_init();")
|
||||||
if "MAGNVM" in ctx.modules:
|
if "MAGNVM" in ctx.modules:
|
||||||
lines.append(" cent_magnvm = 1;")
|
lines.append(" cent_magnvm = 1;")
|
||||||
|
if "SVBNVLLA" in ctx.modules:
|
||||||
|
lines.append(" cent_svbnvlla = 1;")
|
||||||
lines.append(" CentScope _scope = {0};")
|
lines.append(" CentScope _scope = {0};")
|
||||||
lines.append(" CentValue _return_val = cent_null();")
|
lines.append(" CentValue _return_val = cent_null();")
|
||||||
|
|
||||||
|
# Build a map from id(Defini_node) → c_name for scope registration
|
||||||
|
defini_c_names = {id(stmt): c_name for c_name, stmt in func_definitions}
|
||||||
|
|
||||||
for stmt in program.statements:
|
for stmt in program.statements:
|
||||||
if isinstance(stmt, Defini):
|
if isinstance(stmt, Defini):
|
||||||
|
name = stmt.name.name
|
||||||
|
c_name = defini_c_names[id(stmt)]
|
||||||
|
param_names = ctx.functions[c_name]
|
||||||
|
pn_var = f"_pn_{c_name}"
|
||||||
|
lines.append(
|
||||||
|
f" static const char *{pn_var}[] = {{"
|
||||||
|
+ ", ".join(f'"{p}"' for p in param_names)
|
||||||
|
+ "};"
|
||||||
|
)
|
||||||
|
lines.append(
|
||||||
|
f' cent_scope_set(&_scope, "{name}", '
|
||||||
|
f"cent_func_val({c_name}, {pn_var}, {len(param_names)}));"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
for l in emit_stmt(stmt, ctx):
|
for l in emit_stmt(stmt, ctx):
|
||||||
lines.append(f" {l}")
|
lines.append(f" {l}")
|
||||||
|
|||||||
426
centvrion/compiler/runtime/cent_iason.c
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
#include "cent_iason.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* ---------- shared helpers ----------------------------------------- */
|
||||||
|
|
||||||
|
static long iason_gcd(long a, long b) {
|
||||||
|
if (a < 0) a = -a;
|
||||||
|
if (b < 0) b = -b;
|
||||||
|
while (b) { long t = b; b = a % b; a = t; }
|
||||||
|
return a ? a : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static CentValue iason_frac_reduce(long num, long den) {
|
||||||
|
if (den < 0) { num = -num; den = -den; }
|
||||||
|
long g = iason_gcd(num, den);
|
||||||
|
num /= g; den /= g;
|
||||||
|
if (den == 1) return cent_int(num);
|
||||||
|
return cent_frac(num, den);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- parser ------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *src;
|
||||||
|
size_t pos;
|
||||||
|
size_t len;
|
||||||
|
int fractio;
|
||||||
|
} IasonParser;
|
||||||
|
|
||||||
|
static void iason_die(const char *msg) {
|
||||||
|
cent_runtime_error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iason_skip_ws(IasonParser *p) {
|
||||||
|
while (p->pos < p->len) {
|
||||||
|
char c = p->src[p->pos];
|
||||||
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') p->pos++;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int iason_peek(IasonParser *p) {
|
||||||
|
return (p->pos < p->len) ? (unsigned char)p->src[p->pos] : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iason_expect(IasonParser *p, char c, const char *msg) {
|
||||||
|
if (p->pos >= p->len || p->src[p->pos] != c)
|
||||||
|
iason_die(msg);
|
||||||
|
p->pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iason_expect_word(IasonParser *p, const char *word) {
|
||||||
|
size_t n = strlen(word);
|
||||||
|
if (p->len - p->pos < n || memcmp(p->src + p->pos, word, n) != 0)
|
||||||
|
iason_die("IASON_LEGE: invalid JSON literal");
|
||||||
|
p->pos += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Encode a Unicode codepoint as UTF-8 into buf; returns bytes written. */
|
||||||
|
static int iason_utf8_encode(unsigned cp, char *buf) {
|
||||||
|
if (cp <= 0x7F) { buf[0] = (char)cp; return 1; }
|
||||||
|
if (cp <= 0x7FF) { buf[0] = (char)(0xC0 | (cp >> 6));
|
||||||
|
buf[1] = (char)(0x80 | (cp & 0x3F)); return 2; }
|
||||||
|
if (cp <= 0xFFFF) { buf[0] = (char)(0xE0 | (cp >> 12));
|
||||||
|
buf[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
||||||
|
buf[2] = (char)(0x80 | (cp & 0x3F)); return 3; }
|
||||||
|
if (cp <= 0x10FFFF) { buf[0] = (char)(0xF0 | (cp >> 18));
|
||||||
|
buf[1] = (char)(0x80 | ((cp >> 12) & 0x3F));
|
||||||
|
buf[2] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
||||||
|
buf[3] = (char)(0x80 | (cp & 0x3F)); return 4; }
|
||||||
|
iason_die("IASON_LEGE: codepoint out of range");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned iason_read_hex4(IasonParser *p) {
|
||||||
|
if (p->len - p->pos < 4) iason_die("IASON_LEGE: truncated \\u escape");
|
||||||
|
unsigned v = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
char c = p->src[p->pos++];
|
||||||
|
v <<= 4;
|
||||||
|
if (c >= '0' && c <= '9') v |= c - '0';
|
||||||
|
else if (c >= 'a' && c <= 'f') v |= c - 'a' + 10;
|
||||||
|
else if (c >= 'A' && c <= 'F') v |= c - 'A' + 10;
|
||||||
|
else iason_die("IASON_LEGE: invalid hex in \\u escape");
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parses a JSON string literal at p->pos (positioned at the opening "),
|
||||||
|
returns an arena-allocated NUL-terminated UTF-8 string. */
|
||||||
|
static char *iason_parse_string(IasonParser *p) {
|
||||||
|
iason_expect(p, '"', "IASON_LEGE: expected string");
|
||||||
|
/* upper bound on output: same as remaining input (escapes shrink). */
|
||||||
|
size_t cap = (p->len - p->pos) + 1;
|
||||||
|
char *buf = cent_arena_alloc(cent_arena, cap);
|
||||||
|
size_t out = 0;
|
||||||
|
while (p->pos < p->len) {
|
||||||
|
unsigned char c = (unsigned char)p->src[p->pos++];
|
||||||
|
if (c == '"') { buf[out] = '\0'; return buf; }
|
||||||
|
if (c == '\\') {
|
||||||
|
if (p->pos >= p->len) iason_die("IASON_LEGE: trailing \\ in string");
|
||||||
|
char esc = p->src[p->pos++];
|
||||||
|
switch (esc) {
|
||||||
|
case '"': buf[out++] = '"'; break;
|
||||||
|
case '\\': buf[out++] = '\\'; break;
|
||||||
|
case '/': buf[out++] = '/'; break;
|
||||||
|
case 'b': buf[out++] = '\b'; break;
|
||||||
|
case 'f': buf[out++] = '\f'; break;
|
||||||
|
case 'n': buf[out++] = '\n'; break;
|
||||||
|
case 'r': buf[out++] = '\r'; break;
|
||||||
|
case 't': buf[out++] = '\t'; break;
|
||||||
|
case 'u': {
|
||||||
|
unsigned cp = iason_read_hex4(p);
|
||||||
|
if (cp >= 0xD800 && cp <= 0xDBFF) {
|
||||||
|
/* high surrogate; expect \uXXXX low surrogate */
|
||||||
|
if (p->len - p->pos < 6 || p->src[p->pos] != '\\' || p->src[p->pos + 1] != 'u')
|
||||||
|
iason_die("IASON_LEGE: missing low surrogate after high surrogate");
|
||||||
|
p->pos += 2;
|
||||||
|
unsigned lo = iason_read_hex4(p);
|
||||||
|
if (lo < 0xDC00 || lo > 0xDFFF)
|
||||||
|
iason_die("IASON_LEGE: invalid low surrogate");
|
||||||
|
cp = 0x10000 + (((cp - 0xD800) << 10) | (lo - 0xDC00));
|
||||||
|
} else if (cp >= 0xDC00 && cp <= 0xDFFF) {
|
||||||
|
iason_die("IASON_LEGE: stray low surrogate");
|
||||||
|
}
|
||||||
|
out += iason_utf8_encode(cp, buf + out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: iason_die("IASON_LEGE: invalid escape sequence");
|
||||||
|
}
|
||||||
|
} else if (c < 0x20) {
|
||||||
|
iason_die("IASON_LEGE: unescaped control character in string");
|
||||||
|
} else {
|
||||||
|
buf[out++] = (char)c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iason_die("IASON_LEGE: unterminated string");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cap on fractional digits parsed exactly; beyond this we truncate to
|
||||||
|
keep `long` arithmetic safe (10^18 fits in int64). */
|
||||||
|
#define IASON_MAX_FRAC_DIGITS 18
|
||||||
|
|
||||||
|
static CentValue iason_parse_number(IasonParser *p) {
|
||||||
|
size_t start = p->pos;
|
||||||
|
int negative = 0;
|
||||||
|
if (p->src[p->pos] == '-') { negative = 1; p->pos++; }
|
||||||
|
|
||||||
|
/* Integer part. */
|
||||||
|
if (p->pos >= p->len || !isdigit((unsigned char)p->src[p->pos]))
|
||||||
|
iason_die("IASON_LEGE: invalid number");
|
||||||
|
if (p->src[p->pos] == '0') {
|
||||||
|
p->pos++;
|
||||||
|
} else {
|
||||||
|
while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos])) p->pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int has_frac = 0, has_exp = 0;
|
||||||
|
size_t frac_start = 0, frac_end = 0;
|
||||||
|
if (p->pos < p->len && p->src[p->pos] == '.') {
|
||||||
|
has_frac = 1;
|
||||||
|
p->pos++;
|
||||||
|
frac_start = p->pos;
|
||||||
|
if (p->pos >= p->len || !isdigit((unsigned char)p->src[p->pos]))
|
||||||
|
iason_die("IASON_LEGE: invalid number");
|
||||||
|
while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos])) p->pos++;
|
||||||
|
frac_end = p->pos;
|
||||||
|
}
|
||||||
|
long exp = 0;
|
||||||
|
if (p->pos < p->len && (p->src[p->pos] == 'e' || p->src[p->pos] == 'E')) {
|
||||||
|
has_exp = 1;
|
||||||
|
p->pos++;
|
||||||
|
int esign = 1;
|
||||||
|
if (p->pos < p->len && (p->src[p->pos] == '+' || p->src[p->pos] == '-')) {
|
||||||
|
if (p->src[p->pos] == '-') esign = -1;
|
||||||
|
p->pos++;
|
||||||
|
}
|
||||||
|
if (p->pos >= p->len || !isdigit((unsigned char)p->src[p->pos]))
|
||||||
|
iason_die("IASON_LEGE: invalid number");
|
||||||
|
while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos])) {
|
||||||
|
exp = exp * 10 + (p->src[p->pos] - '0');
|
||||||
|
p->pos++;
|
||||||
|
}
|
||||||
|
exp *= esign;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t end = p->pos;
|
||||||
|
|
||||||
|
if (!has_frac && !has_exp) {
|
||||||
|
/* Pure integer. Use strtol-style parse (we already validated digits). */
|
||||||
|
long v = 0;
|
||||||
|
for (size_t i = (negative ? start + 1 : start); i < end; i++) {
|
||||||
|
v = v * 10 + (p->src[i] - '0');
|
||||||
|
}
|
||||||
|
return cent_int(negative ? -v : v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p->fractio) {
|
||||||
|
/* Floor to int. strtod handles the full grammar here. */
|
||||||
|
char *tmp = cent_arena_alloc(cent_arena, end - start + 1);
|
||||||
|
memcpy(tmp, p->src + start, end - start);
|
||||||
|
tmp[end - start] = '\0';
|
||||||
|
double d = strtod(tmp, NULL);
|
||||||
|
return cent_int((long)floor(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FRACTIO loaded: build an exact fraction from the decimal/exponent form. */
|
||||||
|
long num = 0;
|
||||||
|
/* Integer-part digits */
|
||||||
|
size_t int_start = negative ? start + 1 : start;
|
||||||
|
size_t int_end = has_frac ? (frac_start - 1) : (has_exp ? end - 0 : end);
|
||||||
|
/* If we have an exponent without a fraction part, find where digits end. */
|
||||||
|
if (has_exp && !has_frac) {
|
||||||
|
int_end = int_start;
|
||||||
|
while (int_end < p->len && isdigit((unsigned char)p->src[int_end])) int_end++;
|
||||||
|
}
|
||||||
|
for (size_t i = int_start; i < int_end; i++) {
|
||||||
|
num = num * 10 + (p->src[i] - '0');
|
||||||
|
}
|
||||||
|
/* Fractional digits, capped */
|
||||||
|
long den = 1;
|
||||||
|
if (has_frac) {
|
||||||
|
size_t take = frac_end - frac_start;
|
||||||
|
if (take > IASON_MAX_FRAC_DIGITS) take = IASON_MAX_FRAC_DIGITS;
|
||||||
|
for (size_t i = 0; i < take; i++) {
|
||||||
|
num = num * 10 + (p->src[frac_start + i] - '0');
|
||||||
|
den *= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (negative) num = -num;
|
||||||
|
/* Apply exponent: positive shifts num, negative shifts den. */
|
||||||
|
while (exp > 0) { num *= 10; exp--; }
|
||||||
|
while (exp < 0) { den *= 10; exp++; }
|
||||||
|
return iason_frac_reduce(num, den);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CentValue iason_parse_value(IasonParser *p);
|
||||||
|
|
||||||
|
static CentValue iason_parse_array(IasonParser *p) {
|
||||||
|
iason_expect(p, '[', "IASON_LEGE: expected [");
|
||||||
|
iason_skip_ws(p);
|
||||||
|
CentValue lst = cent_list_new(4);
|
||||||
|
if (iason_peek(p) == ']') { p->pos++; return lst; }
|
||||||
|
for (;;) {
|
||||||
|
iason_skip_ws(p);
|
||||||
|
CentValue elem = iason_parse_value(p);
|
||||||
|
cent_list_push(&lst, elem);
|
||||||
|
iason_skip_ws(p);
|
||||||
|
int c = iason_peek(p);
|
||||||
|
if (c == ',') { p->pos++; continue; }
|
||||||
|
if (c == ']') { p->pos++; return lst; }
|
||||||
|
iason_die("IASON_LEGE: expected , or ] in array");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static CentValue iason_parse_object(IasonParser *p) {
|
||||||
|
iason_expect(p, '{', "IASON_LEGE: expected {");
|
||||||
|
iason_skip_ws(p);
|
||||||
|
CentValue d = cent_dict_new(4);
|
||||||
|
if (iason_peek(p) == '}') { p->pos++; return d; }
|
||||||
|
for (;;) {
|
||||||
|
iason_skip_ws(p);
|
||||||
|
if (iason_peek(p) != '"') iason_die("IASON_LEGE: object key must be a string");
|
||||||
|
char *key = iason_parse_string(p);
|
||||||
|
iason_skip_ws(p);
|
||||||
|
iason_expect(p, ':', "IASON_LEGE: expected : after object key");
|
||||||
|
iason_skip_ws(p);
|
||||||
|
CentValue val = iason_parse_value(p);
|
||||||
|
cent_dict_set(&d, cent_str(key), val);
|
||||||
|
iason_skip_ws(p);
|
||||||
|
int c = iason_peek(p);
|
||||||
|
if (c == ',') { p->pos++; continue; }
|
||||||
|
if (c == '}') { p->pos++; return d; }
|
||||||
|
iason_die("IASON_LEGE: expected , or } in object");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static CentValue iason_parse_value(IasonParser *p) {
|
||||||
|
iason_skip_ws(p);
|
||||||
|
int c = iason_peek(p);
|
||||||
|
if (c < 0) iason_die("IASON_LEGE: unexpected end of input");
|
||||||
|
if (c == '{') return iason_parse_object(p);
|
||||||
|
if (c == '[') return iason_parse_array(p);
|
||||||
|
if (c == '"') return cent_str(iason_parse_string(p));
|
||||||
|
if (c == 't') { iason_expect_word(p, "true"); return cent_bool(1); }
|
||||||
|
if (c == 'f') { iason_expect_word(p, "false"); return cent_bool(0); }
|
||||||
|
if (c == 'n') { iason_expect_word(p, "null"); return cent_null(); }
|
||||||
|
if (c == '-' || (c >= '0' && c <= '9')) return iason_parse_number(p);
|
||||||
|
iason_die("IASON_LEGE: unexpected character");
|
||||||
|
return cent_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
CentValue cent_iason_lege(CentValue s, int fractio_loaded) {
|
||||||
|
if (s.type != CENT_STR) cent_type_error("IASON_LEGE requires a string");
|
||||||
|
IasonParser p = { s.sval, 0, strlen(s.sval), fractio_loaded };
|
||||||
|
CentValue v = iason_parse_value(&p);
|
||||||
|
iason_skip_ws(&p);
|
||||||
|
if (p.pos != p.len) iason_die("IASON_LEGE: trailing data after JSON value");
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- serializer --------------------------------------------- */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
size_t cap;
|
||||||
|
} IasonBuf;
|
||||||
|
|
||||||
|
static void iason_buf_reserve(IasonBuf *b, size_t extra) {
|
||||||
|
if (b->len + extra <= b->cap) return;
|
||||||
|
size_t new_cap = b->cap ? b->cap * 2 : 64;
|
||||||
|
while (new_cap < b->len + extra) new_cap *= 2;
|
||||||
|
char *nb = cent_arena_alloc(cent_arena, new_cap);
|
||||||
|
if (b->len) memcpy(nb, b->buf, b->len);
|
||||||
|
b->buf = nb;
|
||||||
|
b->cap = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iason_buf_putc(IasonBuf *b, char c) {
|
||||||
|
iason_buf_reserve(b, 1);
|
||||||
|
b->buf[b->len++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iason_buf_puts(IasonBuf *b, const char *s) {
|
||||||
|
size_t n = strlen(s);
|
||||||
|
iason_buf_reserve(b, n);
|
||||||
|
memcpy(b->buf + b->len, s, n);
|
||||||
|
b->len += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iason_buf_putn(IasonBuf *b, const char *s, size_t n) {
|
||||||
|
iason_buf_reserve(b, n);
|
||||||
|
memcpy(b->buf + b->len, s, n);
|
||||||
|
b->len += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iason_emit_string(IasonBuf *b, const char *s) {
|
||||||
|
iason_buf_putc(b, '"');
|
||||||
|
for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
|
||||||
|
unsigned char c = *p;
|
||||||
|
switch (c) {
|
||||||
|
case '"': iason_buf_puts(b, "\\\""); break;
|
||||||
|
case '\\': iason_buf_puts(b, "\\\\"); break;
|
||||||
|
case '\b': iason_buf_puts(b, "\\b"); break;
|
||||||
|
case '\f': iason_buf_puts(b, "\\f"); break;
|
||||||
|
case '\n': iason_buf_puts(b, "\\n"); break;
|
||||||
|
case '\r': iason_buf_puts(b, "\\r"); break;
|
||||||
|
case '\t': iason_buf_puts(b, "\\t"); break;
|
||||||
|
default:
|
||||||
|
if (c < 0x20) {
|
||||||
|
char tmp[8];
|
||||||
|
snprintf(tmp, sizeof tmp, "\\u%04x", c);
|
||||||
|
iason_buf_puts(b, tmp);
|
||||||
|
} else {
|
||||||
|
iason_buf_putc(b, (char)c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iason_buf_putc(b, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iason_emit_value(IasonBuf *b, CentValue v) {
|
||||||
|
switch (v.type) {
|
||||||
|
case CENT_NULL: iason_buf_puts(b, "null"); return;
|
||||||
|
case CENT_BOOL: iason_buf_puts(b, v.bval ? "true" : "false"); return;
|
||||||
|
case CENT_INT: {
|
||||||
|
char tmp[32];
|
||||||
|
int n = snprintf(tmp, sizeof tmp, "%ld", v.ival);
|
||||||
|
iason_buf_putn(b, tmp, (size_t)n);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case CENT_FRAC: {
|
||||||
|
double d = (double)v.fval.num / (double)v.fval.den;
|
||||||
|
/* Shortest round-trippable representation, like Python's float repr. */
|
||||||
|
char tmp[64];
|
||||||
|
int n = 0;
|
||||||
|
for (int prec = 15; prec <= 17; prec++) {
|
||||||
|
n = snprintf(tmp, sizeof tmp, "%.*g", prec, d);
|
||||||
|
if (strtod(tmp, NULL) == d) break;
|
||||||
|
}
|
||||||
|
iason_buf_putn(b, tmp, (size_t)n);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case CENT_STR: iason_emit_string(b, v.sval); return;
|
||||||
|
case CENT_LIST: {
|
||||||
|
iason_buf_putc(b, '[');
|
||||||
|
for (int i = 0; i < v.lval.len; i++) {
|
||||||
|
if (i > 0) iason_buf_puts(b, ", ");
|
||||||
|
iason_emit_value(b, v.lval.items[i]);
|
||||||
|
}
|
||||||
|
iason_buf_putc(b, ']');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case CENT_DICT: {
|
||||||
|
iason_buf_putc(b, '{');
|
||||||
|
for (int i = 0; i < v.dval.len; i++) {
|
||||||
|
if (i > 0) iason_buf_puts(b, ", ");
|
||||||
|
CentValue k = v.dval.keys[i];
|
||||||
|
if (k.type != CENT_STR)
|
||||||
|
cent_runtime_error("IASON_SCRIBE: dict keys must be strings to serialize as JSON");
|
||||||
|
iason_emit_string(b, k.sval);
|
||||||
|
iason_buf_puts(b, ": ");
|
||||||
|
iason_emit_value(b, v.dval.vals[i]);
|
||||||
|
}
|
||||||
|
iason_buf_putc(b, '}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case CENT_FUNC:
|
||||||
|
cent_runtime_error("IASON_SCRIBE: cannot serialize a function");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CentValue cent_iason_scribe(CentValue v) {
|
||||||
|
IasonBuf b = { NULL, 0, 0 };
|
||||||
|
iason_emit_value(&b, v);
|
||||||
|
iason_buf_putc(&b, '\0');
|
||||||
|
return cent_str(b.buf);
|
||||||
|
}
|
||||||
14
centvrion/compiler/runtime/cent_iason.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef CENT_IASON_H
|
||||||
|
#define CENT_IASON_H
|
||||||
|
|
||||||
|
#include "cent_runtime.h"
|
||||||
|
|
||||||
|
/* IASON_LEGE — parse a JSON string into a CENTVRION value tree.
|
||||||
|
When fractio_loaded != 0, JSON floats become exact fractions; otherwise
|
||||||
|
they are floored to ints. */
|
||||||
|
CentValue cent_iason_lege(CentValue s, int fractio_loaded);
|
||||||
|
|
||||||
|
/* IASON_SCRIBE — serialize a CENTVRION value to a JSON string. */
|
||||||
|
CentValue cent_iason_scribe(CentValue v);
|
||||||
|
|
||||||
|
#endif /* CENT_IASON_H */
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Types */
|
/* Types */
|
||||||
@@ -14,11 +15,24 @@ typedef enum {
|
|||||||
CENT_BOOL,
|
CENT_BOOL,
|
||||||
CENT_LIST,
|
CENT_LIST,
|
||||||
CENT_FRAC,
|
CENT_FRAC,
|
||||||
|
CENT_DICT,
|
||||||
|
CENT_FUNC,
|
||||||
CENT_NULL
|
CENT_NULL
|
||||||
} CentType;
|
} CentType;
|
||||||
|
|
||||||
typedef struct CentValue CentValue;
|
typedef struct CentValue CentValue;
|
||||||
typedef struct CentList CentList;
|
typedef struct CentList CentList;
|
||||||
|
typedef struct CentDict CentDict;
|
||||||
|
struct CentScope; /* forward declaration */
|
||||||
|
|
||||||
|
/* First-class function value */
|
||||||
|
typedef CentValue (*CentFuncPtr)(struct CentScope);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
CentFuncPtr fn;
|
||||||
|
const char **param_names;
|
||||||
|
int param_count;
|
||||||
|
} CentFuncInfo;
|
||||||
|
|
||||||
/* Duodecimal fraction: num/den stored as exact integers */
|
/* Duodecimal fraction: num/den stored as exact integers */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -32,6 +46,16 @@ struct CentList {
|
|||||||
int cap;
|
int cap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CentDict {
|
||||||
|
CentValue *keys; /* insertion-order array, len entries */
|
||||||
|
CentValue *vals; /* parallel to keys */
|
||||||
|
int *buckets; /* hash table; values are indices into */
|
||||||
|
/* keys/vals, or -1 for empty */
|
||||||
|
int len; /* number of entries */
|
||||||
|
int cap; /* capacity of keys/vals */
|
||||||
|
int nbuckets; /* size of buckets, power of 2 */
|
||||||
|
};
|
||||||
|
|
||||||
struct CentValue {
|
struct CentValue {
|
||||||
CentType type;
|
CentType type;
|
||||||
union {
|
union {
|
||||||
@@ -39,13 +63,15 @@ struct CentValue {
|
|||||||
char *sval; /* CENT_STR */
|
char *sval; /* CENT_STR */
|
||||||
int bval; /* CENT_BOOL */
|
int bval; /* CENT_BOOL */
|
||||||
CentList lval; /* CENT_LIST */
|
CentList lval; /* CENT_LIST */
|
||||||
CentFrac fval; /* CENT_FRAC */
|
CentFrac fval; /* CENT_FRAC */
|
||||||
|
CentDict dval; /* CENT_DICT */
|
||||||
|
CentFuncInfo fnval; /* CENT_FUNC */
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Scope: flat name→value array. Stack-allocated by the caller;
|
/* Scope: flat name→value array. Stack-allocated by the caller;
|
||||||
cent_scope_set uses cent_arena when it needs to grow. */
|
cent_scope_set uses cent_arena when it needs to grow. */
|
||||||
typedef struct {
|
typedef struct CentScope {
|
||||||
const char **names;
|
const char **names;
|
||||||
CentValue *vals;
|
CentValue *vals;
|
||||||
int len;
|
int len;
|
||||||
@@ -74,6 +100,9 @@ extern CentArena *cent_arena;
|
|||||||
/* Set to 1 when CVM MAGNVM is active; enables extended numeral display */
|
/* Set to 1 when CVM MAGNVM is active; enables extended numeral display */
|
||||||
extern int cent_magnvm;
|
extern int cent_magnvm;
|
||||||
|
|
||||||
|
/* Set to 1 when CVM SVBNVLLA is active; enables negative number display */
|
||||||
|
extern int cent_svbnvlla;
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Value constructors */
|
/* Value constructors */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -101,13 +130,42 @@ static inline CentValue cent_list(CentValue *items, int len, int cap) {
|
|||||||
r.lval.cap = cap;
|
r.lval.cap = cap;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
static inline CentValue cent_func_val(CentFuncPtr fn, const char **param_names, int param_count) {
|
||||||
|
CentValue r;
|
||||||
|
r.type = CENT_FUNC;
|
||||||
|
r.fnval.fn = fn;
|
||||||
|
r.fnval.param_names = param_names;
|
||||||
|
r.fnval.param_count = param_count;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
static inline CentValue cent_dict_val(CentValue *keys, CentValue *vals,
|
||||||
|
int *buckets, int len, int cap,
|
||||||
|
int nbuckets) {
|
||||||
|
CentValue r;
|
||||||
|
r.type = CENT_DICT;
|
||||||
|
r.dval.keys = keys;
|
||||||
|
r.dval.vals = vals;
|
||||||
|
r.dval.buckets = buckets;
|
||||||
|
r.dval.len = len;
|
||||||
|
r.dval.cap = cap;
|
||||||
|
r.dval.nbuckets = nbuckets;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Error handling */
|
/* Error handling */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
void cent_type_error(const char *msg); /* type mismatch → exit(1) */
|
#define CENT_TRY_STACK_MAX 64
|
||||||
void cent_runtime_error(const char *msg); /* runtime fault → exit(1) */
|
extern jmp_buf _cent_try_stack[];
|
||||||
|
extern int _cent_try_depth;
|
||||||
|
extern const char *_cent_error_msg;
|
||||||
|
|
||||||
|
/* Updated at the start of every emitted statement; 0 means "no line known". */
|
||||||
|
extern int _cent_current_line;
|
||||||
|
|
||||||
|
void cent_type_error(const char *msg); /* type mismatch → longjmp or exit(1) */
|
||||||
|
void cent_runtime_error(const char *msg); /* runtime fault → longjmp or exit(1) */
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Truthiness — conditions must be booleans; anything else is a fault */
|
/* Truthiness — conditions must be booleans; anything else is a fault */
|
||||||
@@ -152,15 +210,22 @@ char *cent_make_string(CentValue v);
|
|||||||
/* Arithmetic and comparison operators */
|
/* Arithmetic and comparison operators */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
CentValue cent_neg(CentValue v); /* unary minus: INT or FRAC */
|
||||||
CentValue cent_add(CentValue a, CentValue b); /* INT+INT or FRAC+FRAC/INT */
|
CentValue cent_add(CentValue a, CentValue b); /* INT+INT or FRAC+FRAC/INT */
|
||||||
|
CentValue cent_array_concat(CentValue a, CentValue b); /* @ operator: concatenate two arrays */
|
||||||
CentValue cent_concat(CentValue a, CentValue b); /* & operator: coerce all types to str */
|
CentValue cent_concat(CentValue a, CentValue b); /* & operator: coerce all types to str */
|
||||||
CentValue cent_sub(CentValue a, CentValue b); /* INT-INT or FRAC-FRAC/INT */
|
CentValue cent_sub(CentValue a, CentValue b); /* INT-INT or FRAC-FRAC/INT */
|
||||||
CentValue cent_mul(CentValue a, CentValue b); /* INT*INT or FRAC*FRAC/INT */
|
CentValue cent_mul(CentValue a, CentValue b); /* INT*INT or FRAC*FRAC/INT */
|
||||||
CentValue cent_div(CentValue a, CentValue b); /* INT/INT integer div */
|
CentValue cent_div(CentValue a, CentValue b); /* INT/INT integer div */
|
||||||
CentValue cent_div_frac(CentValue a, CentValue b); /* FRACTIO: exact div → FRAC */
|
CentValue cent_div_frac(CentValue a, CentValue b); /* FRACTIO: exact div → FRAC */
|
||||||
|
CentValue cent_mod(CentValue a, CentValue b); /* INT%INT integer modulo */
|
||||||
|
CentValue cent_mod_frac(CentValue a, CentValue b); /* FRACTIO: floored mod → FRAC */
|
||||||
CentValue cent_eq (CentValue a, CentValue b); /* EST → BOOL */
|
CentValue cent_eq (CentValue a, CentValue b); /* EST → BOOL */
|
||||||
|
CentValue cent_neq(CentValue a, CentValue b); /* DISPAR → BOOL */
|
||||||
CentValue cent_lt (CentValue a, CentValue b); /* MINVS → BOOL */
|
CentValue cent_lt (CentValue a, CentValue b); /* MINVS → BOOL */
|
||||||
CentValue cent_gt (CentValue a, CentValue b); /* PLVS → BOOL */
|
CentValue cent_gt (CentValue a, CentValue b); /* PLVS → BOOL */
|
||||||
|
CentValue cent_lte(CentValue a, CentValue b); /* HAVD_PLVS → BOOL */
|
||||||
|
CentValue cent_gte(CentValue a, CentValue b); /* HAVD_MINVS → BOOL */
|
||||||
CentValue cent_and(CentValue a, CentValue b); /* ET → BOOL */
|
CentValue cent_and(CentValue a, CentValue b); /* ET → BOOL */
|
||||||
CentValue cent_or (CentValue a, CentValue b); /* AVT → BOOL */
|
CentValue cent_or (CentValue a, CentValue b); /* AVT → BOOL */
|
||||||
|
|
||||||
@@ -168,13 +233,41 @@ CentValue cent_or (CentValue a, CentValue b); /* AVT → BOOL */
|
|||||||
/* Builtin functions */
|
/* Builtin functions */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
void cent_dice(CentValue v); /* DICE */
|
void cent_dic(CentValue v); /* DIC */
|
||||||
CentValue cent_avdi(void); /* AVDI */
|
CentValue cent_avdi(void); /* AVDI */
|
||||||
CentValue cent_avdi_numerus(void); /* AVDI_NVMERVS */
|
CentValue cent_avdi_numerus(void); /* AVDI_NVMERVS */
|
||||||
CentValue cent_longitudo(CentValue v); /* LONGITVDO */
|
CentValue cent_longitudo(CentValue v); /* LONGITVDO */
|
||||||
CentValue cent_fortis_numerus(CentValue lo, CentValue hi); /* FORTIS_NVMERVS */
|
CentValue cent_littera(CentValue v); /* LITTERA */
|
||||||
CentValue cent_fortis_electionis(CentValue lst); /* FORTIS_ELECTIONIS */
|
CentValue cent_maivscvla(CentValue v); /* MAIVSCVLA */
|
||||||
void cent_everro(void); /* EVERRO */
|
CentValue cent_minvscvla(CentValue v); /* MINVSCVLA */
|
||||||
|
CentValue cent_fortuitus_numerus(CentValue lo, CentValue hi); /* FORTVITVS_NVMERVS */
|
||||||
|
CentValue cent_fortuita_electionis(CentValue lst); /* FORTVITA_ELECTIO */
|
||||||
|
CentValue cent_decimatio(CentValue lst); /* DECIMATIO */
|
||||||
|
void cent_semen(CentValue seed); /* SEMEN */
|
||||||
|
void cent_everre(void); /* EVERRE */
|
||||||
|
CentValue cent_senatus(CentValue *args, int n); /* SENATVS */
|
||||||
|
CentValue cent_typvs(CentValue v); /* TYPVS */
|
||||||
|
void cent_dormi(CentValue n); /* DORMI */
|
||||||
|
CentValue cent_ordina(CentValue lst); /* ORDINA */
|
||||||
|
CentValue cent_ordina_cmp(CentValue lst, CentValue cmp, CentScope scope); /* ORDINA w/ comparator */
|
||||||
|
CentValue cent_mvta(CentValue lst, CentValue fn, CentScope scope); /* MVTA */
|
||||||
|
CentValue cent_cribra(CentValue lst, CentValue fn, CentScope scope); /* CRIBRA */
|
||||||
|
CentValue cent_confla(CentValue lst, CentValue init, CentValue fn, CentScope scope); /* CONFLA */
|
||||||
|
CentValue cent_adde(CentValue lst, CentValue v); /* ADDE */
|
||||||
|
CentValue cent_tolle(CentValue lst, CentValue idx); /* TOLLE */
|
||||||
|
CentValue cent_insere(CentValue lst, CentValue idx, CentValue v); /* INSERE */
|
||||||
|
CentValue cent_necte(CentValue a, CentValue b); /* NECTE */
|
||||||
|
CentValue cent_ivnge(CentValue keys, CentValue vals); /* IVNGE */
|
||||||
|
CentValue cent_lege(CentValue path); /* LEGE */
|
||||||
|
void cent_scribe(CentValue path, CentValue content); /* SCRIBE */
|
||||||
|
void cent_adivnge(CentValue path, CentValue content); /* ADIVNGE */
|
||||||
|
CentValue cent_numerus(CentValue s); /* NVMERVS */
|
||||||
|
CentValue cent_qvaere(CentValue pattern, CentValue text); /* QVAERE */
|
||||||
|
CentValue cent_svbstitve(CentValue pattern, CentValue replacement, CentValue text); /* SVBSTITVE */
|
||||||
|
CentValue cent_scinde(CentValue str, CentValue delim); /* SCINDE */
|
||||||
|
CentValue cent_pete(CentValue url); /* PETE */
|
||||||
|
void cent_petitvr(CentValue path, CentValue handler, CentScope scope); /* PETITVR */
|
||||||
|
void cent_avscvlta(CentValue port); /* AVSCVLTA */
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Array helpers */
|
/* Array helpers */
|
||||||
@@ -183,8 +276,18 @@ void cent_everro(void); /* EVERRO */
|
|||||||
CentValue cent_list_new(int cap);
|
CentValue cent_list_new(int cap);
|
||||||
void cent_list_push(CentValue *lst, CentValue v);
|
void cent_list_push(CentValue *lst, CentValue v);
|
||||||
CentValue cent_list_index(CentValue lst, CentValue idx); /* 1-based */
|
CentValue cent_list_index(CentValue lst, CentValue idx); /* 1-based */
|
||||||
|
CentValue cent_list_slice(CentValue lst, CentValue lo, CentValue hi); /* 1-based, inclusive */
|
||||||
void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v);
|
void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v);
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Dict helpers */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
CentValue cent_dict_new(int cap);
|
||||||
|
void cent_dict_set(CentValue *dict, CentValue key, CentValue val);
|
||||||
|
CentValue cent_dict_get(CentValue dict, CentValue key);
|
||||||
|
CentValue cent_dict_keys(CentValue dict);
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Initialisation */
|
/* Initialisation */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|||||||
@@ -1 +1,13 @@
|
|||||||
class CentvrionError(Exception): pass
|
class CentvrionError(Exception):
|
||||||
|
def __init__(self, msg, lineno=None, colno=None):
|
||||||
|
self.msg = msg
|
||||||
|
self.lineno = lineno
|
||||||
|
self.colno = colno
|
||||||
|
super().__init__(msg)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.lineno is None:
|
||||||
|
return self.msg
|
||||||
|
if self.colno is None:
|
||||||
|
return f"{self.msg} at line {self.lineno}"
|
||||||
|
return f"{self.msg} at line {self.lineno}, column {self.colno}"
|
||||||
|
|||||||
@@ -3,28 +3,42 @@ from rply import LexerGenerator
|
|||||||
valid_characters = '|'.join(list("abcdefghiklmnopqrstvxyz_"))
|
valid_characters = '|'.join(list("abcdefghiklmnopqrstvxyz_"))
|
||||||
|
|
||||||
keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
||||||
"ALVID",
|
"AETERNVM",
|
||||||
|
"ALIVD",
|
||||||
|
"AVGE",
|
||||||
|
"CAPE",
|
||||||
"AVT",
|
"AVT",
|
||||||
"DEFINI",
|
"DEFINI",
|
||||||
"DESIGNA",
|
"DESIGNA",
|
||||||
|
"DISPAR",
|
||||||
|
"DIVIDE",
|
||||||
"DONICVM",
|
"DONICVM",
|
||||||
"DVM",
|
"DVM",
|
||||||
"CONTINVA",
|
"CONTINVA",
|
||||||
"ERVMPE",
|
"ERVMPE",
|
||||||
"EST",
|
"EST",
|
||||||
"ET",
|
"ET",
|
||||||
"FACE",
|
"FAC",
|
||||||
"FALSITAS",
|
"FALSITAS",
|
||||||
|
"FVNCTIO",
|
||||||
|
"GRADV",
|
||||||
|
"HAVD_MINVS",
|
||||||
|
"HAVD_PLVS",
|
||||||
"INVOCA",
|
"INVOCA",
|
||||||
"IN",
|
"IN",
|
||||||
|
"MINVE",
|
||||||
"MINVS",
|
"MINVS",
|
||||||
|
"MVLTIPLICA",
|
||||||
"NON",
|
"NON",
|
||||||
"NVLLVS",
|
"NVLLVS",
|
||||||
"PER",
|
"PER",
|
||||||
"PLVS",
|
"PLVS",
|
||||||
"REDI",
|
"REDI",
|
||||||
|
"RELIQVVM",
|
||||||
"SI",
|
"SI",
|
||||||
"TVNC",
|
"TVNC",
|
||||||
|
"TABVLA",
|
||||||
|
"TEMPTA",
|
||||||
"VSQVE",
|
"VSQVE",
|
||||||
"VT",
|
"VT",
|
||||||
"VERITAS",
|
"VERITAS",
|
||||||
@@ -32,17 +46,47 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
builtin_tokens = [("BUILTIN", i) for i in [
|
builtin_tokens = [("BUILTIN", i) for i in [
|
||||||
|
"ADDE",
|
||||||
"AVDI_NVMERVS",
|
"AVDI_NVMERVS",
|
||||||
"AVDI",
|
"AVDI",
|
||||||
"DICE",
|
"CLAVES",
|
||||||
"EVERRO",
|
"CONFLA",
|
||||||
"FORTIS_NVMERVS",
|
"CRIBRA",
|
||||||
"FORTIS_ELECTIONIS",
|
"DECIMATIO",
|
||||||
"LONGITVDO"
|
"DIC",
|
||||||
|
"DORMI",
|
||||||
|
"EVERRE",
|
||||||
|
"FORTVITVS_NVMERVS",
|
||||||
|
"FORTVITA_ELECTIO",
|
||||||
|
"INSERE",
|
||||||
|
"IVNGE",
|
||||||
|
"LITTERA",
|
||||||
|
"LONGITVDO",
|
||||||
|
"MAIVSCVLA",
|
||||||
|
"MINVSCVLA",
|
||||||
|
"MVTA",
|
||||||
|
"NECTE",
|
||||||
|
"NVMERVS",
|
||||||
|
"ORDINA",
|
||||||
|
"SEMEN",
|
||||||
|
"SENATVS",
|
||||||
|
"TOLLE",
|
||||||
|
"TYPVS",
|
||||||
|
"LEGE",
|
||||||
|
"SCRIBE",
|
||||||
|
"ADIVNGE",
|
||||||
|
"QVAERE",
|
||||||
|
"SVBSTITVE",
|
||||||
|
"SCINDE",
|
||||||
|
"PETE",
|
||||||
|
"PETITVR",
|
||||||
|
"AVSCVLTA",
|
||||||
|
"IASON_LEGE",
|
||||||
|
"IASON_SCRIBE"
|
||||||
]]
|
]]
|
||||||
|
|
||||||
data_tokens = [
|
data_tokens = [
|
||||||
("DATA_STRING", r"(\".*?\"|'.*?')"),
|
("DATA_STRING", r'("(?:[^"\\]|\\.)*"|' + r"'(?:[^'\\]|\\.)*')"),
|
||||||
("DATA_FRACTION", r"([IVXLCDM][IVXLCDM_]*)?([S][S:.|]*|:[S:.|]+|\.[S:.|]*)"),
|
("DATA_FRACTION", r"([IVXLCDM][IVXLCDM_]*)?([S][S:.|]*|:[S:.|]+|\.[S:.|]*)"),
|
||||||
("DATA_NUMERAL", r"[IVXLCDM][IVXLCDM_]*")
|
("DATA_NUMERAL", r"[IVXLCDM][IVXLCDM_]*")
|
||||||
]
|
]
|
||||||
@@ -50,8 +94,11 @@ data_tokens = [
|
|||||||
module_tokens = [("MODULE", i) for i in [
|
module_tokens = [("MODULE", i) for i in [
|
||||||
"FORS",
|
"FORS",
|
||||||
"FRACTIO",
|
"FRACTIO",
|
||||||
|
"IASON",
|
||||||
"MAGNVM",
|
"MAGNVM",
|
||||||
"SVBNVLLA"
|
"SCRIPTA",
|
||||||
|
"SVBNVLLA",
|
||||||
|
"RETE"
|
||||||
]]
|
]]
|
||||||
|
|
||||||
symbol_tokens = [
|
symbol_tokens = [
|
||||||
@@ -66,6 +113,7 @@ symbol_tokens = [
|
|||||||
("SYMBOL_TIMES", r"\*"),
|
("SYMBOL_TIMES", r"\*"),
|
||||||
("SYMBOL_DIVIDE", r"\/"),
|
("SYMBOL_DIVIDE", r"\/"),
|
||||||
("SYMBOL_AMPERSAND", r"&"),
|
("SYMBOL_AMPERSAND", r"&"),
|
||||||
|
("SYMBOL_AT", r"@"),
|
||||||
("SYMBOL_COMMA", r",")
|
("SYMBOL_COMMA", r",")
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -74,8 +122,8 @@ whitespace_tokens = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
all_tokens = (
|
all_tokens = (
|
||||||
keyword_tokens +
|
|
||||||
builtin_tokens +
|
builtin_tokens +
|
||||||
|
keyword_tokens +
|
||||||
module_tokens +
|
module_tokens +
|
||||||
data_tokens +
|
data_tokens +
|
||||||
symbol_tokens +
|
symbol_tokens +
|
||||||
|
|||||||
@@ -1,10 +1,136 @@
|
|||||||
from rply import ParserGenerator
|
from rply import ParserGenerator
|
||||||
|
|
||||||
from centvrion.lexer import all_tokens
|
from centvrion.errors import CentvrionError
|
||||||
|
from centvrion.lexer import Lexer, all_tokens
|
||||||
from . import ast_nodes
|
from . import ast_nodes
|
||||||
|
|
||||||
ALL_TOKENS = list(set([i[0] for i in all_tokens]))
|
ALL_TOKENS = list(set([i[0] for i in all_tokens]))
|
||||||
|
|
||||||
|
|
||||||
|
_ESCAPE_MAP = {
|
||||||
|
'n': '\n',
|
||||||
|
't': '\t',
|
||||||
|
'r': '\r',
|
||||||
|
'\\': '\\',
|
||||||
|
'"': '"',
|
||||||
|
"'": "'",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _read_escape(s, i):
|
||||||
|
"""Read a backslash escape at position i (the backslash). Returns (char, new_i)."""
|
||||||
|
if i + 1 >= len(s):
|
||||||
|
raise CentvrionError("Trailing backslash in string")
|
||||||
|
nxt = s[i + 1]
|
||||||
|
if nxt in _ESCAPE_MAP:
|
||||||
|
return _ESCAPE_MAP[nxt], i + 2
|
||||||
|
# unknown escapes pass through literally (e.g. \1 for regex backrefs)
|
||||||
|
return '\\' + nxt, i + 2
|
||||||
|
|
||||||
|
|
||||||
|
def _unescape(s):
|
||||||
|
"""Process escape sequences in a string with no interpolation."""
|
||||||
|
out = []
|
||||||
|
i = 0
|
||||||
|
while i < len(s):
|
||||||
|
if s[i] == '\\':
|
||||||
|
ch, i = _read_escape(s, i)
|
||||||
|
out.append(ch)
|
||||||
|
else:
|
||||||
|
out.append(s[i])
|
||||||
|
i += 1
|
||||||
|
return ''.join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def _at(node, src):
|
||||||
|
"""Stamp a (lineno, colno) onto a freshly built AST node.
|
||||||
|
|
||||||
|
`src` can be an rply Token (uses .source_pos) or another Node (copies .pos).
|
||||||
|
"""
|
||||||
|
if src is None:
|
||||||
|
return node
|
||||||
|
pos = getattr(src, "pos", None)
|
||||||
|
if pos is not None:
|
||||||
|
node.pos = pos
|
||||||
|
return node
|
||||||
|
sp = getattr(src, "source_pos", None)
|
||||||
|
if sp is not None:
|
||||||
|
node.pos = (sp.lineno, sp.colno)
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_interpolated(raw_value, source_pos=None):
|
||||||
|
lineno = source_pos.lineno if source_pos is not None else None
|
||||||
|
colno = source_pos.colno if source_pos is not None else None
|
||||||
|
|
||||||
|
def _err(msg):
|
||||||
|
return CentvrionError(msg, lineno, colno)
|
||||||
|
|
||||||
|
quote_char = raw_value[0]
|
||||||
|
inner = raw_value[1:-1]
|
||||||
|
|
||||||
|
if len(inner) == 0:
|
||||||
|
return ast_nodes.String(inner)
|
||||||
|
|
||||||
|
if quote_char == "'":
|
||||||
|
return ast_nodes.String(_unescape(inner))
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
i = 0
|
||||||
|
current = []
|
||||||
|
|
||||||
|
while i < len(inner):
|
||||||
|
ch = inner[i]
|
||||||
|
if ch == '\\':
|
||||||
|
c, i = _read_escape(inner, i)
|
||||||
|
current.append(c)
|
||||||
|
continue
|
||||||
|
if ch == '{':
|
||||||
|
if i + 1 < len(inner) and inner[i + 1] == '{':
|
||||||
|
current.append('{')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if current:
|
||||||
|
parts.append(ast_nodes.String(''.join(current)))
|
||||||
|
current = []
|
||||||
|
j = i + 1
|
||||||
|
depth = 1
|
||||||
|
while j < len(inner) and depth > 0:
|
||||||
|
if inner[j] == '{':
|
||||||
|
depth += 1
|
||||||
|
elif inner[j] == '}':
|
||||||
|
depth -= 1
|
||||||
|
j += 1
|
||||||
|
if depth != 0:
|
||||||
|
raise _err("Unclosed '{' in interpolated string")
|
||||||
|
expr_src = inner[i + 1:j - 1]
|
||||||
|
tokens = Lexer().get_lexer().lex(expr_src + "\n")
|
||||||
|
program = Parser().parse(tokens)
|
||||||
|
if len(program.statements) != 1:
|
||||||
|
raise _err("Interpolation must contain exactly one expression")
|
||||||
|
stmt = program.statements[0]
|
||||||
|
if not isinstance(stmt, ast_nodes.ExpressionStatement):
|
||||||
|
raise _err("Interpolation must contain an expression, not a statement")
|
||||||
|
parts.append(stmt.expression)
|
||||||
|
i = j
|
||||||
|
elif ch == '}':
|
||||||
|
if i + 1 < len(inner) and inner[i + 1] == '}':
|
||||||
|
current.append('}')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
raise _err("Unmatched '}' in string (use '}}' for literal '}')")
|
||||||
|
else:
|
||||||
|
current.append(ch)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if current:
|
||||||
|
parts.append(ast_nodes.String(''.join(current)))
|
||||||
|
|
||||||
|
if len(parts) == 1 and isinstance(parts[0], ast_nodes.String):
|
||||||
|
return parts[0]
|
||||||
|
|
||||||
|
return ast_nodes.InterpolatedString(parts)
|
||||||
|
|
||||||
class Parser():
|
class Parser():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.pg = ParserGenerator(
|
self.pg = ParserGenerator(
|
||||||
@@ -12,9 +138,10 @@ class Parser():
|
|||||||
precedence=[
|
precedence=[
|
||||||
('left', ["KEYWORD_AVT"]),
|
('left', ["KEYWORD_AVT"]),
|
||||||
('left', ["KEYWORD_ET"]),
|
('left', ["KEYWORD_ET"]),
|
||||||
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST"]),
|
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST", "KEYWORD_DISPAR",
|
||||||
('left', ["SYMBOL_AMPERSAND", "SYMBOL_PLUS", "SYMBOL_MINUS"]),
|
"KEYWORD_HAVD_PLVS", "KEYWORD_HAVD_MINVS"]),
|
||||||
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"]),
|
('left', ["SYMBOL_AMPERSAND", "SYMBOL_AT", "SYMBOL_PLUS", "SYMBOL_MINUS"]),
|
||||||
|
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE", "KEYWORD_RELIQVVM"]),
|
||||||
('right', ["UMINUS", "UNOT"]),
|
('right', ["UMINUS", "UNOT"]),
|
||||||
('left', ["SYMBOL_LBRACKET", "INDEX"]),
|
('left', ["SYMBOL_LBRACKET", "INDEX"]),
|
||||||
]
|
]
|
||||||
@@ -50,7 +177,7 @@ class Parser():
|
|||||||
|
|
||||||
@self.pg.production('module_call : KEYWORD_CVM MODULE')
|
@self.pg.production('module_call : KEYWORD_CVM MODULE')
|
||||||
def module_call(tokens):
|
def module_call(tokens):
|
||||||
return ast_nodes.ModuleCall(tokens[1].value)
|
return _at(ast_nodes.ModuleCall(tokens[1].value), tokens[0])
|
||||||
|
|
||||||
|
|
||||||
# Statements
|
# Statements
|
||||||
@@ -68,67 +195,114 @@ class Parser():
|
|||||||
|
|
||||||
@self.pg.production('statement : KEYWORD_DESIGNA id KEYWORD_VT expression')
|
@self.pg.production('statement : KEYWORD_DESIGNA id KEYWORD_VT expression')
|
||||||
def statement_designa(tokens):
|
def statement_designa(tokens):
|
||||||
return ast_nodes.Designa(tokens[1], tokens[3])
|
return _at(ast_nodes.Designa(tokens[1], tokens[3]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('statement : KEYWORD_DESIGNA id SYMBOL_LBRACKET expression SYMBOL_RBRACKET KEYWORD_VT expression')
|
@self.pg.production('index_chain : SYMBOL_LBRACKET expression SYMBOL_RBRACKET')
|
||||||
|
def index_chain_single(tokens):
|
||||||
|
return [tokens[1]]
|
||||||
|
|
||||||
|
@self.pg.production('index_chain : SYMBOL_LBRACKET expression SYMBOL_RBRACKET index_chain')
|
||||||
|
def index_chain_multi(tokens):
|
||||||
|
return [tokens[1]] + tokens[3]
|
||||||
|
|
||||||
|
@self.pg.production('statement : KEYWORD_DESIGNA id index_chain KEYWORD_VT expression')
|
||||||
def statement_designa_index(tokens):
|
def statement_designa_index(tokens):
|
||||||
return ast_nodes.DesignaIndex(tokens[1], tokens[3], tokens[6])
|
return _at(ast_nodes.DesignaIndex(tokens[1], tokens[2], tokens[4]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('statement : KEYWORD_DESIGNA id SYMBOL_COMMA id_list_rest KEYWORD_VT expression')
|
||||||
|
def statement_designa_destructure(tokens):
|
||||||
|
return _at(ast_nodes.DesignaDestructure([tokens[1]] + tokens[3], tokens[5]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('statement : id KEYWORD_AVGE expression')
|
||||||
|
def statement_avge(tokens):
|
||||||
|
return _at(ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_PLUS")), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('statement : id KEYWORD_MINVE expression')
|
||||||
|
def statement_minve(tokens):
|
||||||
|
return _at(ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_MINUS")), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('statement : id KEYWORD_MVLTIPLICA expression')
|
||||||
|
def statement_mvltiplica(tokens):
|
||||||
|
return _at(ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_TIMES")), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('statement : id KEYWORD_DIVIDE expression')
|
||||||
|
def statement_divide(tokens):
|
||||||
|
return _at(ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_DIVIDE")), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('statement : expression')
|
@self.pg.production('statement : expression')
|
||||||
def statement_expression(tokens):
|
def statement_expression(tokens):
|
||||||
return ast_nodes.ExpressionStatement(tokens[0])
|
return _at(ast_nodes.ExpressionStatement(tokens[0]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('statement : KEYWORD_DEFINI id ids KEYWORD_VT SYMBOL_LCURL statements SYMBOL_RCURL')
|
@self.pg.production('statement : KEYWORD_DEFINI id ids KEYWORD_VT SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
def defini(tokens):
|
def defini(tokens):
|
||||||
return ast_nodes.Defini(tokens[1], tokens[2], tokens[5])
|
return _at(ast_nodes.Defini(tokens[1], tokens[2], tokens[5]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('statement : KEYWORD_REDI expressions')
|
@self.pg.production('statement : KEYWORD_REDI expressions')
|
||||||
def redi(tokens):
|
def redi(tokens):
|
||||||
return ast_nodes.Redi(tokens[1])
|
return _at(ast_nodes.Redi(tokens[1]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('statement : per_statement')
|
@self.pg.production('statement : per_statement')
|
||||||
@self.pg.production('statement : dum_statement')
|
@self.pg.production('statement : dum_statement')
|
||||||
@self.pg.production('statement : donicum_statement')
|
@self.pg.production('statement : donicum_statement')
|
||||||
@self.pg.production('statement : si_statement')
|
@self.pg.production('statement : si_statement')
|
||||||
|
@self.pg.production('statement : tempta_statement')
|
||||||
def nested_statements(tokens):
|
def nested_statements(tokens):
|
||||||
return tokens[0]
|
return tokens[0]
|
||||||
|
|
||||||
@self.pg.production('statement : KEYWORD_ERVMPE')
|
@self.pg.production('statement : KEYWORD_ERVMPE')
|
||||||
def erumpe(_):
|
def erumpe(tokens):
|
||||||
return ast_nodes.Erumpe()
|
return _at(ast_nodes.Erumpe(), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('statement : KEYWORD_CONTINVA')
|
@self.pg.production('statement : KEYWORD_CONTINVA')
|
||||||
def continva(_):
|
def continva(tokens):
|
||||||
return ast_nodes.Continva()
|
return _at(ast_nodes.Continva(), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL statements SYMBOL_RCURL')
|
@self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
@self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL statements SYMBOL_RCURL aluid_statement')
|
@self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL statements SYMBOL_RCURL aluid_statement')
|
||||||
def si_statement(tokens):
|
def si_statement(tokens):
|
||||||
if len(tokens) == 7:
|
if len(tokens) == 7:
|
||||||
return ast_nodes.SiStatement(tokens[1], tokens[4], tokens[6])
|
return _at(ast_nodes.SiStatement(tokens[1], tokens[4], tokens[6]), tokens[0])
|
||||||
else:
|
else:
|
||||||
return ast_nodes.SiStatement(tokens[1], tokens[4], None)
|
return _at(ast_nodes.SiStatement(tokens[1], tokens[4], None), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('aluid_statement : KEYWORD_ALVID si_statement')
|
@self.pg.production('aluid_statement : KEYWORD_ALIVD si_statement')
|
||||||
def aluid_si(tokens):
|
def aluid_si(tokens):
|
||||||
return [tokens[1]]
|
return [tokens[1]]
|
||||||
|
|
||||||
@self.pg.production('aluid_statement : KEYWORD_ALVID SYMBOL_LCURL statements SYMBOL_RCURL')
|
@self.pg.production('aluid_statement : KEYWORD_ALIVD SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
def aluid(tokens):
|
def aluid(tokens):
|
||||||
return tokens[2]
|
return tokens[2]
|
||||||
|
|
||||||
@self.pg.production('dum_statement : KEYWORD_DVM expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
|
@self.pg.production('dum_statement : KEYWORD_DVM expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
def dum(tokens):
|
def dum(tokens):
|
||||||
return ast_nodes.DumStatement(tokens[1], tokens[4])
|
return _at(ast_nodes.DumStatement(tokens[1], tokens[4]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
|
# AETERNVM is sugar for `DVM FALSITAS` — same AST, no observable difference.
|
||||||
|
@self.pg.production('dum_statement : KEYWORD_AETERNVM KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
|
def aeternvm(tokens):
|
||||||
|
return _at(ast_nodes.DumStatement(ast_nodes.Bool(False), tokens[3]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('per_statement : KEYWORD_PER id SYMBOL_COMMA id_list_rest KEYWORD_IN expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
|
def per_destructure(tokens):
|
||||||
|
return _at(ast_nodes.PerStatement(tokens[5], [tokens[1]] + tokens[3], tokens[8]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
def per(tokens):
|
def per(tokens):
|
||||||
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6])
|
return _at(ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
|
@self.pg.production('tempta_statement : KEYWORD_TEMPTA SYMBOL_LCURL statements SYMBOL_RCURL KEYWORD_CAPE id SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
|
def tempta(tokens):
|
||||||
|
return _at(ast_nodes.TemptaStatement(tokens[2], tokens[5], tokens[7]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
def donicum(tokens):
|
def donicum(tokens):
|
||||||
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
|
range_array = _at(ast_nodes.DataRangeArray(tokens[3], tokens[5]), tokens[0])
|
||||||
return ast_nodes.PerStatement(range_array, tokens[1], tokens[8])
|
return _at(ast_nodes.PerStatement(range_array, tokens[1], tokens[8]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_GRADV expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
|
def donicum_step(tokens):
|
||||||
|
range_array = _at(ast_nodes.DataRangeArray(tokens[3], tokens[5], tokens[7]), tokens[0])
|
||||||
|
return _at(ast_nodes.PerStatement(range_array, tokens[1], tokens[10]), tokens[0])
|
||||||
|
|
||||||
# expressions
|
# expressions
|
||||||
@self.pg.production('expressions : SYMBOL_LPARENS expression_list')
|
@self.pg.production('expressions : SYMBOL_LPARENS expression_list')
|
||||||
@@ -146,16 +320,13 @@ class Parser():
|
|||||||
else:
|
else:
|
||||||
return [calls[0]] + calls[2]
|
return [calls[0]] + calls[2]
|
||||||
|
|
||||||
@self.pg.production('array_items : ')
|
|
||||||
@self.pg.production('array_items : expression')
|
@self.pg.production('array_items : expression')
|
||||||
@self.pg.production('array_items : expression SYMBOL_COMMA array_items')
|
@self.pg.production('array_items : expression SYMBOL_COMMA opt_newline array_items')
|
||||||
def array_items(calls):
|
def array_items(calls):
|
||||||
if len(calls) == 0:
|
if len(calls) == 1:
|
||||||
return []
|
|
||||||
elif len(calls) == 1:
|
|
||||||
return [calls[0]]
|
return [calls[0]]
|
||||||
else:
|
else:
|
||||||
return [calls[0]] + calls[2]
|
return [calls[0]] + calls[3]
|
||||||
|
|
||||||
@self.pg.production('expression : id')
|
@self.pg.production('expression : id')
|
||||||
def expression_id(tokens):
|
def expression_id(tokens):
|
||||||
@@ -163,69 +334,112 @@ class Parser():
|
|||||||
|
|
||||||
@self.pg.production('expression : BUILTIN expressions')
|
@self.pg.production('expression : BUILTIN expressions')
|
||||||
def expression_builtin(tokens):
|
def expression_builtin(tokens):
|
||||||
return ast_nodes.BuiltIn(tokens[0].value, tokens[1])
|
return _at(ast_nodes.BuiltIn(tokens[0].value, tokens[1]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : DATA_STRING')
|
@self.pg.production('expression : DATA_STRING')
|
||||||
def expression_string(tokens):
|
def expression_string(tokens):
|
||||||
return ast_nodes.String(tokens[0].value[1:-1])
|
node = _parse_interpolated(tokens[0].value, tokens[0].source_pos)
|
||||||
|
return _at(node, tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : DATA_NUMERAL')
|
@self.pg.production('expression : DATA_NUMERAL')
|
||||||
def expression_numeral(tokens):
|
def expression_numeral(tokens):
|
||||||
return ast_nodes.Numeral(tokens[0].value)
|
return _at(ast_nodes.Numeral(tokens[0].value), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : DATA_FRACTION')
|
@self.pg.production('expression : DATA_FRACTION')
|
||||||
def expression_fraction(tokens):
|
def expression_fraction(tokens):
|
||||||
return ast_nodes.Fractio(tokens[0].value)
|
return _at(ast_nodes.Fractio(tokens[0].value), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : KEYWORD_FALSITAS')
|
@self.pg.production('expression : KEYWORD_FALSITAS')
|
||||||
@self.pg.production('expression : KEYWORD_VERITAS')
|
@self.pg.production('expression : KEYWORD_VERITAS')
|
||||||
def expression_bool(tokens):
|
def expression_bool(tokens):
|
||||||
return ast_nodes.Bool(tokens[0].name == "KEYWORD_VERITAS")
|
return _at(ast_nodes.Bool(tokens[0].name == "KEYWORD_VERITAS"), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : KEYWORD_NVLLVS')
|
@self.pg.production('expression : KEYWORD_NVLLVS')
|
||||||
def expression_nullus(_):
|
def expression_nullus(tokens):
|
||||||
return ast_nodes.Nullus()
|
return _at(ast_nodes.Nullus(), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('expression : expression SYMBOL_AT expression')
|
||||||
@self.pg.production('expression : expression SYMBOL_AMPERSAND expression')
|
@self.pg.production('expression : expression SYMBOL_AMPERSAND expression')
|
||||||
@self.pg.production('expression : expression SYMBOL_MINUS expression')
|
@self.pg.production('expression : expression SYMBOL_MINUS expression')
|
||||||
@self.pg.production('expression : expression SYMBOL_PLUS expression')
|
@self.pg.production('expression : expression SYMBOL_PLUS expression')
|
||||||
@self.pg.production('expression : expression SYMBOL_TIMES expression')
|
@self.pg.production('expression : expression SYMBOL_TIMES expression')
|
||||||
@self.pg.production('expression : expression SYMBOL_DIVIDE expression')
|
@self.pg.production('expression : expression SYMBOL_DIVIDE expression')
|
||||||
|
@self.pg.production('expression : expression KEYWORD_RELIQVVM expression')
|
||||||
@self.pg.production('expression : expression KEYWORD_EST expression')
|
@self.pg.production('expression : expression KEYWORD_EST expression')
|
||||||
|
@self.pg.production('expression : expression KEYWORD_DISPAR expression')
|
||||||
@self.pg.production('expression : expression KEYWORD_MINVS expression')
|
@self.pg.production('expression : expression KEYWORD_MINVS expression')
|
||||||
@self.pg.production('expression : expression KEYWORD_PLVS expression')
|
@self.pg.production('expression : expression KEYWORD_PLVS expression')
|
||||||
|
@self.pg.production('expression : expression KEYWORD_HAVD_PLVS expression')
|
||||||
|
@self.pg.production('expression : expression KEYWORD_HAVD_MINVS expression')
|
||||||
@self.pg.production('expression : expression KEYWORD_ET expression')
|
@self.pg.production('expression : expression KEYWORD_ET expression')
|
||||||
@self.pg.production('expression : expression KEYWORD_AVT expression')
|
@self.pg.production('expression : expression KEYWORD_AVT expression')
|
||||||
def binop(tokens):
|
def binop(tokens):
|
||||||
return ast_nodes.BinOp(tokens[0], tokens[2], tokens[1].name)
|
return _at(ast_nodes.BinOp(tokens[0], tokens[2], tokens[1].name), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : SYMBOL_MINUS expression', precedence='UMINUS')
|
@self.pg.production('expression : SYMBOL_MINUS expression', precedence='UMINUS')
|
||||||
def unary_minus(tokens):
|
def unary_minus(tokens):
|
||||||
return ast_nodes.UnaryMinus(tokens[1])
|
return _at(ast_nodes.UnaryMinus(tokens[1]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : KEYWORD_NON expression', precedence='UNOT')
|
@self.pg.production('expression : KEYWORD_NON expression', precedence='UNOT')
|
||||||
def unary_not(tokens):
|
def unary_not(tokens):
|
||||||
return ast_nodes.UnaryNot(tokens[1])
|
return _at(ast_nodes.UnaryNot(tokens[1]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : KEYWORD_INVOCA id expressions')
|
@self.pg.production('expression : KEYWORD_INVOCA expression expressions')
|
||||||
def invoca(tokens):
|
def invoca(tokens):
|
||||||
return ast_nodes.Invoca(tokens[1], tokens[2])
|
return _at(ast_nodes.Invoca(tokens[1], tokens[2]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('expression : KEYWORD_FVNCTIO ids KEYWORD_VT SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
|
def fvnctio(tokens):
|
||||||
|
return _at(ast_nodes.Fvnctio(tokens[1], tokens[4]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : SYMBOL_LPARENS expression SYMBOL_RPARENS')
|
@self.pg.production('expression : SYMBOL_LPARENS expression SYMBOL_RPARENS')
|
||||||
def parens(tokens):
|
def parens(tokens):
|
||||||
return tokens[1]
|
return tokens[1]
|
||||||
|
|
||||||
@self.pg.production('expression : SYMBOL_LBRACKET array_items SYMBOL_RBRACKET')
|
@self.pg.production('dict_items : expression KEYWORD_VT expression')
|
||||||
|
@self.pg.production('dict_items : expression KEYWORD_VT expression SYMBOL_COMMA opt_newline dict_items')
|
||||||
|
def dict_items(calls):
|
||||||
|
if len(calls) == 3:
|
||||||
|
return [(calls[0], calls[2])]
|
||||||
|
else:
|
||||||
|
return [(calls[0], calls[2])] + calls[5]
|
||||||
|
|
||||||
|
@self.pg.production('expression : KEYWORD_TABVLA SYMBOL_LCURL opt_newline SYMBOL_RCURL')
|
||||||
|
def dict_literal_empty(tokens):
|
||||||
|
return _at(ast_nodes.DataDict([]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('expression : KEYWORD_TABVLA SYMBOL_LCURL opt_newline dict_items opt_newline SYMBOL_RCURL')
|
||||||
|
def dict_literal(tokens):
|
||||||
|
return _at(ast_nodes.DataDict(tokens[3]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('expression : SYMBOL_LBRACKET SYMBOL_RBRACKET')
|
||||||
|
@self.pg.production('expression : SYMBOL_LBRACKET newlines SYMBOL_RBRACKET')
|
||||||
|
def array_empty(tokens):
|
||||||
|
return _at(ast_nodes.DataArray([]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('expression : SYMBOL_LBRACKET array_items opt_newline SYMBOL_RBRACKET')
|
||||||
def array(tokens):
|
def array(tokens):
|
||||||
return ast_nodes.DataArray(tokens[1])
|
return _at(ast_nodes.DataArray(tokens[1]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('expression : SYMBOL_LBRACKET newlines array_items opt_newline SYMBOL_RBRACKET')
|
||||||
|
def array_leading_newline(tokens):
|
||||||
|
return _at(ast_nodes.DataArray(tokens[2]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : SYMBOL_LBRACKET expression KEYWORD_VSQVE expression SYMBOL_RBRACKET')
|
@self.pg.production('expression : SYMBOL_LBRACKET expression KEYWORD_VSQVE expression SYMBOL_RBRACKET')
|
||||||
def range_array(tokens):
|
def range_array(tokens):
|
||||||
return ast_nodes.DataRangeArray(tokens[1], tokens[3])
|
return _at(ast_nodes.DataRangeArray(tokens[1], tokens[3]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('expression : SYMBOL_LBRACKET expression KEYWORD_VSQVE expression KEYWORD_GRADV expression SYMBOL_RBRACKET')
|
||||||
|
def range_array_step(tokens):
|
||||||
|
return _at(ast_nodes.DataRangeArray(tokens[1], tokens[3], tokens[5]), tokens[0])
|
||||||
|
|
||||||
@self.pg.production('expression : expression SYMBOL_LBRACKET expression SYMBOL_RBRACKET', precedence='INDEX')
|
@self.pg.production('expression : expression SYMBOL_LBRACKET expression SYMBOL_RBRACKET', precedence='INDEX')
|
||||||
def array_index(tokens):
|
def array_index(tokens):
|
||||||
return ast_nodes.ArrayIndex(tokens[0], tokens[2])
|
return _at(ast_nodes.ArrayIndex(tokens[0], tokens[2]), tokens[0])
|
||||||
|
|
||||||
|
@self.pg.production('expression : expression SYMBOL_LBRACKET expression KEYWORD_VSQVE expression SYMBOL_RBRACKET', precedence='INDEX')
|
||||||
|
def array_slice(tokens):
|
||||||
|
return _at(ast_nodes.ArraySlice(tokens[0], tokens[2], tokens[4]), tokens[0])
|
||||||
|
|
||||||
# ids
|
# ids
|
||||||
@self.pg.production('ids : SYMBOL_LPARENS id_list')
|
@self.pg.production('ids : SYMBOL_LPARENS id_list')
|
||||||
@@ -243,13 +457,27 @@ class Parser():
|
|||||||
else:
|
else:
|
||||||
return [calls[0]] + calls[2]
|
return [calls[0]] + calls[2]
|
||||||
|
|
||||||
|
@self.pg.production('id_list_rest : id')
|
||||||
|
@self.pg.production('id_list_rest : id SYMBOL_COMMA id_list_rest')
|
||||||
|
def id_list_rest(calls):
|
||||||
|
if len(calls) == 1:
|
||||||
|
return [calls[0]]
|
||||||
|
else:
|
||||||
|
return [calls[0]] + calls[2]
|
||||||
|
|
||||||
@self.pg.production("id : ID")
|
@self.pg.production("id : ID")
|
||||||
def id_expression(tokens):
|
def id_expression(tokens):
|
||||||
return ast_nodes.ID(tokens[0].value)
|
return _at(ast_nodes.ID(tokens[0].value), tokens[0])
|
||||||
|
|
||||||
@self.pg.error
|
@self.pg.error
|
||||||
def error_handle(token):
|
def error_handle(token):
|
||||||
raise SyntaxError(f"{token.name}, {token.value}, {token.source_pos}")
|
pos = token.source_pos
|
||||||
|
loc = f" at line {pos.lineno}, column {pos.colno}" if pos else ""
|
||||||
|
if token.name == "SYMBOL_LPARENS":
|
||||||
|
raise SyntaxError(
|
||||||
|
f"Unexpected '('{loc}. To call a function, use INVOCA: INVOCA func (args)"
|
||||||
|
)
|
||||||
|
raise SyntaxError(f"Unexpected token '{token.value}'{loc}")
|
||||||
|
|
||||||
parser = self.pg.build()
|
parser = self.pg.build()
|
||||||
return parser.parse(tokens_input) # type: ignore
|
return parser.parse(tokens_input) # type: ignore
|
||||||
|
|||||||
@@ -66,6 +66,21 @@ class ValList(Val):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self._v)
|
return iter(self._v)
|
||||||
|
|
||||||
|
class ValDict(Val):
|
||||||
|
def __init__(self, v: dict):
|
||||||
|
assert isinstance(v, dict)
|
||||||
|
self._v = v
|
||||||
|
|
||||||
|
def value(self):
|
||||||
|
return self._v
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return len(self._v) > 0
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._v.keys())
|
||||||
|
|
||||||
|
|
||||||
class ValFrac(Val):
|
class ValFrac(Val):
|
||||||
def __init__(self, v: Fraction):
|
def __init__(self, v: Fraction):
|
||||||
assert isinstance(v, Fraction)
|
assert isinstance(v, Fraction)
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ DESIGNA array VT [II,XVIII,XV,IX,XIV,XIV,I,VII,VIII,VI]
|
|||||||
DESIGNA max VT NVLLVS
|
DESIGNA max VT NVLLVS
|
||||||
DESIGNA svm VT NVLLVS
|
DESIGNA svm VT NVLLVS
|
||||||
|
|
||||||
PER x IN array FACE {
|
PER x IN array FAC {
|
||||||
DESIGNA svm VT svm + x
|
DESIGNA svm VT svm + x
|
||||||
SI x PLVS max TVNC {
|
SI x PLVS max TVNC {
|
||||||
DESIGNA max VT x
|
DESIGNA max VT x
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DICE("Sum:", svm)
|
DIC("Sum:", svm)
|
||||||
DICE("Max:", max)
|
DIC("Max:", max)
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
DESIGNA arr VT [V, III, VIII, I, IX, II, VII, IV, VI, X]
|
DESIGNA arr VT [V, III, VIII, I, IX, II, VII, IV, VI, X]
|
||||||
DESIGNA n VT LONGITVDO(arr)
|
DESIGNA n VT LONGITVDO(arr)
|
||||||
|
|
||||||
DONICVM i VT I VSQVE n FACE {
|
DONICVM i VT I VSQVE n - I FAC {
|
||||||
DONICVM k VT I VSQVE n - i + I FACE {
|
DONICVM k VT I VSQVE n - i FAC {
|
||||||
SI arr[k] PLVS arr[k + I] TVNC {
|
SI arr[k] PLVS arr[k + I] TVNC {
|
||||||
DESIGNA temp VT arr[k]
|
DESIGNA temp VT arr[k]
|
||||||
DESIGNA arr[k] VT arr[k + I]
|
DESIGNA arr[k] VT arr[k + I]
|
||||||
@@ -13,6 +13,6 @@ DONICVM i VT I VSQVE n FACE {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PER x IN arr FACE {
|
PER x IN arr FAC {
|
||||||
DICE(x)
|
DIC(x)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Returns the bottommost empty row in col, or NVLLVS if full
|
// Returns the bottommost empty row in col, or NVLLVS if full
|
||||||
DEFINI find_slot(b, col) VT {
|
DEFINI find_slot(b, col) VT {
|
||||||
DESIGNA ans VT NVLLVS
|
DESIGNA ans VT NVLLVS
|
||||||
DONICVM r VT I VSQVE VII FACE {
|
DONICVM r VT I VSQVE VI FAC {
|
||||||
SI b[(r - I) * VII + col] EST NVLLVS TVNC {
|
SI b[(r - I) * VII + col] EST NVLLVS TVNC {
|
||||||
DESIGNA ans VT r
|
DESIGNA ans VT r
|
||||||
}
|
}
|
||||||
@@ -16,32 +16,32 @@ DEFINI find_slot(b, col) VT {
|
|||||||
|
|
||||||
// Returns VERITAS if player has four in a row
|
// Returns VERITAS if player has four in a row
|
||||||
DEFINI est_victor(b, player) VT {
|
DEFINI est_victor(b, player) VT {
|
||||||
DONICVM r VT I VSQVE VII FACE {
|
DONICVM r VT I VSQVE VI FAC {
|
||||||
DONICVM c VT I VSQVE V FACE {
|
DONICVM c VT I VSQVE IV FAC {
|
||||||
DESIGNA idx VT (r - I) * VII + c
|
DESIGNA idx VT (r - I) * VII + c
|
||||||
SI b[idx] EST player ET b[idx + I] EST player ET b[idx + II] EST player ET b[idx + III] EST player TVNC {
|
SI b[idx] EST player ET b[idx + I] EST player ET b[idx + II] EST player ET b[idx + III] EST player TVNC {
|
||||||
REDI(VERITAS)
|
REDI(VERITAS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DONICVM r VT I VSQVE IV FACE {
|
DONICVM r VT I VSQVE III FAC {
|
||||||
DONICVM c VT I VSQVE VIII FACE {
|
DONICVM c VT I VSQVE VII FAC {
|
||||||
DESIGNA idx VT (r - I) * VII + c
|
DESIGNA idx VT (r - I) * VII + c
|
||||||
SI b[idx] EST player ET b[idx + VII] EST player ET b[idx + XIV] EST player ET b[idx + XXI] EST player TVNC {
|
SI b[idx] EST player ET b[idx + VII] EST player ET b[idx + XIV] EST player ET b[idx + XXI] EST player TVNC {
|
||||||
REDI(VERITAS)
|
REDI(VERITAS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DONICVM r VT I VSQVE IV FACE {
|
DONICVM r VT I VSQVE III FAC {
|
||||||
DONICVM c VT I VSQVE V FACE {
|
DONICVM c VT I VSQVE IV FAC {
|
||||||
DESIGNA idx VT (r - I) * VII + c
|
DESIGNA idx VT (r - I) * VII + c
|
||||||
SI b[idx] EST player ET b[idx + VIII] EST player ET b[idx + XVI] EST player ET b[idx + XXIV] EST player TVNC {
|
SI b[idx] EST player ET b[idx + VIII] EST player ET b[idx + XVI] EST player ET b[idx + XXIV] EST player TVNC {
|
||||||
REDI(VERITAS)
|
REDI(VERITAS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DONICVM r VT I VSQVE IV FACE {
|
DONICVM r VT I VSQVE III FAC {
|
||||||
DONICVM c VT IV VSQVE VIII FACE {
|
DONICVM c VT IV VSQVE VII FAC {
|
||||||
DESIGNA idx VT (r - I) * VII + c
|
DESIGNA idx VT (r - I) * VII + c
|
||||||
SI b[idx] EST player ET b[idx + VI] EST player ET b[idx + XII] EST player ET b[idx + XVIII] EST player TVNC {
|
SI b[idx] EST player ET b[idx + VI] EST player ET b[idx + XII] EST player ET b[idx + XVIII] EST player TVNC {
|
||||||
REDI(VERITAS)
|
REDI(VERITAS)
|
||||||
@@ -52,23 +52,23 @@ DEFINI est_victor(b, player) VT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEFINI print_board(b) VT {
|
DEFINI print_board(b) VT {
|
||||||
DICE("+---+---+---+---+---+---+---+")
|
DIC("+---+---+---+---+---+---+---+")
|
||||||
DONICVM r VT I VSQVE VII FACE {
|
DONICVM r VT I VSQVE VI FAC {
|
||||||
DESIGNA line VT "| "
|
DESIGNA line VT "| "
|
||||||
DONICVM c VT I VSQVE VIII FACE {
|
DONICVM c VT I VSQVE VII FAC {
|
||||||
DESIGNA cell VT b[(r - I) * VII + c]
|
DESIGNA cell VT b[(r - I) * VII + c]
|
||||||
SI cell EST I TVNC {
|
SI cell EST I TVNC {
|
||||||
DESIGNA line VT line & "X | "
|
DESIGNA line VT line & "X | "
|
||||||
} ALVID SI cell EST II TVNC {
|
} ALIVD SI cell EST II TVNC {
|
||||||
DESIGNA line VT line & "O | "
|
DESIGNA line VT line & "O | "
|
||||||
} ALVID {
|
} ALIVD {
|
||||||
DESIGNA line VT line & ". | "
|
DESIGNA line VT line & ". | "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DICE(line)
|
DIC(line)
|
||||||
}
|
}
|
||||||
DICE("+---+---+---+---+---+---+---+")
|
DIC("+---+---+---+---+---+---+---+")
|
||||||
DICE(" I II III IV V VI VII")
|
DIC(" I II III IV V VI VII")
|
||||||
REDI(NVLLVS)
|
REDI(NVLLVS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,35 +101,35 @@ DEFINI score_fenestram(a, b, c, d) VT {
|
|||||||
DEFINI aestima(b) VT {
|
DEFINI aestima(b) VT {
|
||||||
DESIGNA score VT NVLLVS
|
DESIGNA score VT NVLLVS
|
||||||
// Center column preference: each AI piece in column IV is worth +1
|
// Center column preference: each AI piece in column IV is worth +1
|
||||||
DONICVM r VT I VSQVE VII FACE {
|
DONICVM r VT I VSQVE VI FAC {
|
||||||
SI b[(r - I) * VII + IV] EST II TVNC {
|
SI b[(r - I) * VII + IV] EST II TVNC {
|
||||||
DESIGNA score VT score + I
|
DESIGNA score VT score + I
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Horizontal windows (6 rows x 4 starting columns = 24)
|
// Horizontal windows (6 rows x 4 starting columns = 24)
|
||||||
DONICVM r VT I VSQVE VII FACE {
|
DONICVM r VT I VSQVE VI FAC {
|
||||||
DONICVM c VT I VSQVE V FACE {
|
DONICVM c VT I VSQVE IV FAC {
|
||||||
DESIGNA idx VT (r - I) * VII + c
|
DESIGNA idx VT (r - I) * VII + c
|
||||||
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + I], b[idx + II], b[idx + III])
|
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + I], b[idx + II], b[idx + III])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Vertical windows (3 starting rows x 7 columns = 21)
|
// Vertical windows (3 starting rows x 7 columns = 21)
|
||||||
DONICVM r VT I VSQVE IV FACE {
|
DONICVM r VT I VSQVE III FAC {
|
||||||
DONICVM c VT I VSQVE VIII FACE {
|
DONICVM c VT I VSQVE VII FAC {
|
||||||
DESIGNA idx VT (r - I) * VII + c
|
DESIGNA idx VT (r - I) * VII + c
|
||||||
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VII], b[idx + XIV], b[idx + XXI])
|
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VII], b[idx + XIV], b[idx + XXI])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Diagonal up-right windows (3 starting rows x 4 starting columns = 12)
|
// Diagonal up-right windows (3 starting rows x 4 starting columns = 12)
|
||||||
DONICVM r VT I VSQVE IV FACE {
|
DONICVM r VT I VSQVE III FAC {
|
||||||
DONICVM c VT I VSQVE V FACE {
|
DONICVM c VT I VSQVE IV FAC {
|
||||||
DESIGNA idx VT (r - I) * VII + c
|
DESIGNA idx VT (r - I) * VII + c
|
||||||
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VIII], b[idx + XVI], b[idx + XXIV])
|
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VIII], b[idx + XVI], b[idx + XXIV])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Diagonal up-left windows (3 starting rows x 4 starting columns = 12)
|
// Diagonal up-left windows (3 starting rows x 4 starting columns = 12)
|
||||||
DONICVM r VT I VSQVE IV FACE {
|
DONICVM r VT I VSQVE III FAC {
|
||||||
DONICVM c VT IV VSQVE VIII FACE {
|
DONICVM c VT IV VSQVE VII FAC {
|
||||||
DESIGNA idx VT (r - I) * VII + c
|
DESIGNA idx VT (r - I) * VII + c
|
||||||
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VI], b[idx + XII], b[idx + XVIII])
|
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VI], b[idx + XII], b[idx + XVIII])
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ DEFINI minimax(b, depth, alpha, beta, maxi) VT {
|
|||||||
SI INVOCA est_victor(b, I) TVNC {
|
SI INVOCA est_victor(b, I) TVNC {
|
||||||
REDI(NVLLVS - M - depth)
|
REDI(NVLLVS - M - depth)
|
||||||
}
|
}
|
||||||
} ALVID {
|
} ALIVD {
|
||||||
SI INVOCA est_victor(b, II) TVNC {
|
SI INVOCA est_victor(b, II) TVNC {
|
||||||
REDI(M + depth)
|
REDI(M + depth)
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ DEFINI minimax(b, depth, alpha, beta, maxi) VT {
|
|||||||
DESIGNA col_order VT [IV, III, V, II, VI, I, VII]
|
DESIGNA col_order VT [IV, III, V, II, VI, I, VII]
|
||||||
SI maxi TVNC {
|
SI maxi TVNC {
|
||||||
DESIGNA best VT NVLLVS - M - VII
|
DESIGNA best VT NVLLVS - M - VII
|
||||||
PER c IN col_order FACE {
|
PER c IN col_order FAC {
|
||||||
DESIGNA linea VT INVOCA find_slot(b, c)
|
DESIGNA linea VT INVOCA find_slot(b, c)
|
||||||
SI NON (linea EST NVLLVS) TVNC {
|
SI NON (linea EST NVLLVS) TVNC {
|
||||||
DESIGNA b[(linea - I) * VII + c] VT II
|
DESIGNA b[(linea - I) * VII + c] VT II
|
||||||
@@ -175,9 +175,9 @@ DEFINI minimax(b, depth, alpha, beta, maxi) VT {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
REDI(best)
|
REDI(best)
|
||||||
} ALVID {
|
} ALIVD {
|
||||||
DESIGNA best VT M + VII
|
DESIGNA best VT M + VII
|
||||||
PER c IN col_order FACE {
|
PER c IN col_order FAC {
|
||||||
DESIGNA linea VT INVOCA find_slot(b, c)
|
DESIGNA linea VT INVOCA find_slot(b, c)
|
||||||
SI NON (linea EST NVLLVS) TVNC {
|
SI NON (linea EST NVLLVS) TVNC {
|
||||||
DESIGNA b[(linea - I) * VII + c] VT I
|
DESIGNA b[(linea - I) * VII + c] VT I
|
||||||
@@ -203,7 +203,7 @@ DEFINI ai_move(b) VT {
|
|||||||
DESIGNA col_order VT [IV, III, V, II, VI, I, VII]
|
DESIGNA col_order VT [IV, III, V, II, VI, I, VII]
|
||||||
DESIGNA best_score VT NVLLVS - M - VII
|
DESIGNA best_score VT NVLLVS - M - VII
|
||||||
DESIGNA best_col VT IV
|
DESIGNA best_col VT IV
|
||||||
PER c IN col_order FACE {
|
PER c IN col_order FAC {
|
||||||
DESIGNA linea VT INVOCA find_slot(b, c)
|
DESIGNA linea VT INVOCA find_slot(b, c)
|
||||||
SI NON (linea EST NVLLVS) TVNC {
|
SI NON (linea EST NVLLVS) TVNC {
|
||||||
DESIGNA b[(linea - I) * VII + c] VT II
|
DESIGNA b[(linea - I) * VII + c] VT II
|
||||||
@@ -219,64 +219,64 @@ DEFINI ai_move(b) VT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Board setup ---
|
// --- Board setup ---
|
||||||
DESIGNA board VT [I VSQVE XLIII]
|
DESIGNA board VT [I VSQVE XLII]
|
||||||
DONICVM i VT I VSQVE XLIII FACE {
|
DONICVM i VT I VSQVE XLII FAC {
|
||||||
DESIGNA board[i] VT NVLLVS
|
DESIGNA board[i] VT NVLLVS
|
||||||
}
|
}
|
||||||
|
|
||||||
DESIGNA moves VT NVLLVS
|
DESIGNA moves VT NVLLVS
|
||||||
DESIGNA game_over VT FALSITAS
|
DESIGNA game_over VT FALSITAS
|
||||||
|
|
||||||
DICE("=== CONNECT IV ===")
|
DIC("=== CONNECT IV ===")
|
||||||
DICE("You are X. AI is O.")
|
DIC("You are X. AI is O.")
|
||||||
DICE("Enter column as Roman numeral (I-VII).")
|
DIC("Enter column as Roman numeral (I-VII).")
|
||||||
DICE("")
|
DIC("")
|
||||||
|
|
||||||
DVM game_over FACE {
|
DVM game_over FAC {
|
||||||
EVERRO()
|
EVERRE()
|
||||||
INVOCA print_board(board)
|
INVOCA print_board(board)
|
||||||
|
|
||||||
DICE("Your move:")
|
DIC("Your move:")
|
||||||
DESIGNA col VT AVDI_NVMERVS()
|
DESIGNA col VT AVDI_NVMERVS()
|
||||||
SI col PLVS VII TVNC {
|
SI col PLVS VII TVNC {
|
||||||
DICE("Invalid column! Enter I through VII.")
|
DIC("Invalid column! Enter I through VII.")
|
||||||
CONTINVA
|
CONTINVA
|
||||||
}
|
}
|
||||||
DESIGNA linea VT INVOCA find_slot(board, col)
|
DESIGNA linea VT INVOCA find_slot(board, col)
|
||||||
SI linea EST NVLLVS TVNC {
|
SI linea EST NVLLVS TVNC {
|
||||||
DICE("Column full! Try another.")
|
DIC("Column full! Try another.")
|
||||||
CONTINVA
|
CONTINVA
|
||||||
}
|
}
|
||||||
DESIGNA board[(linea - I) * VII + col] VT I
|
DESIGNA board[(linea - I) * VII + col] VT I
|
||||||
DESIGNA moves VT moves + I
|
DESIGNA moves VT moves + I
|
||||||
SI INVOCA est_victor(board, I) TVNC {
|
SI INVOCA est_victor(board, I) TVNC {
|
||||||
INVOCA print_board(board)
|
INVOCA print_board(board)
|
||||||
DICE("You win!")
|
DIC("You win!")
|
||||||
DESIGNA game_over VT VERITAS
|
DESIGNA game_over VT VERITAS
|
||||||
CONTINVA
|
CONTINVA
|
||||||
}
|
}
|
||||||
SI moves EST XLII TVNC {
|
SI moves EST XLII TVNC {
|
||||||
INVOCA print_board(board)
|
INVOCA print_board(board)
|
||||||
DICE("Draw!")
|
DIC("Draw!")
|
||||||
DESIGNA game_over VT VERITAS
|
DESIGNA game_over VT VERITAS
|
||||||
CONTINVA
|
CONTINVA
|
||||||
}
|
}
|
||||||
|
|
||||||
DICE("AI is thinking...")
|
DIC("AI is thinking...")
|
||||||
DESIGNA col VT INVOCA ai_move(board)
|
DESIGNA col VT INVOCA ai_move(board)
|
||||||
DESIGNA linea VT INVOCA find_slot(board, col)
|
DESIGNA linea VT INVOCA find_slot(board, col)
|
||||||
DICE("AI plays column " & col & ".")
|
DIC("AI plays column " & col & ".")
|
||||||
DESIGNA board[(linea - I) * VII + col] VT II
|
DESIGNA board[(linea - I) * VII + col] VT II
|
||||||
DESIGNA moves VT moves + I
|
DESIGNA moves VT moves + I
|
||||||
SI INVOCA est_victor(board, II) TVNC {
|
SI INVOCA est_victor(board, II) TVNC {
|
||||||
INVOCA print_board(board)
|
INVOCA print_board(board)
|
||||||
DICE("AI wins!")
|
DIC("AI wins!")
|
||||||
DESIGNA game_over VT VERITAS
|
DESIGNA game_over VT VERITAS
|
||||||
CONTINVA
|
CONTINVA
|
||||||
}
|
}
|
||||||
SI moves EST XLII TVNC {
|
SI moves EST XLII TVNC {
|
||||||
INVOCA print_board(board)
|
INVOCA print_board(board)
|
||||||
DICE("Draw!")
|
DIC("Draw!")
|
||||||
DESIGNA game_over VT VERITAS
|
DESIGNA game_over VT VERITAS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Counts down from 10
|
// Counts down from 10
|
||||||
DESIGNA conta VT X
|
DESIGNA conta VT X
|
||||||
|
|
||||||
DVM conta MINVS I FACE {
|
DVM conta MINVS I FAC {
|
||||||
DICE(conta)
|
DIC(conta)
|
||||||
DESIGNA conta VT conta - I
|
DESIGNA conta VT conta - I
|
||||||
}
|
}
|
||||||
|
|
||||||
DICE("Blast off!")
|
DIC("Blast off!")
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ CVM MAGNVM
|
|||||||
DEFINI fact(n) VT {
|
DEFINI fact(n) VT {
|
||||||
SI n MINVS I TVNC {
|
SI n MINVS I TVNC {
|
||||||
REDI(I)
|
REDI(I)
|
||||||
} ALVID {
|
} ALIVD {
|
||||||
REDI(n * INVOCA fact(n - I))
|
REDI(n * INVOCA fact(n - I))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DICE(INVOCA fact(AVDI_NVMERVS()))
|
DIC(INVOCA fact(AVDI_NVMERVS()))
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
// A number guessing game
|
// A number guessing game
|
||||||
CVM FORS
|
CVM FORS
|
||||||
|
|
||||||
DESIGNA correct VT FORTIS_NVMERVS(I,C)
|
DESIGNA correct VT FORTVITVS_NVMERVS(I,C)
|
||||||
DESIGNA gvess VT NVLLVS
|
DESIGNA gvess VT NVLLVS
|
||||||
|
|
||||||
DVM correct EST gvess FACE {
|
DVM correct EST gvess FAC {
|
||||||
DESIGNA gvess VT AVDI_NVMERVS()
|
DESIGNA gvess VT AVDI_NVMERVS()
|
||||||
SI gvess MINVS correct TVNC {
|
SI gvess MINVS correct TVNC {
|
||||||
DICE("Too low!")
|
DIC("Too low!")
|
||||||
} ALVID SI gvess PLVS correct TVNC {
|
} ALIVD SI gvess PLVS correct TVNC {
|
||||||
DICE("Too high!")
|
DIC("Too high!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DICE("You guessed correctly!")
|
DIC("You guessed correctly!")
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
// Prints an X×X multiplication table
|
// Prints an X×X multiplication table
|
||||||
DESIGNA n VT X
|
DESIGNA n VT X
|
||||||
|
|
||||||
DONICVM i VT I VSQVE n + I FACE {
|
DONICVM i VT I VSQVE n FAC {
|
||||||
DESIGNA line VT ""
|
DESIGNA line VT ""
|
||||||
DONICVM k VT I VSQVE n + I FACE {
|
DONICVM k VT I VSQVE n FAC {
|
||||||
DESIGNA line VT line & i * k & " "
|
DESIGNA line VT line & i * k & " "
|
||||||
}
|
}
|
||||||
DICE(line)
|
DIC(line)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
CVM FORS
|
CVM FORS
|
||||||
|
|
||||||
DESIGNA choices VT ["PETRA", "CHARTA", "FORFEX"]
|
DESIGNA choices VT ["PETRA", "CHARTA", "FORFEX"]
|
||||||
DESIGNA compvter VT FORTIS_ELECTIONIS(choices)
|
DESIGNA compvter VT FORTVITA_ELECTIO(choices)
|
||||||
DICE("Choose: PETRA (rock), CHARTA (paper), or FORFEX (scissors)")
|
DIC("Choose: PETRA (rock), CHARTA (paper), or FORFEX (scissors)")
|
||||||
DESIGNA player VT AVDI()
|
DESIGNA player VT AVDI()
|
||||||
|
|
||||||
DICE("Computer chose:", compvter)
|
DIC("Computer chose:", compvter)
|
||||||
|
|
||||||
SI player EST compvter TVNC {
|
SI player EST compvter TVNC {
|
||||||
DICE("Draw!")
|
DIC("Draw!")
|
||||||
} ALVID SI (player EST "PETRA" ET compvter EST "FORFEX") AVT (player EST "CHARTA" ET compvter EST "PETRA") AVT (player EST "FORFEX" ET compvter EST "CHARTA") TVNC {
|
} ALIVD SI (player EST "PETRA" ET compvter EST "FORFEX") AVT (player EST "CHARTA" ET compvter EST "PETRA") AVT (player EST "FORFEX" ET compvter EST "CHARTA") TVNC {
|
||||||
DICE("You win!")
|
DIC("You win!")
|
||||||
} ALVID {
|
} ALIVD {
|
||||||
DICE("Computer wins!")
|
DIC("Computer wins!")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
DESIGNA n VT L
|
DESIGNA n VT L
|
||||||
|
|
||||||
DONICVM i VT II VSQVE n + I FACE {
|
DONICVM i VT II VSQVE n FAC {
|
||||||
DESIGNA is_prime VT VERITAS
|
DESIGNA is_prime VT VERITAS
|
||||||
DONICVM k VT II VSQVE i FACE {
|
DONICVM k VT II VSQVE i - I FAC {
|
||||||
SI (i / k) * k EST i TVNC {
|
SI (i / k) * k EST i TVNC {
|
||||||
DESIGNA is_prime VT FALSITAS
|
DESIGNA is_prime VT FALSITAS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SI is_prime TVNC {
|
SI is_prime TVNC {
|
||||||
DICE(i)
|
DIC(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
examples/web_server.cent
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// A simple web server with dynamic content
|
||||||
|
// Run: ./cent -i examples/web_server.cent
|
||||||
|
// Then visit http://localhost:80/ in your browser
|
||||||
|
CVM RETE
|
||||||
|
CVM FORS
|
||||||
|
|
||||||
|
DESIGNA salve VT ["SALVE", "AVE", "HAVETE", "SALVETE"]
|
||||||
|
DESIGNA sententiae VT ["Alea iacta est.", "Veni, vidi, vici.", "Carpe diem.", "Cogito, ergo svm."]
|
||||||
|
|
||||||
|
PETITVR("/", FVNCTIO (petitio) VT {
|
||||||
|
DESIGNA verbvm VT FORTVITA_ELECTIO(salve)
|
||||||
|
DESIGNA sententia VT FORTVITA_ELECTIO(sententiae)
|
||||||
|
DESIGNA a VT FORTVITVS_NVMERVS(I, VI)
|
||||||
|
DESIGNA b VT FORTVITVS_NVMERVS(I, VI)
|
||||||
|
REDI("{verbvm} MVNDE!\n\n{sententia}\n\nAlea: {a} + {b} = {a + b}")
|
||||||
|
})
|
||||||
|
|
||||||
|
DIC("Avscvlta in port MLXXX...")
|
||||||
|
AVSCVLTA(MLXXX)
|
||||||
@@ -30,20 +30,30 @@
|
|||||||
\multicolumn{3}{|c|}{\textbf{Statements}} \\ \hline
|
\multicolumn{3}{|c|}{\textbf{Statements}} \\ \hline
|
||||||
\languageline{statement}{\textit{expression}} \\
|
\languageline{statement}{\textit{expression}} \\
|
||||||
\languageline{statement}{\texttt{DESIGNA} \textbf{id} \texttt{VT} \textit{expression}} \\
|
\languageline{statement}{\texttt{DESIGNA} \textbf{id} \texttt{VT} \textit{expression}} \\
|
||||||
|
\languageline{statement}{\texttt{DESIGNA} \textbf{id} \texttt{,} \textit{ids} \texttt{VT} \textit{expression}} \\
|
||||||
|
\languageline{statement}{\textbf{id} \texttt{AVGE} \textit{expression}} \\
|
||||||
|
\languageline{statement}{\textbf{id} \texttt{MINVE} \textit{expression}} \\
|
||||||
|
\languageline{statement}{\textbf{id} \texttt{MVLTIPLICA} \textit{expression}} \\
|
||||||
|
\languageline{statement}{\textbf{id} \texttt{DIVIDE} \textit{expression}} \\
|
||||||
\languageline{statement}{\texttt{DEFINI} \textbf{id} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\
|
\languageline{statement}{\texttt{DEFINI} \textbf{id} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\
|
||||||
\languageline{statement}{\textit{if-statement}} \\
|
\languageline{statement}{\textit{if-statement}} \\
|
||||||
\languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FACE} \textit{scope}} \\
|
\languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FACE} \textit{scope}} \\
|
\languageline{statement}{\texttt{AETERNVM} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{FACE} \textit{scope}} \\
|
\languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
||||||
|
\languageline{statement}{\texttt{PER} \textbf{id}\texttt{,} \textbf{id-list} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
||||||
|
\languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \textit{optional-step} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\
|
\languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\
|
||||||
\languageline{statement}{\texttt{ERVMPE}} \\
|
\languageline{statement}{\texttt{ERVMPE}} \\
|
||||||
\languageline{statement}{\texttt{CONTINVA}} \\ \hline
|
\languageline{statement}{\texttt{CONTINVA}} \\
|
||||||
|
\languageline{statement}{\textit{try-statement}} \\ \hline
|
||||||
|
|
||||||
|
\languageline{try-statement}{\texttt{TEMPTA} \textit{scope} \texttt{CAPE} \textbf{id} \textit{scope}} \\ \hline
|
||||||
|
|
||||||
\languageline{if-statement}{\texttt{SI} \textit{expression} \texttt{TVNC} \textit{scope}} \\
|
\languageline{if-statement}{\texttt{SI} \textit{expression} \texttt{TVNC} \textit{scope}} \\
|
||||||
\languageline{if-statement}{\texttt{SI} \textit{expression} \texttt{TVNC} \textit{scope} \textit{optional-newline} \textit{else-statement}} \\ \hline
|
\languageline{if-statement}{\texttt{SI} \textit{expression} \texttt{TVNC} \textit{scope} \textit{optional-newline} \textit{else-statement}} \\ \hline
|
||||||
|
|
||||||
\languageline{else-statement}{\texttt{ALVID} \textit{scope}} \\
|
\languageline{else-statement}{\texttt{ALIVD} \textit{scope}} \\
|
||||||
\languageline{else-statement}{\texttt{ALVID} \textit{if-statement}} \\ \hline
|
\languageline{else-statement}{\texttt{ALIVD} \textit{if-statement}} \\ \hline
|
||||||
|
|
||||||
\languageline{scope}{\textit{optional-newline} \texttt{\{} \textbf{newline} \textit{statements} \texttt{\}}} \\ \hline \hline
|
\languageline{scope}{\textit{optional-newline} \texttt{\{} \textbf{newline} \textit{statements} \texttt{\}}} \\ \hline \hline
|
||||||
|
|
||||||
@@ -51,16 +61,26 @@
|
|||||||
\languageline{expression}{\texttt{(} \textit{expression} \texttt{)}} \\
|
\languageline{expression}{\texttt{(} \textit{expression} \texttt{)}} \\
|
||||||
\languageline{expression}{\textbf{id}} \\
|
\languageline{expression}{\textbf{id}} \\
|
||||||
\languageline{expression}{\textbf{builtin} \texttt{(} \textit{optional-expressions} \texttt{)}} \\
|
\languageline{expression}{\textbf{builtin} \texttt{(} \textit{optional-expressions} \texttt{)}} \\
|
||||||
\languageline{expression}{\texttt{INVOCA} \textbf{id} \texttt{(} \textit{optional-expressions} \texttt{)}} \\
|
\languageline{expression}{\texttt{INVOCA} \textit{expression} \texttt{(} \textit{optional-expressions} \texttt{)}} \\
|
||||||
|
\languageline{expression}{\texttt{FVNCTIO} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\
|
||||||
\languageline{expression}{\textit{literal}} \\
|
\languageline{expression}{\textit{literal}} \\
|
||||||
\languageline{expression}{\textit{expression} \texttt{[} \textit{expression} \texttt{]}} \\
|
\languageline{expression}{\textit{expression} \texttt{[} \textit{expression} \texttt{]}} \\
|
||||||
|
\languageline{expression}{\textit{expression} \texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]} \textnormal{\small\ (inclusive slice)}} \\
|
||||||
\languageline{expression}{\textit{expression} \textbf{binop} \textit{expression}} \\
|
\languageline{expression}{\textit{expression} \textbf{binop} \textit{expression}} \\
|
||||||
\languageline{expression}{\textbf{unop} \textit{expression}} \\ \hline
|
\languageline{expression}{\textbf{unop} \textit{expression}} \\ \hline
|
||||||
\languageline{literal}{\textbf{string}} \\
|
\languageline{literal}{\textbf{string}} \\
|
||||||
|
\languageline{literal}{\textbf{interpolated-string}} \\
|
||||||
\languageline{literal}{\textbf{numeral}} \\
|
\languageline{literal}{\textbf{numeral}} \\
|
||||||
\languageline{literal}{\textbf{bool}} \\
|
\languageline{literal}{\textbf{bool}} \\
|
||||||
\languageline{literal}{\texttt{[} \textit{optional-expressions} \texttt{]}} \\
|
\languageline{literal}{\texttt{[} \textit{optional-expressions} \texttt{]}} \\
|
||||||
\languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]}} \\ \hline \hline
|
\languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \textit{optional-step} \texttt{]} \textnormal{\small\ (inclusive on both ends)}} \\
|
||||||
|
\languageline{literal}{\texttt{TABVLA} \texttt{\{} \textit{optional-dict-items} \texttt{\}}} \\ \hline
|
||||||
|
|
||||||
|
\languageline{optional-dict-items}{\textit{dict-items}} \\
|
||||||
|
\languageline{optional-dict-items}{} \\ \hline
|
||||||
|
|
||||||
|
\languageline{dict-items}{\textit{expression} \texttt{VT} \textit{expression} \texttt{,} \textit{dict-items}} \\
|
||||||
|
\languageline{dict-items}{\textit{expression} \texttt{VT} \textit{expression}} \\ \hline \hline
|
||||||
|
|
||||||
\multicolumn{3}{|c|}{\textbf{Lists}} \\ \hline
|
\multicolumn{3}{|c|}{\textbf{Lists}} \\ \hline
|
||||||
\languageline{optional-ids}{ids} \\
|
\languageline{optional-ids}{ids} \\
|
||||||
@@ -74,6 +94,9 @@
|
|||||||
|
|
||||||
\languageline{expressions}{\textit{expression}, \textit{expressions}} \\
|
\languageline{expressions}{\textit{expression}, \textit{expressions}} \\
|
||||||
\languageline{expressions}{\textit{expression}} \\ \hline
|
\languageline{expressions}{\textit{expression}} \\ \hline
|
||||||
|
|
||||||
|
\languageline{optional-step}{\texttt{GRADV} \textit{expression}} \\
|
||||||
|
\languageline{optional-step}{} \\ \hline
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
\end{center}
|
\end{center}
|
||||||
\end{table}
|
\end{table}
|
||||||
@@ -81,13 +104,14 @@
|
|||||||
\newpage
|
\newpage
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item \textbf{newline}: \\ Newlines are combined, so a single newline is the same as multiple.
|
\item \textbf{newline}: \\ Newlines are combined, so a single newline is the same as multiple.
|
||||||
\item \textbf{module-name}: \\ Modules are flags given to the interpreter/compiler, to let it know you want to be using certain rules, functions, or features.
|
\item \textbf{module-name}: \\ Modules are flags given to the interpreter/compiler, to let it know you want to be using certain rules, functions, or features. Available modules: \texttt{FORS} (randomness), \texttt{FRACTIO} (fractions), \texttt{IASON} (JSON I/O: \texttt{IASON\_LEGE}, \texttt{IASON\_SCRIBE}), \texttt{MAGNVM} (large integers), \texttt{SCRIPTA} (file I/O: \texttt{LEGE}, \texttt{SCRIBE}, \texttt{ADIVNGE}), \texttt{SVBNVLLA} (negative literals), \texttt{RETE} (networking: \texttt{PETE}, \texttt{PETITVR}, \texttt{AVSCVLTA}).
|
||||||
\item \textbf{id}: \\ Variable. Can only consist of lowercase characters and underscores, but not the letters j, u, or w.
|
\item \textbf{id}: \\ Variable. Can only consist of lowercase characters and underscores, but not the letters j, u, or w.
|
||||||
\item \textbf{builtin}: \\ Builtin functions are uppercase latin words.
|
\item \textbf{builtin}: \\ Builtin functions are uppercase latin words.
|
||||||
\item \textbf{string}: \\ Any text encased in " characters.
|
\item \textbf{string}: \\ Any text encased in \texttt{"} or \texttt{'} characters. Single-quoted strings are always literal. Strings support 1-based indexing (\texttt{string[I]}) and inclusive slicing (\texttt{string[I VSQVE III]}), returning single-character strings and substrings respectively.
|
||||||
|
\item \textbf{interpolated-string}: \\ A double-quoted string containing \texttt{\{}\textit{expression}\texttt{\}} segments. Each expression is evaluated and coerced to a string. Use \texttt{\{\{} and \texttt{\}\}} for literal braces.
|
||||||
\item \textbf{numeral}: \\ Roman numerals consisting of the uppercase characters I, V, X, L, C, D, and M. Can also include underscore if the module MAGNVM.
|
\item \textbf{numeral}: \\ Roman numerals consisting of the uppercase characters I, V, X, L, C, D, and M. Can also include underscore if the module MAGNVM.
|
||||||
\item \textbf{bool}: \\ VERITAS or FALSITAS.
|
\item \textbf{bool}: \\ VERITAS or FALSITAS.
|
||||||
\item \textbf{binop}: \\ Binary operators: \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{EST} (equality), \texttt{MINVS} (<), \texttt{PLVS} (>), \texttt{ET} (and), \texttt{AVT} (or), \texttt{\&} (string concatenation).
|
\item \textbf{binop}: \\ Binary operators: \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{RELIQVVM} (modulo), \texttt{EST} (equality), \texttt{DISPAR} (not-equal), \texttt{MINVS} (<), \texttt{PLVS} (>), \texttt{HAVD\_PLVS} ($\leq$), \texttt{HAVD\_MINVS} ($\geq$), \texttt{ET} (and), \texttt{AVT} (or), \texttt{\&} (string concatenation), \texttt{@} (array concatenation).
|
||||||
\item \textbf{unop}: \\ Unary operators: \texttt{-} (negation), \texttt{NON} (boolean not).
|
\item \textbf{unop}: \\ Unary operators: \texttt{-} (negation), \texttt{NON} (boolean not).
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
|
|||||||
3
pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[tool.pytest.ini_options]
|
||||||
|
python_files = ["[0-9][0-9]_test_*.py", "test_*.py"]
|
||||||
|
testpaths = ["tests"]
|
||||||
8
snippets/aeternvm.cent
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
DESIGNA x VT NVLLVS
|
||||||
|
AETERNVM FAC {
|
||||||
|
DESIGNA x VT x+I
|
||||||
|
SI x EST X TVNC {
|
||||||
|
ERVMPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DIC(x)
|
||||||
BIN
snippets/aeternvm.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
6
snippets/alivd.cent
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
DESIGNA x VT VERITAS
|
||||||
|
SI x TVNC {
|
||||||
|
DIC(I)
|
||||||
|
} ALIVD {
|
||||||
|
DIC(NVLLVS)
|
||||||
|
}
|
||||||
BIN
snippets/alivd.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
8
snippets/alivd_si.cent
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
DESIGNA x VT II
|
||||||
|
SI x EST I TVNC {
|
||||||
|
DIC(I)
|
||||||
|
} ALIVD SI x EST II TVNC {
|
||||||
|
DIC(II)
|
||||||
|
} ALIVD {
|
||||||
|
DIC(III)
|
||||||
|
}
|
||||||
BIN
snippets/alivd_si.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
@@ -1,6 +0,0 @@
|
|||||||
DESIGNA x VT VERITAS
|
|
||||||
SI x TVNC {
|
|
||||||
DICE(I)
|
|
||||||
} ALVID {
|
|
||||||
DICE(NVLLVS)
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,8 +0,0 @@
|
|||||||
DESIGNA x VT II
|
|
||||||
SI x EST I TVNC {
|
|
||||||
DICE(I)
|
|
||||||
} ALVID SI x EST II TVNC {
|
|
||||||
DICE(II)
|
|
||||||
} ALVID {
|
|
||||||
DICE(III)
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 26 KiB |
2
snippets/array_concat.cent
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DESIGNA x VT [I, II, III] @ [IV, V]
|
||||||
|
DIC(x)
|
||||||
BIN
snippets/array_concat.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
@@ -1,2 +1,2 @@
|
|||||||
DESIGNA x VT [I, II, III]
|
DESIGNA x VT [I, II, III]
|
||||||
DICE(x[I])
|
DIC(x[I])
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.6 KiB |
2
snippets/array_slice.cent
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DESIGNA x VT [X, XX, XXX, XL, L]
|
||||||
|
DIC(x[II VSQVE IV])
|
||||||
BIN
snippets/array_slice.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.1 KiB |
@@ -1,9 +1,9 @@
|
|||||||
DESIGNA x VT VERITAS
|
DESIGNA x VT VERITAS
|
||||||
DESIGNA y VT FALSITAS
|
DESIGNA y VT FALSITAS
|
||||||
SI x ET y TVNC {
|
SI x ET y TVNC {
|
||||||
DICE(I)
|
DIC(I)
|
||||||
} ALVID SI x AVT y TVNC {
|
} ALIVD SI x AVT y TVNC {
|
||||||
DICE(II)
|
DIC(II)
|
||||||
} ALVID {
|
} ALIVD {
|
||||||
DICE(III)
|
DIC(III)
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
6
snippets/compound.cent
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
DESIGNA x VT V
|
||||||
|
x AVGE III
|
||||||
|
x MINVE II
|
||||||
|
x MVLTIPLICA IV
|
||||||
|
x DIVIDE III
|
||||||
|
DIC(x)
|
||||||
BIN
snippets/compound.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
4
snippets/confla.cent
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
DEFINI addi (a, b) VT {
|
||||||
|
REDI (a + b)
|
||||||
|
}
|
||||||
|
DIC (CONFLA([I, II, III, IV, V], I, addi))
|
||||||
BIN
snippets/confla.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
3
snippets/cribra.cent
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DIC (CRIBRA([I, II, III, IV, V, VI], FVNCTIO (x) VT {
|
||||||
|
REDI (x HAVD_PLVS III)
|
||||||
|
}))
|
||||||
BIN
snippets/cribra.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
1
snippets/destructure_array.cent
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DESIGNA a, b, c VT [I, II, III]
|
||||||
BIN
snippets/destructure_array.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
2
snippets/destructure_fn.cent
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DEFINI pair (a, b) VT { REDI (a, b) }
|
||||||
|
DESIGNA x, y VT INVOCA pair (III, VII)
|
||||||
BIN
snippets/destructure_fn.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
2
snippets/dic.cent
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DIC("Hello, world!")
|
||||||
|
DIC(I, II, III)
|
||||||
BIN
snippets/dic.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
@@ -1,2 +0,0 @@
|
|||||||
DICE("Hello, world!")
|
|
||||||
DICE(I, II, III)
|
|
||||||
|
Before Width: | Height: | Size: 9.6 KiB |
3
snippets/dict_access.cent
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DIC(d["nomen"])
|
||||||
|
DESIGNA d["aetas"] VT XXVI
|
||||||
|
DESIGNA d["novus"] VT I
|
||||||
BIN
snippets/dict_access.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
1
snippets/dict_create.cent
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DESIGNA d VT TABVLA {"nomen" VT "Marcus", "aetas" VT XXV}
|
||||||
BIN
snippets/dict_create.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
3
snippets/dict_per.cent
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
PER k IN d FAC {
|
||||||
|
DIC(k)
|
||||||
|
}
|
||||||
BIN
snippets/dict_per.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
@@ -1,5 +1,5 @@
|
|||||||
DESIGNA x VT NVLLVS
|
DESIGNA x VT NVLLVS
|
||||||
DONICVM y VT NVLLVS VSQVE X FACE {
|
DONICVM y VT NVLLVS VSQVE X FAC {
|
||||||
DESIGNA x VT x + y
|
DESIGNA x VT x + y
|
||||||
}
|
}
|
||||||
DICE(x)
|
DIC(x)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 26 KiB |
5
snippets/donicvm_gradv.cent
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
DESIGNA s VT NVLLVS
|
||||||
|
DONICVM i VT I VSQVE X GRADV II FAC {
|
||||||
|
s AVGE i
|
||||||
|
}
|
||||||
|
DIC(s)
|
||||||
BIN
snippets/donicvm_gradv.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
@@ -1,5 +1,5 @@
|
|||||||
DESIGNA x VT NVLLVS
|
DESIGNA x VT NVLLVS
|
||||||
DVM x PLVS X FACE {
|
DVM x PLVS X FAC {
|
||||||
DESIGNA x VT x+I
|
DESIGNA x VT x+I
|
||||||
}
|
}
|
||||||
DICE(x)
|
DIC(x)
|
||||||
|
|||||||
BIN
snippets/dvm.png
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@@ -1,9 +1,9 @@
|
|||||||
DEFINI fib(x) VT {
|
DEFINI fib(x) VT {
|
||||||
SI x EST NVLLVS TVNC {
|
SI x EST NVLLVS TVNC {
|
||||||
REDI(NVLLVS)
|
REDI(NVLLVS)
|
||||||
} ALVID SI x EST I TVNC {
|
} ALIVD SI x EST I TVNC {
|
||||||
REDI(I)
|
REDI(I)
|
||||||
} ALVID {
|
} ALIVD {
|
||||||
REDI(INVOCA fib(x-II) + INVOCA fib(x-I))
|
REDI(INVOCA fib(x-II) + INVOCA fib(x-I))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
@@ -1,5 +1,5 @@
|
|||||||
DEFINI sqvare(x) VT {
|
DEFINI sqvare(x) VT {
|
||||||
REDI(x*x)
|
REDI(x*x)
|
||||||
}
|
}
|
||||||
|
|
||||||
DICE(INVOCA sqvare(XI))
|
DIC(INVOCA sqvare(XI))
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
9
snippets/fvnctio.cent
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
DEFINI apply (f, x) VT {
|
||||||
|
REDI (INVOCA f (x))
|
||||||
|
}
|
||||||
|
|
||||||
|
DESIGNA dbl VT FVNCTIO (n) VT {
|
||||||
|
REDI (n * II)
|
||||||
|
}
|
||||||
|
|
||||||
|
DIC(INVOCA apply (dbl, VII))
|
||||||
BIN
snippets/fvnctio.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
@@ -1,15 +1,15 @@
|
|||||||
CVM FORS
|
CVM FORS
|
||||||
|
|
||||||
DESIGNA correct VT FORTIS_NVMERVS(I,C)
|
DESIGNA correct VT FORTVITVS_NVMERVS(I,C)
|
||||||
DESIGNA gvess VT NVLLVS
|
DESIGNA gvess VT NVLLVS
|
||||||
|
|
||||||
DVM correct EST gvess FACE {
|
DVM correct EST gvess FAC {
|
||||||
DESIGNA gvess VT AVDI_NVMERVS()
|
DESIGNA gvess VT AVDI_NVMERVS()
|
||||||
SI gvess MINVS correct TVNC {
|
SI gvess MINVS correct TVNC {
|
||||||
DICE("Too low!")
|
DIC("Too low!")
|
||||||
} ALVID SI gvess PLVS correct TVNC {
|
} ALIVD SI gvess PLVS correct TVNC {
|
||||||
DICE("Too high!")
|
DIC("Too high!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DICE("You guessed correctly!")
|
DIC("You guessed correctly!")
|
||||||
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
@@ -1,2 +1,2 @@
|
|||||||
DESIGNA x VT "Hello World!"
|
DESIGNA x VT "Hello World!"
|
||||||
DICE(x)
|
DIC(x)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
1
snippets/iason.cent
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CVM IASON
|
||||||
BIN
snippets/iason.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
5
snippets/iason_lege.cent
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
CVM IASON
|
||||||
|
DESIGNA data VT IASON_LEGE('{"nomen": "Marcus", "anni": 30, "armorum": ["gladius", "scutum"]}')
|
||||||
|
DIC(data["nomen"])
|
||||||
|
DIC(data["anni"])
|
||||||
|
DIC(data["armorum"])
|
||||||
BIN
snippets/iason_lege.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
3
snippets/iason_scribe.cent
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
CVM IASON
|
||||||
|
DESIGNA persona VT TABVLA {"nomen" VT "Marcus", "anni" VT XXX}
|
||||||
|
DIC(IASON_SCRIBE(persona))
|
||||||
BIN
snippets/iason_scribe.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
11
snippets/invoca_expr.cent
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Immediately invoked
|
||||||
|
DIC(INVOCA FVNCTIO (x) VT { REDI (x + I) } (V))
|
||||||
|
|
||||||
|
// From an array
|
||||||
|
DESIGNA fns VT [FVNCTIO (x) VT { REDI (x + I) }]
|
||||||
|
DIC(INVOCA fns[I] (V))
|
||||||
|
|
||||||
|
// Passing a named function as an argument
|
||||||
|
DEFINI apply (f, x) VT { REDI (INVOCA f (x)) }
|
||||||
|
DEFINI sqr (x) VT { REDI (x * x) }
|
||||||
|
DIC(INVOCA apply (sqr, IV))
|
||||||
BIN
snippets/invoca_expr.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
3
snippets/littera.cent
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DESIGNA n VT VII
|
||||||
|
DESIGNA s VT LITTERA(n) & " est septem"
|
||||||
|
DIC(s)
|
||||||
BIN
snippets/littera.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
4
snippets/mvta.cent
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
DEFINI dbl (x) VT {
|
||||||
|
REDI (x + x)
|
||||||
|
}
|
||||||
|
DIC (MVTA([I, II, III, IV], dbl))
|
||||||
BIN
snippets/mvta.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
4
snippets/ordina_cmp.cent
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
DEFINI gt (a, b) VT {
|
||||||
|
REDI (a PLVS b)
|
||||||
|
}
|
||||||
|
DIC (ORDINA([II, V, I, III], gt))
|
||||||
BIN
snippets/ordina_cmp.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
@@ -1,4 +1,4 @@
|
|||||||
DESIGNA x VT [I, II, III, IV, V]
|
DESIGNA x VT [I, II, III, IV, V]
|
||||||
PER y IN x FACE {
|
PER y IN x FAC {
|
||||||
DICE(y)
|
DIC(y)
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
snippets/per.png
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |