tangerine-lang 0.0.0-stage → 3.1.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +295 -2
  4. package/bin/tangerine.js +296 -0
  5. package/dist/tangerine.cjs +2142 -0
  6. package/dist/tangerine.global.js +2318 -0
  7. package/dist/tangerine.global.min.js +10 -0
  8. package/dist/tangerine.mjs +2120 -0
  9. package/editores/vscode/CHANGELOG.md +5 -0
  10. package/editores/vscode/LICENSE +21 -0
  11. package/editores/vscode/README.md +13 -0
  12. package/editores/vscode/tangerine-vscode.vsix +0 -0
  13. package/package.json +98 -4
  14. package/plantillas/consola/README.md +8 -0
  15. package/plantillas/consola/_gitignore +1 -0
  16. package/plantillas/consola/package.json +13 -0
  17. package/plantillas/consola/src/main.tng +19 -0
  18. package/plantillas/consola/src/texto.tng +13 -0
  19. package/plantillas/paquete/README.md +23 -0
  20. package/plantillas/paquete/_gitignore +1 -0
  21. package/plantillas/paquete/index.tng +10 -0
  22. package/plantillas/paquete/package.json +15 -0
  23. package/plantillas/paquete/pruebas.tng +6 -0
  24. package/plantillas/react/README.md +13 -0
  25. package/plantillas/react/_gitignore +2 -0
  26. package/plantillas/react/index.html +12 -0
  27. package/plantillas/react/package.json +21 -0
  28. package/plantillas/react/src/App.tng +19 -0
  29. package/plantillas/react/src/Contador.tng +14 -0
  30. package/plantillas/react/src/estilos.css +21 -0
  31. package/plantillas/react/src/main.jsx +10 -0
  32. package/plantillas/react/vite.config.js +8 -0
  33. package/plantillas/web/README.md +11 -0
  34. package/plantillas/web/_gitignore +2 -0
  35. package/plantillas/web/package.json +14 -0
  36. package/plantillas/web/src/contador.tng +10 -0
  37. package/plantillas/web/src/estilos.css +18 -0
  38. package/plantillas/web/src/main.tng +22 -0
  39. package/plantillas/web/tangerine.json +8 -0
  40. package/src/construir.js +142 -0
  41. package/src/errores.js +47 -0
  42. package/src/herramientas.js +335 -0
  43. package/src/index.js +242 -0
  44. package/src/interprete.js +684 -0
  45. package/src/lexer.js +148 -0
  46. package/src/navegador.js +80 -0
  47. package/src/node.js +101 -0
  48. package/src/parser.js +469 -0
  49. package/src/plugins/dom.js +106 -0
  50. package/src/plugins/react.js +143 -0
  51. package/src/sugerencias.js +59 -0
  52. package/src/valores.js +170 -0
  53. package/src/vite.js +102 -0
  54. package/types/dom.d.ts +5 -0
  55. package/types/index.d.ts +132 -0
  56. package/types/node.d.ts +10 -0
  57. package/types/react.d.ts +7 -0
  58. package/types/vite.d.ts +3 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,38 @@
