@xnoxs/flux-lang 3.1.1

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +1089 -0
  3. package/bin/flux.js +1397 -0
  4. package/dist/flux.cjs.js +6664 -0
  5. package/dist/flux.esm.js +6674 -0
  6. package/dist/flux.min.js +263 -0
  7. package/index.d.ts +202 -0
  8. package/index.js +26 -0
  9. package/package.json +77 -0
  10. package/scripts/build.js +76 -0
  11. package/src/bundler.js +216 -0
  12. package/src/checker.js +322 -0
  13. package/src/codegen.js +785 -0
  14. package/src/css-preprocessor.js +399 -0
  15. package/src/formatter.js +140 -0
  16. package/src/jsx.js +480 -0
  17. package/src/lexer.js +518 -0
  18. package/src/linter.js +758 -0
  19. package/src/mangler.js +280 -0
  20. package/src/parser.js +1671 -0
  21. package/src/self/bundler.flux +167 -0
  22. package/src/self/bundler.js +187 -0
  23. package/src/self/checker.flux +249 -0
  24. package/src/self/checker.js +338 -0
  25. package/src/self/codegen.flux +555 -0
  26. package/src/self/codegen.js +784 -0
  27. package/src/self/css-preprocessor.flux +373 -0
  28. package/src/self/css-preprocessor.js +387 -0
  29. package/src/self/formatter.flux +93 -0
  30. package/src/self/formatter.js +114 -0
  31. package/src/self/jsx.flux +430 -0
  32. package/src/self/jsx.js +396 -0
  33. package/src/self/lexer.flux +529 -0
  34. package/src/self/lexer.js +709 -0
  35. package/src/self/lexer.stage2.js +700 -0
  36. package/src/self/linter.flux +515 -0
  37. package/src/self/linter.js +804 -0
  38. package/src/self/mangler.flux +253 -0
  39. package/src/self/mangler.js +348 -0
  40. package/src/self/parser.flux +1146 -0
  41. package/src/self/parser.js +1571 -0
  42. package/src/self/sourcemap.flux +66 -0
  43. package/src/self/sourcemap.js +72 -0
  44. package/src/self/stdlib.flux +356 -0
  45. package/src/self/stdlib.js +396 -0
  46. package/src/self/test-runner.flux +201 -0
  47. package/src/self/test-runner.js +132 -0
  48. package/src/self/transpiler.flux +123 -0
  49. package/src/self/transpiler.js +83 -0
  50. package/src/self/type-checker.flux +821 -0
  51. package/src/self/type-checker.js +1106 -0
  52. package/src/sourcemap.js +82 -0
  53. package/src/stdlib.js +436 -0
  54. package/src/test-runner.js +239 -0
  55. package/src/transpiler.js +172 -0
  56. package/src/type-checker.js +1206 -0
