mirror of
https://github.com/duhanbalci/dexpr.git
synced 2026-08-30 16:01:41 +00:00
0.4.0
This commit is contained in:
@@ -62,7 +62,8 @@ let result = vm.execute()?; // Returns last expression's value
|
|||||||
The dexpr language supports:
|
The dexpr language supports:
|
||||||
- If/else conditionals with `if ... then ... else ... end`
|
- If/else conditionals with `if ... then ... else ... end`
|
||||||
- String methods (e.g., `.upper()`, `.lower()`, `.trim()`, `.trimStart()`, `.trimEnd()`, `.split()`, `.replace()`, `.contains()`, `.startsWith()`, `.endsWith()`, `.length`, `.charAt()`, `.substring()`)
|
- String methods (e.g., `.upper()`, `.lower()`, `.trim()`, `.trimStart()`, `.trimEnd()`, `.split()`, `.replace()`, `.contains()`, `.startsWith()`, `.endsWith()`, `.length`, `.charAt()`, `.substring()`)
|
||||||
- Arithmetic (`+`, `-`, `*`, `/`, `%`, `**`), comparison, and logical operators
|
- Arithmetic (`+`, `-`, `*`, `/`, `%`, `**`), comparison, and logical operators. `==`/`!=` work on all types (structural, different types → `false`); `<`/`<=`/`>`/`>=` work on Number and String (lexicographic)
|
||||||
|
- `null` literal (`x == null`)
|
||||||
- `in` operator for membership testing (`"finans" in categories`, `5 in numbers`, `"hello" in "hello world"`, `"key" in obj`)
|
- `in` operator for membership testing (`"finans" in categories`, `5 in numbers`, `"hello" in "hello world"`, `"key" in obj`)
|
||||||
- Compound assignments (`+=`, `-=`, `*=`, `/=`, `%=`)
|
- Compound assignments (`+=`, `-=`, `*=`, `/=`, `%=`)
|
||||||
- Built-in `log()` function for output and `rand(min, max)` for random integers
|
- Built-in `log()` function for output and `rand(min, max)` for random integers
|
||||||
|
|||||||
Generated
+1
-1
@@ -304,7 +304,7 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dexpr"
|
name = "dexpr"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dexpr"
|
name = "dexpr"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Embeddable expression evaluator and bytecode VM"
|
description = "Embeddable expression evaluator and bytecode VM"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
+2
-1
@@ -74,7 +74,7 @@ Dil küçük olduğundan (7 keyword, ~20 operatör) iki gramer dosyasını sync
|
|||||||
|
|
||||||
### Keyword Yönetimi
|
### Keyword Yönetimi
|
||||||
|
|
||||||
Keyword'ler `@extend` ile tanımlanır — `identifier` token'ından türetilir ama **her zaman** keyword olarak parse edilir. dexpr'de keyword'ler reserved'dır (`if`, `then`, `else`, `end`, `in`, `true`, `false`).
|
Keyword'ler `@extend` ile tanımlanır — `identifier` token'ından türetilir ama **her zaman** keyword olarak parse edilir. dexpr'de keyword'ler reserved'dır (`if`, `then`, `else`, `end`, `in`, `true`, `false`, `null`).
|
||||||
|
|
||||||
### Error Recovery
|
### Error Recovery
|
||||||
|
|
||||||
@@ -95,6 +95,7 @@ Lezer GLR parser kullanır. Bozuk/yarım kod yazılırken:
|
|||||||
|-------|-----------|-----------------|
|
|-------|-----------|-----------------|
|
||||||
| `if`, `then`, `else`, `end`, `in`, `elseIf` | `keyword` | `#7c3aed` (mor) |
|
| `if`, `then`, `else`, `end`, `in`, `elseIf` | `keyword` | `#7c3aed` (mor) |
|
||||||
| `true`, `false` | `bool` | `#d97706` (turuncu) |
|
| `true`, `false` | `bool` | `#d97706` (turuncu) |
|
||||||
|
| `null` | `null` | `#d97706` (turuncu) |
|
||||||
| `"string"`, `'string'` | `string` | `#059669` (yeşil) |
|
| `"string"`, `'string'` | `string` | `#059669` (yeşil) |
|
||||||
| `42`, `3.14` | `number` | `#2563eb` (mavi) |
|
| `42`, `3.14` | `number` | `#2563eb` (mavi) |
|
||||||
| `// comment`, `/* comment */` | `lineComment` / `blockComment` | `#9ca3af` (gri, italic) |
|
| `// comment`, `/* comment */` | `lineComment` / `blockComment` | `#9ca3af` (gri, italic) |
|
||||||
|
|||||||
+2
-1
@@ -72,6 +72,7 @@ En düşükten en yükseğe:
|
|||||||
| Sayı | `42`, `3.14` | `Expr::Value(Number)` |
|
| Sayı | `42`, `3.14` | `Expr::Value(Number)` |
|
||||||
| String | `"hello"`, `'world'` | `Expr::Value(String)` |
|
| String | `"hello"`, `'world'` | `Expr::Value(String)` |
|
||||||
| Boolean | `true`, `false` | `Expr::Value(Boolean)` |
|
| Boolean | `true`, `false` | `Expr::Value(Boolean)` |
|
||||||
|
| Null | `null` | `Expr::Value(Null)` |
|
||||||
| Parantezli ifade | `(a + b)` | İç ifade |
|
| Parantezli ifade | `(a + b)` | İç ifade |
|
||||||
| Tekli negatif | `-x` | `Expr::UnaryOp(Neg, x)` |
|
| Tekli negatif | `-x` | `Expr::UnaryOp(Neg, x)` |
|
||||||
| Tekli NOT | `!flag` | `Expr::UnaryOp(Not, flag)` |
|
| Tekli NOT | `!flag` | `Expr::UnaryOp(Not, flag)` |
|
||||||
@@ -84,7 +85,7 @@ En düşükten en yükseğe:
|
|||||||
|
|
||||||
### Ayrılmış Kelimeler
|
### Ayrılmış Kelimeler
|
||||||
|
|
||||||
`if`, `then`, `else`, `end`, `true`, `false`, `in`
|
`if`, `then`, `else`, `end`, `true`, `false`, `null`, `in`
|
||||||
|
|
||||||
### Boşluk ve Yorumlar
|
### Boşluk ve Yorumlar
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -126,7 +126,8 @@ struct VM<'a> {
|
|||||||
- **`handle_neg()`** — Sadece Number tipinde tekli negatif
|
- **`handle_neg()`** — Sadece Number tipinde tekli negatif
|
||||||
|
|
||||||
### Karşılaştırma
|
### Karşılaştırma
|
||||||
- **`compare_op(f, name)`** — Decimal değerler üzerinde karşılaştırma, Boolean döndürür
|
- **`equality_op(negate)`** — `==` / `!=`. Her tip için çalışır, `Value`'nun `PartialEq`'i ile yapısal eşitlik (list, object dahil derin karşılaştırma). Farklı tipler hata vermez, `false` döner (`"1" == 1` → `false`, `x == null` → `x` Null ise `true`)
|
||||||
|
- **`compare_op(f, name)`** — `<`, `<=`, `>`, `>=`. Number/Number ve String/String (lexicographic byte sırası) destekler; diğer kombinasyonlar `InvalidOperation` hatası
|
||||||
|
|
||||||
### Boolean
|
### Boolean
|
||||||
- **`handle_and()`**, **`handle_or()`**, **`handle_not()`** — Boolean register'lar üzerinde mantık operasyonları
|
- **`handle_and()`**, **`handle_or()`**, **`handle_not()`** — Boolean register'lar üzerinde mantık operasyonları
|
||||||
|
|||||||
Vendored
+35
-18
@@ -41,7 +41,7 @@ var import_lr2 = require("@lezer/lr");
|
|||||||
var import_lr = require("@lezer/lr");
|
var import_lr = require("@lezer/lr");
|
||||||
|
|
||||||
// src/parser.terms.js
|
// src/parser.terms.js
|
||||||
var elseIf = 44;
|
var elseIf = 46;
|
||||||
|
|
||||||
// src/tokens.ts
|
// src/tokens.ts
|
||||||
var CH_e = 101;
|
var CH_e = 101;
|
||||||
@@ -81,32 +81,33 @@ var elseIfTokenizer = new import_lr.ExternalTokenizer((input) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// src/parser.js
|
// src/parser.js
|
||||||
var spec_identifier = { __proto__: null, if: 11, true: 21, false: 21, in: 43, then: 69, else: 71, end: 73 };
|
var spec_identifier = { __proto__: null, if: 11, true: 21, false: 21, null: 25, in: 47, then: 73, else: 75, end: 77 };
|
||||||
var parser = import_lr2.LRParser.deserialize({
|
var parser = import_lr2.LRParser.deserialize({
|
||||||
version: 14,
|
version: 14,
|
||||||
states: "+^Q]QQOOOOQP'#Cb'#CbOOQP'#Ce'#CeOwQQO'#CiO`QQO'#CjO#TQRO'#DTO%bQRO'#D_OOQP'#D_'#D_O%iQRO'#D_OOQP'#D]'#D]OOQP'#DU'#DUQ]QQOOOwQQO'#C`O&eQQO,59TO&lQRO'#D_O(zQRO,59UO`QQO,59XO`QQO,59XO`QQO,59XO`QQO,59XO`QQO,59XO)wQQO,59hO)|QQO'#CzOOQP,59i,59iO`QQO,59mOOQP-E7S-E7SO*TQQO,58zOOQP1G.o1G.oO+oQRO1G.sO+vQRO1G.sO-bQRO1G.sO-iQRO1G.sO.[QRO1G.sOOQP'#Cy'#CyOOQP1G/S1G/SO/[QQO'#D`OOQP,59f,59fO/fQQO,59fO/kQRO1G/XO0bQRO1G.fO0oQQO1G/SOOQP7+$i7+$iOwQQO'#DVO1pQQO,59zOOQP1G/Q1G/QO1xQRO7+$QO2TQRO7+$QOwQQO'#DWOOQP7+$Q7+$QO2bQQO7+$QO2iQQO,59qOOQO-E7T-E7TOOQP-E7U-E7UOOQP<<Gl<<GlO2sQQO<<GlO2zQRO<<GlO3VQQO,59rO2sQQO<<GlO3^QQOAN=WOOQPAN=WAN=WO3^QQOAN=WO3eQRO1G/^OOQPG22rG22rO3rQQOG22rO3yQRO7+$xOOQPLD(^LD(^O)wQQO,59hO5RQQO1G.sO5YQQO1G.sO6[QQO1G.sO6cQQO1G.sO6jQQO1G.sO7QQQO,59UOwQQO,59XOwQQO,59XOwQQO,59XOwQQO,59XOwQQO,59XOwQQO'#Cj",
|
states: "+dQ]QQOOOOQP'#Cb'#CbOOQP'#Ce'#CeOOQP'#Cg'#CgOzQQO'#CkO`QQO'#ClO#ZQRO'#DVO%nQRO'#DaOOQP'#Da'#DaO%uQRO'#DaOOQP'#D_'#D_OOQP'#DW'#DWQ]QQOOOzQQO'#C`O&qQQO,59VO&xQRO'#DaO)ZQRO,59WO`QQO,59ZO`QQO,59ZO`QQO,59ZO`QQO,59ZO`QQO,59ZO*ZQQO,59jO*`QQO'#C|OOQP,59k,59kO`QQO,59oOOQP-E7U-E7UO*gQQO,58zOOQP1G.q1G.qO,UQRO1G.uO,]QRO1G.uO-zQRO1G.uO.RQRO1G.uO.tQRO1G.uOOQP'#C{'#C{OOQP1G/U1G/UO/wQQO'#DbOOQP,59h,59hO0RQQO,59hO0WQRO1G/ZO1QQRO1G.fO1_QQO1G/UOOQP7+$k7+$kOzQQO'#DXO2`QQO,59|OOQP1G/S1G/SO2hQRO7+$QO2sQRO7+$QOzQQO'#DYOOQP7+$Q7+$QO3QQQO7+$QO3XQQO,59sOOQO-E7V-E7VOOQP-E7W-E7WOOQP<<Gl<<GlO3cQQO<<GlO3jQRO<<GlO3uQQO,59tO3cQQO<<GlO3|QQOAN=WOOQPAN=WAN=WO3|QQOAN=WO4TQRO1G/`OOQPG22rG22rO4bQQOG22rO4iQRO7+$zOOQPLD(^LD(^O*ZQQO,59jO5qQQO1G.uO5xQQO1G.uO6zQQO1G.uO7RQQO1G.uO7YQQO1G.uO7pQQO,59WOzQQO,59ZOzQQO,59ZOzQQO,59ZOzQQO,59ZOzQQO,59ZOzQQO'#Cl",
|
||||||
stateData: "7n~O!OOSPOSQOS~OT[OVVOWVOYQO[RO_SO`SO!QPO~OVVOWVOYQO[RO_!pO`!pO!QPO~O_cOb`OcaOdbOebOfcOgdOhdOidOjdOleO~OTwXVwXWwXYwX[wX`wX{wX!QwXswXtwX|wX~P!`OvhOT!RXV!RXW!RXY!RX_!RX`!RXb!RXc!RXd!RXe!RXf!RXg!RXh!RXi!RXj!RXl!RX{!RX!Q!RXs!RXt!RX|!RX~O[fO~P#zO[!RX~P#zO_!nOb!kOc!lOd!mOe!mOf!nOg!oOh!oOi!oOj!oOl!dO~OZkO~P%pO[fOZ!RX_!RXb!RXc!RXd!RXe!RXf!RXg!RXh!RXi!RXj!RXl!RXT!RXV!RXW!RXY!RX`!RX{!RX!Q!RXr!RXo!RXs!RXt!RX|!RX~Ob^ac^ad^ae^af^ag^ah^ai^aj^a~O_cOleOT^aV^aW^aY^a[^a`^a{^a!Q^as^at^a|^a~P(]O!QqO~OZtO~PwOrwO~P%pO_cOdbOebOfcOgdOhdOidOjdOleOTaiVaiWaiYai[ai`aibai{ai!Qaisaitai|ai~OcaO~P*[Ocai~P*[O_cOgdOhdOidOjdOleOTaiVaiWaiYai[ai`aibaicaidaieai{ai!Qaisaitai|ai~OfcO~P+}Ofai~P+}Obaicaidaieaifaigaihaiiai~O_cOjdOleOTaiVaiWaiYai[ai`ai{ai!Qaisaitai|ai~P-pOozOZ!SX~P%pOZ|O~OTuiVuiWuiYui[ui`ui{ui!Quisuitui|ui~P!`Os!ROt!QO|!PO~P]O[fOZpi_pibpicpidpiepifpigpihpiipijpilpirpiopi~OozOZ!Sa~Os!WOt!VO|!PO~Os!WOt!VO|!PO~P]Ot!VO~P]OZyaoya~P%pOt!]O~P]Os!^Ot!]O|!PO~Or!_O~P%pOt!`O~P]Oszitzi|zi~P]Ot!cO~P]Oszqtzq|zq~P]O_!nOd!mOe!mOf!nOg!oOh!oOi!oOj!oOl!dOZaibairaioai~Oc!lO~P4WOcai~P4WO_!nOg!oOh!oOi!oOj!oOl!dOZaibaicaidaieairaioai~Of!nO~P5aOfai~P5aO_!nOj!oOl!dOZairaioai~P-pO_!nOl!dOZ^ar^ao^a~P(]OvdjgQPQh~",
|
stateData: "8^~O!QOSPOSQOS~OT]OVWOWWOYQO[RO^SOaTObTO!SPO~OVWOWWOYQO[RO^SOa!qOb!qO!SPO~OadOdaOebOfcOgcOhdOieOjeOkeOleOnfO~OTyXVyXWyXYyX[yX^yXbyX}yX!SyXuyXvyX!OyX~P!fOxiOT!TXV!TXW!TXY!TX[!TXa!TXb!TXd!TXe!TXf!TXg!TXh!TXi!TXj!TXk!TXl!TXn!TX}!TX!S!TXu!TXv!TX!O!TX~O^gO~P$TO^!TX~P$TOa!oOd!lOe!mOf!nOg!nOh!oOi!pOj!pOk!pOl!pOn!eO~O]lO~P%|O^gO]!TXa!TXd!TXe!TXf!TXg!TXh!TXi!TXj!TXk!TXl!TXn!TXT!TXV!TXW!TXY!TX[!TXb!TX}!TX!S!TXt!TXq!TXu!TXv!TX!O!TX~Od`ae`af`ag`ah`ai`aj`ak`al`a~OadOnfOT`aV`aW`aY`a[`a^`ab`a}`a!S`au`av`a!O`a~P(lO!SrO~O]uO~PzOtxO~P%|OadOfcOgcOhdOieOjeOkeOleOnfOTciVciWciYci[ci^cibcidci}ci!Sciucivci!Oci~OebO~P*nOeci~P*nOadOieOjeOkeOleOnfOTciVciWciYci[ci^cibcidciecifcigci}ci!Sciucivci!Oci~OhdO~P,dOhci~P,dOdciecifcigcihciicijcikci~OadOleOnfOTciVciWciYci[ci^cibci}ci!Sciucivci!Oci~P.YOq{O]!UX~P%|O]}O~OTwiVwiWwiYwi[wi^wibwi}wi!Swiuwivwi!Owi~P!fOu!SOv!RO!O!QO~P]O^gO]riaridrierifrigrihriirijrikrilrinritriqri~Oq{O]!Ua~Ou!XOv!WO!O!QO~Ou!XOv!WO!O!QO~P]Ov!WO~P]O]{aq{a~P%|Ov!^O~P]Ou!_Ov!^O!O!QO~Ot!`O~P%|Ov!aO~P]Ou|iv|i!O|i~P]Ov!dO~P]Ou|qv|q!O|q~P]Oa!oOf!nOg!nOh!oOi!pOj!pOk!pOl!pOn!eO]cidcitciqci~Oe!mO~P4vOeci~P4vOa!oOi!pOj!pOk!pOl!pOn!eO]cidciecifcigcitciqci~Oh!oO~P6POhci~P6POa!oOl!pOn!eO]citciqci~P.YOa!oOn!eO]`at`aq`a~P(lOxfliQPQj~",
|
||||||
goto: "'n!TPPPP!UP!dPP#WPPP#W#WPP#WPPPPPPPPP#WP#x$OP$V#WPPP!UP!U$y%e%kPPPP%uP&T'kiXOZw!O!R!W!Z![!^!_!a!bhUOZw!O!R!W!Z![!^!_!a!bu^RS[`abcdfhz!P!k!l!m!n!o!p!_VORSZ[`abcdfhwz!O!P!R!W!Z![!^!_!a!b!k!l!m!n!o!pQreRx!dSgU^RyxtVRS[`abcdfhz!P!k!l!m!n!o!piWOZw!O!R!W!Z![!^!_!a!bQZO[iZ!O!Z![!a!bQ!OwQ!Z!RQ![!WQ!a!^R!b!_Q{sR!T{Q}wS!U}!XR!X!OiYOZw!O!R!W!Z![!^!_!a!bhTOZw!O!R!W!Z![!^!_!a!bQ]RQ_SQj[Ql`QmaQnbQocQpdQsfQvhQ!SzQ!Y!PQ!e!kQ!f!lQ!g!mQ!h!nQ!i!oR!j!pRuf",
|
goto: "'p!VPPPP!WP!fPP#YP#YPPP#Y#YPP#YPPPPPPPPP#YP#z$QP$X#YPPP!WP!W${%g%mPPPP%wP&V'miYO[x!P!S!X![!]!_!`!b!chVO[x!P!S!X![!]!_!`!b!cu_ST]abcdegi{!Q!l!m!n!o!p!q!_WOST[]abcdegix{!P!Q!S!X![!]!_!`!b!c!l!m!n!o!p!qQsfRy!eShV_RzytWST]abcdegi{!Q!l!m!n!o!p!qiXO[x!P!S!X![!]!_!`!b!cQ[O[j[!P![!]!b!cQ!PxQ![!SQ!]!XQ!b!_R!c!`Q|tR!U|Q!OxS!V!O!YR!Y!PiZO[x!P!S!X![!]!_!`!b!chUO[x!P!S!X![!]!_!`!b!cQ^SQ`TQk]QmaQnbQocQpdQqeQtgQwiQ!T{Q!Z!QQ!f!lQ!g!mQ!h!nQ!i!oQ!j!pR!k!qRvg",
|
||||||
nodeNames: "\u26A0 LineComment BlockComment Program IfStatement if VariableName Number String BooleanLiteral BooleanLiteral ) ( ParenExpression UnaryExpression - ! BinaryExpression || && CompareOp in + * / % Power MethodCall . PropertyName ArgList , PropertyAccess FunctionCall then else end Assignment AssignOp ExprStatement",
|
nodeNames: "\u26A0 LineComment BlockComment Program IfStatement if VariableName Number String BooleanLiteral BooleanLiteral NullLiteral NullLiteral ) ( ParenExpression UnaryExpression - ! BinaryExpression || && CompareOp in + * / % Power MethodCall . PropertyName ArgList , PropertyAccess FunctionCall then else end Assignment AssignOp ExprStatement",
|
||||||
maxTerm: 50,
|
maxTerm: 52,
|
||||||
nodeProps: [
|
nodeProps: [
|
||||||
["group", -3, 4, 37, 39, "Statement", -10, 6, 7, 8, 9, 13, 14, 17, 27, 32, 33, "Expression"],
|
["group", -3, 4, 39, 41, "Statement", -11, 6, 7, 8, 9, 11, 15, 16, 19, 29, 34, 35, "Expression"],
|
||||||
["openedBy", 11, "("],
|
["openedBy", 13, "("],
|
||||||
["closedBy", 12, ")"]
|
["closedBy", 14, ")"]
|
||||||
],
|
],
|
||||||
skippedNodes: [0, 1, 2],
|
skippedNodes: [0, 1, 2],
|
||||||
repeatNodeCount: 3,
|
repeatNodeCount: 3,
|
||||||
tokenData: "+p~RiXY!pYZ!p]^!ppq!pqr#Rrs#`uv%Svw%awx%lxy'Zyz'`z{'e{|'u|}'}}!O(S!O!P([!P!Q(a!Q![*X!^!_*r!_!`*z!`!a*r!c!}+S#R#S+S#T#o+S#p#q+e~!uS!O~XY!pYZ!p]^!ppq!p~#WP`~!_!`#Z~#`Od~~#cWOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|<%lO#`~$QOW~~$TRO;'S#`;'S;=`$^;=`O#`~$aXOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|;=`<%l#`<%lO#`~%PP;=`<%l#`~%XPi~!_!`%[~%aOv~~%dPvw%g~%lOc~~%oWOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T<%lO%l~&[RO;'S%l;'S;=`&e;=`O%l~&hXOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T;=`<%l%l<%lO%l~'WP;=`<%l%l~'`O[~~'eOZ~~'jQg~z{'p!_!`%[~'uOj~~'zPf~!_!`%[~(SOo~~(XP_~!_!`%[~(aOl~~(fRh~z{(o!P!Q)p!_!`%[~(rTOz(oz{)R{;'S(o;'S;=`)j<%lO(o~)UTO!P(o!P!Q)e!Q;'S(o;'S;=`)j<%lO(o~)jOQ~~)mP;=`<%l(o~)uSP~OY)pZ;'S)p;'S;=`*R<%lO)p~*UP;=`<%l)p~*^QV~!O!P*d!Q![*X~*gP!Q![*j~*oPV~!Q![*j~*wPd~!_!`#Z~+PPv~!_!`#Z~+XS!Q~!Q![+S!c!}+S#R#S+S#T#o+S~+hP#p#q+k~+pOb~",
|
tokenData: "+p~RiXY!pYZ!p]^!ppq!pqr#Rrs#`uv%Svw%awx%lxy'Zyz'`z{'e{|'u|}'}}!O(S!O!P([!P!Q(a!Q![*X!^!_*r!_!`*z!`!a*r!c!}+S#R#S+S#T#o+S#p#q+e~!uS!Q~XY!pYZ!p]^!ppq!p~#WPb~!_!`#Z~#`Of~~#cWOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|<%lO#`~$QOW~~$TRO;'S#`;'S;=`$^;=`O#`~$aXOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|;=`<%l#`<%lO#`~%PP;=`<%l#`~%XPk~!_!`%[~%aOx~~%dPvw%g~%lOe~~%oWOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T<%lO%l~&[RO;'S%l;'S;=`&e;=`O%l~&hXOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T;=`<%l%l<%lO%l~'WP;=`<%l%l~'`O^~~'eO]~~'jQi~z{'p!_!`%[~'uOl~~'zPh~!_!`%[~(SOq~~(XPa~!_!`%[~(aOn~~(fRj~z{(o!P!Q)p!_!`%[~(rTOz(oz{)R{;'S(o;'S;=`)j<%lO(o~)UTO!P(o!P!Q)e!Q;'S(o;'S;=`)j<%lO(o~)jOQ~~)mP;=`<%l(o~)uSP~OY)pZ;'S)p;'S;=`*R<%lO)p~*UP;=`<%l)p~*^QV~!O!P*d!Q![*X~*gP!Q![*j~*oPV~!Q![*j~*wPf~!_!`#Z~+PPx~!_!`#Z~+XS!S~!Q![+S!c!}+S#R#S+S#T#o+S~+hP#p#q+k~+pOd~",
|
||||||
tokenizers: [elseIfTokenizer, 0],
|
tokenizers: [elseIfTokenizer, 0],
|
||||||
topRules: { "Program": [0, 3] },
|
topRules: { "Program": [0, 3] },
|
||||||
specialized: [{ term: 48, get: (value) => spec_identifier[value] || -1 }],
|
specialized: [{ term: 50, get: (value) => spec_identifier[value] || -1 }],
|
||||||
tokenPrec: 1033
|
tokenPrec: 1063
|
||||||
});
|
});
|
||||||
|
|
||||||
// src/language.ts
|
// src/language.ts
|
||||||
var dexprHighlighting = (0, import_highlight.styleTags)({
|
var dexprHighlighting = (0, import_highlight.styleTags)({
|
||||||
"if then else end in elseIf": import_highlight.tags.keyword,
|
"if then else end in elseIf": import_highlight.tags.keyword,
|
||||||
BooleanLiteral: import_highlight.tags.bool,
|
BooleanLiteral: import_highlight.tags.bool,
|
||||||
|
NullLiteral: import_highlight.tags.null,
|
||||||
String: import_highlight.tags.string,
|
String: import_highlight.tags.string,
|
||||||
Number: import_highlight.tags.number,
|
Number: import_highlight.tags.number,
|
||||||
LineComment: import_highlight.tags.lineComment,
|
LineComment: import_highlight.tags.lineComment,
|
||||||
@@ -165,6 +166,7 @@ var KEYWORDS = [
|
|||||||
{ label: "end", type: "keyword" },
|
{ label: "end", type: "keyword" },
|
||||||
{ label: "true", type: "keyword", detail: "Boolean" },
|
{ label: "true", type: "keyword", detail: "Boolean" },
|
||||||
{ label: "false", type: "keyword", detail: "Boolean" },
|
{ label: "false", type: "keyword", detail: "Boolean" },
|
||||||
|
{ label: "null", type: "keyword", detail: "Null" },
|
||||||
{ label: "in", type: "keyword", detail: "membership test" }
|
{ label: "in", type: "keyword", detail: "membership test" }
|
||||||
];
|
];
|
||||||
function inferVariableTypes(context, knownVars) {
|
function inferVariableTypes(context, knownVars) {
|
||||||
@@ -225,6 +227,10 @@ function inferExprType(node, doc, knownTypes) {
|
|||||||
if (rootType === "Object") {
|
if (rootType === "Object") {
|
||||||
return knownTypes.get(`${varName}.${fieldName}`) ?? null;
|
return knownTypes.get(`${varName}.${fieldName}`) ?? null;
|
||||||
}
|
}
|
||||||
|
if (rootType === "List") {
|
||||||
|
const fieldType = knownTypes.get(`${varName}.${fieldName}`) ?? null;
|
||||||
|
return projectedListType(fieldType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -298,6 +304,11 @@ function inferMethodReturnType(method) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function projectedListType(fieldType) {
|
||||||
|
if (fieldType === "Number") return "NumberList";
|
||||||
|
if (fieldType === "String") return "StringList";
|
||||||
|
return "List";
|
||||||
|
}
|
||||||
function dedup(items) {
|
function dedup(items) {
|
||||||
const seen = /* @__PURE__ */ new Set();
|
const seen = /* @__PURE__ */ new Set();
|
||||||
return items.filter((item) => {
|
return items.filter((item) => {
|
||||||
@@ -367,12 +378,16 @@ function dexprCompletion(info) {
|
|||||||
if (path.length === 1) return { type: rootType, path };
|
if (path.length === 1) return { type: rootType, path };
|
||||||
let currentType = rootType;
|
let currentType = rootType;
|
||||||
for (let i = 1; i < path.length; i++) {
|
for (let i = 1; i < path.length; i++) {
|
||||||
if (currentType !== "Object") {
|
if (currentType === "Object") {
|
||||||
|
const key = `${path[i - 1]}.${path[i]}`;
|
||||||
|
currentType = varTypes.get(key) ?? null;
|
||||||
|
} else if (currentType === "List") {
|
||||||
|
const key = `${path[0]}.${path[i]}`;
|
||||||
|
const fieldType = varTypes.get(key) ?? null;
|
||||||
|
currentType = projectedListType(fieldType);
|
||||||
|
} else {
|
||||||
return { type: currentType, path };
|
return { type: currentType, path };
|
||||||
}
|
}
|
||||||
const key = `${path[i - 1]}.${path[i]}`;
|
|
||||||
const fieldType = varTypes.get(key) ?? null;
|
|
||||||
currentType = fieldType;
|
|
||||||
}
|
}
|
||||||
return { type: currentType, path };
|
return { type: currentType, path };
|
||||||
}
|
}
|
||||||
@@ -403,8 +418,9 @@ function dexprCompletion(info) {
|
|||||||
options = [...fieldItems, ...objMethods];
|
options = [...fieldItems, ...objMethods];
|
||||||
} else if (finalType === "List") {
|
} else if (finalType === "List") {
|
||||||
const rootVarName = path[0];
|
const rootVarName = path[0];
|
||||||
|
const fieldItems = objectFieldCompletions.get(rootVarName) ?? [];
|
||||||
const listMethods = methodsByType["List"] ?? [];
|
const listMethods = methodsByType["List"] ?? [];
|
||||||
options = [...listMethods];
|
options = [...fieldItems, ...listMethods];
|
||||||
} else if (finalType) {
|
} else if (finalType) {
|
||||||
options = methodsByType[finalType] ?? allMethods;
|
options = methodsByType[finalType] ?? allMethods;
|
||||||
} else {
|
} else {
|
||||||
@@ -436,6 +452,7 @@ var import_highlight2 = require("@lezer/highlight");
|
|||||||
var dexprHighlightStyle = import_language3.HighlightStyle.define([
|
var dexprHighlightStyle = import_language3.HighlightStyle.define([
|
||||||
{ tag: import_highlight2.tags.keyword, color: "#7c3aed" },
|
{ tag: import_highlight2.tags.keyword, color: "#7c3aed" },
|
||||||
{ tag: import_highlight2.tags.bool, color: "#d97706" },
|
{ tag: import_highlight2.tags.bool, color: "#d97706" },
|
||||||
|
{ tag: import_highlight2.tags.null, color: "#d97706" },
|
||||||
{ tag: import_highlight2.tags.string, color: "#059669" },
|
{ tag: import_highlight2.tags.string, color: "#059669" },
|
||||||
{ tag: import_highlight2.tags.number, color: "#2563eb" },
|
{ tag: import_highlight2.tags.number, color: "#2563eb" },
|
||||||
{ tag: import_highlight2.tags.lineComment, color: "#9ca3af", fontStyle: "italic" },
|
{ tag: import_highlight2.tags.lineComment, color: "#9ca3af", fontStyle: "italic" },
|
||||||
|
|||||||
Vendored
+35
-18
@@ -12,7 +12,7 @@ import { LRParser } from "@lezer/lr";
|
|||||||
import { ExternalTokenizer } from "@lezer/lr";
|
import { ExternalTokenizer } from "@lezer/lr";
|
||||||
|
|
||||||
// src/parser.terms.js
|
// src/parser.terms.js
|
||||||
var elseIf = 44;
|
var elseIf = 46;
|
||||||
|
|
||||||
// src/tokens.ts
|
// src/tokens.ts
|
||||||
var CH_e = 101;
|
var CH_e = 101;
|
||||||
@@ -52,32 +52,33 @@ var elseIfTokenizer = new ExternalTokenizer((input) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// src/parser.js
|
// src/parser.js
|
||||||
var spec_identifier = { __proto__: null, if: 11, true: 21, false: 21, in: 43, then: 69, else: 71, end: 73 };
|
var spec_identifier = { __proto__: null, if: 11, true: 21, false: 21, null: 25, in: 47, then: 73, else: 75, end: 77 };
|
||||||
var parser = LRParser.deserialize({
|
var parser = LRParser.deserialize({
|
||||||
version: 14,
|
version: 14,
|
||||||
states: "+^Q]QQOOOOQP'#Cb'#CbOOQP'#Ce'#CeOwQQO'#CiO`QQO'#CjO#TQRO'#DTO%bQRO'#D_OOQP'#D_'#D_O%iQRO'#D_OOQP'#D]'#D]OOQP'#DU'#DUQ]QQOOOwQQO'#C`O&eQQO,59TO&lQRO'#D_O(zQRO,59UO`QQO,59XO`QQO,59XO`QQO,59XO`QQO,59XO`QQO,59XO)wQQO,59hO)|QQO'#CzOOQP,59i,59iO`QQO,59mOOQP-E7S-E7SO*TQQO,58zOOQP1G.o1G.oO+oQRO1G.sO+vQRO1G.sO-bQRO1G.sO-iQRO1G.sO.[QRO1G.sOOQP'#Cy'#CyOOQP1G/S1G/SO/[QQO'#D`OOQP,59f,59fO/fQQO,59fO/kQRO1G/XO0bQRO1G.fO0oQQO1G/SOOQP7+$i7+$iOwQQO'#DVO1pQQO,59zOOQP1G/Q1G/QO1xQRO7+$QO2TQRO7+$QOwQQO'#DWOOQP7+$Q7+$QO2bQQO7+$QO2iQQO,59qOOQO-E7T-E7TOOQP-E7U-E7UOOQP<<Gl<<GlO2sQQO<<GlO2zQRO<<GlO3VQQO,59rO2sQQO<<GlO3^QQOAN=WOOQPAN=WAN=WO3^QQOAN=WO3eQRO1G/^OOQPG22rG22rO3rQQOG22rO3yQRO7+$xOOQPLD(^LD(^O)wQQO,59hO5RQQO1G.sO5YQQO1G.sO6[QQO1G.sO6cQQO1G.sO6jQQO1G.sO7QQQO,59UOwQQO,59XOwQQO,59XOwQQO,59XOwQQO,59XOwQQO,59XOwQQO'#Cj",
|
states: "+dQ]QQOOOOQP'#Cb'#CbOOQP'#Ce'#CeOOQP'#Cg'#CgOzQQO'#CkO`QQO'#ClO#ZQRO'#DVO%nQRO'#DaOOQP'#Da'#DaO%uQRO'#DaOOQP'#D_'#D_OOQP'#DW'#DWQ]QQOOOzQQO'#C`O&qQQO,59VO&xQRO'#DaO)ZQRO,59WO`QQO,59ZO`QQO,59ZO`QQO,59ZO`QQO,59ZO`QQO,59ZO*ZQQO,59jO*`QQO'#C|OOQP,59k,59kO`QQO,59oOOQP-E7U-E7UO*gQQO,58zOOQP1G.q1G.qO,UQRO1G.uO,]QRO1G.uO-zQRO1G.uO.RQRO1G.uO.tQRO1G.uOOQP'#C{'#C{OOQP1G/U1G/UO/wQQO'#DbOOQP,59h,59hO0RQQO,59hO0WQRO1G/ZO1QQRO1G.fO1_QQO1G/UOOQP7+$k7+$kOzQQO'#DXO2`QQO,59|OOQP1G/S1G/SO2hQRO7+$QO2sQRO7+$QOzQQO'#DYOOQP7+$Q7+$QO3QQQO7+$QO3XQQO,59sOOQO-E7V-E7VOOQP-E7W-E7WOOQP<<Gl<<GlO3cQQO<<GlO3jQRO<<GlO3uQQO,59tO3cQQO<<GlO3|QQOAN=WOOQPAN=WAN=WO3|QQOAN=WO4TQRO1G/`OOQPG22rG22rO4bQQOG22rO4iQRO7+$zOOQPLD(^LD(^O*ZQQO,59jO5qQQO1G.uO5xQQO1G.uO6zQQO1G.uO7RQQO1G.uO7YQQO1G.uO7pQQO,59WOzQQO,59ZOzQQO,59ZOzQQO,59ZOzQQO,59ZOzQQO,59ZOzQQO'#Cl",
|
||||||
stateData: "7n~O!OOSPOSQOS~OT[OVVOWVOYQO[RO_SO`SO!QPO~OVVOWVOYQO[RO_!pO`!pO!QPO~O_cOb`OcaOdbOebOfcOgdOhdOidOjdOleO~OTwXVwXWwXYwX[wX`wX{wX!QwXswXtwX|wX~P!`OvhOT!RXV!RXW!RXY!RX_!RX`!RXb!RXc!RXd!RXe!RXf!RXg!RXh!RXi!RXj!RXl!RX{!RX!Q!RXs!RXt!RX|!RX~O[fO~P#zO[!RX~P#zO_!nOb!kOc!lOd!mOe!mOf!nOg!oOh!oOi!oOj!oOl!dO~OZkO~P%pO[fOZ!RX_!RXb!RXc!RXd!RXe!RXf!RXg!RXh!RXi!RXj!RXl!RXT!RXV!RXW!RXY!RX`!RX{!RX!Q!RXr!RXo!RXs!RXt!RX|!RX~Ob^ac^ad^ae^af^ag^ah^ai^aj^a~O_cOleOT^aV^aW^aY^a[^a`^a{^a!Q^as^at^a|^a~P(]O!QqO~OZtO~PwOrwO~P%pO_cOdbOebOfcOgdOhdOidOjdOleOTaiVaiWaiYai[ai`aibai{ai!Qaisaitai|ai~OcaO~P*[Ocai~P*[O_cOgdOhdOidOjdOleOTaiVaiWaiYai[ai`aibaicaidaieai{ai!Qaisaitai|ai~OfcO~P+}Ofai~P+}Obaicaidaieaifaigaihaiiai~O_cOjdOleOTaiVaiWaiYai[ai`ai{ai!Qaisaitai|ai~P-pOozOZ!SX~P%pOZ|O~OTuiVuiWuiYui[ui`ui{ui!Quisuitui|ui~P!`Os!ROt!QO|!PO~P]O[fOZpi_pibpicpidpiepifpigpihpiipijpilpirpiopi~OozOZ!Sa~Os!WOt!VO|!PO~Os!WOt!VO|!PO~P]Ot!VO~P]OZyaoya~P%pOt!]O~P]Os!^Ot!]O|!PO~Or!_O~P%pOt!`O~P]Oszitzi|zi~P]Ot!cO~P]Oszqtzq|zq~P]O_!nOd!mOe!mOf!nOg!oOh!oOi!oOj!oOl!dOZaibairaioai~Oc!lO~P4WOcai~P4WO_!nOg!oOh!oOi!oOj!oOl!dOZaibaicaidaieairaioai~Of!nO~P5aOfai~P5aO_!nOj!oOl!dOZairaioai~P-pO_!nOl!dOZ^ar^ao^a~P(]OvdjgQPQh~",
|
stateData: "8^~O!QOSPOSQOS~OT]OVWOWWOYQO[RO^SOaTObTO!SPO~OVWOWWOYQO[RO^SOa!qOb!qO!SPO~OadOdaOebOfcOgcOhdOieOjeOkeOleOnfO~OTyXVyXWyXYyX[yX^yXbyX}yX!SyXuyXvyX!OyX~P!fOxiOT!TXV!TXW!TXY!TX[!TXa!TXb!TXd!TXe!TXf!TXg!TXh!TXi!TXj!TXk!TXl!TXn!TX}!TX!S!TXu!TXv!TX!O!TX~O^gO~P$TO^!TX~P$TOa!oOd!lOe!mOf!nOg!nOh!oOi!pOj!pOk!pOl!pOn!eO~O]lO~P%|O^gO]!TXa!TXd!TXe!TXf!TXg!TXh!TXi!TXj!TXk!TXl!TXn!TXT!TXV!TXW!TXY!TX[!TXb!TX}!TX!S!TXt!TXq!TXu!TXv!TX!O!TX~Od`ae`af`ag`ah`ai`aj`ak`al`a~OadOnfOT`aV`aW`aY`a[`a^`ab`a}`a!S`au`av`a!O`a~P(lO!SrO~O]uO~PzOtxO~P%|OadOfcOgcOhdOieOjeOkeOleOnfOTciVciWciYci[ci^cibcidci}ci!Sciucivci!Oci~OebO~P*nOeci~P*nOadOieOjeOkeOleOnfOTciVciWciYci[ci^cibcidciecifcigci}ci!Sciucivci!Oci~OhdO~P,dOhci~P,dOdciecifcigcihciicijcikci~OadOleOnfOTciVciWciYci[ci^cibci}ci!Sciucivci!Oci~P.YOq{O]!UX~P%|O]}O~OTwiVwiWwiYwi[wi^wibwi}wi!Swiuwivwi!Owi~P!fOu!SOv!RO!O!QO~P]O^gO]riaridrierifrigrihriirijrikrilrinritriqri~Oq{O]!Ua~Ou!XOv!WO!O!QO~Ou!XOv!WO!O!QO~P]Ov!WO~P]O]{aq{a~P%|Ov!^O~P]Ou!_Ov!^O!O!QO~Ot!`O~P%|Ov!aO~P]Ou|iv|i!O|i~P]Ov!dO~P]Ou|qv|q!O|q~P]Oa!oOf!nOg!nOh!oOi!pOj!pOk!pOl!pOn!eO]cidcitciqci~Oe!mO~P4vOeci~P4vOa!oOi!pOj!pOk!pOl!pOn!eO]cidciecifcigcitciqci~Oh!oO~P6POhci~P6POa!oOl!pOn!eO]citciqci~P.YOa!oOn!eO]`at`aq`a~P(lOxfliQPQj~",
|
||||||
goto: "'n!TPPPP!UP!dPP#WPPP#W#WPP#WPPPPPPPPP#WP#x$OP$V#WPPP!UP!U$y%e%kPPPP%uP&T'kiXOZw!O!R!W!Z![!^!_!a!bhUOZw!O!R!W!Z![!^!_!a!bu^RS[`abcdfhz!P!k!l!m!n!o!p!_VORSZ[`abcdfhwz!O!P!R!W!Z![!^!_!a!b!k!l!m!n!o!pQreRx!dSgU^RyxtVRS[`abcdfhz!P!k!l!m!n!o!piWOZw!O!R!W!Z![!^!_!a!bQZO[iZ!O!Z![!a!bQ!OwQ!Z!RQ![!WQ!a!^R!b!_Q{sR!T{Q}wS!U}!XR!X!OiYOZw!O!R!W!Z![!^!_!a!bhTOZw!O!R!W!Z![!^!_!a!bQ]RQ_SQj[Ql`QmaQnbQocQpdQsfQvhQ!SzQ!Y!PQ!e!kQ!f!lQ!g!mQ!h!nQ!i!oR!j!pRuf",
|
goto: "'p!VPPPP!WP!fPP#YP#YPPP#Y#YPP#YPPPPPPPPP#YP#z$QP$X#YPPP!WP!W${%g%mPPPP%wP&V'miYO[x!P!S!X![!]!_!`!b!chVO[x!P!S!X![!]!_!`!b!cu_ST]abcdegi{!Q!l!m!n!o!p!q!_WOST[]abcdegix{!P!Q!S!X![!]!_!`!b!c!l!m!n!o!p!qQsfRy!eShV_RzytWST]abcdegi{!Q!l!m!n!o!p!qiXO[x!P!S!X![!]!_!`!b!cQ[O[j[!P![!]!b!cQ!PxQ![!SQ!]!XQ!b!_R!c!`Q|tR!U|Q!OxS!V!O!YR!Y!PiZO[x!P!S!X![!]!_!`!b!chUO[x!P!S!X![!]!_!`!b!cQ^SQ`TQk]QmaQnbQocQpdQqeQtgQwiQ!T{Q!Z!QQ!f!lQ!g!mQ!h!nQ!i!oQ!j!pR!k!qRvg",
|
||||||
nodeNames: "\u26A0 LineComment BlockComment Program IfStatement if VariableName Number String BooleanLiteral BooleanLiteral ) ( ParenExpression UnaryExpression - ! BinaryExpression || && CompareOp in + * / % Power MethodCall . PropertyName ArgList , PropertyAccess FunctionCall then else end Assignment AssignOp ExprStatement",
|
nodeNames: "\u26A0 LineComment BlockComment Program IfStatement if VariableName Number String BooleanLiteral BooleanLiteral NullLiteral NullLiteral ) ( ParenExpression UnaryExpression - ! BinaryExpression || && CompareOp in + * / % Power MethodCall . PropertyName ArgList , PropertyAccess FunctionCall then else end Assignment AssignOp ExprStatement",
|
||||||
maxTerm: 50,
|
maxTerm: 52,
|
||||||
nodeProps: [
|
nodeProps: [
|
||||||
["group", -3, 4, 37, 39, "Statement", -10, 6, 7, 8, 9, 13, 14, 17, 27, 32, 33, "Expression"],
|
["group", -3, 4, 39, 41, "Statement", -11, 6, 7, 8, 9, 11, 15, 16, 19, 29, 34, 35, "Expression"],
|
||||||
["openedBy", 11, "("],
|
["openedBy", 13, "("],
|
||||||
["closedBy", 12, ")"]
|
["closedBy", 14, ")"]
|
||||||
],
|
],
|
||||||
skippedNodes: [0, 1, 2],
|
skippedNodes: [0, 1, 2],
|
||||||
repeatNodeCount: 3,
|
repeatNodeCount: 3,
|
||||||
tokenData: "+p~RiXY!pYZ!p]^!ppq!pqr#Rrs#`uv%Svw%awx%lxy'Zyz'`z{'e{|'u|}'}}!O(S!O!P([!P!Q(a!Q![*X!^!_*r!_!`*z!`!a*r!c!}+S#R#S+S#T#o+S#p#q+e~!uS!O~XY!pYZ!p]^!ppq!p~#WP`~!_!`#Z~#`Od~~#cWOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|<%lO#`~$QOW~~$TRO;'S#`;'S;=`$^;=`O#`~$aXOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|;=`<%l#`<%lO#`~%PP;=`<%l#`~%XPi~!_!`%[~%aOv~~%dPvw%g~%lOc~~%oWOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T<%lO%l~&[RO;'S%l;'S;=`&e;=`O%l~&hXOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T;=`<%l%l<%lO%l~'WP;=`<%l%l~'`O[~~'eOZ~~'jQg~z{'p!_!`%[~'uOj~~'zPf~!_!`%[~(SOo~~(XP_~!_!`%[~(aOl~~(fRh~z{(o!P!Q)p!_!`%[~(rTOz(oz{)R{;'S(o;'S;=`)j<%lO(o~)UTO!P(o!P!Q)e!Q;'S(o;'S;=`)j<%lO(o~)jOQ~~)mP;=`<%l(o~)uSP~OY)pZ;'S)p;'S;=`*R<%lO)p~*UP;=`<%l)p~*^QV~!O!P*d!Q![*X~*gP!Q![*j~*oPV~!Q![*j~*wPd~!_!`#Z~+PPv~!_!`#Z~+XS!Q~!Q![+S!c!}+S#R#S+S#T#o+S~+hP#p#q+k~+pOb~",
|
tokenData: "+p~RiXY!pYZ!p]^!ppq!pqr#Rrs#`uv%Svw%awx%lxy'Zyz'`z{'e{|'u|}'}}!O(S!O!P([!P!Q(a!Q![*X!^!_*r!_!`*z!`!a*r!c!}+S#R#S+S#T#o+S#p#q+e~!uS!Q~XY!pYZ!p]^!ppq!p~#WPb~!_!`#Z~#`Of~~#cWOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|<%lO#`~$QOW~~$TRO;'S#`;'S;=`$^;=`O#`~$aXOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|;=`<%l#`<%lO#`~%PP;=`<%l#`~%XPk~!_!`%[~%aOx~~%dPvw%g~%lOe~~%oWOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T<%lO%l~&[RO;'S%l;'S;=`&e;=`O%l~&hXOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T;=`<%l%l<%lO%l~'WP;=`<%l%l~'`O^~~'eO]~~'jQi~z{'p!_!`%[~'uOl~~'zPh~!_!`%[~(SOq~~(XPa~!_!`%[~(aOn~~(fRj~z{(o!P!Q)p!_!`%[~(rTOz(oz{)R{;'S(o;'S;=`)j<%lO(o~)UTO!P(o!P!Q)e!Q;'S(o;'S;=`)j<%lO(o~)jOQ~~)mP;=`<%l(o~)uSP~OY)pZ;'S)p;'S;=`*R<%lO)p~*UP;=`<%l)p~*^QV~!O!P*d!Q![*X~*gP!Q![*j~*oPV~!Q![*j~*wPf~!_!`#Z~+PPx~!_!`#Z~+XS!S~!Q![+S!c!}+S#R#S+S#T#o+S~+hP#p#q+k~+pOd~",
|
||||||
tokenizers: [elseIfTokenizer, 0],
|
tokenizers: [elseIfTokenizer, 0],
|
||||||
topRules: { "Program": [0, 3] },
|
topRules: { "Program": [0, 3] },
|
||||||
specialized: [{ term: 48, get: (value) => spec_identifier[value] || -1 }],
|
specialized: [{ term: 50, get: (value) => spec_identifier[value] || -1 }],
|
||||||
tokenPrec: 1033
|
tokenPrec: 1063
|
||||||
});
|
});
|
||||||
|
|
||||||
// src/language.ts
|
// src/language.ts
|
||||||
var dexprHighlighting = styleTags({
|
var dexprHighlighting = styleTags({
|
||||||
"if then else end in elseIf": tags.keyword,
|
"if then else end in elseIf": tags.keyword,
|
||||||
BooleanLiteral: tags.bool,
|
BooleanLiteral: tags.bool,
|
||||||
|
NullLiteral: tags.null,
|
||||||
String: tags.string,
|
String: tags.string,
|
||||||
Number: tags.number,
|
Number: tags.number,
|
||||||
LineComment: tags.lineComment,
|
LineComment: tags.lineComment,
|
||||||
@@ -138,6 +139,7 @@ var KEYWORDS = [
|
|||||||
{ label: "end", type: "keyword" },
|
{ label: "end", type: "keyword" },
|
||||||
{ label: "true", type: "keyword", detail: "Boolean" },
|
{ label: "true", type: "keyword", detail: "Boolean" },
|
||||||
{ label: "false", type: "keyword", detail: "Boolean" },
|
{ label: "false", type: "keyword", detail: "Boolean" },
|
||||||
|
{ label: "null", type: "keyword", detail: "Null" },
|
||||||
{ label: "in", type: "keyword", detail: "membership test" }
|
{ label: "in", type: "keyword", detail: "membership test" }
|
||||||
];
|
];
|
||||||
function inferVariableTypes(context, knownVars) {
|
function inferVariableTypes(context, knownVars) {
|
||||||
@@ -198,6 +200,10 @@ function inferExprType(node, doc, knownTypes) {
|
|||||||
if (rootType === "Object") {
|
if (rootType === "Object") {
|
||||||
return knownTypes.get(`${varName}.${fieldName}`) ?? null;
|
return knownTypes.get(`${varName}.${fieldName}`) ?? null;
|
||||||
}
|
}
|
||||||
|
if (rootType === "List") {
|
||||||
|
const fieldType = knownTypes.get(`${varName}.${fieldName}`) ?? null;
|
||||||
|
return projectedListType(fieldType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -271,6 +277,11 @@ function inferMethodReturnType(method) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function projectedListType(fieldType) {
|
||||||
|
if (fieldType === "Number") return "NumberList";
|
||||||
|
if (fieldType === "String") return "StringList";
|
||||||
|
return "List";
|
||||||
|
}
|
||||||
function dedup(items) {
|
function dedup(items) {
|
||||||
const seen = /* @__PURE__ */ new Set();
|
const seen = /* @__PURE__ */ new Set();
|
||||||
return items.filter((item) => {
|
return items.filter((item) => {
|
||||||
@@ -340,12 +351,16 @@ function dexprCompletion(info) {
|
|||||||
if (path.length === 1) return { type: rootType, path };
|
if (path.length === 1) return { type: rootType, path };
|
||||||
let currentType = rootType;
|
let currentType = rootType;
|
||||||
for (let i = 1; i < path.length; i++) {
|
for (let i = 1; i < path.length; i++) {
|
||||||
if (currentType !== "Object") {
|
if (currentType === "Object") {
|
||||||
|
const key = `${path[i - 1]}.${path[i]}`;
|
||||||
|
currentType = varTypes.get(key) ?? null;
|
||||||
|
} else if (currentType === "List") {
|
||||||
|
const key = `${path[0]}.${path[i]}`;
|
||||||
|
const fieldType = varTypes.get(key) ?? null;
|
||||||
|
currentType = projectedListType(fieldType);
|
||||||
|
} else {
|
||||||
return { type: currentType, path };
|
return { type: currentType, path };
|
||||||
}
|
}
|
||||||
const key = `${path[i - 1]}.${path[i]}`;
|
|
||||||
const fieldType = varTypes.get(key) ?? null;
|
|
||||||
currentType = fieldType;
|
|
||||||
}
|
}
|
||||||
return { type: currentType, path };
|
return { type: currentType, path };
|
||||||
}
|
}
|
||||||
@@ -376,8 +391,9 @@ function dexprCompletion(info) {
|
|||||||
options = [...fieldItems, ...objMethods];
|
options = [...fieldItems, ...objMethods];
|
||||||
} else if (finalType === "List") {
|
} else if (finalType === "List") {
|
||||||
const rootVarName = path[0];
|
const rootVarName = path[0];
|
||||||
|
const fieldItems = objectFieldCompletions.get(rootVarName) ?? [];
|
||||||
const listMethods = methodsByType["List"] ?? [];
|
const listMethods = methodsByType["List"] ?? [];
|
||||||
options = [...listMethods];
|
options = [...fieldItems, ...listMethods];
|
||||||
} else if (finalType) {
|
} else if (finalType) {
|
||||||
options = methodsByType[finalType] ?? allMethods;
|
options = methodsByType[finalType] ?? allMethods;
|
||||||
} else {
|
} else {
|
||||||
@@ -409,6 +425,7 @@ import { tags as tags2 } from "@lezer/highlight";
|
|||||||
var dexprHighlightStyle = HighlightStyle.define([
|
var dexprHighlightStyle = HighlightStyle.define([
|
||||||
{ tag: tags2.keyword, color: "#7c3aed" },
|
{ tag: tags2.keyword, color: "#7c3aed" },
|
||||||
{ tag: tags2.bool, color: "#d97706" },
|
{ tag: tags2.bool, color: "#d97706" },
|
||||||
|
{ tag: tags2.null, color: "#d97706" },
|
||||||
{ tag: tags2.string, color: "#059669" },
|
{ tag: tags2.string, color: "#059669" },
|
||||||
{ tag: tags2.number, color: "#2563eb" },
|
{ tag: tags2.number, color: "#2563eb" },
|
||||||
{ tag: tags2.lineComment, color: "#9ca3af", fontStyle: "italic" },
|
{ tag: tags2.lineComment, color: "#9ca3af", fontStyle: "italic" },
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@duhanbalci/codemirror-lang-dexpr",
|
"name": "@duhanbalci/codemirror-lang-dexpr",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"description": "CodeMirror 6 language support for dexpr",
|
"description": "CodeMirror 6 language support for dexpr",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.cjs",
|
"main": "dist/index.cjs",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ const KEYWORDS: Completion[] = [
|
|||||||
{ label: "end", type: "keyword" },
|
{ label: "end", type: "keyword" },
|
||||||
{ label: "true", type: "keyword", detail: "Boolean" },
|
{ label: "true", type: "keyword", detail: "Boolean" },
|
||||||
{ label: "false", type: "keyword", detail: "Boolean" },
|
{ label: "false", type: "keyword", detail: "Boolean" },
|
||||||
|
{ label: "null", type: "keyword", detail: "Null" },
|
||||||
{ label: "in", type: "keyword", detail: "membership test" },
|
{ label: "in", type: "keyword", detail: "membership test" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ expression[@isGroup=Expression] {
|
|||||||
Number |
|
Number |
|
||||||
String |
|
String |
|
||||||
BooleanLiteral |
|
BooleanLiteral |
|
||||||
|
NullLiteral |
|
||||||
ParenExpression |
|
ParenExpression |
|
||||||
UnaryExpression |
|
UnaryExpression |
|
||||||
BinaryExpression |
|
BinaryExpression |
|
||||||
@@ -91,6 +92,8 @@ kw<term> { @extend[@name={term}]<identifier, term> }
|
|||||||
|
|
||||||
BooleanLiteral { @extend[@name=BooleanLiteral]<identifier, "true" | "false"> }
|
BooleanLiteral { @extend[@name=BooleanLiteral]<identifier, "true" | "false"> }
|
||||||
|
|
||||||
|
NullLiteral { @extend[@name=NullLiteral]<identifier, "null"> }
|
||||||
|
|
||||||
@tokens {
|
@tokens {
|
||||||
space { $[ \t\n\r]+ }
|
space { $[ \t\n\r]+ }
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { Extension } from "@codemirror/state";
|
|||||||
export const dexprHighlightStyle = HighlightStyle.define([
|
export const dexprHighlightStyle = HighlightStyle.define([
|
||||||
{ tag: tags.keyword, color: "#7c3aed" },
|
{ tag: tags.keyword, color: "#7c3aed" },
|
||||||
{ tag: tags.bool, color: "#d97706" },
|
{ tag: tags.bool, color: "#d97706" },
|
||||||
|
{ tag: tags.null, color: "#d97706" },
|
||||||
{ tag: tags.string, color: "#059669" },
|
{ tag: tags.string, color: "#059669" },
|
||||||
{ tag: tags.number, color: "#2563eb" },
|
{ tag: tags.number, color: "#2563eb" },
|
||||||
{ tag: tags.lineComment, color: "#9ca3af", fontStyle: "italic" },
|
{ tag: tags.lineComment, color: "#9ca3af", fontStyle: "italic" },
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { parser } from "./parser.js";
|
|||||||
const dexprHighlighting = styleTags({
|
const dexprHighlighting = styleTags({
|
||||||
"if then else end in elseIf": tags.keyword,
|
"if then else end in elseIf": tags.keyword,
|
||||||
BooleanLiteral: tags.bool,
|
BooleanLiteral: tags.bool,
|
||||||
|
NullLiteral: tags.null,
|
||||||
String: tags.string,
|
String: tags.string,
|
||||||
Number: tags.number,
|
Number: tags.number,
|
||||||
LineComment: tags.lineComment,
|
LineComment: tags.lineComment,
|
||||||
|
|||||||
+12
-12
@@ -1,24 +1,24 @@
|
|||||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
import {LRParser} from "@lezer/lr"
|
import {LRParser} from "@lezer/lr"
|
||||||
import {elseIfTokenizer} from "./tokens"
|
import {elseIfTokenizer} from "./tokens"
|
||||||
const spec_identifier = {__proto__:null,if:11, true:21, false:21, in:43, then:69, else:71, end:73}
|
const spec_identifier = {__proto__:null,if:11, true:21, false:21, null:25, in:47, then:73, else:75, end:77}
|
||||||
export const parser = LRParser.deserialize({
|
export const parser = LRParser.deserialize({
|
||||||
version: 14,
|
version: 14,
|
||||||
states: "+^Q]QQOOOOQP'#Cb'#CbOOQP'#Ce'#CeOwQQO'#CiO`QQO'#CjO#TQRO'#DTO%bQRO'#D_OOQP'#D_'#D_O%iQRO'#D_OOQP'#D]'#D]OOQP'#DU'#DUQ]QQOOOwQQO'#C`O&eQQO,59TO&lQRO'#D_O(zQRO,59UO`QQO,59XO`QQO,59XO`QQO,59XO`QQO,59XO`QQO,59XO)wQQO,59hO)|QQO'#CzOOQP,59i,59iO`QQO,59mOOQP-E7S-E7SO*TQQO,58zOOQP1G.o1G.oO+oQRO1G.sO+vQRO1G.sO-bQRO1G.sO-iQRO1G.sO.[QRO1G.sOOQP'#Cy'#CyOOQP1G/S1G/SO/[QQO'#D`OOQP,59f,59fO/fQQO,59fO/kQRO1G/XO0bQRO1G.fO0oQQO1G/SOOQP7+$i7+$iOwQQO'#DVO1pQQO,59zOOQP1G/Q1G/QO1xQRO7+$QO2TQRO7+$QOwQQO'#DWOOQP7+$Q7+$QO2bQQO7+$QO2iQQO,59qOOQO-E7T-E7TOOQP-E7U-E7UOOQP<<Gl<<GlO2sQQO<<GlO2zQRO<<GlO3VQQO,59rO2sQQO<<GlO3^QQOAN=WOOQPAN=WAN=WO3^QQOAN=WO3eQRO1G/^OOQPG22rG22rO3rQQOG22rO3yQRO7+$xOOQPLD(^LD(^O)wQQO,59hO5RQQO1G.sO5YQQO1G.sO6[QQO1G.sO6cQQO1G.sO6jQQO1G.sO7QQQO,59UOwQQO,59XOwQQO,59XOwQQO,59XOwQQO,59XOwQQO,59XOwQQO'#Cj",
|
states: "+dQ]QQOOOOQP'#Cb'#CbOOQP'#Ce'#CeOOQP'#Cg'#CgOzQQO'#CkO`QQO'#ClO#ZQRO'#DVO%nQRO'#DaOOQP'#Da'#DaO%uQRO'#DaOOQP'#D_'#D_OOQP'#DW'#DWQ]QQOOOzQQO'#C`O&qQQO,59VO&xQRO'#DaO)ZQRO,59WO`QQO,59ZO`QQO,59ZO`QQO,59ZO`QQO,59ZO`QQO,59ZO*ZQQO,59jO*`QQO'#C|OOQP,59k,59kO`QQO,59oOOQP-E7U-E7UO*gQQO,58zOOQP1G.q1G.qO,UQRO1G.uO,]QRO1G.uO-zQRO1G.uO.RQRO1G.uO.tQRO1G.uOOQP'#C{'#C{OOQP1G/U1G/UO/wQQO'#DbOOQP,59h,59hO0RQQO,59hO0WQRO1G/ZO1QQRO1G.fO1_QQO1G/UOOQP7+$k7+$kOzQQO'#DXO2`QQO,59|OOQP1G/S1G/SO2hQRO7+$QO2sQRO7+$QOzQQO'#DYOOQP7+$Q7+$QO3QQQO7+$QO3XQQO,59sOOQO-E7V-E7VOOQP-E7W-E7WOOQP<<Gl<<GlO3cQQO<<GlO3jQRO<<GlO3uQQO,59tO3cQQO<<GlO3|QQOAN=WOOQPAN=WAN=WO3|QQOAN=WO4TQRO1G/`OOQPG22rG22rO4bQQOG22rO4iQRO7+$zOOQPLD(^LD(^O*ZQQO,59jO5qQQO1G.uO5xQQO1G.uO6zQQO1G.uO7RQQO1G.uO7YQQO1G.uO7pQQO,59WOzQQO,59ZOzQQO,59ZOzQQO,59ZOzQQO,59ZOzQQO,59ZOzQQO'#Cl",
|
||||||
stateData: "7n~O!OOSPOSQOS~OT[OVVOWVOYQO[RO_SO`SO!QPO~OVVOWVOYQO[RO_!pO`!pO!QPO~O_cOb`OcaOdbOebOfcOgdOhdOidOjdOleO~OTwXVwXWwXYwX[wX`wX{wX!QwXswXtwX|wX~P!`OvhOT!RXV!RXW!RXY!RX_!RX`!RXb!RXc!RXd!RXe!RXf!RXg!RXh!RXi!RXj!RXl!RX{!RX!Q!RXs!RXt!RX|!RX~O[fO~P#zO[!RX~P#zO_!nOb!kOc!lOd!mOe!mOf!nOg!oOh!oOi!oOj!oOl!dO~OZkO~P%pO[fOZ!RX_!RXb!RXc!RXd!RXe!RXf!RXg!RXh!RXi!RXj!RXl!RXT!RXV!RXW!RXY!RX`!RX{!RX!Q!RXr!RXo!RXs!RXt!RX|!RX~Ob^ac^ad^ae^af^ag^ah^ai^aj^a~O_cOleOT^aV^aW^aY^a[^a`^a{^a!Q^as^at^a|^a~P(]O!QqO~OZtO~PwOrwO~P%pO_cOdbOebOfcOgdOhdOidOjdOleOTaiVaiWaiYai[ai`aibai{ai!Qaisaitai|ai~OcaO~P*[Ocai~P*[O_cOgdOhdOidOjdOleOTaiVaiWaiYai[ai`aibaicaidaieai{ai!Qaisaitai|ai~OfcO~P+}Ofai~P+}Obaicaidaieaifaigaihaiiai~O_cOjdOleOTaiVaiWaiYai[ai`ai{ai!Qaisaitai|ai~P-pOozOZ!SX~P%pOZ|O~OTuiVuiWuiYui[ui`ui{ui!Quisuitui|ui~P!`Os!ROt!QO|!PO~P]O[fOZpi_pibpicpidpiepifpigpihpiipijpilpirpiopi~OozOZ!Sa~Os!WOt!VO|!PO~Os!WOt!VO|!PO~P]Ot!VO~P]OZyaoya~P%pOt!]O~P]Os!^Ot!]O|!PO~Or!_O~P%pOt!`O~P]Oszitzi|zi~P]Ot!cO~P]Oszqtzq|zq~P]O_!nOd!mOe!mOf!nOg!oOh!oOi!oOj!oOl!dOZaibairaioai~Oc!lO~P4WOcai~P4WO_!nOg!oOh!oOi!oOj!oOl!dOZaibaicaidaieairaioai~Of!nO~P5aOfai~P5aO_!nOj!oOl!dOZairaioai~P-pO_!nOl!dOZ^ar^ao^a~P(]OvdjgQPQh~",
|
stateData: "8^~O!QOSPOSQOS~OT]OVWOWWOYQO[RO^SOaTObTO!SPO~OVWOWWOYQO[RO^SOa!qOb!qO!SPO~OadOdaOebOfcOgcOhdOieOjeOkeOleOnfO~OTyXVyXWyXYyX[yX^yXbyX}yX!SyXuyXvyX!OyX~P!fOxiOT!TXV!TXW!TXY!TX[!TXa!TXb!TXd!TXe!TXf!TXg!TXh!TXi!TXj!TXk!TXl!TXn!TX}!TX!S!TXu!TXv!TX!O!TX~O^gO~P$TO^!TX~P$TOa!oOd!lOe!mOf!nOg!nOh!oOi!pOj!pOk!pOl!pOn!eO~O]lO~P%|O^gO]!TXa!TXd!TXe!TXf!TXg!TXh!TXi!TXj!TXk!TXl!TXn!TXT!TXV!TXW!TXY!TX[!TXb!TX}!TX!S!TXt!TXq!TXu!TXv!TX!O!TX~Od`ae`af`ag`ah`ai`aj`ak`al`a~OadOnfOT`aV`aW`aY`a[`a^`ab`a}`a!S`au`av`a!O`a~P(lO!SrO~O]uO~PzOtxO~P%|OadOfcOgcOhdOieOjeOkeOleOnfOTciVciWciYci[ci^cibcidci}ci!Sciucivci!Oci~OebO~P*nOeci~P*nOadOieOjeOkeOleOnfOTciVciWciYci[ci^cibcidciecifcigci}ci!Sciucivci!Oci~OhdO~P,dOhci~P,dOdciecifcigcihciicijcikci~OadOleOnfOTciVciWciYci[ci^cibci}ci!Sciucivci!Oci~P.YOq{O]!UX~P%|O]}O~OTwiVwiWwiYwi[wi^wibwi}wi!Swiuwivwi!Owi~P!fOu!SOv!RO!O!QO~P]O^gO]riaridrierifrigrihriirijrikrilrinritriqri~Oq{O]!Ua~Ou!XOv!WO!O!QO~Ou!XOv!WO!O!QO~P]Ov!WO~P]O]{aq{a~P%|Ov!^O~P]Ou!_Ov!^O!O!QO~Ot!`O~P%|Ov!aO~P]Ou|iv|i!O|i~P]Ov!dO~P]Ou|qv|q!O|q~P]Oa!oOf!nOg!nOh!oOi!pOj!pOk!pOl!pOn!eO]cidcitciqci~Oe!mO~P4vOeci~P4vOa!oOi!pOj!pOk!pOl!pOn!eO]cidciecifcigcitciqci~Oh!oO~P6POhci~P6POa!oOl!pOn!eO]citciqci~P.YOa!oOn!eO]`at`aq`a~P(lOxfliQPQj~",
|
||||||
goto: "'n!TPPPP!UP!dPP#WPPP#W#WPP#WPPPPPPPPP#WP#x$OP$V#WPPP!UP!U$y%e%kPPPP%uP&T'kiXOZw!O!R!W!Z![!^!_!a!bhUOZw!O!R!W!Z![!^!_!a!bu^RS[`abcdfhz!P!k!l!m!n!o!p!_VORSZ[`abcdfhwz!O!P!R!W!Z![!^!_!a!b!k!l!m!n!o!pQreRx!dSgU^RyxtVRS[`abcdfhz!P!k!l!m!n!o!piWOZw!O!R!W!Z![!^!_!a!bQZO[iZ!O!Z![!a!bQ!OwQ!Z!RQ![!WQ!a!^R!b!_Q{sR!T{Q}wS!U}!XR!X!OiYOZw!O!R!W!Z![!^!_!a!bhTOZw!O!R!W!Z![!^!_!a!bQ]RQ_SQj[Ql`QmaQnbQocQpdQsfQvhQ!SzQ!Y!PQ!e!kQ!f!lQ!g!mQ!h!nQ!i!oR!j!pRuf",
|
goto: "'p!VPPPP!WP!fPP#YP#YPPP#Y#YPP#YPPPPPPPPP#YP#z$QP$X#YPPP!WP!W${%g%mPPPP%wP&V'miYO[x!P!S!X![!]!_!`!b!chVO[x!P!S!X![!]!_!`!b!cu_ST]abcdegi{!Q!l!m!n!o!p!q!_WOST[]abcdegix{!P!Q!S!X![!]!_!`!b!c!l!m!n!o!p!qQsfRy!eShV_RzytWST]abcdegi{!Q!l!m!n!o!p!qiXO[x!P!S!X![!]!_!`!b!cQ[O[j[!P![!]!b!cQ!PxQ![!SQ!]!XQ!b!_R!c!`Q|tR!U|Q!OxS!V!O!YR!Y!PiZO[x!P!S!X![!]!_!`!b!chUO[x!P!S!X![!]!_!`!b!cQ^SQ`TQk]QmaQnbQocQpdQqeQtgQwiQ!T{Q!Z!QQ!f!lQ!g!mQ!h!nQ!i!oQ!j!pR!k!qRvg",
|
||||||
nodeNames: "⚠ LineComment BlockComment Program IfStatement if VariableName Number String BooleanLiteral BooleanLiteral ) ( ParenExpression UnaryExpression - ! BinaryExpression || && CompareOp in + * / % Power MethodCall . PropertyName ArgList , PropertyAccess FunctionCall then else end Assignment AssignOp ExprStatement",
|
nodeNames: "⚠ LineComment BlockComment Program IfStatement if VariableName Number String BooleanLiteral BooleanLiteral NullLiteral NullLiteral ) ( ParenExpression UnaryExpression - ! BinaryExpression || && CompareOp in + * / % Power MethodCall . PropertyName ArgList , PropertyAccess FunctionCall then else end Assignment AssignOp ExprStatement",
|
||||||
maxTerm: 50,
|
maxTerm: 52,
|
||||||
nodeProps: [
|
nodeProps: [
|
||||||
["group", -3,4,37,39,"Statement",-10,6,7,8,9,13,14,17,27,32,33,"Expression"],
|
["group", -3,4,39,41,"Statement",-11,6,7,8,9,11,15,16,19,29,34,35,"Expression"],
|
||||||
["openedBy", 11,"("],
|
["openedBy", 13,"("],
|
||||||
["closedBy", 12,")"]
|
["closedBy", 14,")"]
|
||||||
],
|
],
|
||||||
skippedNodes: [0,1,2],
|
skippedNodes: [0,1,2],
|
||||||
repeatNodeCount: 3,
|
repeatNodeCount: 3,
|
||||||
tokenData: "+p~RiXY!pYZ!p]^!ppq!pqr#Rrs#`uv%Svw%awx%lxy'Zyz'`z{'e{|'u|}'}}!O(S!O!P([!P!Q(a!Q![*X!^!_*r!_!`*z!`!a*r!c!}+S#R#S+S#T#o+S#p#q+e~!uS!O~XY!pYZ!p]^!ppq!p~#WP`~!_!`#Z~#`Od~~#cWOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|<%lO#`~$QOW~~$TRO;'S#`;'S;=`$^;=`O#`~$aXOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|;=`<%l#`<%lO#`~%PP;=`<%l#`~%XPi~!_!`%[~%aOv~~%dPvw%g~%lOc~~%oWOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T<%lO%l~&[RO;'S%l;'S;=`&e;=`O%l~&hXOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T;=`<%l%l<%lO%l~'WP;=`<%l%l~'`O[~~'eOZ~~'jQg~z{'p!_!`%[~'uOj~~'zPf~!_!`%[~(SOo~~(XP_~!_!`%[~(aOl~~(fRh~z{(o!P!Q)p!_!`%[~(rTOz(oz{)R{;'S(o;'S;=`)j<%lO(o~)UTO!P(o!P!Q)e!Q;'S(o;'S;=`)j<%lO(o~)jOQ~~)mP;=`<%l(o~)uSP~OY)pZ;'S)p;'S;=`*R<%lO)p~*UP;=`<%l)p~*^QV~!O!P*d!Q![*X~*gP!Q![*j~*oPV~!Q![*j~*wPd~!_!`#Z~+PPv~!_!`#Z~+XS!Q~!Q![+S!c!}+S#R#S+S#T#o+S~+hP#p#q+k~+pOb~",
|
tokenData: "+p~RiXY!pYZ!p]^!ppq!pqr#Rrs#`uv%Svw%awx%lxy'Zyz'`z{'e{|'u|}'}}!O(S!O!P([!P!Q(a!Q![*X!^!_*r!_!`*z!`!a*r!c!}+S#R#S+S#T#o+S#p#q+e~!uS!Q~XY!pYZ!p]^!ppq!p~#WPb~!_!`#Z~#`Of~~#cWOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|<%lO#`~$QOW~~$TRO;'S#`;'S;=`$^;=`O#`~$aXOY#`Zr#`rs#{s#O#`#O#P$Q#P;'S#`;'S;=`$|;=`<%l#`<%lO#`~%PP;=`<%l#`~%XPk~!_!`%[~%aOx~~%dPvw%g~%lOe~~%oWOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T<%lO%l~&[RO;'S%l;'S;=`&e;=`O%l~&hXOY%lZw%lwx#{x#O%l#O#P&X#P;'S%l;'S;=`'T;=`<%l%l<%lO%l~'WP;=`<%l%l~'`O^~~'eO]~~'jQi~z{'p!_!`%[~'uOl~~'zPh~!_!`%[~(SOq~~(XPa~!_!`%[~(aOn~~(fRj~z{(o!P!Q)p!_!`%[~(rTOz(oz{)R{;'S(o;'S;=`)j<%lO(o~)UTO!P(o!P!Q)e!Q;'S(o;'S;=`)j<%lO(o~)jOQ~~)mP;=`<%l(o~)uSP~OY)pZ;'S)p;'S;=`*R<%lO)p~*UP;=`<%l)p~*^QV~!O!P*d!Q![*X~*gP!Q![*j~*oPV~!Q![*j~*wPf~!_!`#Z~+PPx~!_!`#Z~+XS!S~!Q![+S!c!}+S#R#S+S#T#o+S~+hP#p#q+k~+pOd~",
|
||||||
tokenizers: [elseIfTokenizer, 0],
|
tokenizers: [elseIfTokenizer, 0],
|
||||||
topRules: {"Program":[0,3]},
|
topRules: {"Program":[0,3]},
|
||||||
specialized: [{term: 48, get: (value) => spec_identifier[value] || -1}],
|
specialized: [{term: 50, get: (value) => spec_identifier[value] || -1}],
|
||||||
tokenPrec: 1033
|
tokenPrec: 1063
|
||||||
})
|
})
|
||||||
|
|||||||
+19
-18
@@ -1,6 +1,6 @@
|
|||||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
export const
|
export const
|
||||||
elseIf = 44,
|
elseIf = 46,
|
||||||
LineComment = 1,
|
LineComment = 1,
|
||||||
BlockComment = 2,
|
BlockComment = 2,
|
||||||
Program = 3,
|
Program = 3,
|
||||||
@@ -10,20 +10,21 @@ export const
|
|||||||
Number = 7,
|
Number = 7,
|
||||||
String = 8,
|
String = 8,
|
||||||
BooleanLiteral = 10,
|
BooleanLiteral = 10,
|
||||||
ParenExpression = 13,
|
NullLiteral = 12,
|
||||||
UnaryExpression = 14,
|
ParenExpression = 15,
|
||||||
BinaryExpression = 17,
|
UnaryExpression = 16,
|
||||||
CompareOp = 20,
|
BinaryExpression = 19,
|
||||||
_in = 21,
|
CompareOp = 22,
|
||||||
Power = 26,
|
_in = 23,
|
||||||
MethodCall = 27,
|
Power = 28,
|
||||||
PropertyName = 29,
|
MethodCall = 29,
|
||||||
ArgList = 30,
|
PropertyName = 31,
|
||||||
PropertyAccess = 32,
|
ArgList = 32,
|
||||||
FunctionCall = 33,
|
PropertyAccess = 34,
|
||||||
then = 34,
|
FunctionCall = 35,
|
||||||
_else = 35,
|
then = 36,
|
||||||
end = 36,
|
_else = 37,
|
||||||
Assignment = 37,
|
end = 38,
|
||||||
AssignOp = 38,
|
Assignment = 39,
|
||||||
ExprStatement = 39
|
AssignOp = 40,
|
||||||
|
ExprStatement = 41
|
||||||
|
|||||||
+431
-430
@@ -1,430 +1,431 @@
|
|||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
expr::{Expr, Op},
|
expr::{Expr, Op},
|
||||||
stmt::Stmt,
|
stmt::Stmt,
|
||||||
value::Value,
|
value::Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
peg::parser!(
|
peg::parser!(
|
||||||
pub grammar parser() for str {
|
pub grammar parser() for str {
|
||||||
pub rule program() -> Vec<Stmt>
|
pub rule program() -> Vec<Stmt>
|
||||||
= s:statement()* { s }
|
= s:statement()* { s }
|
||||||
|
|
||||||
/// Parse program with source location info for each statement
|
/// Parse program with source location info for each statement
|
||||||
pub rule program_with_spans() -> Vec<(usize, Stmt)>
|
pub rule program_with_spans() -> Vec<(usize, Stmt)>
|
||||||
= s:statement_with_pos()* { s }
|
= s:statement_with_pos()* { s }
|
||||||
|
|
||||||
/// Statement with position info (byte offset)
|
/// Statement with position info (byte offset)
|
||||||
rule statement_with_pos() -> (usize, Stmt)
|
rule statement_with_pos() -> (usize, Stmt)
|
||||||
= whitespace()?
|
= whitespace()?
|
||||||
pos:position!()
|
pos:position!()
|
||||||
s:(
|
s:(
|
||||||
assignment()
|
assignment()
|
||||||
/ if_stmt()
|
/ if_stmt()
|
||||||
/ expr_stmt()
|
/ expr_stmt()
|
||||||
)
|
)
|
||||||
whitespace()? { (pos, s) }
|
whitespace()? { (pos, s) }
|
||||||
|
|
||||||
pub rule statement() -> Stmt
|
pub rule statement() -> Stmt
|
||||||
= whitespace()?
|
= whitespace()?
|
||||||
s:(
|
s:(
|
||||||
assignment()
|
assignment()
|
||||||
/ if_stmt()
|
/ if_stmt()
|
||||||
/ expr_stmt()
|
/ expr_stmt()
|
||||||
)
|
)
|
||||||
whitespace()? { s }
|
whitespace()? { s }
|
||||||
|
|
||||||
pub rule expression() -> Expr
|
pub rule expression() -> Expr
|
||||||
= binary_op()
|
= binary_op()
|
||||||
|
|
||||||
pub rule mul_div() -> Expr =
|
pub rule mul_div() -> Expr =
|
||||||
left:power() mul_div_right:(
|
left:power() mul_div_right:(
|
||||||
_ op:$("*" / "/" / "%") _ right:power()
|
_ op:$("*" / "/" / "%") _ right:power()
|
||||||
{ (op, right) }
|
{ (op, right) }
|
||||||
)* {
|
)* {
|
||||||
let mut result = left;
|
let mut result = left;
|
||||||
for (op, right) in mul_div_right {
|
for (op, right) in mul_div_right {
|
||||||
result = match op {
|
result = match op {
|
||||||
"*" => Expr::BinaryOp(Box::new(result), Op::Mul, Box::new(right)),
|
"*" => Expr::BinaryOp(Box::new(result), Op::Mul, Box::new(right)),
|
||||||
"/" => Expr::BinaryOp(Box::new(result), Op::Div, Box::new(right)),
|
"/" => Expr::BinaryOp(Box::new(result), Op::Div, Box::new(right)),
|
||||||
"%" => Expr::BinaryOp(Box::new(result), Op::Mod, Box::new(right)),
|
"%" => Expr::BinaryOp(Box::new(result), Op::Mod, Box::new(right)),
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub rule power() -> Expr =
|
pub rule power() -> Expr =
|
||||||
base:postfix() _ "**" _ exp:power() { Expr::BinaryOp(Box::new(base), Op::Pow, Box::new(exp)) }
|
base:postfix() _ "**" _ exp:power() { Expr::BinaryOp(Box::new(base), Op::Pow, Box::new(exp)) }
|
||||||
/ a:postfix() { a }
|
/ a:postfix() { a }
|
||||||
|
|
||||||
|
|
||||||
pub rule binary_op() -> Expr = precedence!{
|
pub rule binary_op() -> Expr = precedence!{
|
||||||
i:identifier() _ "(" args:((_ e:expression() _ {e}) ** ",") ")" { Expr::FunctionCall(i, args) }
|
i:identifier() _ "(" args:((_ e:expression() _ {e}) ** ",") ")" { Expr::FunctionCall(i, args) }
|
||||||
--
|
--
|
||||||
x:@ _ "&&" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::And, Box::new(y)) }
|
x:@ _ "&&" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::And, Box::new(y)) }
|
||||||
x:@ _ "||" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Or, Box::new(y)) }
|
x:@ _ "||" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Or, Box::new(y)) }
|
||||||
--
|
--
|
||||||
x:@ _ "==" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Eq, Box::new(y)) }
|
x:@ _ "==" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Eq, Box::new(y)) }
|
||||||
x:@ _ "!=" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Neq, Box::new(y)) }
|
x:@ _ "!=" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Neq, Box::new(y)) }
|
||||||
x:@ _ "<" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Lt, Box::new(y)) }
|
x:@ _ "<" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Lt, Box::new(y)) }
|
||||||
x:@ _ "<=" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Lte, Box::new(y)) }
|
x:@ _ "<=" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Lte, Box::new(y)) }
|
||||||
x:@ _ ">" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Gt, Box::new(y)) }
|
x:@ _ ">" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Gt, Box::new(y)) }
|
||||||
x:@ _ ">=" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Gte, Box::new(y)) }
|
x:@ _ ">=" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Gte, Box::new(y)) }
|
||||||
x:@ _ "in" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::In, Box::new(y)) }
|
x:@ _ "in" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::In, Box::new(y)) }
|
||||||
--
|
--
|
||||||
x:@ _ "+" _ y:(@) { Expr::BinaryOp(Box::new(x),Op::Add, Box::new(y)) }
|
x:@ _ "+" _ y:(@) { Expr::BinaryOp(Box::new(x),Op::Add, Box::new(y)) }
|
||||||
x:@ _ "-" _ y:(@) { Expr::BinaryOp(Box::new(x),Op::Sub, Box::new(y)) }
|
x:@ _ "-" _ y:(@) { Expr::BinaryOp(Box::new(x),Op::Sub, Box::new(y)) }
|
||||||
--
|
--
|
||||||
x:mul_div() { x }
|
x:mul_div() { x }
|
||||||
--
|
--
|
||||||
p:postfix() { p }
|
p:postfix() { p }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Postfix operations: property access and method calls with chaining
|
/// Postfix operations: property access and method calls with chaining
|
||||||
rule postfix() -> Expr
|
rule postfix() -> Expr
|
||||||
= base:atom() chain:(
|
= base:atom() chain:(
|
||||||
"." m:identifier() "(" args:((_ e:expression() _ {e}) ** ",") ")" { (m, Some(args)) }
|
"." m:identifier() "(" args:((_ e:expression() _ {e}) ** ",") ")" { (m, Some(args)) }
|
||||||
/ "." p:identifier() { (p, None) }
|
/ "." p:identifier() { (p, None) }
|
||||||
)* {
|
)* {
|
||||||
let mut result = base;
|
let mut result = base;
|
||||||
for (name, args) in chain {
|
for (name, args) in chain {
|
||||||
if let Some(args) = args {
|
if let Some(args) = args {
|
||||||
result = Expr::MethodCall(Box::new(result), name, args);
|
result = Expr::MethodCall(Box::new(result), name, args);
|
||||||
} else {
|
} else {
|
||||||
result = Expr::PropertyAccess(Box::new(result), name);
|
result = Expr::PropertyAccess(Box::new(result), name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
rule atom() -> Expr
|
rule atom() -> Expr
|
||||||
= i:identifier() { Expr::Variable(i) }
|
= i:identifier() { Expr::Variable(i) }
|
||||||
/ i:string() { Expr::Value(Value::String(i)) }
|
/ i:string() { Expr::Value(Value::String(i)) }
|
||||||
/ i:number() { Expr::Value(Value::Number(i)) }
|
/ i:number() { Expr::Value(Value::Number(i)) }
|
||||||
/ i:boolean_literal() { Expr::Value(i) }
|
/ i:boolean_literal() { Expr::Value(i) }
|
||||||
/ "(" e:expression() ")" { e }
|
/ "(" e:expression() ")" { e }
|
||||||
/ "-" e:atom() { Expr::UnaryOp(Op::Neg, Box::new(e)) }
|
/ "-" e:atom() { Expr::UnaryOp(Op::Neg, Box::new(e)) }
|
||||||
/ "!" e:atom() { Expr::UnaryOp(Op::Not, Box::new(e)) }
|
/ "!" e:atom() { Expr::UnaryOp(Op::Not, Box::new(e)) }
|
||||||
|
|
||||||
pub rule string() -> SmolStr
|
pub rule string() -> SmolStr
|
||||||
= "\"" s:$(([^'"'] / "\\\"")*) "\"" {
|
= "\"" s:$(([^'"'] / "\\\"")*) "\"" {
|
||||||
s.replace("\\\"", "\"").into()
|
s.replace("\\\"", "\"").into()
|
||||||
}
|
}
|
||||||
/ "'" s:$(([^'\''] / "\\''")*) "'" {
|
/ "'" s:$(([^'\''] / "\\''")*) "'" {
|
||||||
s.replace("\\'", "'").into()
|
s.replace("\\'", "'").into()
|
||||||
}
|
}
|
||||||
|
|
||||||
rule boolean_literal() -> Value
|
rule boolean_literal() -> Value
|
||||||
= "true" { Value::Boolean(true) }
|
= "true" { Value::Boolean(true) }
|
||||||
/ "false" { Value::Boolean(false) }
|
/ "false" { Value::Boolean(false) }
|
||||||
|
/ "null" { Value::Null }
|
||||||
|
|
||||||
pub rule expr_stmt() -> Stmt
|
|
||||||
= e:expression() { Stmt::ExprStmt(Box::new(e)) }
|
pub rule expr_stmt() -> Stmt
|
||||||
|
= e:expression() { Stmt::ExprStmt(Box::new(e)) }
|
||||||
pub rule if_stmt() -> Stmt
|
|
||||||
= "if" _ cond:expression() whitespace()? "then" whitespace()?
|
pub rule if_stmt() -> Stmt
|
||||||
then_body:statement()* whitespace()?
|
= "if" _ cond:expression() whitespace()? "then" whitespace()?
|
||||||
else_part:else_clause()?
|
then_body:statement()* whitespace()?
|
||||||
"end" whitespace()? {
|
else_part:else_clause()?
|
||||||
Stmt::If(Box::new(cond), then_body, else_part)
|
"end" whitespace()? {
|
||||||
}
|
Stmt::If(Box::new(cond), then_body, else_part)
|
||||||
|
}
|
||||||
pub rule else_clause() -> Vec<Stmt>
|
|
||||||
= "else if" whitespace()? cond:expression() whitespace()? "then" whitespace()?
|
pub rule else_clause() -> Vec<Stmt>
|
||||||
then_body:statement()* whitespace()?
|
= "else if" whitespace()? cond:expression() whitespace()? "then" whitespace()?
|
||||||
else_part:else_clause()? whitespace()? {
|
then_body:statement()* whitespace()?
|
||||||
vec![Stmt::If(Box::new(cond), then_body, else_part)]
|
else_part:else_clause()? whitespace()? {
|
||||||
}
|
vec![Stmt::If(Box::new(cond), then_body, else_part)]
|
||||||
/ "else" whitespace()? else_body:statement()* whitespace()? {
|
}
|
||||||
else_body
|
/ "else" whitespace()? else_body:statement()* whitespace()? {
|
||||||
}
|
else_body
|
||||||
|
}
|
||||||
pub rule assignment() -> Stmt
|
|
||||||
= i:identifier() path:("." p:identifier() { p })+ _ "=" _ value:expression() {
|
pub rule assignment() -> Stmt
|
||||||
Stmt::PropertyAssignment(i, path, Box::new(value))
|
= i:identifier() path:("." p:identifier() { p })+ _ "=" _ value:expression() {
|
||||||
}
|
Stmt::PropertyAssignment(i, path, Box::new(value))
|
||||||
/ i:identifier() _ op:compound_op() _ value:expression() {
|
}
|
||||||
// Desugar compound assignment: x += 1 becomes x = x + 1
|
/ i:identifier() _ op:compound_op() _ value:expression() {
|
||||||
let var_expr = Expr::Variable(i.clone());
|
// Desugar compound assignment: x += 1 becomes x = x + 1
|
||||||
let combined = Expr::BinaryOp(Box::new(var_expr), op, Box::new(value));
|
let var_expr = Expr::Variable(i.clone());
|
||||||
Stmt::Assignment(i, Box::new(combined))
|
let combined = Expr::BinaryOp(Box::new(var_expr), op, Box::new(value));
|
||||||
}
|
Stmt::Assignment(i, Box::new(combined))
|
||||||
/ i:identifier() _ "=" _ value:expression() { Stmt::Assignment(i, Box::new(value)) }
|
}
|
||||||
|
/ i:identifier() _ "=" _ value:expression() { Stmt::Assignment(i, Box::new(value)) }
|
||||||
rule compound_op() -> Op
|
|
||||||
= "+=" { Op::Add }
|
rule compound_op() -> Op
|
||||||
/ "-=" { Op::Sub }
|
= "+=" { Op::Add }
|
||||||
/ "*=" { Op::Mul }
|
/ "-=" { Op::Sub }
|
||||||
/ "/=" { Op::Div }
|
/ "*=" { Op::Mul }
|
||||||
/ "%=" { Op::Mod }
|
/ "/=" { Op::Div }
|
||||||
|
/ "%=" { Op::Mod }
|
||||||
rule keyword()
|
|
||||||
= ("if" / "then" / "else" / "end" / "true" / "false" / "in") !['a'..='z' | 'A'..='Z' | '0'..='9' | '_']
|
rule keyword()
|
||||||
|
= ("if" / "then" / "else" / "end" / "true" / "false" / "null" / "in") !['a'..='z' | 'A'..='Z' | '0'..='9' | '_']
|
||||||
rule identifier() -> SmolStr
|
|
||||||
= !keyword() s:$(['a'..='z' | 'A'..='Z' | '_']['a'..='z' | 'A'..='Z' | '0'..='9' | '_']*)
|
rule identifier() -> SmolStr
|
||||||
{ s.into() }
|
= !keyword() s:$(['a'..='z' | 'A'..='Z' | '_']['a'..='z' | 'A'..='Z' | '0'..='9' | '_']*)
|
||||||
|
{ s.into() }
|
||||||
rule number() -> Decimal
|
|
||||||
= n:$(['0'..='9']+ ("." ['0'..='9']+)?) {?
|
rule number() -> Decimal
|
||||||
Decimal::from_str(n).map_err(|_| "invalid decimal")
|
= n:$(['0'..='9']+ ("." ['0'..='9']+)?) {?
|
||||||
}
|
Decimal::from_str(n).map_err(|_| "invalid decimal")
|
||||||
|
}
|
||||||
rule whitespace()
|
|
||||||
= ([' ' | '\t' | '\n' | '\r'] / comment())+
|
rule whitespace()
|
||||||
|
= ([' ' | '\t' | '\n' | '\r'] / comment())+
|
||||||
rule comment()
|
|
||||||
= "//" [^'\n']* "\n"?
|
rule comment()
|
||||||
/ "/*" (!"*/" [_])* "*/"
|
= "//" [^'\n']* "\n"?
|
||||||
|
/ "/*" (!"*/" [_])* "*/"
|
||||||
rule _() = quiet!{([' ' | '\t'] / comment())*}
|
|
||||||
|
rule _() = quiet!{([' ' | '\t'] / comment())*}
|
||||||
// rule string_lit() -> Expr
|
|
||||||
// = "\"" s:$([^'"']*) "\""
|
// rule string_lit() -> Expr
|
||||||
// { Expr::String(s.to_string()) }
|
// = "\"" s:$([^'"']*) "\""
|
||||||
}
|
// { Expr::String(s.to_string()) }
|
||||||
);
|
}
|
||||||
|
);
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
#[cfg(test)]
|
||||||
use super::*;
|
mod tests {
|
||||||
|
use super::*;
|
||||||
#[test]
|
|
||||||
fn test_binary_op() {
|
#[test]
|
||||||
let res = parser::binary_op("1 + 2 * 3 - 4 / 5");
|
fn test_binary_op() {
|
||||||
if let Err(e) = &res {
|
let res = parser::binary_op("1 + 2 * 3 - 4 / 5");
|
||||||
println!("{}", e);
|
if let Err(e) = &res {
|
||||||
}
|
println!("{}", e);
|
||||||
if let Ok(expr) = res {
|
}
|
||||||
match expr {
|
if let Ok(expr) = res {
|
||||||
Expr::BinaryOp(left, Op::Add, right) => {
|
match expr {
|
||||||
assert!(matches!(*left, Expr::Value(_)));
|
Expr::BinaryOp(left, Op::Add, right) => {
|
||||||
match *right {
|
assert!(matches!(*left, Expr::Value(_)));
|
||||||
Expr::BinaryOp(left2, Op::Sub, right2) => {
|
match *right {
|
||||||
// Check 2 * 3
|
Expr::BinaryOp(left2, Op::Sub, right2) => {
|
||||||
match *left2 {
|
// Check 2 * 3
|
||||||
Expr::BinaryOp(left3, Op::Mul, right3) => {
|
match *left2 {
|
||||||
assert!(matches!(*left3, Expr::Value(_)));
|
Expr::BinaryOp(left3, Op::Mul, right3) => {
|
||||||
assert!(matches!(*right3, Expr::Value(_)));
|
assert!(matches!(*left3, Expr::Value(_)));
|
||||||
}
|
assert!(matches!(*right3, Expr::Value(_)));
|
||||||
_ => panic!("Expected multiplication"),
|
}
|
||||||
}
|
_ => panic!("Expected multiplication"),
|
||||||
// Check 4 / 5
|
}
|
||||||
match *right2 {
|
// Check 4 / 5
|
||||||
Expr::BinaryOp(left3, Op::Div, right3) => {
|
match *right2 {
|
||||||
assert!(matches!(*left3, Expr::Value(_)));
|
Expr::BinaryOp(left3, Op::Div, right3) => {
|
||||||
assert!(matches!(*right3, Expr::Value(_)));
|
assert!(matches!(*left3, Expr::Value(_)));
|
||||||
}
|
assert!(matches!(*right3, Expr::Value(_)));
|
||||||
_ => panic!("Expected division"),
|
}
|
||||||
}
|
_ => panic!("Expected division"),
|
||||||
}
|
}
|
||||||
_ => panic!("Expected subtraction"),
|
}
|
||||||
}
|
_ => panic!("Expected subtraction"),
|
||||||
}
|
}
|
||||||
_ => panic!("Expected addition at top level"),
|
}
|
||||||
}
|
_ => panic!("Expected addition at top level"),
|
||||||
} else {
|
}
|
||||||
panic!("Failed to parse expression");
|
} else {
|
||||||
}
|
panic!("Failed to parse expression");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[test]
|
|
||||||
fn test_function_call() {
|
#[test]
|
||||||
assert!(matches!(
|
fn test_function_call() {
|
||||||
parser::expression("add(1, 2)"),
|
assert!(matches!(
|
||||||
Ok(Expr::FunctionCall(_, _))
|
parser::expression("add(1, 2)"),
|
||||||
));
|
Ok(Expr::FunctionCall(_, _))
|
||||||
}
|
));
|
||||||
|
}
|
||||||
#[test]
|
|
||||||
fn test_var_decl() {
|
#[test]
|
||||||
let input = "x = 1";
|
fn test_var_decl() {
|
||||||
let res = parser::assignment(input);
|
let input = "x = 1";
|
||||||
assert!(matches!(res, Ok(Stmt::Assignment(_, _))));
|
let res = parser::assignment(input);
|
||||||
}
|
assert!(matches!(res, Ok(Stmt::Assignment(_, _))));
|
||||||
|
}
|
||||||
#[test]
|
|
||||||
fn test_simple_arithmetic() {
|
#[test]
|
||||||
let input = "x = 1 + 2 * 3";
|
fn test_simple_arithmetic() {
|
||||||
let result = parser::program(input).unwrap();
|
let input = "x = 1 + 2 * 3";
|
||||||
|
let result = parser::program(input).unwrap();
|
||||||
assert_eq!(result.len(), 1);
|
|
||||||
if let Stmt::Assignment(name, expr) = &result[0] {
|
assert_eq!(result.len(), 1);
|
||||||
assert_eq!(name, "x");
|
if let Stmt::Assignment(name, expr) = &result[0] {
|
||||||
if let Expr::BinaryOp(left, op, right) = expr.as_ref() {
|
assert_eq!(name, "x");
|
||||||
assert!(matches!(op, Op::Add));
|
if let Expr::BinaryOp(left, op, right) = expr.as_ref() {
|
||||||
assert!(matches!(**left, Expr::Value(Value::Number(_))));
|
assert!(matches!(op, Op::Add));
|
||||||
if let Expr::BinaryOp(mul_left, mul_op, mul_right) = right.as_ref() {
|
assert!(matches!(**left, Expr::Value(Value::Number(_))));
|
||||||
assert!(matches!(mul_op, Op::Mul));
|
if let Expr::BinaryOp(mul_left, mul_op, mul_right) = right.as_ref() {
|
||||||
assert!(matches!(**mul_left, Expr::Value(Value::Number(_))));
|
assert!(matches!(mul_op, Op::Mul));
|
||||||
assert!(matches!(**mul_right, Expr::Value(Value::Number(_))));
|
assert!(matches!(**mul_left, Expr::Value(Value::Number(_))));
|
||||||
} else {
|
assert!(matches!(**mul_right, Expr::Value(Value::Number(_))));
|
||||||
panic!("Expected multiplication operation");
|
} else {
|
||||||
}
|
panic!("Expected multiplication operation");
|
||||||
} else {
|
}
|
||||||
panic!("Expected binary operation");
|
} else {
|
||||||
}
|
panic!("Expected binary operation");
|
||||||
} else {
|
}
|
||||||
panic!("Expected assignment statement");
|
} else {
|
||||||
}
|
panic!("Expected assignment statement");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[test]
|
|
||||||
fn test_if_statement() {
|
#[test]
|
||||||
let input = "if x < 10 then y = x else y = 0 end";
|
fn test_if_statement() {
|
||||||
let result = parser::program(input).unwrap();
|
let input = "if x < 10 then y = x else y = 0 end";
|
||||||
|
let result = parser::program(input).unwrap();
|
||||||
assert_eq!(result.len(), 1);
|
|
||||||
if let Stmt::If(condition, then_branch, else_branch) = &result[0] {
|
assert_eq!(result.len(), 1);
|
||||||
// Check condition
|
if let Stmt::If(condition, then_branch, else_branch) = &result[0] {
|
||||||
if let Expr::BinaryOp(left, op, right) = condition.as_ref() {
|
// Check condition
|
||||||
assert!(matches!(op, Op::Lt));
|
if let Expr::BinaryOp(left, op, right) = condition.as_ref() {
|
||||||
assert!(matches!(**left, Expr::Variable(_)));
|
assert!(matches!(op, Op::Lt));
|
||||||
assert!(matches!(**right, Expr::Value(Value::Number(_))));
|
assert!(matches!(**left, Expr::Variable(_)));
|
||||||
} else {
|
assert!(matches!(**right, Expr::Value(Value::Number(_))));
|
||||||
panic!("Expected binary operation in condition");
|
} else {
|
||||||
}
|
panic!("Expected binary operation in condition");
|
||||||
|
}
|
||||||
// Check then branch
|
|
||||||
assert_eq!(then_branch.len(), 1);
|
// Check then branch
|
||||||
assert!(matches!(&then_branch[0], Stmt::Assignment(_, _)));
|
assert_eq!(then_branch.len(), 1);
|
||||||
|
assert!(matches!(&then_branch[0], Stmt::Assignment(_, _)));
|
||||||
// Check else branch
|
|
||||||
assert!(else_branch.is_some());
|
// Check else branch
|
||||||
let else_branch = else_branch.as_ref().unwrap();
|
assert!(else_branch.is_some());
|
||||||
assert_eq!(else_branch.len(), 1);
|
let else_branch = else_branch.as_ref().unwrap();
|
||||||
assert!(matches!(&else_branch[0], Stmt::Assignment(_, _)));
|
assert_eq!(else_branch.len(), 1);
|
||||||
} else {
|
assert!(matches!(&else_branch[0], Stmt::Assignment(_, _)));
|
||||||
panic!("Expected if statement");
|
} else {
|
||||||
}
|
panic!("Expected if statement");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[test]
|
|
||||||
fn test_nested_function_calls() {
|
#[test]
|
||||||
let input = "result = max(min(a, b), abs(c))";
|
fn test_nested_function_calls() {
|
||||||
let result = parser::program(input).unwrap();
|
let input = "result = max(min(a, b), abs(c))";
|
||||||
|
let result = parser::program(input).unwrap();
|
||||||
assert_eq!(result.len(), 1);
|
|
||||||
if let Stmt::Assignment(name, expr) = &result[0] {
|
assert_eq!(result.len(), 1);
|
||||||
assert_eq!(name, "result");
|
if let Stmt::Assignment(name, expr) = &result[0] {
|
||||||
if let Expr::FunctionCall(func_name, args) = expr.as_ref() {
|
assert_eq!(name, "result");
|
||||||
assert_eq!(func_name, "max");
|
if let Expr::FunctionCall(func_name, args) = expr.as_ref() {
|
||||||
assert_eq!(args.len(), 2);
|
assert_eq!(func_name, "max");
|
||||||
|
assert_eq!(args.len(), 2);
|
||||||
// Check first argument (min call)
|
|
||||||
if let Expr::FunctionCall(inner_func, inner_args) = &args[0] {
|
// Check first argument (min call)
|
||||||
assert_eq!(inner_func, "min");
|
if let Expr::FunctionCall(inner_func, inner_args) = &args[0] {
|
||||||
assert_eq!(inner_args.len(), 2);
|
assert_eq!(inner_func, "min");
|
||||||
} else {
|
assert_eq!(inner_args.len(), 2);
|
||||||
panic!("Expected min function call");
|
} else {
|
||||||
}
|
panic!("Expected min function call");
|
||||||
|
}
|
||||||
// Check second argument (abs call)
|
|
||||||
if let Expr::FunctionCall(inner_func, inner_args) = &args[1] {
|
// Check second argument (abs call)
|
||||||
assert_eq!(inner_func, "abs");
|
if let Expr::FunctionCall(inner_func, inner_args) = &args[1] {
|
||||||
assert_eq!(inner_args.len(), 1);
|
assert_eq!(inner_func, "abs");
|
||||||
} else {
|
assert_eq!(inner_args.len(), 1);
|
||||||
panic!("Expected abs function call");
|
} else {
|
||||||
}
|
panic!("Expected abs function call");
|
||||||
} else {
|
}
|
||||||
panic!("Expected function call");
|
} else {
|
||||||
}
|
panic!("Expected function call");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[test]
|
|
||||||
fn test_decimal_numbers() {
|
#[test]
|
||||||
let input = "x = 123.456";
|
fn test_decimal_numbers() {
|
||||||
let result = parser::program(input).unwrap();
|
let input = "x = 123.456";
|
||||||
|
let result = parser::program(input).unwrap();
|
||||||
if let Stmt::Assignment(_, expr) = &result[0] {
|
|
||||||
if let Expr::Value(Value::Number(n)) = expr.as_ref() {
|
if let Stmt::Assignment(_, expr) = &result[0] {
|
||||||
assert_eq!(*n, Decimal::from_str("123.456").unwrap());
|
if let Expr::Value(Value::Number(n)) = expr.as_ref() {
|
||||||
} else {
|
assert_eq!(*n, Decimal::from_str("123.456").unwrap());
|
||||||
panic!("Expected decimal number");
|
} else {
|
||||||
}
|
panic!("Expected decimal number");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[test]
|
|
||||||
fn test_complex_nested_if() {
|
#[test]
|
||||||
let input = r#"
|
fn test_complex_nested_if() {
|
||||||
if x > 0 then
|
let input = r#"
|
||||||
if y > 0 then
|
if x > 0 then
|
||||||
result = x + y
|
if y > 0 then
|
||||||
else
|
result = x + y
|
||||||
result = x - y
|
else
|
||||||
end
|
result = x - y
|
||||||
else
|
end
|
||||||
result = 0
|
else
|
||||||
end
|
result = 0
|
||||||
"#;
|
end
|
||||||
let result = parser::program(input).unwrap();
|
"#;
|
||||||
|
let result = parser::program(input).unwrap();
|
||||||
assert_eq!(result.len(), 1);
|
|
||||||
if let Stmt::If(_, then_branch, else_branch) = &result[0] {
|
assert_eq!(result.len(), 1);
|
||||||
// Check that then_branch contains another if statement
|
if let Stmt::If(_, then_branch, else_branch) = &result[0] {
|
||||||
assert_eq!(then_branch.len(), 1);
|
// Check that then_branch contains another if statement
|
||||||
assert!(matches!(&then_branch[0], Stmt::If(_, _, _)));
|
assert_eq!(then_branch.len(), 1);
|
||||||
|
assert!(matches!(&then_branch[0], Stmt::If(_, _, _)));
|
||||||
// Check else branch
|
|
||||||
assert!(else_branch.is_some());
|
// Check else branch
|
||||||
let else_branch = else_branch.as_ref().unwrap();
|
assert!(else_branch.is_some());
|
||||||
assert_eq!(else_branch.len(), 1);
|
let else_branch = else_branch.as_ref().unwrap();
|
||||||
assert!(matches!(&else_branch[0], Stmt::Assignment(_, _)));
|
assert_eq!(else_branch.len(), 1);
|
||||||
}
|
assert!(matches!(&else_branch[0], Stmt::Assignment(_, _)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[test]
|
|
||||||
fn test_syntax_errors() {
|
#[test]
|
||||||
// Missing 'end' keyword
|
fn test_syntax_errors() {
|
||||||
assert!(parser::program("if x < 10 then y = x").is_err());
|
// Missing 'end' keyword
|
||||||
|
assert!(parser::program("if x < 10 then y = x").is_err());
|
||||||
// Invalid expression
|
|
||||||
assert!(parser::program("x = 1 + * 2").is_err());
|
// Invalid expression
|
||||||
}
|
assert!(parser::program("x = 1 + * 2").is_err());
|
||||||
|
}
|
||||||
#[test]
|
|
||||||
fn test_whitespace_handling() {
|
#[test]
|
||||||
let input1 = "x=1+2";
|
fn test_whitespace_handling() {
|
||||||
let input2 = "x = 1 + 2";
|
let input1 = "x=1+2";
|
||||||
let input3 = "x = 1 + 2";
|
let input2 = "x = 1 + 2";
|
||||||
|
let input3 = "x = 1 + 2";
|
||||||
let result1 = parser::program(input1).unwrap();
|
|
||||||
let result2 = parser::program(input2).unwrap();
|
let result1 = parser::program(input1).unwrap();
|
||||||
let result3 = parser::program(input3).unwrap();
|
let result2 = parser::program(input2).unwrap();
|
||||||
|
let result3 = parser::program(input3).unwrap();
|
||||||
// All should produce equivalent ASTs
|
|
||||||
assert_eq!(result1, result2);
|
// All should produce equivalent ASTs
|
||||||
assert_eq!(result2, result3);
|
assert_eq!(result1, result2);
|
||||||
}
|
assert_eq!(result2, result3);
|
||||||
|
}
|
||||||
#[test]
|
|
||||||
fn test_compound_assignment_parsing() {
|
#[test]
|
||||||
let input = "x += 5";
|
fn test_compound_assignment_parsing() {
|
||||||
let result = parser::program(input);
|
let input = "x += 5";
|
||||||
println!("Result: {:?}", result);
|
let result = parser::program(input);
|
||||||
let result = result.unwrap();
|
println!("Result: {:?}", result);
|
||||||
assert_eq!(result.len(), 1);
|
let result = result.unwrap();
|
||||||
if let Stmt::Assignment(name, expr) = &result[0] {
|
assert_eq!(result.len(), 1);
|
||||||
assert_eq!(name, "x");
|
if let Stmt::Assignment(name, expr) = &result[0] {
|
||||||
// Should be desugared to x + 5
|
assert_eq!(name, "x");
|
||||||
if let Expr::BinaryOp(left, op, right) = expr.as_ref() {
|
// Should be desugared to x + 5
|
||||||
assert!(matches!(op, Op::Add));
|
if let Expr::BinaryOp(left, op, right) = expr.as_ref() {
|
||||||
// left should be Variable("x")
|
assert!(matches!(op, Op::Add));
|
||||||
assert!(matches!(**left, Expr::Variable(_)));
|
// left should be Variable("x")
|
||||||
// right should be Number(5)
|
assert!(matches!(**left, Expr::Variable(_)));
|
||||||
assert!(matches!(**right, Expr::Value(Value::Number(_))));
|
// right should be Number(5)
|
||||||
} else {
|
assert!(matches!(**right, Expr::Value(Value::Number(_))));
|
||||||
panic!("Expected BinaryOp after desugaring, got {:?}", expr);
|
} else {
|
||||||
}
|
panic!("Expected BinaryOp after desugaring, got {:?}", expr);
|
||||||
} else {
|
}
|
||||||
panic!("Expected Assignment, got {:?}", result[0]);
|
} else {
|
||||||
}
|
panic!("Expected Assignment, got {:?}", result[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+57
-14
@@ -1,4 +1,5 @@
|
|||||||
use crate::{ast::value::Value, bytecode::BytecodeReader, opcodes::OpCodeByte};
|
use crate::{ast::value::Value, bytecode::BytecodeReader, opcodes::OpCodeByte};
|
||||||
|
use std::cmp::Ordering;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use micromap::Map;
|
use micromap::Map;
|
||||||
use rust_decimal::{Decimal, MathematicalOps};
|
use rust_decimal::{Decimal, MathematicalOps};
|
||||||
@@ -208,12 +209,12 @@ impl<'a> VM<'a> {
|
|||||||
"modulo",
|
"modulo",
|
||||||
),
|
),
|
||||||
OpCodeByte::Pow => self.binary_op(|a, b| Ok(a.powd(b)), "power"),
|
OpCodeByte::Pow => self.binary_op(|a, b| Ok(a.powd(b)), "power"),
|
||||||
OpCodeByte::Lt => self.compare_op(|a, b| a < b, "less than"),
|
OpCodeByte::Lt => self.compare_op(|o| o == Ordering::Less, "less than"),
|
||||||
OpCodeByte::Lte => self.compare_op(|a, b| a <= b, "less than or equal"),
|
OpCodeByte::Lte => self.compare_op(|o| o != Ordering::Greater, "less than or equal"),
|
||||||
OpCodeByte::Gt => self.compare_op(|a, b| a > b, "greater than"),
|
OpCodeByte::Gt => self.compare_op(|o| o == Ordering::Greater, "greater than"),
|
||||||
OpCodeByte::Gte => self.compare_op(|a, b| a >= b, "greater than or equal"),
|
OpCodeByte::Gte => self.compare_op(|o| o != Ordering::Less, "greater than or equal"),
|
||||||
OpCodeByte::Eq => self.compare_op(|a, b| a == b, "equal"),
|
OpCodeByte::Eq => self.equality_op(false),
|
||||||
OpCodeByte::Neq => self.compare_op(|a, b| a != b, "not equal"),
|
OpCodeByte::Neq => self.equality_op(true),
|
||||||
OpCodeByte::Contains => self.handle_contains(),
|
OpCodeByte::Contains => self.handle_contains(),
|
||||||
OpCodeByte::And => self.handle_and(),
|
OpCodeByte::And => self.handle_and(),
|
||||||
OpCodeByte::Or => self.handle_or(),
|
OpCodeByte::Or => self.handle_or(),
|
||||||
@@ -893,11 +894,54 @@ impl<'a> VM<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper for comparison operations
|
/// Helper for equality operations (`==`, `!=`).
|
||||||
|
/// Works on every value type via structural equality; values of different
|
||||||
|
/// types are never equal (no error).
|
||||||
|
#[inline]
|
||||||
|
fn equality_op(&mut self, negate: bool) -> Result<(), VMError> {
|
||||||
|
let dest = self
|
||||||
|
.reader
|
||||||
|
.read_register()
|
||||||
|
.map_err(|e| VMError::BytecodeError(e))? as usize;
|
||||||
|
let a = self
|
||||||
|
.reader
|
||||||
|
.read_register()
|
||||||
|
.map_err(|e| VMError::BytecodeError(e))? as usize;
|
||||||
|
let b = self
|
||||||
|
.reader
|
||||||
|
.read_register()
|
||||||
|
.map_err(|e| VMError::BytecodeError(e))? as usize;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if dest >= MAX_REGISTERS || a >= MAX_REGISTERS || b >= MAX_REGISTERS {
|
||||||
|
return Err(VMError::RuntimeError(format!(
|
||||||
|
"Invalid register: dest={}, a={}, b={}",
|
||||||
|
dest, a, b
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let equal = self.registers[a] == self.registers[b];
|
||||||
|
self.registers[dest] = Value::Boolean(equal != negate);
|
||||||
|
|
||||||
|
log_debug!(
|
||||||
|
self,
|
||||||
|
"{} r{} = r{} {} r{}",
|
||||||
|
if negate { "not equal" } else { "equal" },
|
||||||
|
dest,
|
||||||
|
a,
|
||||||
|
if negate { "!=" } else { "==" },
|
||||||
|
b
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper for ordering comparisons (`<`, `<=`, `>`, `>=`).
|
||||||
|
/// Supported on Number/Number and String/String (lexicographic byte order).
|
||||||
#[inline]
|
#[inline]
|
||||||
fn compare_op<F>(&mut self, op: F, op_name: &'static str) -> Result<(), VMError>
|
fn compare_op<F>(&mut self, op: F, op_name: &'static str) -> Result<(), VMError>
|
||||||
where
|
where
|
||||||
F: FnOnce(&Decimal, &Decimal) -> bool,
|
F: FnOnce(Ordering) -> bool,
|
||||||
{
|
{
|
||||||
let dest = self
|
let dest = self
|
||||||
.reader
|
.reader
|
||||||
@@ -920,11 +964,9 @@ impl<'a> VM<'a> {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
match (&self.registers[a], &self.registers[b]) {
|
let ordering = match (&self.registers[a], &self.registers[b]) {
|
||||||
(Value::Number(a_num), Value::Number(b_num)) => {
|
(Value::Number(a_num), Value::Number(b_num)) => a_num.cmp(b_num),
|
||||||
let result = op(a_num, b_num);
|
(Value::String(a_str), Value::String(b_str)) => a_str.as_str().cmp(b_str.as_str()),
|
||||||
self.registers[dest] = Value::Boolean(result);
|
|
||||||
}
|
|
||||||
(a_val, b_val) => {
|
(a_val, b_val) => {
|
||||||
return Err(VMError::InvalidOperation {
|
return Err(VMError::InvalidOperation {
|
||||||
operation: op_name,
|
operation: op_name,
|
||||||
@@ -932,7 +974,8 @@ impl<'a> VM<'a> {
|
|||||||
right_type: b_val.type_name(),
|
right_type: b_val.type_name(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
self.registers[dest] = Value::Boolean(op(ordering));
|
||||||
|
|
||||||
log_debug!(self, "{} r{} = r{} {} r{}", op_name, dest, a, op_name, b);
|
log_debug!(self, "{} r{} = r{} {} r{}", op_name, dest, a, op_name, b);
|
||||||
|
|
||||||
|
|||||||
+130
-1
@@ -1939,4 +1939,133 @@ fn test_invoice_full_scenario() {
|
|||||||
("kdvOrani", Value::Number(dec!(0.20))),
|
("kdvOrani", Value::Number(dec!(0.20))),
|
||||||
]);
|
]);
|
||||||
assert_eq!(result, Value::Number(dec!(272760.00)));
|
assert_eq!(result, Value::Number(dec!(272760.00)));
|
||||||
}
|
}
|
||||||
|
// ==================== COMPARISON TESTS ====================
|
||||||
|
|
||||||
|
fn run_expr_err(code: &str, globals: Vec<(&str, Value)>) -> String {
|
||||||
|
let ast = parser::program(code).expect("Failed to parse");
|
||||||
|
let mut compiler = Compiler::new();
|
||||||
|
let bytecode = compiler.compile(ast).expect("Failed to compile");
|
||||||
|
let mut vm = VM::new(&bytecode);
|
||||||
|
for (name, value) in globals {
|
||||||
|
vm.set_global(name, value);
|
||||||
|
}
|
||||||
|
vm.execute().unwrap_err().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compare_number_list_equality() {
|
||||||
|
let a = Value::NumberList(Rc::new(vec![dec!(1), dec!(2)]));
|
||||||
|
let b = Value::NumberList(Rc::new(vec![dec!(1), dec!(2)]));
|
||||||
|
let c = Value::NumberList(Rc::new(vec![dec!(1), dec!(3)]));
|
||||||
|
assert_eq!(
|
||||||
|
run_expr_with_globals("a == b", vec![("a", a.clone()), ("b", b.clone())]),
|
||||||
|
Value::Boolean(true)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_expr_with_globals("a == c", vec![("a", a.clone()), ("c", c.clone())]),
|
||||||
|
Value::Boolean(false)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_expr_with_globals("a != c", vec![("a", a), ("c", c)]),
|
||||||
|
Value::Boolean(true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compare_string_list_equality() {
|
||||||
|
let a = Value::StringList(Rc::new(vec![SmolStr::new("x"), SmolStr::new("y")]));
|
||||||
|
let b = Value::StringList(Rc::new(vec![SmolStr::new("x"), SmolStr::new("y")]));
|
||||||
|
let c = Value::StringList(Rc::new(vec![SmolStr::new("y"), SmolStr::new("x")]));
|
||||||
|
assert_eq!(
|
||||||
|
run_expr_with_globals("a == b", vec![("a", a.clone()), ("b", b)]),
|
||||||
|
Value::Boolean(true)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_expr_with_globals("a == c", vec![("a", a), ("c", c)]),
|
||||||
|
Value::Boolean(false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compare_list_of_objects_equality() {
|
||||||
|
let mk = |n: i64| {
|
||||||
|
let mut m = IndexMap::new();
|
||||||
|
m.insert(SmolStr::new("id"), Value::Number(dec!(1) * rust_decimal::Decimal::from(n)));
|
||||||
|
Value::Object(Rc::new(m))
|
||||||
|
};
|
||||||
|
let a = Value::List(Rc::new(vec![mk(1), mk(2)]));
|
||||||
|
let b = Value::List(Rc::new(vec![mk(1), mk(2)]));
|
||||||
|
let c = Value::List(Rc::new(vec![mk(1)]));
|
||||||
|
assert_eq!(
|
||||||
|
run_expr_with_globals("a == b", vec![("a", a.clone()), ("b", b)]),
|
||||||
|
Value::Boolean(true)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_expr_with_globals("a == c", vec![("a", a), ("c", c)]),
|
||||||
|
Value::Boolean(false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compare_different_types_never_equal() {
|
||||||
|
let list = Value::NumberList(Rc::new(vec![dec!(1)]));
|
||||||
|
assert_eq!(
|
||||||
|
run_expr_with_globals("l == 1", vec![("l", list.clone())]),
|
||||||
|
Value::Boolean(false)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_expr_with_globals("l == \"1\"", vec![("l", list.clone())]),
|
||||||
|
Value::Boolean(false)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_expr_with_globals("l == null", vec![("l", list)]),
|
||||||
|
Value::Boolean(false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compare_ordering_string_vs_number_errors() {
|
||||||
|
let err = run_expr_err("\"a\" < 1", vec![]);
|
||||||
|
assert!(err.contains("less than"), "got: {err}");
|
||||||
|
assert!(err.contains("String"), "got: {err}");
|
||||||
|
assert!(err.contains("Number"), "got: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compare_ordering_boolean_errors() {
|
||||||
|
let err = run_expr_err("true > false", vec![]);
|
||||||
|
assert!(err.contains("greater than"), "got: {err}");
|
||||||
|
assert!(err.contains("Boolean"), "got: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compare_ordering_null_errors() {
|
||||||
|
let err = run_expr_err("null <= 1", vec![]);
|
||||||
|
assert!(err.contains("less than or equal"), "got: {err}");
|
||||||
|
assert!(err.contains("Null"), "got: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compare_ordering_lists_errors() {
|
||||||
|
let list = Value::NumberList(Rc::new(vec![dec!(1)]));
|
||||||
|
let err = run_expr_err("l >= l", vec![("l", list)]);
|
||||||
|
assert!(err.contains("greater than or equal"), "got: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_null_is_reserved_keyword() {
|
||||||
|
assert!(parser::program("null = 5").is_err());
|
||||||
|
// identifiers that merely start with "null" are still fine
|
||||||
|
assert_eq!(run_expr("nullable = 3\nnullable"), Value::Number(dec!(3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_string_compare_in_condition() {
|
||||||
|
let code = r#"
|
||||||
|
grade = "B"
|
||||||
|
result = 0
|
||||||
|
if grade == "A" then result = 100 else if grade == "B" then result = 80 else result = 0 end
|
||||||
|
"#;
|
||||||
|
assert_eq!(run_and_get_result(code), Value::Number(dec!(80)));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1284,5 +1284,179 @@
|
|||||||
"maxPrice": { "type": "number", "value": "200" }
|
"maxPrice": { "type": "number", "value": "200" }
|
||||||
},
|
},
|
||||||
"expected": { "type": "number", "value": "200" }
|
"expected": { "type": "number", "value": "200" }
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "compare: string equality",
|
||||||
|
"code": "\"a\" == \"a\"",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string inequality",
|
||||||
|
"code": "\"a\" == \"b\"",
|
||||||
|
"expected": { "type": "boolean", "value": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string not equal",
|
||||||
|
"code": "\"a\" != \"b\"",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string case sensitive",
|
||||||
|
"code": "\"A\" == \"a\"",
|
||||||
|
"expected": { "type": "boolean", "value": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string less than",
|
||||||
|
"code": "\"a\" < \"b\"",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string greater than",
|
||||||
|
"code": "\"b\" > \"a\"",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string lte equal",
|
||||||
|
"code": "\"a\" <= \"a\"",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string gte",
|
||||||
|
"code": "\"abc\" >= \"abd\"",
|
||||||
|
"expected": { "type": "boolean", "value": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string prefix ordering",
|
||||||
|
"code": "\"ab\" < \"abc\"",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: boolean equality",
|
||||||
|
"code": "true == true",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: boolean inequality",
|
||||||
|
"code": "true == false",
|
||||||
|
"expected": { "type": "boolean", "value": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: boolean not equal",
|
||||||
|
"code": "true != false",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: null literal",
|
||||||
|
"code": "null",
|
||||||
|
"expected": { "type": "null" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: null == null",
|
||||||
|
"code": "null == null",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: null != null",
|
||||||
|
"code": "null != null",
|
||||||
|
"expected": { "type": "boolean", "value": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: number == null is false",
|
||||||
|
"code": "1 == null",
|
||||||
|
"expected": { "type": "boolean", "value": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: number != null",
|
||||||
|
"code": "1 != null",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string == number is false",
|
||||||
|
"code": "\"1\" == 1",
|
||||||
|
"expected": { "type": "boolean", "value": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string != number",
|
||||||
|
"code": "\"1\" != 1",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: boolean == number is false",
|
||||||
|
"code": "true == 1",
|
||||||
|
"expected": { "type": "boolean", "value": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: number equality still works",
|
||||||
|
"code": "1 == 1",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: number decimal equality",
|
||||||
|
"code": "1.0 == 1",
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string eq in if",
|
||||||
|
"code": "x = \"ok\"\nif x == \"ok\" then 1 else 2 end",
|
||||||
|
"expected": { "type": "number", "value": "1" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: string in condition with globals",
|
||||||
|
"code": "if status == \"active\" then \"yes\" else \"no\" end",
|
||||||
|
"globals": {
|
||||||
|
"status": {"type": "string", "value": "active"}
|
||||||
|
},
|
||||||
|
"expected": { "type": "string", "value": "yes" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: null global",
|
||||||
|
"code": "x == null",
|
||||||
|
"globals": {
|
||||||
|
"x": {"type": "null"}
|
||||||
|
},
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: non-null global vs null",
|
||||||
|
"code": "x == null",
|
||||||
|
"globals": {
|
||||||
|
"x": {"type": "number", "value": "5"}
|
||||||
|
},
|
||||||
|
"expected": { "type": "boolean", "value": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: object equality",
|
||||||
|
"code": "a == b",
|
||||||
|
"globals": {
|
||||||
|
"a": {"type": "object", "value": {"k": "1"}},
|
||||||
|
"b": {"type": "object", "value": {"k": "1"}}
|
||||||
|
},
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: object inequality",
|
||||||
|
"code": "a == b",
|
||||||
|
"globals": {
|
||||||
|
"a": {"type": "object", "value": {"k": "1"}},
|
||||||
|
"b": {"type": "object", "value": {"k": "2"}}
|
||||||
|
},
|
||||||
|
"expected": { "type": "boolean", "value": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: object field string eq",
|
||||||
|
"code": "o.name == \"ali\"",
|
||||||
|
"globals": {
|
||||||
|
"o": {"type": "object", "value": {"name": "ali"}}
|
||||||
|
},
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compare: object field string ordering",
|
||||||
|
"code": "o.name < \"b\"",
|
||||||
|
"globals": {
|
||||||
|
"o": {"type": "object", "value": {"name": "ali"}}
|
||||||
|
},
|
||||||
|
"expected": { "type": "boolean", "value": true }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Generated
+2
-2
@@ -141,7 +141,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dexpr"
|
name = "dexpr"
|
||||||
version = "0.1.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@@ -159,7 +159,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dexpr-wasm"
|
name = "dexpr-wasm"
|
||||||
version = "0.1.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dexpr",
|
"dexpr",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dexpr-wasm"
|
name = "dexpr-wasm"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|||||||
Reference in New Issue
Block a user