nexicscript 0.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/CODE_OF_CONDUCT.md +29 -0
- package/CONTRIBUTING.md +39 -0
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/SECURITY.md +27 -0
- package/bin/nexic.js +44 -0
- package/ejemplos/bot.nxs +13 -0
- package/ejemplos/hola.nxs +45 -0
- package/ejemplos/web.nxs +17 -0
- package/icon.png +0 -0
- package/package.json +15 -0
- package/src/index.js +13 -0
- package/src/interpreter.js +334 -0
- package/src/lexer.js +159 -0
- package/src/parser.js +352 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Código de Conducta de NexicScript
|
|
2
|
+
|
|
3
|
+
## Nuestro compromiso
|
|
4
|
+
|
|
5
|
+
Nos comprometemos a hacer de NexicScript una comunidad abierta y respetuosa para todos.
|
|
6
|
+
|
|
7
|
+
## Comportamiento esperado
|
|
8
|
+
|
|
9
|
+
- Ser respetuoso y amable con todos
|
|
10
|
+
- Aceptar crÃticas constructivas
|
|
11
|
+
- Enfocarse en lo mejor para la comunidad
|
|
12
|
+
- Mostrar empatÃa hacia otros miembros
|
|
13
|
+
|
|
14
|
+
## Comportamiento inaceptable
|
|
15
|
+
|
|
16
|
+
- Lenguaje ofensivo o insultos
|
|
17
|
+
- Acoso de cualquier tipo
|
|
18
|
+
- Publicar información privada de otros
|
|
19
|
+
- Cualquier conducta que se considere inapropiada
|
|
20
|
+
|
|
21
|
+
## Aplicación
|
|
22
|
+
|
|
23
|
+
Los casos de comportamiento abusivo pueden ser reportados abriendo un Issue privado o contactando a los mantenedores del proyecto.
|
|
24
|
+
|
|
25
|
+
Los mantenedores tienen el derecho de eliminar comentarios, commits, o contribuciones que no respeten este código.
|
|
26
|
+
|
|
27
|
+
## Atribución
|
|
28
|
+
|
|
29
|
+
Este Código de Conducta está adaptado del [Contributor Covenant](https://www.contributor-covenant.org), versión 2.1.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Cómo contribuir a NexicScript
|
|
2
|
+
|
|
3
|
+
¡Gracias por querer contribuir! NexicScript es un proyecto abierto y toda ayuda es bienvenida.
|
|
4
|
+
|
|
5
|
+
## Formas de contribuir
|
|
6
|
+
|
|
7
|
+
- 🛠Reportar bugs abriendo un Issue
|
|
8
|
+
- 💡 Proponer nuevas funcionalidades
|
|
9
|
+
- 🔧 Enviar Pull Requests con mejoras
|
|
10
|
+
- 📖 Mejorar la documentación
|
|
11
|
+
- 🌠Traducir documentación
|
|
12
|
+
|
|
13
|
+
## Proceso para contribuir
|
|
14
|
+
|
|
15
|
+
1. Haz fork del repositorio
|
|
16
|
+
2. Crea una rama: git checkout -b mi-mejora
|
|
17
|
+
3. Haz tus cambios
|
|
18
|
+
4. Haz commit: git commit -m "Descripción clara del cambio"
|
|
19
|
+
5. Haz push: git push origin mi-mejora
|
|
20
|
+
6. Abre un Pull Request
|
|
21
|
+
|
|
22
|
+
## Estilo de código
|
|
23
|
+
|
|
24
|
+
- Usa 2 espacios para indentar
|
|
25
|
+
- Nombres de variables en camelCase
|
|
26
|
+
- Comentarios en español o inglés
|
|
27
|
+
- Prueba tus cambios con los ejemplos en /ejemplos
|
|
28
|
+
|
|
29
|
+
## Reportar bugs
|
|
30
|
+
|
|
31
|
+
Abre un Issue con:
|
|
32
|
+
- Versión de NexicScript
|
|
33
|
+
- Sistema operativo
|
|
34
|
+
- Código .nxs que produce el error
|
|
35
|
+
- Mensaje de error completo
|
|
36
|
+
|
|
37
|
+
## Código de conducta
|
|
38
|
+
|
|
39
|
+
Lee nuestro [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) antes de contribuir.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NexicScript
|
|
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
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# NexicScript
|
|
2
|
+
|
|
3
|
+
> Lenguaje de programación minimalista para bots, web y bases de datos.
|
|
4
|
+
|
|
5
|
+
```nxs
|
|
6
|
+
fn saludar(nombre)
|
|
7
|
+
"Hola, " + nombre
|
|
8
|
+
|
|
9
|
+
print(saludar("Mundo"))
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Instalación
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g nexicscript
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Uso
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
nexic mi_programa.nxs
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Características
|
|
25
|
+
|
|
26
|
+
- Sin punto y coma
|
|
27
|
+
- Sin llaves — bloques por indentación
|
|
28
|
+
- Módulos integrados: `discord`, `web`, `db`
|
|
29
|
+
- Sin dependencias externas para proyectos simples
|
|
30
|
+
- Archivos `.nxs`
|
|
31
|
+
|
|
32
|
+
## Ejemplos
|
|
33
|
+
|
|
34
|
+
```nxs
|
|
35
|
+
// Variables
|
|
36
|
+
let nombre = "Bot"
|
|
37
|
+
const puerto = 8080
|
|
38
|
+
|
|
39
|
+
// Funciones
|
|
40
|
+
fn sumar(a, b)
|
|
41
|
+
a + b
|
|
42
|
+
|
|
43
|
+
// Listas y objetos
|
|
44
|
+
let usuarios = ["Ana", "Luis"]
|
|
45
|
+
let config = { puerto: 8080, debug: true }
|
|
46
|
+
|
|
47
|
+
// Discord bot
|
|
48
|
+
import discord
|
|
49
|
+
discord.conectar("TOKEN")
|
|
50
|
+
discord.on("mensaje") fn(msg)
|
|
51
|
+
if msg.contenido == "!hola"
|
|
52
|
+
discord.enviar(msg.canal, "Hola!")
|
|
53
|
+
|
|
54
|
+
// Servidor web
|
|
55
|
+
import web
|
|
56
|
+
let srv = web.crearServidor fn(req)
|
|
57
|
+
web.responder(req, 200, "OK")
|
|
58
|
+
srv.iniciar(8080)
|
|
59
|
+
|
|
60
|
+
// Base de datos
|
|
61
|
+
import db
|
|
62
|
+
db.conectar("datos.db")
|
|
63
|
+
db.insertar("usuarios", { nombre: "Ana", edad: 28 })
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Licencia
|
|
67
|
+
|
|
68
|
+
MIT
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Seguridad de NexicScript
|
|
2
|
+
|
|
3
|
+
## Reportar una vulnerabilidad
|
|
4
|
+
|
|
5
|
+
Si encuentras una vulnerabilidad de seguridad en NexicScript, **NO** abras un Issue público.
|
|
6
|
+
|
|
7
|
+
En su lugar, repórtala de forma privada a:
|
|
8
|
+
- Email: security@nexicscript.dev
|
|
9
|
+
- O usa GitHub Private Vulnerability Reporting
|
|
10
|
+
|
|
11
|
+
## Qué reportar
|
|
12
|
+
|
|
13
|
+
- Errores en el intérprete que permitan ejecutar código malicioso
|
|
14
|
+
- Vulnerabilidades en los módulos integrados (discord, web, db)
|
|
15
|
+
- Cualquier comportamiento inesperado que comprometa la seguridad
|
|
16
|
+
|
|
17
|
+
## Tiempo de respuesta
|
|
18
|
+
|
|
19
|
+
Nos comprometemos a responder en menos de 72 horas.
|
|
20
|
+
|
|
21
|
+
## Versiones soportadas
|
|
22
|
+
|
|
23
|
+
| Versión | Soporte de seguridad |
|
|
24
|
+
|---------|----------------------|
|
|
25
|
+
| 0.1.x | ✅ Activa |
|
|
26
|
+
|
|
27
|
+
Gracias por ayudar a mantener NexicScript seguro.
|
package/bin/nexic.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const { ejecutarCodigo } = require('../src/index')
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2)
|
|
9
|
+
|
|
10
|
+
if (args.length === 0) {
|
|
11
|
+
console.log(`
|
|
12
|
+
███╗ ██╗███████╗██╗ ██╗██╗ ██████╗███████╗ ██████╗██████╗ ██╗██████╗ ████████╗
|
|
13
|
+
████╗ ██║██╔════╝╚██╗██╔╝██║██╔════╝██╔════╝██╔════╝██╔══██╗██║██╔══██╗╚══██╔══╝
|
|
14
|
+
██╔██╗ ██║█████╗ ╚███╔╝ ██║██║ ███████╗██║ ██████╔╝██║██████╔╝ ██║
|
|
15
|
+
██║╚██╗██║██╔══╝ ██╔██╗ ██║██║ ╚════██║██║ ██╔══██╗██║██╔═══╝ ██║
|
|
16
|
+
██║ ╚████║███████╗██╔╝ ██╗██║╚██████╗███████║╚██████╗██║ ██║██║██║ ██║
|
|
17
|
+
╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝
|
|
18
|
+
|
|
19
|
+
NexicScript v0.1.0
|
|
20
|
+
Uso: nexic <archivo.nxs>
|
|
21
|
+
`)
|
|
22
|
+
process.exit(0)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const archivo = args[0]
|
|
26
|
+
|
|
27
|
+
if (!archivo.endsWith('.nxs')) {
|
|
28
|
+
console.error('[NexicScript] Los archivos deben tener extensión .nxs')
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(archivo)) {
|
|
33
|
+
console.error(`[NexicScript] Archivo no encontrado: ${archivo}`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const codigo = fs.readFileSync(archivo, 'utf8')
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
ejecutarCodigo(codigo)
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.error(e.message)
|
|
43
|
+
process.exit(1)
|
|
44
|
+
}
|
package/ejemplos/bot.nxs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Bot de Discord en NexicScript
|
|
2
|
+
import discord
|
|
3
|
+
|
|
4
|
+
discord.conectar("TU_TOKEN_AQUI")
|
|
5
|
+
|
|
6
|
+
discord.on("mensaje") fn(msg)
|
|
7
|
+
if msg.contenido == "!hola"
|
|
8
|
+
discord.enviar(msg.canal, "Hola! Soy un bot hecho con NexicScript")
|
|
9
|
+
|
|
10
|
+
if msg.contenido == "!version"
|
|
11
|
+
discord.enviar(msg.canal, "NexicScript v0.1.0")
|
|
12
|
+
|
|
13
|
+
print("Bot iniciado y escuchando...")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Ejemplo básico de NexicScript
|
|
2
|
+
let nombre = "NexicScript"
|
|
3
|
+
const version = "0.1.0"
|
|
4
|
+
|
|
5
|
+
fn saludar(quien)
|
|
6
|
+
"Hola desde " + quien + " v" + version
|
|
7
|
+
|
|
8
|
+
print(saludar(nombre))
|
|
9
|
+
|
|
10
|
+
// Listas y bucles
|
|
11
|
+
let frutas = ["manzana", "pera", "uva"]
|
|
12
|
+
|
|
13
|
+
for fruta in frutas
|
|
14
|
+
print("Fruta: " + fruta)
|
|
15
|
+
|
|
16
|
+
// Condicional
|
|
17
|
+
let x = 42
|
|
18
|
+
|
|
19
|
+
if x > 100
|
|
20
|
+
print("Grande")
|
|
21
|
+
else if x > 10
|
|
22
|
+
print("Mediano: " + x)
|
|
23
|
+
else
|
|
24
|
+
print("Pequeño")
|
|
25
|
+
|
|
26
|
+
// Objetos
|
|
27
|
+
let usuario = {
|
|
28
|
+
nombre: "Ana"
|
|
29
|
+
edad: 28
|
|
30
|
+
activo: true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
print("Usuario: " + usuario.nombre + ", edad: " + usuario.edad)
|
|
34
|
+
|
|
35
|
+
// Try / catch
|
|
36
|
+
fn dividir(a, b)
|
|
37
|
+
if b == 0
|
|
38
|
+
throw "No se puede dividir entre cero"
|
|
39
|
+
a / b
|
|
40
|
+
|
|
41
|
+
try
|
|
42
|
+
print("10 / 2 = " + dividir(10, 2))
|
|
43
|
+
print("10 / 0 = " + dividir(10, 0))
|
|
44
|
+
catch error
|
|
45
|
+
print("Error atrapado: " + error)
|
package/ejemplos/web.nxs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Servidor web en NexicScript
|
|
2
|
+
import web
|
|
3
|
+
import db
|
|
4
|
+
|
|
5
|
+
db.conectar("datos.db")
|
|
6
|
+
|
|
7
|
+
let manejador = fn(req)
|
|
8
|
+
if req.ruta == "/"
|
|
9
|
+
web.responder(req, 200, "Bienvenido a NexicScript")
|
|
10
|
+
else if req.ruta == "/usuarios"
|
|
11
|
+
let lista = db.consultar("usuarios", fn(u) true)
|
|
12
|
+
web.responder(req, 200, lista)
|
|
13
|
+
else
|
|
14
|
+
web.responder(req, 404, "Página no encontrada")
|
|
15
|
+
|
|
16
|
+
let servidor = web.crearServidor(manejador)
|
|
17
|
+
servidor.iniciar(8080)
|
package/icon.png
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nexicscript",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "NexicScript - Lenguaje de programación minimalista para bots, web y bases de datos",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nexic": "./bin/nexic.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node bin/nexic.js ejemplos/hola.nxs"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["nexicscript", "lenguaje", "discord", "web", "db"],
|
|
13
|
+
"author": "NexicScript Team",
|
|
14
|
+
"license": "MIT"
|
|
15
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { lexer } = require('./lexer')
|
|
4
|
+
const { parsear } = require('./parser')
|
|
5
|
+
const { interpretar } = require('./interpreter')
|
|
6
|
+
|
|
7
|
+
function ejecutarCodigo(codigo) {
|
|
8
|
+
const tokens = lexer(codigo)
|
|
9
|
+
const ast = parsear(tokens)
|
|
10
|
+
return interpretar(ast)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { ejecutarCodigo }
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class ReturnSignal { constructor(v) { this.valor = v } }
|
|
4
|
+
class ThrowSignal { constructor(v) { this.valor = v } }
|
|
5
|
+
|
|
6
|
+
class Entorno {
|
|
7
|
+
constructor(padre = null) {
|
|
8
|
+
this.vars = new Map()
|
|
9
|
+
this.padre = padre
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
obtener(nombre) {
|
|
13
|
+
if (this.vars.has(nombre)) return this.vars.get(nombre)
|
|
14
|
+
if (this.padre) return this.padre.obtener(nombre)
|
|
15
|
+
throw new Error(`[NexicScript] Variable '${nombre}' no definida`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
definir(nombre, valor) {
|
|
19
|
+
this.vars.set(nombre, valor)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
asignar(nombre, valor) {
|
|
23
|
+
if (this.vars.has(nombre)) { this.vars.set(nombre, valor); return }
|
|
24
|
+
if (this.padre) { this.padre.asignar(nombre, valor); return }
|
|
25
|
+
throw new Error(`[NexicScript] Variable '${nombre}' no definida`)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class Funcion {
|
|
30
|
+
constructor(nombre, params, cuerpo, entorno) {
|
|
31
|
+
this.nombre = nombre
|
|
32
|
+
this.params = params
|
|
33
|
+
this.cuerpo = cuerpo
|
|
34
|
+
this.entorno = entorno
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function crearEntornoGlobal() {
|
|
39
|
+
const env = new Entorno()
|
|
40
|
+
|
|
41
|
+
// Funciones nativas
|
|
42
|
+
env.definir('print', (...args) => { console.log(...args.map(nxsStr)); return null })
|
|
43
|
+
env.definir('largo', (v) => v.length ?? 0)
|
|
44
|
+
env.definir('texto', (v) => nxsStr(v))
|
|
45
|
+
env.definir('numero', (v) => Number(v))
|
|
46
|
+
env.definir('tipo', (v) => typeof v)
|
|
47
|
+
|
|
48
|
+
// Módulo discord (simulado — conectar con discord.js en producción)
|
|
49
|
+
env.definir('discord', {
|
|
50
|
+
_listeners: {},
|
|
51
|
+
conectar(token) { console.log(`[discord] Conectando con token: ${token.slice(0,8)}...`) },
|
|
52
|
+
on(evento, fn) { this._listeners[evento] = fn },
|
|
53
|
+
enviar(canal, mensaje) { console.log(`[discord] → #${canal}: ${mensaje}`) },
|
|
54
|
+
_simular(evento, datos) {
|
|
55
|
+
if (this._listeners[evento]) this._listeners[evento](datos)
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Módulo web (simulado — conectar con http en producción)
|
|
60
|
+
env.definir('web', {
|
|
61
|
+
crearServidor(manejador) {
|
|
62
|
+
const srv = {
|
|
63
|
+
_manejador: manejador,
|
|
64
|
+
iniciar: function(puerto) { console.log(`[web] Servidor escuchando en puerto ${puerto}`) },
|
|
65
|
+
_simular: function(ruta) {
|
|
66
|
+
const req = { ruta }
|
|
67
|
+
this._manejador(req)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return srv
|
|
71
|
+
},
|
|
72
|
+
responder(req, codigo, cuerpo) {
|
|
73
|
+
console.log(`[web] ${codigo} ${req.ruta} → ${nxsStr(cuerpo)}`)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// Módulo db (simulado — conectar con SQLite/PostgreSQL en producción)
|
|
78
|
+
env.definir('db', {
|
|
79
|
+
_datos: {},
|
|
80
|
+
_url: null,
|
|
81
|
+
conectar(url) { this._url = url; console.log(`[db] Conectado a: ${url}`) },
|
|
82
|
+
insertar(tabla, obj) {
|
|
83
|
+
if (!this._datos[tabla]) this._datos[tabla] = []
|
|
84
|
+
this._datos[tabla].push({ ...obj })
|
|
85
|
+
console.log(`[db] Insertado en '${tabla}':`, obj)
|
|
86
|
+
},
|
|
87
|
+
consultar(tabla, filtro) {
|
|
88
|
+
const rows = this._datos[tabla] || []
|
|
89
|
+
return typeof filtro === 'function' ? rows.filter(filtro) : rows
|
|
90
|
+
},
|
|
91
|
+
actualizar(tabla, filtro, cambios) {
|
|
92
|
+
const rows = this._datos[tabla] || []
|
|
93
|
+
rows.forEach((r, i) => { if (filtro(r)) this._datos[tabla][i] = { ...r, ...cambios } })
|
|
94
|
+
},
|
|
95
|
+
eliminar(tabla, filtro) {
|
|
96
|
+
this._datos[tabla] = (this._datos[tabla] || []).filter(r => !filtro(r))
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
return env
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function nxsStr(v) {
|
|
104
|
+
if (v === null || v === undefined) return 'null'
|
|
105
|
+
if (typeof v === 'object' && !Array.isArray(v)) return JSON.stringify(v)
|
|
106
|
+
if (Array.isArray(v)) return '[' + v.map(nxsStr).join(', ') + ']'
|
|
107
|
+
return String(v)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function ejecutarBloque(stmts, env) {
|
|
111
|
+
let ultimo = null
|
|
112
|
+
for (const s of stmts) {
|
|
113
|
+
ultimo = ejecutar(s, env)
|
|
114
|
+
if (ultimo instanceof ReturnSignal || ultimo instanceof ThrowSignal) return ultimo
|
|
115
|
+
}
|
|
116
|
+
return ultimo
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function ejecutar(nodo, env) {
|
|
120
|
+
if (!nodo) return null
|
|
121
|
+
|
|
122
|
+
switch (nodo.tipo) {
|
|
123
|
+
case 'Programa':
|
|
124
|
+
return ejecutarBloque(nodo.cuerpo, env)
|
|
125
|
+
|
|
126
|
+
case 'DeclVar': {
|
|
127
|
+
const val = evaluar(nodo.valor, env)
|
|
128
|
+
env.definir(nodo.nombre, val)
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
case 'DeclFn': {
|
|
133
|
+
const fn = new Funcion(nodo.nombre, nodo.params, nodo.cuerpo, env)
|
|
134
|
+
if (nodo.nombre) env.definir(nodo.nombre, fn)
|
|
135
|
+
return fn
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
case 'Export': {
|
|
139
|
+
const resultado = ejecutar(nodo.decl, env)
|
|
140
|
+
if (nodo.decl.nombre) env.definir('__export__' + nodo.decl.nombre, resultado)
|
|
141
|
+
return resultado
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case 'Import': {
|
|
145
|
+
// Módulos integrados ya están en el entorno global
|
|
146
|
+
// Para módulos propios se extendería aquí
|
|
147
|
+
try {
|
|
148
|
+
const modulo = env.obtener(nodo.nombre)
|
|
149
|
+
return modulo
|
|
150
|
+
} catch {
|
|
151
|
+
throw new Error(`[NexicScript] Módulo '${nodo.nombre}' no encontrado`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case 'ExprStmt':
|
|
156
|
+
return evaluar(nodo.expr, env)
|
|
157
|
+
|
|
158
|
+
case 'If': {
|
|
159
|
+
const cond = evaluar(nodo.condicion, env)
|
|
160
|
+
if (cond) {
|
|
161
|
+
return ejecutarBloque(nodo.entonces, env)
|
|
162
|
+
} else if (nodo.sino) {
|
|
163
|
+
if (nodo.sino.tipo === 'If') return ejecutar(nodo.sino, env)
|
|
164
|
+
return ejecutarBloque(nodo.sino.cuerpo, env)
|
|
165
|
+
}
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'While': {
|
|
170
|
+
let res = null
|
|
171
|
+
while (evaluar(nodo.condicion, env)) {
|
|
172
|
+
res = ejecutarBloque(nodo.cuerpo, env)
|
|
173
|
+
if (res instanceof ReturnSignal || res instanceof ThrowSignal) return res
|
|
174
|
+
}
|
|
175
|
+
return res
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
case 'For': {
|
|
179
|
+
const lista = evaluar(nodo.lista, env)
|
|
180
|
+
let res = null
|
|
181
|
+
for (const item of lista) {
|
|
182
|
+
const loopEnv = new Entorno(env)
|
|
183
|
+
loopEnv.definir(nodo.variable, item)
|
|
184
|
+
res = ejecutarBloque(nodo.cuerpo, loopEnv)
|
|
185
|
+
if (res instanceof ReturnSignal || res instanceof ThrowSignal) return res
|
|
186
|
+
}
|
|
187
|
+
return res
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
case 'Try': {
|
|
191
|
+
try {
|
|
192
|
+
const res = ejecutarBloque(nodo.intentar, env)
|
|
193
|
+
if (res instanceof ThrowSignal) {
|
|
194
|
+
const catchEnv = new Entorno(env)
|
|
195
|
+
catchEnv.definir(nodo.errorVar, res.valor)
|
|
196
|
+
return ejecutarBloque(nodo.capturar, catchEnv)
|
|
197
|
+
}
|
|
198
|
+
return res
|
|
199
|
+
} catch (e) {
|
|
200
|
+
const catchEnv = new Entorno(env)
|
|
201
|
+
catchEnv.definir(nodo.errorVar, e.message)
|
|
202
|
+
return ejecutarBloque(nodo.capturar, catchEnv)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case 'Throw':
|
|
207
|
+
return new ThrowSignal(evaluar(nodo.valor, env))
|
|
208
|
+
|
|
209
|
+
case 'Return':
|
|
210
|
+
return new ReturnSignal(nodo.valor ? evaluar(nodo.valor, env) : null)
|
|
211
|
+
|
|
212
|
+
default:
|
|
213
|
+
return evaluar(nodo, env)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function evaluar(nodo, env) {
|
|
218
|
+
switch (nodo.tipo) {
|
|
219
|
+
case 'Numero': return nodo.valor
|
|
220
|
+
case 'Texto': return nodo.valor
|
|
221
|
+
case 'Booleano': return nodo.valor
|
|
222
|
+
case 'Nulo': return null
|
|
223
|
+
|
|
224
|
+
case 'Identificador':
|
|
225
|
+
return env.obtener(nodo.nombre)
|
|
226
|
+
|
|
227
|
+
case 'Lista':
|
|
228
|
+
return nodo.elementos.map(e => evaluar(e, env))
|
|
229
|
+
|
|
230
|
+
case 'Objeto': {
|
|
231
|
+
const obj = {}
|
|
232
|
+
for (const { clave, valor } of nodo.pares) obj[clave] = evaluar(valor, env)
|
|
233
|
+
return obj
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case 'Acceso': {
|
|
237
|
+
const obj = evaluar(nodo.objeto, env)
|
|
238
|
+
if (obj === null || obj === undefined)
|
|
239
|
+
throw new Error(`[NexicScript] No se puede acceder a '${nodo.propiedad}' de null`)
|
|
240
|
+
const val = obj[nodo.propiedad]
|
|
241
|
+
if (typeof val === 'function') return val.bind(obj)
|
|
242
|
+
if (val instanceof Funcion) return val
|
|
243
|
+
return val
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
case 'Indice': {
|
|
247
|
+
const obj = evaluar(nodo.objeto, env)
|
|
248
|
+
const idx = evaluar(nodo.indice, env)
|
|
249
|
+
return obj[idx]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
case 'BinOp': {
|
|
253
|
+
const i = evaluar(nodo.izq, env)
|
|
254
|
+
const d = evaluar(nodo.der, env)
|
|
255
|
+
switch (nodo.op) {
|
|
256
|
+
case '+': return typeof i === 'string' || typeof d === 'string' ? nxsStr(i) + nxsStr(d) : i + d
|
|
257
|
+
case '-': return i - d
|
|
258
|
+
case '*': return i * d
|
|
259
|
+
case '/': return i / d
|
|
260
|
+
case '==': return i === d
|
|
261
|
+
case '!=': return i !== d
|
|
262
|
+
case '>': return i > d
|
|
263
|
+
case '<': return i < d
|
|
264
|
+
case '>=': return i >= d
|
|
265
|
+
case '<=': return i <= d
|
|
266
|
+
case 'and': return i && d
|
|
267
|
+
case 'or': return i || d
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case 'Unario': {
|
|
272
|
+
const v = evaluar(nodo.valor, env)
|
|
273
|
+
if (nodo.op === '-') return -v
|
|
274
|
+
if (nodo.op === 'not') return !v
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
case 'Asignacion': {
|
|
278
|
+
const val = evaluar(nodo.valor, env)
|
|
279
|
+
if (nodo.objetivo.tipo === 'Identificador') {
|
|
280
|
+
env.asignar(nodo.objetivo.nombre, val)
|
|
281
|
+
} else if (nodo.objetivo.tipo === 'Acceso') {
|
|
282
|
+
const obj = evaluar(nodo.objetivo.objeto, env)
|
|
283
|
+
obj[nodo.objetivo.propiedad] = val
|
|
284
|
+
}
|
|
285
|
+
return val
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
case 'Llamada': {
|
|
289
|
+
const fn = evaluar(nodo.fn, env)
|
|
290
|
+
const args = nodo.args.map(a => evaluar(a, env))
|
|
291
|
+
|
|
292
|
+
if (typeof fn === 'function') {
|
|
293
|
+
// Wrap any NexicScript Funcion args as JS callables for native modules
|
|
294
|
+
const jsArgs = args.map(a => {
|
|
295
|
+
if (a instanceof Funcion) {
|
|
296
|
+
return (...innerArgs) => {
|
|
297
|
+
const fnEnv = new Entorno(a.entorno)
|
|
298
|
+
a.params.forEach((p, i) => fnEnv.definir(p, innerArgs[i] ?? null))
|
|
299
|
+
const res = ejecutarBloque(a.cuerpo, fnEnv)
|
|
300
|
+
if (res instanceof ReturnSignal) return res.valor
|
|
301
|
+
return res
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return a
|
|
305
|
+
})
|
|
306
|
+
return fn(...jsArgs)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (fn instanceof Funcion) {
|
|
310
|
+
const fnEnv = new Entorno(fn.entorno)
|
|
311
|
+
fn.params.forEach((p, i) => fnEnv.definir(p, args[i] ?? null))
|
|
312
|
+
const res = ejecutarBloque(fn.cuerpo, fnEnv)
|
|
313
|
+
if (res instanceof ReturnSignal) return res.valor
|
|
314
|
+
if (res instanceof ThrowSignal) throw new Error(nxsStr(res.valor))
|
|
315
|
+
return res
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
throw new Error(`[NexicScript] '${nodo.fn.nombre || nodo.fn.propiedad}' no es una función`)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
case 'DeclFn':
|
|
322
|
+
return ejecutar(nodo, env)
|
|
323
|
+
|
|
324
|
+
default:
|
|
325
|
+
return ejecutar(nodo, env)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function interpretar(ast) {
|
|
330
|
+
const env = crearEntornoGlobal()
|
|
331
|
+
return ejecutar(ast, env)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = { interpretar }
|
package/src/lexer.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const PALABRAS_CLAVE = new Set([
|
|
4
|
+
'let', 'const', 'fn', 'import', 'export', 'from',
|
|
5
|
+
'if', 'else', 'for', 'in', 'while',
|
|
6
|
+
'try', 'catch', 'throw', 'return',
|
|
7
|
+
'true', 'false', 'null'
|
|
8
|
+
])
|
|
9
|
+
|
|
10
|
+
const TIPOS = {
|
|
11
|
+
// Literales
|
|
12
|
+
NUMERO: 'NUMERO',
|
|
13
|
+
TEXTO: 'TEXTO',
|
|
14
|
+
BOOLEANO: 'BOOLEANO',
|
|
15
|
+
NULO: 'NULO',
|
|
16
|
+
// Identificadores
|
|
17
|
+
IDENTIFICADOR:'IDENTIFICADOR',
|
|
18
|
+
PALABRA_CLAVE:'PALABRA_CLAVE',
|
|
19
|
+
// Operadores
|
|
20
|
+
IGUAL: 'IGUAL', // =
|
|
21
|
+
IGUAL_IGUAL: 'IGUAL_IGUAL', // ==
|
|
22
|
+
NO_IGUAL: 'NO_IGUAL', // !=
|
|
23
|
+
MAYOR: 'MAYOR', // >
|
|
24
|
+
MENOR: 'MENOR', // <
|
|
25
|
+
MAYOR_IGUAL: 'MAYOR_IGUAL', // >=
|
|
26
|
+
MENOR_IGUAL: 'MENOR_IGUAL', // <=
|
|
27
|
+
MAS: 'MAS', // +
|
|
28
|
+
MENOS: 'MENOS', // -
|
|
29
|
+
POR: 'POR', // *
|
|
30
|
+
ENTRE: 'ENTRE', // /
|
|
31
|
+
Y: 'Y', // and
|
|
32
|
+
O: 'O', // or
|
|
33
|
+
NO: 'NO', // not
|
|
34
|
+
// Puntuación
|
|
35
|
+
PAREN_IZQ: 'PAREN_IZQ', // (
|
|
36
|
+
PAREN_DER: 'PAREN_DER', // )
|
|
37
|
+
LLAVE_IZQ: 'LLAVE_IZQ', // {
|
|
38
|
+
LLAVE_DER: 'LLAVE_DER', // }
|
|
39
|
+
CORCHETE_IZQ: 'CORCHETE_IZQ', // [
|
|
40
|
+
CORCHETE_DER: 'CORCHETE_DER', // ]
|
|
41
|
+
COMA: 'COMA', // ,
|
|
42
|
+
PUNTO: 'PUNTO', // .
|
|
43
|
+
DOS_PUNTOS: 'DOS_PUNTOS', // :
|
|
44
|
+
// Estructura
|
|
45
|
+
NUEVA_LINEA: 'NUEVA_LINEA',
|
|
46
|
+
INDENTAR: 'INDENTAR', // bloque entra
|
|
47
|
+
DESINDENTAR: 'DESINDENTAR', // bloque sale
|
|
48
|
+
FIN: 'FIN',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function lexer(codigo) {
|
|
52
|
+
const tokens = []
|
|
53
|
+
const lineas = codigo.split('\n')
|
|
54
|
+
const pilaIndentacion = [0]
|
|
55
|
+
|
|
56
|
+
for (let numLinea = 0; numLinea < lineas.length; numLinea++) {
|
|
57
|
+
const linea = lineas[numLinea]
|
|
58
|
+
|
|
59
|
+
// Ignorar líneas vacías y comentarios
|
|
60
|
+
const lineaSinEspacios = linea.trimEnd()
|
|
61
|
+
if (lineaSinEspacios === '' || lineaSinEspacios.trimStart().startsWith('//')) continue
|
|
62
|
+
|
|
63
|
+
// Contar espacios al inicio
|
|
64
|
+
let espacios = 0
|
|
65
|
+
while (espacios < linea.length && linea[espacios] === ' ') espacios++
|
|
66
|
+
|
|
67
|
+
const indentActual = pilaIndentacion[pilaIndentacion.length - 1]
|
|
68
|
+
|
|
69
|
+
if (espacios > indentActual) {
|
|
70
|
+
pilaIndentacion.push(espacios)
|
|
71
|
+
tokens.push({ tipo: TIPOS.INDENTAR, valor: null, linea: numLinea + 1 })
|
|
72
|
+
} else {
|
|
73
|
+
while (espacios < pilaIndentacion[pilaIndentacion.length - 1]) {
|
|
74
|
+
pilaIndentacion.pop()
|
|
75
|
+
tokens.push({ tipo: TIPOS.DESINDENTAR, valor: null, linea: numLinea + 1 })
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Tokenizar la línea
|
|
80
|
+
let i = espacios
|
|
81
|
+
while (i < linea.length) {
|
|
82
|
+
// Saltar espacios
|
|
83
|
+
if (linea[i] === ' ') { i++; continue }
|
|
84
|
+
|
|
85
|
+
// Comentario inline
|
|
86
|
+
if (linea[i] === '/' && linea[i+1] === '/') break
|
|
87
|
+
|
|
88
|
+
// Número
|
|
89
|
+
if (/[0-9]/.test(linea[i])) {
|
|
90
|
+
let num = ''
|
|
91
|
+
while (i < linea.length && /[0-9.]/.test(linea[i])) num += linea[i++]
|
|
92
|
+
tokens.push({ tipo: TIPOS.NUMERO, valor: parseFloat(num), linea: numLinea + 1 })
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Texto con comillas dobles o simples
|
|
97
|
+
if (linea[i] === '"' || linea[i] === "'") {
|
|
98
|
+
const comilla = linea[i++]
|
|
99
|
+
let texto = ''
|
|
100
|
+
while (i < linea.length && linea[i] !== comilla) texto += linea[i++]
|
|
101
|
+
i++ // cerrar comilla
|
|
102
|
+
tokens.push({ tipo: TIPOS.TEXTO, valor: texto, linea: numLinea + 1 })
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Identificador o palabra clave
|
|
107
|
+
if (/[a-zA-Z_]/.test(linea[i])) {
|
|
108
|
+
let nombre = ''
|
|
109
|
+
while (i < linea.length && /[a-zA-Z0-9_]/.test(linea[i])) nombre += linea[i++]
|
|
110
|
+
if (nombre === 'true' || nombre === 'false') {
|
|
111
|
+
tokens.push({ tipo: TIPOS.BOOLEANO, valor: nombre === 'true', linea: numLinea + 1 })
|
|
112
|
+
} else if (nombre === 'null') {
|
|
113
|
+
tokens.push({ tipo: TIPOS.NULO, valor: null, linea: numLinea + 1 })
|
|
114
|
+
} else if (PALABRAS_CLAVE.has(nombre)) {
|
|
115
|
+
tokens.push({ tipo: TIPOS.PALABRA_CLAVE, valor: nombre, linea: numLinea + 1 })
|
|
116
|
+
} else {
|
|
117
|
+
tokens.push({ tipo: TIPOS.IDENTIFICADOR, valor: nombre, linea: numLinea + 1 })
|
|
118
|
+
}
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Operadores y puntuación
|
|
123
|
+
const doble = linea.slice(i, i+2)
|
|
124
|
+
if (doble === '==') { tokens.push({ tipo: TIPOS.IGUAL_IGUAL, valor: '==', linea: numLinea + 1 }); i+=2; continue }
|
|
125
|
+
if (doble === '!=') { tokens.push({ tipo: TIPOS.NO_IGUAL, valor: '!=', linea: numLinea + 1 }); i+=2; continue }
|
|
126
|
+
if (doble === '>=') { tokens.push({ tipo: TIPOS.MAYOR_IGUAL, valor: '>=', linea: numLinea + 1 }); i+=2; continue }
|
|
127
|
+
if (doble === '<=') { tokens.push({ tipo: TIPOS.MENOR_IGUAL, valor: '<=', linea: numLinea + 1 }); i+=2; continue }
|
|
128
|
+
|
|
129
|
+
const simple = linea[i]
|
|
130
|
+
const mapaSimple = {
|
|
131
|
+
'=': TIPOS.IGUAL, '>': TIPOS.MAYOR, '<': TIPOS.MENOR,
|
|
132
|
+
'+': TIPOS.MAS, '-': TIPOS.MENOS, '*': TIPOS.POR, '/': TIPOS.ENTRE,
|
|
133
|
+
'(': TIPOS.PAREN_IZQ, ')': TIPOS.PAREN_DER,
|
|
134
|
+
'{': TIPOS.LLAVE_IZQ, '}': TIPOS.LLAVE_DER,
|
|
135
|
+
'[': TIPOS.CORCHETE_IZQ, ']': TIPOS.CORCHETE_DER,
|
|
136
|
+
',': TIPOS.COMA, '.': TIPOS.PUNTO, ':': TIPOS.DOS_PUNTOS,
|
|
137
|
+
}
|
|
138
|
+
if (mapaSimple[simple]) {
|
|
139
|
+
tokens.push({ tipo: mapaSimple[simple], valor: simple, linea: numLinea + 1 })
|
|
140
|
+
i++; continue
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
throw new Error(`[NexicScript] Carácter desconocido '${linea[i]}' en línea ${numLinea + 1}`)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
tokens.push({ tipo: TIPOS.NUEVA_LINEA, valor: null, linea: numLinea + 1 })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Cerrar indentaciones abiertas
|
|
150
|
+
while (pilaIndentacion.length > 1) {
|
|
151
|
+
pilaIndentacion.pop()
|
|
152
|
+
tokens.push({ tipo: TIPOS.DESINDENTAR, valor: null, linea: -1 })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
tokens.push({ tipo: TIPOS.FIN, valor: null, linea: -1 })
|
|
156
|
+
return tokens
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = { lexer, TIPOS }
|
package/src/parser.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { TIPOS } = require('./lexer')
|
|
4
|
+
|
|
5
|
+
class Parser {
|
|
6
|
+
constructor(tokens) {
|
|
7
|
+
this.tokens = tokens
|
|
8
|
+
this.pos = 0
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
actual() { return this.tokens[this.pos] }
|
|
12
|
+
siguiente() { return this.tokens[this.pos + 1] }
|
|
13
|
+
|
|
14
|
+
consumir(tipo, valor) {
|
|
15
|
+
const t = this.actual()
|
|
16
|
+
if (tipo && t.tipo !== tipo) {
|
|
17
|
+
throw new Error(`[NexicScript] Se esperaba ${tipo} pero se encontró ${t.tipo} ('${t.valor}') en línea ${t.linea}`)
|
|
18
|
+
}
|
|
19
|
+
if (valor !== undefined && t.valor !== valor) {
|
|
20
|
+
throw new Error(`[NexicScript] Se esperaba '${valor}' pero se encontró '${t.valor}' en línea ${t.linea}`)
|
|
21
|
+
}
|
|
22
|
+
this.pos++
|
|
23
|
+
return t
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
saltarLineas() {
|
|
27
|
+
while (this.actual().tipo === TIPOS.NUEVA_LINEA) this.pos++
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Salta líneas nuevas, INDENTAR y DESINDENTAR (para dentro de {} y [])
|
|
31
|
+
saltarEstructura() {
|
|
32
|
+
while ([TIPOS.NUEVA_LINEA, TIPOS.INDENTAR, TIPOS.DESINDENTAR].includes(this.actual().tipo)) this.pos++
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
parsear() {
|
|
36
|
+
const stmts = []
|
|
37
|
+
this.saltarLineas()
|
|
38
|
+
while (this.actual().tipo !== TIPOS.FIN) {
|
|
39
|
+
stmts.push(this.stmt())
|
|
40
|
+
this.saltarLineas()
|
|
41
|
+
}
|
|
42
|
+
return { tipo: 'Programa', cuerpo: stmts }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
stmt() {
|
|
46
|
+
const t = this.actual()
|
|
47
|
+
|
|
48
|
+
if (t.tipo === TIPOS.PALABRA_CLAVE) {
|
|
49
|
+
switch (t.valor) {
|
|
50
|
+
case 'let': return this.declVar(false)
|
|
51
|
+
case 'const': return this.declVar(true)
|
|
52
|
+
case 'fn': return this.declFn()
|
|
53
|
+
case 'export': return this.declExport()
|
|
54
|
+
case 'import': return this.declImport()
|
|
55
|
+
case 'if': return this.declIf()
|
|
56
|
+
case 'while': return this.declWhile()
|
|
57
|
+
case 'for': return this.declFor()
|
|
58
|
+
case 'try': return this.declTry()
|
|
59
|
+
case 'throw': return this.declThrow()
|
|
60
|
+
case 'return': return this.declReturn()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return this.exprStmt()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
declVar(esConst) {
|
|
68
|
+
this.consumir(TIPOS.PALABRA_CLAVE)
|
|
69
|
+
const nombre = this.consumir(TIPOS.IDENTIFICADOR).valor
|
|
70
|
+
this.consumir(TIPOS.IGUAL)
|
|
71
|
+
const valor = this.expr()
|
|
72
|
+
this.saltarLineas()
|
|
73
|
+
return { tipo: 'DeclVar', nombre, valor, esConst }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
declFn() {
|
|
77
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'fn')
|
|
78
|
+
const nombre = this.actual().tipo === TIPOS.IDENTIFICADOR ? this.consumir(TIPOS.IDENTIFICADOR).valor : null
|
|
79
|
+
this.consumir(TIPOS.PAREN_IZQ)
|
|
80
|
+
const params = []
|
|
81
|
+
while (this.actual().tipo !== TIPOS.PAREN_DER) {
|
|
82
|
+
params.push(this.consumir(TIPOS.IDENTIFICADOR).valor)
|
|
83
|
+
if (this.actual().tipo === TIPOS.COMA) this.consumir(TIPOS.COMA)
|
|
84
|
+
}
|
|
85
|
+
this.consumir(TIPOS.PAREN_DER)
|
|
86
|
+
const cuerpo = this.bloque()
|
|
87
|
+
return { tipo: 'DeclFn', nombre, params, cuerpo }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
declExport() {
|
|
91
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'export')
|
|
92
|
+
const decl = this.stmt()
|
|
93
|
+
return { tipo: 'Export', decl }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
declImport() {
|
|
97
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'import')
|
|
98
|
+
const nombre = this.consumir(TIPOS.IDENTIFICADOR).valor
|
|
99
|
+
let desde = null
|
|
100
|
+
if (this.actual().tipo === TIPOS.PALABRA_CLAVE && this.actual().valor === 'from') {
|
|
101
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'from')
|
|
102
|
+
desde = this.consumir(TIPOS.TEXTO).valor
|
|
103
|
+
}
|
|
104
|
+
this.saltarLineas()
|
|
105
|
+
return { tipo: 'Import', nombre, desde }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
declIf() {
|
|
109
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'if')
|
|
110
|
+
const condicion = this.expr()
|
|
111
|
+
const entonces = this.bloque()
|
|
112
|
+
let sino = null
|
|
113
|
+
if (this.actual().tipo === TIPOS.PALABRA_CLAVE && this.actual().valor === 'else') {
|
|
114
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'else')
|
|
115
|
+
if (this.actual().tipo === TIPOS.PALABRA_CLAVE && this.actual().valor === 'if') {
|
|
116
|
+
sino = this.declIf()
|
|
117
|
+
} else {
|
|
118
|
+
sino = { tipo: 'Bloque', cuerpo: this.bloque() }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { tipo: 'If', condicion, entonces, sino }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
declWhile() {
|
|
125
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'while')
|
|
126
|
+
const condicion = this.expr()
|
|
127
|
+
const cuerpo = this.bloque()
|
|
128
|
+
return { tipo: 'While', condicion, cuerpo }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
declFor() {
|
|
132
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'for')
|
|
133
|
+
const variable = this.consumir(TIPOS.IDENTIFICADOR).valor
|
|
134
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'in')
|
|
135
|
+
const lista = this.expr()
|
|
136
|
+
const cuerpo = this.bloque()
|
|
137
|
+
return { tipo: 'For', variable, lista, cuerpo }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
declTry() {
|
|
141
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'try')
|
|
142
|
+
const intentar = this.bloque()
|
|
143
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'catch')
|
|
144
|
+
const errorVar = this.consumir(TIPOS.IDENTIFICADOR).valor
|
|
145
|
+
const capturar = this.bloque()
|
|
146
|
+
return { tipo: 'Try', intentar, errorVar, capturar }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
declThrow() {
|
|
150
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'throw')
|
|
151
|
+
const valor = this.expr()
|
|
152
|
+
this.saltarLineas()
|
|
153
|
+
return { tipo: 'Throw', valor }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
declReturn() {
|
|
157
|
+
this.consumir(TIPOS.PALABRA_CLAVE, 'return')
|
|
158
|
+
const valor = this.actual().tipo === TIPOS.NUEVA_LINEA ? null : this.expr()
|
|
159
|
+
this.saltarLineas()
|
|
160
|
+
return { tipo: 'Return', valor }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
bloque() {
|
|
164
|
+
this.saltarLineas()
|
|
165
|
+
if (this.actual().tipo !== TIPOS.INDENTAR) {
|
|
166
|
+
// bloque de una sola expresión en la misma línea
|
|
167
|
+
const s = this.stmt()
|
|
168
|
+
return [s]
|
|
169
|
+
}
|
|
170
|
+
this.consumir(TIPOS.INDENTAR)
|
|
171
|
+
const stmts = []
|
|
172
|
+
this.saltarLineas()
|
|
173
|
+
while (this.actual().tipo !== TIPOS.DESINDENTAR && this.actual().tipo !== TIPOS.FIN) {
|
|
174
|
+
stmts.push(this.stmt())
|
|
175
|
+
this.saltarLineas()
|
|
176
|
+
}
|
|
177
|
+
if (this.actual().tipo === TIPOS.DESINDENTAR) this.consumir(TIPOS.DESINDENTAR)
|
|
178
|
+
return stmts
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
exprStmt() {
|
|
182
|
+
const e = this.expr()
|
|
183
|
+
this.saltarLineas()
|
|
184
|
+
return { tipo: 'ExprStmt', expr: e }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
expr() { return this.exprAsignacion() }
|
|
188
|
+
|
|
189
|
+
exprAsignacion() {
|
|
190
|
+
const izq = this.exprOr()
|
|
191
|
+
if (this.actual().tipo === TIPOS.IGUAL) {
|
|
192
|
+
this.consumir(TIPOS.IGUAL)
|
|
193
|
+
const der = this.expr()
|
|
194
|
+
return { tipo: 'Asignacion', objetivo: izq, valor: der }
|
|
195
|
+
}
|
|
196
|
+
return izq
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
exprOr() {
|
|
200
|
+
let izq = this.exprAnd()
|
|
201
|
+
while (this.actual().tipo === TIPOS.PALABRA_CLAVE && this.actual().valor === 'or') {
|
|
202
|
+
this.consumir(TIPOS.PALABRA_CLAVE)
|
|
203
|
+
izq = { tipo: 'BinOp', op: 'or', izq, der: this.exprAnd() }
|
|
204
|
+
}
|
|
205
|
+
return izq
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
exprAnd() {
|
|
209
|
+
let izq = this.exprIgualdad()
|
|
210
|
+
while (this.actual().tipo === TIPOS.PALABRA_CLAVE && this.actual().valor === 'and') {
|
|
211
|
+
this.consumir(TIPOS.PALABRA_CLAVE)
|
|
212
|
+
izq = { tipo: 'BinOp', op: 'and', izq, der: this.exprIgualdad() }
|
|
213
|
+
}
|
|
214
|
+
return izq
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
exprIgualdad() {
|
|
218
|
+
let izq = this.exprComparacion()
|
|
219
|
+
while ([TIPOS.IGUAL_IGUAL, TIPOS.NO_IGUAL].includes(this.actual().tipo)) {
|
|
220
|
+
const op = this.consumir(this.actual().tipo).valor
|
|
221
|
+
izq = { tipo: 'BinOp', op, izq, der: this.exprComparacion() }
|
|
222
|
+
}
|
|
223
|
+
return izq
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
exprComparacion() {
|
|
227
|
+
let izq = this.exprSuma()
|
|
228
|
+
while ([TIPOS.MAYOR, TIPOS.MENOR, TIPOS.MAYOR_IGUAL, TIPOS.MENOR_IGUAL].includes(this.actual().tipo)) {
|
|
229
|
+
const op = this.consumir(this.actual().tipo).valor
|
|
230
|
+
izq = { tipo: 'BinOp', op, izq, der: this.exprSuma() }
|
|
231
|
+
}
|
|
232
|
+
return izq
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
exprSuma() {
|
|
236
|
+
let izq = this.exprProducto()
|
|
237
|
+
while ([TIPOS.MAS, TIPOS.MENOS].includes(this.actual().tipo)) {
|
|
238
|
+
const op = this.consumir(this.actual().tipo).valor
|
|
239
|
+
izq = { tipo: 'BinOp', op, izq, der: this.exprProducto() }
|
|
240
|
+
}
|
|
241
|
+
return izq
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
exprProducto() {
|
|
245
|
+
let izq = this.exprUnario()
|
|
246
|
+
while ([TIPOS.POR, TIPOS.ENTRE].includes(this.actual().tipo)) {
|
|
247
|
+
const op = this.consumir(this.actual().tipo).valor
|
|
248
|
+
izq = { tipo: 'BinOp', op, izq, der: this.exprUnario() }
|
|
249
|
+
}
|
|
250
|
+
return izq
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
exprUnario() {
|
|
254
|
+
if (this.actual().tipo === TIPOS.MENOS) {
|
|
255
|
+
this.consumir(TIPOS.MENOS)
|
|
256
|
+
return { tipo: 'Unario', op: '-', valor: this.exprUnario() }
|
|
257
|
+
}
|
|
258
|
+
if (this.actual().tipo === TIPOS.PALABRA_CLAVE && this.actual().valor === 'not') {
|
|
259
|
+
this.consumir(TIPOS.PALABRA_CLAVE)
|
|
260
|
+
return { tipo: 'Unario', op: 'not', valor: this.exprUnario() }
|
|
261
|
+
}
|
|
262
|
+
return this.exprLlamada()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
exprLlamada() {
|
|
266
|
+
let expr = this.exprPrimaria()
|
|
267
|
+
while (true) {
|
|
268
|
+
if (this.actual().tipo === TIPOS.PAREN_IZQ) {
|
|
269
|
+
this.consumir(TIPOS.PAREN_IZQ)
|
|
270
|
+
const args = []
|
|
271
|
+
while (this.actual().tipo !== TIPOS.PAREN_DER) {
|
|
272
|
+
args.push(this.expr())
|
|
273
|
+
if (this.actual().tipo === TIPOS.COMA) this.consumir(TIPOS.COMA)
|
|
274
|
+
}
|
|
275
|
+
this.consumir(TIPOS.PAREN_DER)
|
|
276
|
+
expr = { tipo: 'Llamada', fn: expr, args }
|
|
277
|
+
} else if (this.actual().tipo === TIPOS.PUNTO) {
|
|
278
|
+
this.consumir(TIPOS.PUNTO)
|
|
279
|
+
const prop = this.consumir(TIPOS.IDENTIFICADOR).valor
|
|
280
|
+
expr = { tipo: 'Acceso', objeto: expr, propiedad: prop }
|
|
281
|
+
} else if (this.actual().tipo === TIPOS.CORCHETE_IZQ) {
|
|
282
|
+
this.consumir(TIPOS.CORCHETE_IZQ)
|
|
283
|
+
const indice = this.expr()
|
|
284
|
+
this.consumir(TIPOS.CORCHETE_DER)
|
|
285
|
+
expr = { tipo: 'Indice', objeto: expr, indice }
|
|
286
|
+
} else {
|
|
287
|
+
break
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return expr
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
exprPrimaria() {
|
|
294
|
+
const t = this.actual()
|
|
295
|
+
|
|
296
|
+
if (t.tipo === TIPOS.NUMERO) { this.pos++; return { tipo: 'Numero', valor: t.valor } }
|
|
297
|
+
if (t.tipo === TIPOS.TEXTO) { this.pos++; return { tipo: 'Texto', valor: t.valor } }
|
|
298
|
+
if (t.tipo === TIPOS.BOOLEANO) { this.pos++; return { tipo: 'Booleano', valor: t.valor } }
|
|
299
|
+
if (t.tipo === TIPOS.NULO) { this.pos++; return { tipo: 'Nulo', valor: null } }
|
|
300
|
+
|
|
301
|
+
if (t.tipo === TIPOS.IDENTIFICADOR) {
|
|
302
|
+
this.pos++
|
|
303
|
+
return { tipo: 'Identificador', nombre: t.valor }
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (t.tipo === TIPOS.PALABRA_CLAVE && t.valor === 'fn') {
|
|
307
|
+
return this.declFn()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (t.tipo === TIPOS.PAREN_IZQ) {
|
|
311
|
+
this.consumir(TIPOS.PAREN_IZQ)
|
|
312
|
+
const e = this.expr()
|
|
313
|
+
this.consumir(TIPOS.PAREN_DER)
|
|
314
|
+
return e
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (t.tipo === TIPOS.CORCHETE_IZQ) {
|
|
318
|
+
this.consumir(TIPOS.CORCHETE_IZQ)
|
|
319
|
+
const elementos = []
|
|
320
|
+
while (this.actual().tipo !== TIPOS.CORCHETE_DER) {
|
|
321
|
+
elementos.push(this.expr())
|
|
322
|
+
if (this.actual().tipo === TIPOS.COMA) this.consumir(TIPOS.COMA)
|
|
323
|
+
}
|
|
324
|
+
this.consumir(TIPOS.CORCHETE_DER)
|
|
325
|
+
return { tipo: 'Lista', elementos }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (t.tipo === TIPOS.LLAVE_IZQ) {
|
|
329
|
+
this.consumir(TIPOS.LLAVE_IZQ)
|
|
330
|
+
this.saltarEstructura()
|
|
331
|
+
const pares = []
|
|
332
|
+
while (this.actual().tipo !== TIPOS.LLAVE_DER) {
|
|
333
|
+
const clave = this.consumir(TIPOS.IDENTIFICADOR).valor
|
|
334
|
+
this.consumir(TIPOS.DOS_PUNTOS)
|
|
335
|
+
const valor = this.expr()
|
|
336
|
+
pares.push({ clave, valor })
|
|
337
|
+
this.saltarEstructura()
|
|
338
|
+
if (this.actual().tipo === TIPOS.COMA) { this.consumir(TIPOS.COMA); this.saltarEstructura() }
|
|
339
|
+
}
|
|
340
|
+
this.consumir(TIPOS.LLAVE_DER)
|
|
341
|
+
return { tipo: 'Objeto', pares }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
throw new Error(`[NexicScript] Token inesperado '${t.valor}' (${t.tipo}) en línea ${t.linea}`)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function parsear(tokens) {
|
|
349
|
+
return new Parser(tokens).parsear()
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
module.exports = { parsear }
|