Skip to content

Commit

Permalink
Merge 97b27de into 10ac76e
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Mock committed Jul 4, 2017
2 parents 10ac76e + 97b27de commit 059d245
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 059d245

Please sign in to comment.