An open API service indexing awesome lists of open source software.

https://github.com/grazen0/the-ultimate-parser


https://github.com/grazen0/the-ultimate-parser

Last synced: 19 days ago
JSON representation

Awesome Lists containing this project

README

          

# The Ultimate Parser

Aplicación web para visualizar métodos de análisis sintáctico usados en el curso CS3402 Compiladores. Permite cargar gramáticas, probar cadenas de entrada y comparar parsers Top-Down y Bottom-Up con tablas, trazas y diagnósticos.

## Funcionalidades

- Parsers implementados:
- Descenso recursivo.
- Predictivo LL(1).
- LR(0).
- SLR(1).
- LALR(1).
- LR(1).
- Construcción de FIRST y FOLLOW.
- Construcción de tablas LL(1), ACTION y GOTO.
- Simulación paso a paso con pila, entrada restante y acción aplicada.
- Visualización de árbol de derivación.
- Diagnósticos locales para conflictos, prefijos comunes y recursión izquierda directa.
- Teclado formal con símbolos como `ϵ`, `→`, `|` y `$`.
- Ejemplos precargados.
- Exportación básica mediante impresión del navegador.
- PWA básica con manifest e icono.

## Stack

- Next.js 16
- React 19
- TypeScript
- Tailwind CSS 4
- Vitest
- `next/font/google` con JetBrains Mono

## Formato de Gramáticas

Una producción por línea:

```txt
E -> T E'
E' -> + T E' | ϵ
T -> F T'
T' -> * F T' | ϵ
F -> ( E ) | id
```

Reglas aceptadas:

- Flechas: `->`, o `::=`.
- Alternativas con `|`.
- Epsilon como `ϵ`, `ε`, `eps` o `epsilon`.
- Los símbolos deben estar separados por espacios.
- La primera producción define el símbolo inicial.
- Los comentarios de línea empiezan con `#`.

Ejemplo de cadena:

```txt
id + id * id
```

## Arquitectura

```
┌─────────────────────────────────────────────────────────────┐
│ Next.js 16 App Router │
│ src/app/ (pages, layout, styles, manifest) │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ src/features/parser-lab/ │
│ - use-parser-lab.ts (React hooks + estado) │
│ - components/ (componentes UI) │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ src/lib/parser/ │
│ (Motor puro de parsing - sin dependencias de React) │
├─────────────────────────────────────────────────────────────┤
│ grammar.ts - Parsing de gramáticas │
│ sets.ts - Cálculo de conjuntos FIRST/FOLLOW │
│ diagnostics.ts - Sugerencias y advertencias de gramática │
│ ll1.ts - Tabla y simulación LL(1) │
│ lr.ts - Autómata LR, tabla y simulación │
│ analyze.ts - Punto de entrada principal │
│ types.ts - Definiciones de tipos TypeScript │
│ examples.ts - Gramáticas de ejemplo │
└─────────────────────────────────────────────────────────────┘
```

La lógica de parsing vive en `src/lib/parser` y no depende de React. La interfaz vive en `src/features/parser-lab`, separando el estado del feature de sus componentes visuales.

### Principios de Diseño

- **Motor puro**: Todas las funciones de parsing están en `src/lib/parser/` y no importan ningún componente de React. Esto permite que el código sea testeable y reutilizable.
- **Interfaz reactiva**: Los componentes de UI en `src/features/parser-lab/` consumen el motor de parsing y manejan el estado de la aplicación.
- **Separación de responsabilidades**: La capa de análisis (`analyze.ts`) orquesta el flujo completo: parsing → conjuntos → tabla → simulación.

## Tipos Principales

### Grammar (types.ts)

```typescript
type Grammar = {
start: string; // Símbolo inicial de la gramática
augmentedStart: string; // Símbolo inicial aumentado (S')
nonterminals: string[]; // Lista de no terminales
terminals: string[]; // Lista de terminales
productions: Production[]; // Lista de producciones
byLhs: Record; // Producciones agrupadas por LHS
};
```

### Production (types.ts)

```typescript
type Production = {
id: number; // Identificador único de producción
lhs: string; // Lado izquierdo (no terminal)
rhs: string[]; // Lado derecho (secuencia de símbolos)
label: string; // Representación formateada "A -> α"
};
```

### LRItem (types.ts)

```typescript
type LRItem = {
productionId: number; // ID de la producción
dot: number; // Posición del punto (0 = inicio)
lookahead?: string; // Lookahead (para LR(1)/LALR)
};
```

### LRState (types.ts)

```typescript
type LRState = {
id: number; // ID del estado
items: LRItem[]; // Items LR en el estado
transitions: Record; // Transiciones: símbolo → estado
};
```

### AnalysisResult (types.ts)