@@ -0,0 +1,430 @@
1
+ // ============================================================
2
+ // Flux Self-Hosted JSX Preprocessor
3
+ // src/self/jsx.flux — written in Flux, compiled by stage-0
4
+ //
5
+ // Transforms <jsx> syntax into _fluxH() calls before lexing.
6
+ // <div class="x">text</div> → _fluxH("div",{"class":"x"},"text")
7
+ // <br /> → _fluxH("br",null)
8
+ // <>...</> → _fluxH("",null,...)
9
+ // ============================================================
10
+
11
+ val JSX_STYLE_VALUE_PROPS = {
12
+ bg: v -> "background:" + v,
13
+ fg: v -> "color:" + v,
14
+ color: v -> "color:" + v,
15
+ p: v -> "padding:" + v,
16
+ px: v -> "paddingLeft:" + v + ",paddingRight:" + v,
17
+ py: v -> "paddingTop:" + v + ",paddingBottom:" + v,
18
+ pt: v -> "paddingTop:" + v,
19
+ pb: v -> "paddingBottom:" + v,
20
+ pl: v -> "paddingLeft:" + v,
21
+ pr: v -> "paddingRight:" + v,
22
+ m: v -> "margin:" + v,
23
+ mx: v -> "marginLeft:" + v + ",marginRight:" + v,
24
+ my: v -> "marginTop:" + v + ",marginBottom:" + v,
25
+ mt: v -> "marginTop:" + v,
26
+ mb: v -> "marginBottom:" + v,
27
+ ml: v -> "marginLeft:" + v,
28
+ mr: v -> "marginRight:" + v,
29
+ radius: v -> "borderRadius:" + v,
30
+ w: v -> "width:" + v,
31
+ h: v -> "height:" + v,
32
+ "min-w": v -> "minWidth:" + v,
33
+ "max-w": v -> "maxWidth:" + v,
34
+ "min-h": v -> "minHeight:" + v,
35
+ "max-h": v -> "maxHeight:" + v,
36
+ gap: v -> "gap:" + v,
37
+ "col-gap": v -> "columnGap:" + v,
38
+ "row-gap": v -> "rowGap:" + v,
39
+ text: v -> "fontSize:" + v,
40
+ font: v -> "fontFamily:" + v,
41
+ weight: v -> "fontWeight:" + v,
42
+ tracking: v -> "letterSpacing:" + v,
43
+ leading: v -> "lineHeight:" + v,
44
+ shadow: v -> "boxShadow:" + v,
45
+ opacity: v -> "opacity:" + v,
46
+ border: v -> "border:" + v,
47
+ outline: v -> "outline:" + v,
48
+ transition: v -> "transition:" + v,
49
+ cursor: v -> "cursor:" + v,
50
+ overflow: v -> "overflow:" + v,
51
+ z: v -> "zIndex:" + v,
52
+ transform: v -> "transform:" + v,
53
+ direction: v -> "flexDirection:" + v,
54
+ align: v -> "alignItems:" + v,
55
+ justify: v -> "justifyContent:" + v,
56
+ "align-self": v -> "alignSelf:" + v,
57
+ "place-items":v -> "placeItems:" + v,
58
+ grow: v -> "flexGrow:" + v,
59
+ shrink: v -> "flexShrink:" + v,
60
+ basis: v -> "flexBasis:" + v,
61
+ cols: v -> "gridTemplateColumns:" + v,
62
+ rows: v -> "gridTemplateRows:" + v,
63
+ inset: v -> "inset:" + v,
64
+ top: v -> "top:" + v,
65
+ right: v -> "right:" + v,
66
+ bottom: v -> "bottom:" + v,
67
+ left: v -> "left:" + v,
68
+ "object-fit": v -> "objectFit:" + v,
69
+ "line-height":v -> "lineHeight:" + v,
70
+ "text-align": v -> "textAlign:" + v,
71
+ decoration: v -> "textDecoration:" + v,
72
+ clip: v -> "clipPath:" + v,
73
+ filter: v -> "filter:" + v,
74
+ backdrop: v -> "backdropFilter:" + v,
75
+ animation: v -> "animation:" + v,
76
+ }
77
+
78
+ val JSX_STYLE_BOOL_PROPS = {
79
+ flex: 'display:"flex"',
80
+ grid: 'display:"grid"',
81
+ block: 'display:"block"',
82
+ "inline-flex": 'display:"inline-flex"',
83
+ "inline-block": 'display:"inline-block"',
84
+ bold: "fontWeight:700",
85
+ italic: 'fontStyle:"italic"',
86
+ underline: 'textDecoration:"underline"',
87
+ pointer: 'cursor:"pointer"',
88
+ hidden: 'display:"none"',
89
+ relative: 'position:"relative"',
90
+ absolute: 'position:"absolute"',
91
+ fixed: 'position:"fixed"',
92
+ sticky: 'position:"sticky"',
93
+ "flex-col": 'flexDirection:"column"',
94
+ "flex-row": 'flexDirection:"row"',
95
+ "flex-wrap": 'flexWrap:"wrap"',
96
+ "flex-1": "flex:1",
97
+ "w-full": 'width:"100%"',
98
+ "h-full": 'height:"100%"',
99
+ center: 'textAlign:"center"',
100
+ truncate: 'overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"',
101
+ "select-none": 'userSelect:"none"',
102
+ "no-wrap": 'whiteSpace:"nowrap"',
103
+ "no-list": 'listStyle:"none"',
104
+ "no-outline": 'outline:"none"',
105
+ "no-border": 'border:"none"',
106
+ "box-border": 'boxSizing:"border-box"',
107
+ }
108
+
109
+ export val FLUX_H_BROWSER = `
110
+ function _fluxH(tag,props,...children){
111
+ if(tag===""){const f=document.createDocumentFragment();children.flat(Infinity).forEach(c=>{if(c==null)return;f.appendChild(c instanceof Node?c:document.createTextNode(String(c)));});return f;}
112
+ const el=document.createElement(tag);
113
+ if(props){for(const[k,v]of Object.entries(props)){if(k==="class"||k==="className"){el.className=v;}else if(k==="style"&&typeof v==="object"){Object.assign(el.style,v);}else if(k.startsWith("on")&&typeof v==="function"){el.addEventListener(k.slice(2).toLowerCase(),v);}else if(typeof v==="boolean"){if(v)el.setAttribute(k,"");}else{el.setAttribute(k,String(v));}}}
114
+ children.flat(Infinity).forEach(c=>{if(c==null)return;el.appendChild(c instanceof Node?c:document.createTextNode(String(c)));});
115
+ return el;
116
+ }`
117
+
118
+ export val FLUX_H_SERVER = `
119
+ function _fluxH(tag,props,...children){
120
+ const VOID=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);
121
+ if(tag==="")return children.flat(Infinity).map(c=>c==null?"":String(c)).join("");
122
+ let attrs="";
123
+ if(props){for(const[k,v]of Object.entries(props)){if(k==="class"||k==="className")attrs+=` + '` class="${v}"`' + `;else if(k==="style"&&typeof v==="object")attrs+=` + '` style="${Object.entries(v).map(([p,val])=>p.replace(/[A-Z]/g,m=>"-"+m.toLowerCase())+":"+val).join(";")}"`' + `;else if(typeof v!=="function"&&typeof v!=="boolean")attrs+=` + '` ${k}="${String(v).replace(/"/g,"&quot;")}"`' + `;else if(v===true)attrs+=` + '` ${k}`' + `;}}
124
+ const inner=children.flat(Infinity).map(c=>c==null?"":String(c)).join("");
125
+ if(VOID.has(tag))return` + '`<${tag}${attrs}>`' + `;
126
+ return` + '`<${tag}${attrs}>${inner}</${tag}>`' + `;
127
+ }`
128
+
129
+ export val FLUX_CSS_BROWSER = `
130
+ function _fluxCSS(css){const s=document.createElement("style");s.textContent=css;document.head.appendChild(s);return css;}`
131
+
132
+ export val FLUX_CSS_SERVER = `
133
+ function _fluxCSS(css){return css;}`
134
+
135
+ export class JsxPreprocessor:
136
+ src: any
137
+ pos: int
138
+ out: any
139
+ hasJsx: bool
140
+
141
+ fn transform():
142
+ while self.pos < self.src.length:
143
+ self.scanTop()
144
+ self.hasJsx = self.out != self.src
145
+ return self.out
146
+
147
+ fn scanTop():
148
+ val c = self.src[self.pos]
149
+
150
+ // Line comment
151
+ if c == "/" and self.src[self.pos + 1] == "/":
152
+ val end_ = self.src.indexOf("\n", self.pos)
153
+ if end_ == -1:
154
+ self.out = self.out + self.src.slice(self.pos)
155
+ self.pos = self.src.length
156
+ else:
157
+ self.out = self.out + self.src.slice(self.pos, end_ + 1)
158
+ self.pos = end_ + 1
159
+ return
160
+
161
+ // Block comment
162
+ if c == "/" and self.src[self.pos + 1] == "*":
163
+ val end_ = self.src.indexOf("*/", self.pos + 2)
164
+ if end_ == -1:
165
+ self.out = self.out + self.src.slice(self.pos)
166
+ self.pos = self.src.length
167
+ else:
168
+ self.out = self.out + self.src.slice(self.pos, end_ + 2)
169
+ self.pos = end_ + 2
170
+ return
171
+
172
+ if c == '"':
173
+ self.passString('"')
174
+ return
175
+ if c == "'":
176
+ self.passString("'")
177
+ return
178
+ if c == "`":
179
+ self.passTemplateLit()
180
+ return
181
+
182
+ // Potential JSX open
183
+ if c == "<" and self.isJsxStart():
184
+ val jsxCode = self.parseJsxElement()
185
+ self.out = self.out + jsxCode
186
+ return
187
+
188
+ self.out = self.out + c
189
+ self.pos = self.pos + 1
190
+
191
+ fn isJsxStart():
192
+ val next = self.src[self.pos + 1] ?? ""
193
+ if next != ">" and not (next >= "a" and next <= "z") and not (next >= "A" and next <= "Z"):
194
+ return false
195
+ var i = self.pos - 1
196
+ while i >= 0 and (self.src[i] == " " or self.src[i] == "\t"):
197
+ i = i - 1
198
+ if i < 0: return true
199
+ val prev = self.src[i]
200
+ if '=([{,:>\n?'.includes(prev): return true
201
+ if /[a-z]/.test(prev):
202
+ var j = i
203
+ while j >= 0 and /[a-z]/.test(self.src[j]):
204
+ j = j - 1
205
+ val word = self.src.slice(j + 1, i + 1)
206
+ val exprKw = new Set(["return", "not", "and", "or", "val", "var", "await", "yield", "else", "in", "throw"])
207
+ if exprKw.has(word): return true
208
+ return false
209
+
210
+ fn passString(quote):
211
+ self.out = self.out + quote
212
+ self.pos = self.pos + 1
213
+ while self.pos < self.src.length:
214
+ val c = self.src[self.pos]
215
+ if c == "\\":
216
+ self.out = self.out + c + (self.src[self.pos + 1] ?? "")
217
+ self.pos = self.pos + 2
218
+ continue
219
+ if c == quote:
220
+ self.out = self.out + c
221
+ self.pos = self.pos + 1
222
+ return
223
+ self.out = self.out + c
224
+ self.pos = self.pos + 1
225
+
226
+ fn passTemplateLit():
227
+ self.out = self.out + "`"
228
+ self.pos = self.pos + 1
229
+ while self.pos < self.src.length:
230
+ val c = self.src[self.pos]
231
+ if c == "\\":
232
+ self.out = self.out + c + (self.src[self.pos + 1] ?? "")
233
+ self.pos = self.pos + 2
234
+ continue
235
+ if c == "`":
236
+ self.out = self.out + c
237
+ self.pos = self.pos + 1
238
+ return
239
+ self.out = self.out + c
240
+ self.pos = self.pos + 1
241
+
242
+ fn parseJsxElement():
243
+ self.pos = self.pos + 1 // consume '<'
244
+
245
+ // Fragment: <>...</>
246
+ if self.src[self.pos] == ">":
247
+ self.pos = self.pos + 1
248
+ val children = self.parseJsxChildren("")
249
+ self.expectClose("")
250
+ val childStr = children.length > 0 ? "," + children.join(",") : ""
251
+ return '_fluxH("",null' + childStr + ")"
252
+
253
+ val tag = self.readTagName()
254
+ val attrs = self.parseJsxAttrs()
255
+
256
+ // Self-closing
257
+ if self.src[self.pos] == "/" and self.src[self.pos + 1] == ">":
258
+ self.pos = self.pos + 2
259
+ return '_fluxH("' + tag + '",' + attrs + ")"
260
+
261
+ // Opening >
262
+ if self.src[self.pos] == ">":
263
+ self.pos = self.pos + 1
264
+ val children = self.parseJsxChildren(tag)
265
+ self.expectClose(tag)
266
+ val childStr = children.length > 0 ? "," + children.join(",") : ""
267
+ return '_fluxH("' + tag + '",' + attrs + childStr + ")"
268
+
269
+ return "<" + tag
270
+
271
+ fn readTagName():
272
+ var name = ""
273
+ while self.pos < self.src.length:
274
+ val c = self.src[self.pos]
275
+ if /[a-zA-Z0-9\-_\.]/.test(c):
276
+ name = name + c
277
+ self.pos = self.pos + 1
278
+ else:
279
+ break
280
+ return name
281
+
282
+ fn parseJsxAttrs():
283
+ self.skipWs()
284
+ val pairs = []
285
+ val styleParts = []
286
+ var explicitStyle = null
287
+
288
+ while self.pos < self.src.length:
289
+ val c = self.src[self.pos]
290
+ if c == ">" or (c == "/" and self.src[self.pos + 1] == ">"): break
291
+ self.skipWs()
292
+ if not /[a-zA-Z_\-]/.test(self.src[self.pos] ?? ""): break
293
+
294
+ var attrName = ""
295
+ while self.pos < self.src.length and /[a-zA-Z0-9\-_:]/.test(self.src[self.pos]):
296
+ attrName = attrName + self.src[self.pos]
297
+ self.pos = self.pos + 1
298
+ self.skipWs()
299
+
300
+ // Boolean attribute
301
+ if self.src[self.pos] != "=":
302
+ val boolStyle = JSX_STYLE_BOOL_PROPS[attrName]
303
+ if boolStyle != undefined:
304
+ styleParts.push(boolStyle)
305
+ else:
306
+ pairs.push('"' + attrName + '":true')
307
+ continue
308
+
309
+ self.pos = self.pos + 1 // consume '='
310
+ self.skipWs()
311
+
312
+ val ch = self.src[self.pos]
313
+ var rawVal = ""
314
+ var exprVal_ = ""
315
+
316
+ if ch == '"' or ch == "'":
317
+ val q = ch
318
+ self.pos = self.pos + 1
319
+ var v = ""
320
+ while self.pos < self.src.length and self.src[self.pos] != q:
321
+ if self.src[self.pos] == "\\":
322
+ v = v + self.src[self.pos] + self.src[self.pos + 1]
323
+ self.pos = self.pos + 2
324
+ else:
325
+ v = v + self.src[self.pos]
326
+ self.pos = self.pos + 1
327
+ self.pos = self.pos + 1 // close quote
328
+ rawVal = v
329
+ else if ch == '{':
330
+ exprVal_ = self.readBraced()
331
+ else:
332
+ val boolStyle = JSX_STYLE_BOOL_PROPS[attrName]
333
+ if boolStyle != undefined:
334
+ styleParts.push(boolStyle)
335
+ else:
336
+ pairs.push('"' + attrName + '":true')
337
+ self.skipWs()
338
+ continue
339
+
340
+ if rawVal != null and JSX_STYLE_VALUE_PROPS[attrName]:
341
+ val expanded = JSX_STYLE_VALUE_PROPS[attrName](JSON.stringify(rawVal))
342
+ styleParts.push(expanded)
343
+ else if exprVal_ != null and attrName == "style":
344
+ explicitStyle = exprVal_
345
+ else:
346
+ if rawVal != null:
347
+ pairs.push('"' + attrName + '":' + JSON.stringify(rawVal))
348
+ else:
349
+ pairs.push('"' + attrName + '":' + exprVal_)
350
+
351
+ self.skipWs()
352
+
353
+ // Build style entry
354
+ if styleParts.length > 0 or explicitStyle != null:
355
+ val utilObj = styleParts.length > 0 ? '{' + styleParts.join(',') + '}' : null
356
+ if explicitStyle != null and utilObj != null:
357
+ pairs.push('"style":Object.assign(' + explicitStyle + ',' + utilObj + ')')
358
+ else if explicitStyle != null:
359
+ pairs.push('"style":' + explicitStyle)
360
+ else:
361
+ pairs.push('"style":{' + styleParts.join(',') + '}')
362
+
363
+ return pairs.length > 0 ? '{' + pairs.join(',') + '}' : 'null'
364
+
365
+ fn readBraced():
366
+ self.pos = self.pos + 1 // consume '{'
367
+ var depth = 1
368
+ var content = ""
369
+ while self.pos < self.src.length and depth > 0:
370
+ val c = self.src[self.pos]
371
+ if c == '{': depth = depth + 1
372
+ if c == '}':
373
+ depth = depth - 1
374
+ if depth == 0:
375
+ self.pos = self.pos + 1
376
+ break
377
+ content = content + c
378
+ self.pos = self.pos + 1
379
+ return content.trim()
380
+
381
+ fn parseJsxChildren(tag):
382
+ val children = []
383
+ while self.pos < self.src.length:
384
+ // End tag
385
+ if self.src[self.pos] == "<" and self.src[self.pos + 1] == "/": break
386
+
387
+ // Nested element
388
+ if self.src[self.pos] == "<":
389
+ val next = self.src[self.pos + 1] ?? ""
390
+ if (next >= "a" and next <= "z") or (next >= "A" and next <= "Z") or next == ">":
391
+ children.push(self.parseJsxElement())
392
+ continue
393
+
394
+ // Expression {expr}
395
+ if self.src[self.pos] == '{':
396
+ val expr = self.readBraced()
397
+ if expr.trim(): children.push(expr)
398
+ continue
399
+
400
+ // Text node
401
+ var text = ""
402
+ while self.pos < self.src.length:
403
+ val c = self.src[self.pos]
404
+ if c == "<" or c == '{': break
405
+ text = text + c
406
+ self.pos = self.pos + 1
407
+ val trimmed = text.replace(/\s+/g, " ").trim()
408
+ if trimmed: children.push(JSON.stringify(trimmed))
409
+
410
+ return children
411
+
412
+ fn expectClose(tag):
413
+ if self.src[self.pos] == "<" and self.src[self.pos + 1] == "/":
414
+ self.pos = self.pos + 2
415
+ while self.pos < self.src.length and self.src[self.pos] != ">":
416
+ self.pos = self.pos + 1
417
+ if self.src[self.pos] == ">": self.pos = self.pos + 1
418
+
419
+ fn skipWs():
420
+ while self.pos < self.src.length and (self.src[self.pos] == " " or self.src[self.pos] == "\t"):
421
+ self.pos = self.pos + 1
422
+
423
+ export fn transformJsx(src, opts):
424
+ val target = (opts ?? {}).target ?? "browser"
425
+ val proc = new JsxPreprocessor(src, 0, "", false)
426
+ val source = proc.transform()
427
+ val hasJsx = proc.hasJsx or src.includes("_fluxH") or src.includes("_fluxCSS")
428
+ val hHelper = target == "browser" ? FLUX_H_BROWSER : FLUX_H_SERVER
429
+ val cssHelper = target == "browser" ? FLUX_CSS_BROWSER : FLUX_CSS_SERVER
430
+ return { source, hasJsx, runtimeHelpers: hHelper + cssHelper }