Arcsecond
✨Zero Dependency Parser Combinator Library for JS Based on Haskell's Parsec
Install / Use
/learn @francisrstokes/ArcsecondREADME
Arcsecond
<img src="./logo.png">Arcsecond is a zero-dependency, Fantasy Land compliant JavaScript Parser Combinator library largely inspired by Haskell's Parsec.
The arcsecond-binary peer library includes parsers specifically for working with binary data.
-
-
<details>
<summary>Click to expand</summary>
- Parser Methods
- Functions
- setData
- withData
- mapData
- getData
- coroutine
- char
- anyChar
- str
- digit
- digits
- letter
- letters
- whitespace
- optionalWhitespace
- peek
- anyOfString
- regex
- sequenceOf
- namedSequenceOf
- choice
- lookAhead
- sepBy
- sepBy1
- exactly
- many
- many1
- between
- everythingUntil
- everyCharUntil
- anythingExcept
- anyCharExcept
- possibly
- startOfInput
- endOfInput
- skip
- pipeParsers
- composeParsers
- takeRight
- takeLeft
- recursiveParser
- tapParser
- decide
- mapTo
- errorMapTo
- fail
- succeedWith
- either
- toPromise
- toValue
- parse
Release Notes
Since version 2.0.0, the release notes track changes to arcsecond.
Installation
npm i arcsecond
Tutorials
The tutorials provide a practical introduction to many of the concepts in arcsecond, starting from the most basic foundations and working up to more complex topics.
- 1. Parsing weather data
- 2. Extracting useful information
- 3. Error handling
- 4. Building utility parsers
- 5. Recursive Structures
- 6. Debugging
- 7. Stateful parsing
Usage
You can use ES6 imports or CommonJS requires.
const {char} = require('arcsecond');
const parsingResult = char('a').fork(
// The string to parse
'abc123',
// The error handler (you can also return from this function!)
(error, parsingState) => {
const e = new Error(error);
e.parsingState = parsingState;
throw e;
},
// The success handler
(result, parsingState) => {
console.log(`Result: ${result}`);
return result;
}
);
Running the examples
git clone git@github.com:francisrstokes/arcsecond.git
cd arcsecond
npm i
# json example
node -r esm examples/json/json.js
The examples are built as es6 modules, which means they need node to be launched with the -r esm require flag, which allows import and export statements to be used.
API
Methods
.run
.run is a method on every parser, which takes input (which may be a string, TypedArray, ArrayBuffer, or DataView), and returns the result of parsing the input using the parser.
Example
str('hello').run('hello')
// -> {
// isError: false,
// result: "hello",
// index: 5,
// data: null
// }
.fork
The .fork method is similar to .run. It takes input (which may be a string, TypedArray, ArrayBuffer, or DataView), an error transforming function and a success transforming function, and parses the input. If parsing was successful, the result is transformed using the success transforming function and returned. If parsing was not successful, the result is transformed using the error transforming function and returned.
Example
str('hello').fork(
'hello',
(errorMsg, parsingState) => {
console.log(errorMsg);
console.log(parsingState);
return "goodbye"
},
(result, parsingState) => {
console.log(parsingState);
return result;
}
);
// [console.log] Object {isError: false, error: null, target: "hello", data: null, index: 5, …}
// -> "hello"
str('hello').fork(
'farewell',
(errorMsg, parsingState) => {
console.log(errorMsg);
console.log(parsingState);
return "goodbye"
},
(result, parsingState) => {
console.log(parsingState);
return result;
}
);
// [console.log] ParseError (position 0): Expecting string 'hello', got 'farew...'
// [console.log] Object {isError: true, error: "ParseError (position 0): Expecting string 'hello',…", target: "farewell", data: null, index: 0, …}
// "goodbye"
.map
.map takes a function and returns a parser does not consume input, but instead runs the provided function on the last matched value, and set that as the new last matched value. This method can be used to apply structure or transform the values as they are being parsed.
Example
const newParser = letters.map(x => ({
matchType: 'string',
value: x
});
newParser.run('hello world')
// -> {
// isError: false,
// result: {
// matchType: "string",
// value: "hello"
// },
// index: 5,
// data: null
// }
.chain
.chain takes a function which recieves the last matched value and should return a parser. That parser is then used to parse the following input, forming a chain of parsers based on previous input. .chain is the fundamental way of creating contextual parsers.
Example
const lettersThenSpace = sequenceOf([
letters,
char(' ')
]).map(x => x[0]);
const newParser = lettersThenSpace.chain(matchedValue => {
switch (matchedValue) {
case 'number': return digits;
case 'string': return letters;
case 'bracketed': return sequenceOf([
char('('),
letters,
char(')')
]).map(values => values[1]);
default: return fail('Unrecognised input type');
}
});
newParser.run('string Hello')
// -> {
// isError: false,
// result: "Hello",
// index: 12,
// data: null
// }
newParser.run('number 42')
// -> {
// isError: false,
// result: "42",
// index: 9,
// data: null
// }
newParser.run('bracketed (arcsecond)')
// -> {
// isError: false,
// result: "arcsecond",
// index: 21,
// data: null
// }
newParser.run('nope nothing')
// -> {
// isError: true,
// error: "Unrecognised input type",
// index: 5,
// data: null
// }
.mapFromData
.mapFromData is almost the same as .map, except the function which it is passed also has access to the internal state data, and can thus transform values based on this data.
Example
const parserWithData = withData(letters.mapFromData(({result, data}) => ({
matchedValueWas: result,
internalDataWas: data
})));
parserWithData(42).run('hello');
// -> {
// isError: false,
// result: {
// matchedValueWas: "hello",
// internalDataWas: 42
// },
// index: 5,
// data: 42
// }
.chainFromData
.chainFromData is almost the same as .chain, except the function which it is passed also has access to the internal state data, and can choose how parsing continues based on this data.
Example
const lettersThenSpace = sequenceOf([
letters,
char(' ')
]).map(x => x[0]);
const parser = withData(lettersThenSpace.chainFromData(({result, data}) => {
if (data.bypassNormalApproach) {
return digits;
}
return letters;
}));
parser({ bypassNormalApproach: false }).run('hello world');
// -> {
// isError: false,
// result: "world",
// index: 11,
// data: { bypassNormalApproach: false }
// }
parser({ bypassNormalApproach: true }).run('hello world');
// -> {
// isError: true,
// error: "ParseError (position 6): Expecting digits",
// index: 6,
// data: { bypassNormalApproach: true }
// }
.errorMap
.errorMap is like .map but it transforms the error value. The function passed to `.er
Related Skills
node-connect
339.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
339.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.8kCommit, push, and open a PR