```typescript
type AnalysisResult = {
kind: AnalysisKind; // Tipo de parser (recursive, ll1, lr0, etc.)
name: string; // Nombre legible del parser
accepted: boolean; // Si la cadena fue aceptada
diagnostics: Diagnostic[]; // Diagnósticos y errores
steps: ParserStep[]; // Pasos de la simulación
tree?: TreeNode; // Árbol de derivación
ll1Table?: LL1Table; // Tabla predictiva LL(1)
lrStates?: LRState[]; // Estados del autómata LR
lrTable?: LRTable; // Tablas ACTION y GOTO
first: Record; // Conjuntos FIRST (solo no terminales)
follow: Record; // Conjuntos FOLLOW (solo no terminales)
};
```

## API del Motor de Parsing

### grammar.ts

#### tokenizeInput(input: string): string[]

Convierte una cadena de entrada en una lista de símbolos normalizados.

- **Parámetros**: `input` - Cadena de texto con tokens separados por espacios
- **Retorna**: Array de símbolos normalizados

#### normalizeSymbol(symbol: string): string

Normaliza un símbolo individuales, convirtiendo aliases de epsilon a `ϵ`.

- **Parámetros**: `symbol` - Símbolo a normalizar
- **Retorna**: Símbolo normalizado o cadena vacía si es whitespace

#### parseGrammar(source: string): ParseResult

Parsea texto de gramática y construye un objeto Grammar.

- **Parámetros**: `source` - Texto con producciones (una por línea)
- **Retorna**: `ParseResult` con `ok`, `value` (Grammar) y `diagnostics`

#### formatProduction(production: Production): string

Formatea una producción como string legible.

- **Parámetros**: `production` - Objeto Production
- **Retorna**: String formateado "A -> α" (usa ϵ si RHS es vacío)

---

### sets.ts

#### firstSets(grammar: Grammar): Record>

Calcula los conjuntos FIRST para todos los símbolos de la gramática.

- **Parámetros**: `grammar` - Objeto Grammar
- **Retorna**: Objeto mapeando símbolo → Set de terminales en FIRST

#### firstOfSequence(sequence: string[], first: Record>): Set

Calcula FIRST(α) para una secuencia de símbolos.

- **Parámetros**:
- `sequence` - Array de símbolos
- `first` - Mapa de conjuntos FIRST precalculados
- **Retorna**: Set con los FIRST de la secuencia (incluye ϵ si toda la secuencia es nullable)

#### followSets(grammar: Grammar, first = firstSets(grammar)): Record>

Calcula los conjuntos FOLLOW para todos los no terminales.

- **Parámetros**:
- `grammar` - Objeto Grammar
- `first` - (opcional) Conjuntos FIRST precalculados
- **Retorna**: Objeto mapeando no terminal → Set de símbolos en FOLLOW

#### printableSets(sets: Record>): Record

Convierte conjuntos de Sets a arrays de strings ordenados para display.

- **Parámetros**: `sets` - Mapa de símbolo → Set
- **Retorna**: Mapa de símbolo → array de strings ordenados alfabeticamente

---

### diagnostics.ts

#### grammarHints(grammar: Grammar, conflicts: Diagnostic[] = []): Diagnostic[]

Genera sugerencias y advertencias sobre la gramática.

- **Parámetros**:
- `grammar` - Objeto Grammar
- `conflicts` - (opcional) Array de conflictos detectados
- **Retorna**: Array de Diagnostic con advertencias sobre recursión izquierda, prefijos comunes, y conflictos

---

### ll1.ts

#### buildLL1Table(grammar: Grammar, first: Record>, follow: Record>): LL1Table

Construye la tabla predictiva LL(1).

- **Parámetros**:
- `grammar` - Objeto Grammar
- `first` - Conjuntos FIRST
- `follow` - Conjuntos FOLLOW
- **Retorna**: Tabla LL(1) como `Record>`

#### simulateLL1(grammar: Grammar, table: LL1Table, tokens: string[]): { accepted: boolean; steps: ParserStep[]; tree?: TreeNode }

Simula el parsing LL(1) sobre una cadena de entrada.

- **Parámetros**:
- `grammar` - Objeto Grammar
- `table` - Tabla predictiva LL(1)
- `tokens` - Array de tokens de entrada
- **Retorna**: Objeto con `accepted`, `steps` (traza de ejecución), y `tree` (árbol de derivación)

#### simulateRecursiveDescent(grammar: Grammar, table: LL1Table, tokens: string[])

Igual que simulateLL1 pero formatea las acciones con notación de funciones (para "Descenso recursivo").

- **Parámetros**: Mismos que simulateLL1
- **Retorna**: Mismo formato que simulateLL1 pero con acciones como "Función E(): Aplicar ..."

---

### lr.ts

#### buildLRMachine(grammar: Grammar, mode: LRMode, first: Record>): { states: LRState[]; augmented: Production[] }

Construye el autómata LR (conjunto de estados y transiciones).

- **Parámetros**:
- `grammar` - Objeto Grammar
- `mode` - Modo LR: "lr0" | "slr1" | "lalr1" | "lr1"
- `first` - Conjuntos FIRST
- **Retorna**: Objeto con `states` (array de LRState) y `augmented` (producciones aumentadas)

