diff --git a/.gitignore b/.gitignore index e43b0f9..9daa824 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +node_modules diff --git a/lib/applescript-parser.js b/lib/applescript-parser.js index 78bf3ae..c789f7c 100644 --- a/lib/applescript-parser.js +++ b/lib/applescript-parser.js @@ -1,118 +1,1665 @@ +module.exports = (function() { + /* + * Generated by PEG.js 0.8.0. + * + * http://pegjs.majda.cz/ + */ -// 'parse' accepts a string that is expected to be the stdout stream of an -// osascript invocation. It reads the fist char of the string to determine -// the data-type of the result, and creates the appropriate type parser. -exports.parse = function(str) { - if (str.length == 0) { - return; + function peg$subclass(child, parent) { + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); } - - var rtn = parseFromFirstRemaining.call({ - value: str, - index: 0 - }); - return rtn; -} - -// Attemps to determine the data type of the next part of the String to -// parse. The 'this' value has a Object with 'value' as the AppleScript -// string to parse, and 'index' as the pointer to the current position -// of parsing in the String. This Function does not need to be exported??? -function parseFromFirstRemaining() { - var cur = this.value[this.index]; - switch(cur) { - case '{': - return exports.ArrayParser.call(this); - break; - case '"': - return exports.StringParser.call(this); - break; - case 'a': - if (this.value.substring(this.index, this.index+5) == 'alias') { - return exports.AliasParser.call(this); - } - break; - case '«': - if (this.value.substring(this.index, this.index+5) == '«data') { - return exports.DataParser.call(this); - } - break; - } - if (!isNaN(cur)) { - return exports.NumberParser.call(this); - } - return exports.UndefinedParser.call(this); -} - -// Parses an AppleScript "alias", which is really just a reference to a -// location on the filesystem, but formatted kinda weirdly. -exports.AliasParser = function() { - this.index += 6; - return "/Volumes/" + exports.StringParser.call(this).replace(/:/g, "/"); -} - -// Parses an AppleScript Array. Which looks like {}, instead of JavaScript's []. -exports.ArrayParser = function() { - var rtn = [], - cur = this.value[++this.index]; - while (cur != '}') { - rtn.push(parseFromFirstRemaining.call(this)); - if (this.value[this.index] == ',') this.index += 2; - cur = this.value[this.index]; - } - this.index++; - return rtn; -} - -// Parses «data » results into native Buffer instances. -exports.DataParser = function() { - var body = exports.UndefinedParser.call(this); - body = body.substring(6, body.length-1); - var type = body.substring(0,4); - body = body.substring(4, body.length); - var buf = new Buffer(body.length/2); - var count = 0; - for (var i=0, l=body.length; i 1 ? arguments[1] : {}, + + peg$FAILED = {}, + + peg$startRuleFunctions = { start: peg$parsestart }, + peg$startRuleFunction = peg$parsestart, + + peg$c0 = peg$FAILED, + peg$c1 = [], + peg$c2 = function(v) { return v; }, + peg$c3 = function() { return null; }, + peg$c4 = { type: "other", description: "whitespace" }, + peg$c5 = /^[ \t\n\r]/, + peg$c6 = { type: "class", value: "[ \\t\\n\\r]", description: "[ \\t\\n\\r]" }, + peg$c7 = ",", + peg$c8 = { type: "literal", value: ",", description: "\",\"" }, + peg$c9 = ":", + peg$c10 = { type: "literal", value: ":", description: "\":\"" }, + peg$c11 = { type: "other", description: "number" }, + peg$c12 = null, + peg$c13 = "-", + peg$c14 = { type: "literal", value: "-", description: "\"-\"" }, + peg$c15 = function() { return parseFloat(text()); }, + peg$c16 = /^[0-9]/, + peg$c17 = { type: "class", value: "[0-9]", description: "[0-9]" }, + peg$c18 = /^[1-9]/, + peg$c19 = { type: "class", value: "[1-9]", description: "[1-9]" }, + peg$c20 = /^[eE]/, + peg$c21 = { type: "class", value: "[eE]", description: "[eE]" }, + peg$c22 = "+", + peg$c23 = { type: "literal", value: "+", description: "\"+\"" }, + peg$c24 = ".", + peg$c25 = { type: "literal", value: ".", description: "\".\"" }, + peg$c26 = "0", + peg$c27 = { type: "literal", value: "0", description: "\"0\"" }, + peg$c28 = { type: "other", description: "boolean" }, + peg$c29 = "true", + peg$c30 = { type: "literal", value: "true", description: "\"true\"" }, + peg$c31 = function() { return true; }, + peg$c32 = "false", + peg$c33 = { type: "literal", value: "false", description: "\"false\"" }, + peg$c34 = function() { return false; }, + peg$c35 = { type: "other", description: "alias" }, + peg$c36 = "alias", + peg$c37 = { type: "literal", value: "alias", description: "\"alias\"" }, + peg$c38 = function(path) { return pathFromAlias(path); }, + peg$c39 = { type: "other", description: "text" }, + peg$c40 = "\"", + peg$c41 = { type: "literal", value: "\"", description: "\"\\\"\"" }, + peg$c42 = function(chars) { return chars.join(""); }, + peg$c43 = "\\", + peg$c44 = { type: "literal", value: "\\", description: "\"\\\\\"" }, + peg$c45 = "/", + peg$c46 = { type: "literal", value: "/", description: "\"/\"" }, + peg$c47 = "b", + peg$c48 = { type: "literal", value: "b", description: "\"b\"" }, + peg$c49 = function() { return "\b"; }, + peg$c50 = "f", + peg$c51 = { type: "literal", value: "f", description: "\"f\"" }, + peg$c52 = function() { return "\f"; }, + peg$c53 = "n", + peg$c54 = { type: "literal", value: "n", description: "\"n\"" }, + peg$c55 = function() { return "\n"; }, + peg$c56 = "r", + peg$c57 = { type: "literal", value: "r", description: "\"r\"" }, + peg$c58 = function() { return "\r"; }, + peg$c59 = "t", + peg$c60 = { type: "literal", value: "t", description: "\"t\"" }, + peg$c61 = function() { return "\t"; }, + peg$c62 = "u", + peg$c63 = { type: "literal", value: "u", description: "\"u\"" }, + peg$c64 = function(digits) { + return String.fromCharCode(parseInt(digits, 16)); + }, + peg$c65 = function(sequence) { return sequence; }, + peg$c66 = /^[^\\"]/, + peg$c67 = { type: "class", value: "[^\\\\\"]", description: "[^\\\\\"]" }, + peg$c68 = /^[0-9a-f]/i, + peg$c69 = { type: "class", value: "[0-9a-f]i", description: "[0-9a-f]i" }, + peg$c70 = { type: "other", description: "list" }, + peg$c71 = "{", + peg$c72 = { type: "literal", value: "{", description: "\"{\"" }, + peg$c73 = function(first, rest) { return [first].concat(rest); }, + peg$c74 = "}", + peg$c75 = { type: "literal", value: "}", description: "\"}\"" }, + peg$c76 = function(values) { return values !== null ? values : []; }, + peg$c77 = { type: "other", description: "record" }, + peg$c78 = function(m) { return m; }, + peg$c79 = function(first, rest) { + var result = {}, i; + + result[first.name] = first.value; + + for (i = 0; i < rest.length; i++) { + result[rest[i].name] = rest[i].value; + } + + return result; + }, + peg$c80 = function(members) { return members !== null ? members: {}; }, + peg$c81 = function(name, value) { + return { name: name, value: value }; + }, + peg$c82 = /^[_a-z0-9\-]/i, + peg$c83 = { type: "class", value: "[_a-z0-9\\-]i", description: "[_a-z0-9\\-]i" }, + peg$c84 = { type: "other", description: "date" }, + peg$c85 = "date", + peg$c86 = { type: "literal", value: "date", description: "\"date\"" }, + peg$c87 = function(value) { return createDate(value); }, + peg$c88 = { type: "other", description: "application" }, + peg$c89 = "application", + peg$c90 = { type: "literal", value: "application", description: "\"application\"" }, + peg$c91 = function(appname) { return {_application: appname }; }, + peg$c92 = { type: "other", description: "class name" }, + peg$c93 = function(name) { return {_class: name }; }, + peg$c94 = "boolean", + peg$c95 = { type: "literal", value: "boolean", description: "\"boolean\"" }, + peg$c96 = "integer", + peg$c97 = { type: "literal", value: "integer", description: "\"integer\"" }, + peg$c98 = "real", + peg$c99 = { type: "literal", value: "real", description: "\"real\"" }, + peg$c100 = "text", + peg$c101 = { type: "literal", value: "text", description: "\"text\"" }, + peg$c102 = "list", + peg$c103 = { type: "literal", value: "list", description: "\"list\"" }, + peg$c104 = "class", + peg$c105 = { type: "literal", value: "class", description: "\"class\"" }, + peg$c106 = { type: "other", description: "raw" }, + peg$c107 = "\xAB", + peg$c108 = { type: "literal", value: "\xAB", description: "\"\\xAB\"" }, + peg$c109 = /^[^\xBB]/, + peg$c110 = { type: "class", value: "[^\\xBB]", description: "[^\\xBB]" }, + peg$c111 = "\xBB", + peg$c112 = { type: "literal", value: "\xBB", description: "\"\\xBB\"" }, + peg$c113 = function(contents) { return {_raw: contents.join("")}; }, + + peg$currPos = 0, + peg$reportedPos = 0, + peg$cachedPos = 0, + peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }, + peg$maxFailPos = 0, + peg$maxFailExpected = [], + peg$silentFails = 0, + + peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$reportedPos, peg$currPos); + } + + function offset() { + return peg$reportedPos; + } + + function line() { + return peg$computePosDetails(peg$reportedPos).line; + } + + function column() { + return peg$computePosDetails(peg$reportedPos).column; + } + + function expected(description) { + throw peg$buildException( + null, + [{ type: "other", description: description }], + peg$reportedPos + ); + } + + function error(message) { + throw peg$buildException(message, null, peg$reportedPos); + } + + function peg$computePosDetails(pos) { + function advance(details, startPos, endPos) { + var p, ch; + + for (p = startPos; p < endPos; p++) { + ch = input.charAt(p); + if (ch === "\n") { + if (!details.seenCR) { details.line++; } + details.column = 1; + details.seenCR = false; + } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { + details.line++; + details.column = 1; + details.seenCR = true; + } else { + details.column++; + details.seenCR = false; + } + } + } + + if (peg$cachedPos !== pos) { + if (peg$cachedPos > pos) { + peg$cachedPos = 0; + peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }; + } + advance(peg$cachedPosDetails, peg$cachedPos, pos); + peg$cachedPos = pos; + } + + return peg$cachedPosDetails; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildException(message, expected, pos) { + function cleanupExpected(expected) { + var i = 1; + + expected.sort(function(a, b) { + if (a.description < b.description) { + return -1; + } else if (a.description > b.description) { + return 1; + } else { + return 0; + } + }); + + while (i < expected.length) { + if (expected[i - 1] === expected[i]) { + expected.splice(i, 1); + } else { + i++; + } + } + } + + function buildMessage(expected, found) { + function stringEscape(s) { + function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } + + return s + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\x08/g, '\\b') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\f/g, '\\f') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) + .replace(/[\u0180-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) + .replace(/[\u1080-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); + } + + var expectedDescs = new Array(expected.length), + expectedDesc, foundDesc, i; + + for (i = 0; i < expected.length; i++) { + expectedDescs[i] = expected[i].description; + } + + expectedDesc = expected.length > 1 + ? expectedDescs.slice(0, -1).join(", ") + + " or " + + expectedDescs[expected.length - 1] + : expectedDescs[0]; + + foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; + + return "Expected " + expectedDesc + " but " + foundDesc + " found."; + } + + var posDetails = peg$computePosDetails(pos), + found = pos < input.length ? input.charAt(pos) : null; + + if (expected !== null) { + cleanupExpected(expected); + } + + return new SyntaxError( + message !== null ? message : buildMessage(expected, found), + expected, + found, + pos, + posDetails.line, + posDetails.column + ); + } + + function peg$parsestart() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsevalue(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parse_(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parse_(); + } + if (s2 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c2(s1); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = []; + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c3(); + } + s0 = s1; + } + + return s0; + } + + function peg$parsevalue() { + var s0; + + s0 = peg$parsenumber(); + if (s0 === peg$FAILED) { + s0 = peg$parseboolean(); + if (s0 === peg$FAILED) { + s0 = peg$parsealias(); + if (s0 === peg$FAILED) { + s0 = peg$parsetext(); + if (s0 === peg$FAILED) { + s0 = peg$parserecord(); + if (s0 === peg$FAILED) { + s0 = peg$parselist(); + if (s0 === peg$FAILED) { + s0 = peg$parsedate(); + if (s0 === peg$FAILED) { + s0 = peg$parseapplication(); + if (s0 === peg$FAILED) { + s0 = peg$parseclassName(); + if (s0 === peg$FAILED) { + s0 = peg$parseraw(); + } + } + } + } + } + } + } + } + } + + return s0; + } + + function peg$parse_() { + var s0, s1; + + peg$silentFails++; + if (peg$c5.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c4); } + } + + return s0; + } + + function peg$parsevalueSeparator() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parse_(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parse_(); + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s2 = peg$c7; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c8); } + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parse_(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parse_(); + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + + return s0; + } + + function peg$parsenameSeparator() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parse_(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parse_(); + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s2 = peg$c9; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c10); } + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parse_(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parse_(); + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + + return s0; + } + + function peg$parsenumber() { + var s0, s1, s2, s3, s4; + + peg$silentFails++; + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 45) { + s1 = peg$c13; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c14); } + } + if (s1 === peg$FAILED) { + s1 = peg$c12; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseint(); + if (s2 !== peg$FAILED) { + s3 = peg$parsefrac(); + if (s3 === peg$FAILED) { + s3 = peg$c12; + } + if (s3 !== peg$FAILED) { + s4 = peg$parseexp(); + if (s4 === peg$FAILED) { + s4 = peg$c12; + } + if (s4 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c15(); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c11); } + } + + return s0; + } + + function peg$parsedigit() { + var s0; + + if (peg$c16.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c17); } + } + + return s0; + } + + function peg$parsenonZeroDigit() { + var s0; + + if (peg$c18.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + + return s0; + } + + function peg$parsee() { + var s0; + + if (peg$c20.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + + return s0; + } + + function peg$parseexp() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parsee(); + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 45) { + s2 = peg$c13; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c14); } + } + if (s2 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 43) { + s2 = peg$c22; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c23); } + } + } + if (s2 === peg$FAILED) { + s2 = peg$c12; + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parsedigit(); + if (s4 !== peg$FAILED) { + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parsedigit(); + } + } else { + s3 = peg$c0; + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + + return s0; + } + + function peg$parsefrac() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 46) { + s1 = peg$c24; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c25); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsedigit(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsedigit(); + } + } else { + s2 = peg$c0; + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + + return s0; + } + + function peg$parseint() { + var s0, s1, s2, s3; + + if (input.charCodeAt(peg$currPos) === 48) { + s0 = peg$c26; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c27); } + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsenonZeroDigit(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsedigit(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsedigit(); + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } + + return s0; + } + + function peg$parseboolean() { + var s0, s1; + + peg$silentFails++; + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c29) { + s1 = peg$c29; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c30); } + } + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c31(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 5) === peg$c32) { + s1 = peg$c32; + peg$currPos += 5; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c33); } + } + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c34(); + } + s0 = s1; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c28); } + } + + return s0; + } + + function peg$parsealias() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + if (input.substr(peg$currPos, 5) === peg$c36) { + s1 = peg$c36; + peg$currPos += 5; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c37); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parse_(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parse_(); + } + } else { + s2 = peg$c0; + } + if (s2 !== peg$FAILED) { + s3 = peg$parsetext(); + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c38(s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c35); } + } + + return s0; + } + + function peg$parsetext() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 34) { + s1 = peg$c40; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c41); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsechar(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsechar(); + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 34) { + s3 = peg$c40; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c41); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c42(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c39); } + } + + return s0; + } + + function peg$parsechar() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + + s0 = peg$parseunescaped(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseescape(); + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 34) { + s2 = peg$c40; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c41); } + } + if (s2 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 92) { + s2 = peg$c43; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c44); } + } + if (s2 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 47) { + s2 = peg$c45; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c46); } + } + if (s2 === peg$FAILED) { + s2 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 98) { + s3 = peg$c47; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c48); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s2; + s3 = peg$c49(); + } + s2 = s3; + if (s2 === peg$FAILED) { + s2 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 102) { + s3 = peg$c50; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c51); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s2; + s3 = peg$c52(); + } + s2 = s3; + if (s2 === peg$FAILED) { + s2 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 110) { + s3 = peg$c53; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c54); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s2; + s3 = peg$c55(); + } + s2 = s3; + if (s2 === peg$FAILED) { + s2 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 114) { + s3 = peg$c56; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c57); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s2; + s3 = peg$c58(); + } + s2 = s3; + if (s2 === peg$FAILED) { + s2 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 116) { + s3 = peg$c59; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c60); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s2; + s3 = peg$c61(); + } + s2 = s3; + if (s2 === peg$FAILED) { + s2 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 117) { + s3 = peg$c62; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c63); } + } + if (s3 !== peg$FAILED) { + s4 = peg$currPos; + s5 = peg$currPos; + s6 = peg$parseHEXDIG(); + if (s6 !== peg$FAILED) { + s7 = peg$parseHEXDIG(); + if (s7 !== peg$FAILED) { + s8 = peg$parseHEXDIG(); + if (s8 !== peg$FAILED) { + s9 = peg$parseHEXDIG(); + if (s9 !== peg$FAILED) { + s6 = [s6, s7, s8, s9]; + s5 = s6; + } else { + peg$currPos = s5; + s5 = peg$c0; + } + } else { + peg$currPos = s5; + s5 = peg$c0; + } + } else { + peg$currPos = s5; + s5 = peg$c0; + } + } else { + peg$currPos = s5; + s5 = peg$c0; + } + if (s5 !== peg$FAILED) { + s5 = input.substring(s4, peg$currPos); + } + s4 = s5; + if (s4 !== peg$FAILED) { + peg$reportedPos = s2; + s3 = peg$c64(s4); + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$c0; + } + } else { + peg$currPos = s2; + s2 = peg$c0; + } + } + } + } + } + } + } + } + } + if (s2 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c65(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } + + return s0; + } + + function peg$parseescape() { + var s0; + + if (input.charCodeAt(peg$currPos) === 92) { + s0 = peg$c43; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c44); } + } + + return s0; + } + + function peg$parseunescaped() { + var s0; + + if (peg$c66.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c67); } + } + + return s0; + } + + function peg$parseHEXDIG() { + var s0; + + if (peg$c68.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c69); } + } + + return s0; + } + + function peg$parselist() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + peg$silentFails++; + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 123) { + s1 = peg$c71; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c72); } + } + if (s1 !== peg$FAILED) { + s2 = peg$currPos; + s3 = peg$parsevalue(); + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$currPos; + s6 = peg$parsevalueSeparator(); + if (s6 !== peg$FAILED) { + s7 = peg$parsevalue(); + if (s7 !== peg$FAILED) { + peg$reportedPos = s5; + s6 = peg$c2(s7); + s5 = s6; + } else { + peg$currPos = s5; + s5 = peg$c0; + } + } else { + peg$currPos = s5; + s5 = peg$c0; + } + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$currPos; + s6 = peg$parsevalueSeparator(); + if (s6 !== peg$FAILED) { + s7 = peg$parsevalue(); + if (s7 !== peg$FAILED) { + peg$reportedPos = s5; + s6 = peg$c2(s7); + s5 = s6; + } else { + peg$currPos = s5; + s5 = peg$c0; + } + } else { + peg$currPos = s5; + s5 = peg$c0; + } + } + if (s4 !== peg$FAILED) { + peg$reportedPos = s2; + s3 = peg$c73(s3, s4); + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$c0; + } + } else { + peg$currPos = s2; + s2 = peg$c0; + } + if (s2 === peg$FAILED) { + s2 = peg$c12; + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 125) { + s3 = peg$c74; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c75); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c76(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c70); } + } + + return s0; + } + + function peg$parserecord() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + peg$silentFails++; + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 123) { + s1 = peg$c71; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c72); } + } + if (s1 !== peg$FAILED) { + s2 = peg$currPos; + s3 = peg$parsemember(); + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$currPos; + s6 = peg$parsevalueSeparator(); + if (s6 !== peg$FAILED) { + s7 = peg$parsemember(); + if (s7 !== peg$FAILED) { + peg$reportedPos = s5; + s6 = peg$c78(s7); + s5 = s6; + } else { + peg$currPos = s5; + s5 = peg$c0; + } + } else { + peg$currPos = s5; + s5 = peg$c0; + } + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$currPos; + s6 = peg$parsevalueSeparator(); + if (s6 !== peg$FAILED) { + s7 = peg$parsemember(); + if (s7 !== peg$FAILED) { + peg$reportedPos = s5; + s6 = peg$c78(s7); + s5 = s6; + } else { + peg$currPos = s5; + s5 = peg$c0; + } + } else { + peg$currPos = s5; + s5 = peg$c0; + } + } + if (s4 !== peg$FAILED) { + peg$reportedPos = s2; + s3 = peg$c79(s3, s4); + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$c0; + } + } else { + peg$currPos = s2; + s2 = peg$c0; + } + if (s2 === peg$FAILED) { + s2 = peg$c12; + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 125) { + s3 = peg$c74; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c75); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c80(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c77); } + } + + return s0; + } + + function peg$parsemember() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parseidentifier(); + if (s1 !== peg$FAILED) { + s2 = peg$parsenameSeparator(); + if (s2 !== peg$FAILED) { + s3 = peg$parsevalue(); + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c81(s1, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + + return s0; + } + + function peg$parseidentifier() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parsenmchar(); + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsenmchar(); + } + } else { + s1 = peg$c0; + } + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c42(s1); + } + s0 = s1; + + return s0; + } + + function peg$parsenmchar() { + var s0; + + if (peg$c82.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c83); } + } + + return s0; + } + + function peg$parsedate() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c85) { + s1 = peg$c85; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c86); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parse_(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parse_(); + } + } else { + s2 = peg$c0; + } + if (s2 !== peg$FAILED) { + s3 = peg$parsetext(); + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c87(s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c84); } + } + + return s0; + } + + function peg$parseapplication() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + if (input.substr(peg$currPos, 11) === peg$c89) { + s1 = peg$c89; + peg$currPos += 11; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c90); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parse_(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parse_(); + } + } else { + s2 = peg$c0; + } + if (s2 !== peg$FAILED) { + s3 = peg$parsetext(); + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c91(s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c88); } + } + + return s0; + } + + function peg$parseclassName() { + var s0, s1; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parsecname(); + if (s1 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c93(s1); + } + s0 = s1; + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c92); } + } + + return s0; + } + + function peg$parsecname() { + var s0; + + if (input.substr(peg$currPos, 5) === peg$c36) { + s0 = peg$c36; + peg$currPos += 5; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c37); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 11) === peg$c89) { + s0 = peg$c89; + peg$currPos += 11; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c90); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 7) === peg$c94) { + s0 = peg$c94; + peg$currPos += 7; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c95); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c85) { + s0 = peg$c85; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c86); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 7) === peg$c96) { + s0 = peg$c96; + peg$currPos += 7; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c97); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c98) { + s0 = peg$c98; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c99); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c100) { + s0 = peg$c100; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c101); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c102) { + s0 = peg$c102; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c103); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 5) === peg$c104) { + s0 = peg$c104; + peg$currPos += 5; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c105); } + } + } + } + } + } + } + } + } + } + + return s0; + } + + function peg$parseraw() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 171) { + s1 = peg$c107; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c108); } + } + if (s1 !== peg$FAILED) { + s2 = []; + if (peg$c109.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c110); } + } + while (s3 !== peg$FAILED) { + s2.push(s3); + if (peg$c109.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c110); } + } + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 187) { + s3 = peg$c111; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c112); } + } + if (s3 !== peg$FAILED) { + peg$reportedPos = s0; + s1 = peg$c113(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + } else { + peg$currPos = s0; + s0 = peg$c0; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c106); } + } + + return s0; + } + + + function pathFromAlias(alias) { + return "/Volumes/" + alias.replace(/:/g, '/'); + } + + function createDate(value) { + var dateText = value.replace(/^[\w]+,\s+/, "").replace(/ at /, " "); + return new Date(dateText); + } + + + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail({ type: "end", description: "end of input" }); + } + + throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos); + } } - var rtn = this.value.substring(this.index, end-1); - this.index = end-1; - return rtn; -} + + return { + SyntaxError: SyntaxError, + parse: parse + }; +})(); diff --git a/lib/applescript-parser.pegjs b/lib/applescript-parser.pegjs new file mode 100644 index 0000000..89d2cf9 --- /dev/null +++ b/lib/applescript-parser.pegjs @@ -0,0 +1,169 @@ +/* + Grammar for AppleScript machine readable format. + + Since there is no formal specification for the format returned by osascript, + it was guessed from return values. + + This grammar was created with a lot of help from this example: + https://github.com/dmajda/pegjs/blob/master/examples/json.pegjs +*/ + +{ + function pathFromAlias(alias) { + return "/Volumes/" + alias.replace(/:/g, '/'); + } + + function createDate(value) { + var dateText = value.replace(/^[\w]+,\s+/, "").replace(/ at /, " "); + return new Date(dateText); + } +} + + +start + = v:value _* { return v; } + / { return null; } + +value + = number + / boolean + / alias + / text + / record + / list + / date + / application + / className + / raw + + +/* Common rules ------------------------------------------------------------- */ +_ "whitespace" + = [ \t\n\r] + +valueSeparator = _* "," _* +nameSeparator = _* ":" _* + + +/* Number (Integer/Real) ---------------------------------------------------- */ +number "number" + = "-"? int frac? exp? { return parseFloat(text()); } + +digit = [0-9] +nonZeroDigit = [1-9] +e = [eE] +exp = e ("-" / "+")? digit+ +frac = "." digit+ +int = "0" / (nonZeroDigit digit*) + + +/* Boolean ------------------------------------------------------------------ */ +boolean "boolean" + = "true" { return true; } / "false" { return false; } + + +/* Alias -------------------------------------------------------------------- */ +alias "alias" + = "alias" _+ path:text { return pathFromAlias(path); } + + +/* Text --------------------------------------------------------------------- */ +text "text" + = '"' chars:char* '"' { return chars.join(""); } + +char + = unescaped + / escape + sequence:( + '"' + / "\\" + / "/" + / "b" { return "\b"; } + / "f" { return "\f"; } + / "n" { return "\n"; } + / "r" { return "\r"; } + / "t" { return "\t"; } + / "u" digits:$(HEXDIG HEXDIG HEXDIG HEXDIG) { + return String.fromCharCode(parseInt(digits, 16)); + } + ) + { return sequence; } + +escape = "\\" +unescaped = [^\\"] +HEXDIG = [0-9a-f]i + + +/* List --------------------------------------------------------------------- */ +list "list" + = "{" + values:( + first:value + rest:(valueSeparator v:value { return v; })* + { return [first].concat(rest); } + )? + "}" + { return values !== null ? values : []; } + + +/* Record ------------------------------------------------------------------- */ +record "record" + = "{" + members:( + first:member + rest:(valueSeparator m:member { return m; })* + { + var result = {}, i; + + result[first.name] = first.value; + + for (i = 0; i < rest.length; i++) { + result[rest[i].name] = rest[i].value; + } + + return result; + } + )? + "}" + { return members !== null ? members: {}; } + +member + = name:identifier nameSeparator value:value { + return { name: name, value: value }; + } + +identifier + = chars:nmchar+ { return chars.join(""); } + +nmchar + = [_a-z0-9-]i + + +/* Date --------------------------------------------------------------------- */ +date "date" + = "date" _+ value:text { return createDate(value); } + + +/* Application -------------------------------------------------------------- */ +application "application" + = "application" _+ appname:text { return {_application: appname }; } + + +/* Built-in classname ------------------------------------------------------- */ +className "class name" + = name:cname { return {_class: name }; } + +cname + = "alias" + / "application" + / "boolean" + / "date" + / "integer" + / "real" + / "text" + / "list" + / "class" + +/* Raw format --------------------------------------------------------------- */ +raw "raw" + = "\xAB" contents: [^\xBB]* "\xBB" { return {_raw: contents.join("")}; } \ No newline at end of file diff --git a/lib/applescript.js b/lib/applescript.js index a5988ab..25d1ded 100644 --- a/lib/applescript.js +++ b/lib/applescript.js @@ -41,15 +41,24 @@ function runApplescript(strOrPath, args, callback) { bufferBody(interpreter.stderr); interpreter.on('exit', function(code) { - var result = parse(interpreter.stdout.body); - var err; + var result, err; + if (code) { // If the exit code was something other than 0, we're gonna // return an Error object. err = new Error(interpreter.stderr.body); err.appleScript = strOrPath; err.exitCode = code; + } else { + // Parse the output only in case of success + try { + result = parse(interpreter.stdout.body); + } catch (e) { + // If there is a parse error, set the err value. + err = e; + } } + callback(err, result, interpreter.stderr.body); }); diff --git a/package.json b/package.json index 25d7ffb..319f03a 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,22 @@ "description": "Easily execute arbitrary AppleScript code on OS X through NodeJS.", "version": "0.2.1", "author": "Nathan Rajlich ", - "keywords": ["applescript", "mac", "osx"], + "keywords": [ + "applescript", + "mac", + "osx" + ], "main": "./lib/applescript", "lib": "./lib", - "engines": { "node": ">= 0.2.0" } -} \ No newline at end of file + "engines": { + "node": ">= 0.2.0" + }, + "scripts": { + "pretest": "pegjs lib/applescript-parser.pegjs", + "test": "mocha -R spec" + }, + "devDependencies": { + "mocha": "~1.18.2", + "pegjs": "~0.8.0" + } +} diff --git a/test/applescript-parser.js b/test/applescript-parser.js new file mode 100644 index 0000000..1319b4e --- /dev/null +++ b/test/applescript-parser.js @@ -0,0 +1,111 @@ +var + parse = require("../lib/applescript-parser").parse, + assert = require("assert"); + +describe("AppleScript Parser", function () { + + it("supports text", function () { + assert.strictEqual("Hello", parse("\"Hello\"")); + }); + + it("supports multi line text", function () { + assert.strictEqual("Hello\nWorld", parse("\"Hello\nWorld\"")); + }); + + it("supports integer", function () { + assert.strictEqual(123, parse("123")); + }); + + it("supports real", function () { + assert.strictEqual(3.14159, parse("3.14159")); + }); + + it("supports alias", function () { + assert.strictEqual( + "/Volumes/Macintosh HD/System/Library/CoreServices/Finder.app/", + parse("alias \"Macintosh HD:System:Library:CoreServices:Finder.app:\"") + ); + }); + + it("supports application", function () { + assert.deepEqual( + {_application:"iTunes"}, + parse("application \"iTunes\"") + ); + }); + + it("supports boolean", function () { + assert.equal(true, parse("true")); + assert.equal(false, parse("false")); + }); + + it("supports class", function () { + assert.deepEqual({_class:"integer"}, parse("integer")); + assert.deepEqual({_class:"real"}, parse("real")); + assert.deepEqual({_class:"text"}, parse("text")); + assert.deepEqual({_class:"class"}, parse("class")); + assert.deepEqual({_class:"application"}, parse("application")); + assert.deepEqual({_class:"alias"}, parse("alias")); + assert.deepEqual({_class:"date"}, parse("date")); + assert.deepEqual({_class:"boolean"}, parse("boolean")); + }); + + it("supports single element list", function () { + assert.deepEqual([1], parse("{1}")); + }); + + it("supports multi element list", function () { + assert.deepEqual([1,2,3], parse("{1,2,3}")); + }); + + it("supports list with multiple types", function () { + assert.deepEqual( + [1, "Hello", true, 3.2], + parse("{1, \"Hello\", true, 3.2}")); + }); + + it("supports simple records", function () { + assert.deepEqual( + {name: "John", surname: "Doe"}, + parse("{name:\"John\", surname:\"Doe\"}") + ); + }); + + it("supports nested records", function () { + assert.deepEqual( + {name:"John", location: {id: 1, name: "test"} }, + parse("{name:\"John\", location:{id:1, name:\"test\"}}") + ); + }); + + it("supports records with lists", function () { + assert.deepEqual( + {name:"John", keys: [1,2,3]}, + parse("{name:\"John\", keys:{1,2,3}}") + ); + }); + + it("supports date", function () { + assert.equal( + new Date(2014, 2, 29, 1, 25, 32, 0).getTime(), + parse("date \"Saturday, March 29, 2014 at 1:25:32 AM\"").getTime() + ); + }); + + it("supports results in raw format", function () { + assert.deepEqual({_raw: 'script Joe'}, parse("\xABscript Joe\xBB")); + }); + + it("return null if the input is empty", function () { + assert.strictEqual(null, parse("")); + }); + + it("supports double quotes escaped", function () { + assert.equal("\"hello\"", parse("\"\\\"hello\\\"\"")); + }); + + it("supports unicode", function () { + assert.equal("…", parse("\"…\"")); + }); + +}); \ No newline at end of file diff --git a/test/applescript.js b/test/applescript.js new file mode 100644 index 0000000..6f58bdf --- /dev/null +++ b/test/applescript.js @@ -0,0 +1,56 @@ +var + applescript = require("../lib/applescript"), + assert = require("assert"); + +describe("Applescript", function () { + + it("supports integer output", function (done) { + applescript.execString("return 123", function (err, result) { + assert.equal(123, result); + done(); + }); + }); + + it("supports list output", function (done) { + applescript.execString("return {1,2,3}", function (err, result) { + assert.deepEqual([1,2,3], result); + done(); + }); + }); + + it("supports real number output", function (done) { + applescript.execString("return 3.14", function (err, result) { + assert.equal(3.14, result); + done(); + }); + }); + + it("supports list/record output", function (done) { + applescript.execString( + "return {true, 3.14, {name:\"John\", surname: \"Doe\"}}", + function (err, result) { + assert.deepEqual([true, 3.14, {name:"John", surname: "Doe"}], result); + done(); + }); + }); + + it("supports raw format", function (done) { + applescript.execString([ + "script Joe", + "property theCount : 0", + "end script", + "set scriptObjectJoe to Joe", + "scriptObjectJoe"].join("\n"), function (err, result) { + assert.deepEqual({_raw: 'script Joe'}, result); + done(); + }) + }); + + it("doesn't parese output on error", function (done) { + applescript.execString("kfdhks", function (err, result, errout) { + assert(typeof result === "undefined"); + assert(err); + done(); + }); + }) +}); \ No newline at end of file