Skip to content

@gram-lang/parser

Transforme le texte source .gram en un Arbre Syntaxique Abstrait (AST) fait d'objets simples. C'est la seule étape du pipeline qui peut échouer sur une entrée malformée — tous les autres paquets font confiance au fait que s'ils ont reçu un AST, il est structurellement valide.

getAST

typescript
function getAST(input: string): RecipeAST

Parse une chaîne source .gram et retourne le nœud racine RecipeAST. Lève une GramParseError en cas de syntaxe invalide.

typescript
import { getAST } from '@gram-lang/parser';

const ast = getAST(`
---
title: 'Crêpes'
---

## Pâte

Mélanger @farine{200g} et @lait{200ml}.
`);

GramParseError

typescript
class GramParseError extends Error {
  readonly offset: number;
  readonly expected: string;
}

Levée par getAST en cas d'erreur de syntaxe.

ChampDescription
messageLe texte lisible d'ohm-js (extrait de la source inclus) — peut être affiché tel quel.
offsetDécalage en caractères dans input où l'échec est survenu.
expectedDescription de ce que le parser attendait à cet endroit.

offset et expected sont les parties portables et structurées de l'échec — utiles pour les intégrations éditeur (soulignement, correctifs rapides) qui ne veulent pas parser le texte d'ohm.

Types de nœuds AST

Chaque nœud a un discriminant type: ASTNodeType et un loc: { start, end } optionnel (décalages en caractères dans la source, présent sur la plupart mais pas tous les types de nœuds — voir les interfaces ci-dessous).

Valeur
Recipe
Section
Step
Comment
Text
IntermediateDecl
RelativeQuantity
TextQuantity
Quantity
Ingredient
Composite
Cookware
Reference
Timer
Temperature
Alternative

Interfaces clés

typescript
interface RecipeAST {
  type: ASTNodeType.Recipe;
  meta: Meta;              // Frontmatter parsé (title, date, tags, densities, ...)
  children: (SectionAST | StepAST | CommentAST)[]; // Sections, ou étapes/commentaires de premier niveau
}

interface SectionAST {
  type: ASTNodeType.Section;
  title: string | null;
  retroPlanning?: RetroPlanningAST | null;
  intermediateDecl?: IntermediateDecl | null;
  children: (StepAST | CommentAST)[];
  loc?: Location;
}

interface RetroPlanningAST {
  raw: string;             // ex : "-2h", tel qu'écrit
  sign: 1 | -1;
  value: number | null;    // null si aucun nombre n'a pu être extrait (ex : texte libre)
  unit: string | null;     // null si aucune unité trouvée ; sinon le token brut tel
                           // qu'écrit (kitchen le résout/valide ensuite contre d/h/min)
}

Pour ## Pâte Feuilletée ~{-2h}, retroPlanning vaut { raw: "-2h", sign: -1, value: 2, unit: "h" }. Un texte libre comme ~{la veille} continue de parser (le parser ne lève jamais d'erreur pour ça — voir Temps & Planification pour le pourquoi), mais produit { raw: "la veille", sign: 1, value: null, unit: null } ; c'est @gram-lang/kitchen qui signale ce cas comme invalide (MISSING_UNIT) lors de la compilation.

typescript
interface StepAST {
  type: ASTNodeType.Step;
  action?: string | null;              // ex : "Mélanger" issu de "[Mélanger] ..."
  children: (TextAST | IngredientAST | CookwareAST | TimerAST | TemperatureAST
    | ReferenceAST | AlternativeAST | IntermediateDecl | CommentAST)[];
  loc?: Location;
}

type Modifier = "?" | "-" | "*" | "&" | "="; // optionnel, masqué, % boulanger, référence, fixe

interface IngredientAST {
  type: ASTNodeType.Ingredient;
  name: string;
  modifiers: Modifier[];               // Modificateurs sous forme de sigles présents sur l'ingrédient
  quantity: QuantityAST | RelativeQuantityAST | TextQuantityAST | null;
  alias?: string | null;
  preparation?: string | null;
  composite?: CompositeAST | null;     // défini pour les ingrédients composites "<@parent"
  loc?: Location;
}

interface QuantityAST {
  type: ASTNodeType.Quantity;
  value?: QuantityValueAST;            // Union discriminée : SingleQuantityAST | FractionQuantityAST | RangeQuantityAST
  unit?: string | null;
  fixed: boolean;                      // true pour le modificateur "@=" (jamais mis à l'échelle)
}

interface RelativeQuantityAST {
  type: ASTNodeType.RelativeQuantity;
  percent: number;
  target: string;
  referenceType: "variable" | "ingredient"; // "&nom" vs "@nom"
}

Les autres interfaces de nœuds (CookwareAST, ReferenceAST, TimerAST, TemperatureAST, CommentAST, AlternativeAST, IntermediateDecl, TextQuantityAST) suivent le même schéma — voir packages/parser/src/types.ts pour la liste exhaustive.

Type guards

13 fonctions de garde de type (« type guards ») sont exportées pour restreindre en toute sécurité des entrées ASTNode | StepAST | ... | null | undefined sans vérifications manuelles de .type :

isIngredient, isCookware, isTimer, isTemperature, isReference, isIntermediateDecl, isAlternative, isComment, isStep, isSection, isQuantity, isTextQuantity, isRelativeQuantity.

typescript
import { getAST, isIngredient, isTimer } from '@gram-lang/parser';

const ast = getAST(source);

for (const section of ast.children) {
  for (const step of section.children) {
    if (step.type !== 'Step') continue;
    for (const node of step.children) {
      if (isIngredient(node)) {
        console.log('Ingrédient :', node.name);
      } else if (isTimer(node)) {
        console.log('Minuteur :', node.name, node.quantity);
      }
    }
  }
}

Coloration syntaxique : @gram-lang/parser/textmate

Un export de sous-chemin fournit la grammaire TextMate utilisée par l'extension VS Code et par les blocs de code Shiki de cette documentation :

typescript
import gramGrammar from '@gram-lang/parser/textmate' with { type: 'json' };

Il résout vers un fichier .tmLanguage.json — passez-le directement à n'importe quel colorateur syntaxique compatible avec les grammaires TextMate (Shiki, Monaco, VS Code).