mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into auth
Conflicts: packages/livedata/livedata_server.js
This commit is contained in:
58
History.md
58
History.md
@@ -1,6 +1,64 @@
|
||||
|
||||
## vNEXT
|
||||
|
||||
## v0.4.1
|
||||
|
||||
* New `email` smart package, with [`Email.send`](http://docs.meteor.com/#email)
|
||||
API.
|
||||
|
||||
* Upgrade Node from 0.6.17 to 0.8.8, as well as many Node modules in the dev
|
||||
bundle; those that are user-exposed are:
|
||||
* coffee-script: 1.3.3 (from 1.3.1)
|
||||
* stylus: 0.29.0 (from 0.28.1)
|
||||
* nib: 0.8.2 (from 0.7.0)
|
||||
|
||||
* All publicly documented APIs now use `camelCase` rather than
|
||||
`under_scores`. The old spellings continue to work for now. New names are:
|
||||
- `Meteor.isClient`/`isServer`
|
||||
- `this.isSimulation` inside a method invocation
|
||||
- `Meteor.deps.Context.onInvalidate`
|
||||
- `Meteor.status().retryCount`/`retryTime`
|
||||
|
||||
* Spark improvements
|
||||
* Optimize selector matching for event maps.
|
||||
* Fix `Spark._currentRenderer` behavior in timer callbacks.
|
||||
* Fix bug caused by interaction between `Template.foo.preserve` and
|
||||
`{{#constant}}`. #323
|
||||
* Allow `{{#each}}` over a collection of objects without `_id`. #281
|
||||
* Spark now supports Firefox 3.6.
|
||||
* Added a script to build a standalone spark.js that does not depend on
|
||||
Meteor (it depends on jQuery or Sizzle if you need IE7 support,
|
||||
and otherwise is fully standalone).
|
||||
|
||||
* Database writes from within `Meteor.setTimeout`/`setInterval`/`defer` will be
|
||||
batched with other writes from the current method invocation if they start
|
||||
before the method completes.
|
||||
|
||||
* Make `Meteor.Cursor.forEach` fully synchronous even if the user's callback
|
||||
yields. #321.
|
||||
|
||||
* Recover from exceptions thrown in `Meteor.publish` handlers.
|
||||
|
||||
* Upgrade bootstrap to version 2.1.1. #336, #337, #288, #293
|
||||
|
||||
* Change the implementation of the `meteor deploy` password prompt to not crash
|
||||
Emacs M-x shell.
|
||||
|
||||
* Optimize `LocalCollection.remove(id)` to be O(1) rather than O(n).
|
||||
|
||||
* Optimize client-side database performance when receiving updated data from the
|
||||
server outside of method calls.
|
||||
|
||||
* Better error reporting when a package in `.meteor/packages` does not exist.
|
||||
|
||||
* Better error reporting for coffeescript. #331
|
||||
|
||||
* Better error handling in `Handlebars.Exception`.
|
||||
|
||||
|
||||
Patches contributed by GitHub users fivethirty, tmeasday, and xenolf.
|
||||
|
||||
|
||||
## v0.4.0
|
||||
|
||||
* Merge Spark, a new live page update engine
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
meteor (0.4.0-1) unstable; urgency=low
|
||||
meteor (0.4.1-1) unstable; urgency=low
|
||||
|
||||
* Automated debian build.
|
||||
|
||||
|
||||
@@ -17,11 +17,5 @@ override_dh_prep:
|
||||
tar -C debian/tmp/usr/lib -xzf $(TARBALL)
|
||||
echo -n 'deb' > debian/tmp/usr/lib/meteor/.package_stamp
|
||||
|
||||
# node fibers distributes copies of the library pre-compiled for many
|
||||
# different architectures. This confuses shlibdeps. Just ignore the
|
||||
# fibers library.
|
||||
override_dh_shlibdeps:
|
||||
dh_shlibdeps -Xfibers.node
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
BUNDLE_VERSION=0.2.2
|
||||
BUNDLE_VERSION=0.2.3
|
||||
UNAME=$(uname)
|
||||
ARCH=$(uname -m)
|
||||
|
||||
@@ -182,7 +183,6 @@ npm install mongodb@1.1.5
|
||||
npm install uglify-js@1.3.3
|
||||
npm install clean-css@0.6.0
|
||||
npm install progress@0.0.5
|
||||
npm install fibers@0.6.9
|
||||
npm install useragent@1.1.0
|
||||
npm install request@2.11.0
|
||||
npm install http-proxy@0.8.2
|
||||
@@ -201,6 +201,19 @@ git clone http://github.com/akdubya/rbytes.git
|
||||
npm install sockjs@0.3.1
|
||||
rm -rf rbytes
|
||||
|
||||
npm install fibers@0.6.9
|
||||
# Fibers ships with compiled versions of its C code for a dozen platforms. This
|
||||
# bloats our dev bundle, and confuses dpkg-buildpackage and rpmbuild into
|
||||
# thinking that the packages need to depend on both 32- and 64-bit versions of
|
||||
# libstd++. Remove all the ones other than our architecture. (Expression based
|
||||
# on build.js in fibers source.)
|
||||
FIBERS_ARCH=$(node -p -e 'process.platform + "-" + process.arch + "-v8-" + /[0-9]+\.[0-9]+/.exec(process.versions.v8)[0]')
|
||||
cd fibers/bin
|
||||
mv $FIBERS_ARCH ..
|
||||
rm -rf *
|
||||
mv ../$FIBERS_ARCH .
|
||||
cd ../..
|
||||
|
||||
|
||||
cd "$DIR"
|
||||
curl "$MONGO_URL" | tar -xz
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
## example.
|
||||
|
||||
URLBASE="https://d3sqy0vbqsdhku.cloudfront.net"
|
||||
VERSION="0.4.0"
|
||||
VERSION="0.4.1"
|
||||
PKGVERSION="${VERSION}-1"
|
||||
|
||||
UNAME=`uname`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "0.4.0",
|
||||
"deb_version": "0.4.0-1",
|
||||
"rpm_version": "0.4.0-1",
|
||||
"version": "0.4.1",
|
||||
"deb_version": "0.4.1-1",
|
||||
"rpm_version": "0.4.1-1",
|
||||
"urlbase": "https://d3sqy0vbqsdhku.cloudfront.net"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Summary: Meteor platform and JavaScript application server
|
||||
Vendor: Meteor
|
||||
Name: meteor
|
||||
Version: 0.4.0
|
||||
Version: 0.4.1
|
||||
Release: 1
|
||||
License: MIT
|
||||
Group: Networking/WWW
|
||||
|
||||
@@ -12,4 +12,4 @@ fi
|
||||
|
||||
cd "$ORIGDIR"
|
||||
export NODE_PATH="$TOPDIR/dev_bundle/lib/node_modules"
|
||||
exec "$TOPDIR/dev_bundle/bin/node" $*
|
||||
exec "$TOPDIR/dev_bundle/bin/node" "$@"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
exports.CURRENT_VERSION = "0.4.0";
|
||||
exports.CURRENT_VERSION = "0.4.1";
|
||||
|
||||
var fs = require("fs");
|
||||
var http = require("http");
|
||||
|
||||
@@ -2,7 +2,7 @@ try {
|
||||
// XXX can't get this from updater.js because in 0.3.7 and before the
|
||||
// updater didn't have the right NODE_PATH set. At some point we can
|
||||
// remove this and just use updater.CURRENT_VERSION.
|
||||
var VERSION = "0.4.0";
|
||||
var VERSION = "0.4.1";
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
@@ -28,6 +28,7 @@ put on the screen.
|
||||
});
|
||||
}
|
||||
|
||||
{{> api_box absoluteUrl}}
|
||||
|
||||
<h2 id="publishandsubscribe"><span>Publish and subscribe</span></h2>
|
||||
|
||||
|
||||
@@ -24,6 +24,36 @@ Template.api.startup = {
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.absoluteUrl = {
|
||||
id: "meteor_absoluteurl",
|
||||
name: "Meteor.absoluteUrl([path], [options])",
|
||||
locus: "Anywhere",
|
||||
descr: ["Generate an absolute URL pointing to the application. The server "
|
||||
+ "reads from the `ROOT_URL` environment variable to determine "
|
||||
+ "where it is running. This is taken care of automatically for "
|
||||
+ "apps deployed with `meteor deploy`, but must be provided when "
|
||||
+ "using `meteor bundle`."],
|
||||
args: [
|
||||
{name: "path",
|
||||
type: "String",
|
||||
descr: 'A path to append to the root URL. Do not include a leading "`/`".'
|
||||
}
|
||||
],
|
||||
options: [
|
||||
{name: "secure",
|
||||
type: "Boolean",
|
||||
descr: "Create an HTTPS URL."
|
||||
},
|
||||
{name: "replaceLocalhost",
|
||||
type: "Boolean",
|
||||
descr: "Replace localhost with 127.0.0.1. Useful for services that don't recognize localhost as a domain name."},
|
||||
{name: "rootUrl",
|
||||
type: "String",
|
||||
descr: "Override the default ROOT_URL from the server environment. For example: \"`http://foo.example.com`\""
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.publish = {
|
||||
id: "meteor_publish",
|
||||
name: "Meteor.publish(name, func)",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</div>
|
||||
<div id="main">
|
||||
<div id="top"></div>
|
||||
<h1 class="main-headline">Meteor 0.4.0</h1>
|
||||
<h1 class="main-headline">Meteor 0.4.1</h1>
|
||||
{{> introduction }}
|
||||
{{> concepts }}
|
||||
{{> api }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
METEOR_VERSION = "0.4.0";
|
||||
METEOR_VERSION = "0.4.1";
|
||||
|
||||
Meteor.startup(function () {
|
||||
// XXX this is broken by the new multi-page layout. Also, it was
|
||||
@@ -82,7 +82,8 @@ var toc = [
|
||||
"Core", [
|
||||
"Meteor.isClient",
|
||||
"Meteor.isServer",
|
||||
"Meteor.startup"
|
||||
"Meteor.startup",
|
||||
"Meteor.absoluteUrl"
|
||||
],
|
||||
|
||||
"Publish and subscribe", [
|
||||
@@ -205,7 +206,6 @@ var toc = [
|
||||
],
|
||||
|
||||
"Packages", [ [
|
||||
"absolute-url",
|
||||
"amplify",
|
||||
"backbone",
|
||||
"bootstrap",
|
||||
|
||||
@@ -16,7 +16,6 @@ and removed with:
|
||||
|
||||
$ meteor remove <package_name>
|
||||
|
||||
{{> pkg_absolute_url}}
|
||||
{{> pkg_amplify}}
|
||||
{{> pkg_backbone}}
|
||||
{{> pkg_bootstrap}}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<template name="pkg_absolute_url">
|
||||
{{#better_markdown}}
|
||||
## `absolute-url`
|
||||
|
||||
This package allows constructing absolute URLs pointing back to the
|
||||
application. The server reads from the `ROOT_URL` environment variable
|
||||
to determine where it is running. This is taken care of automatically
|
||||
for apps deployed with `meteor deploy`, but must be provided when using
|
||||
`meteor bundle`.
|
||||
|
||||
{{/better_markdown}}
|
||||
|
||||
{{> api_box absoluteUrl}}
|
||||
|
||||
</template>
|
||||
@@ -1,24 +0,0 @@
|
||||
Template.pkg_absolute_url.absoluteUrl = {
|
||||
id: "meteor_absoluteUrl",
|
||||
name: "Meteor.absoluteUrl([path], [options])",
|
||||
locus: "Anywhere",
|
||||
descr: ["Generate an absolute URL pointing to the application."],
|
||||
args: [
|
||||
{name: "path",
|
||||
type: "String",
|
||||
descr: 'A path to append to the root URL. Do not include a leading "`/`".'
|
||||
}
|
||||
],
|
||||
options: [
|
||||
{name: "secure",
|
||||
type: "Boolean",
|
||||
descr: "Create an HTTPS URL."
|
||||
},
|
||||
{name: "rootUrl",
|
||||
type: "String",
|
||||
descr: "Override the default ROOT_URL from the server environment. For example: \"`http://foo.example.com`\""
|
||||
}
|
||||
]
|
||||
|
||||
};
|
||||
|
||||
1
examples/unfinished/jsparse-demo/.meteor/.gitignore
vendored
Normal file
1
examples/unfinished/jsparse-demo/.meteor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
local
|
||||
7
examples/unfinished/jsparse-demo/.meteor/packages
Normal file
7
examples/unfinished/jsparse-demo/.meteor/packages
Normal file
@@ -0,0 +1,7 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
autopublish
|
||||
jsparse
|
||||
147
examples/unfinished/jsparse-demo/jsparse-demo.css
Normal file
147
examples/unfinished/jsparse-demo/jsparse-demo.css
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
* { padding: 0; margin: 0; }
|
||||
html, body { height: 100%; }
|
||||
|
||||
#topbar {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 39px;
|
||||
border-bottom: 1px solid #555;
|
||||
overflow: auto;
|
||||
background: #cfc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#topbarinner {
|
||||
padding: 7px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#main {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 40px;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#inputarea textarea {
|
||||
border: 0;
|
||||
border-right: 1px solid #555;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
right: 50%;
|
||||
font-family: monospace;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
#output {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
left: 50%;
|
||||
right: 0;
|
||||
overflow: auto;
|
||||
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#inputarea textarea, #output {
|
||||
line-height: 130%;
|
||||
}
|
||||
|
||||
.lex { border: 1px solid #333; }
|
||||
|
||||
.lex_keyword { background: #0f0; }
|
||||
.lex_identifier { background: #ff0; }
|
||||
.lex_punctuation { background: #0ff; }
|
||||
.lex_error { background: #f00; }
|
||||
.lex_whitespace { background: #fcc; }
|
||||
.lex_comment { background: #ccc; }
|
||||
|
||||
.lex_regex { background: #f0f; }
|
||||
.lex_null { background: #dac; }
|
||||
.lex_boolean { background: #faf; }
|
||||
.lex_number { background: #c3f; }
|
||||
.lex_string { background: #fc3; }
|
||||
|
||||
.parseerror {
|
||||
background: #f99;
|
||||
border: 1px solid blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
.parseerrormessage { color: #c00; }
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
margin-top: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.box.statement {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#output > .box {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.box.named {
|
||||
border: 1px solid #888;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
/* position:relative breaks overflow:hidden effect of rounded corners? */
|
||||
position: static;
|
||||
}
|
||||
|
||||
.box.head {
|
||||
font-family: sans-serif;
|
||||
font-size: 70%;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
background: #ccc;
|
||||
color: #000;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-bottom: 1px solid #888;
|
||||
}
|
||||
|
||||
.box.head:last-child {
|
||||
margin: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.box.token {
|
||||
background: #ddd;
|
||||
/*border: 1px solid #999;*/
|
||||
border: 1px solid #00f;
|
||||
cursor: pointer;
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.box.named[mousehover] {
|
||||
background: #cdf;
|
||||
border: 1px solid #448;
|
||||
}
|
||||
|
||||
.box.token[mousehover] {
|
||||
background: #ace;
|
||||
border: 1px solid #448;
|
||||
}
|
||||
|
||||
.box.named[mousehover] > .box.head {
|
||||
background: #58b;
|
||||
border-bottom: 1px solid #448;
|
||||
}
|
||||
|
||||
.box.named[mousehover] > .box.head:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
29
examples/unfinished/jsparse-demo/jsparse-demo.html
Normal file
29
examples/unfinished/jsparse-demo/jsparse-demo.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<head>
|
||||
<title>jsparser</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> page}}
|
||||
</body>
|
||||
|
||||
<template name="page">
|
||||
<div id="topbar">
|
||||
<div id="topbarinner">
|
||||
This is a demo of the new <code>jsparse</code> package.
|
||||
<strong>Edit</strong> code on the left.
|
||||
<strong>Click</strong> on an outlined box on the right
|
||||
to select it in the code. The full spec is supported.
|
||||
</div>
|
||||
</div>
|
||||
<div id="main">
|
||||
<div id="inputarea">{{!
|
||||
whitespace is significant here; browser swallows initial
|
||||
newline in textarea
|
||||
}}<textarea>
|
||||
{{input}}</textarea>
|
||||
</div>
|
||||
<div id="output">
|
||||
{{output}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
151
examples/unfinished/jsparse-demo/jsparse-demo.js
Normal file
151
examples/unfinished/jsparse-demo/jsparse-demo.js
Normal file
@@ -0,0 +1,151 @@
|
||||
|
||||
|
||||
if (Meteor.is_client) {
|
||||
Meteor.startup(function () {
|
||||
if (! Session.get("input"))
|
||||
Session.set("input", "var x = 3");
|
||||
});
|
||||
|
||||
Template.page.input = function () {
|
||||
return Session.get("input") || '';
|
||||
};
|
||||
|
||||
Template.page.output = function () {
|
||||
var input = Session.get("input") || "";
|
||||
|
||||
// LEXER
|
||||
/*
|
||||
if (! input)
|
||||
return "";
|
||||
|
||||
var L = new Lexer(input);
|
||||
var html = "";
|
||||
while (L.next() !== 'EOF') {
|
||||
if (L.type === "NEWLINE") {
|
||||
html += '<br>';
|
||||
} else {
|
||||
var text = Handlebars._escape(L.text || ' ');
|
||||
text = text.replace(/(?!.)\s/g, '<br>'); // for multiline comments
|
||||
text = text.replace(/\s/g, ' ');
|
||||
html += '<span class="lex lex_' + L.type.toLowerCase() + '">' +
|
||||
text + '</span>';
|
||||
if (L.type === "ERROR")
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
// PARSER
|
||||
var html;
|
||||
var tree = null;
|
||||
var parser = new JSParser(input);
|
||||
try {
|
||||
tree = parser.getSyntaxTree();
|
||||
} catch (parseError) {
|
||||
var errorLexeme = parser.lexer.lastLexeme;
|
||||
|
||||
html = Handlebars._escape(
|
||||
input.substring(0, errorLexeme.startPos()));
|
||||
html += Spark.setDataContext(
|
||||
errorLexeme,
|
||||
'<span class="parseerror">' +
|
||||
Handlebars._escape(errorLexeme.text() || '<EOF>') +
|
||||
'</span>');
|
||||
html = html.replace(/(?!.)\s/g, '<br>');
|
||||
html += '<div class="parseerrormessage">' +
|
||||
Handlebars._escape(parseError.toString()) + '</div>';
|
||||
}
|
||||
if (tree) {
|
||||
var curPos = 0;
|
||||
var unclosedInfos = [];
|
||||
var toHtml = function (obj) {
|
||||
if (obj instanceof ParseNode) {
|
||||
var head = obj.name || '';
|
||||
var children = obj.children;
|
||||
var info = { startPos: curPos };
|
||||
var isStatement = (head.indexOf('Stmnt') >= 0);
|
||||
var html = Spark.setDataContext(
|
||||
info,
|
||||
'<div class="box named' + (isStatement ? ' statement' : '') +
|
||||
'"><div class="box head">' + Handlebars._escape(head) + '</div>' +
|
||||
_.map(children, toHtml).join('') + '</div>');
|
||||
unclosedInfos.push(info);
|
||||
return html;
|
||||
} else if (obj.text) {
|
||||
// token
|
||||
_.each(unclosedInfos, function (info) {
|
||||
info.endPos = curPos;
|
||||
});
|
||||
curPos = obj.endPos();
|
||||
unclosedInfos.length = 0;
|
||||
var text = obj.text();
|
||||
// insert zero-width spaces to allow wrapping
|
||||
text = text.replace(/.{20}/g, "$&\n");
|
||||
text = Handlebars._escape(text);
|
||||
text = text.replace(/\n/g, '​');
|
||||
return Spark.setDataContext(
|
||||
obj,
|
||||
'<div class="box token">' + text + '</div>');
|
||||
} else {
|
||||
// other?
|
||||
return '<div class="box other">' +
|
||||
Handlebars._escape(JSON.stringify(obj)) + '</div>';
|
||||
}
|
||||
};
|
||||
html = toHtml(tree);
|
||||
curPos = parser.lexer.pos;
|
||||
_.each(unclosedInfos, function (info) {
|
||||
info.endPos = curPos;
|
||||
});
|
||||
}
|
||||
|
||||
return new Handlebars.SafeString(html);
|
||||
};
|
||||
|
||||
Template.page.events({
|
||||
'keyup #inputarea textarea': function (event) {
|
||||
var input = event.currentTarget.value;
|
||||
Session.set("input", input);
|
||||
},
|
||||
'mouseover .box.named, mouseover .box.token': function (event) {
|
||||
event.currentTarget.setAttribute('mousehover', 'mousehover');
|
||||
event.stopImmediatePropagation();
|
||||
},
|
||||
'mouseout .box.named, mouseout .box.token': function (event) {
|
||||
event.currentTarget.removeAttribute('mousehover');
|
||||
event.stopImmediatePropagation();
|
||||
},
|
||||
'click .box.token': function (event) {
|
||||
selectInputText(this.startPos(), this.endPos());
|
||||
return false;
|
||||
},
|
||||
'click .box.named': function (event) {
|
||||
selectInputText(this.startPos, this.endPos);
|
||||
return false;
|
||||
},
|
||||
'click .parseerror': function (event) {
|
||||
selectInputText(this.startPos(), this.endPos());
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Template.page.preserve(['#inputarea textarea']);
|
||||
|
||||
var selectTextInArea = function (e, start, end){
|
||||
e.focus();
|
||||
if (e.setSelectionRange) {
|
||||
e.setSelectionRange(start, end);
|
||||
} else if (e.createTextRange) {
|
||||
var r = e.createTextRange();
|
||||
r.collapse(true);
|
||||
r.moveEnd('character', end);
|
||||
r.moveStart('character', start);
|
||||
r.select();
|
||||
}
|
||||
};
|
||||
|
||||
var selectInputText = function (start, end) {
|
||||
var textarea = DomUtils.find(document, '#inputarea textarea');
|
||||
selectTextInArea(textarea, start, end);
|
||||
};
|
||||
|
||||
}
|
||||
2
meteor
2
meteor
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
BUNDLE_VERSION=0.2.2
|
||||
BUNDLE_VERSION=0.2.3
|
||||
|
||||
# OS Check. Put here because here is where we download the precompiled
|
||||
# bundles that are arch specific.
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
Package.describe({
|
||||
summary: "Generate absolute URLs pointing to the application"
|
||||
summary: "DEPRECATED: Generate absolute URLs pointing to the application",
|
||||
internal: true
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
// note server before common. usually it is the other way around, but
|
||||
// in this case server must load first.
|
||||
api.add_files('url_server.js', 'server');
|
||||
api.add_files('url_common.js', ['client', 'server']);
|
||||
});
|
||||
|
||||
Package.on_test(function (api) {
|
||||
api.use('absolute-url', ['client', 'server']);
|
||||
api.use('tinytest');
|
||||
|
||||
api.add_files('url_tests.js', ['client', 'server']);
|
||||
console.log('DEPRECATED. The `absolute-url` package has been folded into '
|
||||
+ 'the `meteor` package and should not be used directly. Run '
|
||||
+ '`meteor remove absolute-url` to resolve this.');
|
||||
});
|
||||
|
||||
@@ -5,11 +5,10 @@ DomUtils = {};
|
||||
(function () {
|
||||
|
||||
var qsaFindAllBySelector = function (selector, contextNode) {
|
||||
// If IE7 users report the following error message, you
|
||||
// can fix it with "meteor add jquery".
|
||||
if (! document.querySelectorAll)
|
||||
// IE 7
|
||||
throw new Error(
|
||||
"This browser doesn't support querySelectorAll. " +
|
||||
"You need Sizzle or jQuery (`meteor add jquery`).");
|
||||
throw new Error("This browser doesn't support querySelectorAll.");
|
||||
|
||||
// the search is constrained to descendants of `ancestor`,
|
||||
// but it doesn't affect the scope of the query.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
streamBuffers = __meteor_bootstrap__.require('stream-buffers');
|
||||
|
||||
Tinytest.add("email - dev mode smoke test", function (test) {
|
||||
// This only tests dev mode, so don't run the test if this is deployed.
|
||||
if (process.env.MAIL_URL) return;
|
||||
|
||||
var old_stream = Email._output_stream;
|
||||
try {
|
||||
Email._output_stream = new streamBuffers.WritableStreamBuffer;
|
||||
|
||||
@@ -8,11 +8,6 @@ Package.on_use(function (api) {
|
||||
// server has been instantiated.
|
||||
api.use('livedata', 'server');
|
||||
|
||||
// we don't really depend on absolute-url, but we do modify its
|
||||
// behavior. If there were a way to say "if the other package is
|
||||
// loaded, make sure we come after it", we should do that here.
|
||||
api.use('absolute-url', ['client', 'server']);
|
||||
|
||||
api.add_files('force_ssl_common.js', ['client', 'server']);
|
||||
api.add_files('force_ssl_server.js', 'server');
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ testAsyncMulti("httpcall - basic", [
|
||||
"/foo?fruit=apple&dog=Spot+the+dog");
|
||||
}]);
|
||||
|
||||
testAsyncMulti("httpcall - failure", [
|
||||
testAsyncMulti("httpcall - errors", [
|
||||
function(test, expect) {
|
||||
|
||||
// Accessing unknown server (should fail to make any connection)
|
||||
|
||||
397
packages/jsparse/lexer.js
Normal file
397
packages/jsparse/lexer.js
Normal file
@@ -0,0 +1,397 @@
|
||||
|
||||
(function () {
|
||||
|
||||
var regexEscape = function (str) {
|
||||
return str.replace(/[\][^$\\.*+?(){}|]/g, '\\$&');
|
||||
};
|
||||
|
||||
// Adapted from source code of http://xregexp.com/plugins/#unicode
|
||||
var unicodeCategories = {
|
||||
Ll: "0061-007A00B500DF-00F600F8-00FF01010103010501070109010B010D010F01110113011501170119011B011D011F01210123012501270129012B012D012F01310133013501370138013A013C013E014001420144014601480149014B014D014F01510153015501570159015B015D015F01610163016501670169016B016D016F0171017301750177017A017C017E-0180018301850188018C018D019201950199-019B019E01A101A301A501A801AA01AB01AD01B001B401B601B901BA01BD-01BF01C601C901CC01CE01D001D201D401D601D801DA01DC01DD01DF01E101E301E501E701E901EB01ED01EF01F001F301F501F901FB01FD01FF02010203020502070209020B020D020F02110213021502170219021B021D021F02210223022502270229022B022D022F02310233-0239023C023F0240024202470249024B024D024F-02930295-02AF037103730377037B-037D039003AC-03CE03D003D103D5-03D703D903DB03DD03DF03E103E303E503E703E903EB03ED03EF-03F303F503F803FB03FC0430-045F04610463046504670469046B046D046F04710473047504770479047B047D047F0481048B048D048F04910493049504970499049B049D049F04A104A304A504A704A904AB04AD04AF04B104B304B504B704B904BB04BD04BF04C204C404C604C804CA04CC04CE04CF04D104D304D504D704D904DB04DD04DF04E104E304E504E704E904EB04ED04EF04F104F304F504F704F904FB04FD04FF05010503050505070509050B050D050F05110513051505170519051B051D051F05210523052505270561-05871D00-1D2B1D6B-1D771D79-1D9A1E011E031E051E071E091E0B1E0D1E0F1E111E131E151E171E191E1B1E1D1E1F1E211E231E251E271E291E2B1E2D1E2F1E311E331E351E371E391E3B1E3D1E3F1E411E431E451E471E491E4B1E4D1E4F1E511E531E551E571E591E5B1E5D1E5F1E611E631E651E671E691E6B1E6D1E6F1E711E731E751E771E791E7B1E7D1E7F1E811E831E851E871E891E8B1E8D1E8F1E911E931E95-1E9D1E9F1EA11EA31EA51EA71EA91EAB1EAD1EAF1EB11EB31EB51EB71EB91EBB1EBD1EBF1EC11EC31EC51EC71EC91ECB1ECD1ECF1ED11ED31ED51ED71ED91EDB1EDD1EDF1EE11EE31EE51EE71EE91EEB1EED1EEF1EF11EF31EF51EF71EF91EFB1EFD1EFF-1F071F10-1F151F20-1F271F30-1F371F40-1F451F50-1F571F60-1F671F70-1F7D1F80-1F871F90-1F971FA0-1FA71FB0-1FB41FB61FB71FBE1FC2-1FC41FC61FC71FD0-1FD31FD61FD71FE0-1FE71FF2-1FF41FF61FF7210A210E210F2113212F21342139213C213D2146-2149214E21842C30-2C5E2C612C652C662C682C6A2C6C2C712C732C742C76-2C7B2C812C832C852C872C892C8B2C8D2C8F2C912C932C952C972C992C9B2C9D2C9F2CA12CA32CA52CA72CA92CAB2CAD2CAF2CB12CB32CB52CB72CB92CBB2CBD2CBF2CC12CC32CC52CC72CC92CCB2CCD2CCF2CD12CD32CD52CD72CD92CDB2CDD2CDF2CE12CE32CE42CEC2CEE2CF32D00-2D252D272D2DA641A643A645A647A649A64BA64DA64FA651A653A655A657A659A65BA65DA65FA661A663A665A667A669A66BA66DA681A683A685A687A689A68BA68DA68FA691A693A695A697A723A725A727A729A72BA72DA72F-A731A733A735A737A739A73BA73DA73FA741A743A745A747A749A74BA74DA74FA751A753A755A757A759A75BA75DA75FA761A763A765A767A769A76BA76DA76FA771-A778A77AA77CA77FA781A783A785A787A78CA78EA791A793A7A1A7A3A7A5A7A7A7A9A7FAFB00-FB06FB13-FB17FF41-FF5A",
|
||||
Lm: "02B0-02C102C6-02D102E0-02E402EC02EE0374037A0559064006E506E607F407F507FA081A0824082809710E460EC610FC17D718431AA71C78-1C7D1D2C-1D6A1D781D9B-1DBF2071207F2090-209C2C7C2C7D2D6F2E2F30053031-3035303B309D309E30FC-30FEA015A4F8-A4FDA60CA67FA717-A71FA770A788A7F8A7F9A9CFAA70AADDAAF3AAF4FF70FF9EFF9F",
|
||||
Lo: "00AA00BA01BB01C0-01C3029405D0-05EA05F0-05F20620-063F0641-064A066E066F0671-06D306D506EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA0800-08150840-085808A008A2-08AC0904-0939093D09500958-09610972-09770979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610B710B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BD00C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D0C580C590C600C610C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD0CDE0CE00CE10CF10CF20D05-0D0C0D0E-0D100D12-0D3A0D3D0D4E0D600D610D7A-0D7F0D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60E01-0E300E320E330E40-0E450E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB00EB20EB30EBD0EC0-0EC40EDC-0EDF0F000F40-0F470F49-0F6C0F88-0F8C1000-102A103F1050-1055105A-105D106110651066106E-10701075-1081108E10D0-10FA10FD-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA1700-170C170E-17111720-17311740-17511760-176C176E-17701780-17B317DC1820-18421844-18771880-18A818AA18B0-18F51900-191C1950-196D1970-19741980-19AB19C1-19C71A00-1A161A20-1A541B05-1B331B45-1B4B1B83-1BA01BAE1BAF1BBA-1BE51C00-1C231C4D-1C4F1C5A-1C771CE9-1CEC1CEE-1CF11CF51CF62135-21382D30-2D672D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE3006303C3041-3096309F30A1-30FA30FF3105-312D3131-318E31A0-31BA31F0-31FF3400-4DB54E00-9FCCA000-A014A016-A48CA4D0-A4F7A500-A60BA610-A61FA62AA62BA66EA6A0-A6E5A7FB-A801A803-A805A807-A80AA80C-A822A840-A873A882-A8B3A8F2-A8F7A8FBA90A-A925A930-A946A960-A97CA984-A9B2AA00-AA28AA40-AA42AA44-AA4BAA60-AA6FAA71-AA76AA7AAA80-AAAFAAB1AAB5AAB6AAB9-AABDAAC0AAC2AADBAADCAAE0-AAEAAAF2AB01-AB06AB09-AB0EAB11-AB16AB20-AB26AB28-AB2EABC0-ABE2AC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA6DFA70-FAD9FB1DFB1F-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF66-FF6FFF71-FF9DFFA0-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC",
|
||||
Lt: "01C501C801CB01F21F88-1F8F1F98-1F9F1FA8-1FAF1FBC1FCC1FFC",
|
||||
Lu: "0041-005A00C0-00D600D8-00DE01000102010401060108010A010C010E01100112011401160118011A011C011E01200122012401260128012A012C012E01300132013401360139013B013D013F0141014301450147014A014C014E01500152015401560158015A015C015E01600162016401660168016A016C016E017001720174017601780179017B017D018101820184018601870189-018B018E-0191019301940196-0198019C019D019F01A001A201A401A601A701A901AC01AE01AF01B1-01B301B501B701B801BC01C401C701CA01CD01CF01D101D301D501D701D901DB01DE01E001E201E401E601E801EA01EC01EE01F101F401F6-01F801FA01FC01FE02000202020402060208020A020C020E02100212021402160218021A021C021E02200222022402260228022A022C022E02300232023A023B023D023E02410243-02460248024A024C024E03700372037603860388-038A038C038E038F0391-03A103A3-03AB03CF03D2-03D403D803DA03DC03DE03E003E203E403E603E803EA03EC03EE03F403F703F903FA03FD-042F04600462046404660468046A046C046E04700472047404760478047A047C047E0480048A048C048E04900492049404960498049A049C049E04A004A204A404A604A804AA04AC04AE04B004B204B404B604B804BA04BC04BE04C004C104C304C504C704C904CB04CD04D004D204D404D604D804DA04DC04DE04E004E204E404E604E804EA04EC04EE04F004F204F404F604F804FA04FC04FE05000502050405060508050A050C050E05100512051405160518051A051C051E05200522052405260531-055610A0-10C510C710CD1E001E021E041E061E081E0A1E0C1E0E1E101E121E141E161E181E1A1E1C1E1E1E201E221E241E261E281E2A1E2C1E2E1E301E321E341E361E381E3A1E3C1E3E1E401E421E441E461E481E4A1E4C1E4E1E501E521E541E561E581E5A1E5C1E5E1E601E621E641E661E681E6A1E6C1E6E1E701E721E741E761E781E7A1E7C1E7E1E801E821E841E861E881E8A1E8C1E8E1E901E921E941E9E1EA01EA21EA41EA61EA81EAA1EAC1EAE1EB01EB21EB41EB61EB81EBA1EBC1EBE1EC01EC21EC41EC61EC81ECA1ECC1ECE1ED01ED21ED41ED61ED81EDA1EDC1EDE1EE01EE21EE41EE61EE81EEA1EEC1EEE1EF01EF21EF41EF61EF81EFA1EFC1EFE1F08-1F0F1F18-1F1D1F28-1F2F1F38-1F3F1F48-1F4D1F591F5B1F5D1F5F1F68-1F6F1FB8-1FBB1FC8-1FCB1FD8-1FDB1FE8-1FEC1FF8-1FFB21022107210B-210D2110-211221152119-211D212421262128212A-212D2130-2133213E213F214521832C00-2C2E2C602C62-2C642C672C692C6B2C6D-2C702C722C752C7E-2C802C822C842C862C882C8A2C8C2C8E2C902C922C942C962C982C9A2C9C2C9E2CA02CA22CA42CA62CA82CAA2CAC2CAE2CB02CB22CB42CB62CB82CBA2CBC2CBE2CC02CC22CC42CC62CC82CCA2CCC2CCE2CD02CD22CD42CD62CD82CDA2CDC2CDE2CE02CE22CEB2CED2CF2A640A642A644A646A648A64AA64CA64EA650A652A654A656A658A65AA65CA65EA660A662A664A666A668A66AA66CA680A682A684A686A688A68AA68CA68EA690A692A694A696A722A724A726A728A72AA72CA72EA732A734A736A738A73AA73CA73EA740A742A744A746A748A74AA74CA74EA750A752A754A756A758A75AA75CA75EA760A762A764A766A768A76AA76CA76EA779A77BA77DA77EA780A782A784A786A78BA78DA790A792A7A0A7A2A7A4A7A6A7A8A7AAFF21-FF3A",
|
||||
Mc: "0903093B093E-09400949-094C094E094F0982098309BE-09C009C709C809CB09CC09D70A030A3E-0A400A830ABE-0AC00AC90ACB0ACC0B020B030B3E0B400B470B480B4B0B4C0B570BBE0BBF0BC10BC20BC6-0BC80BCA-0BCC0BD70C01-0C030C41-0C440C820C830CBE0CC0-0CC40CC70CC80CCA0CCB0CD50CD60D020D030D3E-0D400D46-0D480D4A-0D4C0D570D820D830DCF-0DD10DD8-0DDF0DF20DF30F3E0F3F0F7F102B102C10311038103B103C105610571062-10641067-106D108310841087-108C108F109A-109C17B617BE-17C517C717C81923-19261929-192B193019311933-193819B0-19C019C819C91A19-1A1B1A551A571A611A631A641A6D-1A721B041B351B3B1B3D-1B411B431B441B821BA11BA61BA71BAA1BAC1BAD1BE71BEA-1BEC1BEE1BF21BF31C24-1C2B1C341C351CE11CF21CF3302E302FA823A824A827A880A881A8B4-A8C3A952A953A983A9B4A9B5A9BAA9BBA9BD-A9C0AA2FAA30AA33AA34AA4DAA7BAAEBAAEEAAEFAAF5ABE3ABE4ABE6ABE7ABE9ABEAABEC",
|
||||
Mn: "0300-036F0483-04870591-05BD05BF05C105C205C405C505C70610-061A064B-065F067006D6-06DC06DF-06E406E706E806EA-06ED07110730-074A07A6-07B007EB-07F30816-0819081B-08230825-08270829-082D0859-085B08E4-08FE0900-0902093A093C0941-0948094D0951-095709620963098109BC09C1-09C409CD09E209E30A010A020A3C0A410A420A470A480A4B-0A4D0A510A700A710A750A810A820ABC0AC1-0AC50AC70AC80ACD0AE20AE30B010B3C0B3F0B41-0B440B4D0B560B620B630B820BC00BCD0C3E-0C400C46-0C480C4A-0C4D0C550C560C620C630CBC0CBF0CC60CCC0CCD0CE20CE30D41-0D440D4D0D620D630DCA0DD2-0DD40DD60E310E34-0E3A0E47-0E4E0EB10EB4-0EB90EBB0EBC0EC8-0ECD0F180F190F350F370F390F71-0F7E0F80-0F840F860F870F8D-0F970F99-0FBC0FC6102D-10301032-10371039103A103D103E10581059105E-10601071-1074108210851086108D109D135D-135F1712-17141732-1734175217531772177317B417B517B7-17BD17C617C9-17D317DD180B-180D18A91920-19221927192819321939-193B1A171A181A561A58-1A5E1A601A621A65-1A6C1A73-1A7C1A7F1B00-1B031B341B36-1B3A1B3C1B421B6B-1B731B801B811BA2-1BA51BA81BA91BAB1BE61BE81BE91BED1BEF-1BF11C2C-1C331C361C371CD0-1CD21CD4-1CE01CE2-1CE81CED1CF41DC0-1DE61DFC-1DFF20D0-20DC20E120E5-20F02CEF-2CF12D7F2DE0-2DFF302A-302D3099309AA66FA674-A67DA69FA6F0A6F1A802A806A80BA825A826A8C4A8E0-A8F1A926-A92DA947-A951A980-A982A9B3A9B6-A9B9A9BCAA29-AA2EAA31AA32AA35AA36AA43AA4CAAB0AAB2-AAB4AAB7AAB8AABEAABFAAC1AAECAAEDAAF6ABE5ABE8ABEDFB1EFE00-FE0FFE20-FE26",
|
||||
Nd: "0030-00390660-066906F0-06F907C0-07C90966-096F09E6-09EF0A66-0A6F0AE6-0AEF0B66-0B6F0BE6-0BEF0C66-0C6F0CE6-0CEF0D66-0D6F0E50-0E590ED0-0ED90F20-0F291040-10491090-109917E0-17E91810-18191946-194F19D0-19D91A80-1A891A90-1A991B50-1B591BB0-1BB91C40-1C491C50-1C59A620-A629A8D0-A8D9A900-A909A9D0-A9D9AA50-AA59ABF0-ABF9FF10-FF19",
|
||||
Nl: "16EE-16F02160-21822185-218830073021-30293038-303AA6E6-A6EF",
|
||||
Pc: "005F203F20402054FE33FE34FE4D-FE4FFF3F"
|
||||
};
|
||||
|
||||
var unicodeClass = function (abbrev) {
|
||||
return '[' +
|
||||
unicodeCategories[abbrev].replace(/[0-9A-F]{4}/ig, "\\u$&") + ']';
|
||||
};
|
||||
|
||||
// See ECMA-262 spec, 3rd edition, section 7
|
||||
|
||||
// Section 7.2
|
||||
// Match one or more characters of whitespace, excluding line terminators.
|
||||
// Do this by matching reluctantly, stopping at a non-dot (line terminator
|
||||
// or end of string) or a non-whitespace.
|
||||
// We are taking advantage of the fact that we are parsing JS from JS in
|
||||
// regexes like this by "passing through" the spec's definition of whitespace,
|
||||
// which is the same in regexes and the lexical grammar.
|
||||
var rWhiteSpace = /[^\S\u000A\u000D\u2028\u2029]+/g;
|
||||
// Section 7.3
|
||||
// Match one line terminator. Same as (?!.)[\s\S] but more explicit.
|
||||
var rLineTerminator = /[\u000A\u000D\u2028\u2029]/g;
|
||||
// Section 7.4
|
||||
// Match one multi-line comment.
|
||||
// [\s\S] is shorthand for any character, including newlines.
|
||||
// The *? reluctant qualifier makes this easy.
|
||||
var rMultiLineComment = /\/\*[\s\S]*?\*\//g;
|
||||
// Match one single-line comment, not including the line terminator.
|
||||
var rSingleLineComment = /\/\/.*/g;
|
||||
// Section 7.6
|
||||
// Match one or more characters that can start an identifier.
|
||||
// This is IdentifierStart+.
|
||||
var rIdentifierPrefix = new RegExp(
|
||||
"([a-zA-Z$_]+|\\\\u[0-9a-fA-F]{4}|" +
|
||||
[unicodeClass('Lu'), unicodeClass('Ll'), unicodeClass('Lt'),
|
||||
unicodeClass('Lm'), unicodeClass('Lo'), unicodeClass('Nl')].join('|') +
|
||||
")+", 'g');
|
||||
// Match one or more characters that can continue an identifier.
|
||||
// This is (IdentifierPart and not IdentifierStart)+.
|
||||
// To match a full identifier, match rIdentifierPrefix, then
|
||||
// match rIdentifierMiddle followed by rIdentifierPrefix until they both fail.
|
||||
var rIdentifierMiddle = new RegExp(
|
||||
"([0-9]|" + [unicodeClass('Mn'), unicodeClass('Mc'), unicodeClass('Nd'),
|
||||
unicodeClass('Pc')].join('|') + ")+", 'g');
|
||||
// Section 7.7
|
||||
// Match one punctuator (except for division punctuators).
|
||||
var rPunctuator = new RegExp(
|
||||
regexEscape("{ } ( ) [ ] . ; , < > <= >= == != === !== + - * % ++ -- << >> "+
|
||||
">>> & | ^ ! ~ && || ? : = += -= *= %= <<= >>= >>>= &= |= ^=")
|
||||
// sort from longest to shortest so that we don't match '==' for '===' and
|
||||
// '*' for '*=', etc.
|
||||
.split(' ').sort(function (a,b) { return b.length - a.length; })
|
||||
.join('|'), 'g');
|
||||
var rDivPunctuator = /\/=?/g;
|
||||
// Section 7.8.3
|
||||
var rHexLiteral = /0x[0-9a-fA-F]+$/g;
|
||||
var rOctLiteral = /0[0-7]+/g; // deprecated
|
||||
var rDecLiteral =
|
||||
/(((0|[1-9][0-9]*)(\.[0-9]*)?)|\.[0-9]+)([Ee][+-]?[0-9]+)?/g;
|
||||
// Section 7.8.4
|
||||
var rStringQuote = /["']/g;
|
||||
// Match one or more characters besides quotes, backslashes, or line ends
|
||||
var rStringMiddle = /(?=.)[^"'\\]+?((?!.)|(?=["'\\]))/g;
|
||||
// Match one escape sequence, including the backslash.
|
||||
var rEscapeSequence =
|
||||
/\\(['"\\bfnrtv]|0(?![0-9])|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|(?=.)[^ux0-9])/g;
|
||||
// Section 7.8.5
|
||||
// Match one regex literal, including slashes, not including flags.
|
||||
// XXX Add support for unescaped '/' in character class, allowed by 5th ed.
|
||||
var rRegexLiteral = /\/(?![*\/])(\\.|(?=.)[^\\])+?\//g;
|
||||
var rRegexFlags = /[a-zA-Z]*/g;
|
||||
|
||||
var rDecider =
|
||||
/((?=.)\s)|(\/[\/\*]?)|([\][{}().;,<>=!+*%&|^~?:-])|(\d)|(["'])|(.)|([\S\s])/g;
|
||||
|
||||
var keywordLookup = {
|
||||
' break': 'KEYWORD',
|
||||
' case': 'KEYWORD',
|
||||
' catch': 'KEYWORD',
|
||||
' continue': 'KEYWORD',
|
||||
' debugger': 'KEYWORD',
|
||||
' default': 'KEYWORD',
|
||||
' delete': 'KEYWORD',
|
||||
' do': 'KEYWORD',
|
||||
' else': 'KEYWORD',
|
||||
' finally': 'KEYWORD',
|
||||
' for': 'KEYWORD',
|
||||
' function': 'KEYWORD',
|
||||
' if': 'KEYWORD',
|
||||
' in': 'KEYWORD',
|
||||
' instanceof': 'KEYWORD',
|
||||
' new': 'KEYWORD',
|
||||
' return': 'KEYWORD',
|
||||
' switch': 'KEYWORD',
|
||||
' this': 'KEYWORD',
|
||||
' throw': 'KEYWORD',
|
||||
' try': 'KEYWORD',
|
||||
' typeof': 'KEYWORD',
|
||||
' var': 'KEYWORD',
|
||||
' void': 'KEYWORD',
|
||||
' while': 'KEYWORD',
|
||||
' with': 'KEYWORD',
|
||||
|
||||
' false': 'BOOLEAN',
|
||||
' true': 'BOOLEAN',
|
||||
|
||||
' null': 'NULL'
|
||||
};
|
||||
|
||||
var makeSet = function (array) {
|
||||
var s = {};
|
||||
for (var i = 0, N = array.length; i < N; i++)
|
||||
s[array[i]] = true;
|
||||
return s;
|
||||
};
|
||||
|
||||
var nonTokenTypes = makeSet('WHITESPACE COMMENT NEWLINE EOF ERROR'.split(' '));
|
||||
|
||||
var punctuationBeforeDivision = makeSet('] ) } ++ --'.split(' '));
|
||||
var keywordsBeforeDivision = makeSet('this'.split(' '));
|
||||
|
||||
var guessIsDivisionPermittedAfterToken = function (tok) {
|
||||
// Figure out if a '/' character should be interpreted as division
|
||||
// rather than the start of a regular expression when it follows the
|
||||
// token, which must be a token lexeme per isToken().
|
||||
// The beginning of section 7 of the spec briefly
|
||||
// explains what's going on; basically the lexical grammar can't
|
||||
// distinguish, for example, `e/f/g` (division) from `e=/f/g`
|
||||
// (assignment of a regular expression), among many other variations.
|
||||
//
|
||||
// THIS IS ONLY A HEURISTIC, though it will rarely fail.
|
||||
// Here are the two cases I know of where help from the parser is needed:
|
||||
// - if (foo)
|
||||
// /ba/.test("banana") && console.log("matches");
|
||||
// (Close paren of a control structure before a statement starting with
|
||||
// a regex literal. Starting a statement with a regex literal is
|
||||
// unusual, of course, because it's hard to have a side effect.)
|
||||
// - ++ /foo/.abc
|
||||
// (Prefix `++` or `--` before an expression starting with a regex
|
||||
// literal. This will run but I can't see any use for it.)
|
||||
switch (tok.type()) {
|
||||
case "PUNCTUATION":
|
||||
// few punctuators can end an expression, but e.g. `)`
|
||||
return !! punctuationBeforeDivision[tok.text()];
|
||||
case "KEYWORD":
|
||||
// few keywords can end an expression, but e.g. `this`
|
||||
return !! keywordsBeforeDivision[tok.text()];
|
||||
case "IDENTIFIER":
|
||||
return true;
|
||||
default: // literal
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
////////// PUBLIC API
|
||||
|
||||
var Lexeme = function (pos, type, text) {
|
||||
this._pos = pos;
|
||||
this._type = type;
|
||||
this._text = text;
|
||||
};
|
||||
|
||||
Lexeme.prototype.startPos = function () {
|
||||
return this._pos;
|
||||
};
|
||||
|
||||
Lexeme.prototype.endPos = function () {
|
||||
return this._pos + this._text.length;
|
||||
};
|
||||
|
||||
Lexeme.prototype.type = function () {
|
||||
return this._type;
|
||||
};
|
||||
|
||||
Lexeme.prototype.text = function () {
|
||||
return this._text;
|
||||
};
|
||||
|
||||
Lexeme.prototype.isToken = function () {
|
||||
return ! nonTokenTypes[this._type];
|
||||
};
|
||||
|
||||
Lexeme.prototype.isError = function () {
|
||||
return this._type === "ERROR";
|
||||
};
|
||||
|
||||
Lexeme.prototype.isEOF = function () {
|
||||
return this._type === "EOF";
|
||||
};
|
||||
|
||||
Lexeme.prototype.prev = function () {
|
||||
return this._prev;
|
||||
};
|
||||
|
||||
Lexeme.prototype.next = function () {
|
||||
return this._next;
|
||||
};
|
||||
|
||||
Lexeme.prototype.toString = function () {
|
||||
return this.isError() ? "ERROR" :
|
||||
this.isEOF() ? "EOF" : "`" + this.text() + "`";
|
||||
};
|
||||
|
||||
// Create a Lexer for the given string of JavaScript code.
|
||||
//
|
||||
// A lexer keeps a pointer `pos` into the string that is
|
||||
// advanced when you ask for the next lexeme with `next()`.
|
||||
//
|
||||
// XXXXX UPDATE DOCS
|
||||
// Properties:
|
||||
// code: Original JavaScript code string.
|
||||
// pos: Current index into the string. You can assign to it
|
||||
// to continue lexing from a different position. After
|
||||
// calling next(), it is the ending index of the most
|
||||
// recent lexeme.
|
||||
// lastPos: The starting index of the most recent lexeme.
|
||||
// Equal to `pos - text.length`.
|
||||
// text: Text of the last lexeme as a string.
|
||||
// type: Type of the last lexeme, as returned by `next()`.
|
||||
// divisionPermitted: Whether a '/' character should be interpreted
|
||||
// as division rather than the start of a regular expression.
|
||||
// This flag is set automatically during lexing based on the
|
||||
// previous token (i.e. the most recent token lexeme), but
|
||||
// it is technically only a heuristic.
|
||||
// Thie flag can be read and set manually to affect the
|
||||
// parsing of the next token.
|
||||
|
||||
JSLexer = function (code) {
|
||||
this.code = code;
|
||||
this.pos = 0;
|
||||
this.divisionPermitted = false;
|
||||
this.lastLexeme = null;
|
||||
};
|
||||
|
||||
JSLexer.Lexeme = Lexeme;
|
||||
|
||||
// XXXX UPDATE DOCS
|
||||
// Return the type of the next of lexeme starting at `pos`, and advance
|
||||
// `pos` to the end of the lexeme. The text of the lexeme is available
|
||||
// in `text`. The text is always the substring of `code` between the
|
||||
// old and new values of `pos`. An "EOF" lexeme terminates
|
||||
// the stream. "ERROR" lexemes indicate a bad input string. Out of all
|
||||
// lexemes, only "EOF" has empty text, and it always has empty text.
|
||||
// All others contain at least one character from the source code.
|
||||
//
|
||||
// Lexeme types:
|
||||
// Literals: BOOLEAN, NULL, REGEX, NUMBER, STRING
|
||||
// Whitespace-like: WHITESPACE, COMMENT, NEWLINE, EOF
|
||||
// Other Tokens: IDENTIFIER, KEYWORD, PUNCTUATION
|
||||
// ... and ERROR
|
||||
|
||||
JSLexer.prototype.next = function () {
|
||||
var self = this;
|
||||
var code = self.code;
|
||||
var origPos = self.pos;
|
||||
var divisionPermitted = self.divisionPermitted;
|
||||
|
||||
if (origPos > code.length)
|
||||
throw new Error("out of range");
|
||||
|
||||
// Running regexes inside this function will move this local
|
||||
// `pos` forward.
|
||||
// When we commit to emitting a lexeme, we'll set self.pos
|
||||
// based on it.
|
||||
var pos = origPos;
|
||||
|
||||
// Emit a lexeme. Always called as `return lexeme(type)`.
|
||||
var lexeme = function (type) {
|
||||
// If `pos` hasn't moved, we consider this an error.
|
||||
// This means that grammar cases that only run one regex
|
||||
// or an alternation ('||') of regexes don't need to
|
||||
// check for failure.
|
||||
// This also guarantees that only EOF lexemes are empty.
|
||||
if (pos === origPos && type !== 'EOF') {
|
||||
type = 'ERROR';
|
||||
pos = origPos + 1;
|
||||
}
|
||||
self.pos = pos;
|
||||
var lex = new JSLexer.Lexeme(origPos, type, code.substring(origPos, pos));
|
||||
if (self.lastLexeme) {
|
||||
self.lastLexeme._next = lex;
|
||||
lex._prev = self.lastLexeme;
|
||||
}
|
||||
self.lastLexeme = lex;
|
||||
if (lex.isToken())
|
||||
self.divisionPermitted = guessIsDivisionPermittedAfterToken(lex);
|
||||
return lex;
|
||||
};
|
||||
|
||||
if (pos === code.length)
|
||||
return lexeme('EOF');
|
||||
|
||||
// Result of the regex match in the most recent call to `run`.
|
||||
var match = null;
|
||||
|
||||
// Run a regex starting from `pos`, recording the end of the matched
|
||||
// string in `pos` and the match data in `match`. The regex must have
|
||||
// the 'g' (global) flag. If it doesn't match at `pos`, set `match`
|
||||
// to null. The caller should expect the regex to match at `pos`, as
|
||||
// failure is too expensive to run in a tight loop.
|
||||
var run = function (regex) {
|
||||
// Cause regex matching to start at `pos`.
|
||||
regex.lastIndex = pos;
|
||||
match = regex.exec(code);
|
||||
// Simulate "sticky" matching by throwing out the match if it
|
||||
// didn't match exactly at `pos`. If it didn't, we may have
|
||||
// just searched the entire string.
|
||||
if (match && (match.index !== pos))
|
||||
match = null;
|
||||
// Record the end position of the match back into `pos`.
|
||||
if (match)
|
||||
pos = regex.lastIndex;
|
||||
return match;
|
||||
};
|
||||
|
||||
// Decide which case of the grammar we are in based on one or two
|
||||
// characters, then roll back `pos`.
|
||||
run(rDecider);
|
||||
pos = origPos;
|
||||
|
||||
// Grammar cases
|
||||
if (match[1]) { // \s
|
||||
run(rWhiteSpace);
|
||||
return lexeme('WHITESPACE');
|
||||
}
|
||||
if (match[2]) { // one of //, /*, /
|
||||
if (match[2] === '//') {
|
||||
run(rSingleLineComment);
|
||||
return lexeme('COMMENT');
|
||||
}
|
||||
if (match[2] === '/*') {
|
||||
run(rMultiLineComment);
|
||||
return lexeme(match ? 'COMMENT' : 'ERROR');
|
||||
}
|
||||
if (match[2] === '/') {
|
||||
if (divisionPermitted) {
|
||||
run(rDivPunctuator);
|
||||
return lexeme('PUNCTUATION');
|
||||
} else {
|
||||
run(rRegexLiteral);
|
||||
if (! match)
|
||||
return lexeme('ERROR');
|
||||
run(rRegexFlags);
|
||||
return lexeme('REGEX');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (match[3]) { // any other punctuation char
|
||||
run(rPunctuator);
|
||||
return lexeme(match ? 'PUNCTUATION' : 'ERROR');
|
||||
}
|
||||
if (match[4]) { // 0-9
|
||||
run(rDecLiteral) || run(rHexLiteral) || run(rOctLiteral);
|
||||
return lexeme(match ? 'NUMBER' : 'ERROR');
|
||||
}
|
||||
if (match[5]) { // " or '
|
||||
run(rStringQuote);
|
||||
var quote = match[0];
|
||||
do {
|
||||
run(rStringMiddle) || run(rEscapeSequence) || run(rStringQuote);
|
||||
} while (match && match[0] !== quote);
|
||||
if (! (match && match[0] === quote))
|
||||
return lexeme('ERROR');
|
||||
return lexeme('STRING');
|
||||
}
|
||||
if (match[7]) { // non-dot (line terminator)
|
||||
run(rLineTerminator);
|
||||
return lexeme('NEWLINE');
|
||||
}
|
||||
// dot (any non-line-terminator)
|
||||
run(rIdentifierPrefix);
|
||||
// Use non-short-circuiting OR, '|', to allow matching
|
||||
// both regexes in sequence, returning false only if neither
|
||||
// matched.
|
||||
while (run(rIdentifierMiddle) | run(rIdentifierPrefix)) {/*continue*/}
|
||||
var word = code.substring(origPos, pos);
|
||||
return lexeme(keywordLookup[' '+word] || 'IDENTIFIER');
|
||||
};
|
||||
|
||||
})();
|
||||
20
packages/jsparse/package.js
Normal file
20
packages/jsparse/package.js
Normal file
@@ -0,0 +1,20 @@
|
||||
Package.describe({
|
||||
summary: "Full-featured JavaScript parser",
|
||||
internal: true
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.add_files(['lexer.js', 'parserlib.js', 'stringify.js', 'parser.js'],
|
||||
['client', 'server']);
|
||||
});
|
||||
|
||||
Package.on_test(function (api) {
|
||||
api.use('tinytest');
|
||||
api.use('jsparse', 'client');
|
||||
|
||||
api.add_files('parser_tests.js',
|
||||
// Test just on client for faster running; should run
|
||||
// identically on server.
|
||||
'client');
|
||||
//['client', 'server']);
|
||||
});
|
||||
761
packages/jsparse/parser.js
Normal file
761
packages/jsparse/parser.js
Normal file
@@ -0,0 +1,761 @@
|
||||
///// JAVASCRIPT PARSER
|
||||
|
||||
// What we don't support from ECMA-262 5.1:
|
||||
// - object literal trailing comma
|
||||
// - object literal get/set
|
||||
|
||||
(function () {
|
||||
|
||||
var expecting = Parser.expecting;
|
||||
|
||||
var assertion = Parsers.assertion;
|
||||
var node = Parsers.node;
|
||||
var or = Parsers.or;
|
||||
var and = Parsers.and;
|
||||
var not = Parsers.not;
|
||||
var list = Parsers.list;
|
||||
var seq = Parsers.seq;
|
||||
var opt = Parsers.opt;
|
||||
var constant = Parsers.constant;
|
||||
var mapResult = Parsers.mapResult;
|
||||
|
||||
|
||||
var makeSet = function (array) {
|
||||
var s = {};
|
||||
for (var i = 0, N = array.length; i < N; i++)
|
||||
s[array[i]] = true;
|
||||
return s;
|
||||
};
|
||||
|
||||
|
||||
JSParser = function (code, options) {
|
||||
this.lexer = new JSLexer(code);
|
||||
this.oldToken = null;
|
||||
this.newToken = null;
|
||||
this.pos = 0;
|
||||
this.isLineTerminatorHere = false;
|
||||
|
||||
options = options || {};
|
||||
// pass {tokens:'strings'} to get strings for
|
||||
// tokens instead of token objects
|
||||
if (options.tokens === 'strings') {
|
||||
this.tokenFunc = function (tok) {
|
||||
return tok.text();
|
||||
};
|
||||
} else {
|
||||
this.tokenFunc = function (tok) {
|
||||
return tok;
|
||||
};
|
||||
}
|
||||
|
||||
this.consumeNewToken();
|
||||
};
|
||||
|
||||
JSParser.prototype.consumeNewToken = function () {
|
||||
var self = this;
|
||||
var lexer = self.lexer;
|
||||
self.oldToken = self.newToken;
|
||||
self.isLineTerminatorHere = false;
|
||||
var lex;
|
||||
do {
|
||||
lex = lexer.next();
|
||||
if (lex.isError())
|
||||
throw new Error("Bad token at position " + lex.startPos() +
|
||||
", text `" + lex.text() + "`");
|
||||
else if (lex.type() === "NEWLINE")
|
||||
self.isLineTerminatorHere = true;
|
||||
else if (lex.type() === "COMMENT" && ! /^.*$/.test(lex.text()))
|
||||
// multiline comments containing line terminators count
|
||||
// as line terminators.
|
||||
self.isLineTerminatorHere = true;
|
||||
} while (! lex.isEOF() && ! lex.isToken());
|
||||
self.newToken = lex;
|
||||
self.pos = lex.startPos();
|
||||
};
|
||||
|
||||
JSParser.prototype.getParseError = function (expecting, found) {
|
||||
var msg = (expecting ? "Expected " + expecting : "Unexpected token");
|
||||
if (this.oldToken)
|
||||
msg += " after " + this.oldToken;
|
||||
var pos = this.pos;
|
||||
msg += " at position " + pos;
|
||||
msg += ", found " + (found || this.newToken);
|
||||
return new Error(msg);
|
||||
};
|
||||
|
||||
JSParser.prototype.getSyntaxTree = function () {
|
||||
var self = this;
|
||||
|
||||
var NIL = new ParseNode('nil', []);
|
||||
|
||||
var booleanFlaggedParser = function (parserConstructFunc) {
|
||||
return {
|
||||
false: parserConstructFunc(false),
|
||||
true: parserConstructFunc(true)
|
||||
};
|
||||
};
|
||||
|
||||
// Takes a space-separated list of either punctuation or keyword tokens
|
||||
var lookAheadToken = function (tokens) {
|
||||
var type = (/\w/.test(tokens) ? 'KEYWORD' : 'PUNCTUATION');
|
||||
var textSet = makeSet(tokens.split(' '));
|
||||
return expecting(
|
||||
tokens.split(' ').join(', '),
|
||||
assertion(function (t) {
|
||||
return (t.newToken.type() === type && textSet[t.newToken.text()]);
|
||||
}));
|
||||
};
|
||||
|
||||
var lookAheadTokenType = function (type) {
|
||||
return expecting(type, assertion(function (t) {
|
||||
return t.newToken.type() === type;
|
||||
}));
|
||||
};
|
||||
|
||||
// Takes a space-separated list of either punctuation or keyword tokens
|
||||
var token = function (tokens) {
|
||||
var type = (/\w/.test(tokens) ? 'KEYWORD' : 'PUNCTUATION');
|
||||
var textSet = makeSet(tokens.split(' '));
|
||||
return new Parser(
|
||||
tokens.split(' ').join(', '),
|
||||
function (t) {
|
||||
if (t.newToken.type() === type && textSet[t.newToken.text()]) {
|
||||
t.consumeNewToken();
|
||||
return self.tokenFunc(t.oldToken);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
var tokenType = function (type) {
|
||||
return new Parser(type, function (t) {
|
||||
if (t.newToken.type() === type) {
|
||||
t.consumeNewToken();
|
||||
return self.tokenFunc(t.oldToken);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
var noLineTerminatorHere = expecting(
|
||||
'noLineTerminator', assertion(function (t) {
|
||||
return ! t.isLineTerminatorHere;
|
||||
}));
|
||||
|
||||
var nonLHSExpressionNames = makeSet(
|
||||
'unary binary postfix ternary assignment comma'.split(' '));
|
||||
var isExpressionLHS = function (exprNode) {
|
||||
return ! nonLHSExpressionNames[exprNode.name];
|
||||
};
|
||||
|
||||
// Like token, but marks tokens that need to defy the lexer's
|
||||
// heuristic about whether the next '/' is a division or
|
||||
// starts a regex.
|
||||
var preSlashToken = function (text, divisionNotRegex) {
|
||||
var inner = token(text);
|
||||
return new Parser(
|
||||
inner.expecting,
|
||||
function (t) {
|
||||
// temporarily set divisionPermitted,
|
||||
// restoring it if we don't match.
|
||||
var oldValue = t.lexer.divisionPermitted;
|
||||
var result;
|
||||
try {
|
||||
t.lexer.divisionPermitted = divisionNotRegex;
|
||||
result = inner.parse(t);
|
||||
return result;
|
||||
} finally {
|
||||
if (! result)
|
||||
t.lexer.divisionPermitted = oldValue;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Mark some productions "lazy" to allow grammar circularity, i.e. accessing
|
||||
// later parsers from earlier ones.
|
||||
// These lazy versions will be replaced with real ones, which they will
|
||||
// access when run.
|
||||
var expressionMaybeNoIn = {
|
||||
'false': Parsers.lazy(
|
||||
'expression',
|
||||
function () { return expressionMaybeNoIn[false]; }),
|
||||
'true': Parsers.lazy(
|
||||
'expression',
|
||||
function () { return expressionMaybeNoIn[true]; })
|
||||
};
|
||||
var expression = expressionMaybeNoIn[false];
|
||||
|
||||
var assignmentExpressionMaybeNoIn = {
|
||||
'false': Parsers.lazy(
|
||||
'expression',
|
||||
function () { return assignmentExpressionMaybeNoIn[false]; }),
|
||||
'true': Parsers.lazy(
|
||||
'expression',
|
||||
function () { return assignmentExpressionMaybeNoIn[true]; })
|
||||
};
|
||||
var assignmentExpression = assignmentExpressionMaybeNoIn[false];
|
||||
|
||||
var functionBody = Parsers.lazy(
|
||||
'statement', function () { return functionBody; });
|
||||
var statement = Parsers.lazy(
|
||||
'statement', function () { return statement; });
|
||||
////
|
||||
|
||||
var arrayLiteral =
|
||||
node('array',
|
||||
seq(token('['),
|
||||
opt(list(token(','))),
|
||||
or(
|
||||
lookAheadToken(']'),
|
||||
list(
|
||||
expecting(
|
||||
'expression',
|
||||
or(assignmentExpression,
|
||||
// count a peeked-at ']' as an expression
|
||||
// to support elisions at end, e.g.
|
||||
// `[1,2,3,,,,,,]`.
|
||||
lookAheadToken(']'))),
|
||||
// list seperator is one or more commas
|
||||
// to support elision
|
||||
list(token(',')))),
|
||||
token(']')));
|
||||
|
||||
var propertyName = expecting('propertyName', or(
|
||||
node('idPropName', tokenType('IDENTIFIER')),
|
||||
node('numPropName', tokenType('NUMBER')),
|
||||
node('strPropName', tokenType('STRING'))));
|
||||
var nameColonValue = expecting(
|
||||
'propertyName',
|
||||
node('prop', seq(propertyName, token(':'), assignmentExpression)));
|
||||
|
||||
var objectLiteral =
|
||||
node('object',
|
||||
seq(token('{'),
|
||||
or(lookAheadToken('}'),
|
||||
list(nameColonValue,
|
||||
token(','))),
|
||||
token('}')));
|
||||
|
||||
var functionMaybeNameRequired = booleanFlaggedParser(
|
||||
function (nameRequired) {
|
||||
return seq(token('function'),
|
||||
(nameRequired ? tokenType('IDENTIFIER') :
|
||||
or(tokenType('IDENTIFIER'),
|
||||
and(lookAheadToken('('), constant(NIL)))),
|
||||
token('('),
|
||||
or(lookAheadToken(')'),
|
||||
list(tokenType('IDENTIFIER'), token(','))),
|
||||
token(')'),
|
||||
token('{'),
|
||||
functionBody,
|
||||
token('}'));
|
||||
});
|
||||
var functionExpression = node('functionExpr',
|
||||
functionMaybeNameRequired[false]);
|
||||
|
||||
var primaryOrFunctionExpression =
|
||||
expecting('expression',
|
||||
or(node('this', token('this')),
|
||||
node('identifier', tokenType('IDENTIFIER')),
|
||||
node('number', tokenType('NUMBER')),
|
||||
node('boolean', tokenType('BOOLEAN')),
|
||||
node('null', tokenType('NULL')),
|
||||
node('regex', tokenType('REGEX')),
|
||||
node('string', tokenType('STRING')),
|
||||
node('parens',
|
||||
seq(token('('), expression, token(')'))),
|
||||
arrayLiteral,
|
||||
objectLiteral,
|
||||
functionExpression));
|
||||
|
||||
var dotEnding = seq(token('.'), tokenType('IDENTIFIER'));
|
||||
var bracketEnding = seq(token('['), expression, token(']'));
|
||||
var callArgs = seq(token('('),
|
||||
or(lookAheadToken(')'),
|
||||
list(assignmentExpression,
|
||||
token(','))),
|
||||
token(')'));
|
||||
|
||||
var newKeyword = token('new');
|
||||
|
||||
// This is a completely equivalent refactor of the spec's production
|
||||
// for a LeftHandSideExpression.
|
||||
//
|
||||
// An lhsExpression is basically an expression that can serve as
|
||||
// the left-hand-side of an assignment, though function calls and
|
||||
// "new" invocation are included because they have the same
|
||||
// precedence. Actually, the spec technically allows a function
|
||||
// call to "return" a valid l-value, as in `foo(bar) = baz`,
|
||||
// though no built-in or user-specifiable call has this property
|
||||
// (it would have to be defined by a browser or other "host").
|
||||
var lhsExpression = new Parser(
|
||||
'expression',
|
||||
function (t) {
|
||||
// Accumulate all initial "new" keywords, not yet knowing
|
||||
// if they have a corresponding argument list later.
|
||||
var news = [];
|
||||
var n;
|
||||
while ((n = newKeyword.parse(t)))
|
||||
news.push(n);
|
||||
|
||||
// Read the primaryOrFunctionExpression that will be the "core"
|
||||
// of this lhsExpression. It is preceded by zero or more `new`
|
||||
// keywords, and followed by any sequence of (...), [...],
|
||||
// and .foo add-ons.
|
||||
// if we have 'new' keywords, we are committed and must
|
||||
// match an expression or error.
|
||||
var result = primaryOrFunctionExpression.parseRequiredIf(t, news.length);
|
||||
if (! result)
|
||||
return null;
|
||||
|
||||
// Our plan of attack is to apply each dot, bracket, or call
|
||||
// as we come across it. Whether a call is a `new` call depends
|
||||
// on whether there are `new` keywords we haven't used. If so,
|
||||
// we pop one off the stack.
|
||||
var done = false;
|
||||
while (! done) {
|
||||
var r;
|
||||
if ((r = dotEnding.parse(t))) {
|
||||
result = new ParseNode('dot', [result].concat(r));
|
||||
} else if ((r = bracketEnding.parse(t))) {
|
||||
result = new ParseNode('bracket', [result].concat(r));
|
||||
} else if ((r = callArgs.parse(t))) {
|
||||
if (news.length)
|
||||
result = new ParseNode('newcall', [news.pop(), result].concat(r));
|
||||
else
|
||||
result = new ParseNode('call', [result].concat(r));
|
||||
} else {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
// There may be more `new` keywords than calls, which is how
|
||||
// paren-less constructions (`new Date`) are parsed. We've
|
||||
// already handled `new foo().bar()`, now handle `new new foo().bar`.
|
||||
while (news.length)
|
||||
result = new ParseNode('new', [news.pop(), result]);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
var postfixToken = token('++ --');
|
||||
var postfixLookahead = lookAheadToken('++ --');
|
||||
var postfixExpression = expecting(
|
||||
'expression',
|
||||
mapResult(seq(lhsExpression,
|
||||
opt(and(noLineTerminatorHere,
|
||||
postfixLookahead,
|
||||
postfixToken))),
|
||||
function (v) {
|
||||
if (v.length === 1)
|
||||
return v[0];
|
||||
return new ParseNode('postfix', v);
|
||||
}));
|
||||
|
||||
var unaryExpression = Parsers.unary(
|
||||
'unary', postfixExpression,
|
||||
or(token('delete void typeof'),
|
||||
preSlashToken('++ -- + - ~ !', false)));
|
||||
|
||||
// The "noIn" business is all to facilitate parsing
|
||||
// of for-in constructs, though the cases that make
|
||||
// this required are quite obscure.
|
||||
// The `for(var x in y)` form is allowed to take
|
||||
// an initializer for `x` (which is only useful for
|
||||
// its side effects, or if `y` has no properties).
|
||||
// So an example might be:
|
||||
// `for(var x = a().b in c);`
|
||||
// In this example, `var x = a().b` is parsed without
|
||||
// the `in`, which would otherwise be part of the
|
||||
// varDecl, using varDeclNoIn.
|
||||
|
||||
// Our binaryExpression is the spec's LogicalORExpression,
|
||||
// which includes all the higher-precendence operators.
|
||||
var binaryExpressionMaybeNoIn = booleanFlaggedParser(
|
||||
function (noIn) {
|
||||
// high to low precedence
|
||||
var binaryOps = [token('* / %'),
|
||||
token('+ -'),
|
||||
token('<< >> >>>'),
|
||||
or(token('< > <= >='),
|
||||
noIn ? token('instanceof') :
|
||||
token('instanceof in')),
|
||||
token('== != === !=='),
|
||||
token('&'),
|
||||
token('^'),
|
||||
token('|'),
|
||||
token('&&'),
|
||||
token('||')];
|
||||
return expecting(
|
||||
'expression',
|
||||
Parsers.binaryLeft('binary', unaryExpression, binaryOps));
|
||||
});
|
||||
var binaryExpression = binaryExpressionMaybeNoIn[false];
|
||||
|
||||
var conditionalExpressionMaybeNoIn = booleanFlaggedParser(
|
||||
function (noIn) {
|
||||
return expecting(
|
||||
'expression',
|
||||
mapResult(
|
||||
seq(binaryExpressionMaybeNoIn[noIn],
|
||||
opt(seq(
|
||||
token('?'),
|
||||
assignmentExpression, token(':'),
|
||||
assignmentExpressionMaybeNoIn[noIn]))),
|
||||
function (v) {
|
||||
if (v.length === 1)
|
||||
return v[0];
|
||||
return new ParseNode('ternary', v);
|
||||
}));
|
||||
});
|
||||
var conditionalExpression = conditionalExpressionMaybeNoIn[false];
|
||||
|
||||
var assignOp = token('= *= /= %= += -= <<= >>= >>>= &= ^= |=');
|
||||
|
||||
assignmentExpressionMaybeNoIn = booleanFlaggedParser(
|
||||
function (noIn) {
|
||||
return new Parser(
|
||||
'expression',
|
||||
function (t) {
|
||||
var r = conditionalExpressionMaybeNoIn[noIn].parse(t);
|
||||
if (! r)
|
||||
return null;
|
||||
|
||||
// Assignment is right-associative.
|
||||
// Plan of attack: make a list of all the parts
|
||||
// [expression, op, expression, op, ... expression]
|
||||
// and then fold them up at the end.
|
||||
var parts = [r];
|
||||
var op;
|
||||
while (isExpressionLHS(r) &&(op = assignOp.parse(t)))
|
||||
parts.push(op,
|
||||
conditionalExpressionMaybeNoIn[noIn].parseRequired(t));
|
||||
|
||||
var result = parts.pop();
|
||||
while (parts.length) {
|
||||
op = parts.pop();
|
||||
var lhs = parts.pop();
|
||||
result = new ParseNode('assignment', [lhs, op, result]);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
});
|
||||
assignmentExpression = assignmentExpressionMaybeNoIn[false];
|
||||
|
||||
expressionMaybeNoIn = booleanFlaggedParser(
|
||||
function (noIn) {
|
||||
return expecting(
|
||||
'expression',
|
||||
mapResult(
|
||||
list(assignmentExpressionMaybeNoIn[noIn], token(',')),
|
||||
function (v) {
|
||||
if (v.length === 1)
|
||||
return v[0];
|
||||
return new ParseNode('comma', v);
|
||||
}));
|
||||
});
|
||||
expression = expressionMaybeNoIn[false];
|
||||
|
||||
// STATEMENTS
|
||||
|
||||
var statements = list(statement);
|
||||
|
||||
// implements JavaScript's semicolon "insertion" rules
|
||||
var maybeSemicolon = expecting(
|
||||
'semicolon',
|
||||
or(token(';'),
|
||||
and(
|
||||
or(
|
||||
lookAheadToken('}'),
|
||||
lookAheadTokenType('EOF'),
|
||||
assertion(function (t) {
|
||||
return t.isLineTerminatorHere;
|
||||
})),
|
||||
constant(new ParseNode(';', [])))));
|
||||
|
||||
var expressionStatement = node(
|
||||
'expressionStmnt',
|
||||
and(
|
||||
not(or(lookAheadToken('{'), lookAheadToken('function'))),
|
||||
seq(expression,
|
||||
expecting('semicolon',
|
||||
or(maybeSemicolon,
|
||||
// allow presence of colon to terminate
|
||||
// statement legally, for the benefit of
|
||||
// expressionOrLabelStatement. Basically assume
|
||||
// an implicit semicolon. This
|
||||
// is safe because a colon can never legally
|
||||
// follow a semicolon anyway.
|
||||
and(lookAheadToken(':'),
|
||||
constant(new ParseNode(';', []))))))));
|
||||
|
||||
// it's hard to parse statement labels, as in
|
||||
// `foo: x = 1`, because we can't tell from the
|
||||
// first token whether we are looking at an expression
|
||||
// statement or a label statement. To work around this,
|
||||
// expressionOrLabelStatement parses the expression and
|
||||
// then rewrites the result if it is an identifier
|
||||
// followed by a colon.
|
||||
var labelColonAndStatement = seq(token(':'), statement);
|
||||
var noColon = expecting(
|
||||
'semicolon', not(lookAheadToken(':')));
|
||||
var expressionOrLabelStatement = new Parser(
|
||||
null,
|
||||
function (t) {
|
||||
var exprStmnt = expressionStatement.parse(t);
|
||||
if (! exprStmnt)
|
||||
return null;
|
||||
|
||||
var expr = exprStmnt.children[0];
|
||||
var maybeSemi = exprStmnt.children[1];
|
||||
if (expr.name !== 'identifier' ||
|
||||
! (maybeSemi instanceof ParseNode)) {
|
||||
// We either have a non-identifier expression or a present
|
||||
// semicolon. This is not a label.
|
||||
//
|
||||
// Fail now if we are looking at a colon, causing an
|
||||
// error message on input like `1+1:` of the same kind
|
||||
// you'd get without statement label parsing.
|
||||
noColon.parseRequired(t);
|
||||
return exprStmnt;
|
||||
}
|
||||
|
||||
var rest = labelColonAndStatement.parse(t);
|
||||
if (! rest)
|
||||
return exprStmnt;
|
||||
|
||||
return new ParseNode('labelStmnt',
|
||||
[expr.children[0]].concat(rest));
|
||||
});
|
||||
|
||||
var emptyStatement = node('emptyStmnt', token(';')); // required semicolon
|
||||
|
||||
var blockStatement = expecting('block', node('blockStmnt', seq(
|
||||
token('{'), or(lookAheadToken('}'), statements),
|
||||
token('}'))));
|
||||
|
||||
var varDeclMaybeNoIn = booleanFlaggedParser(function (noIn) {
|
||||
return node(
|
||||
'varDecl',
|
||||
seq(tokenType('IDENTIFIER'),
|
||||
opt(seq(token('='),
|
||||
assignmentExpressionMaybeNoIn[noIn]))));
|
||||
});
|
||||
var varDecl = varDeclMaybeNoIn[false];
|
||||
|
||||
var variableStatement = node(
|
||||
'varStmnt',
|
||||
seq(token('var'), list(varDecl, token(',')),
|
||||
maybeSemicolon));
|
||||
|
||||
// A paren that may be followed by a statement
|
||||
// beginning with a regex literal.
|
||||
var closeParenBeforeStatement = preSlashToken(')', false);
|
||||
|
||||
var ifStatement = node(
|
||||
'ifStmnt',
|
||||
seq(token('if'), token('('), expression,
|
||||
closeParenBeforeStatement, statement,
|
||||
opt(seq(token('else'), statement))));
|
||||
|
||||
var secondThirdClauses = expecting(
|
||||
'semicolon',
|
||||
and(lookAheadToken(';'),
|
||||
seq(
|
||||
expecting('semicolon', token(';')),
|
||||
or(and(lookAheadToken(';'),
|
||||
constant(NIL)),
|
||||
expression),
|
||||
expecting('semicolon', token(';')),
|
||||
or(and(lookAheadToken(')'),
|
||||
constant(NIL)),
|
||||
expression))));
|
||||
var inExpr = seq(token('in'), expression);
|
||||
var inExprExpectingSemi = expecting('semicolon',
|
||||
seq(token('in'), expression));
|
||||
var forSpec = mapResult(node(
|
||||
'forSpec',
|
||||
or(seq(token('var'),
|
||||
varDeclMaybeNoIn[true],
|
||||
expecting(
|
||||
'commaOrIn',
|
||||
or(inExpr,
|
||||
seq(
|
||||
or(
|
||||
lookAheadToken(';'),
|
||||
seq(token(','),
|
||||
list(varDeclMaybeNoIn[true], token(',')))),
|
||||
secondThirdClauses)))),
|
||||
// get the case where the first clause is empty out of the way.
|
||||
// the lookAhead's return value is the empty placeholder for the
|
||||
// missing expression.
|
||||
seq(and(lookAheadToken(';'),
|
||||
constant(NIL)), secondThirdClauses),
|
||||
// custom parser the non-var case because we have to
|
||||
// read the first expression before we know if there's
|
||||
// an "in".
|
||||
new Parser(
|
||||
null,
|
||||
function (t) {
|
||||
var firstExpr = expressionMaybeNoIn[true].parse(t);
|
||||
if (! firstExpr)
|
||||
return null;
|
||||
var rest = secondThirdClauses.parse(t);
|
||||
if (! rest) {
|
||||
// we need a left-hand-side expression for a
|
||||
// `for (x in y)` loop.
|
||||
if (! isExpressionLHS(firstExpr))
|
||||
throw t.getParseError("semicolon");
|
||||
// if we don't see 'in' at this point, it's probably
|
||||
// a missing semicolon
|
||||
rest = inExprExpectingSemi.parseRequired(t);
|
||||
}
|
||||
|
||||
return [firstExpr].concat(rest);
|
||||
}))),
|
||||
function (clauses) {
|
||||
// There are four kinds of for-loop, and we call the
|
||||
// part between the parens one of forSpec, forVarSpec,
|
||||
// forInSpec, and forVarInSpec. Having parsed it
|
||||
// already, we rewrite the node name based on how
|
||||
// many items came out. forIn and forVarIn always
|
||||
// have 3 and 4 items respectively. for has 5
|
||||
// (the optional expressions are present as nils).
|
||||
// forVar has 6 or more, because `for(var x;;);`
|
||||
// produces [`var` `x` `;` nil `;` nil].
|
||||
var numChildren = clauses.children.length;
|
||||
if (numChildren === 3)
|
||||
return new ParseNode('forInSpec', clauses.children);
|
||||
else if (numChildren === 4)
|
||||
return new ParseNode('forVarInSpec', clauses.children);
|
||||
else if (numChildren >= 6)
|
||||
return new ParseNode('forVarSpec', clauses.children);
|
||||
return clauses;
|
||||
});
|
||||
|
||||
var iterationStatement = or(
|
||||
node('doStmnt', seq(token('do'), statement, token('while'),
|
||||
token('('), expression, token(')'),
|
||||
maybeSemicolon)),
|
||||
node('whileStmnt', seq(token('while'), token('('), expression,
|
||||
closeParenBeforeStatement, statement)),
|
||||
// semicolons must be real, not maybeSemicolons
|
||||
node('forStmnt', seq(
|
||||
token('for'), token('('), forSpec, closeParenBeforeStatement,
|
||||
statement)));
|
||||
|
||||
var returnStatement = node(
|
||||
'returnStmnt',
|
||||
seq(token('return'), or(
|
||||
and(noLineTerminatorHere, expression), constant(NIL)),
|
||||
maybeSemicolon));
|
||||
var continueStatement = node(
|
||||
'continueStmnt',
|
||||
seq(token('continue'), or(
|
||||
and(noLineTerminatorHere, tokenType('IDENTIFIER')), constant(NIL)),
|
||||
maybeSemicolon));
|
||||
var breakStatement = node(
|
||||
'breakStmnt',
|
||||
seq(token('break'), or(
|
||||
and(noLineTerminatorHere, tokenType('IDENTIFIER')), constant(NIL)),
|
||||
maybeSemicolon));
|
||||
var throwStatement = node(
|
||||
'throwStmnt',
|
||||
seq(token('throw'),
|
||||
and(or(noLineTerminatorHere,
|
||||
// If there is a line break here and more tokens after,
|
||||
// we want to error appropriately. `throw \n e` should
|
||||
// complain about the "end of line", not the `e`.
|
||||
and(not(lookAheadTokenType("EOF")),
|
||||
new Parser(null,
|
||||
function (t) {
|
||||
throw t.getParseError('expression', 'end of line');
|
||||
}))),
|
||||
expression),
|
||||
maybeSemicolon));
|
||||
|
||||
var withStatement = node(
|
||||
'withStmnt',
|
||||
seq(token('with'), token('('), expression, closeParenBeforeStatement,
|
||||
statement));
|
||||
|
||||
var switchCase = node(
|
||||
'case',
|
||||
seq(token('case'), expression, token(':'),
|
||||
or(lookAheadToken('}'),
|
||||
lookAheadToken('case default'),
|
||||
statements)));
|
||||
var switchDefault = node(
|
||||
'default',
|
||||
seq(token('default'), token(':'),
|
||||
or(lookAheadToken('}'),
|
||||
lookAheadToken('case'),
|
||||
statements)));
|
||||
|
||||
var switchStatement = node(
|
||||
'switchStmnt',
|
||||
seq(token('switch'), token('('), expression, token(')'),
|
||||
token('{'),
|
||||
or(lookAheadToken('}'),
|
||||
lookAheadToken('default'),
|
||||
list(switchCase)),
|
||||
opt(seq(switchDefault,
|
||||
opt(list(switchCase)))),
|
||||
token('}')));
|
||||
|
||||
var catchFinally = expecting(
|
||||
'catch',
|
||||
and(lookAheadToken('catch finally'),
|
||||
seq(
|
||||
or(node(
|
||||
'catch',
|
||||
seq(token('catch'), token('('), tokenType('IDENTIFIER'),
|
||||
token(')'), blockStatement)),
|
||||
constant(NIL)),
|
||||
or(node(
|
||||
'finally',
|
||||
seq(token('finally'), blockStatement)),
|
||||
constant(NIL)))));
|
||||
var tryStatement = node(
|
||||
'tryStmnt',
|
||||
seq(token('try'), blockStatement, catchFinally));
|
||||
var debuggerStatement = node(
|
||||
'debuggerStmnt', seq(token('debugger'), maybeSemicolon));
|
||||
|
||||
statement = expecting('statement',
|
||||
or(expressionOrLabelStatement,
|
||||
emptyStatement,
|
||||
blockStatement,
|
||||
variableStatement,
|
||||
ifStatement,
|
||||
iterationStatement,
|
||||
returnStatement,
|
||||
continueStatement,
|
||||
breakStatement,
|
||||
withStatement,
|
||||
switchStatement,
|
||||
throwStatement,
|
||||
tryStatement,
|
||||
debuggerStatement));
|
||||
|
||||
// PROGRAM
|
||||
|
||||
var functionDecl = node(
|
||||
'functionDecl', functionMaybeNameRequired[true]);
|
||||
|
||||
var sourceElement = or(functionDecl, statement);
|
||||
var sourceElements = list(sourceElement);
|
||||
|
||||
functionBody = expecting(
|
||||
'functionBody', or(lookAheadToken('}'), sourceElements));
|
||||
|
||||
var program = node(
|
||||
'program',
|
||||
seq(opt(sourceElements),
|
||||
// If not at EOF, complain "expecting statement"
|
||||
expecting('statement', lookAheadTokenType("EOF"))));
|
||||
|
||||
return program.parse(this);
|
||||
};
|
||||
|
||||
})();
|
||||
607
packages/jsparse/parser_tests.js
Normal file
607
packages/jsparse/parser_tests.js
Normal file
@@ -0,0 +1,607 @@
|
||||
|
||||
var allNodeNames = [
|
||||
";",
|
||||
"array",
|
||||
"assignment",
|
||||
"binary",
|
||||
"blockStmnt",
|
||||
"boolean",
|
||||
"bracket",
|
||||
"breakStmnt",
|
||||
"call",
|
||||
"case",
|
||||
"catch",
|
||||
"comma",
|
||||
"continueStmnt",
|
||||
"debuggerStmnt",
|
||||
"default",
|
||||
"doStmnt",
|
||||
"dot",
|
||||
"emptyStmnt",
|
||||
"expressionStmnt",
|
||||
"finally",
|
||||
"forInSpec",
|
||||
"forSpec",
|
||||
"forStmnt",
|
||||
"forVarInSpec",
|
||||
"forVarSpec",
|
||||
"functionDecl",
|
||||
"functionExpr",
|
||||
"idPropName",
|
||||
"identifier",
|
||||
"ifStmnt",
|
||||
"labelStmnt",
|
||||
"new",
|
||||
"newcall",
|
||||
"nil",
|
||||
"null",
|
||||
"numPropName",
|
||||
"number",
|
||||
"object",
|
||||
"parens",
|
||||
"postfix",
|
||||
"program",
|
||||
"prop",
|
||||
"regex",
|
||||
"returnStmnt",
|
||||
"strPropName",
|
||||
"string",
|
||||
"switchStmnt",
|
||||
"ternary",
|
||||
"this",
|
||||
"throwStmnt",
|
||||
"tryStmnt",
|
||||
"unary",
|
||||
"varDecl",
|
||||
"varStmnt",
|
||||
"whileStmnt",
|
||||
"withStmnt"
|
||||
];
|
||||
|
||||
var allNodeNamesSet = {};
|
||||
_.each(allNodeNames, function (n) { allNodeNamesSet[n] = true; });
|
||||
|
||||
|
||||
var makeTester = function (test) {
|
||||
return {
|
||||
// Parse code and make sure it matches expectedTreeString.
|
||||
goodParse: function (code, expectedTreeString, regexTokenHints) {
|
||||
var expectedTree = ParseNode.unstringify(expectedTreeString);
|
||||
|
||||
// first use lexer to collect all tokens
|
||||
var lexer = new JSLexer(code);
|
||||
var allTokensInOrder = [];
|
||||
while (! lexer.next().isEOF()) {
|
||||
var lex = lexer.lastLexeme;
|
||||
if (lex.isError())
|
||||
test.fail("Lexer error at " + lex.startPos());
|
||||
if (lex.isToken())
|
||||
allTokensInOrder.push(lex);
|
||||
if (regexTokenHints && regexTokenHints[allTokensInOrder.length])
|
||||
lexer.divisionPermitted = false;
|
||||
}
|
||||
|
||||
var parser = new JSParser(code);
|
||||
var actualTree = parser.getSyntaxTree();
|
||||
|
||||
var nextTokenIndex = 0;
|
||||
var check = function (tree) {
|
||||
if (tree instanceof ParseNode) {
|
||||
// This is a NODE (non-terminal).
|
||||
var nodeName = tree.name;
|
||||
if (! (nodeName && typeof nodeName === "string" &&
|
||||
allNodeNamesSet[nodeName] === true))
|
||||
test.fail("Not a node name: " + nodeName);
|
||||
_.each(tree.children, check);
|
||||
} else if (typeof tree === 'object' &&
|
||||
typeof tree.text === 'function') {
|
||||
// This is a TOKEN (terminal).
|
||||
// Make sure we are visiting every token once, in order.
|
||||
if (nextTokenIndex >= allTokensInOrder.length)
|
||||
test.fail("Too many tokens: " + (nextTokenIndex + 1));
|
||||
var referenceToken = allTokensInOrder[nextTokenIndex++];
|
||||
if (tree.text() !== referenceToken.text())
|
||||
test.fail(tree.text() + " !== " + referenceToken.text());
|
||||
if (tree.startPos() !== referenceToken.startPos())
|
||||
test.fail(tree.startPos() + " !== " + referenceToken.startPos());
|
||||
if (code.substring(tree.startPos(), tree.endPos()) !== tree.text())
|
||||
test.fail("Didn't see " + tree.text() + " at " + tree.startPos() +
|
||||
" in " + code);
|
||||
} else {
|
||||
test.fail("Unknown tree part: " + tree);
|
||||
}
|
||||
};
|
||||
|
||||
check(actualTree);
|
||||
if (nextTokenIndex !== allTokensInOrder.length)
|
||||
test.fail("Too few tokens: " + nextTokenIndex);
|
||||
|
||||
test.equal(parser.pos, code.length);
|
||||
|
||||
test.equal(ParseNode.stringify(actualTree),
|
||||
ParseNode.stringify(expectedTree), code);
|
||||
},
|
||||
// Takes code with part of it surrounding with backticks.
|
||||
// Removes the two backtick characters, tries to parse the code,
|
||||
// and then asserts that there was a tokenization-level error,
|
||||
// with the part that was between the backticks called out as
|
||||
// the bad token.
|
||||
//
|
||||
// For example, the test "123`@`" will try to parse "123@" and
|
||||
// assert that a tokenization error occurred at '@'.
|
||||
badToken: function (code) {
|
||||
var constructMessage = function (pos, text) {
|
||||
return "Bad token at position " + pos + ", text `" + text + "`";
|
||||
};
|
||||
var pos = code.indexOf('`');
|
||||
var text = code.match(/`(.*?)`/)[1];
|
||||
code = code.replace(/`/g, '');
|
||||
|
||||
var parsed = false;
|
||||
var error = null;
|
||||
try {
|
||||
var tree = new JSParser(code).getSyntaxTree();
|
||||
parsed = true;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
test.isFalse(parsed);
|
||||
test.isTrue(error);
|
||||
test.equal(error.message, constructMessage(pos, text));
|
||||
},
|
||||
// Takes code with a backtick-quoted string embedded in it.
|
||||
// Removes the backticks and their contents, tries to parse the code,
|
||||
// and then asserts that there was a parse error at the location
|
||||
// where the backtick-quoted string was embedded. The embedded
|
||||
// string must match whatever the error message says was "expected".
|
||||
//
|
||||
// For example, the test "{`statement`" will try to parse the code
|
||||
// "{" and then assert that an error occured at the end of the string
|
||||
// saying "Expected statement". The test "1 `semicolon`2" will try
|
||||
// to parse "1 2" and assert that the error "Expected semicolon"
|
||||
// appeared after the space and before the 2.
|
||||
//
|
||||
// A second backtick-quoted string is used as the "found" token
|
||||
// in the error message.
|
||||
badParse: function (code) {
|
||||
var constructMessage = function (whatExpected, pos, found, after) {
|
||||
return "Expected " + whatExpected + (after ? " after " + after : "") +
|
||||
" at position " + pos + ", found " + found;
|
||||
};
|
||||
var pos = code.indexOf('`');
|
||||
|
||||
var backticked = code.match(/`.*?`/g);
|
||||
var whatExpected = backticked[0] && backticked[0].slice(1,-1);
|
||||
var found = backticked[1] && backticked[1].slice(1, -1);
|
||||
code = code.replace(/`.*?`/g, '');
|
||||
|
||||
var parsed = false;
|
||||
var error = null;
|
||||
var parser = new JSParser(code);
|
||||
try {
|
||||
var tree = parser.getSyntaxTree();
|
||||
parsed = true;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
test.isFalse(parsed);
|
||||
test.isTrue(error);
|
||||
if (! parsed && error) {
|
||||
var after = parser.oldToken;
|
||||
found = (found || parser.newToken);
|
||||
test.equal(error.message,
|
||||
constructMessage(whatExpected, pos, found, after));
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Tinytest.add("jsparse - basics", function (test) {
|
||||
var tester = makeTester(test);
|
||||
tester.goodParse('1', "program(expressionStmnt(number(1) ;()))");
|
||||
tester.goodParse('1 + 1', "program(expressionStmnt(binary(number(1) + number(1)) ;()))");
|
||||
tester.goodParse('1*2+3*4', "program(expressionStmnt(binary(binary(number(1) * number(2)) + " +
|
||||
"binary(number(3) * number(4))) ;()))");
|
||||
tester.goodParse('1 + 1;', "program(expressionStmnt(binary(number(1) + number(1)) ;))");
|
||||
tester.goodParse('1 + 1;;', "program(expressionStmnt(binary(number(1) + number(1)) ;) emptyStmnt(;))");
|
||||
tester.goodParse('', "program()");
|
||||
tester.goodParse('\n', "program()");
|
||||
tester.goodParse(';;;\n\n;\n', "program(emptyStmnt(;) emptyStmnt(;) emptyStmnt(;) emptyStmnt(;))");
|
||||
tester.goodParse('foo', "program(expressionStmnt(identifier(foo) ;()))");
|
||||
tester.goodParse('foo();', "program(expressionStmnt(call(identifier(foo) `(` `)`) ;))");
|
||||
tester.goodParse('var x = 3', "program(varStmnt(var varDecl(x = number(3)) ;()))");
|
||||
tester.goodParse('++x;', "program(expressionStmnt(unary(++ identifier(x)) ;))");
|
||||
tester.goodParse('x++;', "program(expressionStmnt(postfix(identifier(x) ++) ;))");
|
||||
tester.goodParse(
|
||||
'throw new Error',
|
||||
"program(throwStmnt(throw new(new identifier(Error)) ;()))");
|
||||
tester.goodParse(
|
||||
'var x = function () { return 123; };',
|
||||
'program(varStmnt(var varDecl(x = functionExpr(function nil() `(` `)` ' +
|
||||
'{ returnStmnt(return number(123) ;) })) ;))');
|
||||
|
||||
tester.badParse("var x = `expression`");
|
||||
tester.badParse("1 `semicolon`1");
|
||||
tester.badParse("1+1`semicolon`:");
|
||||
});
|
||||
|
||||
Tinytest.add("jsparse - tokenization errors", function (test) {
|
||||
var tester = makeTester(test);
|
||||
tester.badToken("123`@`");
|
||||
tester.badToken("thisIsATestOf = `'unterminated `\n strings'");
|
||||
});
|
||||
|
||||
Tinytest.add("jsparse - syntax forms", function (test) {
|
||||
var tester = makeTester(test);
|
||||
var trials = [
|
||||
// STATEMENTS
|
||||
['1',
|
||||
'program(expressionStmnt(number(1) ;()))'],
|
||||
['1;;;;2',
|
||||
'program(expressionStmnt(number(1) ;) emptyStmnt(;) emptyStmnt(;) emptyStmnt(;) ' +
|
||||
'expressionStmnt(number(2) ;()))'],
|
||||
['{}',
|
||||
'program(blockStmnt({ }))'],
|
||||
['{null}',
|
||||
'program(blockStmnt({ expressionStmnt(null(null) ;()) }))'],
|
||||
['{\nfoo()\nbar();\n}',
|
||||
'program(blockStmnt({ expressionStmnt(call(identifier(foo) `(` `)`) ;()) ' +
|
||||
'expressionStmnt(call(identifier(bar) `(` `)`) ;) }))'],
|
||||
['{{{}}}',
|
||||
'program(blockStmnt({ blockStmnt({ blockStmnt({ }) }) }))'],
|
||||
['var x = y, z,\n a = b = c;',
|
||||
'program(varStmnt(var varDecl(x = identifier(y)) , varDecl(z) , varDecl(a = ' +
|
||||
'assignment(identifier(b) = identifier(c))) ;))'],
|
||||
['if (x === y);',
|
||||
'program(ifStmnt(if `(` binary(identifier(x) === identifier(y)) `)` emptyStmnt(;)))'],
|
||||
['if (z) return',
|
||||
'program(ifStmnt(if `(` identifier(z) `)` returnStmnt(return nil() ;())))'],
|
||||
['if (a) b; else c',
|
||||
'program(ifStmnt(if `(` identifier(a) `)` expressionStmnt(identifier(b) ;) else ' +
|
||||
'expressionStmnt(identifier(c) ;())))'],
|
||||
['if (n === 1) { foo(); } else if (n === 2) { bar(); } else { baz(); }',
|
||||
'program(ifStmnt(if `(` binary(identifier(n) === number(1)) `)` blockStmnt(' +
|
||||
'{ expressionStmnt(call(identifier(foo) `(` `)`) ;) }) else ifStmnt(' +
|
||||
'if `(` binary(identifier(n) === number(2)) `)` blockStmnt(' +
|
||||
'{ expressionStmnt(call(identifier(bar) `(` `)`) ;) }) else blockStmnt(' +
|
||||
'{ expressionStmnt(call(identifier(baz) `(` `)`) ;) }))))'],
|
||||
['while (false);',
|
||||
'program(whileStmnt(while `(` boolean(false) `)` emptyStmnt(;)))'],
|
||||
['while (/foo/.test(bar.baz)) {\n bar = bar.baz;\n}',
|
||||
'program(whileStmnt(while `(` call(dot(regex(/foo/) . test) `(` ' +
|
||||
'dot(identifier(bar) . baz) `)`) `)` blockStmnt({ expressionStmnt(' +
|
||||
'assignment(identifier(bar) = dot(identifier(bar) . baz)) ;) })))'],
|
||||
['while (false) while (false);',
|
||||
'program(whileStmnt(while `(` boolean(false) `)` ' +
|
||||
'whileStmnt(while `(` boolean (false) `)` emptyStmnt(;))))'],
|
||||
['do a; while (b);',
|
||||
'program(doStmnt(do expressionStmnt(identifier(a) ;) while `(` identifier(b) `)` ;))'],
|
||||
['do { x-- } while (x);',
|
||||
'program(doStmnt(do blockStmnt({ expressionStmnt(postfix(identifier(x) --) ;()) }) ' +
|
||||
'while `(` identifier(x) `)` ;))'],
|
||||
['do a\n while (b)\n x++',
|
||||
'program(doStmnt(do expressionStmnt(identifier(a) ;()) while `(` identifier(b) `)` ;()) ' +
|
||||
'expressionStmnt(postfix(identifier(x) ++) ;()))'],
|
||||
["for(;;);",
|
||||
"program(forStmnt(for `(` forSpec(nil() ; nil() ; nil()) `)` emptyStmnt(;)))"],
|
||||
["for(x in y);",
|
||||
"program(forStmnt(for `(` forInSpec(identifier(x) in identifier(y)) `)` emptyStmnt(;)))"],
|
||||
["for(var x in y);",
|
||||
"program(forStmnt(for `(` forVarInSpec(var varDecl(x) in identifier(y)) `)` emptyStmnt(;)))"],
|
||||
["for(var x;;);",
|
||||
"program(forStmnt(for `(` forVarSpec(var varDecl(x) ; nil() ; nil()) `)` emptyStmnt(;)))"],
|
||||
["for(var i=0;i<N;i++) {}",
|
||||
"program(forStmnt(for `(` forVarSpec(var varDecl(i = number(0)) ; " +
|
||||
"binary(identifier(i) < identifier(N)) ; postfix(identifier(i) ++)) `)` blockStmnt({ })))"],
|
||||
["for (var x=3 in y);",
|
||||
"program(forStmnt(for `(` forVarInSpec(var varDecl(x = number(3)) in identifier(y)) `)` " +
|
||||
"emptyStmnt(;)))"],
|
||||
["for (x.foo in y);",
|
||||
"program(forStmnt(for `(` forInSpec(dot(identifier(x) . foo) in identifier(y)) `)` emptyStmnt(;)))"],
|
||||
["return",
|
||||
"program(returnStmnt(return nil() ;()))"],
|
||||
["return;",
|
||||
"program(returnStmnt(return nil() ;))"],
|
||||
["return null",
|
||||
"program(returnStmnt(return null(null) ;()))"],
|
||||
["return null;",
|
||||
"program(returnStmnt(return null(null) ;))"],
|
||||
["return\n1+1",
|
||||
"program(returnStmnt(return nil() ;()) expressionStmnt(binary(number(1) + number(1)) ;()))"],
|
||||
["return 1\n +1",
|
||||
"program(returnStmnt(return binary(number(1) + number(1)) ;()))"],
|
||||
["continue",
|
||||
"program(continueStmnt(continue nil() ;()))"],
|
||||
["continue foo",
|
||||
"program(continueStmnt(continue foo ;()))"],
|
||||
["continue foo;",
|
||||
"program(continueStmnt(continue foo ;))"],
|
||||
["continue\n foo;",
|
||||
"program(continueStmnt(continue nil() ;()) expressionStmnt(identifier(foo) ;))"],
|
||||
["break",
|
||||
"program(breakStmnt(break nil() ;()))"],
|
||||
["break foo",
|
||||
"program(breakStmnt(break foo ;()))"],
|
||||
["break foo;",
|
||||
"program(breakStmnt(break foo ;))"],
|
||||
["break\n foo;",
|
||||
"program(breakStmnt(break nil() ;()) expressionStmnt(identifier(foo) ;))"],
|
||||
["throw e;",
|
||||
"program(throwStmnt(throw identifier(e) ;))"],
|
||||
["throw e",
|
||||
"program(throwStmnt(throw identifier(e) ;()))"],
|
||||
["throw new Error;",
|
||||
"program(throwStmnt(throw new(new identifier(Error)) ;))"],
|
||||
["with(x);",
|
||||
"program(withStmnt(with `(` identifier(x) `)` emptyStmnt(;)))"],
|
||||
["with(a=b) {}",
|
||||
"program(withStmnt(with `(` assignment(identifier(a) = identifier(b)) `)` blockStmnt({ })))"],
|
||||
["switch(x) {}",
|
||||
"program(switchStmnt(switch `(` identifier(x) `)` { }))"],
|
||||
["switch(x) {case 1:case 2:case 3:default:case 4:}",
|
||||
"program(switchStmnt(switch `(` identifier(x) `)` { " +
|
||||
"case(case number(1) :) case(case number(2) :) case(case number(3) :) " +
|
||||
"default(default :) case(case number(4) :) }))"],
|
||||
["switch(x) {\ncase 1:\n return\ncase 2:\ncase 3:\n throw e}",
|
||||
"program(switchStmnt(switch `(` identifier(x) `)` { " +
|
||||
"case(case number(1) : returnStmnt(return nil() ;())) " +
|
||||
"case(case number(2) :) case(case number(3) : " +
|
||||
"throwStmnt(throw identifier(e) ;())) }))"],
|
||||
["switch(x) {default:;}",
|
||||
"program(switchStmnt(switch `(` identifier(x) `)` { default(default : emptyStmnt(;)) }))"],
|
||||
["try {} catch (e) {} finally {}",
|
||||
"program(tryStmnt(try blockStmnt({ }) catch(catch `(` e `)` blockStmnt({ })) " +
|
||||
"finally(finally blockStmnt({ }))))"],
|
||||
["try {} finally {}",
|
||||
"program(tryStmnt(try blockStmnt({ }) nil() finally(finally blockStmnt({ }))))"],
|
||||
["try {} catch (e) {}",
|
||||
"program(tryStmnt(try blockStmnt({ }) catch(catch `(` e `)` blockStmnt({ })) nil()))"],
|
||||
["a:;",
|
||||
"program(labelStmnt(a : emptyStmnt(;)))"],
|
||||
["{x:1}",
|
||||
"program(blockStmnt({ labelStmnt(x : expressionStmnt(number(1) ;())) }))"],
|
||||
["{x:y:z:1}",
|
||||
"program(blockStmnt({ labelStmnt(x : labelStmnt(y : " +
|
||||
"labelStmnt(z : expressionStmnt(number(1) ;())))) }))"],
|
||||
[";;foo:\nfor(;;);",
|
||||
"program(emptyStmnt(;) emptyStmnt(;) labelStmnt(foo : " +
|
||||
"forStmnt(for `(` forSpec(nil() ; nil() ; nil()) `)` emptyStmnt(;))))"],
|
||||
["debugger",
|
||||
"program(debuggerStmnt(debugger ;()))"],
|
||||
["debugger;",
|
||||
"program(debuggerStmnt(debugger ;))"],
|
||||
["function foo() {}",
|
||||
"program(functionDecl(function foo `(` `)` { }))"],
|
||||
["function foo() {function bar() {}}",
|
||||
"program(functionDecl(function foo `(` `)` { functionDecl(function bar `(` `)` { }) }))"],
|
||||
[";;function f() {};;",
|
||||
"program(emptyStmnt(;) emptyStmnt(;) functionDecl(function f `(` `)` { }) " +
|
||||
"emptyStmnt(;) emptyStmnt(;))"],
|
||||
["function foo(a,b,c) {}",
|
||||
"program(functionDecl(function foo `(` a , b , c `)` { }))"],
|
||||
|
||||
// EXPRESSIONS
|
||||
["null + this - 3 + true",
|
||||
"program(expressionStmnt(binary(binary(binary(null(null) + this(this)) - " +
|
||||
"number(3)) + boolean(true)) ;()))"],
|
||||
["a / /b/mgi / c",
|
||||
"program(expressionStmnt(binary(binary(identifier(a) / " +
|
||||
"regex(/b/mgi)) / identifier(c)) ;()))"],
|
||||
["'a' + \"\" + \"b\" + '\\''",
|
||||
"program(expressionStmnt(binary(binary(binary(string('a') + string(\"\")) + " +
|
||||
"string(\"b\")) + string('\\'')) ;()))"],
|
||||
["_ + x0123 + $",
|
||||
"program(expressionStmnt(binary(binary(identifier(_) + " +
|
||||
"identifier(x0123)) + identifier($)) ;()))"],
|
||||
["if ((x = 1)) return ((1+2))*((1<<2));",
|
||||
"program(ifStmnt(if `(` parens(`(` assignment(identifier(x) = number(1)) `)`) " +
|
||||
"`)` returnStmnt(return binary(parens(`(` parens(`(` binary(number(1) + " +
|
||||
"number(2)) `)`) `)`) * parens(`(` parens(`(` binary(number(1) << number(2)) " +
|
||||
"`)`) `)`)) ;)))"],
|
||||
["[];",
|
||||
"program(expressionStmnt(array([ ]) ;))"],
|
||||
["[,,,];",
|
||||
"program(expressionStmnt(array([ , , , ]) ;))"],
|
||||
["[(1,2),,3];",
|
||||
"program(expressionStmnt(array([ parens(`(` comma(number(1) , " +
|
||||
"number(2)) `)`) , , number(3) ]) ;))"],
|
||||
["({});",
|
||||
"program(expressionStmnt(parens(`(` object({ }) `)`) ;))"],
|
||||
["({1:1});",
|
||||
"program(expressionStmnt(parens(`(` object({ prop(numPropName(1) : number(1)) }) `)`) ;))"],
|
||||
["({x:true});",
|
||||
"program(expressionStmnt(parens(`(` object({ prop(idPropName(x) : boolean(true)) }) `)`) ;))"],
|
||||
["({'a':b, c:'d', 1:null});",
|
||||
"program(expressionStmnt(parens(`(` object({ prop(strPropName('a') : " +
|
||||
"identifier(b)) , prop(idPropName(c) : string('d')) , prop(numPropName(1) " +
|
||||
": null(null)) }) `)`) ;))"],
|
||||
["(function () {});",
|
||||
"program(expressionStmnt(parens(`(` functionExpr(function nil() `(` `)` { }) `)`) ;))"],
|
||||
["(function foo() {});",
|
||||
"program(expressionStmnt(parens(`(` functionExpr(function foo `(` `)` { }) `)`) ;))"],
|
||||
["x = function () {}.y;",
|
||||
"program(expressionStmnt(assignment(identifier(x) = dot(functionExpr(" +
|
||||
"function nil() `(` `)` { }) . y)) ;))"],
|
||||
["(function (a) {})",
|
||||
"program(expressionStmnt(parens(`(` functionExpr(function nil() " +
|
||||
"`(` a `)` { }) `)`) ;()))"],
|
||||
["(function (a,b,c) {})",
|
||||
"program(expressionStmnt(parens(`(` functionExpr(function nil() `(` " +
|
||||
"a , b , c `)` { }) `)`) ;()))"],
|
||||
["foo.bar.baz;",
|
||||
"program(expressionStmnt(dot(dot(identifier(foo) . bar) . baz) ;))"],
|
||||
["foo[bar,bar][baz].qux[1+1];",
|
||||
"program(expressionStmnt(bracket(dot(bracket(bracket(identifier(foo) " +
|
||||
"[ comma(identifier(bar) , identifier(bar)) ]) [ identifier(baz) ]) . qux) " +
|
||||
"[ binary(number(1) + number(1)) ]) ;))"],
|
||||
["new new a.b.c[d]",
|
||||
"program(expressionStmnt(new(new new(new bracket(dot(dot(identifier(a) " +
|
||||
". b) . c) [ identifier(d) ]))) ;()))"],
|
||||
["new new a.b.c[d]()",
|
||||
"program(expressionStmnt(new(new newcall(new " +
|
||||
"bracket(dot(dot(identifier(a) . b) . c) [ identifier(d) ]) `(` `)`)) ;()))"],
|
||||
["new new a.b.c[d]()()",
|
||||
"program(expressionStmnt(newcall(new newcall(new " +
|
||||
"bracket(dot(dot(identifier(a) . b) . c) [ identifier(d) ]) `(` `)`) `(` `)`) ;()))"],
|
||||
["new foo(x).bar(y)",
|
||||
"program(expressionStmnt(call(dot(newcall(new identifier(foo) `(` " +
|
||||
"identifier(x) `)`) . bar) `(` identifier(y) `)`) ;()))"],
|
||||
["new new foo().bar",
|
||||
"program(expressionStmnt(new(new dot(newcall(new identifier(foo) `(` `)`) . bar)) ;()))"],
|
||||
["delete void typeof - + ~ ! -- ++ x;",
|
||||
"program(expressionStmnt(unary(delete unary(void unary(typeof unary(- unary(+ " +
|
||||
"unary(~ unary(! unary(-- unary(++ identifier(x)))))))))) ;))"],
|
||||
["x++ + ++y",
|
||||
"program(expressionStmnt(binary(postfix(identifier(x) ++) + " +
|
||||
"unary(++ identifier(y))) ;()))"],
|
||||
["1*2+3*4",
|
||||
"program(expressionStmnt(binary(binary(number(1) * number(2)) " +
|
||||
"+ binary(number(3) * number(4))) ;()))"],
|
||||
["a*b/c%d+e-f<<g>>h>>>i<j>k<=l>=m instanceof n in o==p!=q===r!==s&t^u|v&&w||x",
|
||||
"program(expressionStmnt(binary(binary(binary(binary(binary(binary(binary(" +
|
||||
"binary(binary(binary(binary(binary(binary(binary(binary(binary(binary(binary(" +
|
||||
"binary(binary(binary(binary(binary(identifier(a) * identifier(b)) / " +
|
||||
"identifier(c)) % identifier(d)) + identifier(e)) - identifier(f)) << identifier(g)) " +
|
||||
">> identifier(h)) >>> identifier(i)) < identifier(j)) > identifier(k)) <= " +
|
||||
"identifier(l)) >= identifier(m)) instanceof identifier(n)) in identifier(o)) == " +
|
||||
"identifier(p)) != identifier(q)) === identifier(r)) !== identifier(s)) & " +
|
||||
"identifier(t)) ^ identifier(u)) | identifier(v)) && identifier(w)) || " +
|
||||
"identifier(x)) ;()))"],
|
||||
["a||b&&c|d^e&f!==g===h!=i==j in k instanceof l>=m<=n<o<p>>>q>>r<<s-t+u%v/w*x",
|
||||
"program(expressionStmnt(binary(identifier(a) || binary(identifier(b) && " +
|
||||
"binary(identifier(c) | binary(identifier(d) ^ binary(identifier(e) & " +
|
||||
"binary(binary(binary(binary(identifier(f) !== identifier(g)) === identifier(h)) " +
|
||||
"!= identifier(i)) == binary(binary(binary(binary(binary(binary(identifier(j) in " +
|
||||
"identifier(k)) instanceof identifier(l)) >= identifier(m)) <= identifier(n)) < " +
|
||||
"identifier(o)) < binary(binary(binary(identifier(p) >>> identifier(q)) >> " +
|
||||
"identifier(r)) << binary(binary(identifier(s) - identifier(t)) + " +
|
||||
"binary(binary(binary(identifier(u) % identifier(v)) / identifier(w)) * " +
|
||||
"identifier(x))))))))))) ;()))"],
|
||||
["a?b:c",
|
||||
"program(expressionStmnt(ternary(identifier(a) ? identifier(b) : " +
|
||||
"identifier(c)) ;()))"],
|
||||
["1==2?3=4:5=6",
|
||||
"program(expressionStmnt(ternary(binary(number(1) == number(2)) ? " +
|
||||
"assignment(number(3) = number(4)) : assignment(number(5) = number(6))) ;()))"],
|
||||
["a=b,c=d",
|
||||
"program(expressionStmnt(comma(assignment(identifier(a) = identifier(b)) , " +
|
||||
"assignment(identifier(c) = identifier(d))) ;()))"],
|
||||
["a=b=c=d",
|
||||
"program(expressionStmnt(assignment(identifier(a) = assignment(identifier(b) " +
|
||||
"= assignment(identifier(c) = identifier(d)))) ;()))"],
|
||||
["x[0]=x[1]=true",
|
||||
"program(expressionStmnt(assignment(bracket(identifier(x) [ number(0) ]) = " +
|
||||
"assignment(bracket(identifier(x) [ number(1) ]) = boolean(true))) ;()))"],
|
||||
["a*=b/=c%=d+=e-=f<<=g>>=h>>>=i&=j^=k|=l",
|
||||
"program(expressionStmnt(assignment(identifier(a) *= assignment(identifier(b) " +
|
||||
"/= assignment(identifier(c) %= assignment(identifier(d) += " +
|
||||
"assignment(identifier(e) -= assignment(identifier(f) <<= " +
|
||||
"assignment(identifier(g) >>= assignment(identifier(h) >>>= " +
|
||||
"assignment(identifier(i) &= assignment(identifier(j) ^= " +
|
||||
"assignment(identifier(k) |= identifier(l)))))))))))) ;()))"],
|
||||
["1;\n\n\n\n/* foo */\n// bar\n", // trailing whitespace and comments
|
||||
"program(expressionStmnt(number(1) ;))"]
|
||||
];
|
||||
_.each(trials, function (tr) {
|
||||
tester.goodParse(tr[0], tr[1]);
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("jsparse - bad parses", function (test) {
|
||||
var tester = makeTester(test);
|
||||
var trials = [
|
||||
'{`statement`',
|
||||
'if (`expression`)',
|
||||
'if `(`else',
|
||||
'var`varDecl`;',
|
||||
'while (`expression`);',
|
||||
'while`(`;',
|
||||
'do a `semicolon`while b;',
|
||||
'do a\n while `(`b;',
|
||||
'1 `semicolon`2',
|
||||
'for (`forSpec`);',
|
||||
'for (1\n`semicolon`2\n3);',
|
||||
'continue `semicolon`1+1;',
|
||||
'break `semicolon`1+1;',
|
||||
'throw`expression`',
|
||||
'throw`expression`;',
|
||||
'throw\n`expression`',
|
||||
'throw\n`expression``end of line`e',
|
||||
'throw `expression`=;',
|
||||
'with(`expression`);',
|
||||
'switch(`expression`)',
|
||||
'switch(x)`{`;',
|
||||
'try`block`',
|
||||
'try {}`catch`',
|
||||
'try {} catch`(`;',
|
||||
'try {} catch(e)`block`;',
|
||||
'1+1`semicolon`:',
|
||||
'{a:`statement`}',
|
||||
'function `IDENTIFIER`() {}',
|
||||
'foo: `statement`function foo() {}',
|
||||
'[`expression`=',
|
||||
'[,,`expression`=',
|
||||
'({`propertyName`true:3})',
|
||||
'({1:2,3`:`})',
|
||||
'({1:2,`propertyName`',
|
||||
'x.`IDENTIFIER`true',
|
||||
'foo;`semicolon`:;',
|
||||
'1;`statement`=',
|
||||
'a+b`semicolon`=c;',
|
||||
'for(1+1 `semicolon`in {});',
|
||||
'`statement`=',
|
||||
'for(;`expression`var;) {}'
|
||||
];
|
||||
_.each(trials, function (tr) {
|
||||
tester.badParse(tr);
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("jsparse - regex division ambiguity", function (test) {
|
||||
var tester = makeTester(test);
|
||||
tester.goodParse("if (e) /f/g;",
|
||||
"program(ifStmnt(if `(` identifier(e) `)` expressionStmnt(regex(/f/g) ;)))",
|
||||
{4: true});
|
||||
tester.goodParse("++/x/.y;",
|
||||
"program(expressionStmnt(unary(++ dot(regex(/x/) . y)) ;))",
|
||||
{1: true});
|
||||
tester.goodParse("x++/2/g;",
|
||||
"program(expressionStmnt(binary(binary(postfix(identifier(x) ++) / " +
|
||||
"number(2)) / identifier(g)) ;))");
|
||||
tester.goodParse("(1+1)/2/g;",
|
||||
"program(expressionStmnt(binary(binary(parens(`(` binary(number(1) + " +
|
||||
"number(1)) `)`) / " +
|
||||
"number(2)) / identifier(g)) ;))");
|
||||
tester.goodParse("/x/",
|
||||
"program(expressionStmnt(regex(/x/) ;()))");
|
||||
});
|
||||
|
||||
Tinytest.add("jsparse - semicolon insertion", function (test) {
|
||||
var tester = makeTester(test);
|
||||
// Spec section 7.9.2
|
||||
tester.badParse("{ 1 `semicolon`2 } 3");
|
||||
tester.goodParse("{ 1\n2 } 3", "program(blockStmnt({ expressionStmnt(number(1) " +
|
||||
";()) expressionStmnt(number(2) ;()) }) expressionStmnt(number(3) ;()))");
|
||||
tester.badParse("for (a; b\n`semicolon`)");
|
||||
tester.goodParse("return\na + b",
|
||||
"program(returnStmnt(return nil() ;()) " +
|
||||
"expressionStmnt(binary(identifier(a) + identifier(b)) ;()))");
|
||||
tester.goodParse("a = b\n++c",
|
||||
"program(expressionStmnt(assignment(identifier(a) = identifier(b)) ;())" +
|
||||
"expressionStmnt(unary(++ identifier(c)) ;()))");
|
||||
tester.badParse("if (a > b)\n`statement`else c = d");
|
||||
tester.goodParse("a = b + c\n(d + e).print()",
|
||||
"program(expressionStmnt(assignment(identifier(a) = " +
|
||||
"binary(identifier(b) + call(dot(call(identifier(c) `(` " +
|
||||
"binary(identifier(d) + identifier(e)) `)`) . print) `(` `)`))) ;()))");
|
||||
});
|
||||
|
||||
Tinytest.add("jsparse - comments", function (test) {
|
||||
var tester = makeTester(test);
|
||||
// newline in multi-line comment makes it into a line break for semicolon
|
||||
// insertion purposes
|
||||
tester.badParse("1/**/`semicolon`2");
|
||||
tester.goodParse("1/*\n*/2",
|
||||
"program(expressionStmnt(number(1) ;()) expressionStmnt(number(2) ;()))");
|
||||
});
|
||||
264
packages/jsparse/parserlib.js
Normal file
264
packages/jsparse/parserlib.js
Normal file
@@ -0,0 +1,264 @@
|
||||
///// TOKENIZER AND PARSER COMBINATORS
|
||||
|
||||
(function () {
|
||||
|
||||
// XXX track line/col position, for errors and maybe token info
|
||||
|
||||
var isArray = function (obj) {
|
||||
return obj && (typeof obj === 'object') && (typeof obj.length === 'number');
|
||||
};
|
||||
|
||||
ParseNode = function (name, children) {
|
||||
this.name = name;
|
||||
this.children = children;
|
||||
|
||||
if (! isArray(children))
|
||||
throw new Error("Expected array in new ParseNode(" + name + ", ...)");
|
||||
};
|
||||
|
||||
|
||||
Parser = function (expecting, runFunc) {
|
||||
this.expecting = expecting;
|
||||
this._run = runFunc;
|
||||
};
|
||||
|
||||
Parser.prototype.parse = function (t) {
|
||||
return this._run(t);
|
||||
};
|
||||
|
||||
Parser.prototype.parseRequired = function (t) {
|
||||
return this.parseRequiredIf(t, true);
|
||||
};
|
||||
|
||||
Parser.prototype.parseRequiredIf = function (t, required) {
|
||||
var result = this._run(t);
|
||||
|
||||
if (required && ! result)
|
||||
throw t.getParseError(this.expecting);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Parser.expecting = function (expecting, parser) {
|
||||
return new Parser(expecting, parser._run);
|
||||
};
|
||||
|
||||
|
||||
// A parser that consume()s has to succeed.
|
||||
// Similarly, a parser that fails can't have consumed.
|
||||
|
||||
Parsers = {};
|
||||
|
||||
Parsers.assertion = function (test) {
|
||||
return new Parser(
|
||||
null, function (t) {
|
||||
return test(t) ? [] : null;
|
||||
});
|
||||
};
|
||||
|
||||
Parsers.node = function (name, childrenParser) {
|
||||
return new Parser(name, function (t) {
|
||||
var children = childrenParser.parse(t);
|
||||
if (! children)
|
||||
return null;
|
||||
if (! isArray(children))
|
||||
children = [children];
|
||||
return new ParseNode(name, children);
|
||||
});
|
||||
};
|
||||
|
||||
Parsers.or = function (/*parsers*/) {
|
||||
var args = arguments;
|
||||
return new Parser(
|
||||
args[args.length - 1].expecting,
|
||||
function (t) {
|
||||
var result;
|
||||
for(var i = 0, N = args.length; i < N; i++) {
|
||||
result = args[i].parse(t);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
// Parses a left-recursive expression with zero or more occurrences
|
||||
// of a binary op. Leaves the term unwrapped if no op. For example
|
||||
// (in a hypothetical use case):
|
||||
// `1` => "1"
|
||||
// `1+2` => ["binary", "1", "+", "2"]
|
||||
// `1+2+3` => ["binary", ["binary", "1", "+", "2"], "+", "3"]
|
||||
//
|
||||
// opParsers is an array of op parsers from high to low
|
||||
// precedence (tightest-binding first)
|
||||
Parsers.binaryLeft = function (name, termParser, opParsers) {
|
||||
var opParser;
|
||||
|
||||
if (opParsers.length === 1) {
|
||||
// take single opParser out of its array
|
||||
opParser = opParsers[0];
|
||||
} else {
|
||||
// pop off last opParser (non-destructively) and replace
|
||||
// termParser with a recursive binaryLeft on the remaining
|
||||
// ops.
|
||||
termParser = Parsers.binaryLeft(name, termParser, opParsers.slice(0, -1));
|
||||
opParser = opParsers[opParsers.length - 1];
|
||||
}
|
||||
|
||||
return new Parser(
|
||||
termParser.expecting,
|
||||
function (t) {
|
||||
var result = termParser.parse(t);
|
||||
if (! result)
|
||||
return null;
|
||||
|
||||
var op;
|
||||
while ((op = opParser.parse(t))) {
|
||||
result = new ParseNode(
|
||||
name,
|
||||
[result, op, termParser.parseRequired(t)]);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
Parsers.unary = function (name, termParser, opParser) {
|
||||
var unaryList = Parsers.opt(Parsers.list(opParser));
|
||||
return new Parser(
|
||||
termParser.expecting,
|
||||
function (t) {
|
||||
var unaries = unaryList.parse(t);
|
||||
// if we have unaries, we are committed and
|
||||
// have to match a term or error.
|
||||
var result = termParser.parseRequiredIf(t, unaries.length);
|
||||
if (! result)
|
||||
return null;
|
||||
|
||||
while (unaries.length)
|
||||
result = new ParseNode(name, [unaries.pop(), result]);
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
// Parses a list of one or more items with a separator, listing the
|
||||
// items and separators. (Separator is optional.) For example:
|
||||
// `x` => ["x"]
|
||||
// `x,y` => ["x", ",", "y"]
|
||||
// `x,y,z` => ["x", ",", "y", ",", "z"]
|
||||
// Unpacks.
|
||||
Parsers.list = function (itemParser, sepParser) {
|
||||
var push = function(array, newThing) {
|
||||
if (isArray(newThing))
|
||||
array.push.apply(array, newThing);
|
||||
else
|
||||
array.push(newThing);
|
||||
};
|
||||
return new Parser(
|
||||
itemParser.expecting,
|
||||
function (t) {
|
||||
var result = [];
|
||||
var firstItem = itemParser.parse(t);
|
||||
if (! firstItem)
|
||||
return null;
|
||||
push(result, firstItem);
|
||||
|
||||
if (sepParser) {
|
||||
var sep;
|
||||
while ((sep = sepParser.parse(t))) {
|
||||
push(result, sep);
|
||||
push(result, itemParser.parseRequired(t));
|
||||
}
|
||||
} else {
|
||||
var item;
|
||||
while ((item = itemParser.parse(t)))
|
||||
push(result, item);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
// Unpacks arrays (nested seqs).
|
||||
Parsers.seq = function (/*parsers*/) {
|
||||
var args = arguments;
|
||||
if (! args.length)
|
||||
return Parsers.constant([]);
|
||||
|
||||
return new Parser(
|
||||
args[0].expecting,
|
||||
function (t) {
|
||||
var result = [];
|
||||
for (var i = 0, N = args.length; i < N; i++) {
|
||||
// first item in sequence can fail, and we
|
||||
// fail (without error); after that, error on failure
|
||||
var r = args[i].parseRequiredIf(t, i > 0);
|
||||
if (! r)
|
||||
return null;
|
||||
|
||||
if (isArray(r)) // append array!
|
||||
result.push.apply(result, r);
|
||||
else
|
||||
result.push(r);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
// parsers except last must never consume
|
||||
Parsers.and = function (/*parsers*/) {
|
||||
var args = arguments;
|
||||
if (! args.length)
|
||||
return Parsers.constant([]);
|
||||
|
||||
return new Parser(
|
||||
args[args.length - 1].expecting,
|
||||
function (t) {
|
||||
var result;
|
||||
for(var i = 0, N = args.length; i < N; i++) {
|
||||
result = args[i].parse(t);
|
||||
if (! result)
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
// parser must not consume
|
||||
Parsers.not = function (parser) {
|
||||
return new Parser(
|
||||
null,
|
||||
function (t) {
|
||||
return parser.parse(t) ? null : [];
|
||||
});
|
||||
};
|
||||
|
||||
// parser that looks at nothing and returns result
|
||||
Parsers.constant = function (result) {
|
||||
return new Parser(null,
|
||||
function (t) { return result; });
|
||||
};
|
||||
|
||||
Parsers.opt = function (parser) {
|
||||
return Parser.expecting(
|
||||
parser.expecting,
|
||||
Parsers.or(parser, Parsers.seq()));
|
||||
};
|
||||
|
||||
Parsers.mapResult = function (parser, func) {
|
||||
return new Parser(
|
||||
parser.expecting,
|
||||
function (t) {
|
||||
var v = parser.parse(t);
|
||||
return v ? func(v, t) : null;
|
||||
});
|
||||
};
|
||||
|
||||
Parsers.lazy = function (expecting, parserFunc) {
|
||||
var inner = null;
|
||||
return new Parser(expecting, function (t) {
|
||||
if (! inner)
|
||||
inner = parserFunc();
|
||||
return inner.parse(t);
|
||||
});
|
||||
};
|
||||
|
||||
})();
|
||||
122
packages/jsparse/stringify.js
Normal file
122
packages/jsparse/stringify.js
Normal file
@@ -0,0 +1,122 @@
|
||||
(function() {
|
||||
|
||||
// The "tree string" format is a simple format for representing syntax trees.
|
||||
//
|
||||
// For example, the parse of `x++;` is written as:
|
||||
// "program(expressionStmnt(postfix(identifier(x) ++) ;))"
|
||||
//
|
||||
// A Node is written as "name(item1 item2 item3)", with additional whitespace
|
||||
// allowed anywhere between the name, parentheses, and items.
|
||||
//
|
||||
// Tokens don't need to be escaped unless they contain '(', ')', whitespace, or
|
||||
// backticks, or are empty. If they do, they can be written enclosed in backticks.
|
||||
// To escape a backtick within backticks, double it.
|
||||
//
|
||||
// `stringify` generates "canonical" tree strings, which have no extra escaping
|
||||
// or whitespace, just one space between items in a Node.
|
||||
|
||||
|
||||
ParseNode.prototype.stringify = function () {
|
||||
return ParseNode.stringify(this);
|
||||
};
|
||||
|
||||
var backtickEscape = function (str) {
|
||||
if (/[\s()`]/.test(str))
|
||||
return '`' + str.replace(/`/g, '``') + '`';
|
||||
else if (! str)
|
||||
return '``';
|
||||
else
|
||||
return str;
|
||||
};
|
||||
|
||||
var backtickUnescape = function (str) {
|
||||
if (str.charAt(0) === '`') {
|
||||
if (str.length === 1 || str.slice(-1) !== '`')
|
||||
throw new Error("Mismatched ` in " + str);
|
||||
if (str.length === 2)
|
||||
str = '';
|
||||
else
|
||||
str = str.slice(1, -1).replace(/``/g, '`');
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
ParseNode.stringify = function (tree) {
|
||||
if (tree instanceof ParseNode) {
|
||||
var str = backtickEscape(tree.name);
|
||||
str += '(';
|
||||
var escapedChildren = [];
|
||||
for(var i = 0, N = tree.children.length; i < N; i++)
|
||||
escapedChildren.push(ParseNode.stringify(tree.children[i]));
|
||||
str += escapedChildren.join(' ');
|
||||
str += ')';
|
||||
return str;
|
||||
}
|
||||
|
||||
// Treat a token object or string as a token.
|
||||
if (typeof tree.text === 'function')
|
||||
tree = tree.text();
|
||||
else if (typeof tree.text === 'string')
|
||||
tree = tree.text;
|
||||
return backtickEscape(String(tree));
|
||||
};
|
||||
|
||||
ParseNode.unstringify = function (str) {
|
||||
var lexemes = str.match(/\(|\)|`([^`]||``)*`|`|[^\s()`]+/g) || [];
|
||||
var N = lexemes.length;
|
||||
var state = {
|
||||
i: 0,
|
||||
getParseError: function (expecting) {
|
||||
throw new Error("unstringify: Expecting " + expecting +", found " +
|
||||
(lexemes[this.i] || "end of string"));
|
||||
},
|
||||
peek: function () { return lexemes[this.i]; },
|
||||
advance: function () { this.i++; }
|
||||
};
|
||||
var paren = function (chr) {
|
||||
return new Parser(chr, function (t) {
|
||||
if (t.peek() !== chr)
|
||||
return null;
|
||||
t.advance();
|
||||
return chr;
|
||||
});
|
||||
};
|
||||
var EMPTY_STRING = [""];
|
||||
var token = new Parser('token', function (t) {
|
||||
var txt = t.peek();
|
||||
if (!txt || txt.charAt(0) === '(' || txt.charAt(0) === ')')
|
||||
return null;
|
||||
|
||||
t.advance();
|
||||
// can't return falsy value from successful parser
|
||||
return backtickUnescape(txt) || EMPTY_STRING;
|
||||
});
|
||||
|
||||
// Make "item" lazy so it can be recursive.
|
||||
var item = Parsers.lazy('token', function () { return item; });
|
||||
|
||||
// Parse a single node or token.
|
||||
item = Parsers.mapResult(
|
||||
Parsers.seq(token,
|
||||
Parsers.opt(Parsers.seq(
|
||||
paren('('), Parsers.opt(Parsers.list(item)), paren(')')))),
|
||||
function (v) {
|
||||
for(var i = 0, N = v.length; i < N; i++)
|
||||
if (v[i] === EMPTY_STRING)
|
||||
v[i] = "";
|
||||
|
||||
if (v.length === 1)
|
||||
// token
|
||||
return v[0];
|
||||
// node. exclude parens
|
||||
return new ParseNode(v[0], v.slice(2, -1));
|
||||
});
|
||||
|
||||
var endOfString = new Parser("end of string", function (t) {
|
||||
return t.i === N ? [] : null;
|
||||
});
|
||||
|
||||
return Parsers.seq(item, endOfString).parseRequired(state)[0];
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -2,13 +2,12 @@
|
||||
Tinytest.add("less - presence", function(test) {
|
||||
|
||||
var d = OnscreenDiv(Meteor.render(function() {
|
||||
return '<p class="less-unlucky-left-border"></p>'; }));
|
||||
return '<p class="less-dashy-left-border"></p>'; }));
|
||||
d.node().style.display = 'block';
|
||||
|
||||
var p = d.node().firstChild;
|
||||
var leftBorder = getStyleProperty(p, 'border-left-width');
|
||||
test.equal(leftBorder, "13px");
|
||||
var leftBorder = getStyleProperty(p, 'border-left-style');
|
||||
test.equal(leftBorder, "dashed");
|
||||
|
||||
d.kill();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
#less-tests { zoom: 1; /* prop this rule open */ }
|
||||
|
||||
@unlucky: 13px;
|
||||
@dashy: dashed;
|
||||
|
||||
.less-unlucky-left-border {
|
||||
border-left: @unlucky solid white;
|
||||
.less-dashy-left-border {
|
||||
border-left: 1px @dashy black;
|
||||
}
|
||||
|
||||
@@ -337,7 +337,13 @@ _.extend(Meteor._LivedataSession.prototype, {
|
||||
// Store a function to re-run the handler in case we want to rerun
|
||||
// subscriptions, for example when the current user id changes
|
||||
sub._runHandler = function() {
|
||||
var res = handler.apply(sub, params || []);
|
||||
try {
|
||||
var res = handler.apply(sub, params || []);
|
||||
} catch (e) {
|
||||
Meteor._debug("Internal exception while starting subscription", sub_id,
|
||||
e.stack);
|
||||
return;
|
||||
}
|
||||
|
||||
// if Meteor._RemoteCollectionDriver is available (defined in
|
||||
// mongo-livedata), automatically wire up handlers that return a
|
||||
|
||||
@@ -40,6 +40,11 @@ Package.on_use(function (api, where) {
|
||||
api.use('underscore', ['client', 'server']);
|
||||
api.add_files('dynamics_browser.js', 'client');
|
||||
api.add_files('dynamics_nodejs.js', 'server');
|
||||
|
||||
// note server before common. usually it is the other way around, but
|
||||
// in this case server must load first.
|
||||
api.add_files('url_server.js', 'server');
|
||||
api.add_files('url_common.js', ['client', 'server']);
|
||||
});
|
||||
|
||||
Package.on_test(function (api) {
|
||||
@@ -50,4 +55,6 @@ Package.on_test(function (api) {
|
||||
|
||||
api.add_files('helpers_test.js', ['client', 'server']);
|
||||
api.add_files('dynamics_test.js', ['client', 'server']);
|
||||
|
||||
api.add_files('url_tests.js', ['client', 'server']);
|
||||
});
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
!/http:\/\/127\.0\.0\.1[:\/]/.test(url)) // or 127.0.0.1
|
||||
url = url.replace(/^http:/, 'https:');
|
||||
|
||||
if (options.replaceLocalhost)
|
||||
url = url.replace(/^http:\/\/localhost([:\/].*)/, 'http://127.0.0.1$1');
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
@@ -36,6 +36,27 @@ Tinytest.add("absolute-url - basics", function(test) {
|
||||
test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://127.0.0.1:3000',
|
||||
secure: true}),
|
||||
'http://127.0.0.1:3000/foo');
|
||||
|
||||
// test replaceLocalhost
|
||||
test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://localhost:3000',
|
||||
replaceLocalhost: true}),
|
||||
'http://127.0.0.1:3000/foo');
|
||||
test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://localhost',
|
||||
replaceLocalhost: true}),
|
||||
'http://127.0.0.1/foo');
|
||||
test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://127.0.0.1:3000',
|
||||
replaceLocalhost: true}),
|
||||
'http://127.0.0.1:3000/foo');
|
||||
test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://127.0.0.1',
|
||||
replaceLocalhost: true}),
|
||||
'http://127.0.0.1/foo');
|
||||
// don't replace just any localhost
|
||||
test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://foo.com/localhost',
|
||||
replaceLocalhost: true}),
|
||||
'http://foo.com/localhost/foo');
|
||||
test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://foo.localhost.com',
|
||||
replaceLocalhost: true}),
|
||||
'http://foo.localhost.com/foo');
|
||||
});
|
||||
|
||||
|
||||
@@ -201,7 +201,10 @@ Tinytest.add("minimongo - misc", function (test) {
|
||||
var a = {a: [1, 2, 3], b: "x", c: true, d: {x: 12, y: [12]},
|
||||
f: null, g: new Date()};
|
||||
var b = LocalCollection._deepcopy(a);
|
||||
test.isTrue(LocalCollection._f._equal(a, b));
|
||||
// minimongo doesn't support Dates, so we *can't* test
|
||||
// LocalCollection._f._equal here! (Currently _equal considers all dates equal
|
||||
// on most browsers except IE7 where it considers all dates unequal.)
|
||||
test.equal(a, b);
|
||||
a.a.push(4);
|
||||
test.length(b.a, 3);
|
||||
a.c = false;
|
||||
@@ -211,10 +214,10 @@ Tinytest.add("minimongo - misc", function (test) {
|
||||
test.equal(b.d.z, 15);
|
||||
a.d.y.push(88);
|
||||
test.length(b.d.y, 1);
|
||||
test.equal(a.g, b.g)
|
||||
test.equal(a.g, b.g);
|
||||
b.g.setDate(b.g.getDate() + 1);
|
||||
test.notEqual(a.g, b.g)
|
||||
|
||||
test.notEqual(a.g, b.g);
|
||||
|
||||
a = {x: function () {}};
|
||||
b = LocalCollection._deepcopy(a);
|
||||
a.x.a = 14;
|
||||
|
||||
@@ -164,7 +164,7 @@ _Mongo.prototype.remove = function (collection_name, selector) {
|
||||
return;
|
||||
}
|
||||
|
||||
collection.remove(selector, {/* XXXsafe: true*/}, function (err) {
|
||||
collection.remove(selector, {safe: true}, function (err) {
|
||||
if (err) {
|
||||
future.ret(err);
|
||||
return;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Meteor._FailureTestCollection =
|
||||
new Meteor.Collection("___meteor_failure_test_collection");
|
||||
|
||||
testAsyncMulti("mongo-livedata - database failure reporting", [
|
||||
testAsyncMulti("mongo-livedata - database error reporting", [
|
||||
function (test, expect) {
|
||||
var ftc = Meteor._FailureTestCollection;
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
Tinytest.add("sass - presence", function(test) {
|
||||
|
||||
var d = OnscreenDiv(Meteor.render(function() {
|
||||
return '<p class="sass-unlucky-left-border"></p>'; }));
|
||||
return '<p class="sass-dashy-left-border"></p>'; }));
|
||||
d.node().style.display = 'block';
|
||||
|
||||
var p = d.node().firstChild;
|
||||
var leftBorder = getStyleProperty(p, 'border-left-width');
|
||||
test.equal(leftBorder, "13px");
|
||||
var leftBorder = getStyleProperty(p, 'border-left-style');
|
||||
test.equal(leftBorder, "dashed");
|
||||
|
||||
d.kill();
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#sass-tests
|
||||
:zoom 1
|
||||
|
||||
unlucky: 13px
|
||||
dashy: dashed
|
||||
|
||||
.sass-unlucky-left-border
|
||||
:border-left !unlucky solid white
|
||||
.sass-dashy-left-border
|
||||
:border-left 1px !dashy black
|
||||
|
||||
@@ -185,11 +185,11 @@ Tinytest.add("spark - patch - copyAttributes", function(test) {
|
||||
{id:'foo', 'class':'bar',
|
||||
style:'border:1px solid blue;', name:'baz'});
|
||||
a.check();
|
||||
test.equal(a.node().style.borderColor, "blue");
|
||||
test.equal(a.node().style.borderLeftColor, "blue");
|
||||
|
||||
a.copy({id: "foo", style:'border:1px solid red'});
|
||||
a.check();
|
||||
test.equal(a.node().style.borderColor, "red");
|
||||
test.equal(a.node().style.borderLeftColor, "red");
|
||||
|
||||
a.copy({id: "foo", 'class':'ha'});
|
||||
a.check();
|
||||
|
||||
@@ -91,8 +91,9 @@ var notifyWatchers = function (start, end) {
|
||||
};
|
||||
|
||||
Spark._createId = function () {
|
||||
// Chars can't include '-' to be safe inside HTML comments.
|
||||
var chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_";
|
||||
var id = "";
|
||||
for (var i = 0; i < 8; i++)
|
||||
id += chars.substr(Math.floor(Meteor.random() * 64), 1);
|
||||
|
||||
@@ -24,7 +24,12 @@
|
||||
// Use '/dev/stdin' to avoid writing to a temporary file. Can't
|
||||
// just omit the file, as PhantomJS takes that to mean 'use a
|
||||
// REPL' and exits as soon as stdin closes.
|
||||
var cp = spawn('phantomjs', ['--load-images=no', '/dev/stdin']);
|
||||
//
|
||||
// However, Node 0.8 broke the ability to open /dev/stdin in the
|
||||
// subprocess; see https://gist.github.com/3751746 for the gory
|
||||
// details. Work around this with a not-so-useless use of cat.
|
||||
var cp = spawn('bash',
|
||||
['-c', 'cat | phantomjs --load-images=no /dev/stdin']);
|
||||
|
||||
var data = '';
|
||||
cp.stdout.setEncoding('utf8');
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
Tinytest.add("stylus - presence", function(test) {
|
||||
|
||||
var d = OnscreenDiv(Meteor.render(function() {
|
||||
return '<p class="stylus-unlucky-left-border"></p>'; }));
|
||||
return '<p class="stylus-dashy-left-border"></p>'; }));
|
||||
d.node().style.display = 'block';
|
||||
|
||||
var p = d.node().firstChild;
|
||||
var leftBorder = getStyleProperty(p, 'border-left-width');
|
||||
test.equal(leftBorder, "13px");
|
||||
var leftBorder = getStyleProperty(p, 'border-left-style');
|
||||
test.equal(leftBorder, "dashed");
|
||||
|
||||
d.kill();
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#stylus-tests
|
||||
zoom: 1
|
||||
|
||||
unlucky = 13px
|
||||
dashy = dashed
|
||||
|
||||
.stylus-unlucky-left-border
|
||||
border-left: unlucky solid white
|
||||
.stylus-dashy-left-border
|
||||
border-left: 1px dashy black
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
line-height: 24px;
|
||||
vertical-align: middle;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.test_table .groupname {
|
||||
|
||||
Reference in New Issue
Block a user