A generic code modification tool.
import { g, parse } from "jsr:@srob/srcm";
const barOrBaz = g.or([/^bar/i, "baz"]);
const grammar = g`Foo is ${barOrBaz}`;
// parse("Plop", grammar); // Throws :
// SyntaxError: Syntax error on line 1, columns 1:
// Plop
// ^Expected one of ["Foo is "]
const $ = parse("Foo is BAR", grammar);
console.log($.text()); // Foo is BAR
// $.findByGrammar(barOrBaz)[0].text("ohno"); // Throws with : Expected one of ["/^bar/i", "baz"]
const $barOrBaz = $.findByGrammar(barOrBaz)[0];
$barOrBaz.text("baz");
console.log($barOrBaz.text()); // baz
console.log($.text()); // Foo is baz
The goal of this project is to allow you to convert any javascript text into a DOM-like Abstract Syntax Tree, using a grammar written in simple javascript.
The module provides two exports :
g
provides utilities for converting convenient javascript objects and values into grammars usable by the internal parser :
g("a"); // => { type: "string", value: "a" }
g("a", { id: "A" }); // => { type: "string", value: "a", id: "A" }
g(["a", "b"]); // => { type: "sequence", value: [ { type: "string", value: "a" }, { type: "string", value: "b" } ] }
g.or(["a", "b"]); // => { type: "choice", value: [ { type: "string", value: "a" }, { type: "string", value: "b" } ] }
Notice that the grammars are themselves plain old js object, the g
helper is not actually required to use the parser.
g
can also be used as a tag function for tagged template litterals:
g`Foo ${bar} baz`
// is actually equivalent to
g(["Foo ", bar, " baz"])
This feature allows to paste full chunks of existing source code in a template litteral and use it as a grammar:
const myFileGrammar = g`<?php
class FooBar
{
}
`;
...then replace directly in-place some code by sub-grammars:
const phpClassName = g(/^[\w][\w\d_]+/);
const myFileGrammar = g`<?php
class ${phpClassName}
{
}
`;
Being simple js objects, grammars can be manipulated to produce interesting results. For example :
const digits = g(/^[\d]+/);
const expression = g.or([digits]);
const addition = g`${expr}+${expr}`;
const multiplication = g`${expr}*${expr}`;
// Here, expression is: { type: "choice", value: [ { type: "regexp", value: /^[\d]+/ } ] }
// Create a recursive grammar by adding addition and multiplication as possible children of expression:
expression.value.push(addition, multiplication);
Note that the expression
grammar now has an infinite depth, due to the cyclic references. This is not a problem for the internal parser, as it uses a bottom-up parsing algorithm.
The parse()
function converts a given string into a pseudo-DOM structure :
const $ = parse("Foo", g("Foo"));
console.log($);
/* =>
Node {
grammar: { type: "string", value: "Foo" },
parent: null,
prev: null,
next: null,
children: [],
textContent: "Foo",
parse: [Function (anonymous)]
}
*/
The Node
class provides features inspired by the DOM structure used in browser (but really simpler!) to manipulate the generated Abstract Syntax Tree.
TODO Node methods
Execute this in a shell at your project root :
pnpm add srcm
yarn add srcm
npm i --save srcm
Execute this in a shell at your project root :
deno add @srob/srcm
or skip the install step and use it in your source :
import { g, parse } from "jsr:@srob/srcm";
Simon Robert
Copyright © 2024 Simon Robert
Released under the MIT license.