@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
function getAST(input: string): RecipeASTParse une chaîne source .gram et retourne le nœud racine RecipeAST. Lève une GramParseError en cas de syntaxe invalide.
import { getAST } from '@gram-lang/parser';
const ast = getAST(`
---
title: 'Crêpes'
---
## Pâte
Mélanger @farine{200g} et @lait{200ml}.
`);GramParseError
class GramParseError extends Error {
readonly offset: number;
readonly expected: string;
}Levée par getAST en cas d'erreur de syntaxe.
| Champ | Description |
|---|---|
message | Le texte lisible d'ohm-js (extrait de la source inclus) — peut être affiché tel quel. |
offset | Décalage en caractères dans input où l'échec est survenu. |
expected | Description 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
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.
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.
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 :
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).