Skip to content

Commit

Permalink
WIP this probably works now?
Browse files Browse the repository at this point in the history
My only concern is that state may not be correctly piped through all the combinator functions. This would require a lot of extra testing to validate. The good part is it wasn't much work to make the test suite pass, and I ran this version of the library against three of my programming languages, including Squiggle, and it appeared to function correctly still.
  • Loading branch information
Brian Mock committed Jul 4, 2017
1 parent 10ac76e commit 97b27de
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 157 deletions.
104 changes: 27 additions & 77 deletions examples/python-ish.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,96 +7,46 @@ let P = require('..');

///////////////////////////////////////////////////////////////////////

// LIMITATIONS: Python allows not only multiline blocks, but inline blocks too.
//
// if x == y: print("nice")
//
// vs.
//
// if x == y:
// print("nice")
//
// This parser only supports the multiline indented form.

// NOTE: This is a hack and is not recommended. Maintaining state throughout
// Parsimmon parsers is not reliable since backtracking may occur, leaving your
// state inaccurate. See the relevant GitHub issue for discussion.
//
// https://github.com/jneen/parsimmon/issues/158
//
function indentPeek() {
return indentStack[indentStack.length - 1];
}

let indentStack = [0];

let Pythonish = P.createLanguage({
// If this were actually Python, "Block" wouldn't be a statement on its own,
// but rather "If" and "While" would be statements that used "Block" inside.
Statement: r =>
P.alt(r.ExpressionStatement, r.Block),
Program: r =>
r.Statement.many().trim(r._).node('Program'),

// Just a simple `foo()` style function call.
FunctionCall: () =>
P.regexp(/[a-z]+/).skip(P.string('()'))
.node('FunctionCall')
.desc('a function call'),
Statement: r =>
P.alt(r.Call, r.Block),

// To make it a statement we just need a newline afterward.
ExpressionStatement: r =>
r.FunctionCall.skip(P.string('\n')),
Call: r =>
P.letter.skip(P.string('()')).skip(r.End).node('Call'),

// The general idea of this is to assume there's "block:" on its own line,
// then we capture the whitespace used to indent the first statement of the
// block, and require that every other statement has the same exact string of
// indentation in front of it.
Block: r =>
P.seqObj(
P.string('block:'),
P.string('\n'),
['indent', P.regexp(/[ ]+/)],
['statement', r.Statement]
).chain(args => {
// `.chain` is called after a parser succeeds. It returns the next parser
// to use for parsing. This allows subsequent parsing to be dependent on
// previous text.
let {indent, statement} = args;
let indentSize = indent.length;
let currentSize = indentPeek();
// Indentation must be deeper than the current block context. Otherwise
// you could indent *less* for a block and it would still work. This is
// not how any language I know of works.
if (indentSize <= currentSize) {
return P.fail('at least ' + currentSize + ' spaces');
}
indentStack.push(indentSize);
return P.string(indent)
.then(r.Statement)
.many()
.map(statements => {
indentStack.pop();
return [statement].concat(statements);
});
})
.node('Block'),
r.End,
P.indentMore,
['first', r.Statement],
['rest', P.indentSame.then(r.Statement).many()],
P.indentLess
).map(args => {
let {first, rest} = args;
let statements = [first, ...rest];
return {statements};
}).node('Block'),

_: () => P.optWhitespace,
NL: () => P.string('\n'),
End: r => P.alt(r.NL, P.eof),
});

///////////////////////////////////////////////////////////////////////

let text = `\
z()
block:
a()
b()
a()
b()
block:
c()
block:
d()
e()
f()
block:
g()
h()
i()
j()
d()
e()
`;

function prettyPrint(x) {
Expand All @@ -105,5 +55,5 @@ function prettyPrint(x) {
console.log(s);
}

let ast = Pythonish.Block.tryParse(text);
let ast = Pythonish.Program.tryParse(text);
prettyPrint(ast);

0 comments on commit 97b27de

Please sign in to comment.