#### buildLRTable(grammar: Grammar, states: LRState[], augmented: Production[], mode: LRMode, follow: Record>): LRTable

Construye las tablas ACTION y GOTO para parsing LR.

- **Parámetros**:
- `grammar` - Objeto Grammar
- `states` - Estados del autómata
- `augmented` - Producciones aumentadas
- `mode` - Modo LR
- `follow` - Conjuntos FOLLOW
- **Retorna**: LRTable con `action`, `goto`, y `conflicts`

#### simulateLR(grammar: Grammar, table: LRTable, tokens: string[]): { accepted: boolean; steps: ParserStep[]; tree?: TreeNode }

Simula el parsing LR sobre una cadena de entrada.

- **Parámetros**:
- `grammar` - Objeto Grammar
- `table` - Tabla LR (ACTION/GOTO)
- `tokens` - Array de tokens de entrada
- **Retorna**: Objeto con `accepted`, `steps` (traza de ejecución), y `tree` (árbol de derivación)

#### formatItem(grammar: Grammar, item: LRItem, augmented: Production[]): string

Formatea un item LR con el punto (•) en la posición correcta.

- **Parámetros**:
- `grammar` - Objeto Grammar
- `item` - Item LR
- `augmented` - Producciones aumentadas
- **Retorna**: String formateado "A -> •α" o "A -> •α, a" (con lookahead)

---

### analyze.ts

#### analyze(grammarSource: string, inputSource: string, kind: AnalysisKind): AnalysisResult

Punto de entrada principal. Orquesta todo el proceso de análisis.

1. Parsea la gramática
2. Calcula FIRST y FOLLOW
3. Construye la tabla (LL1 o LR según `kind`)
4. Simula el parsing
5. Genera diagnósticos

- **Parámetros**:
- `grammarSource` - Texto con la gramática
- `inputSource` - Cadena de entrada a analizar
- `kind` - Tipo de parser: "recursive" | "ll1" | "lr0" | "slr1" | "lalr1" | "lr1"
- **Retorna**: AnalysisResult completo con tabla, pasos, árbol y diagnósticos

---

## Tests

Los tests están en `scripts/` y utilizan Vitest. La suite cubre:

### Estructura de Tests

```
scripts/
└── parser-engine.test.ts
├── describe("grammar parsing")
│ ├── it("parsea flechas, alternativas, comentarios, epsilon y terminales")
│ ├── it("reporta producciones malformadas y gramáticas vacías")
│ └── it("normaliza whitespace y tokens epsilon")
├── describe("FIRST y FOLLOW")
│ └── it("calcula FIRST y FOLLOW para gramática expression")
├── describe("LL(1) parser")
│ ├── it("acepta expresión LL(1) válida")
│ ├── it("rechaza expresión incompleta")
│ ├── it("acepta expresiones parentizadas")
│ ├── it("reporta conflictos LL(1)")
│ └── it("maneja prefijos comunes en gramática")
├── describe("LR parsers", { each: ["lr0", "slr1", "lalr1", "lr1"] })
│ ├── it("acepta gramática bottom-up canónica")
│ ├── it("rechaza orden de tokens inválido")
│ └── (otros tests específicos por modo)
├── describe("LALR(1) parser specifics")
│ └── it("acepta producciones nullables")
├── describe("ambiguous grammars")
│ ├── it("detecta ambigüedad en gramática if-else")
│ ├── it("detecta recursión izquierda")
│ └── it("detecta conflictos shift-reduce")
└── describe("LR(1) specifics")
├── it("detecta conflicto dangling else")
└── (otros tests específicos)
```

### Categorías de Tests

1. **Parsing de gramáticas**: Validación de formato, normalización de símbolos, detección de errores.
2. **Conjuntos**: Cálculo correcto de FIRST y FOLLOW.
3. **LL(1)**: Aceptación/rechazo, construcción de tabla, conflictos.
4. **LR(0), SLR(1), LALR(1), LR(1)**: Aceptación/rechazo, construcción de autómata, conflictos.
5. **Gramáticas ambiguas**: Detección de conflictos shift-reduce y reduce-reduce.
6. **Casos edge**: Producciones nullables, recursión izquierda, prefijos comunes.

### Ejecutar Tests

```bash
pnpm test
```

## Comandos

Instalar dependencias:

```bash
pnpm install
```

Desarrollo:

```bash
pnpm dev
```

Abrir:

```txt
http://localhost:3000
```

Tests:

```bash
pnpm test
```

Lint:

```bash
pnpm lint
```

Build de producción:

```bash
pnpm build
```

## Despliegue

El proyecto es una app Next.js sin backend externo. Puede desplegarse en Vercel o cualquier plataforma compatible con Next.js.

```bash
pnpm build
pnpm start
```

## Notas

- Los diagnósticos inteligentes son heurísticas locales; no requieren API keys.
- Las gramáticas muy grandes pueden generar muchas tablas y estados; el objetivo principal es uso didáctico para ejemplos de clase.
- El motor de parsing es puro y no tiene dependencias de React, lo que permite su uso independiente en otros contextos.