Présentation du sous‑ensemble

L’article décrit la création d’un interprète PostScript limité, écrit en JavaScript. Il conserve les concepts fondamentaux du langage : tokens séparés par des espaces, opérateurs, piles d’opérandes et dictionnaire. Les chaînes et la gestion d’erreurs sont volontairement omises pour garder le code compact.

Représentation des valeurs

Les types PostScript sont mappés sur des structures JavaScript simples. Un marqueur unique MARK = {} identifie les débuts de tableau. Les procédures sont encodées comme {t:'proc',v:x}, où v contient la séquence de tokens pré‑analysée. Les fonctions utilitaires suivantes permettent d’interroger ces objets :

proc    = x=>({t:'proc',v:x})
procval = x=>x.v
isproc  = x=>x&&x.t=='proc'
isarray = x=>Array.isArray(x)
isfunc  = x=>'function'==typeof x
isname  = x=>'string'==typeof x&&`/`!=x[0]
litval  = x=>x.slice('/'==x[0])

Cette approche évite la création de classes dédiées et exploite le typage dynamique de JavaScript.

Analyse lexicale et parsing

Le parseur utilise une expression régulière unique pour extraire commentaires, délimiteurs ([]{} ), noms et nombres. Les commentaires (% …) sont filtrés immédiatement. Le code suivant montre le cœur du tokeniseur :

parse = x=>{
  const t=x.trim().match(/(%[^\n]*\n)|(\[|\]|\{|\})|(\/?[^\[\]\{\}\/% \n]+)|(-?\d+)/gm).filter(x=>x[0]!='%')
  const next=()=>t.shift()
  const peek=()=>t[0]
  const isnum=x=>+x==+x
  const rec=()=>peek()=='{'? (next(),seq()) : isnum(peek())? +next() : next()
  const seq=()=>{const r=[];while(peek()&&peek()!='}')r.push(rec());return next(),proc(r)}
  return seq()
}

Le parseur regroupe les tokens entre { et } en une procédure, évitant ainsi d’implémenter les opérateurs de bloc. Le résultat d’un exemple [10 20 30] {2 mul} forall est un objet {t:'proc', v:['[',10,20,30,']',{t:'proc',v:[2,'mul']},'forall']}, ce qui montre la conversion directe des nombres et des opérateurs.

Exécution et piles

Deux tableaux globaux représentent les piles : o=[] pour les opérandes et d=[] pour les dictionnaires. Les primitives de manipulation sont limitées à push, pop, popn (retourne plusieurs valeurs) et popmark (dépile jusqu’au MARK). Le chargement d’un nom recherche la dernière définition dans d :

load = x=>{const v=litval(x);return d.findLast(s=>s.has(v)).get(v)}

L’exécution parcourt chaque token. Si le token est une fonction JavaScript, execfunc consomme le nombre d’arguments indiqué par x.length et pousse le résultat éventuel. Si le token est un nom, execproc récupère la procédure correspondante et l’évalue récursivement. Les opérateurs arithmétiques (add, mul, etc.) sont de simples fonctions pures, ce qui facilite les tests unitaires.

Le principal compromis réside dans l’absence de prise en charge des chaînes, des types de couleur et de la gestion d’erreurs. Le code reste néanmoins fonctionnel pour tout programme PostScript bien formé qui utilise uniquement les opérateurs numériques et de structure de base.