@jondotsoy/don 0.0.2
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/README.md +416 -0
- package/lib/esm/chunk.d.ts +28 -0
- package/lib/esm/chunk.d.ts.map +1 -0
- package/lib/esm/chunk.js +84 -0
- package/lib/esm/chunk.js.map +1 -0
- package/lib/esm/document.d.ts +20 -0
- package/lib/esm/document.d.ts.map +1 -0
- package/lib/esm/document.js +138 -0
- package/lib/esm/document.js.map +1 -0
- package/lib/esm/index.d.ts +3 -0
- package/lib/esm/index.d.ts.map +1 -0
- package/lib/esm/index.js +3 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/lexer.d.ts +38 -0
- package/lib/esm/lexer.d.ts.map +1 -0
- package/lib/esm/lexer.js +600 -0
- package/lib/esm/lexer.js.map +1 -0
- package/lib/esm/utils/inspect.d.ts +7 -0
- package/lib/esm/utils/inspect.d.ts.map +1 -0
- package/lib/esm/utils/inspect.js +39 -0
- package/lib/esm/utils/inspect.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# DON: Directive Object Notation
|
|
2
|
+
|
|
3
|
+
DON es un formato de serialización de datos legible por humanos, diseñado alrededor del concepto de directivas y subdirectivas. Combina la simplicidad de una sintaxis declarativa con la flexibilidad de estructuras anidadas, permitiendo definir configuraciones jerárquicas usando bloques de directivas intuitivos. Cada directiva puede aceptar argumentos y contener subdirectivas anidadas, haciéndolo ideal para archivos de configuración, definiciones de infraestructura y representación de datos estructurados donde la legibilidad y expresividad son prioridades.
|
|
4
|
+
|
|
5
|
+
## Características Principales
|
|
6
|
+
|
|
7
|
+
- **Sintaxis Minimalista**: Menos caracteres especiales, más legibilidad
|
|
8
|
+
- **Procesamiento Secuencial**: Las directivas se procesan como un pipeline, permitiendo sobrescritura y composición incremental
|
|
9
|
+
- **Anidación Flexible**: Soporta jerarquías de cualquier profundidad
|
|
10
|
+
- **Múltiples Tipos de Datos**: Keywords, strings, numbers, booleans y null
|
|
11
|
+
- **Comentarios**: Soporta comentarios de línea (`//`, `#`) y multilínea (`/* */`)
|
|
12
|
+
- **Sin Ambigüedades**: Reglas claras de parsing y delimitación
|
|
13
|
+
|
|
14
|
+
## Instalación
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @jondotsoy/don
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bun add @jondotsoy/don
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Uso Básico
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { DocumentEncoder, LexerEncoder } from "@jondotsoy/don";
|
|
28
|
+
|
|
29
|
+
// Parsear un documento DON
|
|
30
|
+
const source = `
|
|
31
|
+
name "my-app"
|
|
32
|
+
version 1.0.0
|
|
33
|
+
container {
|
|
34
|
+
image "nginx"
|
|
35
|
+
port 8080
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
const lexer = new LexerEncoder();
|
|
40
|
+
const tokens = lexer.encode(source);
|
|
41
|
+
|
|
42
|
+
const document = new DocumentEncoder();
|
|
43
|
+
const directives = document.encode(tokens);
|
|
44
|
+
|
|
45
|
+
console.log(directives);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Sintaxis Básica
|
|
49
|
+
|
|
50
|
+
### Estructura de una Directiva
|
|
51
|
+
|
|
52
|
+
Una directiva tiene la siguiente estructura:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
<nombre> [argumentos...] [{ subdirectivas }]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Componentes:**
|
|
59
|
+
|
|
60
|
+
- **Nombre**: Identificador de la directiva (keyword, string, number o boolean)
|
|
61
|
+
- **Argumentos**: Cero o más valores separados por espacios
|
|
62
|
+
- **Bloque**: Opcional, contiene subdirectivas entre llaves `{ }`
|
|
63
|
+
|
|
64
|
+
### Ejemplos
|
|
65
|
+
|
|
66
|
+
**Directivas simples:**
|
|
67
|
+
|
|
68
|
+
```don
|
|
69
|
+
name "my-app"
|
|
70
|
+
version 1.0.0
|
|
71
|
+
enabled true
|
|
72
|
+
port 8080
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Directivas con bloques:**
|
|
76
|
+
|
|
77
|
+
```don
|
|
78
|
+
container {
|
|
79
|
+
image "nginx"
|
|
80
|
+
port 8080
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Directivas con argumentos y bloques:**
|
|
85
|
+
|
|
86
|
+
```don
|
|
87
|
+
service "web" replicas 3 {
|
|
88
|
+
container "nginx" {
|
|
89
|
+
port 80
|
|
90
|
+
image "nginx:latest"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Directivas repetidas:**
|
|
96
|
+
|
|
97
|
+
```don
|
|
98
|
+
container {
|
|
99
|
+
image "nginx"
|
|
100
|
+
port 80
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
container {
|
|
104
|
+
image "redis"
|
|
105
|
+
port 6379
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Tipos de Datos
|
|
110
|
+
|
|
111
|
+
DON reconoce los siguientes tipos de tokens:
|
|
112
|
+
|
|
113
|
+
- **Keywords**: Identificadores que comienzan con letra o guion bajo (`name`, `version`, `container`)
|
|
114
|
+
- **Strings**: Cadenas de texto entre comillas dobles o simples (`"texto"`, `'texto'`)
|
|
115
|
+
- Soportan caracteres escapados: `\"`, `\'`, `\\`
|
|
116
|
+
- **Numbers**: Enteros o decimales (`123`, `45.67`)
|
|
117
|
+
- Pueden incluir guiones bajos como separadores: `1_000_000`
|
|
118
|
+
- **Booleans**: Valores `true` o `false`
|
|
119
|
+
- **Null**: Valor nulo `null`
|
|
120
|
+
- **Punctuators**: Símbolos delimitadores: `{`, `}`, `[`, `]`, `,`, `+`, `-`, `/`, `*`, `<`, `>`
|
|
121
|
+
- **Comments**:
|
|
122
|
+
- Línea: `// comentario` o `# comentario`
|
|
123
|
+
- Multilínea: `/* comentario */`
|
|
124
|
+
|
|
125
|
+
## Reglas de Sintaxis
|
|
126
|
+
|
|
127
|
+
1. **Nombre de directiva**: Puede ser un keyword, string, number o boolean
|
|
128
|
+
2. **Argumentos**: Cero o más tokens separados por espacios
|
|
129
|
+
3. **Bloque de subdirectivas**: Opcional, delimitado por llaves `{ }`
|
|
130
|
+
4. **Delimitación**: Las directivas se separan por saltos de línea
|
|
131
|
+
5. **Anidación**: Las subdirectivas siguen las mismas reglas
|
|
132
|
+
6. **Restricción**: No puede haber tokens después de un bloque en la misma línea
|
|
133
|
+
|
|
134
|
+
**Sintaxis válida:**
|
|
135
|
+
|
|
136
|
+
```don
|
|
137
|
+
// Directiva simple
|
|
138
|
+
name "my-app"
|
|
139
|
+
version 1.0.0
|
|
140
|
+
|
|
141
|
+
// Directiva con bloque
|
|
142
|
+
container {
|
|
143
|
+
image "nginx"
|
|
144
|
+
port 8080
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Comentarios
|
|
148
|
+
# Comentario de línea
|
|
149
|
+
/* Comentario multilínea */
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Sintaxis inválida:**
|
|
153
|
+
|
|
154
|
+
```don
|
|
155
|
+
// ❌ Tokens después del bloque
|
|
156
|
+
container { image "nginx" } extra
|
|
157
|
+
|
|
158
|
+
// ❌ String sin cerrar
|
|
159
|
+
name "unclosed
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Casos de Uso
|
|
163
|
+
|
|
164
|
+
### Archivos de Configuración
|
|
165
|
+
|
|
166
|
+
```don
|
|
167
|
+
app "my-service" {
|
|
168
|
+
version 2.1.0
|
|
169
|
+
environment "production"
|
|
170
|
+
|
|
171
|
+
database {
|
|
172
|
+
host "localhost"
|
|
173
|
+
port 5432
|
|
174
|
+
name "mydb"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
cache {
|
|
178
|
+
enabled true
|
|
179
|
+
ttl 3600
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Definiciones de Infraestructura (Kubernetes)
|
|
185
|
+
|
|
186
|
+
```yaml
|
|
187
|
+
apiVersion: v1
|
|
188
|
+
kind: Pod
|
|
189
|
+
metadata:
|
|
190
|
+
name: command-demo
|
|
191
|
+
labels:
|
|
192
|
+
purpose: demonstrate-command
|
|
193
|
+
spec:
|
|
194
|
+
containers:
|
|
195
|
+
- name: command-demo-container
|
|
196
|
+
image: debian
|
|
197
|
+
command: ["printenv"]
|
|
198
|
+
args: ["HOSTNAME", "KUBERNETES_PORT"]
|
|
199
|
+
restartPolicy: OnFailure
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```don
|
|
203
|
+
apiVersion v1 {
|
|
204
|
+
pod {
|
|
205
|
+
metadata {
|
|
206
|
+
name "command-demo"
|
|
207
|
+
labels {
|
|
208
|
+
purpose "demonstrate-command"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
spec {
|
|
212
|
+
restartPolicy "OnFailure"
|
|
213
|
+
container debian "command-demo-container" {
|
|
214
|
+
command printenv HOSTNAME KUBERNETES_PORT
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Schemas de Datos
|
|
222
|
+
|
|
223
|
+
```don
|
|
224
|
+
schema "user" {
|
|
225
|
+
id integer primaryKey autoIncrement
|
|
226
|
+
email string unique required
|
|
227
|
+
name string required
|
|
228
|
+
createdAt timestamp default now
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Reglas de Seguridad (Firebase)
|
|
233
|
+
|
|
234
|
+
```don
|
|
235
|
+
service "firebase.storage" {
|
|
236
|
+
match "/b/{bucket}/o" {
|
|
237
|
+
match "/someFolder/{fileName}" {
|
|
238
|
+
allow read write {
|
|
239
|
+
if "request.auth" "!=" null
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Servicios con Múltiples Contenedores
|
|
247
|
+
|
|
248
|
+
```don
|
|
249
|
+
service "my-service" {
|
|
250
|
+
replicas 2
|
|
251
|
+
container {
|
|
252
|
+
image "my-image"
|
|
253
|
+
port 8080
|
|
254
|
+
}
|
|
255
|
+
container {
|
|
256
|
+
image "my-other-image"
|
|
257
|
+
port 8081
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Equivalencias con JSON
|
|
263
|
+
|
|
264
|
+
DON ofrece una sintaxis más limpia y legible para representar estructuras de datos comunes en JSON:
|
|
265
|
+
|
|
266
|
+
**Propiedades simples:**
|
|
267
|
+
|
|
268
|
+
- `{"name":"foo"}` → `name "foo"`
|
|
269
|
+
- `{"version":1}` → `version 1`
|
|
270
|
+
- `{"private": true}` → `private true`
|
|
271
|
+
|
|
272
|
+
**Objetos anidados:**
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{ "name": "foo", "meta": { "format": "json", "version": 1 } }
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
```don
|
|
279
|
+
name "foo"
|
|
280
|
+
meta {
|
|
281
|
+
format "json"
|
|
282
|
+
version 1
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Arrays o Listas:**
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{ "icons": ["foo", "taz"] }
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
```don
|
|
293
|
+
icons {
|
|
294
|
+
icon "foo"
|
|
295
|
+
icon "taz"
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Equivalencias con YAML
|
|
300
|
+
|
|
301
|
+
DON puede representar estructuras YAML de manera más concisa. Los tags de YAML (como `!!date`, `!!str`, `!!int`) se convierten en nombres de directivas en DON:
|
|
302
|
+
|
|
303
|
+
**Tags de tipo:**
|
|
304
|
+
|
|
305
|
+
```yaml
|
|
306
|
+
---
|
|
307
|
+
createdAt: !!date 2002-04-28
|
|
308
|
+
count: !!int 42
|
|
309
|
+
description: !!str "Hello World"
|
|
310
|
+
enabled: !!bool true
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
```don
|
|
314
|
+
createdAt date "2002-04-28"
|
|
315
|
+
count int 42
|
|
316
|
+
description str "Hello World"
|
|
317
|
+
enabled bool true
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Objetos anidados con tags:**
|
|
321
|
+
|
|
322
|
+
```yaml
|
|
323
|
+
---
|
|
324
|
+
metadata:
|
|
325
|
+
createdAt: !!date 2002-04-28
|
|
326
|
+
version: !!int 1
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
```don
|
|
330
|
+
metadata {
|
|
331
|
+
createdAt date "2002-04-28"
|
|
332
|
+
version int 1
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Inspiración
|
|
337
|
+
|
|
338
|
+
DON toma inspiración de la sintaxis de Python para el paso de argumentos, permitiendo tanto argumentos posicionales como argumentos con nombre:
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
# Python - Argumentos posicionales
|
|
342
|
+
print("foo")
|
|
343
|
+
|
|
344
|
+
# Python - Argumentos con nombre
|
|
345
|
+
dict(name="foo")
|
|
346
|
+
|
|
347
|
+
# Python - Combinación
|
|
348
|
+
Container("nginx", replicas=3, port=80)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
```don
|
|
352
|
+
# DON - Argumentos posicionales
|
|
353
|
+
print "foo"
|
|
354
|
+
|
|
355
|
+
# DON - Argumentos con nombre (mediante subdirectivas)
|
|
356
|
+
dict {
|
|
357
|
+
name "foo"
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
# DON - Combinación
|
|
361
|
+
Container "nginx" {
|
|
362
|
+
replicas 3
|
|
363
|
+
port 80
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Desarrollo
|
|
368
|
+
|
|
369
|
+
### Scripts Disponibles
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
# Compilar el proyecto
|
|
373
|
+
npm run build
|
|
374
|
+
|
|
375
|
+
# Limpiar archivos compilados
|
|
376
|
+
npm run clean
|
|
377
|
+
|
|
378
|
+
# Formatear código
|
|
379
|
+
npm run fmt
|
|
380
|
+
|
|
381
|
+
# Verificar formato
|
|
382
|
+
npm run lint
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Estructura del Proyecto
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
.
|
|
389
|
+
├── src/ # Código fuente TypeScript
|
|
390
|
+
│ ├── lexer.ts # Analizador léxico
|
|
391
|
+
│ ├── document.ts # Parser de documentos
|
|
392
|
+
│ ├── chunk.ts # Manejo de chunks
|
|
393
|
+
│ └── utils/ # Utilidades
|
|
394
|
+
├── lib/ # Código compilado
|
|
395
|
+
│ └── esm/ # Módulos ES
|
|
396
|
+
├── docs/ # Documentación
|
|
397
|
+
└── index.ts # Punto de entrada
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Documentación
|
|
401
|
+
|
|
402
|
+
Para más información sobre DON, consulta la [documentación completa](./docs/index.md):
|
|
403
|
+
|
|
404
|
+
- [Formato .DON](./docs/don-file.md): Especificación completa del formato
|
|
405
|
+
- [Directivas](./docs/concepts/directive.md): Anatomía y uso de directivas
|
|
406
|
+
- [Heredocs](./docs/concepts/heredoc.md): Contenido multilínea
|
|
407
|
+
- [Tipos de Datos](./docs/concepts/types/): Especificación de tipos soportados
|
|
408
|
+
- [Inspiración](./docs/inspiration.md): Objetivos y visión del proyecto
|
|
409
|
+
|
|
410
|
+
## Licencia
|
|
411
|
+
|
|
412
|
+
MIT
|
|
413
|
+
|
|
414
|
+
## Autor
|
|
415
|
+
|
|
416
|
+
[@jondotsoy](https://github.com/jondotsoy)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare class Chunk {
|
|
2
|
+
readonly pos: number;
|
|
3
|
+
readonly length: number;
|
|
4
|
+
readonly value: Iterable<number>;
|
|
5
|
+
readonly debug_str: string;
|
|
6
|
+
constructor(pos: number, length: number, value: Iterable<number>, debug_str?: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class ChunkList {
|
|
9
|
+
readonly length: number;
|
|
10
|
+
readonly chunks: Iterable<Chunk>;
|
|
11
|
+
constructor(length: number, chunks: Iterable<Chunk>);
|
|
12
|
+
}
|
|
13
|
+
export declare class Expression {
|
|
14
|
+
readonly m: number[];
|
|
15
|
+
constructor(m: number[]);
|
|
16
|
+
test(buff: number[], fromIndex: number): boolean;
|
|
17
|
+
exec(buff: number[], fromIndex: number): number[];
|
|
18
|
+
static keywordExpression: Expression;
|
|
19
|
+
static spaceExpression: Expression;
|
|
20
|
+
}
|
|
21
|
+
type LexerEncoderOptions = {};
|
|
22
|
+
export declare class ChunkEncoder {
|
|
23
|
+
private options?;
|
|
24
|
+
constructor(options?: LexerEncoderOptions | undefined);
|
|
25
|
+
encode(buff: string | Iterable<number>): ChunkList;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=chunk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk.d.ts","sourceRoot":"","sources":["../../src/chunk.ts"],"names":[],"mappings":"AAAA,qBAAa,KAAK;IAEd,QAAQ,CAAC,GAAG,EAAE,MAAM;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS;gBAHT,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,SAAS,SAEjB;CAEJ;AAED,qBAAa,SAAS;IAElB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC;gBADvB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC;CAEnC;AAKD,qBAAa,UAAU;IACT,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE;gBAAX,CAAC,EAAE,MAAM,EAAE;IAEhC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAMhD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE;IAUjD,MAAM,CAAC,iBAAiB,aASrB;IAEH,MAAM,CAAC,eAAe,aAAwB;CAC/C;AAED,KAAK,mBAAmB,GAAG,EAAE,CAAC;AAE9B,qBAAa,YAAY;IACX,OAAO,CAAC,OAAO,CAAC;gBAAR,OAAO,CAAC,EAAE,mBAAmB,YAAA;IAEjD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS;CAiCnD"}
|
package/lib/esm/chunk.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export class Chunk {
|
|
2
|
+
pos;
|
|
3
|
+
length;
|
|
4
|
+
value;
|
|
5
|
+
debug_str;
|
|
6
|
+
constructor(pos, length, value, debug_str = JSON.stringify(new TextDecoder().decode(new Uint8Array(value)))) {
|
|
7
|
+
this.pos = pos;
|
|
8
|
+
this.length = length;
|
|
9
|
+
this.value = value;
|
|
10
|
+
this.debug_str = debug_str;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class ChunkList {
|
|
14
|
+
length;
|
|
15
|
+
chunks;
|
|
16
|
+
constructor(length, chunks) {
|
|
17
|
+
this.length = length;
|
|
18
|
+
this.chunks = chunks;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const range = (start, end) => Array.from({ length: -start + end + 1 }, (_, i) => i + start);
|
|
22
|
+
export class Expression {
|
|
23
|
+
m;
|
|
24
|
+
constructor(m) {
|
|
25
|
+
this.m = m;
|
|
26
|
+
}
|
|
27
|
+
test(buff, fromIndex) {
|
|
28
|
+
const charCode = buff[fromIndex];
|
|
29
|
+
if (charCode === undefined)
|
|
30
|
+
return false;
|
|
31
|
+
return this.m.includes(charCode);
|
|
32
|
+
}
|
|
33
|
+
exec(buff, fromIndex) {
|
|
34
|
+
let end = fromIndex;
|
|
35
|
+
while (end < buff.length && this.test(buff, end)) {
|
|
36
|
+
end++;
|
|
37
|
+
}
|
|
38
|
+
return buff.slice(fromIndex, end);
|
|
39
|
+
}
|
|
40
|
+
static keywordExpression = new Expression([
|
|
41
|
+
// a-z (97 ... 122)
|
|
42
|
+
...range(97, 122),
|
|
43
|
+
// A-Z (65 ... 90)
|
|
44
|
+
...range(65, 90),
|
|
45
|
+
// _ (95)
|
|
46
|
+
95,
|
|
47
|
+
// 0-9 (48 ... 57)
|
|
48
|
+
...range(48, 57),
|
|
49
|
+
]);
|
|
50
|
+
static spaceExpression = new Expression([32]);
|
|
51
|
+
}
|
|
52
|
+
export class ChunkEncoder {
|
|
53
|
+
options;
|
|
54
|
+
constructor(options) {
|
|
55
|
+
this.options = options;
|
|
56
|
+
}
|
|
57
|
+
encode(buff) {
|
|
58
|
+
const numberIterable = typeof buff === "string" ? new TextEncoder().encode(buff) : buff;
|
|
59
|
+
const bytes = Array.from(numberIterable);
|
|
60
|
+
const tokens = [];
|
|
61
|
+
let pos = 0;
|
|
62
|
+
const expressions = [
|
|
63
|
+
Expression.keywordExpression,
|
|
64
|
+
Expression.spaceExpression,
|
|
65
|
+
];
|
|
66
|
+
while (pos < bytes.length) {
|
|
67
|
+
const charCode = bytes[pos];
|
|
68
|
+
if (charCode === undefined)
|
|
69
|
+
break;
|
|
70
|
+
const expression = expressions.find((expression) => expression.test(bytes, pos));
|
|
71
|
+
if (expression) {
|
|
72
|
+
const value = expression.exec(bytes, pos);
|
|
73
|
+
tokens.push(new Chunk(pos, value.length, value));
|
|
74
|
+
pos += value.length;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Symbols
|
|
78
|
+
tokens.push(new Chunk(pos, 1, [charCode]));
|
|
79
|
+
pos++;
|
|
80
|
+
}
|
|
81
|
+
return new ChunkList(tokens.length, tokens);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=chunk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk.js","sourceRoot":"","sources":["../../src/chunk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ChunkList } from "./chunk";
|
|
2
|
+
import { Lexema, Token } from "./lexer";
|
|
3
|
+
export declare class Directive {
|
|
4
|
+
readonly name: Token;
|
|
5
|
+
readonly args: Token[];
|
|
6
|
+
readonly children: Directive[];
|
|
7
|
+
constructor(name: Token, args: Token[], children: Directive[]);
|
|
8
|
+
static isDirective(doc: unknown): doc is Directive;
|
|
9
|
+
}
|
|
10
|
+
export declare class Document {
|
|
11
|
+
readonly children: Directive[];
|
|
12
|
+
constructor(children: Directive[]);
|
|
13
|
+
static isDocument(doc: unknown): doc is Document;
|
|
14
|
+
}
|
|
15
|
+
export declare class DocumentEncoder {
|
|
16
|
+
encode(input: string | Iterable<number> | ChunkList | Lexema): Document;
|
|
17
|
+
private segmentByBrackets;
|
|
18
|
+
private parseDirectives;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAgB,MAAM,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEtD,qBAAa,SAAS;IAElB,QAAQ,CAAC,IAAI,EAAE,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE;gBAFrB,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,KAAK,EAAE,EACb,QAAQ,EAAE,SAAS,EAAE;IAGhC,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,SAAS;CAGnD;AAED,qBAAa,QAAQ;IACP,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE;gBAArB,QAAQ,EAAE,SAAS,EAAE;IAE1C,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,QAAQ;CAGjD;AAUD,qBAAa,eAAe;IAC1B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ;IAavE,OAAO,CAAC,iBAAiB;IAsCzB,OAAO,CAAC,eAAe;CAiGxB"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { LexerEncoder, Lexema } from "./lexer";
|
|
2
|
+
export class Directive {
|
|
3
|
+
name;
|
|
4
|
+
args;
|
|
5
|
+
children;
|
|
6
|
+
constructor(name, args, children) {
|
|
7
|
+
this.name = name;
|
|
8
|
+
this.args = args;
|
|
9
|
+
this.children = children;
|
|
10
|
+
}
|
|
11
|
+
static isDirective(doc) {
|
|
12
|
+
return doc instanceof Directive;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class Document {
|
|
16
|
+
children;
|
|
17
|
+
constructor(children) {
|
|
18
|
+
this.children = children;
|
|
19
|
+
}
|
|
20
|
+
static isDocument(doc) {
|
|
21
|
+
return doc instanceof Document;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class DocumentEncoder {
|
|
25
|
+
encode(input) {
|
|
26
|
+
const lexema = input instanceof Lexema ? input : new LexerEncoder().encode(input);
|
|
27
|
+
const tokens = Array.from(lexema.tokens);
|
|
28
|
+
// Segmentar por grupos de brackets
|
|
29
|
+
const groups = this.segmentByBrackets(tokens);
|
|
30
|
+
// Parsear las directivas
|
|
31
|
+
const directives = this.parseDirectives(tokens, 0, tokens.length, groups);
|
|
32
|
+
return new Document(directives);
|
|
33
|
+
}
|
|
34
|
+
segmentByBrackets(tokens) {
|
|
35
|
+
const groups = [];
|
|
36
|
+
const stack = [];
|
|
37
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
38
|
+
const token = tokens[i];
|
|
39
|
+
if (!token || token.type !== "punctuator")
|
|
40
|
+
continue;
|
|
41
|
+
const value = token.raw;
|
|
42
|
+
if (value === "{") {
|
|
43
|
+
stack.push({ type: "braces", openIndex: i });
|
|
44
|
+
}
|
|
45
|
+
else if (value === "}") {
|
|
46
|
+
const open = stack.pop();
|
|
47
|
+
if (open && open.type === "braces") {
|
|
48
|
+
groups.push({
|
|
49
|
+
openIndex: open.openIndex,
|
|
50
|
+
closeIndex: i,
|
|
51
|
+
type: "braces",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (value === "[") {
|
|
56
|
+
stack.push({ type: "brackets", openIndex: i });
|
|
57
|
+
}
|
|
58
|
+
else if (value === "]") {
|
|
59
|
+
const open = stack.pop();
|
|
60
|
+
if (open && open.type === "brackets") {
|
|
61
|
+
groups.push({
|
|
62
|
+
openIndex: open.openIndex,
|
|
63
|
+
closeIndex: i,
|
|
64
|
+
type: "brackets",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return groups;
|
|
70
|
+
}
|
|
71
|
+
parseDirectives(tokens, startIndex, endIndex, groups) {
|
|
72
|
+
const directives = [];
|
|
73
|
+
let i = startIndex;
|
|
74
|
+
while (i < endIndex) {
|
|
75
|
+
const token = tokens[i];
|
|
76
|
+
if (!token)
|
|
77
|
+
break;
|
|
78
|
+
// Skip newlines
|
|
79
|
+
if (token.type === "newline") {
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Skip closing brackets (ya fueron procesados)
|
|
84
|
+
if (token.type === "punctuator" &&
|
|
85
|
+
(token.raw === "}" || token.raw === "]")) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
// Parse directive: only keyword, string, number, or boolean can be directive names
|
|
89
|
+
if (token.type === "keyword" ||
|
|
90
|
+
token.type === "string" ||
|
|
91
|
+
token.type === "number" ||
|
|
92
|
+
token.type === "boolean") {
|
|
93
|
+
const name = token;
|
|
94
|
+
const args = [];
|
|
95
|
+
i++;
|
|
96
|
+
// Collect arguments until newline, block start, or end
|
|
97
|
+
while (i < endIndex) {
|
|
98
|
+
const argToken = tokens[i];
|
|
99
|
+
if (!argToken ||
|
|
100
|
+
argToken.type === "newline" ||
|
|
101
|
+
(argToken.type === "punctuator" && argToken.raw === "{") ||
|
|
102
|
+
(argToken.type === "punctuator" && argToken.raw === "}")) {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
args.push(argToken);
|
|
106
|
+
i++;
|
|
107
|
+
// If we just added a heredoc, stop collecting arguments
|
|
108
|
+
// because heredoc already includes its own newline
|
|
109
|
+
if (argToken.type === "heredoc") {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Check for block - buscar el grupo que empieza en la posición actual
|
|
114
|
+
let children = [];
|
|
115
|
+
const group = groups.find((g) => g.openIndex === i && g.type === "braces");
|
|
116
|
+
if (group) {
|
|
117
|
+
// Parsear recursivamente el contenido del grupo
|
|
118
|
+
children = this.parseDirectives(tokens, group.openIndex + 1, group.closeIndex, groups);
|
|
119
|
+
i = group.closeIndex + 1; // Saltar al final del grupo
|
|
120
|
+
// After a block, there should be no more tokens before newline
|
|
121
|
+
if (i < endIndex) {
|
|
122
|
+
const nextToken = tokens[i];
|
|
123
|
+
if (nextToken && nextToken.type !== "newline") {
|
|
124
|
+
throw new Error(`Syntax error: Unexpected token after block. Expected newline but got ${nextToken.type} "${nextToken.raw}"`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
directives.push(new Directive(name, args, children));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Invalid directive name - throw syntax error
|
|
132
|
+
throw new Error(`Syntax error: Invalid directive name. Expected keyword, string, number, or boolean, but got ${token.type} "${token.raw}"`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return directives;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=document.js.map
|