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.
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +295 -2
- package/bin/tangerine.js +296 -0
- package/dist/tangerine.cjs +2142 -0
- package/dist/tangerine.global.js +2318 -0
- package/dist/tangerine.global.min.js +10 -0
- package/dist/tangerine.mjs +2120 -0
- package/editores/vscode/CHANGELOG.md +5 -0
- package/editores/vscode/LICENSE +21 -0
- package/editores/vscode/README.md +13 -0
- package/editores/vscode/tangerine-vscode.vsix +0 -0
- package/package.json +98 -4
- package/plantillas/consola/README.md +8 -0
- package/plantillas/consola/_gitignore +1 -0
- package/plantillas/consola/package.json +13 -0
- package/plantillas/consola/src/main.tng +19 -0
- package/plantillas/consola/src/texto.tng +13 -0
- package/plantillas/paquete/README.md +23 -0
- package/plantillas/paquete/_gitignore +1 -0
- package/plantillas/paquete/index.tng +10 -0
- package/plantillas/paquete/package.json +15 -0
- package/plantillas/paquete/pruebas.tng +6 -0
- package/plantillas/react/README.md +13 -0
- package/plantillas/react/_gitignore +2 -0
- package/plantillas/react/index.html +12 -0
- package/plantillas/react/package.json +21 -0
- package/plantillas/react/src/App.tng +19 -0
- package/plantillas/react/src/Contador.tng +14 -0
- package/plantillas/react/src/estilos.css +21 -0
- package/plantillas/react/src/main.jsx +10 -0
- package/plantillas/react/vite.config.js +8 -0
- package/plantillas/web/README.md +11 -0
- package/plantillas/web/_gitignore +2 -0
- package/plantillas/web/package.json +14 -0
- package/plantillas/web/src/contador.tng +10 -0
- package/plantillas/web/src/estilos.css +18 -0
- package/plantillas/web/src/main.tng +22 -0
- package/plantillas/web/tangerine.json +8 -0
- package/src/construir.js +142 -0
- package/src/errores.js +47 -0
- package/src/herramientas.js +335 -0
- package/src/index.js +242 -0
- package/src/interprete.js +684 -0
- package/src/lexer.js +148 -0
- package/src/navegador.js +80 -0
- package/src/node.js +101 -0
- package/src/parser.js +469 -0
- package/src/plugins/dom.js +106 -0
- package/src/plugins/react.js +143 -0
- package/src/sugerencias.js +59 -0
- package/src/valores.js +170 -0
- package/src/vite.js +102 -0
- package/types/dom.d.ts +5 -0
- package/types/index.d.ts +132 -0
- package/types/node.d.ts +10 -0
- package/types/react.d.ts +7 -0
- 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
|
-
#
|
|
1
|
+
# 🍊 Tangerine
|
|
2
2
|
|
|
3
|
-
|
|
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
|
package/bin/tangerine.js
ADDED
|
@@ -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
|
+
}
|