rip-lang 1.4.2 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +10 -10
- package/docs/dist/rip.browser.js +13 -3
- package/docs/dist/rip.browser.min.js +2 -2
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/package.json +1 -1
- package/src/codegen.js +20 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to Rip will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.3] - 2025-11-07
|
|
9
|
+
|
|
10
|
+
### Refactored
|
|
11
|
+
- **Convert generateNot to s-expression approach** - Following Rip philosophy!
|
|
12
|
+
- Check operand TYPE at s-expression level (not regex on generated code)
|
|
13
|
+
- Handles primitives: `!1`, `!x`, `!true` (clean output without extra parens)
|
|
14
|
+
- Handles property access: `!obj.prop`, `!arr[0]` (clean output)
|
|
15
|
+
- Conservative for complex expressions: `!(a + b)` (keeps parens)
|
|
16
|
+
- Simple, maintainable logic (~26 lines)
|
|
17
|
+
- Follows philosophy from Issues #46 (flattenBinaryChain) and #49 (unwrapComprehensionIIFE)
|
|
18
|
+
- **Work at IR level, not string level!**
|
|
19
|
+
|
|
20
|
+
All 938 tests passing (100%) ✅
|
|
21
|
+
|
|
8
22
|
## [1.4.2] - 2025-11-07
|
|
9
23
|
|
|
10
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-1.4.
|
|
12
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-1.4.2-blue.svg" alt="Version"></a>
|
|
13
13
|
<a href="#es2022-target"><img src="https://img.shields.io/badge/target-ES2022-blue.svg" alt="Target"></a>
|
|
14
|
-
<a href="#current-status"><img src="https://img.shields.io/badge/tests-
|
|
14
|
+
<a href="#current-status"><img src="https://img.shields.io/badge/tests-938%2F938-brightgreen.svg" alt="Tests"></a>
|
|
15
15
|
<a href="#current-status"><img src="https://img.shields.io/badge/coverage-100%25-brightgreen.svg" alt="Coverage"></a>
|
|
16
16
|
<a href="#zero-dependencies"><img src="https://img.shields.io/badge/dependencies-ZERO-brightgreen.svg" alt="Dependencies"></a>
|
|
17
17
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
**Write less. Do more. Zero dependencies.**
|
|
25
25
|
|
|
26
|
-
Rip brings CoffeeScript's elegance to modern JavaScript—but
|
|
26
|
+
Rip brings CoffeeScript's elegance to modern JavaScript—but **~50% smaller**, completely standalone, and self-hosting. No build tools, no external dependencies, not even a parser generator. Just clone and go.
|
|
27
27
|
|
|
28
28
|
#### 💎 Elegant syntax with modern features
|
|
29
29
|
|
|
@@ -183,15 +183,15 @@ case '+': {
|
|
|
183
183
|
}
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
**Result:
|
|
186
|
+
**Result: ~50% smaller implementation**
|
|
187
187
|
|
|
188
188
|
| Component | CoffeeScript | Rip | Notes |
|
|
189
189
|
|-----------|--------------|-----|-------|
|
|
190
190
|
| Lexer+Rewriter | 3,558 LOC | **3,145 LOC** | Expanded syntax |
|
|
191
191
|
| Parser Generator | 2,285 LOC (Jison) | **928 LOC** (Solar) | Built-in, ~156× faster! |
|
|
192
|
-
| Compiler | 10,346 LOC (AST Nodes) | **5,
|
|
192
|
+
| Compiler | 10,346 LOC (AST Nodes) | **5,239 LOC** (S-expressions) | Clean dispatch table |
|
|
193
193
|
| Tools | 1,571 LOC (Repl, Cake) | **520 LOC** (Repl, Browser) | 3 Repl's + Browser |
|
|
194
|
-
| **Total** | **17,760 LOC** | **9,
|
|
194
|
+
| **Total** | **17,760 LOC** | **9,832 LOC** | **~50% smaller** |
|
|
195
195
|
|
|
196
196
|
**Plus:**
|
|
197
197
|
- ✅ **ZERO dependencies** - Everything included
|
|
@@ -886,7 +886,7 @@ Both syntaxes handle `null` and `undefined` - pick the style that fits your proj
|
|
|
886
886
|
│ Source │───>│ Lexer │───>│ Parser │───>│ Codegen │
|
|
887
887
|
│ Code │ │ (Coffee) │ │ (Solar) │ │ (Rip) │
|
|
888
888
|
└────────┘ └────────────┘ └──────────┘ └─────────┘
|
|
889
|
-
3,145 LOC 928 LOC 5,
|
|
889
|
+
3,145 LOC 928 LOC 5,239 LOC
|
|
890
890
|
15 yrs tested Generated! S-expr w/Dispatch!
|
|
891
891
|
```
|
|
892
892
|
|
|
@@ -1200,10 +1200,10 @@ We considered allowing `x => expr` (ES6 style) but decided consistency and simpl
|
|
|
1200
1200
|
| Feature | CoffeeScript | Rip |
|
|
1201
1201
|
|---------|-------------|------|
|
|
1202
1202
|
| Syntax | ✅ Elegant | ✅ Elegant (inspired by CS) |
|
|
1203
|
-
| Implementation | 17,760 LOC | **9,
|
|
1203
|
+
| Implementation | 17,760 LOC | **9,832 LOC (~50% smaller)** |
|
|
1204
1204
|
| Dependencies | ❌ Multiple | ✅ **ZERO** |
|
|
1205
1205
|
| Parser Generator | ❌ External (Jison) | ✅ **Built-in (solar.rip)** |
|
|
1206
|
-
| Self-Hosting | ❌ No | ✅ **Yes** |
|
|
1206
|
+
| Self-Hosting | ❌ No | ✅ **Yes (fully operational)** |
|
|
1207
1207
|
| Modules | CommonJS | ✅ ES6 native |
|
|
1208
1208
|
| Classes | ES5 functions | ✅ ES6 classes |
|
|
1209
1209
|
| Maintenance | Complex AST | ✅ Simple sexps |
|
|
@@ -1345,7 +1345,7 @@ Now supports both ES6 and CoffeeScript syntax:
|
|
|
1345
1345
|
- ✅ v0.5.0: **COMPLETE** - Dual syntax support! Postfix spread/rest (`x...`) + legacy existential (`x ? y`)!
|
|
1346
1346
|
- ✅ v0.5.1: **COMPLETE** - Smart comprehensions! Context-aware optimization, self-hosting, 843/843 tests!
|
|
1347
1347
|
- ✅ v0.9.0: **COMPLETE** - Production release! Zero dependencies, complete documentation, 43KB browser bundle!
|
|
1348
|
-
- ✅ v1.0.0: **RELEASED** - Initial release! ~
|
|
1348
|
+
- ✅ v1.0.0: **RELEASED** - Initial release! ~50% smaller than CoffeeScript! 🎉
|
|
1349
1349
|
|
|
1350
1350
|
See [CHANGELOG.md](CHANGELOG.md) for detailed progress.
|
|
1351
1351
|
|
package/docs/dist/rip.browser.js
CHANGED
|
@@ -4656,8 +4656,18 @@ ${this.indent()}}`;
|
|
|
4656
4656
|
}
|
|
4657
4657
|
generateNot(head, rest, context, sexpr) {
|
|
4658
4658
|
const [operand] = rest;
|
|
4659
|
+
if (typeof operand === "string" || operand instanceof String) {
|
|
4660
|
+
return `!${this.generate(operand, "value")}`;
|
|
4661
|
+
}
|
|
4662
|
+
if (Array.isArray(operand)) {
|
|
4663
|
+
const type = operand[0];
|
|
4664
|
+
const highPrecedence = [".", "?.", "::", "?::", "[]", "?[]", "optindex", "optcall"];
|
|
4665
|
+
if (highPrecedence.includes(type)) {
|
|
4666
|
+
return `!${this.generate(operand, "value")}`;
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4659
4669
|
const operandCode = this.generate(operand, "value");
|
|
4660
|
-
if (
|
|
4670
|
+
if (operandCode.startsWith("(")) {
|
|
4661
4671
|
return `!${operandCode}`;
|
|
4662
4672
|
}
|
|
4663
4673
|
return `(!${operandCode})`;
|
|
@@ -7034,8 +7044,8 @@ function compileToJS(source, options = {}) {
|
|
|
7034
7044
|
return compiler.compileToJS(source);
|
|
7035
7045
|
}
|
|
7036
7046
|
// src/browser.js
|
|
7037
|
-
var VERSION = "1.4.
|
|
7038
|
-
var BUILD_DATE = "2025-11-07@
|
|
7047
|
+
var VERSION = "1.4.3";
|
|
7048
|
+
var BUILD_DATE = "2025-11-07@10:29:23GMT";
|
|
7039
7049
|
var dedent = (s) => {
|
|
7040
7050
|
const m = s.match(/^[ \t]*(?=\S)/gm);
|
|
7041
7051
|
const i = Math.min(...(m || []).map((x) => x.length));
|
|
@@ -81,7 +81,7 @@ ${J.map((O)=>this.indent()+O).join(`
|
|
|
81
81
|
${this.indent()}}`}else Q+=`{ const ${G} = ${Z}[${F}]; ${this.generate(A,"statement")}; }`;else if(D)Q+=this.generateLoopBodyWithGuard(A,D);else Q+=this.generateLoopBody(A);return Q}generateForFrom(Y,_,X,M){let E=Array.isArray(_[0])?_[0]:[_[0]],[U]=E,R=_[1],D=_[2],A=_[3],F=_[4],G=!1,Z=[];if(Array.isArray(U)&&U[0]==="array"){let q=U.slice(1),K=q.findIndex((H)=>Array.isArray(H)&&H[0]==="..."||H==="...");if(K!==-1&&K<q.length-1){G=!0;let H=q.slice(K+1),z=H.length,N=q.slice(0,K),P=q[K],S=Array.isArray(P)&&P[0]==="..."?P[1]:"_rest",B=N.map((j)=>{if(j===",")return"";if(typeof j==="string")return j;return this.generate(j,"value")}).join(", "),I=B?`${B}, ...${S}`:`...${S}`,L=H.map((j)=>{if(j===",")return"";if(typeof j==="string")return j;return this.generate(j,"value")}).join(", ");Z.push(`[${I}] = _item`),Z.push(`[${L}] = ${S}.splice(-${z})`),this.helpers.add("slice"),((j)=>{j.slice(1).forEach((T)=>{if(T===","||T==="...")return;if(typeof T==="string")this.programVars.add(T);else if(Array.isArray(T)&&T[0]==="..."){if(typeof T[1]==="string")this.programVars.add(T[1])}})})(U)}}let Q=this.generate(R,"value"),W=D?"await ":"",J;if(G)J="_item";else if(Array.isArray(U)&&(U[0]==="array"||U[0]==="object"))J=this.generateDestructuringPattern(U);else J=U;let O=`for ${W}(const ${J} of ${Q}) `;if(G&&Z.length>0){let q=this.unwrapBlock(F),K=this.withIndent(()=>[...Z.map((H)=>this.indent()+H+";"),...this.formatStatements(q)]);O+=`{
|
|
82
82
|
${K.join(`
|
|
83
83
|
`)}
|
|
84
|
-
${this.indent()}}`}else if(A)O+=this.generateLoopBodyWithGuard(F,A);else O+=this.generateLoopBody(F);return O}generateWhile(Y,_,X,M){let E=_[0],U=_.length===3?_[1]:null,R=_[_.length-1],A=`while (${this.unwrap(this.generate(E,"value"))}) `;if(U)A+=this.generateLoopBodyWithGuard(R,U);else A+=this.generateLoopBody(R);return A}generateUntil(Y,_,X,M){let[E,U]=_,D=`while (!(${this.unwrap(this.generate(E,"value"))})) `;return D+=this.generateLoopBody(U),D}generateRange(Y,_,X,M){if(Y==="..."){if(_.length===1){let[Q]=_;return`...${this.generate(Q,"value")}`}let[A,F]=_,G=this.generate(A,"value"),Z=this.generate(F,"value");return`((s, e) => Array.from({length: Math.max(0, Math.abs(e - s))}, (_, i) => s + (i * (s <= e ? 1 : -1))))(${G}, ${Z})`}let[E,U]=_,R=this.generate(E,"value"),D=this.generate(U,"value");return`((s, e) => Array.from({length: Math.abs(e - s) + 1}, (_, i) => s + (i * (s <= e ? 1 : -1))))(${R}, ${D})`}generateNot(Y,_,X,M){let[E]=_
|
|
84
|
+
${this.indent()}}`}else if(A)O+=this.generateLoopBodyWithGuard(F,A);else O+=this.generateLoopBody(F);return O}generateWhile(Y,_,X,M){let E=_[0],U=_.length===3?_[1]:null,R=_[_.length-1],A=`while (${this.unwrap(this.generate(E,"value"))}) `;if(U)A+=this.generateLoopBodyWithGuard(R,U);else A+=this.generateLoopBody(R);return A}generateUntil(Y,_,X,M){let[E,U]=_,D=`while (!(${this.unwrap(this.generate(E,"value"))})) `;return D+=this.generateLoopBody(U),D}generateRange(Y,_,X,M){if(Y==="..."){if(_.length===1){let[Q]=_;return`...${this.generate(Q,"value")}`}let[A,F]=_,G=this.generate(A,"value"),Z=this.generate(F,"value");return`((s, e) => Array.from({length: Math.max(0, Math.abs(e - s))}, (_, i) => s + (i * (s <= e ? 1 : -1))))(${G}, ${Z})`}let[E,U]=_,R=this.generate(E,"value"),D=this.generate(U,"value");return`((s, e) => Array.from({length: Math.abs(e - s) + 1}, (_, i) => s + (i * (s <= e ? 1 : -1))))(${R}, ${D})`}generateNot(Y,_,X,M){let[E]=_;if(typeof E==="string"||E instanceof String)return`!${this.generate(E,"value")}`;if(Array.isArray(E)){let R=E[0];if([".","?.","::","?::","[]","?[]","optindex","optcall"].includes(R))return`!${this.generate(E,"value")}`}let U=this.generate(E,"value");if(U.startsWith("("))return`!${U}`;return`(!${U})`}generateBitwiseNot(Y,_,X,M){let[E]=_;return`(~${this.generate(E,"value")})`}generateIncDec(Y,_,X,M){let[E,U]=_,R=this.generate(E,"value");if(U)return`(${R}${Y})`;else return`(${Y}${R})`}generateTypeof(Y,_,X,M){let[E]=_;return`typeof ${this.generate(E,"value")}`}generateDelete(Y,_,X,M){let[E]=_;return`(delete ${this.generate(E,"value")})`}generateInstanceof(Y,_,X,M){let[E,U]=_;return`(${this.generate(E,"value")} instanceof ${this.generate(U,"value")})`}generateIn(Y,_,X,M){let[E,U]=_,R=this.generate(E,"value"),D=this.generate(U,"value"),A=(R.startsWith("'")||R.startsWith('"'))&&(R.endsWith("'")||R.endsWith('"')),F=/^[a-zA-Z_$][\w$]*$/.test(D);if(A&&F)return`(Array.isArray(${D}) || typeof ${D} === 'string' ? ${D}.includes(${R}) : (${R} in ${D}))`;return`(${R} in ${D})`}generateOf(Y,_,X,M){let[E,U]=_,R=this.generate(E,"value"),D=this.generate(U,"value");return`(${R} in ${D})`}generateRegexMatch(Y,_,X,M){let[E,U]=_;this.helpers.add("toSearchable"),this.programVars.add("_");let R=this.generate(U,"value"),A=R.includes("/m")?", true":"";return`(_ = toSearchable(${this.generate(E,"value")}${A}).match(${R}))`}generateNew(Y,_,X,M){let[E]=_;if(Array.isArray(E)&&(E[0]==="."||E[0]==="?.")){let[U,R,D]=E;if(Array.isArray(R)&&!R[0].startsWith)return`(${this.generate(["new",R],"value")}).${D}`;return`new ${this.generate(R,"value")}.${D}`}if(Array.isArray(E)){let[U,...R]=E,D=this.generate(U,"value"),A=R.map((F)=>this.unwrap(this.generate(F,"value"))).join(", ");return`new ${D}(${A})`}return`new ${this.generate(E,"value")}()`}generateLogicalAnd(Y,_,X,M){let U=this.flattenBinaryChain(M).slice(1);if(U.length===0)return"true";if(U.length===1)return this.generate(U[0],"value");return`(${U.map((D)=>this.generate(D,"value")).join(" && ")})`}generateLogicalOr(Y,_,X,M){let U=this.flattenBinaryChain(M).slice(1);if(U.length===0)return"true";if(U.length===1)return this.generate(U[0],"value");return`(${U.map((D)=>this.generate(D,"value")).join(" || ")})`}generateArray(Y,_,X,M){let E=_.length>0&&_[_.length-1]===",",U=_.map((R)=>{if(R===",")return"";if(R==="...")return"";if(Array.isArray(R)&&R[0]==="...")return`...${this.generate(R[1],"value")}`;return this.generate(R,"value")}).join(", ");return E?`[${U},]`:`[${U}]`}generateObject(Y,_,X,M){if(_.length===1&&Array.isArray(_[0])&&Array.isArray(_[0][1])&&_[0][1][0]==="comprehension"){let[U,R]=_[0],[,D,A,F]=R;return this.generate(["object-comprehension",U,D,A,F],X)}return`{${_.map((U)=>{if(Array.isArray(U)&&U[0]==="...")return`...${this.generate(U[1],"value")}`;let[R,D,A]=U,F;if(Array.isArray(R)&&R[0]==="computed"){let Z=R[1];F=`[${this.generate(Z,"value")}]`}else F=this.generate(R,"value");let G=this.generate(D,"value");if(A==="=")return`${F} = ${G}`;else if(A===":")return`${F}: ${G}`;else{if(F===G&&!Array.isArray(R))return F;return`${F}: ${G}`}}).join(", ")}}`}generateBlock(Y,_,X,M){if(X==="statement")return`{
|
|
85
85
|
${this.withIndent(()=>this.formatStatements(_)).join(`
|
|
86
86
|
`)}
|
|
87
87
|
${this.indent()}}`;return this.formatStatements(_,X)}generateTry(Y,_,X,M){let E=X==="value",U="try ",R=_[0];if(E&&Array.isArray(R)&&R[0]==="block")U+=this.generateBlockWithReturns(R);else U+=this.generate(R,"statement");if(_.length>=2&&Array.isArray(_[1])&&_[1].length===2&&_[1][0]!=="block"){let[D,A]=_[1];if(U+=" catch",D&&Array.isArray(D)&&(D[0]==="object"||D[0]==="array")){U+=" (error)";let Z=`(${this.generate(D,"value")} = error)`;if(Array.isArray(A)&&A[0]==="block")A=["block",Z,...A.slice(1)];else A=["block",Z,A]}else if(D)U+=` (${D})`;if(E&&Array.isArray(A)&&A[0]==="block")U+=" "+this.generateBlockWithReturns(A);else U+=" "+this.generate(A,"statement")}else if(_.length===2)U+=" finally "+this.generate(_[1],"statement");if(_.length===3)U+=" finally "+this.generate(_[2],"statement");if(E)return`(${this.containsAwait(_[0])||_[1]&&this.containsAwait(_[1])?"async ":""}() => { ${U} })()`;return U}generateThrow(Y,_,X,M){let[E]=_,U=null;if(Array.isArray(E)){let D=E,A=null;if(E[0]==="new"&&Array.isArray(E[1])&&(E[1][0]==="if"||E[1][0]==="unless"))A="new",D=E[1];else if(E[0]==="if"||E[0]==="unless")D=E;if(D[0]==="if"||D[0]==="unless"){let[F,G,Z]=D,Q=F==="unless",W=Z;if(Array.isArray(Z)&&Z.length===1)W=Z[0];if(A==="new")E=["new",W];else E=W;let J=this.generate(G,"value"),O=`throw ${this.generate(E,"value")}`;if(Q)return`if (!(${J})) {
|
|
@@ -252,4 +252,4 @@ ${this.indent()}}`}return`{ if (${this.generate(_,"value")}) ${this.generate(Y,"
|
|
|
252
252
|
`:"",Y=X.slice(0,M).join(`
|
|
253
253
|
`)}let U=new x1().tokenize(Y);if(this.options.showTokens)U.forEach((F)=>{console.log(`${F[0].padEnd(12)} ${JSON.stringify(F[1])}`)}),console.log();A1.lexer={tokens:U,pos:0,setInput:function(F,G){},lex:function(){if(this.pos>=this.tokens.length)return 1;let F=this.tokens[this.pos++];return this.yytext=F[1],this.yylloc=F[2],F[0]}};let R;try{R=A1.parse(Y)}catch(F){if(/\?\s*\([^)]*\?[^)]*:[^)]*\)\s*:/.test(Y)||/\?\s+\w+\s+\?\s+/.test(Y))throw new Error(`Nested ternary operators are not supported. Use if/else statements instead:
|
|
254
254
|
Instead of: x ? (y ? a : b) : c
|
|
255
|
-
Use: if x then (if y then a else b) else c`);throw F}if(this.options.showSExpr)console.log(_1(R,0,!0)),console.log();let A=new k({dataSection:_}).compile(R);return{tokens:U,sexpr:R,code:A,data:_}}compileToJS(Y){return this.compile(Y).code}compileToSExpr(Y){return this.compile(Y).sexpr}}function o2(Y,_={}){return new K1(_).compile(Y)}function q1(Y,_={}){return new K1(_).compileToJS(Y)}var F3="1.4.
|
|
255
|
+
Use: if x then (if y then a else b) else c`);throw F}if(this.options.showSExpr)console.log(_1(R,0,!0)),console.log();let A=new k({dataSection:_}).compile(R);return{tokens:U,sexpr:R,code:A,data:_}}compileToJS(Y){return this.compile(Y).code}compileToSExpr(Y){return this.compile(Y).sexpr}}function o2(Y,_={}){return new K1(_).compile(Y)}function q1(Y,_={}){return new K1(_).compileToJS(Y)}var F3="1.4.3",G3="2025-11-07@10:29:23GMT",t2=(Y)=>{let _=Y.match(/^[ \t]*(?=\S)/gm),X=Math.min(...(_||[]).map((M)=>M.length));return Y.replace(RegExp(`^[ ]{${X}}`,"gm"),"").trim()};async function C2(){let Y=document.querySelectorAll('script[type="text/rip"]');for(let _ of Y){if(_.hasAttribute("data-rip-processed"))continue;try{let X=t2(_.textContent),M=q1(X);(0,eval)(M),_.setAttribute("data-rip-processed","true")}catch(X){console.error("Error compiling Rip script:",X),console.error("Script content:",_.textContent)}}}if(typeof document!=="undefined")if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",C2);else C2();function e2(Y){try{let X=q1(Y).replace(/^let\s+[^;]+;\s*\n\s*/m,"");X=X.replace(/^const\s+/gm,"var ");let M=(0,eval)(X);if(M!==void 0)globalThis._=M;return M}catch(_){console.error("Rip compilation error:",_.message);return}}if(typeof globalThis!=="undefined")globalThis.rip=e2;export{e2 as rip,C2 as processRipScripts,A1 as parser,_1 as formatSExpr,q1 as compileToJS,o2 as compile,F3 as VERSION,x1 as Lexer,K1 as Compiler,k as CodeGenerator,G3 as BUILD_DATE};
|
|
Binary file
|
package/package.json
CHANGED
package/src/codegen.js
CHANGED
|
@@ -2007,16 +2007,32 @@ export class CodeGenerator {
|
|
|
2007
2007
|
/**
|
|
2008
2008
|
* Generate logical NOT (!)
|
|
2009
2009
|
* Pattern: ["!", operand] → !operand
|
|
2010
|
+
* S-expression approach: Check operand TYPE (IR level, not generated string)
|
|
2010
2011
|
*/
|
|
2011
2012
|
generateNot(head, rest, context, sexpr) {
|
|
2012
2013
|
const [operand] = rest;
|
|
2013
|
-
const operandCode = this.generate(operand, 'value');
|
|
2014
2014
|
|
|
2015
|
-
//
|
|
2016
|
-
|
|
2017
|
-
|
|
2015
|
+
// Check operand TYPE at s-expression level (following Rip philosophy)
|
|
2016
|
+
|
|
2017
|
+
// Primitives (identifiers, numbers, keywords) - no parens needed
|
|
2018
|
+
if (typeof operand === 'string' || operand instanceof String) {
|
|
2019
|
+
return `!${this.generate(operand, 'value')}`; // !x, !1, !true, !null
|
|
2018
2020
|
}
|
|
2019
2021
|
|
|
2022
|
+
// High-precedence s-expressions (property/array access) - no parens
|
|
2023
|
+
if (Array.isArray(operand)) {
|
|
2024
|
+
const type = operand[0];
|
|
2025
|
+
const highPrecedence = ['.', '?.', '::', '?::', '[]', '?[]', 'optindex', 'optcall'];
|
|
2026
|
+
if (highPrecedence.includes(type)) {
|
|
2027
|
+
return `!${this.generate(operand, 'value')}`; // !obj.prop, !arr[0]
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// Everything else - conservative (add parens for safety)
|
|
2032
|
+
const operandCode = this.generate(operand, 'value');
|
|
2033
|
+
if (operandCode.startsWith('(')) {
|
|
2034
|
+
return `!${operandCode}`; // Already has parens: !(a + b)
|
|
2035
|
+
}
|
|
2020
2036
|
return `(!${operandCode})`;
|
|
2021
2037
|
}
|
|
2022
2038
|
|