codexivo 0.0.0-stage → 0.2.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.
@@ -0,0 +1,215 @@
1
+ export interface Position {
2
+ /** Línea, desde 1. */
3
+ line: number;
4
+ /** Columna, desde 1. */
5
+ column: number;
6
+ /** Desplazamiento en caracteres desde el inicio del código, desde 0. */
7
+ offset: number;
8
+ }
9
+
10
+ /** Rango del código fuente. `end` apunta justo después del último carácter. */
11
+ export interface Span {
12
+ start: Position;
13
+ end: Position;
14
+ }
15
+
16
+ export enum TokenType {
17
+ // literales y nombres
18
+ NUMBER = 'NUMBER',
19
+ STRING = 'STRING',
20
+ IDENT = 'IDENT',
21
+
22
+ // palabras reservadas
23
+ LET = 'LET',
24
+ CONST = 'CONST',
25
+ DOMAIN = 'DOMAIN',
26
+ FUNCTION = 'FUNCTION',
27
+ RETURN = 'RETURN',
28
+ IF = 'IF',
29
+ ELSEIF = 'ELSEIF',
30
+ ELSE = 'ELSE',
31
+ WHILE = 'WHILE',
32
+ DO = 'DO',
33
+ UNTIL = 'UNTIL',
34
+ FOR = 'FOR',
35
+ FOR_EACH = 'FOR_EACH',
36
+ FROM = 'FROM',
37
+ TO = 'TO',
38
+ STEP = 'STEP',
39
+ IN = 'IN',
40
+ REPEAT = 'REPEAT',
41
+ TIMES = 'TIMES',
42
+ BREAK = 'BREAK',
43
+ CONTINUE = 'CONTINUE',
44
+ TRUE = 'TRUE',
45
+ FALSE = 'FALSE',
46
+ NULL = 'NULL',
47
+ AND = 'AND',
48
+ OR = 'OR',
49
+ NOT = 'NOT',
50
+ MOD = 'MOD',
51
+ DIV = 'DIV',
52
+
53
+ // operadores
54
+ ASSIGN = 'ASSIGN',
55
+ PLUS_ASSIGN = 'PLUS_ASSIGN',
56
+ MINUS_ASSIGN = 'MINUS_ASSIGN',
57
+ STAR_ASSIGN = 'STAR_ASSIGN',
58
+ SLASH_ASSIGN = 'SLASH_ASSIGN',
59
+ PERCENT_ASSIGN = 'PERCENT_ASSIGN',
60
+ INCREMENT = 'INCREMENT',
61
+ DECREMENT = 'DECREMENT',
62
+ PLUS = 'PLUS',
63
+ MINUS = 'MINUS',
64
+ STAR = 'STAR',
65
+ SLASH = 'SLASH',
66
+ PERCENT = 'PERCENT',
67
+ CARET = 'CARET',
68
+ BANG = 'BANG',
69
+ EQ = 'EQ',
70
+ NEQ = 'NEQ',
71
+ LT = 'LT',
72
+ GT = 'GT',
73
+ LTE = 'LTE',
74
+ GTE = 'GTE',
75
+
76
+ // delimitadores
77
+ COMMA = 'COMMA',
78
+ COLON = 'COLON',
79
+ DOT = 'DOT',
80
+ SEMICOLON = 'SEMICOLON',
81
+ LPAREN = 'LPAREN',
82
+ RPAREN = 'RPAREN',
83
+ LBRACE = 'LBRACE',
84
+ RBRACE = 'RBRACE',
85
+ LBRACKET = 'LBRACKET',
86
+ RBRACKET = 'RBRACKET',
87
+
88
+ EOF = 'EOF',
89
+ ILLEGAL = 'ILLEGAL',
90
+ }
91
+
92
+ export interface Token {
93
+ type: TokenType;
94
+ /** Texto exacto del código. */
95
+ lexeme: string;
96
+ /** Valor ya interpretado: el número de un NUMBER o el contenido (con escapes resueltos) de un STRING. */
97
+ value?: number | string;
98
+ span: Span;
99
+ }
100
+
101
+ export const KEYWORDS: Readonly<Record<string, TokenType>> = {
102
+ variable: TokenType.LET,
103
+ constante: TokenType.CONST,
104
+ dominio: TokenType.DOMAIN,
105
+ procedimiento: TokenType.FUNCTION,
106
+ regresa: TokenType.RETURN,
107
+ si: TokenType.IF,
108
+ pero_si: TokenType.ELSEIF,
109
+ si_no: TokenType.ELSE,
110
+ mientras: TokenType.WHILE,
111
+ hacer: TokenType.DO,
112
+ hasta_que: TokenType.UNTIL,
113
+ para: TokenType.FOR,
114
+ para_cada: TokenType.FOR_EACH,
115
+ desde: TokenType.FROM,
116
+ hasta: TokenType.TO,
117
+ con_paso: TokenType.STEP,
118
+ en: TokenType.IN,
119
+ repetir: TokenType.REPEAT,
120
+ veces: TokenType.TIMES,
121
+ romper: TokenType.BREAK,
122
+ continuar: TokenType.CONTINUE,
123
+ verdadero: TokenType.TRUE,
124
+ falso: TokenType.FALSE,
125
+ nulo: TokenType.NULL,
126
+ y: TokenType.AND,
127
+ o: TokenType.OR,
128
+ no: TokenType.NOT,
129
+ mod: TokenType.MOD,
130
+ div: TokenType.DIV,
131
+ // Las comparaciones en palabras producen el mismo token que su símbolo: el AST no las distingue.
132
+ es_igual_a: TokenType.EQ,
133
+ es_distinto_de: TokenType.NEQ,
134
+ es_menor_que: TokenType.LT,
135
+ es_mayor_que: TokenType.GT,
136
+ es_menor_o_igual_a: TokenType.LTE,
137
+ es_mayor_o_igual_a: TokenType.GTE,
138
+ };
139
+
140
+ export const isKeyword = (word: string): boolean => Object.hasOwn(KEYWORDS, word);
141
+
142
+ const SYMBOLS: Partial<Record<TokenType, string>> = {
143
+ [TokenType.ASSIGN]: '=',
144
+ [TokenType.PLUS_ASSIGN]: '+=',
145
+ [TokenType.MINUS_ASSIGN]: '-=',
146
+ [TokenType.STAR_ASSIGN]: '*=',
147
+ [TokenType.SLASH_ASSIGN]: '/=',
148
+ [TokenType.PERCENT_ASSIGN]: '%=',
149
+ [TokenType.INCREMENT]: '++',
150
+ [TokenType.DECREMENT]: '--',
151
+ [TokenType.PLUS]: '+',
152
+ [TokenType.MINUS]: '-',
153
+ [TokenType.STAR]: '*',
154
+ [TokenType.SLASH]: '/',
155
+ [TokenType.PERCENT]: '%',
156
+ [TokenType.CARET]: '^',
157
+ [TokenType.BANG]: '!',
158
+ [TokenType.EQ]: '==',
159
+ [TokenType.NEQ]: '!=',
160
+ [TokenType.LT]: '<',
161
+ [TokenType.GT]: '>',
162
+ [TokenType.LTE]: '<=',
163
+ [TokenType.GTE]: '>=',
164
+ [TokenType.COMMA]: ',',
165
+ [TokenType.COLON]: ':',
166
+ [TokenType.DOT]: '.',
167
+ [TokenType.SEMICOLON]: ';',
168
+ [TokenType.LPAREN]: '(',
169
+ [TokenType.RPAREN]: ')',
170
+ [TokenType.LBRACE]: '{',
171
+ [TokenType.RBRACE]: '}',
172
+ [TokenType.LBRACKET]: '[',
173
+ [TokenType.RBRACKET]: ']',
174
+ };
175
+
176
+ const KEYWORD_BY_TYPE = Object.fromEntries(Object.entries(KEYWORDS).map(([word, type]) => [type, word])) as Partial<
177
+ Record<TokenType, string>
178
+ >;
179
+
180
+ /** Cómo nombrar un tipo de token en un mensaje: "`)`", "la palabra `si`", "un número"… */
181
+ export const describeTokenType = (type: TokenType): string => {
182
+ const symbol = SYMBOLS[type];
183
+ if (symbol) return `\`${symbol}\``;
184
+ const keyword = KEYWORD_BY_TYPE[type];
185
+ if (keyword) return `la palabra \`${keyword}\``;
186
+ switch (type) {
187
+ case TokenType.NUMBER:
188
+ return 'un número';
189
+ case TokenType.STRING:
190
+ return 'un texto';
191
+ case TokenType.IDENT:
192
+ return 'un nombre';
193
+ case TokenType.EOF:
194
+ return 'el final del programa';
195
+ default:
196
+ return 'un carácter no válido';
197
+ }
198
+ };
199
+
200
+ /** Cómo nombrar un token concreto en un mensaje. */
201
+ export const describeToken = (token: Token): string => {
202
+ switch (token.type) {
203
+ case TokenType.NUMBER:
204
+ return `el número \`${token.lexeme}\``;
205
+ case TokenType.STRING:
206
+ return `el texto ${token.lexeme}`;
207
+ case TokenType.IDENT:
208
+ return `el nombre \`${token.lexeme}\``;
209
+ case TokenType.ILLEGAL:
210
+ return `el carácter \`${token.lexeme}\``;
211
+ default:
212
+ // `es_igual_a` y `==` son el mismo token; se nombra como se escribió.
213
+ return isKeyword(token.lexeme) ? `la palabra \`${token.lexeme}\`` : describeTokenType(token.type);
214
+ }
215
+ };
package/src/worker.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Worker del sandbox: `new Worker(new URL('codexivo/worker', import.meta.url), { type: 'module' })`. Recibe los
3
+ * mensajes de `SandboxRequest` y responde con `SandboxResponse`; `createSandbox()` lo envuelve con promesas.
4
+ */
5
+ import { SandboxHost } from './sandbox-host';
6
+ import type { SandboxRequest, SandboxResponse } from './sandbox-host';
7
+
8
+ declare const self: {
9
+ postMessage(message: SandboxResponse): void;
10
+ addEventListener(type: 'message', listener: (event: { data: SandboxRequest }) => void): void;
11
+ };
12
+
13
+ const host = new SandboxHost(message => self.postMessage(message));
14
+
15
+ // Los mensajes se atienden en orden, pero `pausar` tiene que poder llegar mientras una ejecución cede el control.
16
+ self.addEventListener('message', event => {
17
+ void host.handle(event.data);
18
+ });