1
+ # Cambios
2
+
3
+ ## 3.1.0
4
+
5
+ ### Nuevo
6
+ - **Errores que enseñan**: sugerencias "¿Quisiste decir…?" para variables, campos y métodos (también para nombres de otros lenguajes como `length`, `null` o `toUpperCase`), y rastro de llamadas con archivo y línea.
7
+ - **Parámetros por defecto**: `fn saludar(nombre, saludo = "Hola")`, también en closures.
8
+ - **Paquetes**: `tangerine add` (npm o GitHub `usuario/repo`), `remove` y `buscar`. Los imports sin `./` buscan en `node_modules`; `build` genera un mapa de paquetes para el navegador.
9
+ - **Plantillas**: `tangerine new --con` react, vue, svelte, vite, angular, next, astro, expo, swift, flutter, consola y paquete.
10
+ - **Instalador de herramientas**: `tangerine install` (node, git, swift, python, angular, typescript, vscode, deno, bun, pnpm, yarn) con winget, Homebrew o npm, y `tangerine doctor`.
11
+ - **React en Tangerine**: plugin `tangerine-lang/react` (`h`, `fragment`, `use_state`, `use_effect`, `use_memo`, `use_ref`) y plugin de Vite `tangerine-lang/vite` para importar `.tng` desde JavaScript.
12
+ - **Extensión de VS Code** con colores de sintaxis y fragmentos; se instala con `tangerine vscode`.
13
+ - **Módulos**: `export` para `let`, `var`, `fn`, `class` y `struct`; `import { a, b as c } from "./x.tng"` e `import x from "./x"`. Cada módulo se ejecuta una vez y las importaciones circulares se detectan.
14
+ - **Paquete de npm** `tangerine-lang` con builds ESM, CommonJS y un bundle global para el navegador, más tipos de TypeScript.
15
+ - **CLI** `tangerine`: REPL multilínea, `run`, `check`, `new`, `build`, `dev` con recarga automática y `serve`.
16
+ - **Instancias independientes**: `new Tangerine({ output, stepLimit, natives, plugins, modules })`. Ya no hay estado global, así que varios programas pueden convivir.
17
+ - **Plugins**: `tng.use(plugin)` y `Tangerine.registerPlugin(plugin)`. Las funciones del navegador ahora son el plugin `dom`.
18
+ - **Arranque automático en el navegador**: ejecuta `<script type="text/tangerine">`, registra módulos con `data-module` y descarga archivos con `src`.
19
+ - Plugin `sistema` para Node.js: `read_file`, `write_file`, `file_exists`, `list_dir`, `args`, `env`, `exit`.
20
+ - Funciones `to_json` y `from_json`.
21
+ - Nuevas funciones del DOM: `query_all`, `remove`, `get_attr`, `set_style`, `after`. Los callbacks de `on` con un parámetro reciben `{ type, key, value, target }`.
22
+
23
+ ### Cambios
24
+ - Todos los mensajes de error están en español e indican archivo, línea y, en errores de sintaxis, columna.
25
+ - `==` compara mapas por su contenido, igual que las listas.
26
+ - El límite de pasos es configurable; por defecto no hay límite (el playground usa 400 000).
27
+ - Los identificadores aceptan más letras latinas (`ñ`, `ő`, etc.).
28
+ - `sorted_by_key` calcula cada clave una sola vez y `unique` es lineal para valores simples.
29
+
30
+ ### Internos
31
+ - El intérprete se dividió en `lexer`, `parser`, `valores` e `interprete`.
32
+ - La web oficial se dividió en seis módulos de Tangerine y se construye con `tangerine build`.
33
+ - Pruebas con Vitest, ESLint y CI en Linux y Windows con Node 18, 20 y 22.
34
+ - La web oficial se publica en GitHub Pages en cada push a `main`.
35
+
36
+ ## 3.0.0
37
+
38
+ - `match` con rangos, alternativas y guardas; closures al estilo de Rust; clases con `init`, `self` y `super`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Los autores de Tangerine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,296 @@
1
- # Temporary Holding Version
1
+ # 🍊 Tangerine
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Tangerine es un lenguaje de programación pequeño con la sintaxis de Rust y Swift: llaves sin paréntesis, `match`, rangos, closures, clases y retorno implícito. Funciona en el navegador y en Node.js, y su intérprete no tiene dependencias.
4
+
5
+ **Lo que lo hace distinto**
6
+
7
+ - **Errores que enseñan, en español.** Dicen archivo, línea y columna, muestran el rastro de llamadas y sugieren el nombre correcto: `List no tiene un método llamado 'length'. ¿Quisiste decir 'len'?`
8
+ - **Componentes de React en Tangerine.** `tangerine new app --con react` y escribe componentes con hooks en archivos `.tng`.
9
+ - **Una sola herramienta.** `tangerine` crea proyectos (propios, React, Vue, Angular, Swift…), instala herramientas, maneja paquetes y empaqueta tu web en un solo HTML.
10
+
11
+ ```rust
12
+ import { formato_precio } from "./tienda.tng"
13
+
14
+ struct Canasta {
15
+ var items = []
16
+ fn add(fruta, kilos, precio) { self.items.push({ fruta, kilos, precio }); self }
17
+ fn total() { self.items.fold(0, |s, x| s + x.kilos * x.precio) }
18
+ }
19
+
20
+ let canasta = Canasta().add("mandarina", 2, 3200).add("limón", 1, 2800)
21
+
22
+ for item in canasta.items {
23
+ let etiqueta = match item.kilos {
24
+ 0 => "agotado",
25
+ 1..=2 => "poco",
26
+ _ => "mucho",
27
+ }
28
+ println("{item.fruta}: {etiqueta}")
29
+ }
30
+ println("Total: {formato_precio(canasta.total())}")
31
+ ```
32
+
33
+ ## Instalar
34
+
35
+ ```bash
36
+ npm install -g tangerine-lang # el comando `tangerine` en tu terminal
37
+ npm install tangerine-lang # como biblioteca en tu proyecto
38
+ ```
39
+
40
+ Requiere Node.js 18 o más reciente.
41
+
42
+ ## La terminal
43
+
44
+ | Comando | Qué hace |
45
+ | --- | --- |
46
+ | `tangerine` | Abre el REPL. Las variables se conservan y los bloques sin cerrar siguen en la línea siguiente. |
47
+ | `tangerine archivo.tng [args]` | Ejecuta un archivo. Los argumentos llegan en `args()`. |
48
+ | `tangerine check *.tng` | Revisa la sintaxis sin ejecutar. |
49
+ | `tangerine new mi-app [--con X]` | Crea un proyecto. Sin `--con` es una web en Tangerine; con `--con` usa otra plantilla (abajo). |
50
+ | `tangerine dev` | Construye, sirve en `localhost:3000` y recarga al guardar. |
51
+ | `tangerine build` | Empaqueta el proyecto en un solo `index.html`. |
52
+ | `tangerine serve [carpeta]` | Sirve archivos estáticos en `localhost:8000`. |
53
+ | `tangerine add <paquete>` | Instala un paquete de Tangerine desde npm, o desde GitHub con `usuario/repo`. |
54
+ | `tangerine remove <paquete>`, `tangerine buscar [texto]` | Desinstala o busca paquetes. |
55
+ | `tangerine install <herramienta>` | Instala `node`, `git`, `swift`, `python`, `angular`, `typescript`, `vscode`, `deno`, `bun`, `pnpm`, `yarn`… con winget, Homebrew o npm. Pregunta antes de ejecutar. |
56
+ | `tangerine doctor` | Muestra qué herramientas tienes instaladas y qué falta. |
57
+ | `tangerine vscode` | Instala la extensión de Tangerine en VS Code (colores y fragmentos). |
58
+
59
+ Opciones: `--limite <n>` (máximo de pasos), `-p <puerto>`, `-o <salida>`, `--entrada <archivo>`, `--cdn`, `--con <plantilla>`, `--sin-tangerine`, `--si` (no preguntar), `--forzar`, `--dev`.
60
+
61
+ ### Plantillas de `tangerine new --con`
62
+
63
+ | Plantilla | Qué crea |
64
+ | --- | --- |
65
+ | `web` | Página 100% en Tangerine (por defecto). |
66
+ | `react` | React + Vite con componentes escritos en Tangerine. |
67
+ | `consola` | Programa de terminal. |
68
+ | `paquete` | Paquete de Tangerine listo para publicar en npm. |
69
+ | `vue`, `svelte`, `vite` | Proyecto oficial de Vite, con el plugin de Tangerine ya agregado (`--sin-tangerine` para omitirlo). |
70
+ | `angular`, `next`, `astro`, `expo` | Proyecto creado con el CLI oficial de cada framework. |
71
+ | `swift`, `flutter` | Proyecto con `swift package init` o `flutter create` (necesitan Swift o Flutter instalados). |
72
+
73
+ Los errores dicen dónde está el problema:
74
+
75
+ ```
76
+ error: app.tng, línea 2, columna 1: Se esperaba ']' pero se encontró 'println'
77
+ 2 │ println(x)
78
+ │ ^
79
+ ```
80
+
81
+ Fuera del navegador hay funciones para trabajar con el sistema: `read_file`, `write_file`, `file_exists`, `list_dir`, `args()`, `env(nombre)` y `exit(código)`.
82
+
83
+ ## Proyectos web
84
+
85
+ ```bash
86
+ tangerine new mi-app
87
+ cd mi-app
88
+ npm install
89
+ npm run dev
90
+ ```
91
+
92
+ La configuración vive en `tangerine.json`:
93
+
94
+ ```json
95
+ {
96
+ "entry": "src/main.tng",
97
+ "out": "dist/index.html",
98
+ "title": "Mi app",
99
+ "styles": ["src/estilos.css"],
100
+ "scripts": []
101
+ }
102
+ ```
103
+
104
+ | Clave | Qué hace |
105
+ | --- | --- |
106
+ | `entry` | Archivo de entrada. Sus `import` se siguen solos. |
107
+ | `out` | Dónde escribir el HTML. |
108
+ | `title`, `lang`, `head`, `body` | Título, idioma, HTML extra en `<head>` y el contenido inicial de `<body>` (por defecto `<div id="app"></div>`). |
109
+ | `styles` | Hojas de estilo que se incluyen en el HTML. |
110
+ | `scripts` | Archivos JavaScript que se incluyen, por ejemplo plugins propios. |
111
+ | `runtime` | `"inline"` (por defecto) incluye el intérprete; `"cdn"` lo carga de jsDelivr. |
112
+
113
+ ## Paquetes
114
+
115
+ ```bash
116
+ tangerine add colores # desde npm
117
+ tangerine add manu/colores # desde GitHub
118
+ ```
119
+
120
+ ```rust
121
+ import { NARANJA, mezclar } from "colores"
122
+ import paleta from "colores/paleta"
123
+ ```
124
+
125
+ Un paquete es una carpeta con `package.json` y un archivo de entrada `.tng`, indicado en el campo `"tangerine"` (o `index.tng`). Crea uno con `tangerine new mi-paquete --con paquete` y publícalo con `npm publish`. `tangerine build` incluye los paquetes en el HTML final.
126
+
127
+ ## React y Vite
128
+
129
+ ```bash
130
+ tangerine new mi-app --con react
131
+ ```
132
+
133
+ ```rust
134
+ // src/Contador.tng
135
+ export fn Contador(props) {
136
+ let (n, set_n) = use_state(props.inicio ?? 0)
137
+ h("button", { class: "boton", on_click: || set_n(|x| x + 1) }, "Clics: {n}")
138
+ }
139
+ ```
140
+
141
+ ```jsx
142
+ // src/main.jsx
143
+ import { Contador } from "./Contador.tng";
144
+ <Contador inicio={5} />
145
+ ```
146
+
147
+ Funciones disponibles: `h(etiqueta, props, ...hijos)`, `fragment(...)`, `use_state`, `use_effect`, `use_memo`, `use_ref`. Los eventos van en `on_click`, `on_change`, `on_submit`… y reciben un mapa con `value`, `key`, `checked`, `prevent()` y `stop()`. `class` se convierte en `className` y `style` acepta un mapa.
148
+
149
+ En cualquier proyecto de Vite basta con el plugin:
150
+
151
+ ```js
152
+ // vite.config.js
153
+ import tangerine from "tangerine-lang/vite";
154
+ export default { plugins: [tangerine()] };
155
+ ```
156
+
157
+ Sin Vite, el plugin de React se usa a mano: `new Tangerine({ plugins: [react(React)] })` con `import { react, wrapExport } from "tangerine-lang/react"`.
158
+
159
+ ## En una página, sin instalar nada
160
+
161
+ ```html
162
+ <div id="app"></div>
163
+ <script src="https://cdn.jsdelivr.net/npm/tangerine-lang@3.1.0/dist/tangerine.global.min.js"></script>
164
+ <script type="text/tangerine">
165
+ let titulo = append(query("#app"), el("h1", nil, "Hola"))
166
+ let campo = append(query("#app"), el("input"))
167
+ on(campo, "input", |e| set_text(titulo, "Hola, {e.value}"))
168
+ </script>
169
+ ```
170
+
171
+ Al cargar la página se ejecutan los `<script type="text/tangerine">` en orden:
172
+
173
+ - con `data-module="util.tng"` se registran como módulo para importarlos;
174
+ - con `src="main.tng"` se descargan, junto con sus imports (necesita un servidor, por ejemplo `tangerine serve`);
175
+ - sin atributos se ejecutan tal cual.
176
+
177
+ Para arrancar a mano agrega `data-manual` al script del intérprete y llama a `Tangerine.start()`.
178
+
179
+ ## Como biblioteca
180
+
181
+ ```js
182
+ import { Tangerine } from "tangerine-lang";
183
+
184
+ const tng = new Tangerine({
185
+ output: (linea) => console.log(linea),
186
+ stepLimit: 100_000, // corta ciclos infinitos
187
+ natives: { doble: (x) => x * 2 }, // funciones de JavaScript visibles en Tangerine
188
+ modules: { "mate.tng": "export let IVA = 0.19" },
189
+ });
190
+
191
+ tng.run('import { IVA } from "./mate"\nprintln(doble(21), IVA)'); // 42 0.19
192
+
193
+ const { output, error } = tng.runCode("println(1 / 0)");
194
+ // output: [], error: "Línea 1: División entre cero"
195
+ ```
196
+
197
+ Cada instancia es independiente: tiene su propia salida, sus funciones y su caché de módulos.
198
+
199
+ | Método | Qué hace |
200
+ | --- | --- |
201
+ | `run(código, { file })` | Ejecuta y devuelve el valor de la última instrucción. Lanza `TngError` si falla. |
202
+ | `runCode(código)` | Ejecuta sin lanzar y devuelve `{ output, error }`. |
203
+ | `runModule(ruta)` | Ejecuta un módulo registrado o que `read` pueda leer. |
204
+ | `define(nombre, valor)`, `defineAll(objeto)` | Agrega funciones o valores globales. |
205
+ | `use(plugin)` | Instala un plugin. |
206
+ | `addModule(ruta, código)` | Registra un módulo en memoria. |
207
+ | `session()` | Crea una sesión cuyas variables persisten (para un REPL). |
208
+ | `call(fn, args)`, `invoke(fn, args)` | Llama a una función de Tangerine. `invoke` envía los errores a `onError` en vez de lanzarlos. |
209
+ | `message(error)` | Texto en español del error, con archivo, línea y columna. |
210
+ | `trace(error)` | Rastro de llamadas del error, de la más interna a la más externa. |
211
+ | `import(ruta)`, `forget(ruta)` | Carga un módulo y devuelve sus exports, u olvida uno ya cargado. |
212
+
213
+ Opciones del constructor: `output`, `stepLimit`, `natives`, `plugins`, `modules`, `imports` (mapa de paquetes), `resolve`, `read`, `onError`, `globalPlugins`.
214
+
215
+ En Node.js, `tangerine-lang/node` lee los imports del disco:
216
+
217
+ ```js
218
+ import { runFile, createNodeTangerine } from "tangerine-lang/node";
219
+
220
+ runFile("src/main.tng", { args: ["Ana"] });
221
+ ```
222
+
223
+ ### Plugins
224
+
225
+ Un plugin agrega funciones nativas. Puede ser una función o un objeto con `install`:
226
+
227
+ ```js
228
+ const reloj = {
229
+ name: "reloj",
230
+ install(tng) {
231
+ tng.defineAll({
232
+ hora: () => new Date().toLocaleTimeString("es"),
233
+ cada: (ms, f) => { setInterval(() => tng.invoke(f), ms); return null; },
234
+ });
235
+ },
236
+ };
237
+
238
+ new Tangerine({ plugins: [reloj] });
239
+ Tangerine.registerPlugin(reloj); // para todas las instancias que se creen después
240
+ ```
241
+
242
+ Los valores viajan así: los números, strings, booleanos y listas son los de JavaScript, `nil` es `null` y los mapas son objetos sin prototipo. `Tangerine.fromJS` y `Tangerine.toJS` convierten objetos completos.
243
+
244
+ ## El lenguaje en un vistazo
245
+
246
+ | Tema | Ejemplo |
247
+ | --- | --- |
248
+ | Variables | `let fijo = 1`, `var cambia = 2`, `let (a, b) = (1, 2)` |
249
+ | Strings | `"Hola, {nombre}"`, `'sin interpolar'` |
250
+ | Control | `if x > 0 { "pos" } else { "neg" }` devuelve un valor |
251
+ | Match | `match n { 0 => "cero", 1..=9 => "dígito", x if x < 0 => "neg", _ => "otro" }` |
252
+ | Bucles | `for (i, x) in lista.enumerate() { }`, `while c { }`, `loop { break }` |
253
+ | Funciones | `fn area(r: Float) -> Float { PI * r ** 2 }`, `fn saludar(n, s = "Hola") { }`, `\|x\| x * 2` |
254
+ | Clases | `class Hija : Madre { init(x) { self.x = x } }`, `super.metodo()` |
255
+ | Nil seguro | `usuario?.direccion?.ciudad ?? "sin ciudad"` |
256
+ | Errores | `do { throw "falló" } catch e { println(e.message) }` |
257
+ | Módulos | `export fn f() {}`, `import { f as g } from "./util"`, `import util from "./util"` |
258
+
259
+ La guía completa, con ejemplos que se ejecutan en el navegador, está en el sitio (`sitio/`).
260
+
261
+ ## Desarrollo
262
+
263
+ ```bash
264
+ npm install
265
+ npm test # construye dist/ y corre las pruebas
266
+ npm run lint
267
+ npm run sitio # construye la web oficial en sitio/dist/index.html
268
+ npm run sitio:dev # la web con recarga automática en localhost:3000
269
+ npm run vscode # reempaqueta la extensión de VS Code
270
+ ```
271
+
272
+ | Carpeta | Contenido |
273
+ | --- | --- |
274
+ | `src/lexer.js` | Texto a tokens. |
275
+ | `src/parser.js` | Tokens a árbol de sintaxis. |
276
+ | `src/interprete.js` | Recorre el árbol y lo ejecuta; métodos incorporados y módulos. |
277
+ | `src/valores.js` | Ámbitos, funciones, clases y utilidades de valores. |
278
+ | `src/index.js` | La clase `Tangerine` (API pública). |
279
+ | `src/plugins/dom.js` | Funciones del navegador. |
280
+ | `src/navegador.js` | Bundle global y arranque automático en la página. |
281
+ | `src/sugerencias.js` | El "¿Quisiste decir…?" de los errores. |
282
+ | `src/plugins/react.js`, `src/vite.js` | Componentes de React en Tangerine y el plugin de Vite. |
283
+ | `src/node.js`, `src/construir.js`, `src/herramientas.js`, `bin/` | Node.js, paquetes, empaquetado, plantillas, instalador y CLI. |
284
+ | `sitio/` | La web oficial, escrita en Tangerine. Se publica en GitHub Pages con `.github/workflows/sitio.yml`. |
285
+ | `plantillas/` | Lo que copia `tangerine new`. |
286
+ | `editores/vscode/` | La extensión de VS Code. |
287
+
288
+ ### Publicar una versión
289
+
290
+ 1. Sube la versión en `package.json` y en `VERSION` de `src/index.js` (una prueba verifica que coincidan) y anota los cambios en `CHANGELOG.md`.
291
+ 2. `npm login` y luego `npm publish`. `prepublishOnly` corre el lint, las pruebas y construye `dist/`.
292
+ 3. O crea un release en GitHub: el flujo `publicar.yml` publica con el secreto `NPM_TOKEN`.
293
+
294
+ ## Licencia
295
+
296
+ MIT
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env node
2
+ // CLI de Tangerine: ejecutar, revisar, crear, construir y servir proyectos.
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import http from "node:http";
6
+ import readline from "node:readline";
7
+ import { createNodeTangerine, nodeResolve } from "../src/node.js";
8
+ import { parse, show, VERSION, TngError } from "../src/index.js";
9
+ import { loadConfig, buildToDisk } from "../src/construir.js";
10
+ import { cmdNew, cmdInstall, cmdDoctor, cmdAdd, cmdRemove, cmdBuscar, cmdVscode } from "../src/herramientas.js";
11
+
12
+ const color = process.stdout.isTTY && !process.env.NO_COLOR;
13
+ const paint = (code) => (s) => (color ? `\x1b[${code}m${s}\x1b[0m` : String(s));
14
+ const naranja = paint("38;5;208"), rojo = paint("31"), gris = paint("90"), verde = paint("32"), negrita = paint("1");
15
+
16
+ const AYUDA = `
17
+ ${naranja("Tangerine")} ${VERSION}: un lenguaje pequeño con la sintaxis de Rust y Swift
18
+
19
+ ${negrita("Programar")}
20
+ tangerine abre el REPL interactivo
21
+ tangerine <archivo.tng> [args] ejecuta un archivo (también: tangerine run)
22
+ tangerine check <archivos...> revisa la sintaxis sin ejecutar
23
+
24
+ ${negrita("Proyectos")}
25
+ tangerine new <carpeta> crea un proyecto web en Tangerine
26
+ tangerine new <carpeta> --con X usa otra plantilla: react, vue, svelte, angular,
27
+ next, astro, expo, swift, flutter, consola, paquete
28
+ tangerine new --lista muestra todas las plantillas
29
+ tangerine build empaqueta el proyecto en un solo HTML
30
+ tangerine dev construye, sirve y recarga al guardar
31
+ tangerine serve [carpeta] sirve archivos estáticos
32
+
33
+ ${negrita("Paquetes de Tangerine")}
34
+ tangerine add <paquete...> instala paquetes de npm o de GitHub (usuario/repo)
35
+ tangerine remove <paquete...> los desinstala
36
+ tangerine buscar [texto] busca paquetes de Tangerine en npm
37
+
38
+ ${negrita("Herramientas")}
39
+ tangerine install <nombre...> instala node, git, swift, python, angular, vscode…
40
+ tangerine install --lista muestra todo lo que puede instalar
41
+ tangerine doctor revisa qué tienes instalado
42
+ tangerine vscode instala la extensión de Tangerine en VS Code
43
+
44
+ ${negrita("Opciones")}
45
+ --limite <n> máximo de pasos al ejecutar (por defecto, sin límite)
46
+ -p, --puerto <n> puerto para dev y serve (por defecto 3000 y 8000)
47
+ -o, --salida <f> archivo de salida de build
48
+ --entrada <f> archivo de entrada de build
49
+ --cdn build carga el intérprete desde jsDelivr en vez de incluirlo
50
+ --con <plantilla> plantilla para new
51
+ --sin-tangerine en plantillas de Vite, no agrega el plugin de Tangerine
52
+ --si install no pregunta antes de ejecutar
53
+ --forzar install reinstala aunque ya exista
54
+ --dev add instala como dependencia de desarrollo
55
+ -v, --version muestra la versión
56
+ -h, --ayuda muestra esta ayuda
57
+ `;
58
+
59
+ function parseArgs(argv) {
60
+ const flags = {}, rest = [];
61
+ for (let i = 0; i < argv.length; i++) {
62
+ const a = argv[i];
63
+ const next = () => {
64
+ if (i + 1 >= argv.length) fail(`Falta el valor de ${a}`);
65
+ return argv[++i];
66
+ };
67
+ if (a === "--") { rest.push(...argv.slice(i + 1)); break; }
68
+ else if (a === "--limite") flags.limite = Number(next());
69
+ else if (a === "-p" || a === "--puerto") flags.puerto = Number(next());
70
+ else if (a === "-o" || a === "--salida") flags.salida = next();
71
+ else if (a === "--entrada") flags.entrada = next();
72
+ else if (a === "--cdn") flags.cdn = true;
73
+ else if (a === "--con") flags.con = next().toLowerCase();
74
+ else if (a === "--lista") flags.lista = true;
75
+ else if (a === "--sin-tangerine") flags.sinTangerine = true;
76
+ else if (a === "--si" || a === "-y" || a === "--yes") flags.si = true;
77
+ else if (a === "--forzar") flags.forzar = true;
78
+ else if (a === "--dev" || a === "-D") flags.dev = true;
79
+ else if (a === "-v" || a === "--version") flags.version = true;
80
+ else if (a === "-h" || a === "--ayuda" || a === "--help") flags.ayuda = true;
81
+ else if (a.startsWith("-") && rest.length === 0) fail(`Opción desconocida: ${a}`);
82
+ else rest.push(a);
83
+ }
84
+ return { flags, rest };
85
+ }
86
+
87
+ function fail(msg, code = 1) {
88
+ console.error(rojo("error: ") + msg);
89
+ process.exit(code);
90
+ }
91
+
92
+ /** Muestra un error de Tangerine con la línea de código y una flecha en la columna. */
93
+ function reportError(tng, err) {
94
+ if (err instanceof TngError && err.file && path.isAbsolute(err.file)) {
95
+ const abs = err.file;
96
+ err.file = path.relative(process.cwd(), abs) || abs;
97
+ console.error(rojo("error: ") + tng.message(err));
98
+ const src = fs.existsSync(abs) ? fs.readFileSync(abs, "utf8").split(/\r?\n/) : null;
99
+ if (src && err.line && src[err.line - 1] !== undefined) {
100
+ const num = String(err.line);
101
+ console.error(gris(` ${num} │ `) + src[err.line - 1]);
102
+ if (err.col) console.error(gris(` ${" ".repeat(num.length)} │ `) + " ".repeat(err.col - 1) + rojo("^"));
103
+ }
104
+ printTrace(tng, err);
105
+ return;
106
+ }
107
+ console.error(rojo("error: ") + tng.message(err));
108
+ printTrace(tng, err);
109
+ }
110
+
111
+ function printTrace(tng, err) {
112
+ const rel = (t) => t.replace(/(^|llamada en )(\/[^,]+|[A-Za-z]:\\[^,]+)/, (_m, a, p) => a + (path.relative(process.cwd(), p) || p));
113
+ for (const linea of tng.trace(err)) console.error(gris(" " + rel(linea)));
114
+ }
115
+
116
+ function cmdRun(file, args, flags) {
117
+ if (!file) fail("Indica el archivo a ejecutar: tangerine run main.tng");
118
+ const full = nodeResolve(file, null);
119
+ if (!fs.existsSync(full)) fail(`No existe el archivo ${file}`);
120
+ const tng = createNodeTangerine({ args, stepLimit: flags.limite || 0 });
121
+ try {
122
+ tng.runModule(full);
123
+ } catch (err) {
124
+ reportError(tng, err);
125
+ process.exit(1);
126
+ }
127
+ }
128
+
129
+ function cmdCheck(files) {
130
+ if (!files.length) fail("Indica uno o más archivos: tangerine check main.tng");
131
+ let bad = 0;
132
+ const tng = createNodeTangerine();
133
+ for (const f of files) {
134
+ const full = path.resolve(f);
135
+ if (!fs.existsSync(full)) { console.error(rojo("✗ ") + `${f}: no existe`); bad++; continue; }
136
+ try {
137
+ parse(fs.readFileSync(full, "utf8"), full);
138
+ console.log(verde("✓ ") + f);
139
+ } catch (err) {
140
+ bad++;
141
+ reportError(tng, err);
142
+ }
143
+ }
144
+ if (bad) process.exit(1);
145
+ }
146
+
147
+ function cmdRepl() {
148
+ const tng = createNodeTangerine();
149
+ const session = tng.session();
150
+ console.log(`${naranja("Tangerine")} ${VERSION}. Escribe código y pulsa Enter. ${gris(".ayuda para ver comandos, .salir para terminar")}`);
151
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: naranja("tng> ") });
152
+ let buffer = "";
153
+ rl.prompt();
154
+ rl.on("line", (line) => {
155
+ if (!buffer) {
156
+ const cmd = line.trim();
157
+ if (cmd === ".salir" || cmd === ".exit") return rl.close();
158
+ if (cmd === ".ayuda" || cmd === ".help") {
159
+ console.log(" .salir termina el REPL\n .limpiar descarta el código a medio escribir\n Las variables se conservan entre líneas. Los bloques sin cerrar continúan en la línea siguiente.");
160
+ return rl.prompt();
161
+ }
162
+ }
163
+ if (line.trim() === ".limpiar") { buffer = ""; rl.setPrompt(naranja("tng> ")); return rl.prompt(); }
164
+ buffer += (buffer ? "\n" : "") + line;
165
+ try {
166
+ const value = session.run(buffer);
167
+ if (value !== null && value !== undefined) console.log(gris(show(value, true)));
168
+ buffer = "";
169
+ rl.setPrompt(naranja("tng> "));
170
+ } catch (err) {
171
+ if (err.incomplete) { rl.setPrompt(gris("... ")); return rl.prompt(); }
172
+ buffer = "";
173
+ rl.setPrompt(naranja("tng> "));
174
+ console.error(rojo("error: ") + tng.message(err));
175
+ }
176
+ rl.prompt();
177
+ });
178
+ rl.on("close", () => { console.log(); process.exit(0); });
179
+ }
180
+
181
+ function buildConfig(flags) {
182
+ const config = loadConfig(process.cwd(), { entry: flags.entrada, out: flags.salida, runtime: flags.cdn ? "cdn" : undefined });
183
+ if (!fs.existsSync(path.resolve(config.root, config.entry)))
184
+ fail(`No existe el archivo de entrada ${config.entry}. Créalo o indica otro con --entrada (o en tangerine.json).`);
185
+ return config;
186
+ }
187
+
188
+ function cmdBuild(flags) {
189
+ const config = buildConfig(flags);
190
+ try {
191
+ const r = buildToDisk(config);
192
+ const kb = (Buffer.byteLength(r.html) / 1024).toFixed(1);
193
+ console.log(`${verde("✓")} ${path.relative(process.cwd(), r.out)} ${gris(`(${r.modules.length} módulos, ${kb} KB)`)}`);
194
+ } catch (err) {
195
+ fail(err.message);
196
+ }
197
+ }
198
+
199
+ const TIPOS = {
200
+ ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".mjs": "text/javascript; charset=utf-8",
201
+ ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".tng": "text/plain; charset=utf-8",
202
+ ".svg": "image/svg+xml", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif",
203
+ ".webp": "image/webp", ".ico": "image/x-icon", ".woff2": "font/woff2", ".txt": "text/plain; charset=utf-8",
204
+ };
205
+
206
+ function staticServer(dir, extra) {
207
+ const root = path.resolve(dir);
208
+ return http.createServer((req, res) => {
209
+ if (extra && extra(req, res)) return;
210
+ let pathname;
211
+ try { pathname = decodeURIComponent(new URL(req.url, "http://x").pathname); }
212
+ catch { res.writeHead(400); return res.end(); }
213
+ let file = path.join(root, pathname);
214
+ if (!file.startsWith(root)) { res.writeHead(403); return res.end(); }
215
+ if (fs.existsSync(file) && fs.statSync(file).isDirectory()) file = path.join(file, "index.html");
216
+ if (!fs.existsSync(file)) { res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }); return res.end("No encontrado"); }
217
+ res.writeHead(200, { "Content-Type": TIPOS[path.extname(file).toLowerCase()] || "application/octet-stream", "Cache-Control": "no-cache" });
218
+ fs.createReadStream(file).pipe(res);
219
+ });
220
+ }
221
+
222
+ function listen(server, port, label) {
223
+ server.on("error", (e) => fail(e.code === "EADDRINUSE" ? `El puerto ${port} está ocupado. Usa otro con -p` : e.message));
224
+ server.listen(port, () => console.log(`${naranja("Tangerine")} ${label} en ${negrita(`http://localhost:${port}`)} ${gris("(Ctrl+C para detener)")}`));
225
+ }
226
+
227
+ function cmdServe(dir, flags) {
228
+ const folder = dir || ".";
229
+ if (!fs.existsSync(folder)) fail(`No existe la carpeta ${folder}`);
230
+ listen(staticServer(folder), flags.puerto || 8000, `sirviendo ${folder}`);
231
+ }
232
+
233
+ function cmdDev(flags) {
234
+ const config = buildConfig(flags);
235
+ const clients = new Set();
236
+ const rebuild = () => {
237
+ try {
238
+ const r = buildToDisk(config, { dev: true });
239
+ console.log(`${verde("✓")} ${gris(new Date().toLocaleTimeString("es"))} construido (${r.modules.length} módulos)`);
240
+ } catch (err) {
241
+ console.error(rojo("error: ") + err.message);
242
+ }
243
+ for (const c of clients) c.write("data: recarga\n\n");
244
+ };
245
+ rebuild();
246
+ const outDir = path.dirname(path.resolve(config.root, config.out));
247
+ const server = staticServer(outDir, (req, res) => {
248
+ if (req.url !== "/__tangerine_recarga") return false;
249
+ res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
250
+ res.write(": conectado\n\n");
251
+ clients.add(res);
252
+ req.on("close", () => clients.delete(res));
253
+ return true;
254
+ });
255
+ let timer = null;
256
+ try {
257
+ fs.watch(config.root, { recursive: true }, (_e, name) => {
258
+ if (!name) return;
259
+ const full = path.resolve(config.root, name);
260
+ if (full.startsWith(outDir + path.sep) || full.includes(`${path.sep}node_modules${path.sep}`)) return;
261
+ if (!/\.(tng|css|js|json)$/.test(name)) return;
262
+ clearTimeout(timer);
263
+ timer = setTimeout(rebuild, 80);
264
+ });
265
+ } catch {
266
+ console.warn(gris("Aviso: este sistema no permite vigilar carpetas; reconstruye con tangerine build."));
267
+ }
268
+ listen(server, flags.puerto || 3000, "dev");
269
+ }
270
+
271
+ const ui = { naranja, rojo, gris, verde, negrita, fail };
272
+ const { flags, rest } = parseArgs(process.argv.slice(2));
273
+ if (flags.version) { console.log(VERSION); process.exit(0); }
274
+ if (flags.ayuda) { console.log(AYUDA); process.exit(0); }
275
+
276
+ const [cmd, ...args] = rest;
277
+ switch (cmd) {
278
+ case undefined: cmdRepl(); break;
279
+ case "repl": cmdRepl(); break;
280
+ case "run": cmdRun(args[0], args.slice(1), flags); break;
281
+ case "check": cmdCheck(args); break;
282
+ case "new": await cmdNew(args[0], flags, ui); break;
283
+ case "install": case "instalar": await cmdInstall(args, flags, ui); break;
284
+ case "doctor": cmdDoctor(ui); break;
285
+ case "vscode": case "editor": cmdVscode(ui); break;
286
+ case "add": case "agregar": cmdAdd(args, flags, ui); break;
287
+ case "remove": case "quitar": cmdRemove(args, ui); break;
288
+ case "buscar": case "search": cmdBuscar(args, ui); break;
289
+ case "build": cmdBuild(flags); break;
290
+ case "dev": cmdDev(flags); break;
291
+ case "serve": cmdServe(args[0], flags); break;
292
+ case "help": case "ayuda": console.log(AYUDA); break;
293
+ default:
294
+ if (cmd.endsWith(".tng") || fs.existsSync(cmd)) cmdRun(cmd, args, flags);
295
+ else fail(`Comando desconocido: ${cmd}. Usa tangerine --ayuda`);
296
+ }