codexivo 0.0.0-stage → 0.2.0
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 +15 -0
- package/README.md +158 -2
- package/bin/codexivo.exe +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +20 -0
- package/docs/README.md +25 -0
- package/docs/arquitectura.md +92 -0
- package/docs/especificacion-v1.md +595 -0
- package/docs/guia-aprender-desde-el-codigo.md +91 -0
- package/docs/lenguaje.md +162 -0
- package/docs/recetario.md +159 -0
- package/docs/repl.md +64 -0
- package/package.json +75 -4
- package/postinstall.mjs +38 -0
- package/src/engine.ts +97 -0
- package/src/errors.ts +56 -0
- package/src/index.ts +29 -0
- package/src/runtime/builtins.ts +133 -0
- package/src/runtime/compiler.ts +252 -0
- package/src/runtime/machine.ts +847 -0
- package/src/runtime/suggest.ts +33 -0
- package/src/runtime/values.ts +140 -0
- package/src/syntax/ast.ts +230 -0
- package/src/syntax/lexer.ts +232 -0
- package/src/syntax/parser.ts +666 -0
- package/src/syntax/token.ts +167 -0
|
@@ -0,0 +1,167 @@
|
|
|
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
|
+
FUNCTION = 'FUNCTION',
|
|
25
|
+
RETURN = 'RETURN',
|
|
26
|
+
IF = 'IF',
|
|
27
|
+
ELSEIF = 'ELSEIF',
|
|
28
|
+
ELSE = 'ELSE',
|
|
29
|
+
WHILE = 'WHILE',
|
|
30
|
+
DO = 'DO',
|
|
31
|
+
UNTIL = 'UNTIL',
|
|
32
|
+
FOR = 'FOR',
|
|
33
|
+
TRUE = 'TRUE',
|
|
34
|
+
FALSE = 'FALSE',
|
|
35
|
+
NULL = 'NULL',
|
|
36
|
+
AND = 'AND',
|
|
37
|
+
OR = 'OR',
|
|
38
|
+
NOT = 'NOT',
|
|
39
|
+
|
|
40
|
+
// operadores
|
|
41
|
+
ASSIGN = 'ASSIGN',
|
|
42
|
+
PLUS_ASSIGN = 'PLUS_ASSIGN',
|
|
43
|
+
MINUS_ASSIGN = 'MINUS_ASSIGN',
|
|
44
|
+
STAR_ASSIGN = 'STAR_ASSIGN',
|
|
45
|
+
SLASH_ASSIGN = 'SLASH_ASSIGN',
|
|
46
|
+
PLUS = 'PLUS',
|
|
47
|
+
MINUS = 'MINUS',
|
|
48
|
+
STAR = 'STAR',
|
|
49
|
+
SLASH = 'SLASH',
|
|
50
|
+
BANG = 'BANG',
|
|
51
|
+
EQ = 'EQ',
|
|
52
|
+
NEQ = 'NEQ',
|
|
53
|
+
LT = 'LT',
|
|
54
|
+
GT = 'GT',
|
|
55
|
+
LTE = 'LTE',
|
|
56
|
+
GTE = 'GTE',
|
|
57
|
+
|
|
58
|
+
// delimitadores
|
|
59
|
+
COMMA = 'COMMA',
|
|
60
|
+
SEMICOLON = 'SEMICOLON',
|
|
61
|
+
LPAREN = 'LPAREN',
|
|
62
|
+
RPAREN = 'RPAREN',
|
|
63
|
+
LBRACE = 'LBRACE',
|
|
64
|
+
RBRACE = 'RBRACE',
|
|
65
|
+
LBRACKET = 'LBRACKET',
|
|
66
|
+
RBRACKET = 'RBRACKET',
|
|
67
|
+
|
|
68
|
+
EOF = 'EOF',
|
|
69
|
+
ILLEGAL = 'ILLEGAL',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface Token {
|
|
73
|
+
type: TokenType;
|
|
74
|
+
/** Texto exacto del código. */
|
|
75
|
+
lexeme: string;
|
|
76
|
+
/** Valor ya interpretado: el número de un NUMBER o el contenido (con escapes resueltos) de un STRING. */
|
|
77
|
+
value?: number | string;
|
|
78
|
+
span: Span;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const KEYWORDS: Readonly<Record<string, TokenType>> = {
|
|
82
|
+
variable: TokenType.LET,
|
|
83
|
+
procedimiento: TokenType.FUNCTION,
|
|
84
|
+
regresa: TokenType.RETURN,
|
|
85
|
+
si: TokenType.IF,
|
|
86
|
+
pero_si: TokenType.ELSEIF,
|
|
87
|
+
si_no: TokenType.ELSE,
|
|
88
|
+
mientras: TokenType.WHILE,
|
|
89
|
+
hacer: TokenType.DO,
|
|
90
|
+
hasta_que: TokenType.UNTIL,
|
|
91
|
+
para: TokenType.FOR,
|
|
92
|
+
verdadero: TokenType.TRUE,
|
|
93
|
+
falso: TokenType.FALSE,
|
|
94
|
+
nulo: TokenType.NULL,
|
|
95
|
+
y: TokenType.AND,
|
|
96
|
+
o: TokenType.OR,
|
|
97
|
+
no: TokenType.NOT,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const isKeyword = (word: string): boolean => Object.hasOwn(KEYWORDS, word);
|
|
101
|
+
|
|
102
|
+
const SYMBOLS: Partial<Record<TokenType, string>> = {
|
|
103
|
+
[TokenType.ASSIGN]: '=',
|
|
104
|
+
[TokenType.PLUS_ASSIGN]: '+=',
|
|
105
|
+
[TokenType.MINUS_ASSIGN]: '-=',
|
|
106
|
+
[TokenType.STAR_ASSIGN]: '*=',
|
|
107
|
+
[TokenType.SLASH_ASSIGN]: '/=',
|
|
108
|
+
[TokenType.PLUS]: '+',
|
|
109
|
+
[TokenType.MINUS]: '-',
|
|
110
|
+
[TokenType.STAR]: '*',
|
|
111
|
+
[TokenType.SLASH]: '/',
|
|
112
|
+
[TokenType.BANG]: '!',
|
|
113
|
+
[TokenType.EQ]: '==',
|
|
114
|
+
[TokenType.NEQ]: '!=',
|
|
115
|
+
[TokenType.LT]: '<',
|
|
116
|
+
[TokenType.GT]: '>',
|
|
117
|
+
[TokenType.LTE]: '<=',
|
|
118
|
+
[TokenType.GTE]: '>=',
|
|
119
|
+
[TokenType.COMMA]: ',',
|
|
120
|
+
[TokenType.SEMICOLON]: ';',
|
|
121
|
+
[TokenType.LPAREN]: '(',
|
|
122
|
+
[TokenType.RPAREN]: ')',
|
|
123
|
+
[TokenType.LBRACE]: '{',
|
|
124
|
+
[TokenType.RBRACE]: '}',
|
|
125
|
+
[TokenType.LBRACKET]: '[',
|
|
126
|
+
[TokenType.RBRACKET]: ']',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const KEYWORD_BY_TYPE = Object.fromEntries(Object.entries(KEYWORDS).map(([word, type]) => [type, word])) as Partial<
|
|
130
|
+
Record<TokenType, string>
|
|
131
|
+
>;
|
|
132
|
+
|
|
133
|
+
/** Cómo nombrar un tipo de token en un mensaje: "`)`", "la palabra `si`", "un número"… */
|
|
134
|
+
export const describeTokenType = (type: TokenType): string => {
|
|
135
|
+
const symbol = SYMBOLS[type];
|
|
136
|
+
if (symbol) return `\`${symbol}\``;
|
|
137
|
+
const keyword = KEYWORD_BY_TYPE[type];
|
|
138
|
+
if (keyword) return `la palabra \`${keyword}\``;
|
|
139
|
+
switch (type) {
|
|
140
|
+
case TokenType.NUMBER:
|
|
141
|
+
return 'un número';
|
|
142
|
+
case TokenType.STRING:
|
|
143
|
+
return 'un texto';
|
|
144
|
+
case TokenType.IDENT:
|
|
145
|
+
return 'un nombre';
|
|
146
|
+
case TokenType.EOF:
|
|
147
|
+
return 'el final del programa';
|
|
148
|
+
default:
|
|
149
|
+
return 'un carácter no válido';
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** Cómo nombrar un token concreto en un mensaje. */
|
|
154
|
+
export const describeToken = (token: Token): string => {
|
|
155
|
+
switch (token.type) {
|
|
156
|
+
case TokenType.NUMBER:
|
|
157
|
+
return `el número \`${token.lexeme}\``;
|
|
158
|
+
case TokenType.STRING:
|
|
159
|
+
return `el texto ${token.lexeme}`;
|
|
160
|
+
case TokenType.IDENT:
|
|
161
|
+
return `el nombre \`${token.lexeme}\``;
|
|
162
|
+
case TokenType.ILLEGAL:
|
|
163
|
+
return `el carácter \`${token.lexeme}\``;
|
|
164
|
+
default:
|
|
165
|
+
return describeTokenType(token.type);
|
|
166
|
+
}
|
|
167
|
+
};
|