An object-oriented language for parsing and pattern matching based on parsing expression grammars
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Grammar parsing capabilities with support for incremental parsing, detailed error reporting, and trace visualization.
import { grammar } from "ohm-js";For TypeScript:
import { grammar, Grammar, MatchResult, Matcher, Interval, RuleInfo, LineAndColumnInfo } from "ohm-js";Core interface for compiled grammar objects with parsing and semantic capabilities.
/**
* An Ohm Grammar compiled from source code
*/
interface Grammar {
/** Name of the grammar */
name: string;
/** Parent grammar if this extends another grammar */
superGrammar: Grammar;
/** Dictionary of rule definitions in this grammar */
rules: {[ruleName: string]: RuleInfo};
/** Return true if the grammar is a built-in grammar, otherwise false */
isBuiltIn(): boolean;
/**
* Try to match input with this grammar, returning a MatchResult
* @param input - String to parse
* @param startRule - Optional rule name to start matching from
* @returns MatchResult indicating success or failure
*/
match(input: string, startRule?: string): MatchResult;
/**
* Create a new Matcher object which supports incrementally matching
* this grammar against a changing input string
* @returns Matcher instance for incremental parsing
*/
matcher(): Matcher;
/**
* Like match() except returns a trace object whose toString() returns
* a summary of each parsing step useful for debugging
* @param input - String to parse
* @param startRule - Optional rule name to start matching from
* @returns Trace object with debugging information
*/
trace(input: string, startRule?: string): Object;
/**
* Create a new Semantics object for this Grammar
* @returns Empty semantics instance
*/
createSemantics(): Semantics;
/**
* Create a new Semantics object for this Grammar that inherits all
* of the operations and attributes in superSemantics
* @param superSemantics - Parent semantics to inherit from
* @returns Extended semantics instance
*/
extendSemantics(superSemantics: Semantics): Semantics;
}Usage Examples:
import { grammar } from "ohm-js";
const g = grammar(`
Calculator {
expr = number ("+" number)*
number = digit+
}
`);
// Basic matching
const match = g.match("2+3+4");
if (match.succeeded()) {
console.log("Parsing succeeded!");
}
// Match with specific start rule
const numberMatch = g.match("42", "number");
// Check if grammar is built-in
console.log("Is built-in:", g.isBuiltIn()); // falseResult object returned by grammar match operations containing success/failure status and error information.
/**
* Result of Grammar#match operation
*/
interface MatchResult {
/**
* True iff match succeeded
*/
succeeded(): boolean;
/**
* True iff match did not succeed
*/
failed(): boolean;
/**
* If match failed contains an error message indicating where and
* why the match failed. This message is suitable for end users of a
* language (i.e., people who do not have access to the grammar source).
*/
message?: string;
/**
* If match failed contains an abbreviated version of this.message that
* does not include an excerpt from the invalid input.
*/
shortMessage?: string;
/**
* If this MatchResult is a failure, returns an Interval indicating
* the position of the rightmost failure.
*/
getInterval(): Interval;
}Usage Examples:
const match = grammar.match("invalid input");
if (match.failed()) {
console.error("Parse error:", match.message);
console.error("Short error:", match.shortMessage);
const errorPos = match.getInterval();
console.error("Error at position:", errorPos.startIdx);
}Matcher objects support incremental parsing for use in editors and IDEs where the input changes frequently.
/**
* Matcher objects are used to incrementally match a changing input
* against a Grammar, e.g. in an editor or IDE.
*/
interface Matcher {
/** The grammar this matcher uses */
grammar: Grammar;
/**
* Return the current input string
*/
getInput(): string;
/**
* Set the input string to `str`
* @param str - New input string
*/
setInput(str: string): void;
/**
* Edit the current input string, replacing the characters between
* `startIdx` and `endIdx` with `str`
* @param startIdx - Start position of replacement
* @param endIdx - End position of replacement
* @param str - Replacement string
* @returns This matcher for chaining
*/
replaceInputRange(startIdx: number, endIdx: number, str: string): Matcher;
/**
* Like Grammar#match, but operates incrementally
* @param optStartRule - Optional start rule name
* @returns MatchResult from current input
*/
match(optStartRule?: string): MatchResult;
/**
* Like Grammar#trace, but operates incrementally
* @param optStartRule - Optional start rule name
* @returns Trace object for debugging
*/
trace(optStartRule?: string): Object;
}Usage Examples:
const matcher = grammar.matcher();
// Set initial input
matcher.setInput("2 + 3");
let result = matcher.match();
// Edit the input incrementally
matcher.replaceInputRange(0, 1, "5"); // Changes "2 + 3" to "5 + 3"
result = matcher.match();
// Get current input
console.log("Current input:", matcher.getInput());Metadata about individual grammar rules.
interface RuleInfo {
/** Parsing expression body of the rule */
body: PExpr;
/** Formal parameter names for parameterized rules */
formals: string[];
/** Description string from grammar source */
description: string;
/** Source interval where rule is defined */
source: Interval;
}Represents a subset of a string with position and content information.
/**
* An Interval represents a subset of a string
*/
interface Interval {
/** The string containing the interval */
sourceString: string;
/** The start index of the interval in `sourceString` */
startIdx: number;
/** The end index of the interval in `sourceString` */
endIdx: number;
/** Contents of interval */
contents: string;
/** Returns a new Interval at the start of this one */
collapsedLeft(): Interval;
/** Returns a new Interval at the end of this one */
collapsedRight(): Interval;
/** Returns a new Interval that covers this and all argument Intervals */
coverageWith(...intervals: Interval[]): Interval;
/** Returns a nicely-formatted string describing the start of the Interval */
getLineAndColumnMessage(): string;
/** Returns structure with line and column number information */
getLineAndColumn(): LineAndColumnInfo;
/** Returns array of intervals representing the difference operation */
minus(that: Interval): Interval[];
/** Returns a new Interval relative to another interval */
relativeTo(that: Interval): Interval;
/** Returns a new Interval with whitespace trimmed from both ends */
trimmed(): Interval;
/** Returns a new Interval with given length at startIdx + offset */
subInterval(offset: number, len: number): Interval;
}
interface LineAndColumnInfo {
offset: number;
lineNum: number;
colNum: number;
line: string;
prevLine: string;
nextLine: string;
toString(...ranges: number[][]): string;
}Ohm provides detailed tracing capabilities for debugging grammar matching:
// Get detailed trace information
const trace = grammar.trace("invalid input");
console.log(trace.toString()); // Prints step-by-step parsing traceMatch failures provide human-readable error messages:
const match = grammar.match("2 +"); // Incomplete expression
if (match.failed()) {
// Full error message with context
console.error(match.message);
// Shorter error without input excerpt
console.error(match.shortMessage);
// Get error position for highlighting
const errorInterval = match.getInterval();
console.error(`Error at line ${errorInterval.getLineAndColumn().lineNum}`);
}Install with Tessl CLI
npx tessl i tessl/npm-ohm-js