@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.
- package/CHANGELOG.md +103 -0
- package/README.md +1089 -0
- package/bin/flux.js +1397 -0
- package/dist/flux.cjs.js +6664 -0
- package/dist/flux.esm.js +6674 -0
- package/dist/flux.min.js +263 -0
- package/index.d.ts +202 -0
- package/index.js +26 -0
- package/package.json +77 -0
- package/scripts/build.js +76 -0
- package/src/bundler.js +216 -0
- package/src/checker.js +322 -0
- package/src/codegen.js +785 -0
- package/src/css-preprocessor.js +399 -0
- package/src/formatter.js +140 -0
- package/src/jsx.js +480 -0
- package/src/lexer.js +518 -0
- package/src/linter.js +758 -0
- package/src/mangler.js +280 -0
- package/src/parser.js +1671 -0
- package/src/self/bundler.flux +167 -0
- package/src/self/bundler.js +187 -0
- package/src/self/checker.flux +249 -0
- package/src/self/checker.js +338 -0
- package/src/self/codegen.flux +555 -0
- package/src/self/codegen.js +784 -0
- package/src/self/css-preprocessor.flux +373 -0
- package/src/self/css-preprocessor.js +387 -0
- package/src/self/formatter.flux +93 -0
- package/src/self/formatter.js +114 -0
- package/src/self/jsx.flux +430 -0
- package/src/self/jsx.js +396 -0
- package/src/self/lexer.flux +529 -0
- package/src/self/lexer.js +709 -0
- package/src/self/lexer.stage2.js +700 -0
- package/src/self/linter.flux +515 -0
- package/src/self/linter.js +804 -0
- package/src/self/mangler.flux +253 -0
- package/src/self/mangler.js +348 -0
- package/src/self/parser.flux +1146 -0
- package/src/self/parser.js +1571 -0
- package/src/self/sourcemap.flux +66 -0
- package/src/self/sourcemap.js +72 -0
- package/src/self/stdlib.flux +356 -0
- package/src/self/stdlib.js +396 -0
- package/src/self/test-runner.flux +201 -0
- package/src/self/test-runner.js +132 -0
- package/src/self/transpiler.flux +123 -0
- package/src/self/transpiler.js +83 -0
- package/src/self/type-checker.flux +821 -0
- package/src/self/type-checker.js +1106 -0
- package/src/sourcemap.js +82 -0
- package/src/stdlib.js +436 -0
- package/src/test-runner.js +239 -0
- package/src/transpiler.js +172 -0
- package/src/type-checker.js +1206 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Flux Self-Hosted CSS Preprocessor
|
|
3
|
+
// src/self/css-preprocessor.flux — written in Flux, compiled by stage-0
|
|
4
|
+
//
|
|
5
|
+
// Note: single-quoted strings used for any literal { or } characters
|
|
6
|
+
// because Flux double-quoted strings treat {expr} as interpolation.
|
|
7
|
+
// ============================================================
|
|
8
|
+
|
|
9
|
+
export val CSS_PROP_MAP = {
|
|
10
|
+
bg: 'background', fg: 'color', p: 'padding',
|
|
11
|
+
px: 'padding-inline', py: 'padding-block',
|
|
12
|
+
pt: 'padding-top', pb: 'padding-bottom', pl: 'padding-left', pr: 'padding-right',
|
|
13
|
+
m: 'margin', mx: 'margin-inline', my: 'margin-block',
|
|
14
|
+
mt: 'margin-top', mb: 'margin-bottom', ml: 'margin-left', mr: 'margin-right',
|
|
15
|
+
radius: 'border-radius',
|
|
16
|
+
w: 'width', h: 'height',
|
|
17
|
+
'min-w': 'min-width', 'max-w': 'max-width', 'min-h': 'min-height', 'max-h': 'max-height',
|
|
18
|
+
gap: 'gap', 'col-gap': 'column-gap', 'row-gap': 'row-gap',
|
|
19
|
+
text: 'font-size', font: 'font-family', weight: 'font-weight',
|
|
20
|
+
tracking: 'letter-spacing', leading: 'line-height',
|
|
21
|
+
shadow: 'box-shadow', opacity: 'opacity',
|
|
22
|
+
border: 'border', outline: 'outline',
|
|
23
|
+
transition: 'transition', cursor: 'cursor', overflow: 'overflow',
|
|
24
|
+
'overflow-x': 'overflow-x', 'overflow-y': 'overflow-y',
|
|
25
|
+
z: 'z-index', transform: 'transform', content: 'content',
|
|
26
|
+
resize: 'resize', appearance: 'appearance',
|
|
27
|
+
'object-fit': 'object-fit', 'accent-color': 'accent-color',
|
|
28
|
+
direction: 'flex-direction', wrap: 'flex-wrap',
|
|
29
|
+
align: 'align-items', justify: 'justify-content',
|
|
30
|
+
'align-self': 'align-self', 'justify-self': 'justify-self',
|
|
31
|
+
grow: 'flex-grow', shrink: 'flex-shrink', basis: 'flex-basis',
|
|
32
|
+
order: 'order', cols: 'grid-template-columns', rows: 'grid-template-rows',
|
|
33
|
+
'col-span': 'grid-column', 'row-span': 'grid-row',
|
|
34
|
+
'place-items': 'place-items', 'place-content': 'place-content',
|
|
35
|
+
'list-style': 'list-style', 'text-align': 'text-align',
|
|
36
|
+
decoration: 'text-decoration', 'text-transform': 'text-transform',
|
|
37
|
+
'white-space': 'white-space', 'word-break': 'word-break',
|
|
38
|
+
'user-select': 'user-select', 'pointer-events': 'pointer-events',
|
|
39
|
+
'vertical-align': 'vertical-align', backdrop: 'backdrop-filter',
|
|
40
|
+
filter: 'filter', clip: 'clip-path', animation: 'animation',
|
|
41
|
+
position: 'position', top: 'top', right: 'right', bottom: 'bottom',
|
|
42
|
+
left: 'left', inset: 'inset', color: 'color', background: 'background',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export val CSS_BOOL_MAP = {
|
|
46
|
+
flex: 'display: flex', grid: 'display: grid',
|
|
47
|
+
block: 'display: block', inline: 'display: inline',
|
|
48
|
+
'inline-flex': 'display: inline-flex', 'inline-block': 'display: inline-block',
|
|
49
|
+
'inline-grid': 'display: inline-grid',
|
|
50
|
+
bold: 'font-weight: 700', italic: 'font-style: italic',
|
|
51
|
+
relative: 'position: relative', absolute: 'position: absolute',
|
|
52
|
+
fixed: 'position: fixed', sticky: 'position: sticky',
|
|
53
|
+
hidden: 'display: none', pointer: 'cursor: pointer',
|
|
54
|
+
underline: 'text-decoration: underline',
|
|
55
|
+
'line-through': 'text-decoration: line-through',
|
|
56
|
+
capitalize: 'text-transform: capitalize',
|
|
57
|
+
uppercase: 'text-transform: uppercase',
|
|
58
|
+
lowercase: 'text-transform: lowercase',
|
|
59
|
+
truncate: 'overflow: hidden; text-overflow: ellipsis; white-space: nowrap',
|
|
60
|
+
'select-none': 'user-select: none',
|
|
61
|
+
'no-wrap': 'white-space: nowrap',
|
|
62
|
+
'no-list': 'list-style: none',
|
|
63
|
+
'no-outline': 'outline: none',
|
|
64
|
+
'no-border': 'border: none',
|
|
65
|
+
'no-bg': 'background: transparent',
|
|
66
|
+
'no-padding': 'padding: 0',
|
|
67
|
+
'no-margin': 'margin: 0',
|
|
68
|
+
'no-resize': 'resize: none',
|
|
69
|
+
center: 'text-align: center',
|
|
70
|
+
'items-center': 'align-items: center',
|
|
71
|
+
'justify-center': 'justify-content: center',
|
|
72
|
+
'place-center': 'place-items: center',
|
|
73
|
+
'flex-col': 'flex-direction: column',
|
|
74
|
+
'flex-row': 'flex-direction: row',
|
|
75
|
+
'flex-wrap': 'flex-wrap: wrap',
|
|
76
|
+
'flex-1': 'flex: 1',
|
|
77
|
+
'w-full': 'width: 100%',
|
|
78
|
+
'h-full': 'height: 100%',
|
|
79
|
+
'w-screen': 'width: 100vw',
|
|
80
|
+
'h-screen': 'height: 100vh',
|
|
81
|
+
'box-border': 'box-sizing: border-box',
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn expandProp(name):
|
|
85
|
+
val t = name.trim()
|
|
86
|
+
return CSS_PROP_MAP[t] ?? t
|
|
87
|
+
|
|
88
|
+
fn stripQuotes(v_):
|
|
89
|
+
val v = v_.trim()
|
|
90
|
+
if v.length >= 2:
|
|
91
|
+
val q0 = v[0]
|
|
92
|
+
val ql = v[v.length - 1]
|
|
93
|
+
if (q0 == '"' and ql == '"') or (q0 == "'" and ql == "'"):
|
|
94
|
+
return v.slice(1, -1)
|
|
95
|
+
return v
|
|
96
|
+
|
|
97
|
+
fn resolveSelector(sel, parent):
|
|
98
|
+
val s = sel.trim()
|
|
99
|
+
if not parent: return s
|
|
100
|
+
if s.startsWith('@'): return s
|
|
101
|
+
if parent.startsWith('@keyframes') or parent.startsWith('@font-face'): return s
|
|
102
|
+
if s.includes('&'): return s.replace(/&/g, parent)
|
|
103
|
+
return parent + ' ' + s
|
|
104
|
+
|
|
105
|
+
// ── CSS Block Parser ──────────────────────────────────────────────────────────
|
|
106
|
+
class CssBlockParser:
|
|
107
|
+
content: any
|
|
108
|
+
indentLevel: int
|
|
109
|
+
rules: any[]
|
|
110
|
+
atRuleSegs: any[]
|
|
111
|
+
selStack: any[]
|
|
112
|
+
|
|
113
|
+
fn curSel():
|
|
114
|
+
if self.selStack.length == 0: return null
|
|
115
|
+
return self.selStack[self.selStack.length - 1]
|
|
116
|
+
|
|
117
|
+
fn findOrCreateRule(sel):
|
|
118
|
+
for entry in self.rules:
|
|
119
|
+
if entry.sel == sel: return entry
|
|
120
|
+
val entry = { sel, decls: [] }
|
|
121
|
+
self.rules.push(entry)
|
|
122
|
+
return entry
|
|
123
|
+
|
|
124
|
+
fn addDecl(sel, d):
|
|
125
|
+
val entry = self.findOrCreateRule(sel)
|
|
126
|
+
entry.decls.push(d)
|
|
127
|
+
|
|
128
|
+
fn addDeclarationLine(line, sel):
|
|
129
|
+
if line.includes(';'):
|
|
130
|
+
val parts = line.split(';')
|
|
131
|
+
for part in parts:
|
|
132
|
+
val t = part.trim()
|
|
133
|
+
if t: self.addDeclarationLine(t, sel)
|
|
134
|
+
return
|
|
135
|
+
val boolDecl = CSS_BOOL_MAP[line]
|
|
136
|
+
if boolDecl:
|
|
137
|
+
val bdParts = boolDecl.split(';')
|
|
138
|
+
for bd in bdParts:
|
|
139
|
+
val t = bd.trim()
|
|
140
|
+
if t: self.addDecl(sel, t)
|
|
141
|
+
return
|
|
142
|
+
val ci = line.indexOf(':')
|
|
143
|
+
if ci != -1:
|
|
144
|
+
val prop = expandProp(line.slice(0, ci))
|
|
145
|
+
val v = stripQuotes(line.slice(ci + 1))
|
|
146
|
+
if v != '': self.addDecl(sel, prop + ': ' + v)
|
|
147
|
+
|
|
148
|
+
fn collectBlock(lines, startI):
|
|
149
|
+
var depth = 1
|
|
150
|
+
var inner = ''
|
|
151
|
+
var j = startI
|
|
152
|
+
while j < lines.length and depth > 0:
|
|
153
|
+
val ln = lines[j]
|
|
154
|
+
j = j + 1
|
|
155
|
+
for ch in ln:
|
|
156
|
+
if ch == '{': depth = depth + 1
|
|
157
|
+
else if ch == '}': depth = depth - 1
|
|
158
|
+
if depth > 0:
|
|
159
|
+
inner = inner + ln + "\n"
|
|
160
|
+
else:
|
|
161
|
+
val ci = ln.lastIndexOf('}')
|
|
162
|
+
if ci > 0: inner = inner + ln.slice(0, ci) + "\n"
|
|
163
|
+
return { inner, nextI: j }
|
|
164
|
+
|
|
165
|
+
fn parse():
|
|
166
|
+
val lines = self.content.split("\n")
|
|
167
|
+
var i = 0
|
|
168
|
+
while i < lines.length:
|
|
169
|
+
val raw = lines[i]
|
|
170
|
+
i = i + 1
|
|
171
|
+
val line = raw.trim()
|
|
172
|
+
if not line or line.startsWith('//'): continue
|
|
173
|
+
|
|
174
|
+
val ob = line.indexOf('{')
|
|
175
|
+
val cb = line.lastIndexOf('}')
|
|
176
|
+
|
|
177
|
+
// @keyframes / @font-face — collect raw block
|
|
178
|
+
if line.startsWith('@keyframes') or line.startsWith('@font-face'):
|
|
179
|
+
val atSel = ob != -1 ? line.slice(0, ob).trim() : line.trim()
|
|
180
|
+
var atBody = ''
|
|
181
|
+
if ob != -1 and cb != -1 and cb > ob:
|
|
182
|
+
atBody = line.slice(ob + 1, cb)
|
|
183
|
+
else if ob != -1:
|
|
184
|
+
val res = self.collectBlock(lines, i)
|
|
185
|
+
i = res.nextI
|
|
186
|
+
atBody = res.inner
|
|
187
|
+
val ind_ = ' '.repeat(self.indentLevel ?? 0)
|
|
188
|
+
self.atRuleSegs.push(ind_ + atSel + ' {\n' + atBody + ind_ + '}\n')
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
// @media / @supports / @layer — nested recursive
|
|
192
|
+
if line.startsWith('@media') or line.startsWith('@supports') or line.startsWith('@layer'):
|
|
193
|
+
val atSel = ob != -1 ? line.slice(0, ob).trim() : line.trim()
|
|
194
|
+
var atBody = ''
|
|
195
|
+
if ob != -1 and cb != -1 and cb > ob:
|
|
196
|
+
atBody = line.slice(ob + 1, cb)
|
|
197
|
+
else if ob != -1:
|
|
198
|
+
val res = self.collectBlock(lines, i)
|
|
199
|
+
i = res.nextI
|
|
200
|
+
atBody = res.inner
|
|
201
|
+
val innerCss = parseCssBlockContent(atBody, (self.indentLevel ?? 0) + 1)
|
|
202
|
+
val ind_ = ' '.repeat(self.indentLevel ?? 0)
|
|
203
|
+
self.atRuleSegs.push(ind_ + atSel + ' {\n' + innerCss + ind_ + '}\n')
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
// Inline rule: selector { decls } on one line
|
|
207
|
+
if ob != -1 and cb != -1 and cb > ob:
|
|
208
|
+
val sel = line.slice(0, ob).trim()
|
|
209
|
+
val inner = line.slice(ob + 1, cb).trim()
|
|
210
|
+
val full = resolveSelector(sel, self.curSel())
|
|
211
|
+
self.findOrCreateRule(full)
|
|
212
|
+
if inner: self.addDeclarationLine(inner, full)
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
// Block opener: selector {
|
|
216
|
+
if ob != -1:
|
|
217
|
+
val sel = line.slice(0, ob).trim()
|
|
218
|
+
val full = resolveSelector(sel, self.curSel())
|
|
219
|
+
self.selStack.push(full)
|
|
220
|
+
self.findOrCreateRule(full)
|
|
221
|
+
val tail = line.slice(ob + 1).trim()
|
|
222
|
+
if tail: self.addDeclarationLine(tail, full)
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
// Closing brace
|
|
226
|
+
if line == '}' or line == '};':
|
|
227
|
+
self.selStack.pop()
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
// Declaration inside a rule block
|
|
231
|
+
if self.selStack.length > 0:
|
|
232
|
+
self.addDeclarationLine(line, self.curSel())
|
|
233
|
+
|
|
234
|
+
fn emit():
|
|
235
|
+
var css = ''
|
|
236
|
+
val ind = ' '.repeat(self.indentLevel ?? 0)
|
|
237
|
+
val ind1 = ind + ' '
|
|
238
|
+
for entry in self.rules:
|
|
239
|
+
if entry.decls.length == 0: continue
|
|
240
|
+
css = css + ind + entry.sel + ' {\n'
|
|
241
|
+
for d in entry.decls:
|
|
242
|
+
css = css + ind1 + d + ';\n'
|
|
243
|
+
css = css + ind + '}\n'
|
|
244
|
+
for seg in self.atRuleSegs:
|
|
245
|
+
css = css + seg
|
|
246
|
+
return css
|
|
247
|
+
|
|
248
|
+
export fn parseCssBlockContent(content, indentLevel):
|
|
249
|
+
val parser = new CssBlockParser(content, indentLevel ?? 0, [], [], [])
|
|
250
|
+
parser.parse()
|
|
251
|
+
return parser.emit()
|
|
252
|
+
|
|
253
|
+
// ── Main source preprocessor: replace css { } with CSS string ──────────────
|
|
254
|
+
export class CssPreprocessor:
|
|
255
|
+
src: any
|
|
256
|
+
pos: int
|
|
257
|
+
out: any
|
|
258
|
+
|
|
259
|
+
fn transform():
|
|
260
|
+
while self.pos < self.src.length:
|
|
261
|
+
self.scan()
|
|
262
|
+
return self.out
|
|
263
|
+
|
|
264
|
+
fn scan():
|
|
265
|
+
val c = self.src[self.pos]
|
|
266
|
+
|
|
267
|
+
// Line comment — pass through
|
|
268
|
+
if c == '/' and self.src[self.pos + 1] == '/':
|
|
269
|
+
val end_ = self.src.indexOf("\n", self.pos)
|
|
270
|
+
if end_ == -1:
|
|
271
|
+
self.out = self.out + self.src.slice(self.pos)
|
|
272
|
+
self.pos = self.src.length
|
|
273
|
+
else:
|
|
274
|
+
self.out = self.out + self.src.slice(self.pos, end_ + 1)
|
|
275
|
+
self.pos = end_ + 1
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
// Block comment — pass through
|
|
279
|
+
if c == '/' and self.src[self.pos + 1] == '*':
|
|
280
|
+
val end_ = self.src.indexOf('*/', self.pos + 2)
|
|
281
|
+
if end_ == -1:
|
|
282
|
+
self.out = self.out + self.src.slice(self.pos)
|
|
283
|
+
self.pos = self.src.length
|
|
284
|
+
else:
|
|
285
|
+
self.out = self.out + self.src.slice(self.pos, end_ + 2)
|
|
286
|
+
self.pos = end_ + 2
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
// String literals — pass through unchanged
|
|
290
|
+
if c == '"':
|
|
291
|
+
self.passString('"')
|
|
292
|
+
return
|
|
293
|
+
if c == "'":
|
|
294
|
+
self.passString("'")
|
|
295
|
+
return
|
|
296
|
+
if c == '`':
|
|
297
|
+
self.passTemplate()
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
// Detect standalone css keyword followed by {
|
|
301
|
+
if c == 'c' and self.src.slice(self.pos, self.pos + 3) == 'css':
|
|
302
|
+
val charBefore = self.pos > 0 ? self.src[self.pos - 1] : "\n"
|
|
303
|
+
val charAfter = self.src[self.pos + 3] ?? ''
|
|
304
|
+
if not /[a-zA-Z0-9_]/.test(charBefore) and not /[a-zA-Z0-9_]/.test(charAfter):
|
|
305
|
+
var j = self.pos + 3
|
|
306
|
+
while j < self.src.length and (self.src[j] == ' ' or self.src[j] == "\t" or self.src[j] == "\n" or self.src[j] == "\r"):
|
|
307
|
+
j = j + 1
|
|
308
|
+
if self.src[j] == '{':
|
|
309
|
+
j = j + 1
|
|
310
|
+
var depth = 1
|
|
311
|
+
val blockStart = j
|
|
312
|
+
while j < self.src.length and depth > 0:
|
|
313
|
+
val ch = self.src[j]
|
|
314
|
+
if ch == '"' or ch == "'" or ch == '`':
|
|
315
|
+
val q = ch
|
|
316
|
+
j = j + 1
|
|
317
|
+
while j < self.src.length and self.src[j] != q:
|
|
318
|
+
if self.src[j] == '\\': j = j + 1
|
|
319
|
+
j = j + 1
|
|
320
|
+
j = j + 1
|
|
321
|
+
else if ch == '{':
|
|
322
|
+
depth = depth + 1
|
|
323
|
+
j = j + 1
|
|
324
|
+
else if ch == '}':
|
|
325
|
+
depth = depth - 1
|
|
326
|
+
j = j + 1
|
|
327
|
+
else:
|
|
328
|
+
j = j + 1
|
|
329
|
+
val blockContent = self.src.slice(blockStart, j - 1)
|
|
330
|
+
val css = parseCssBlockContent(blockContent, 0)
|
|
331
|
+
val BT = String.fromCharCode(96)
|
|
332
|
+
val escapedCss = css.replace(/\\/g, '\\\\').replace(new RegExp(BT, 'g'), '\\' + BT)
|
|
333
|
+
self.out = self.out + BT + escapedCss + BT
|
|
334
|
+
self.pos = j
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
self.out = self.out + c
|
|
338
|
+
self.pos = self.pos + 1
|
|
339
|
+
|
|
340
|
+
fn passString(quote):
|
|
341
|
+
self.out = self.out + quote
|
|
342
|
+
self.pos = self.pos + 1
|
|
343
|
+
while self.pos < self.src.length:
|
|
344
|
+
val c = self.src[self.pos]
|
|
345
|
+
if c == '\\':
|
|
346
|
+
self.out = self.out + c + (self.src[self.pos + 1] ?? '')
|
|
347
|
+
self.pos = self.pos + 2
|
|
348
|
+
continue
|
|
349
|
+
if c == quote:
|
|
350
|
+
self.out = self.out + c
|
|
351
|
+
self.pos = self.pos + 1
|
|
352
|
+
return
|
|
353
|
+
self.out = self.out + c
|
|
354
|
+
self.pos = self.pos + 1
|
|
355
|
+
|
|
356
|
+
fn passTemplate():
|
|
357
|
+
self.out = self.out + '`'
|
|
358
|
+
self.pos = self.pos + 1
|
|
359
|
+
while self.pos < self.src.length:
|
|
360
|
+
val c = self.src[self.pos]
|
|
361
|
+
if c == '\\':
|
|
362
|
+
self.out = self.out + c + (self.src[self.pos + 1] ?? '')
|
|
363
|
+
self.pos = self.pos + 2
|
|
364
|
+
continue
|
|
365
|
+
if c == '`':
|
|
366
|
+
self.out = self.out + c
|
|
367
|
+
self.pos = self.pos + 1
|
|
368
|
+
return
|
|
369
|
+
self.out = self.out + c
|
|
370
|
+
self.pos = self.pos + 1
|
|
371
|
+
|
|
372
|
+
export fn transformCss(src):
|
|
373
|
+
return new CssPreprocessor(src, 0, '').transform()
|