Skip to content

Commit

Permalink
Merge pull request #185 from jneen/topic/linter-example
Browse files Browse the repository at this point in the history
Adds linter example
  • Loading branch information
Brian Mock authored Jul 1, 2017
2 parents 4bf90e1 + e19f693 commit bff2ae1
Showing 1 changed file with 160 additions and 0 deletions.
160 changes: 160 additions & 0 deletions examples/linter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
'use strict';

// Run me with Node to see my output!

let util = require('util');
let P = require('..');

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

// -*- Parser -*-

let Lang = P.createLanguage({

s0: () => P.regexp(/[ ]*/),
s1: () => P.regexp(/[ ]+/),
Whitespace: () => P.regexp(/[ \n]*/),
NotNewline: () => P.regexp(/[^\n]*/),
Comma: () => P.string(','),
Comment: r => r.NotNewline.wrap(P.string('//'), P.string('\n')),
End: r => P.alt(P.string(';'), r._, P.string('\n'), P.eof),
_: r => r.Comment.sepBy(r.Whitespace).trim(r.Whitespace),

Program: r =>
r.Statement
.many()
.trim(r._)
.node('Program'),

Statement: r =>
P.alt(
r.Declaration,
r.Assignment,
r.Call
),

Declaration: r =>
P.seqObj(
P.string('var'),
r.s1,
['identifier', r.Identifier],
r.s0,
P.string('='),
r.s0,
['initialValue', r.Expression],
r.End
).node('Declaration'),

Assignment: r =>
P.seqObj(
['identifier', r.Identifier],
r.s0,
P.string('='),
r.s0,
['newValue', r.Expression],
r.End
).node('Assignment'),

Call: r =>
P.seqObj(
['function', r.Expression],
P.string('('),
['arguments', r.Expression.trim(r._).sepBy(r.Comma)],
P.string(')')
).node('Call'),

Expression: r =>
P.alt(r.Number, r.Reference),

Number: () => P.regexp(/[0-9]+/).map(Number).node('Number'),
Identifier: () => P.regexp(/[a-z]+/).node('Identifier'),
Reference: r => r.Identifier.node('Reference'),

});

// -*- Linter -*-

// NOTE: This simplified language only has global scope. Most real languages
// have layers of scope, so your scope data structure will need to be something
// more complicated than just an object keeping track of variable names.

let scope = null;
let messages = null;

function noLint() {
// This function intentionally left blank
}

let lintHelpers = {

Program(node) {
node.value.forEach(lint_);
},

Declaration(node) {
scope[node.value.identifier.value] = true;
lint_(node.value.initialValue);
},

Reference(node) {
let name = node.value.value;
if (!scope.hasOwnProperty(name)) {
messages.push('undeclared variable ' + name);
}
},

Assignment(node) {
lint_(node.value.newValue);
},

Call(node) {
lint_(node.value.function);
node.value.arguments.forEach(lint_);
},

Number: noLint,

};

function lint_(node) {
if (!node) {
throw new TypeError('not an AST node: ' + node);
}
if (!lintHelpers.hasOwnProperty(node.name)) {
throw new TypeError('no lint helper for ' + node.name);
}
return lintHelpers[node.name](node);
}

function lint(node) {
scope = {};
messages = [];
lint_(node);
return messages;
}

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

let text = `\
// nice stuff
var x = 1 // declare this one
x = 2
// Notice this variable is undeclared.
y = 3
// And f is undeclared too...
f(x, y, 3)
`;

function prettyPrint(x) {
let opts = {depth: null, colors: 'auto'};
let s = util.inspect(x, opts);
console.log(s);
}

let ast = Lang.Program.tryParse(text);
let linterMessages = lint(ast);
prettyPrint(linterMessages);
prettyPrint(ast);

0 comments on commit bff2ae1

Please sign in to comment.