From 4204b277517cd2fdcec7f9a46d786698a126b211 Mon Sep 17 00:00:00 2001 From: David Graham & Nathan Sobo Date: Tue, 19 Jun 2012 17:19:47 -0600 Subject: [PATCH 01/30] Start on Snippets extension Can parse a basic snippets file --- spec/extensions/snippets-spec.coffee | 43 +++++++++++++++++++++++++ src/extensions/snippets/index.coffee | 1 + src/extensions/snippets/snippets.coffee | 10 ++++++ src/extensions/snippets/snippets.pegjs | 22 +++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 spec/extensions/snippets-spec.coffee create mode 100644 src/extensions/snippets/index.coffee create mode 100644 src/extensions/snippets/snippets.coffee create mode 100644 src/extensions/snippets/snippets.pegjs diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee new file mode 100644 index 000000000..d7b982e9d --- /dev/null +++ b/spec/extensions/snippets-spec.coffee @@ -0,0 +1,43 @@ +Snippets = require 'snippets' +Buffer = require 'buffer' +Editor = require 'editor' +_ = require 'underscore' + +fdescribe "Snippets extension", -> + [buffer, editor] = [] + beforeEach -> + buffer = new Buffer(require.resolve('fixtures/sample.js')) + editor = new Editor({buffer}) + + describe "when 'tab' is triggered on the editor", -> + describe "when the letters preceding the cursor are registered as a global extension", -> + it "replaces the prefix with the snippet text", -> + Snippets.evalSnippets 'js', """ + snippet te "Test snippet description" + this is a test + endsnippet + + snippet moo "Moo snippet" + Mooooooo! + endsnippet + """ + + editor.insertText("te") + editor.trigger 'tab' + + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + expect(buffer.lineForRow(0)).toBe "this is a testvar quicksort = function () {" + + ffdescribe "Snippets parser", -> + it "can parse a snippet", -> + snippets = Snippets.snippetsParser.parse """ + snippet te "Test snippet description" + this is a test + endsnippet + """ + + expect(_.keys(snippets).length).toBe 1 + snippet = snippets['te'] + expect(snippet.prefix).toBe 'te' + expect(snippet.description).toBe "Test snippet description" + expect(snippet.body).toBe "this is a test" diff --git a/src/extensions/snippets/index.coffee b/src/extensions/snippets/index.coffee new file mode 100644 index 000000000..7d31833a1 --- /dev/null +++ b/src/extensions/snippets/index.coffee @@ -0,0 +1 @@ +module.exports = require 'extensions/snippets/snippets.coffee' \ No newline at end of file diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee new file mode 100644 index 000000000..d29938aba --- /dev/null +++ b/src/extensions/snippets/snippets.coffee @@ -0,0 +1,10 @@ +fs = require 'fs' +PEG = require 'pegjs' + +module.exports = + snippetsByExtension: {} + snippetsParser: PEG.buildParser(fs.read(require.resolve 'extensions/snippets/snippets.pegjs')) + + evalSnippets: (extension, text) -> + @snippetsByExtension[extension] = snippetsParser.parse(text) + diff --git a/src/extensions/snippets/snippets.pegjs b/src/extensions/snippets/snippets.pegjs new file mode 100644 index 000000000..f40d399bc --- /dev/null +++ b/src/extensions/snippets/snippets.pegjs @@ -0,0 +1,22 @@ +snippets = snippets:snippet+ { + var snippetsByPrefix = {}; + snippets.forEach(function(snippet) { + snippetsByPrefix[snippet.prefix] = snippet + }); + return snippetsByPrefix; +} + +snippet = start ws prefix:prefix ws description:string separator body:body end { + return { prefix: prefix, description: description, body: body }; +} + +separator = [ ]* '\n' +start = 'snippet' +prefix = prefix:[A-Za-z0-9_]+ { return prefix.join(''); } +body = body:bodyCharacter* { return body.join(''); } +bodyCharacter = !end char:[a-z ] { return char; } +end = '\nendsnippet' +string + = ['] body:[^']* ['] { return body.join(''); } + / ["] body:[^"]* ["] { return body.join(''); } +ws = [ \n]+ From 5b8cc8a6b6831da74caccf22977d7aa6212dec44 Mon Sep 17 00:00:00 2001 From: David Graham & Nathan Sobo Date: Tue, 19 Jun 2012 17:47:09 -0600 Subject: [PATCH 02/30] Snippets matching the cursor's word prefix are inserted on 'tab' events --- spec/extensions/snippets-spec.coffee | 22 +++++++++++----------- src/app/buffer.coffee | 6 ++++++ src/app/cursor.coffee | 3 +++ src/app/edit-session.coffee | 3 +++ src/app/tokenized-buffer.coffee | 3 +-- src/extensions/snippets/snippets.coffee | 18 ++++++++++++++++-- 6 files changed, 40 insertions(+), 15 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index d7b982e9d..50d939939 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -1,4 +1,5 @@ Snippets = require 'snippets' +RootView = require 'root-view' Buffer = require 'buffer' Editor = require 'editor' _ = require 'underscore' @@ -6,8 +7,11 @@ _ = require 'underscore' fdescribe "Snippets extension", -> [buffer, editor] = [] beforeEach -> - buffer = new Buffer(require.resolve('fixtures/sample.js')) - editor = new Editor({buffer}) + rootView = new RootView(require.resolve('fixtures/sample.js')) + editor = rootView.activeEditor() + buffer = editor.buffer + rootView.activateExtension(Snippets) + rootView.simulateDomAttachment() describe "when 'tab' is triggered on the editor", -> describe "when the letters preceding the cursor are registered as a global extension", -> @@ -16,19 +20,15 @@ fdescribe "Snippets extension", -> snippet te "Test snippet description" this is a test endsnippet - - snippet moo "Moo snippet" - Mooooooo! - endsnippet """ - editor.insertText("te") - editor.trigger 'tab' - expect(editor.getCursorScreenPosition()).toEqual [0, 2] - expect(buffer.lineForRow(0)).toBe "this is a testvar quicksort = function () {" - ffdescribe "Snippets parser", -> + editor.trigger 'tab' + expect(buffer.lineForRow(0)).toBe "this is a testvar quicksort = function () {" + expect(editor.getCursorScreenPosition()).toEqual [0, 14] + + describe "Snippets parser", -> it "can parse a snippet", -> snippets = Snippets.snippetsParser.parse """ snippet te "Test snippet description" diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee index 7fbf8792f..ffe0bb00b 100644 --- a/src/app/buffer.coffee +++ b/src/app/buffer.coffee @@ -32,6 +32,12 @@ class Buffer getPath: -> @path + getExtension: -> + if @getPath() + @getPath().split('/').pop().split('.').pop() + else + null + setPath: (path) -> @path = path @trigger "path-change", this diff --git a/src/app/cursor.coffee b/src/app/cursor.coffee index c151efc21..7db99eb49 100644 --- a/src/app/cursor.coffee +++ b/src/app/cursor.coffee @@ -155,6 +155,9 @@ class Cursor getCurrentLineBufferRange: -> @editSession.bufferRangeForBufferRow(@getBufferRow()) + getCurrentWordPrefix: -> + @editSession.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()]) + isAtBeginningOfLine: -> @getBufferPosition().column == 0 diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 7d71304a0..3c5a89036 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -329,6 +329,9 @@ class EditSession getSelectedText: -> @getLastSelection().getText() + getTextInBufferRange: (range) -> + @buffer.getTextInRange(range) + moveCursorUp: -> @moveCursors (cursor) -> cursor.moveUp() diff --git a/src/app/tokenized-buffer.coffee b/src/app/tokenized-buffer.coffee index 01c015c43..80649b8e4 100644 --- a/src/app/tokenized-buffer.coffee +++ b/src/app/tokenized-buffer.coffee @@ -23,8 +23,7 @@ class TokenizedBuffer @aceAdaptor = new AceAdaptor(this) requireAceMode: -> - extension = if @buffer.getPath() then @buffer.getPath().split('/').pop().split('.').pop() else null - modeName = switch extension + modeName = switch @buffer.getExtension() when 'js' then 'javascript' when 'coffee' then 'coffee' when 'rb', 'ru' then 'ruby' diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index d29938aba..10b4f3ec8 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -2,9 +2,23 @@ fs = require 'fs' PEG = require 'pegjs' module.exports = + name: 'Snippets' snippetsByExtension: {} snippetsParser: PEG.buildParser(fs.read(require.resolve 'extensions/snippets/snippets.pegjs')) - evalSnippets: (extension, text) -> - @snippetsByExtension[extension] = snippetsParser.parse(text) + activate: (@rootView) -> + rootView.on 'editor-open', (e, editor) => + editor.preempt 'tab', => + return false if @expandSnippet() + evalSnippets: (extension, text) -> + @snippetsByExtension[extension] = @snippetsParser.parse(text) + + expandSnippet: -> + editSession = @rootView.activeEditor().activeEditSession + return unless snippets = @snippetsByExtension[editSession.buffer.getExtension()] + prefix = editSession.getLastCursor().getCurrentWordPrefix() + if body = snippets[prefix]?.body + editSession.selectToBeginningOfWord() + editSession.insertText(body) + true From e4409be95acadddf97c80d6c0af0772384c67bba Mon Sep 17 00:00:00 2001 From: David Graham & Nathan Sobo Date: Tue, 19 Jun 2012 17:56:03 -0600 Subject: [PATCH 03/30] Parse multiple snippets and allow any characters in snippet body --- spec/extensions/snippets-spec.coffee | 24 ++++++++++++++++-------- src/extensions/snippets/snippets.pegjs | 4 ++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 50d939939..b40d36b69 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -29,15 +29,23 @@ fdescribe "Snippets extension", -> expect(editor.getCursorScreenPosition()).toEqual [0, 14] describe "Snippets parser", -> - it "can parse a snippet", -> + it "can parse multiple snippets", -> snippets = Snippets.snippetsParser.parse """ - snippet te "Test snippet description" - this is a test + snippet t1 "Test snippet 1" + this is a test 1 + endsnippet + + snippet t2 "Test snippet 2" + this is a test 2 endsnippet """ + expect(_.keys(snippets).length).toBe 2 + snippet = snippets['t1'] + expect(snippet.prefix).toBe 't1' + expect(snippet.description).toBe "Test snippet 1" + expect(snippet.body).toBe "this is a test 1" - expect(_.keys(snippets).length).toBe 1 - snippet = snippets['te'] - expect(snippet.prefix).toBe 'te' - expect(snippet.description).toBe "Test snippet description" - expect(snippet.body).toBe "this is a test" + snippet = snippets['t2'] + expect(snippet.prefix).toBe 't2' + expect(snippet.description).toBe "Test snippet 2" + expect(snippet.body).toBe "this is a test 2" diff --git a/src/extensions/snippets/snippets.pegjs b/src/extensions/snippets/snippets.pegjs index f40d399bc..98993deb3 100644 --- a/src/extensions/snippets/snippets.pegjs +++ b/src/extensions/snippets/snippets.pegjs @@ -6,7 +6,7 @@ snippets = snippets:snippet+ { return snippetsByPrefix; } -snippet = start ws prefix:prefix ws description:string separator body:body end { +snippet = ws? start ws prefix:prefix ws description:string separator body:body end { return { prefix: prefix, description: description, body: body }; } @@ -14,7 +14,7 @@ separator = [ ]* '\n' start = 'snippet' prefix = prefix:[A-Za-z0-9_]+ { return prefix.join(''); } body = body:bodyCharacter* { return body.join(''); } -bodyCharacter = !end char:[a-z ] { return char; } +bodyCharacter = !end char:. { return char; } end = '\nendsnippet' string = ['] body:[^']* ['] { return body.join(''); } From 498adc00bf68f3ad0ed1dbd075cdbd95ea4712c2 Mon Sep 17 00:00:00 2001 From: David Graham & Nathan Sobo Date: Wed, 20 Jun 2012 11:18:51 -0600 Subject: [PATCH 04/30] :lipstick: newline at eof --- src/extensions/snippets/index.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/snippets/index.coffee b/src/extensions/snippets/index.coffee index 7d31833a1..912e393e5 100644 --- a/src/extensions/snippets/index.coffee +++ b/src/extensions/snippets/index.coffee @@ -1 +1 @@ -module.exports = require 'extensions/snippets/snippets.coffee' \ No newline at end of file +module.exports = require 'extensions/snippets/snippets.coffee' From dc6c0a3e352ce1a1bf4e63757150b80a5f56282b Mon Sep 17 00:00:00 2001 From: David Graham & Nathan Sobo Date: Wed, 20 Jun 2012 11:19:40 -0600 Subject: [PATCH 05/30] Rename `userConfigurationPath` to `configFilePath` so I can add `configDirPath` as well --- src/app/atom.coffee | 6 ++++-- src/app/keymap.coffee | 2 +- src/app/window.coffee | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/atom.coffee b/src/app/atom.coffee index 36be59da3..1fdda1f62 100644 --- a/src/app/atom.coffee +++ b/src/app/atom.coffee @@ -9,13 +9,15 @@ module.exports = class Atom keymap: null windows: null - userConfigurationPath: null + configFilePath: null + configDirPath: null rootViewStates: null constructor: (@loadPath, nativeMethods) -> @windows = [] @setUpKeymap() - @userConfigurationPath = fs.absolute "~/.atom/atom.coffee" + @configDirPath = fs.absolute("~/.atom") + @configFilePath = fs.join(@configDirPath, "atom.coffee") @rootViewStates = {} setUpKeymap: -> diff --git a/src/app/keymap.coffee b/src/app/keymap.coffee index 3d4a59014..06f90bda0 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -20,7 +20,7 @@ class Keymap 'meta-o': 'open' $(document).on 'new-window', => $native.newWindow() - $(document).on 'open-user-configuration', => atom.open(atom.userConfigurationPath) + $(document).on 'open-user-configuration', => atom.open(atom.configFilePath) $(document).on 'open', => path = $native.openDialog() atom.open(path) if path diff --git a/src/app/window.coffee b/src/app/window.coffee index 05b7854d6..0685f3f3e 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -50,9 +50,9 @@ windowAdditions = loadUserConfiguration: -> try - require atom.userConfigurationPath if fs.exists(atom.userConfigurationPath) + require atom.configFilePath if fs.exists(atom.configFilePath) catch error - console.error "Failed to load `#{atom.userConfigurationPath}`", error.message, error + console.error "Failed to load `#{atom.configFilePath}`", error.message, error @showConsole() requireStylesheet: (path) -> From d992458c8cacd8ae5b8b8afd28687bc2aef47432 Mon Sep 17 00:00:00 2001 From: David Graham & Nathan Sobo Date: Wed, 20 Jun 2012 11:49:29 -0600 Subject: [PATCH 06/30] Load snippets from .atom/snippets when snippets extension is activated --- spec/extensions/snippets-spec.coffee | 16 ++++++++++++++++ src/extensions/snippets/snippets.coffee | 21 +++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index b40d36b69..759b60a2e 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -3,6 +3,7 @@ RootView = require 'root-view' Buffer = require 'buffer' Editor = require 'editor' _ = require 'underscore' +fs = require 'fs' fdescribe "Snippets extension", -> [buffer, editor] = [] @@ -28,6 +29,21 @@ fdescribe "Snippets extension", -> expect(buffer.lineForRow(0)).toBe "this is a testvar quicksort = function () {" expect(editor.getCursorScreenPosition()).toEqual [0, 14] + describe ".loadSnippetsFile(path)", -> + it "loads the snippets in the given file", -> + spyOn(fs, 'read').andReturn """ + snippet t1 "Test snippet 1" + this is a test 1 + endsnippet + """ + + Snippets.loadSnippetsFile('/tmp/foo/js.snippets') + expect(fs.read).toHaveBeenCalledWith('/tmp/foo/js.snippets') + + editor.insertText("t1") + editor.trigger 'tab' + expect(buffer.lineForRow(0)).toBe "this is a test 1var quicksort = function () {" + describe "Snippets parser", -> it "can parse multiple snippets", -> snippets = Snippets.snippetsParser.parse """ diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index 10b4f3ec8..1eba72844 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -7,14 +7,31 @@ module.exports = snippetsParser: PEG.buildParser(fs.read(require.resolve 'extensions/snippets/snippets.pegjs')) activate: (@rootView) -> + @loadSnippets() + + for editor in rootView.editors() + @enableSnippetsForEditor(editor) + rootView.on 'editor-open', (e, editor) => - editor.preempt 'tab', => - return false if @expandSnippet() + @enableSnippetsForEditor(editor) + + enableSnippetsForEditor: (editor) -> + editor.preempt 'tab', => return false if @expandSnippet() + + loadSnippets: -> + snippetsDir = fs.join(atom.configDirPath, 'snippets') + return unless fs.exists(snippetsDir) + @loadSnippetsFile(path) for path in fs.list(snippetsDir) when fs.extension(path) == '.snippets' + + loadSnippetsFile: (path) -> + @evalSnippets(fs.base(path, '.snippets'), fs.read(path)) evalSnippets: (extension, text) -> @snippetsByExtension[extension] = @snippetsParser.parse(text) + console.log @snippetsByExtension expandSnippet: -> + console.log "EXPAND SNIPPET" editSession = @rootView.activeEditor().activeEditSession return unless snippets = @snippetsByExtension[editSession.buffer.getExtension()] prefix = editSession.getLastCursor().getCurrentWordPrefix() From 8a5af9cd179375b0d1a9b167832761177968c5ee Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 12:06:48 -0600 Subject: [PATCH 07/30] Remove stray console.logs --- src/extensions/snippets/snippets.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index 1eba72844..02a74584e 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -28,10 +28,8 @@ module.exports = evalSnippets: (extension, text) -> @snippetsByExtension[extension] = @snippetsParser.parse(text) - console.log @snippetsByExtension expandSnippet: -> - console.log "EXPAND SNIPPET" editSession = @rootView.activeEditor().activeEditSession return unless snippets = @snippetsByExtension[editSession.buffer.getExtension()] prefix = editSession.getLastCursor().getCurrentWordPrefix() From dd69abbdfe22ac199f6c031f3b6c9145bbdfcd6e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 12:20:37 -0600 Subject: [PATCH 08/30] Add test case for non-matching prefix causing a regular tab to be inserted --- spec/extensions/snippets-spec.coffee | 36 +++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 759b60a2e..f0bd2e959 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -15,19 +15,33 @@ fdescribe "Snippets extension", -> rootView.simulateDomAttachment() describe "when 'tab' is triggered on the editor", -> - describe "when the letters preceding the cursor are registered as a global extension", -> - it "replaces the prefix with the snippet text", -> - Snippets.evalSnippets 'js', """ - snippet te "Test snippet description" - this is a test - endsnippet - """ - editor.insertText("te") - expect(editor.getCursorScreenPosition()).toEqual [0, 2] + beforeEach -> + Snippets.evalSnippets 'js', """ + snippet te "Test snippet description" + this is a test + endsnippet + """ + describe "when the letters preceding the cursor trigger a snippet", -> + describe "when the snippet contains no tab stops", -> + it "replaces the prefix with the snippet text and places the cursor at its end", -> + editor.insertText("te") + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + editor.trigger 'tab' + expect(buffer.lineForRow(0)).toBe "this is a testvar quicksort = function () {" + expect(editor.getCursorScreenPosition()).toEqual [0, 14] + + describe "when the snippet contains tab stops", -> + + + describe "when the letters preceding the cursor don't match a snippet", -> + it "inserts a tab as normal", -> + editor.insertText("xte") + expect(editor.getCursorScreenPosition()).toEqual [0, 3] editor.trigger 'tab' - expect(buffer.lineForRow(0)).toBe "this is a testvar quicksort = function () {" - expect(editor.getCursorScreenPosition()).toEqual [0, 14] + expect(buffer.lineForRow(0)).toBe "xte var quicksort = function () {" + expect(editor.getCursorScreenPosition()).toEqual [0, 5] describe ".loadSnippetsFile(path)", -> it "loads the snippets in the given file", -> From 583af86f6851fe6b3aaae5006f180a87b8ece164 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 17:34:21 -0600 Subject: [PATCH 09/30] Add _.adviseBefore for adding before advice to methods --- spec/stdlib/underscore-extensions-spec.coffee | 22 +++++++++++++++++++ src/stdlib/underscore-extensions.coffee | 6 +++++ 2 files changed, 28 insertions(+) create mode 100644 spec/stdlib/underscore-extensions-spec.coffee diff --git a/spec/stdlib/underscore-extensions-spec.coffee b/spec/stdlib/underscore-extensions-spec.coffee new file mode 100644 index 000000000..2c2b1ceac --- /dev/null +++ b/spec/stdlib/underscore-extensions-spec.coffee @@ -0,0 +1,22 @@ +_ = require 'underscore' + +describe "underscore extensions", -> + describe "_.adviseBefore", -> + [object, calls] = [] + + beforeEach -> + calls = [] + object = { + method: (args...) -> + calls.push(["original", args]) + } + + it "calls the given function before the advised method", -> + _.adviseBefore object, 'method', (args...) -> calls.push(["advice", args]) + object.method(1, 2, 3) + expect(calls).toEqual [['advice', [1, 2, 3]], ['original', [1, 2, 3]]] + + it "cancels the original method's invocation if the advice returns true", -> + _.adviseBefore object, 'method', -> false + object.method(1, 2, 3) + expect(calls).toEqual [] diff --git a/src/stdlib/underscore-extensions.coffee b/src/stdlib/underscore-extensions.coffee index ef59494cf..adbda8861 100644 --- a/src/stdlib/underscore-extensions.coffee +++ b/src/stdlib/underscore-extensions.coffee @@ -10,6 +10,12 @@ _.mixin sum += elt for elt in array sum + adviseBefore: (object, methodName, advice) -> + original = object[methodName] + object[methodName] = (args...) -> + unless advice(args...) == false + original(args...) + escapeRegExp: (string) -> # Referring to the table here: # https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/regexp From d50b7f7a378937dc94ede645fa9a338d939f410b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 17:40:45 -0600 Subject: [PATCH 10/30] Trigger a 'new-edit-session' event when Project.prototype.open creates an EditSession --- spec/app/project-spec.coffee | 16 +++++++++++----- src/app/project.coffee | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/spec/app/project-spec.coffee b/spec/app/project-spec.coffee index 2cab3e5a1..470fe2c4a 100644 --- a/spec/app/project-spec.coffee +++ b/spec/app/project-spec.coffee @@ -7,37 +7,43 @@ describe "Project", -> project = new Project(require.resolve('fixtures/dir')) describe ".open(path)", -> - [absolutePath, newBufferHandler] = [] + [absolutePath, newBufferHandler, newEditSessionHandler] = [] beforeEach -> absolutePath = require.resolve('fixtures/dir/a') newBufferHandler = jasmine.createSpy('newBufferHandler') project.on 'new-buffer', newBufferHandler + newEditSessionHandler = jasmine.createSpy('newEditSessionHandler') + project.on 'new-edit-session', newEditSessionHandler describe "when given an absolute path that hasn't been opened previously", -> - it "returns a new edit session for the given path and emits a 'new-buffer' event", -> + it "returns a new edit session for the given path and emits 'new-buffer' and 'new-edit-session' events", -> editSession = project.open(absolutePath) expect(editSession.buffer.path).toBe absolutePath expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer + expect(newEditSessionHandler).toHaveBeenCalledWith editSession describe "when given a relative path that hasn't been opened previously", -> - it "returns a new edit session for the given path (relative to the project root) and emits a 'new-buffer' event", -> + it "returns a new edit session for the given path (relative to the project root) and emits 'new-buffer' and 'new-edit-session' events", -> editSession = project.open('a') expect(editSession.buffer.path).toBe absolutePath expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer + expect(newEditSessionHandler).toHaveBeenCalledWith editSession describe "when passed the path to a buffer that has already been opened", -> - it "returns a new edit session containing previously opened buffer", -> + it "returns a new edit session containing previously opened buffer and emits a 'new-edit-session' event", -> editSession = project.open(absolutePath) newBufferHandler.reset() expect(project.open(absolutePath).buffer).toBe editSession.buffer expect(project.open('a').buffer).toBe editSession.buffer expect(newBufferHandler).not.toHaveBeenCalled() + expect(newEditSessionHandler).toHaveBeenCalledWith editSession describe "when not passed a path", -> - it "returns a new edit session and emits a new-buffer event", -> + it "returns a new edit session and emits 'new-buffer' and 'new-edit-session' events", -> editSession = project.open() expect(editSession.buffer.getPath()).toBeUndefined() expect(newBufferHandler).toHaveBeenCalledWith(editSession.buffer) + expect(newEditSessionHandler).toHaveBeenCalledWith editSession describe ".resolve(path)", -> it "returns an absolute path based on the project's root", -> diff --git a/src/app/project.coffee b/src/app/project.coffee index 1e66e163c..61e7b5635 100644 --- a/src/app/project.coffee +++ b/src/app/project.coffee @@ -90,6 +90,7 @@ class Project softWrap: @getSoftWrap() @editSessions.push editSession + @trigger 'new-edit-session', editSession editSession buildBuffer: (filePath) -> From a8a1a74b119031b11f5f60f98dd2f97d2c813ee6 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 17:46:22 -0600 Subject: [PATCH 11/30] Refactor Snippets to a class that creates an instance for each Editor --- spec/extensions/snippets-spec.coffee | 2 +- src/extensions/snippets/snippets.coffee | 52 ++++++++++++------------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 0beca965d..f9ac9aa7b 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -32,7 +32,7 @@ describe "Snippets extension", -> expect(editor.getCursorScreenPosition()).toEqual [0, 14] describe "when the snippet contains tab stops", -> - + describe "when the letters preceding the cursor don't match a snippet", -> it "inserts a tab as normal", -> diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index 02a74584e..9fd363f9d 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -2,38 +2,34 @@ fs = require 'fs' PEG = require 'pegjs' module.exports = - name: 'Snippets' - snippetsByExtension: {} - snippetsParser: PEG.buildParser(fs.read(require.resolve 'extensions/snippets/snippets.pegjs')) + class Snippets + @snippetsByExtension: {} + @snippetsParser: PEG.buildParser(fs.read(require.resolve 'extensions/snippets/snippets.pegjs')) - activate: (@rootView) -> - @loadSnippets() + @activate: (@rootView) -> + @loadSnippets() + rootView.on 'editor-open', (e, editor) => new Snippets(editor) - for editor in rootView.editors() - @enableSnippetsForEditor(editor) + @loadSnippets: -> + snippetsDir = fs.join(atom.configDirPath, 'snippets') + return unless fs.exists(snippetsDir) - rootView.on 'editor-open', (e, editor) => - @enableSnippetsForEditor(editor) + @loadSnippetsFile(path) for path in fs.list(snippetsDir) when fs.extension(path) == '.snippets' - enableSnippetsForEditor: (editor) -> - editor.preempt 'tab', => return false if @expandSnippet() + @loadSnippetsFile: (path) -> + @evalSnippets(fs.base(path, '.snippets'), fs.read(path)) - loadSnippets: -> - snippetsDir = fs.join(atom.configDirPath, 'snippets') - return unless fs.exists(snippetsDir) - @loadSnippetsFile(path) for path in fs.list(snippetsDir) when fs.extension(path) == '.snippets' + @evalSnippets: (extension, text) -> + @snippetsByExtension[extension] = @snippetsParser.parse(text) - loadSnippetsFile: (path) -> - @evalSnippets(fs.base(path, '.snippets'), fs.read(path)) + constructor: (@editor) -> + @editor.preempt 'tab', => return false if @expandSnippet() - evalSnippets: (extension, text) -> - @snippetsByExtension[extension] = @snippetsParser.parse(text) - - expandSnippet: -> - editSession = @rootView.activeEditor().activeEditSession - return unless snippets = @snippetsByExtension[editSession.buffer.getExtension()] - prefix = editSession.getLastCursor().getCurrentWordPrefix() - if body = snippets[prefix]?.body - editSession.selectToBeginningOfWord() - editSession.insertText(body) - true + expandSnippet: -> + editSession = @editor.activeEditSession + return unless snippets = @constructor.snippetsByExtension[editSession.buffer.getExtension()] + prefix = editSession.getLastCursor().getCurrentWordPrefix() + if body = snippets[prefix]?.body + editSession.selectToBeginningOfWord() + editSession.insertText(body) + true From 9c02e050519b26bdcdd4ee6cd4be88b93262fc71 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 17:55:29 -0600 Subject: [PATCH 12/30] Preserve the value of `this` when applying before advice --- spec/stdlib/underscore-extensions-spec.coffee | 6 +++--- src/stdlib/underscore-extensions.coffee | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/stdlib/underscore-extensions-spec.coffee b/spec/stdlib/underscore-extensions-spec.coffee index 2c2b1ceac..ee12f269e 100644 --- a/spec/stdlib/underscore-extensions-spec.coffee +++ b/spec/stdlib/underscore-extensions-spec.coffee @@ -8,13 +8,13 @@ describe "underscore extensions", -> calls = [] object = { method: (args...) -> - calls.push(["original", args]) + calls.push(["original", this, args]) } it "calls the given function before the advised method", -> - _.adviseBefore object, 'method', (args...) -> calls.push(["advice", args]) + _.adviseBefore object, 'method', (args...) -> calls.push(["advice", this, args]) object.method(1, 2, 3) - expect(calls).toEqual [['advice', [1, 2, 3]], ['original', [1, 2, 3]]] + expect(calls).toEqual [['advice', object, [1, 2, 3]], ['original', object, [1, 2, 3]]] it "cancels the original method's invocation if the advice returns true", -> _.adviseBefore object, 'method', -> false diff --git a/src/stdlib/underscore-extensions.coffee b/src/stdlib/underscore-extensions.coffee index adbda8861..1fda906ee 100644 --- a/src/stdlib/underscore-extensions.coffee +++ b/src/stdlib/underscore-extensions.coffee @@ -13,8 +13,8 @@ _.mixin adviseBefore: (object, methodName, advice) -> original = object[methodName] object[methodName] = (args...) -> - unless advice(args...) == false - original(args...) + unless advice.apply(this, args) == false + original.apply(this, args) escapeRegExp: (string) -> # Referring to the table here: From 1bec4c8404c13a65d45e7360f890910ebccad687 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 17:56:02 -0600 Subject: [PATCH 13/30] Snippets now advise the insertTab method on EditSessions instead of intercepting events on Editor --- spec/extensions/snippets-spec.coffee | 2 +- src/extensions/snippets/snippets.coffee | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index f9ac9aa7b..1fca1c695 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -9,9 +9,9 @@ describe "Snippets extension", -> [buffer, editor] = [] beforeEach -> rootView = new RootView(require.resolve('fixtures/sample.js')) + rootView.activateExtension(Snippets) editor = rootView.activeEditor() buffer = editor.buffer - rootView.activateExtension(Snippets) rootView.simulateDomAttachment() describe "when 'tab' is triggered on the editor", -> diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index 9fd363f9d..35b2f7308 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -1,5 +1,6 @@ fs = require 'fs' PEG = require 'pegjs' +_ = require 'underscore' module.exports = class Snippets @@ -8,7 +9,12 @@ module.exports = @activate: (@rootView) -> @loadSnippets() - rootView.on 'editor-open', (e, editor) => new Snippets(editor) + + project = rootView.project + + new Snippets(editSession) for editSession in project.editSessions + project.on 'new-edit-session', (editSession) => new Snippets(editSession) + @loadSnippets: -> snippetsDir = fs.join(atom.configDirPath, 'snippets') @@ -22,14 +28,13 @@ module.exports = @evalSnippets: (extension, text) -> @snippetsByExtension[extension] = @snippetsParser.parse(text) - constructor: (@editor) -> - @editor.preempt 'tab', => return false if @expandSnippet() + constructor: (@editSession) -> + _.adviseBefore @editSession, 'insertTab', => @expandSnippet() expandSnippet: -> - editSession = @editor.activeEditSession - return unless snippets = @constructor.snippetsByExtension[editSession.buffer.getExtension()] - prefix = editSession.getLastCursor().getCurrentWordPrefix() + return unless snippets = @constructor.snippetsByExtension[@editSession.buffer.getExtension()] + prefix = @editSession.getLastCursor().getCurrentWordPrefix() if body = snippets[prefix]?.body - editSession.selectToBeginningOfWord() - editSession.insertText(body) - true + @editSession.selectToBeginningOfWord() + @editSession.insertText(body) + false From 4590321f0a9a2960bcd04be6d9d4f16324802838 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 21:52:53 -0600 Subject: [PATCH 14/30] Apply a hybrid approach to handling events in snippets extension --- spec/extensions/snippets-spec.coffee | 8 +++- src/extensions/snippets/snippets.coffee | 57 ++++++++++++++----------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 1fca1c695..9db150add 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -17,14 +17,18 @@ describe "Snippets extension", -> describe "when 'tab' is triggered on the editor", -> beforeEach -> Snippets.evalSnippets 'js', """ - snippet te "Test snippet description" + snippet t1 "Snippet without tab stops" this is a test endsnippet + + snippet t2 "Snippet with tab stops" + first go here:$1 then here:$2 + endsnippet """ describe "when the letters preceding the cursor trigger a snippet", -> describe "when the snippet contains no tab stops", -> it "replaces the prefix with the snippet text and places the cursor at its end", -> - editor.insertText("te") + editor.insertText("t1") expect(editor.getCursorScreenPosition()).toEqual [0, 2] editor.trigger 'tab' diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index 35b2f7308..9e3517d00 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -3,38 +3,43 @@ PEG = require 'pegjs' _ = require 'underscore' module.exports = - class Snippets - @snippetsByExtension: {} - @snippetsParser: PEG.buildParser(fs.read(require.resolve 'extensions/snippets/snippets.pegjs')) + name: 'Snippets' + snippetsByExtension: {} + snippetsParser: PEG.buildParser(fs.read(require.resolve 'extensions/snippets/snippets.pegjs')) - @activate: (@rootView) -> - @loadSnippets() + activate: (@rootView) -> + @loadSnippets() - project = rootView.project + for editor in @rootView.editors() + @enableSnippetsInEditor(editor) - new Snippets(editSession) for editSession in project.editSessions - project.on 'new-edit-session', (editSession) => new Snippets(editSession) + @rootView.on 'editor-open', (e, editor) => + @enableSnippetsInEditor(editor) + loadSnippets: -> + snippetsDir = fs.join(atom.configDirPath, 'snippets') + return unless fs.exists(snippetsDir) + @loadSnippetsFile(path) for path in fs.list(snippetsDir) when fs.extension(path) == '.snippets' - @loadSnippets: -> - snippetsDir = fs.join(atom.configDirPath, 'snippets') - return unless fs.exists(snippetsDir) + loadSnippetsFile: (path) -> + @evalSnippets(fs.base(path, '.snippets'), fs.read(path)) - @loadSnippetsFile(path) for path in fs.list(snippetsDir) when fs.extension(path) == '.snippets' + evalSnippets: (extension, text) -> + @snippetsByExtension[extension] = @snippetsParser.parse(text) - @loadSnippetsFile: (path) -> - @evalSnippets(fs.base(path, '.snippets'), fs.read(path)) + enableSnippetsInEditor: (editor) -> + editor.preempt 'tab', => + editSession = editor.activeEditSession + editSession.snippetsSession ?= new SnippetsSession(editSession, @snippetsByExtension) + editSession.snippetsSession.expandSnippet() - @evalSnippets: (extension, text) -> - @snippetsByExtension[extension] = @snippetsParser.parse(text) +class SnippetsSession + constructor: (@editSession, @snippetsByExtension) -> - constructor: (@editSession) -> - _.adviseBefore @editSession, 'insertTab', => @expandSnippet() - - expandSnippet: -> - return unless snippets = @constructor.snippetsByExtension[@editSession.buffer.getExtension()] - prefix = @editSession.getLastCursor().getCurrentWordPrefix() - if body = snippets[prefix]?.body - @editSession.selectToBeginningOfWord() - @editSession.insertText(body) - false + expandSnippet: -> + return unless snippets = @snippetsByExtension[@editSession.buffer.getExtension()] + prefix = @editSession.getLastCursor().getCurrentWordPrefix() + if body = snippets[prefix]?.body + @editSession.selectToBeginningOfWord() + @editSession.insertText(body) + false From c276a4029e83321382bcc6f8e9af48073ae8ba16 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 21:53:06 -0600 Subject: [PATCH 15/30] Allow whitespace after last snippet declaration --- src/extensions/snippets/snippets.pegjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/snippets/snippets.pegjs b/src/extensions/snippets/snippets.pegjs index 98993deb3..f53683544 100644 --- a/src/extensions/snippets/snippets.pegjs +++ b/src/extensions/snippets/snippets.pegjs @@ -1,4 +1,4 @@ -snippets = snippets:snippet+ { +snippets = snippets:snippet+ ws? { var snippetsByPrefix = {}; snippets.forEach(function(snippet) { snippetsByPrefix[snippet.prefix] = snippet From e1309f7c66a3b028609ffa639f1f442484e1cfd5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 22:08:16 -0600 Subject: [PATCH 16/30] Key binding event handlers can call abortKeyBinding on the event object to abort and try the next binding --- spec/app/keymap-spec.coffee | 20 +++++++++++++++++++- src/app/keymap.coffee | 7 ++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/spec/app/keymap-spec.coffee b/spec/app/keymap-spec.coffee index 60e24434f..f50875bdb 100644 --- a/spec/app/keymap-spec.coffee +++ b/spec/app/keymap-spec.coffee @@ -72,8 +72,10 @@ describe "Keymap", -> expect(insertCharHandler).toHaveBeenCalled() describe "when the event's target node descends from multiple nodes that match selectors with a binding", -> - it "only triggers bindings on selectors associated with the closest ancestor node", -> + beforeEach -> keymap.bindKeys '.child-node', 'x': 'foo' + + it "only triggers bindings on selectors associated with the closest ancestor node", -> fooHandler = jasmine.createSpy 'fooHandler' fragment.on 'foo', fooHandler @@ -83,6 +85,22 @@ describe "Keymap", -> expect(deleteCharHandler).not.toHaveBeenCalled() expect(insertCharHandler).not.toHaveBeenCalled() + describe "when 'abortKeyBinding' is called on the triggered event", -> + it "aborts the current event and tries again with the next-most-specific key binding", -> + fooHandler1 = jasmine.createSpy('fooHandler1').andCallFake (e) -> + expect(deleteCharHandler).not.toHaveBeenCalled() + e.abortKeyBinding() + fooHandler2 = jasmine.createSpy('fooHandler2') + + fragment.find('.child-node').on 'foo', fooHandler1 + fragment.on 'foo', fooHandler2 + + target = fragment.find('.grandchild-node')[0] + keymap.handleKeyEvent(keydownEvent('x', target: target)) + expect(fooHandler1).toHaveBeenCalled() + expect(fooHandler2).not.toHaveBeenCalled() + expect(deleteCharHandler).toHaveBeenCalled() + describe "when the event bubbles to a node that matches multiple selectors", -> describe "when the matching selectors differ in specificity", -> it "triggers the binding for the most specific selector", -> diff --git a/src/app/keymap.coffee b/src/app/keymap.coffee index 06f90bda0..6a8652400 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -51,7 +51,7 @@ class Keymap for bindingSet in candidateBindingSets command = bindingSet.commandForEvent(event) if command - @triggerCommandEvent(event, command) + continue if @triggerCommandEvent(event, command) return false else if command == false return false @@ -66,7 +66,12 @@ class Keymap triggerCommandEvent: (keyEvent, commandName) -> commandEvent = $.Event(commandName) commandEvent.keyEvent = keyEvent + aborted = false + commandEvent.abortKeyBinding = -> + @stopImmediatePropagation() + aborted = true $(keyEvent.target).trigger(commandEvent) + aborted multiKeystrokeStringForEvent: (event) -> currentKeystroke = @keystrokeStringForEvent(event) From 68cb9992fc8640e2d135faa7721a0f334c4e8c75 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 22:45:14 -0600 Subject: [PATCH 17/30] Sort candidate binding sets in a stable way in Keymap to preserve load order for a valid cascade --- src/app/binding-set.coffee | 2 +- src/app/keymap.coffee | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/app/binding-set.coffee b/src/app/binding-set.coffee index aaf08fda9..3882a7236 100644 --- a/src/app/binding-set.coffee +++ b/src/app/binding-set.coffee @@ -12,7 +12,7 @@ class BindingSet commandForEvent: null parser: null - constructor: (@selector, mapOrFunction) -> + constructor: (@selector, mapOrFunction, @index) -> @parser = PEG.buildParser(fs.read(require.resolve 'keystroke-pattern.pegjs')) @specificity = Specificity(@selector) @commandsByKeystrokes = {} diff --git a/src/app/keymap.coffee b/src/app/keymap.coffee index 6a8652400..1faa80063 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -26,15 +26,15 @@ class Keymap atom.open(path) if path bindKeys: (selector, bindings) -> - @bindingSets.unshift(new BindingSet(selector, bindings)) + index = @bindingSets.length + @bindingSets.unshift(new BindingSet(selector, bindings, index)) bindingsForElement: (element) -> keystrokeMap = {} currentNode = $(element) while currentNode.length - bindingSets = @bindingSets.filter (set) -> currentNode.is(set.selector) - bindingSets.sort (a, b) -> b.specificity - a.specificity + bindingSets = @bindingSetsForNode(currentNode) _.defaults(keystrokeMap, set.commandsByKeystrokes) for set in bindingSets currentNode = currentNode.parent() @@ -46,8 +46,7 @@ class Keymap @queuedKeystrokes = null currentNode = $(event.target) while currentNode.length - candidateBindingSets = @bindingSets.filter (set) -> currentNode.is(set.selector) - candidateBindingSets.sort (a, b) -> b.specificity - a.specificity + candidateBindingSets = @bindingSetsForNode(currentNode) for bindingSet in candidateBindingSets command = bindingSet.commandForEvent(event) if command @@ -63,6 +62,14 @@ class Keymap !isMultiKeystroke + bindingSetsForNode: (node) -> + bindingSets = @bindingSets.filter (set) -> node.is(set.selector) + bindingSets.sort (a, b) -> + if b.specificity == a.specificity + b.index - a.index + else + b.specificity - a.specificity + triggerCommandEvent: (keyEvent, commandName) -> commandEvent = $.Event(commandName) commandEvent.keyEvent = keyEvent From f1678fdafe9b2b6ac51b2909175dd36adb0e8f02 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 20 Jun 2012 22:47:05 -0600 Subject: [PATCH 18/30] Use an overloaded 'tab' keybinding and the new `abortKeyBinding` method to implement conditional snippet expansion If the current word prefix doesn't correspond to a valid snippet, we abort the key binding and try the next one, which ends up being the standard tab binding so a typical tab gets inserted. This is a mechanism that could support overloading of arbitrary keys. --- spec/extensions/snippets-spec.coffee | 6 ++++-- src/app/keymaps/snippets.coffee | 6 ++++++ src/extensions/snippets/snippets.coffee | 16 ++++++++-------- 3 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 src/app/keymaps/snippets.coffee diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 9db150add..499d88b95 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -13,6 +13,7 @@ describe "Snippets extension", -> editor = rootView.activeEditor() buffer = editor.buffer rootView.simulateDomAttachment() + rootView.enableKeymap() describe "when 'tab' is triggered on the editor", -> beforeEach -> @@ -25,13 +26,14 @@ describe "Snippets extension", -> first go here:$1 then here:$2 endsnippet """ + describe "when the letters preceding the cursor trigger a snippet", -> describe "when the snippet contains no tab stops", -> it "replaces the prefix with the snippet text and places the cursor at its end", -> editor.insertText("t1") expect(editor.getCursorScreenPosition()).toEqual [0, 2] - editor.trigger 'tab' + editor.trigger keydownEvent('tab', target: editor[0]) expect(buffer.lineForRow(0)).toBe "this is a testvar quicksort = function () {" expect(editor.getCursorScreenPosition()).toEqual [0, 14] @@ -59,7 +61,7 @@ describe "Snippets extension", -> expect(fs.read).toHaveBeenCalledWith('/tmp/foo/js.snippets') editor.insertText("t1") - editor.trigger 'tab' + editor.trigger 'snippets:expand' expect(buffer.lineForRow(0)).toBe "this is a test 1var quicksort = function () {" describe "Snippets parser", -> diff --git a/src/app/keymaps/snippets.coffee b/src/app/keymaps/snippets.coffee new file mode 100644 index 000000000..6227dd8c1 --- /dev/null +++ b/src/app/keymaps/snippets.coffee @@ -0,0 +1,6 @@ +window.keymap.bindKeys '.editor' + 'tab': 'snippets:expand' + +window.keymap.bindKeys '.editor' + 'tab': 'snippets:next-tab-stop' + 'shift-tab': 'snippets:previous-tab-stop' diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index 9e3517d00..d69a8c00d 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -9,12 +9,7 @@ module.exports = activate: (@rootView) -> @loadSnippets() - - for editor in @rootView.editors() - @enableSnippetsInEditor(editor) - - @rootView.on 'editor-open', (e, editor) => - @enableSnippetsInEditor(editor) + @rootView.on 'editor-open', (e, editor) => @enableSnippetsInEditor(editor) loadSnippets: -> snippetsDir = fs.join(atom.configDirPath, 'snippets') @@ -28,10 +23,13 @@ module.exports = @snippetsByExtension[extension] = @snippetsParser.parse(text) enableSnippetsInEditor: (editor) -> - editor.preempt 'tab', => + editor.on 'snippets:expand', (e) => editSession = editor.activeEditSession editSession.snippetsSession ?= new SnippetsSession(editSession, @snippetsByExtension) - editSession.snippetsSession.expandSnippet() + e.abortKeyBinding() unless editSession.snippetsSession.expandSnippet() + + # this is currently disabled. soon we will jump tab stops if a snippet is active + editor.on 'snippets:next-tab-stop', (e) -> e.abortKeyBinding() class SnippetsSession constructor: (@editSession, @snippetsByExtension) -> @@ -42,4 +40,6 @@ class SnippetsSession if body = snippets[prefix]?.body @editSession.selectToBeginningOfWord() @editSession.insertText(body) + true + else false From 2e0943f41a51a0621d0c9e540879b7d214ea56c3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 21 Jun 2012 16:29:12 -0600 Subject: [PATCH 19/30] Upgrade PEG.js to 0.7.0 for line and column tracking (needed for snippet tab-stops) --- vendor/pegjs.js | 6915 +++++++++++++++++++++-------------------------- 1 file changed, 3151 insertions(+), 3764 deletions(-) diff --git a/vendor/pegjs.js b/vendor/pegjs.js index e9d300558..42e67b359 100644 --- a/vendor/pegjs.js +++ b/vendor/pegjs.js @@ -1,12 +1,16 @@ -/* PEG.js 0.6.2 (http://pegjs.majda.cz/) */ - -(function() { - -var undefined; +/* + * PEG.js 0.7.0 + * + * http://pegjs.majda.cz/ + * + * Copyright (c) 2010-2012 David Majda + * Licensend under the MIT license. + */ +var PEG = (function(undefined) { var PEG = { - /* PEG.js version. */ - VERSION: "0.6.2", + /* PEG.js version (uses semantic versioning). */ + VERSION: "0.7.0", /* * Generates a parser from a specified grammar and returns it. @@ -19,8 +23,8 @@ var PEG = { * errors are detected during the generation and some may protrude to the * generated parser and cause its malfunction. */ - buildParser: function(grammar) { - return PEG.compiler.compile(PEG.parser.parse(grammar)); + buildParser: function(grammar, options) { + return PEG.compiler.compile(PEG.parser.parse(grammar), options); } }; @@ -33,6 +37,29 @@ PEG.GrammarError = function(message) { PEG.GrammarError.prototype = Error.prototype; +/* Like Python's |range|, but without |step|. */ +function range(start, stop) { + if (stop === undefined) { + stop = start; + start = 0; + } + + var result = new Array(Math.max(0, stop - start)); + for (var i = 0, j = start; j < stop; i++, j++) { + result[i] = j; + } + return result; +} + +function find(array, callback) { + var length = array.length; + for (var i = 0; i < length; i++) { + if (callback(array[i])) { + return array[i]; + } + } +} + function contains(array, value) { /* * Stupid IE does not have Array.prototype.indexOf, otherwise this function @@ -50,7 +77,7 @@ function contains(array, value) { function each(array, callback) { var length = array.length; for (var i = 0; i < length; i++) { - callback(array[i]); + callback(array[i], i); } } @@ -58,7 +85,27 @@ function map(array, callback) { var result = []; var length = array.length; for (var i = 0; i < length; i++) { - result[i] = callback(array[i]); + result[i] = callback(array[i], i); + } + return result; +} + +function pluck(array, key) { + return map(array, function (e) { return e[key]; }); +} + +function keys(object) { + var result = []; + for (var key in object) { + result.push(key); + } + return result; +} + +function values(object) { + var result = []; + for (var key in object) { + result.push(object[key]); } return result; } @@ -66,7 +113,7 @@ function map(array, callback) { /* * Returns a string padded on the left to a desired length with a character. * - * The code needs to be in sync with th code template in the compilation + * The code needs to be in sync with the code template in the compilation * function for "action" nodes. */ function padLeft(input, padding, length) { @@ -84,18 +131,20 @@ function padLeft(input, padding, length) { * Returns an escape sequence for given character. Uses \x for characters <= * 0xFF to save space, \u for the rest. * - * The code needs to be in sync with th code template in the compilation + * The code needs to be in sync with the code template in the compilation * function for "action" nodes. */ function escape(ch) { var charCode = ch.charCodeAt(0); + var escapeChar; + var length; if (charCode <= 0xFF) { - var escapeChar = 'x'; - var length = 2; + escapeChar = 'x'; + length = 2; } else { - var escapeChar = 'u'; - var length = 4; + escapeChar = 'u'; + length = 4; } return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length); @@ -105,7 +154,7 @@ function escape(ch) { * Surrounds the string with quotes and escapes characters inside so that the * result is a valid JavaScript string. * - * The code needs to be in sync with th code template in the compilation + * The code needs to be in sync with the code template in the compilation * function for "action" nodes. */ function quote(s) { @@ -115,16 +164,21 @@ function quote(s) { * line separator, paragraph separator, and line feed. Any character may * appear in the form of an escape sequence. * - * For portability, we also escape escape all non-ASCII characters. + * For portability, we also escape escape all control and non-ASCII + * characters. Note that "\0" and "\v" escape sequences are not used because + * JSHint does not like the first and IE the second. */ return '"' + s - .replace(/\\/g, '\\\\') // backslash - .replace(/"/g, '\\"') // closing quote character - .replace(/\r/g, '\\r') // carriage return - .replace(/\n/g, '\\n') // line feed - .replace(/[\x80-\uFFFF]/g, escape) // non-ASCII characters + .replace(/\\/g, '\\\\') // backslash + .replace(/"/g, '\\"') // closing quote character + .replace(/\x08/g, '\\b') // backspace + .replace(/\t/g, '\\t') // horizontal tab + .replace(/\n/g, '\\n') // line feed + .replace(/\f/g, '\\f') // form feed + .replace(/\r/g, '\\r') // carriage return + .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, escape) + '"'; -}; +} /* * Escapes characters inside the string so that it can be used as a list of @@ -134,17 +188,21 @@ function quoteForRegexpClass(s) { /* * Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1. * - * For portability, we also escape escape all non-ASCII characters. + * For portability, we also escape escape all control and non-ASCII + * characters. */ return s - .replace(/\\/g, '\\\\') // backslash - .replace(/\0/g, '\\0') // null, IE needs this - .replace(/\//g, '\\/') // closing slash - .replace(/]/g, '\\]') // closing bracket - .replace(/-/g, '\\-') // dash - .replace(/\r/g, '\\r') // carriage return - .replace(/\n/g, '\\n') // line feed - .replace(/[\x80-\uFFFF]/g, escape) // non-ASCII characters + .replace(/\\/g, '\\\\') // backslash + .replace(/\//g, '\\/') // closing slash + .replace(/\]/g, '\\]') // closing bracket + .replace(/-/g, '\\-') // dash + .replace(/\0/g, '\\0') // null + .replace(/\t/g, '\\t') // horizontal tab + .replace(/\n/g, '\\n') // line feed + .replace(/\v/g, '\\x0B') // vertical tab + .replace(/\f/g, '\\f') // form feed + .replace(/\r/g, '\\r') // carriage return + .replace(/[\x01-\x08\x0E-\x1F\x80-\uFFFF]/g, escape); } /* @@ -156,10 +214,41 @@ function quoteForRegexpClass(s) { function buildNodeVisitor(functions) { return function(node) { return functions[node.type].apply(null, arguments); - } + }; +} + +function findRuleByName(ast, name) { + return find(ast.rules, function(r) { return r.name === name; }); } PEG.parser = (function(){ - /* Generated by PEG.js 0.6.2 (http://pegjs.majda.cz/). */ + /* + * Generated by PEG.js 0.7.0. + * + * http://pegjs.majda.cz/ + */ + + function quote(s) { + /* + * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a + * string literal except for the closing quote character, backslash, + * carriage return, line separator, paragraph separator, and line feed. + * Any character may appear in the form of an escape sequence. + * + * For portability, we also escape escape all control and non-ASCII + * characters. Note that "\0" and "\v" escape sequences are not used + * because JSHint does not like the first and IE the second. + */ + return '"' + s + .replace(/\\/g, '\\\\') // backslash + .replace(/"/g, '\\"') // closing quote character + .replace(/\x08/g, '\\b') // backspace + .replace(/\t/g, '\\t') // horizontal tab + .replace(/\n/g, '\\n') // line feed + .replace(/\f/g, '\\f') // form feed + .replace(/\r/g, '\\r') // carriage return + .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, escape) + + '"'; + } var result = { /* @@ -170,61 +259,62 @@ PEG.parser = (function(){ */ parse: function(input, startRule) { var parseFunctions = { - "__": parse___, - "action": parse_action, - "and": parse_and, - "braced": parse_braced, - "bracketDelimitedCharacter": parse_bracketDelimitedCharacter, + "grammar": parse_grammar, + "initializer": parse_initializer, + "rule": parse_rule, "choice": parse_choice, - "class": parse_class, - "classCharacter": parse_classCharacter, - "classCharacterRange": parse_classCharacterRange, + "sequence": parse_sequence, + "labeled": parse_labeled, + "prefixed": parse_prefixed, + "suffixed": parse_suffixed, + "primary": parse_primary, + "action": parse_action, + "braced": parse_braced, + "nonBraceCharacters": parse_nonBraceCharacters, + "nonBraceCharacter": parse_nonBraceCharacter, + "equals": parse_equals, "colon": parse_colon, - "comment": parse_comment, - "digit": parse_digit, + "semicolon": parse_semicolon, + "slash": parse_slash, + "and": parse_and, + "not": parse_not, + "question": parse_question, + "star": parse_star, + "plus": parse_plus, + "lparen": parse_lparen, + "rparen": parse_rparen, "dot": parse_dot, + "identifier": parse_identifier, + "literal": parse_literal, + "string": parse_string, + "doubleQuotedString": parse_doubleQuotedString, "doubleQuotedCharacter": parse_doubleQuotedCharacter, - "doubleQuotedLiteral": parse_doubleQuotedLiteral, + "simpleDoubleQuotedCharacter": parse_simpleDoubleQuotedCharacter, + "singleQuotedString": parse_singleQuotedString, + "singleQuotedCharacter": parse_singleQuotedCharacter, + "simpleSingleQuotedCharacter": parse_simpleSingleQuotedCharacter, + "class": parse_class, + "classCharacterRange": parse_classCharacterRange, + "classCharacter": parse_classCharacter, + "bracketDelimitedCharacter": parse_bracketDelimitedCharacter, + "simpleBracketDelimitedCharacter": parse_simpleBracketDelimitedCharacter, + "simpleEscapeSequence": parse_simpleEscapeSequence, + "zeroEscapeSequence": parse_zeroEscapeSequence, + "hexEscapeSequence": parse_hexEscapeSequence, + "unicodeEscapeSequence": parse_unicodeEscapeSequence, + "eolEscapeSequence": parse_eolEscapeSequence, + "digit": parse_digit, + "hexDigit": parse_hexDigit, + "letter": parse_letter, + "lowerCaseLetter": parse_lowerCaseLetter, + "upperCaseLetter": parse_upperCaseLetter, + "__": parse___, + "comment": parse_comment, + "singleLineComment": parse_singleLineComment, + "multiLineComment": parse_multiLineComment, "eol": parse_eol, "eolChar": parse_eolChar, - "eolEscapeSequence": parse_eolEscapeSequence, - "equals": parse_equals, - "grammar": parse_grammar, - "hexDigit": parse_hexDigit, - "hexEscapeSequence": parse_hexEscapeSequence, - "identifier": parse_identifier, - "initializer": parse_initializer, - "labeled": parse_labeled, - "letter": parse_letter, - "literal": parse_literal, - "lowerCaseLetter": parse_lowerCaseLetter, - "lparen": parse_lparen, - "multiLineComment": parse_multiLineComment, - "nonBraceCharacter": parse_nonBraceCharacter, - "nonBraceCharacters": parse_nonBraceCharacters, - "not": parse_not, - "plus": parse_plus, - "prefixed": parse_prefixed, - "primary": parse_primary, - "question": parse_question, - "rparen": parse_rparen, - "rule": parse_rule, - "semicolon": parse_semicolon, - "sequence": parse_sequence, - "simpleBracketDelimitedCharacter": parse_simpleBracketDelimitedCharacter, - "simpleDoubleQuotedCharacter": parse_simpleDoubleQuotedCharacter, - "simpleEscapeSequence": parse_simpleEscapeSequence, - "simpleSingleQuotedCharacter": parse_simpleSingleQuotedCharacter, - "singleLineComment": parse_singleLineComment, - "singleQuotedCharacter": parse_singleQuotedCharacter, - "singleQuotedLiteral": parse_singleQuotedLiteral, - "slash": parse_slash, - "star": parse_star, - "suffixed": parse_suffixed, - "unicodeEscapeSequence": parse_unicodeEscapeSequence, - "upperCaseLetter": parse_upperCaseLetter, - "whitespace": parse_whitespace, - "zeroEscapeSequence": parse_zeroEscapeSequence + "whitespace": parse_whitespace }; if (startRule !== undefined) { @@ -236,10 +326,9 @@ PEG.parser = (function(){ } var pos = 0; - var reportMatchFailures = true; - var rightmostMatchFailuresPos = 0; - var rightmostMatchFailuresExpected = []; - var cache = {}; + var reportFailures = 0; + var rightmostFailuresPos = 0; + var rightmostFailuresExpected = []; function padLeft(input, padding, length) { var result = input; @@ -254,3795 +343,2728 @@ PEG.parser = (function(){ function escape(ch) { var charCode = ch.charCodeAt(0); + var escapeChar; + var length; if (charCode <= 0xFF) { - var escapeChar = 'x'; - var length = 2; + escapeChar = 'x'; + length = 2; } else { - var escapeChar = 'u'; - var length = 4; + escapeChar = 'u'; + length = 4; } return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length); } - function quote(s) { - /* - * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a - * string literal except for the closing quote character, backslash, - * carriage return, line separator, paragraph separator, and line feed. - * Any character may appear in the form of an escape sequence. - */ - return '"' + s - .replace(/\\/g, '\\\\') // backslash - .replace(/"/g, '\\"') // closing quote character - .replace(/\r/g, '\\r') // carriage return - .replace(/\n/g, '\\n') // line feed - .replace(/[\x80-\uFFFF]/g, escape) // non-ASCII characters - + '"'; - } - function matchFailed(failure) { - if (pos < rightmostMatchFailuresPos) { + if (pos < rightmostFailuresPos) { return; } - if (pos > rightmostMatchFailuresPos) { - rightmostMatchFailuresPos = pos; - rightmostMatchFailuresExpected = []; + if (pos > rightmostFailuresPos) { + rightmostFailuresPos = pos; + rightmostFailuresExpected = []; } - rightmostMatchFailuresExpected.push(failure); + rightmostFailuresExpected.push(failure); } function parse_grammar() { - var cacheKey = 'grammar@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2, result3; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse___(); - if (result3 !== null) { - var result7 = parse_initializer(); - var result4 = result7 !== null ? result7 : ''; - if (result4 !== null) { - var result6 = parse_rule(); - if (result6 !== null) { - var result5 = []; - while (result6 !== null) { - result5.push(result6); - var result6 = parse_rule(); + pos0 = pos; + pos1 = pos; + result0 = parse___(); + if (result0 !== null) { + result1 = parse_initializer(); + result1 = result1 !== null ? result1 : ""; + if (result1 !== null) { + result3 = parse_rule(); + if (result3 !== null) { + result2 = []; + while (result3 !== null) { + result2.push(result3); + result3 = parse_rule(); } } else { - var result5 = null; + result2 = null; } - if (result5 !== null) { - var result1 = [result3, result4, result5]; + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(initializer, rules) { - var rulesConverted = {}; - each(rules, function(rule) { rulesConverted[rule.name] = rule; }); - - return { - type: "grammar", - initializer: initializer !== "" ? initializer : null, - rules: rulesConverted, - startRule: rules[0].name - } - })(result1[1], result1[2]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, initializer, rules) { + return { + type: "grammar", + initializer: initializer !== "" ? initializer : null, + rules: rules, + startRule: rules[0].name + }; + })(pos0, result0[1], result0[2]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_initializer() { - var cacheKey = 'initializer@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_action(); - if (result3 !== null) { - var result5 = parse_semicolon(); - var result4 = result5 !== null ? result5 : ''; - if (result4 !== null) { - var result1 = [result3, result4]; + pos0 = pos; + pos1 = pos; + result0 = parse_action(); + if (result0 !== null) { + result1 = parse_semicolon(); + result1 = result1 !== null ? result1 : ""; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(code) { - return { - type: "initializer", - code: code - }; - })(result1[0]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, code) { + return { + type: "initializer", + code: code + }; + })(pos0, result0[0]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_rule() { - var cacheKey = 'rule@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2, result3, result4; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_identifier(); - if (result3 !== null) { - var result10 = parse_literal(); - if (result10 !== null) { - var result4 = result10; - } else { - if (input.substr(pos, 0) === "") { - var result9 = ""; - pos += 0; - } else { - var result9 = null; - if (reportMatchFailures) { - matchFailed("\"\""); - } - } - if (result9 !== null) { - var result4 = result9; - } else { - var result4 = null;; - }; - } - if (result4 !== null) { - var result5 = parse_equals(); - if (result5 !== null) { - var result6 = parse_choice(); - if (result6 !== null) { - var result8 = parse_semicolon(); - var result7 = result8 !== null ? result8 : ''; - if (result7 !== null) { - var result1 = [result3, result4, result5, result6, result7]; + pos0 = pos; + pos1 = pos; + result0 = parse_identifier(); + if (result0 !== null) { + result1 = parse_string(); + result1 = result1 !== null ? result1 : ""; + if (result1 !== null) { + result2 = parse_equals(); + if (result2 !== null) { + result3 = parse_choice(); + if (result3 !== null) { + result4 = parse_semicolon(); + result4 = result4 !== null ? result4 : ""; + if (result4 !== null) { + result0 = [result0, result1, result2, result3, result4]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(name, displayName, expression) { - return { - type: "rule", - name: name, - displayName: displayName !== "" ? displayName : null, - expression: expression - }; - })(result1[0], result1[1], result1[3]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, name, displayName, expression) { + return { + type: "rule", + name: name, + displayName: displayName !== "" ? displayName : null, + expression: expression + }; + })(pos0, result0[0], result0[1], result0[3]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_choice() { - var cacheKey = 'choice@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2, result3; + var pos0, pos1, pos2; - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_sequence(); - if (result3 !== null) { - var result4 = []; - var savedPos2 = pos; - var result6 = parse_slash(); - if (result6 !== null) { - var result7 = parse_sequence(); - if (result7 !== null) { - var result5 = [result6, result7]; + pos0 = pos; + pos1 = pos; + result0 = parse_sequence(); + if (result0 !== null) { + result1 = []; + pos2 = pos; + result2 = parse_slash(); + if (result2 !== null) { + result3 = parse_sequence(); + if (result3 !== null) { + result2 = [result2, result3]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } - while (result5 !== null) { - result4.push(result5); - var savedPos2 = pos; - var result6 = parse_slash(); - if (result6 !== null) { - var result7 = parse_sequence(); - if (result7 !== null) { - var result5 = [result6, result7]; + while (result2 !== null) { + result1.push(result2); + pos2 = pos; + result2 = parse_slash(); + if (result2 !== null) { + result3 = parse_sequence(); + if (result3 !== null) { + result2 = [result2, result3]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(head, tail) { - if (tail.length > 0) { - var alternatives = [head].concat(map( - tail, - function(element) { return element[1]; } - )); - return { - type: "choice", - alternatives: alternatives - } - } else { - return head; - } - })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, head, tail) { + if (tail.length > 0) { + var alternatives = [head].concat(map( + tail, + function(element) { return element[1]; } + )); + return { + type: "choice", + alternatives: alternatives + }; + } else { + return head; + } + })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_sequence() { - var cacheKey = 'sequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos1 = pos; - var savedPos2 = pos; - var result8 = []; - var result10 = parse_labeled(); - while (result10 !== null) { - result8.push(result10); - var result10 = parse_labeled(); + pos0 = pos; + pos1 = pos; + result0 = []; + result1 = parse_labeled(); + while (result1 !== null) { + result0.push(result1); + result1 = parse_labeled(); } - if (result8 !== null) { - var result9 = parse_action(); - if (result9 !== null) { - var result6 = [result8, result9]; + if (result0 !== null) { + result1 = parse_action(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result6 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result6 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } - var result7 = result6 !== null - ? (function(elements, code) { - var expression = elements.length != 1 + if (result0 !== null) { + result0 = (function(offset, elements, code) { + var expression = elements.length !== 1 + ? { + type: "sequence", + elements: elements + } + : elements[0]; + return { + type: "action", + expression: expression, + code: code + }; + })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + result0 = []; + result1 = parse_labeled(); + while (result1 !== null) { + result0.push(result1); + result1 = parse_labeled(); + } + if (result0 !== null) { + result0 = (function(offset, elements) { + return elements.length !== 1 ? { type: "sequence", elements: elements } : elements[0]; - return { - type: "action", - expression: expression, - code: code - }; - })(result6[0], result6[1]) - : null; - if (result7 !== null) { - var result5 = result7; - } else { - var result5 = null; - pos = savedPos1; - } - if (result5 !== null) { - var result0 = result5; - } else { - var savedPos0 = pos; - var result2 = []; - var result4 = parse_labeled(); - while (result4 !== null) { - result2.push(result4); - var result4 = parse_labeled(); + })(pos0, result0); } - var result3 = result2 !== null - ? (function(elements) { - return elements.length != 1 - ? { - type: "sequence", - elements: elements - } - : elements[0]; - })(result2) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_labeled() { - var cacheKey = 'labeled@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - var result5 = parse_identifier(); - if (result5 !== null) { - var result6 = parse_colon(); - if (result6 !== null) { - var result7 = parse_prefixed(); - if (result7 !== null) { - var result3 = [result5, result6, result7]; + pos0 = pos; + pos1 = pos; + result0 = parse_identifier(); + if (result0 !== null) { + result1 = parse_colon(); + if (result1 !== null) { + result2 = parse_prefixed(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result4 = result3 !== null - ? (function(label, expression) { - return { - type: "labeled", - label: label, - expression: expression - }; - })(result3[0], result3[2]) - : null; - if (result4 !== null) { - var result2 = result4; - } else { - var result2 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, label, expression) { + return { + type: "labeled", + label: label, + expression: expression + }; + })(pos0, result0[0], result0[2]); } - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_prefixed(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + result0 = parse_prefixed(); } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_prefixed() { - var cacheKey = 'prefixed@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos6 = pos; - var savedPos7 = pos; - var result20 = parse_and(); - if (result20 !== null) { - var result21 = parse_action(); - if (result21 !== null) { - var result18 = [result20, result21]; + pos0 = pos; + pos1 = pos; + result0 = parse_and(); + if (result0 !== null) { + result1 = parse_action(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result18 = null; - pos = savedPos7; + result0 = null; + pos = pos1; } } else { - var result18 = null; - pos = savedPos7; + result0 = null; + pos = pos1; } - var result19 = result18 !== null - ? (function(code) { - return { - type: "semantic_and", - code: code - }; - })(result18[1]) - : null; - if (result19 !== null) { - var result17 = result19; - } else { - var result17 = null; - pos = savedPos6; + if (result0 !== null) { + result0 = (function(offset, code) { + return { + type: "semantic_and", + code: code + }; + })(pos0, result0[1]); } - if (result17 !== null) { - var result0 = result17; - } else { - var savedPos4 = pos; - var savedPos5 = pos; - var result15 = parse_and(); - if (result15 !== null) { - var result16 = parse_suffixed(); - if (result16 !== null) { - var result13 = [result15, result16]; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_and(); + if (result0 !== null) { + result1 = parse_suffixed(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result13 = null; - pos = savedPos5; + result0 = null; + pos = pos1; } } else { - var result13 = null; - pos = savedPos5; + result0 = null; + pos = pos1; } - var result14 = result13 !== null - ? (function(expression) { - return { - type: "simple_and", - expression: expression - }; - })(result13[1]) - : null; - if (result14 !== null) { - var result12 = result14; - } else { - var result12 = null; - pos = savedPos4; + if (result0 !== null) { + result0 = (function(offset, expression) { + return { + type: "simple_and", + expression: expression + }; + })(pos0, result0[1]); } - if (result12 !== null) { - var result0 = result12; - } else { - var savedPos2 = pos; - var savedPos3 = pos; - var result10 = parse_not(); - if (result10 !== null) { - var result11 = parse_action(); - if (result11 !== null) { - var result8 = [result10, result11]; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_not(); + if (result0 !== null) { + result1 = parse_action(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } - var result9 = result8 !== null - ? (function(code) { - return { - type: "semantic_not", - code: code - }; - })(result8[1]) - : null; - if (result9 !== null) { - var result7 = result9; - } else { - var result7 = null; - pos = savedPos2; + if (result0 !== null) { + result0 = (function(offset, code) { + return { + type: "semantic_not", + code: code + }; + })(pos0, result0[1]); } - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - var result5 = parse_not(); - if (result5 !== null) { - var result6 = parse_suffixed(); - if (result6 !== null) { - var result3 = [result5, result6]; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_not(); + if (result0 !== null) { + result1 = parse_suffixed(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result4 = result3 !== null - ? (function(expression) { - return { - type: "simple_not", - expression: expression - }; - })(result3[1]) - : null; - if (result4 !== null) { - var result2 = result4; - } else { - var result2 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, expression) { + return { + type: "simple_not", + expression: expression + }; + })(pos0, result0[1]); } - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_suffixed(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + result0 = parse_suffixed(); + } + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_suffixed() { - var cacheKey = 'suffixed@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos4 = pos; - var savedPos5 = pos; - var result15 = parse_primary(); - if (result15 !== null) { - var result16 = parse_question(); - if (result16 !== null) { - var result13 = [result15, result16]; + pos0 = pos; + pos1 = pos; + result0 = parse_primary(); + if (result0 !== null) { + result1 = parse_question(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result13 = null; - pos = savedPos5; + result0 = null; + pos = pos1; } } else { - var result13 = null; - pos = savedPos5; + result0 = null; + pos = pos1; } - var result14 = result13 !== null - ? (function(expression) { + if (result0 !== null) { + result0 = (function(offset, expression) { + return { + type: "optional", + expression: expression + }; + })(pos0, result0[0]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_primary(); + if (result0 !== null) { + result1 = parse_star(); + if (result1 !== null) { + result0 = [result0, result1]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, expression) { return { - type: "optional", + type: "zero_or_more", expression: expression }; - })(result13[0]) - : null; - if (result14 !== null) { - var result12 = result14; - } else { - var result12 = null; - pos = savedPos4; - } - if (result12 !== null) { - var result0 = result12; - } else { - var savedPos2 = pos; - var savedPos3 = pos; - var result10 = parse_primary(); - if (result10 !== null) { - var result11 = parse_star(); - if (result11 !== null) { - var result8 = [result10, result11]; - } else { - var result8 = null; - pos = savedPos3; - } - } else { - var result8 = null; - pos = savedPos3; + })(pos0, result0[0]); } - var result9 = result8 !== null - ? (function(expression) { - return { - type: "zero_or_more", - expression: expression - }; - })(result8[0]) - : null; - if (result9 !== null) { - var result7 = result9; - } else { - var result7 = null; - pos = savedPos2; + if (result0 === null) { + pos = pos0; } - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - var result5 = parse_primary(); - if (result5 !== null) { - var result6 = parse_plus(); - if (result6 !== null) { - var result3 = [result5, result6]; + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_primary(); + if (result0 !== null) { + result1 = parse_plus(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result4 = result3 !== null - ? (function(expression) { - return { - type: "one_or_more", - expression: expression - }; - })(result3[0]) - : null; - if (result4 !== null) { - var result2 = result4; - } else { - var result2 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, expression) { + return { + type: "one_or_more", + expression: expression + }; + })(pos0, result0[0]); } - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_primary(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + result0 = parse_primary(); + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_primary() { - var cacheKey = 'primary@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2; + var pos0, pos1, pos2, pos3; - - var savedPos4 = pos; - var savedPos5 = pos; - var result17 = parse_identifier(); - if (result17 !== null) { - var savedPos6 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - var savedPos7 = pos; - var result23 = parse_literal(); - if (result23 !== null) { - var result20 = result23; - } else { - if (input.substr(pos, 0) === "") { - var result22 = ""; - pos += 0; + pos0 = pos; + pos1 = pos; + result0 = parse_identifier(); + if (result0 !== null) { + pos2 = pos; + reportFailures++; + pos3 = pos; + result1 = parse_string(); + result1 = result1 !== null ? result1 : ""; + if (result1 !== null) { + result2 = parse_equals(); + if (result2 !== null) { + result1 = [result1, result2]; } else { - var result22 = null; - if (reportMatchFailures) { - matchFailed("\"\""); - } - } - if (result22 !== null) { - var result20 = result22; - } else { - var result20 = null;; - }; - } - if (result20 !== null) { - var result21 = parse_equals(); - if (result21 !== null) { - var result19 = [result20, result21]; - } else { - var result19 = null; - pos = savedPos7; + result1 = null; + pos = pos3; } } else { - var result19 = null; - pos = savedPos7; + result1 = null; + pos = pos3; } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result19 === null) { - var result18 = ''; + reportFailures--; + if (result1 === null) { + result1 = ""; } else { - var result18 = null; - pos = savedPos6; + result1 = null; + pos = pos2; } - if (result18 !== null) { - var result15 = [result17, result18]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result15 = null; - pos = savedPos5; + result0 = null; + pos = pos1; } } else { - var result15 = null; - pos = savedPos5; + result0 = null; + pos = pos1; } - var result16 = result15 !== null - ? (function(name) { - return { - type: "rule_ref", - name: name - }; - })(result15[0]) - : null; - if (result16 !== null) { - var result14 = result16; - } else { - var result14 = null; - pos = savedPos4; + if (result0 !== null) { + result0 = (function(offset, name) { + return { + type: "rule_ref", + name: name + }; + })(pos0, result0[0]); } - if (result14 !== null) { - var result0 = result14; - } else { - var savedPos3 = pos; - var result12 = parse_literal(); - var result13 = result12 !== null - ? (function(value) { - return { - type: "literal", - value: value - }; - })(result12) - : null; - if (result13 !== null) { - var result11 = result13; - } else { - var result11 = null; - pos = savedPos3; - } - if (result11 !== null) { - var result0 = result11; - } else { - var savedPos2 = pos; - var result9 = parse_dot(); - var result10 = result9 !== null - ? (function() { return { type: "any" }; })() - : null; - if (result10 !== null) { - var result8 = result10; - } else { - var result8 = null; - pos = savedPos2; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + result0 = parse_literal(); + if (result0 === null) { + pos0 = pos; + result0 = parse_dot(); + if (result0 !== null) { + result0 = (function(offset) { return { type: "any" }; })(pos0); } - if (result8 !== null) { - var result0 = result8; - } else { - var result7 = parse_class(); - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - var result4 = parse_lparen(); - if (result4 !== null) { - var result5 = parse_choice(); - if (result5 !== null) { - var result6 = parse_rparen(); - if (result6 !== null) { - var result2 = [result4, result5, result6]; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + result0 = parse_class(); + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_lparen(); + if (result0 !== null) { + result1 = parse_choice(); + if (result1 !== null) { + result2 = parse_rparen(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result3 = result2 !== null - ? (function(expression) { return expression; })(result2[1]) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, expression) { return expression; })(pos0, result0[1]); } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; + if (result0 === null) { + pos = pos0; + } + } + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_action() { - var cacheKey = 'action@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_braced(); - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + reportFailures++; + pos0 = pos; + pos1 = pos; + result0 = parse_braced(); + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(braced) { return braced.substr(1, braced.length - 2); })(result1[0]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, braced) { return braced.substr(1, braced.length - 2); })(pos0, result0[0]); } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + if (result0 === null) { + pos = pos0; + } + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("action"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_braced() { - var cacheKey = 'braced@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "{") { - var result3 = "{"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 123) { + result0 = "{"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"{\""); } } - if (result3 !== null) { - var result4 = []; - var result8 = parse_braced(); - if (result8 !== null) { - var result6 = result8; - } else { - var result7 = parse_nonBraceCharacter(); - if (result7 !== null) { - var result6 = result7; - } else { - var result6 = null;; - }; + if (result0 !== null) { + result1 = []; + result2 = parse_braced(); + if (result2 === null) { + result2 = parse_nonBraceCharacter(); } - while (result6 !== null) { - result4.push(result6); - var result8 = parse_braced(); - if (result8 !== null) { - var result6 = result8; - } else { - var result7 = parse_nonBraceCharacter(); - if (result7 !== null) { - var result6 = result7; - } else { - var result6 = null;; - }; + while (result2 !== null) { + result1.push(result2); + result2 = parse_braced(); + if (result2 === null) { + result2 = parse_nonBraceCharacter(); } } - if (result4 !== null) { - if (input.substr(pos, 1) === "}") { - var result5 = "}"; - pos += 1; + if (result1 !== null) { + if (input.charCodeAt(pos) === 125) { + result2 = "}"; + pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"}\""); } } - if (result5 !== null) { - var result1 = [result3, result4, result5]; + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(parts) { - return "{" + parts.join("") + "}"; - })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, parts) { + return "{" + parts.join("") + "}"; + })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_nonBraceCharacters() { - var cacheKey = 'nonBraceCharacters@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0; - - var savedPos0 = pos; - var result3 = parse_nonBraceCharacter(); - if (result3 !== null) { - var result1 = []; - while (result3 !== null) { - result1.push(result3); - var result3 = parse_nonBraceCharacter(); + pos0 = pos; + result1 = parse_nonBraceCharacter(); + if (result1 !== null) { + result0 = []; + while (result1 !== null) { + result0.push(result1); + result1 = parse_nonBraceCharacter(); } } else { - var result1 = null; + result0 = null; } - var result2 = result1 !== null - ? (function(chars) { return chars.join(""); })(result1) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, chars) { return chars.join(""); })(pos0, result0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_nonBraceCharacter() { - var cacheKey = 'nonBraceCharacter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - - if (input.substr(pos).match(/^[^{}]/) !== null) { - var result0 = input.charAt(pos); + if (/^[^{}]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[^{}]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_equals() { - var cacheKey = 'equals@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "=") { - var result3 = "="; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 61) { + result0 = "="; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"=\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return "="; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return "="; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_colon() { - var cacheKey = 'colon@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === ":") { - var result3 = ":"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 58) { + result0 = ":"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\":\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return ":"; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return ":"; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_semicolon() { - var cacheKey = 'semicolon@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === ";") { - var result3 = ";"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 59) { + result0 = ";"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\";\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return ";"; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return ";"; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_slash() { - var cacheKey = 'slash@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "/") { - var result3 = "/"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 47) { + result0 = "/"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"/\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return "/"; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return "/"; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_and() { - var cacheKey = 'and@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "&") { - var result3 = "&"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 38) { + result0 = "&"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"&\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return "&"; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return "&"; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_not() { - var cacheKey = 'not@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "!") { - var result3 = "!"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 33) { + result0 = "!"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"!\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return "!"; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return "!"; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_question() { - var cacheKey = 'question@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "?") { - var result3 = "?"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 63) { + result0 = "?"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"?\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return "?"; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return "?"; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_star() { - var cacheKey = 'star@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "*") { - var result3 = "*"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 42) { + result0 = "*"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"*\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return "*"; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return "*"; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_plus() { - var cacheKey = 'plus@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "+") { - var result3 = "+"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 43) { + result0 = "+"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"+\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return "+"; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return "+"; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_lparen() { - var cacheKey = 'lparen@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "(") { - var result3 = "("; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 40) { + result0 = "("; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"(\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return "("; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return "("; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_rparen() { - var cacheKey = 'rparen@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === ")") { - var result3 = ")"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 41) { + result0 = ")"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\")\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return ")"; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return ")"; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_dot() { - var cacheKey = 'dot@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === ".") { - var result3 = "."; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 46) { + result0 = "."; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return "."; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return "."; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_identifier() { - var cacheKey = 'identifier@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2; + var pos0, pos1; - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var savedPos0 = pos; - var savedPos1 = pos; - var result13 = parse_letter(); - if (result13 !== null) { - var result3 = result13; - } else { - if (input.substr(pos, 1) === "_") { - var result12 = "_"; - pos += 1; + reportFailures++; + pos0 = pos; + pos1 = pos; + result0 = parse_letter(); + if (result0 === null) { + if (input.charCodeAt(pos) === 95) { + result0 = "_"; + pos++; } else { - var result12 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"_\""); } } - if (result12 !== null) { - var result3 = result12; - } else { - if (input.substr(pos, 1) === "$") { - var result11 = "$"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 36) { + result0 = "$"; + pos++; } else { - var result11 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"$\""); } } - if (result11 !== null) { - var result3 = result11; - } else { - var result3 = null;; - }; - }; + } } - if (result3 !== null) { - var result4 = []; - var result10 = parse_letter(); - if (result10 !== null) { - var result6 = result10; - } else { - var result9 = parse_digit(); - if (result9 !== null) { - var result6 = result9; - } else { - if (input.substr(pos, 1) === "_") { - var result8 = "_"; - pos += 1; + if (result0 !== null) { + result1 = []; + result2 = parse_letter(); + if (result2 === null) { + result2 = parse_digit(); + if (result2 === null) { + if (input.charCodeAt(pos) === 95) { + result2 = "_"; + pos++; } else { - var result8 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"_\""); } } - if (result8 !== null) { - var result6 = result8; - } else { - if (input.substr(pos, 1) === "$") { - var result7 = "$"; - pos += 1; + if (result2 === null) { + if (input.charCodeAt(pos) === 36) { + result2 = "$"; + pos++; } else { - var result7 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"$\""); } } - if (result7 !== null) { - var result6 = result7; - } else { - var result6 = null;; - }; - }; - }; + } + } } - while (result6 !== null) { - result4.push(result6); - var result10 = parse_letter(); - if (result10 !== null) { - var result6 = result10; - } else { - var result9 = parse_digit(); - if (result9 !== null) { - var result6 = result9; - } else { - if (input.substr(pos, 1) === "_") { - var result8 = "_"; - pos += 1; + while (result2 !== null) { + result1.push(result2); + result2 = parse_letter(); + if (result2 === null) { + result2 = parse_digit(); + if (result2 === null) { + if (input.charCodeAt(pos) === 95) { + result2 = "_"; + pos++; } else { - var result8 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"_\""); } } - if (result8 !== null) { - var result6 = result8; - } else { - if (input.substr(pos, 1) === "$") { - var result7 = "$"; - pos += 1; + if (result2 === null) { + if (input.charCodeAt(pos) === 36) { + result2 = "$"; + pos++; } else { - var result7 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"$\""); } } - if (result7 !== null) { - var result6 = result7; - } else { - var result6 = null;; - }; - }; - }; + } + } } } - if (result4 !== null) { - var result5 = parse___(); - if (result5 !== null) { - var result1 = [result3, result4, result5]; + if (result1 !== null) { + result2 = parse___(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(head, tail) { - return head + tail.join(""); - })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, head, tail) { + return head + tail.join(""); + })(pos0, result0[0], result0[1]); } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + if (result0 === null) { + pos = pos0; + } + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("identifier"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_literal() { - var cacheKey = 'literal@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2; + var pos0, pos1; - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var savedPos0 = pos; - var savedPos1 = pos; - var result6 = parse_doubleQuotedLiteral(); - if (result6 !== null) { - var result3 = result6; - } else { - var result5 = parse_singleQuotedLiteral(); - if (result5 !== null) { - var result3 = result5; - } else { - var result3 = null;; - }; + reportFailures++; + pos0 = pos; + pos1 = pos; + result0 = parse_doubleQuotedString(); + if (result0 === null) { + result0 = parse_singleQuotedString(); } - if (result3 !== null) { - var result4 = parse___(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + if (input.charCodeAt(pos) === 105) { + result1 = "i"; + pos++; } else { - var result1 = null; - pos = savedPos1; + result1 = null; + if (reportFailures === 0) { + matchFailed("\"i\""); + } + } + result1 = result1 !== null ? result1 : ""; + if (result1 !== null) { + result2 = parse___(); + if (result2 !== null) { + result0 = [result0, result1, result2]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(literal) { return literal; })(result1[0]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, value, flags) { + return { + type: "literal", + value: value, + ignoreCase: flags === "i" + }; + })(pos0, result0[0], result0[1]); } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + if (result0 === null) { + pos = pos0; + } + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("literal"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } - function parse_doubleQuotedLiteral() { - var cacheKey = 'doubleQuotedLiteral@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; + function parse_string() { + var result0, result1; + var pos0, pos1; + + reportFailures++; + pos0 = pos; + pos1 = pos; + result0 = parse_doubleQuotedString(); + if (result0 === null) { + result0 = parse_singleQuotedString(); } - - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "\"") { - var result3 = "\""; - pos += 1; + if (result0 !== null) { + result1 = parse___(); + if (result1 !== null) { + result0 = [result0, result1]; + } else { + result0 = null; + pos = pos1; + } } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, string) { return string; })(pos0, result0[0]); + } + if (result0 === null) { + pos = pos0; + } + reportFailures--; + if (reportFailures === 0 && result0 === null) { + matchFailed("string"); + } + return result0; + } + + function parse_doubleQuotedString() { + var result0, result1, result2; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 34) { + result0 = "\""; + pos++; + } else { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\"\""); } } - if (result3 !== null) { - var result4 = []; - var result6 = parse_doubleQuotedCharacter(); - while (result6 !== null) { - result4.push(result6); - var result6 = parse_doubleQuotedCharacter(); + if (result0 !== null) { + result1 = []; + result2 = parse_doubleQuotedCharacter(); + while (result2 !== null) { + result1.push(result2); + result2 = parse_doubleQuotedCharacter(); } - if (result4 !== null) { - if (input.substr(pos, 1) === "\"") { - var result5 = "\""; - pos += 1; + if (result1 !== null) { + if (input.charCodeAt(pos) === 34) { + result2 = "\""; + pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"\\\"\""); } } - if (result5 !== null) { - var result1 = [result3, result4, result5]; + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(chars) { return chars.join(""); })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, chars) { return chars.join(""); })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_doubleQuotedCharacter() { - var cacheKey = 'doubleQuotedCharacter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; + var result0; + + result0 = parse_simpleDoubleQuotedCharacter(); + if (result0 === null) { + result0 = parse_simpleEscapeSequence(); + if (result0 === null) { + result0 = parse_zeroEscapeSequence(); + if (result0 === null) { + result0 = parse_hexEscapeSequence(); + if (result0 === null) { + result0 = parse_unicodeEscapeSequence(); + if (result0 === null) { + result0 = parse_eolEscapeSequence(); + } + } + } + } } - - - var result6 = parse_simpleDoubleQuotedCharacter(); - if (result6 !== null) { - var result0 = result6; - } else { - var result5 = parse_simpleEscapeSequence(); - if (result5 !== null) { - var result0 = result5; - } else { - var result4 = parse_zeroEscapeSequence(); - if (result4 !== null) { - var result0 = result4; - } else { - var result3 = parse_hexEscapeSequence(); - if (result3 !== null) { - var result0 = result3; - } else { - var result2 = parse_unicodeEscapeSequence(); - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_eolEscapeSequence(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; - }; - } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_simpleDoubleQuotedCharacter() { - var cacheKey = 'simpleDoubleQuotedCharacter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1, pos2; - - var savedPos0 = pos; - var savedPos1 = pos; - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - if (input.substr(pos, 1) === "\"") { - var result8 = "\""; - pos += 1; + pos0 = pos; + pos1 = pos; + pos2 = pos; + reportFailures++; + if (input.charCodeAt(pos) === 34) { + result0 = "\""; + pos++; } else { - var result8 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\"\""); } } - if (result8 !== null) { - var result5 = result8; - } else { - if (input.substr(pos, 1) === "\\") { - var result7 = "\\"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result7 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result7 !== null) { - var result5 = result7; - } else { - var result6 = parse_eolChar(); - if (result6 !== null) { - var result5 = result6; - } else { - var result5 = null;; - }; - }; + if (result0 === null) { + result0 = parse_eolChar(); + } } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result5 === null) { - var result3 = ''; + reportFailures--; + if (result0 === null) { + result0 = ""; } else { - var result3 = null; - pos = savedPos2; + result0 = null; + pos = pos2; } - if (result3 !== null) { + if (result0 !== null) { if (input.length > pos) { - var result4 = input.charAt(pos); + result1 = input.charAt(pos); pos++; } else { - var result4 = null; - if (reportMatchFailures) { - matchFailed('any character'); + result1 = null; + if (reportFailures === 0) { + matchFailed("any character"); } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(char_) { return char_; })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, char_) { return char_; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } - function parse_singleQuotedLiteral() { - var cacheKey = 'singleQuotedLiteral@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + function parse_singleQuotedString() { + var result0, result1, result2; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "'") { - var result3 = "'"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 39) { + result0 = "'"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"'\""); } } - if (result3 !== null) { - var result4 = []; - var result6 = parse_singleQuotedCharacter(); - while (result6 !== null) { - result4.push(result6); - var result6 = parse_singleQuotedCharacter(); + if (result0 !== null) { + result1 = []; + result2 = parse_singleQuotedCharacter(); + while (result2 !== null) { + result1.push(result2); + result2 = parse_singleQuotedCharacter(); } - if (result4 !== null) { - if (input.substr(pos, 1) === "'") { - var result5 = "'"; - pos += 1; + if (result1 !== null) { + if (input.charCodeAt(pos) === 39) { + result2 = "'"; + pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"'\""); } } - if (result5 !== null) { - var result1 = [result3, result4, result5]; + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(chars) { return chars.join(""); })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, chars) { return chars.join(""); })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_singleQuotedCharacter() { - var cacheKey = 'singleQuotedCharacter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; + var result0; + + result0 = parse_simpleSingleQuotedCharacter(); + if (result0 === null) { + result0 = parse_simpleEscapeSequence(); + if (result0 === null) { + result0 = parse_zeroEscapeSequence(); + if (result0 === null) { + result0 = parse_hexEscapeSequence(); + if (result0 === null) { + result0 = parse_unicodeEscapeSequence(); + if (result0 === null) { + result0 = parse_eolEscapeSequence(); + } + } + } + } } - - - var result6 = parse_simpleSingleQuotedCharacter(); - if (result6 !== null) { - var result0 = result6; - } else { - var result5 = parse_simpleEscapeSequence(); - if (result5 !== null) { - var result0 = result5; - } else { - var result4 = parse_zeroEscapeSequence(); - if (result4 !== null) { - var result0 = result4; - } else { - var result3 = parse_hexEscapeSequence(); - if (result3 !== null) { - var result0 = result3; - } else { - var result2 = parse_unicodeEscapeSequence(); - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_eolEscapeSequence(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; - }; - } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_simpleSingleQuotedCharacter() { - var cacheKey = 'simpleSingleQuotedCharacter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1, pos2; - - var savedPos0 = pos; - var savedPos1 = pos; - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - if (input.substr(pos, 1) === "'") { - var result8 = "'"; - pos += 1; + pos0 = pos; + pos1 = pos; + pos2 = pos; + reportFailures++; + if (input.charCodeAt(pos) === 39) { + result0 = "'"; + pos++; } else { - var result8 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"'\""); } } - if (result8 !== null) { - var result5 = result8; - } else { - if (input.substr(pos, 1) === "\\") { - var result7 = "\\"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result7 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result7 !== null) { - var result5 = result7; - } else { - var result6 = parse_eolChar(); - if (result6 !== null) { - var result5 = result6; - } else { - var result5 = null;; - }; - }; + if (result0 === null) { + result0 = parse_eolChar(); + } } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result5 === null) { - var result3 = ''; + reportFailures--; + if (result0 === null) { + result0 = ""; } else { - var result3 = null; - pos = savedPos2; + result0 = null; + pos = pos2; } - if (result3 !== null) { + if (result0 !== null) { if (input.length > pos) { - var result4 = input.charAt(pos); + result1 = input.charAt(pos); pos++; } else { - var result4 = null; - if (reportMatchFailures) { - matchFailed('any character'); + result1 = null; + if (reportFailures === 0) { + matchFailed("any character"); } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(char_) { return char_; })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, char_) { return char_; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_class() { - var cacheKey = 'class@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2, result3, result4, result5; + var pos0, pos1; - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "[") { - var result3 = "["; - pos += 1; + reportFailures++; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 91) { + result0 = "["; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"[\""); } } - if (result3 !== null) { - if (input.substr(pos, 1) === "^") { - var result11 = "^"; - pos += 1; + if (result0 !== null) { + if (input.charCodeAt(pos) === 94) { + result1 = "^"; + pos++; } else { - var result11 = null; - if (reportMatchFailures) { + result1 = null; + if (reportFailures === 0) { matchFailed("\"^\""); } } - var result4 = result11 !== null ? result11 : ''; - if (result4 !== null) { - var result5 = []; - var result10 = parse_classCharacterRange(); - if (result10 !== null) { - var result8 = result10; - } else { - var result9 = parse_classCharacter(); - if (result9 !== null) { - var result8 = result9; - } else { - var result8 = null;; - }; + result1 = result1 !== null ? result1 : ""; + if (result1 !== null) { + result2 = []; + result3 = parse_classCharacterRange(); + if (result3 === null) { + result3 = parse_classCharacter(); } - while (result8 !== null) { - result5.push(result8); - var result10 = parse_classCharacterRange(); - if (result10 !== null) { - var result8 = result10; - } else { - var result9 = parse_classCharacter(); - if (result9 !== null) { - var result8 = result9; - } else { - var result8 = null;; - }; + while (result3 !== null) { + result2.push(result3); + result3 = parse_classCharacterRange(); + if (result3 === null) { + result3 = parse_classCharacter(); } } - if (result5 !== null) { - if (input.substr(pos, 1) === "]") { - var result6 = "]"; - pos += 1; + if (result2 !== null) { + if (input.charCodeAt(pos) === 93) { + result3 = "]"; + pos++; } else { - var result6 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\"]\""); } } - if (result6 !== null) { - var result7 = parse___(); - if (result7 !== null) { - var result1 = [result3, result4, result5, result6, result7]; + if (result3 !== null) { + if (input.charCodeAt(pos) === 105) { + result4 = "i"; + pos++; } else { - var result1 = null; - pos = savedPos1; + result4 = null; + if (reportFailures === 0) { + matchFailed("\"i\""); + } + } + result4 = result4 !== null ? result4 : ""; + if (result4 !== null) { + result5 = parse___(); + if (result5 !== null) { + result0 = [result0, result1, result2, result3, result4, result5]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(inverted, parts) { - var partsConverted = map(parts, function(part) { return part.data; }); - var rawText = "[" - + inverted - + map(parts, function(part) { return part.rawText; }).join("") - + "]"; + if (result0 !== null) { + result0 = (function(offset, inverted, parts, flags) { + var partsConverted = map(parts, function(part) { return part.data; }); + var rawText = "[" + + inverted + + map(parts, function(part) { return part.rawText; }).join("") + + "]" + + flags; - return { - type: "class", - inverted: inverted === "^", - parts: partsConverted, - // FIXME: Get the raw text from the input directly. - rawText: rawText - }; - })(result1[1], result1[2]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + return { + type: "class", + inverted: inverted === "^", + ignoreCase: flags === "i", + parts: partsConverted, + // FIXME: Get the raw text from the input directly. + rawText: rawText + }; + })(pos0, result0[1], result0[2], result0[4]); } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + if (result0 === null) { + pos = pos0; + } + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("character class"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_classCharacterRange() { - var cacheKey = 'classCharacterRange@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_classCharacter(); - if (result3 !== null) { - if (input.substr(pos, 1) === "-") { - var result4 = "-"; - pos += 1; + pos0 = pos; + pos1 = pos; + result0 = parse_classCharacter(); + if (result0 !== null) { + if (input.charCodeAt(pos) === 45) { + result1 = "-"; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result1 = null; + if (reportFailures === 0) { matchFailed("\"-\""); } } - if (result4 !== null) { - var result5 = parse_classCharacter(); - if (result5 !== null) { - var result1 = [result3, result4, result5]; + if (result1 !== null) { + result2 = parse_classCharacter(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(begin, end) { - if (begin.data.charCodeAt(0) > end.data.charCodeAt(0)) { - throw new this.SyntaxError( - "Invalid character range: " + begin.rawText + "-" + end.rawText + "." - ); - } + if (result0 !== null) { + result0 = (function(offset, begin, end) { + if (begin.data.charCodeAt(0) > end.data.charCodeAt(0)) { + throw new this.SyntaxError( + "Invalid character range: " + begin.rawText + "-" + end.rawText + "." + ); + } - return { - data: [begin.data, end.data], - // FIXME: Get the raw text from the input directly. - rawText: begin.rawText + "-" + end.rawText - } - })(result1[0], result1[2]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + return { + data: [begin.data, end.data], + // FIXME: Get the raw text from the input directly. + rawText: begin.rawText + "-" + end.rawText + }; + })(pos0, result0[0], result0[2]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_classCharacter() { - var cacheKey = 'classCharacter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; + var result0; + var pos0; + + pos0 = pos; + result0 = parse_bracketDelimitedCharacter(); + if (result0 !== null) { + result0 = (function(offset, char_) { + return { + data: char_, + // FIXME: Get the raw text from the input directly. + rawText: quoteForRegexpClass(char_) + }; + })(pos0, result0); } - - - var savedPos0 = pos; - var result1 = parse_bracketDelimitedCharacter(); - var result2 = result1 !== null - ? (function(char_) { - return { - data: char_, - // FIXME: Get the raw text from the input directly. - rawText: quoteForRegexpClass(char_) - }; - })(result1) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_bracketDelimitedCharacter() { - var cacheKey = 'bracketDelimitedCharacter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; + var result0; + + result0 = parse_simpleBracketDelimitedCharacter(); + if (result0 === null) { + result0 = parse_simpleEscapeSequence(); + if (result0 === null) { + result0 = parse_zeroEscapeSequence(); + if (result0 === null) { + result0 = parse_hexEscapeSequence(); + if (result0 === null) { + result0 = parse_unicodeEscapeSequence(); + if (result0 === null) { + result0 = parse_eolEscapeSequence(); + } + } + } + } } - - - var result6 = parse_simpleBracketDelimitedCharacter(); - if (result6 !== null) { - var result0 = result6; - } else { - var result5 = parse_simpleEscapeSequence(); - if (result5 !== null) { - var result0 = result5; - } else { - var result4 = parse_zeroEscapeSequence(); - if (result4 !== null) { - var result0 = result4; - } else { - var result3 = parse_hexEscapeSequence(); - if (result3 !== null) { - var result0 = result3; - } else { - var result2 = parse_unicodeEscapeSequence(); - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_eolEscapeSequence(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; - }; - } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_simpleBracketDelimitedCharacter() { - var cacheKey = 'simpleBracketDelimitedCharacter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1, pos2; - - var savedPos0 = pos; - var savedPos1 = pos; - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - if (input.substr(pos, 1) === "]") { - var result8 = "]"; - pos += 1; + pos0 = pos; + pos1 = pos; + pos2 = pos; + reportFailures++; + if (input.charCodeAt(pos) === 93) { + result0 = "]"; + pos++; } else { - var result8 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"]\""); } } - if (result8 !== null) { - var result5 = result8; - } else { - if (input.substr(pos, 1) === "\\") { - var result7 = "\\"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result7 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result7 !== null) { - var result5 = result7; - } else { - var result6 = parse_eolChar(); - if (result6 !== null) { - var result5 = result6; - } else { - var result5 = null;; - }; - }; + if (result0 === null) { + result0 = parse_eolChar(); + } } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result5 === null) { - var result3 = ''; + reportFailures--; + if (result0 === null) { + result0 = ""; } else { - var result3 = null; - pos = savedPos2; + result0 = null; + pos = pos2; } - if (result3 !== null) { + if (result0 !== null) { if (input.length > pos) { - var result4 = input.charAt(pos); + result1 = input.charAt(pos); pos++; } else { - var result4 = null; - if (reportMatchFailures) { - matchFailed('any character'); + result1 = null; + if (reportFailures === 0) { + matchFailed("any character"); } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(char_) { return char_; })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, char_) { return char_; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_simpleEscapeSequence() { - var cacheKey = 'simpleEscapeSequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2; + var pos0, pos1, pos2; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "\\") { - var result3 = "\\"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result3 !== null) { - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - var result10 = parse_digit(); - if (result10 !== null) { - var result6 = result10; - } else { - if (input.substr(pos, 1) === "x") { - var result9 = "x"; - pos += 1; + if (result0 !== null) { + pos2 = pos; + reportFailures++; + result1 = parse_digit(); + if (result1 === null) { + if (input.charCodeAt(pos) === 120) { + result1 = "x"; + pos++; } else { - var result9 = null; - if (reportMatchFailures) { + result1 = null; + if (reportFailures === 0) { matchFailed("\"x\""); } } - if (result9 !== null) { - var result6 = result9; - } else { - if (input.substr(pos, 1) === "u") { - var result8 = "u"; - pos += 1; + if (result1 === null) { + if (input.charCodeAt(pos) === 117) { + result1 = "u"; + pos++; } else { - var result8 = null; - if (reportMatchFailures) { + result1 = null; + if (reportFailures === 0) { matchFailed("\"u\""); } } - if (result8 !== null) { - var result6 = result8; - } else { - var result7 = parse_eolChar(); - if (result7 !== null) { - var result6 = result7; - } else { - var result6 = null;; - }; - }; - }; - } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result6 === null) { - var result4 = ''; - } else { - var result4 = null; - pos = savedPos2; - } - if (result4 !== null) { - if (input.length > pos) { - var result5 = input.charAt(pos); - pos++; - } else { - var result5 = null; - if (reportMatchFailures) { - matchFailed('any character'); + if (result1 === null) { + result1 = parse_eolChar(); } } - if (result5 !== null) { - var result1 = [result3, result4, result5]; + } + reportFailures--; + if (result1 === null) { + result1 = ""; + } else { + result1 = null; + pos = pos2; + } + if (result1 !== null) { + if (input.length > pos) { + result2 = input.charAt(pos); + pos++; } else { - var result1 = null; - pos = savedPos1; + result2 = null; + if (reportFailures === 0) { + matchFailed("any character"); + } + } + if (result2 !== null) { + result0 = [result0, result1, result2]; + } else { + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(char_) { - return char_ - .replace("b", "\b") - .replace("f", "\f") - .replace("n", "\n") - .replace("r", "\r") - .replace("t", "\t") - .replace("v", "\x0B") // IE does not recognize "\v". - })(result1[2]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, char_) { + return char_ + .replace("b", "\b") + .replace("f", "\f") + .replace("n", "\n") + .replace("r", "\r") + .replace("t", "\t") + .replace("v", "\x0B"); // IE does not recognize "\v". + })(pos0, result0[2]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_zeroEscapeSequence() { - var cacheKey = 'zeroEscapeSequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1, pos2; - - var savedPos0 = pos; - var savedPos1 = pos; + pos0 = pos; + pos1 = pos; if (input.substr(pos, 2) === "\\0") { - var result3 = "\\0"; + result0 = "\\0"; pos += 2; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\0\""); } } - if (result3 !== null) { - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - var result5 = parse_digit(); - reportMatchFailures = savedReportMatchFailuresVar0; - if (result5 === null) { - var result4 = ''; + if (result0 !== null) { + pos2 = pos; + reportFailures++; + result1 = parse_digit(); + reportFailures--; + if (result1 === null) { + result1 = ""; } else { - var result4 = null; - pos = savedPos2; + result1 = null; + pos = pos2; } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function() { return "\0"; })() - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return "\x00"; })(pos0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_hexEscapeSequence() { - var cacheKey = 'hexEscapeSequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; + pos0 = pos; + pos1 = pos; if (input.substr(pos, 2) === "\\x") { - var result3 = "\\x"; + result0 = "\\x"; pos += 2; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\x\""); } } - if (result3 !== null) { - var result4 = parse_hexDigit(); - if (result4 !== null) { - var result5 = parse_hexDigit(); - if (result5 !== null) { - var result1 = [result3, result4, result5]; + if (result0 !== null) { + result1 = parse_hexDigit(); + if (result1 !== null) { + result2 = parse_hexDigit(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(h1, h2) { - return String.fromCharCode(parseInt("0x" + h1 + h2)); - })(result1[1], result1[2]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, h1, h2) { + return String.fromCharCode(parseInt(h1 + h2, 16)); + })(pos0, result0[1], result0[2]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_unicodeEscapeSequence() { - var cacheKey = 'unicodeEscapeSequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2, result3, result4; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; + pos0 = pos; + pos1 = pos; if (input.substr(pos, 2) === "\\u") { - var result3 = "\\u"; + result0 = "\\u"; pos += 2; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\u\""); } } - if (result3 !== null) { - var result4 = parse_hexDigit(); - if (result4 !== null) { - var result5 = parse_hexDigit(); - if (result5 !== null) { - var result6 = parse_hexDigit(); - if (result6 !== null) { - var result7 = parse_hexDigit(); - if (result7 !== null) { - var result1 = [result3, result4, result5, result6, result7]; + if (result0 !== null) { + result1 = parse_hexDigit(); + if (result1 !== null) { + result2 = parse_hexDigit(); + if (result2 !== null) { + result3 = parse_hexDigit(); + if (result3 !== null) { + result4 = parse_hexDigit(); + if (result4 !== null) { + result0 = [result0, result1, result2, result3, result4]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(h1, h2, h3, h4) { - return String.fromCharCode(parseInt("0x" + h1 + h2 + h3 + h4)); - })(result1[1], result1[2], result1[3], result1[4]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, h1, h2, h3, h4) { + return String.fromCharCode(parseInt(h1 + h2 + h3 + h4, 16)); + })(pos0, result0[1], result0[2], result0[3], result0[4]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_eolEscapeSequence() { - var cacheKey = 'eolEscapeSequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "\\") { - var result3 = "\\"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result3 !== null) { - var result4 = parse_eol(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse_eol(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result2 = result1 !== null - ? (function(eol) { return eol; })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, eol) { return eol; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_digit() { - var cacheKey = 'digit@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - - if (input.substr(pos).match(/^[0-9]/) !== null) { - var result0 = input.charAt(pos); + if (/^[0-9]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[0-9]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_hexDigit() { - var cacheKey = 'hexDigit@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - - if (input.substr(pos).match(/^[0-9a-fA-F]/) !== null) { - var result0 = input.charAt(pos); + if (/^[0-9a-fA-F]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[0-9a-fA-F]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_letter() { - var cacheKey = 'letter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; + var result0; + + result0 = parse_lowerCaseLetter(); + if (result0 === null) { + result0 = parse_upperCaseLetter(); } - - - var result2 = parse_lowerCaseLetter(); - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_upperCaseLetter(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_lowerCaseLetter() { - var cacheKey = 'lowerCaseLetter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - - if (input.substr(pos).match(/^[a-z]/) !== null) { - var result0 = input.charAt(pos); + if (/^[a-z]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[a-z]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_upperCaseLetter() { - var cacheKey = 'upperCaseLetter@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - - if (input.substr(pos).match(/^[A-Z]/) !== null) { - var result0 = input.charAt(pos); + if (/^[A-Z]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[A-Z]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse___() { - var cacheKey = '__@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; - - var result0 = []; - var result4 = parse_whitespace(); - if (result4 !== null) { - var result1 = result4; - } else { - var result3 = parse_eol(); - if (result3 !== null) { - var result1 = result3; - } else { - var result2 = parse_comment(); - if (result2 !== null) { - var result1 = result2; - } else { - var result1 = null;; - }; - }; + result0 = []; + result1 = parse_whitespace(); + if (result1 === null) { + result1 = parse_eol(); + if (result1 === null) { + result1 = parse_comment(); + } } while (result1 !== null) { result0.push(result1); - var result4 = parse_whitespace(); - if (result4 !== null) { - var result1 = result4; - } else { - var result3 = parse_eol(); - if (result3 !== null) { - var result1 = result3; - } else { - var result2 = parse_comment(); - if (result2 !== null) { - var result1 = result2; - } else { - var result1 = null;; - }; - }; + result1 = parse_whitespace(); + if (result1 === null) { + result1 = parse_eol(); + if (result1 === null) { + result1 = parse_comment(); + } } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_comment() { - var cacheKey = 'comment@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var result2 = parse_singleLineComment(); - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_multiLineComment(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; + reportFailures++; + result0 = parse_singleLineComment(); + if (result0 === null) { + result0 = parse_multiLineComment(); } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("comment"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_singleLineComment() { - var cacheKey = 'singleLineComment@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2, result3; + var pos0, pos1, pos2; - - var savedPos0 = pos; + pos0 = pos; if (input.substr(pos, 2) === "//") { - var result1 = "//"; + result0 = "//"; pos += 2; } else { - var result1 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"//\""); } } - if (result1 !== null) { - var result2 = []; - var savedPos1 = pos; - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - var result6 = parse_eolChar(); - reportMatchFailures = savedReportMatchFailuresVar0; - if (result6 === null) { - var result4 = ''; + if (result0 !== null) { + result1 = []; + pos1 = pos; + pos2 = pos; + reportFailures++; + result2 = parse_eolChar(); + reportFailures--; + if (result2 === null) { + result2 = ""; } else { - var result4 = null; - pos = savedPos2; - } - if (result4 !== null) { - if (input.length > pos) { - var result5 = input.charAt(pos); - pos++; - } else { - var result5 = null; - if (reportMatchFailures) { - matchFailed('any character'); - } - } - if (result5 !== null) { - var result3 = [result4, result5]; - } else { - var result3 = null; - pos = savedPos1; - } - } else { - var result3 = null; - pos = savedPos1; - } - while (result3 !== null) { - result2.push(result3); - var savedPos1 = pos; - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - var result6 = parse_eolChar(); - reportMatchFailures = savedReportMatchFailuresVar0; - if (result6 === null) { - var result4 = ''; - } else { - var result4 = null; - pos = savedPos2; - } - if (result4 !== null) { - if (input.length > pos) { - var result5 = input.charAt(pos); - pos++; - } else { - var result5 = null; - if (reportMatchFailures) { - matchFailed('any character'); - } - } - if (result5 !== null) { - var result3 = [result4, result5]; - } else { - var result3 = null; - pos = savedPos1; - } - } else { - var result3 = null; - pos = savedPos1; - } + result2 = null; + pos = pos2; } if (result2 !== null) { - var result0 = [result1, result2]; + if (input.length > pos) { + result3 = input.charAt(pos); + pos++; + } else { + result3 = null; + if (reportFailures === 0) { + matchFailed("any character"); + } + } + if (result3 !== null) { + result2 = [result2, result3]; + } else { + result2 = null; + pos = pos1; + } } else { - var result0 = null; - pos = savedPos0; + result2 = null; + pos = pos1; + } + while (result2 !== null) { + result1.push(result2); + pos1 = pos; + pos2 = pos; + reportFailures++; + result2 = parse_eolChar(); + reportFailures--; + if (result2 === null) { + result2 = ""; + } else { + result2 = null; + pos = pos2; + } + if (result2 !== null) { + if (input.length > pos) { + result3 = input.charAt(pos); + pos++; + } else { + result3 = null; + if (reportFailures === 0) { + matchFailed("any character"); + } + } + if (result3 !== null) { + result2 = [result2, result3]; + } else { + result2 = null; + pos = pos1; + } + } else { + result2 = null; + pos = pos1; + } + } + if (result1 !== null) { + result0 = [result0, result1]; + } else { + result0 = null; + pos = pos0; } } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_multiLineComment() { - var cacheKey = 'multiLineComment@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2, result3; + var pos0, pos1, pos2; - - var savedPos0 = pos; + pos0 = pos; if (input.substr(pos, 2) === "/*") { - var result1 = "/*"; + result0 = "/*"; pos += 2; } else { - var result1 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"/*\""); } } - if (result1 !== null) { - var result2 = []; - var savedPos1 = pos; - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; + if (result0 !== null) { + result1 = []; + pos1 = pos; + pos2 = pos; + reportFailures++; if (input.substr(pos, 2) === "*/") { - var result7 = "*/"; + result2 = "*/"; pos += 2; } else { - var result7 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"*/\""); } } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result7 === null) { - var result5 = ''; + reportFailures--; + if (result2 === null) { + result2 = ""; } else { - var result5 = null; - pos = savedPos2; - } - if (result5 !== null) { - if (input.length > pos) { - var result6 = input.charAt(pos); - pos++; - } else { - var result6 = null; - if (reportMatchFailures) { - matchFailed('any character'); - } - } - if (result6 !== null) { - var result4 = [result5, result6]; - } else { - var result4 = null; - pos = savedPos1; - } - } else { - var result4 = null; - pos = savedPos1; - } - while (result4 !== null) { - result2.push(result4); - var savedPos1 = pos; - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - if (input.substr(pos, 2) === "*/") { - var result7 = "*/"; - pos += 2; - } else { - var result7 = null; - if (reportMatchFailures) { - matchFailed("\"*/\""); - } - } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result7 === null) { - var result5 = ''; - } else { - var result5 = null; - pos = savedPos2; - } - if (result5 !== null) { - if (input.length > pos) { - var result6 = input.charAt(pos); - pos++; - } else { - var result6 = null; - if (reportMatchFailures) { - matchFailed('any character'); - } - } - if (result6 !== null) { - var result4 = [result5, result6]; - } else { - var result4 = null; - pos = savedPos1; - } - } else { - var result4 = null; - pos = savedPos1; - } + result2 = null; + pos = pos2; } if (result2 !== null) { - if (input.substr(pos, 2) === "*/") { - var result3 = "*/"; - pos += 2; + if (input.length > pos) { + result3 = input.charAt(pos); + pos++; } else { - var result3 = null; - if (reportMatchFailures) { - matchFailed("\"*/\""); + result3 = null; + if (reportFailures === 0) { + matchFailed("any character"); } } if (result3 !== null) { - var result0 = [result1, result2, result3]; + result2 = [result2, result3]; } else { - var result0 = null; - pos = savedPos0; + result2 = null; + pos = pos1; } } else { - var result0 = null; - pos = savedPos0; + result2 = null; + pos = pos1; + } + while (result2 !== null) { + result1.push(result2); + pos1 = pos; + pos2 = pos; + reportFailures++; + if (input.substr(pos, 2) === "*/") { + result2 = "*/"; + pos += 2; + } else { + result2 = null; + if (reportFailures === 0) { + matchFailed("\"*/\""); + } + } + reportFailures--; + if (result2 === null) { + result2 = ""; + } else { + result2 = null; + pos = pos2; + } + if (result2 !== null) { + if (input.length > pos) { + result3 = input.charAt(pos); + pos++; + } else { + result3 = null; + if (reportFailures === 0) { + matchFailed("any character"); + } + } + if (result3 !== null) { + result2 = [result2, result3]; + } else { + result2 = null; + pos = pos1; + } + } else { + result2 = null; + pos = pos1; + } + } + if (result1 !== null) { + if (input.substr(pos, 2) === "*/") { + result2 = "*/"; + pos += 2; + } else { + result2 = null; + if (reportFailures === 0) { + matchFailed("\"*/\""); + } + } + if (result2 !== null) { + result0 = [result0, result1, result2]; + } else { + result0 = null; + pos = pos0; + } + } else { + result0 = null; + pos = pos0; } } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_eol() { - var cacheKey = 'eol@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - if (input.substr(pos, 1) === "\n") { - var result5 = "\n"; - pos += 1; + reportFailures++; + if (input.charCodeAt(pos) === 10) { + result0 = "\n"; + pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\n\""); } } - if (result5 !== null) { - var result0 = result5; - } else { + if (result0 === null) { if (input.substr(pos, 2) === "\r\n") { - var result4 = "\r\n"; + result0 = "\r\n"; pos += 2; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\r\\n\""); } } - if (result4 !== null) { - var result0 = result4; - } else { - if (input.substr(pos, 1) === "\r") { - var result3 = "\r"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 13) { + result0 = "\r"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\r\""); } } - if (result3 !== null) { - var result0 = result3; - } else { - if (input.substr(pos, 1) === "\u2028") { - var result2 = "\u2028"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 8232) { + result0 = "\u2028"; + pos++; } else { - var result2 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\u2028\""); } } - if (result2 !== null) { - var result0 = result2; - } else { - if (input.substr(pos, 1) === "\u2029") { - var result1 = "\u2029"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 8233) { + result0 = "\u2029"; + pos++; } else { - var result1 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\u2029\""); } } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; + } + } + } } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("end of line"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_eolChar() { - var cacheKey = 'eolChar@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - - if (input.substr(pos).match(/^[\n\r\u2028\u2029]/) !== null) { - var result0 = input.charAt(pos); + if (/^[\n\r\u2028\u2029]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[\\n\\r\\u2028\\u2029]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_whitespace() { - var cacheKey = 'whitespace@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - if (input.substr(pos).match(/^[ \xA0\uFEFF\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]/) !== null) { - var result0 = input.charAt(pos); + reportFailures++; + if (/^[ \t\x0B\f\xA0\uFEFF\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { - matchFailed("[ \\xA0\\uFEFF\\u1680\\u180E\\u2000-\\u200A\\u202F\\u205F\\u3000]"); + result0 = null; + if (reportFailures === 0) { + matchFailed("[ \\t\\x0B\\f\\xA0\\uFEFF\\u1680\\u180E\\u2000-\\u200A\\u202F\\u205F\\u3000]"); } } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("whitespace"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } - function buildErrorMessage() { - function buildExpected(failuresExpected) { - failuresExpected.sort(); - var lastFailure = null; - var failuresExpectedUnique = []; - for (var i = 0; i < failuresExpected.length; i++) { - if (failuresExpected[i] !== lastFailure) { - failuresExpectedUnique.push(failuresExpected[i]); - lastFailure = failuresExpected[i]; - } - } + function cleanupExpected(expected) { + expected.sort(); - switch (failuresExpectedUnique.length) { - case 0: - return 'end of input'; - case 1: - return failuresExpectedUnique[0]; - default: - return failuresExpectedUnique.slice(0, failuresExpectedUnique.length - 1).join(', ') - + ' or ' - + failuresExpectedUnique[failuresExpectedUnique.length - 1]; + var lastExpected = null; + var cleanExpected = []; + for (var i = 0; i < expected.length; i++) { + if (expected[i] !== lastExpected) { + cleanExpected.push(expected[i]); + lastExpected = expected[i]; } } - - var expected = buildExpected(rightmostMatchFailuresExpected); - var actualPos = Math.max(pos, rightmostMatchFailuresPos); - var actual = actualPos < input.length - ? quote(input.charAt(actualPos)) - : 'end of input'; - - return 'Expected ' + expected + ' but ' + actual + ' found.'; + return cleanExpected; } function computeErrorPosition() { @@ -4057,13 +3079,13 @@ PEG.parser = (function(){ var column = 1; var seenCR = false; - for (var i = 0; i < rightmostMatchFailuresPos; i++) { + for (var i = 0; i < Math.max(pos, rightmostFailuresPos); i++) { var ch = input.charAt(i); - if (ch === '\n') { + if (ch === "\n") { if (!seenCR) { line++; } column = 1; seenCR = false; - } else if (ch === '\r' | ch === '\u2028' || ch === '\u2029') { + } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { line++; column = 1; seenCR = true; @@ -4077,7 +3099,6 @@ PEG.parser = (function(){ } - var result = parseFunctions[startRule](); /* @@ -4087,27 +3108,32 @@ PEG.parser = (function(){ * * - |result !== null| * - |pos === input.length| - * - |rightmostMatchFailuresExpected| may or may not contain something + * - |rightmostFailuresExpected| may or may not contain something * * 2. The parser successfully parsed only a part of the input. * * - |result !== null| * - |pos < input.length| - * - |rightmostMatchFailuresExpected| may or may not contain something + * - |rightmostFailuresExpected| may or may not contain something * * 3. The parser did not successfully parse any part of the input. * * - |result === null| * - |pos === 0| - * - |rightmostMatchFailuresExpected| contains at least one failure + * - |rightmostFailuresExpected| contains at least one failure * * All code following this comment (including called functions) must * handle these states. */ if (result === null || pos !== input.length) { + var offset = Math.max(pos, rightmostFailuresPos); + var found = offset < input.length ? input.charAt(offset) : null; var errorPosition = computeErrorPosition(); + throw new this.SyntaxError( - buildErrorMessage(), + cleanupExpected(rightmostFailuresExpected), + found, + offset, errorPosition.line, errorPosition.column ); @@ -4122,9 +3148,33 @@ PEG.parser = (function(){ /* Thrown when a parser encounters a syntax error. */ - result.SyntaxError = function(message, line, column) { - this.name = 'SyntaxError'; - this.message = message; + result.SyntaxError = function(expected, found, offset, line, column) { + function buildMessage(expected, found) { + var expectedHumanized, foundHumanized; + + switch (expected.length) { + case 0: + expectedHumanized = "end of input"; + break; + case 1: + expectedHumanized = expected[0]; + break; + default: + expectedHumanized = expected.slice(0, expected.length - 1).join(", ") + + " or " + + expected[expected.length - 1]; + } + + foundHumanized = found ? quote(found) : "end of input"; + + return "Expected " + expectedHumanized + " but " + foundHumanized + " found."; + } + + this.name = "SyntaxError"; + this.expected = expected; + this.found = found; + this.message = buildMessage(expected, found); + this.offset = offset; this.line = line; this.column = column; }; @@ -4134,31 +3184,32 @@ PEG.parser = (function(){ return result; })(); PEG.compiler = { + /* + * Names of passes that will get run during the compilation (in the specified + * order). + */ + appliedPassNames: [ + "reportMissingRules", + "reportLeftRecursion", + "removeProxyRules", + "computeVarNames", + "computeParams" + ], + /* * Generates a parser from a specified grammar AST. Throws |PEG.GrammarError| * if the AST contains a semantic error. Note that not all errors are detected * during the generation and some may protrude to the generated parser and * cause its malfunction. */ - compile: function(ast) { - var CHECK_NAMES = [ - "missingReferencedRules", - "leftRecursion" - ]; + compile: function(ast, options) { + var that = this; - var PASS_NAMES = [ - "proxyRules" - ]; + each(this.appliedPassNames, function(passName) { + that.passes[passName](ast); + }); - for (var i = 0; i < CHECK_NAMES.length; i++) { - this.checks[CHECK_NAMES[i]](ast); - } - - for (var i = 0; i < PASS_NAMES.length; i++) { - ast = this.passes[PASS_NAMES[i]](ast); - } - - var source = this.emitter(ast); + var source = this.emitter(ast, options); var result = eval(source); result._source = source; @@ -4167,16 +3218,15 @@ PEG.compiler = { }; /* - * Checks made on the grammar AST before compilation. Each check is a function - * that is passed the AST and does not return anything. If the check passes, the - * function does not do anything special, otherwise it throws - * |PEG.GrammarError|. The order in which the checks are run is specified in - * |PEG.compiler.compile| and should be the same as the order of definitions - * here. + * Compiler passes. + * + * Each pass is a function that is passed the AST. It can perform checks on it + * or modify it as needed. If the pass encounters a semantic error, it throws + * |PEG.GrammarError|. */ -PEG.compiler.checks = { +PEG.compiler.passes = { /* Checks that all referenced rules exist. */ - missingReferencedRules: function(ast) { + reportMissingRules: function(ast) { function nop() {} function checkExpression(node) { check(node.expression); } @@ -4186,13 +3236,7 @@ PEG.compiler.checks = { } var check = buildNodeVisitor({ - grammar: - function(node) { - for (var name in node.rules) { - check(node.rules[name]); - } - }, - + grammar: checkSubnodes("rules"), rule: checkExpression, choice: checkSubnodes("alternatives"), sequence: checkSubnodes("elements"), @@ -4208,7 +3252,7 @@ PEG.compiler.checks = { rule_ref: function(node) { - if (ast.rules[node.name] === undefined) { + if (!findRuleByName(ast, node.name)) { throw new PEG.GrammarError( "Referenced rule \"" + node.name + "\" does not exist." ); @@ -4224,32 +3268,30 @@ PEG.compiler.checks = { }, /* Checks that no left recursion is present. */ - leftRecursion: function(ast) { + reportLeftRecursion: function(ast) { function nop() {} function checkExpression(node, appliedRules) { check(node.expression, appliedRules); } + function checkSubnodes(propertyName) { + return function(node, appliedRules) { + each(node[propertyName], function(subnode) { + check(subnode, appliedRules); + }); + }; + } + var check = buildNodeVisitor({ - grammar: - function(node, appliedRules) { - for (var name in node.rules) { - check(node.rules[name], appliedRules); - } - }, + grammar: checkSubnodes("rules"), rule: function(node, appliedRules) { check(node.expression, appliedRules.concat(node.name)); }, - choice: - function(node, appliedRules) { - each(node.alternatives, function(alternative) { - check(alternative, appliedRules); - }); - }, + choice: checkSubnodes("alternatives"), sequence: function(node, appliedRules) { @@ -4275,7 +3317,7 @@ PEG.compiler.checks = { "Left recursion detected for rule \"" + node.name + "\"." ); } - check(ast.rules[node.name], appliedRules); + check(findRuleByName(ast, node.name), appliedRules); }, literal: nop, @@ -4284,20 +3326,12 @@ PEG.compiler.checks = { }); check(ast, []); - } -}; -/* - * Optimalization passes made on the grammar AST before compilation. Each pass - * is a function that is passed the AST and returns a new AST. The AST can be - * modified in-place by the pass. The order in which the passes are run is - * specified in |PEG.compiler.compile| and should be the same as the order of - * definitions here. - */ -PEG.compiler.passes = { + }, + /* * Removes proxy rules -- that is, rules that only delegate to other rule. */ - proxyRules: function(ast) { + removeProxyRules: function(ast) { function isProxyRule(node) { return node.type === "rule" && node.expression.type === "rule_ref"; } @@ -4318,13 +3352,7 @@ PEG.compiler.passes = { } var replace = buildNodeVisitor({ - grammar: - function(node, from, to) { - for (var name in node.rules) { - replace(node.rules[name], from, to); - } - }, - + grammar: replaceInSubnodes("rules"), rule: replaceInExpression, choice: replaceInSubnodes("alternatives"), sequence: replaceInSubnodes("elements"), @@ -4353,789 +3381,1148 @@ PEG.compiler.passes = { replace(ast, from, to); } - for (var name in ast.rules) { - if (isProxyRule(ast.rules[name])) { - replaceRuleRefs(ast, ast.rules[name].name, ast.rules[name].expression.name); - if (name === ast.startRule) { - ast.startRule = ast.rules[name].expression.name; + var indices = []; + + each(ast.rules, function(rule, i) { + if (isProxyRule(rule)) { + replaceRuleRefs(ast, rule.name, rule.expression.name); + if (rule.name === ast.startRule) { + ast.startRule = rule.expression.name; } - delete ast.rules[name]; + indices.push(i); } + }); + + indices.reverse(); + + each(indices, function(index) { + ast.rules.splice(index, 1); + }); + }, + + /* + * Computes names of variables used for storing match results and parse + * positions in generated code. These variables are organized as two stacks. + * The following will hold after running this pass: + * + * * All nodes except "grammar" and "rule" nodes will have a |resultVar| + * property. It will contain a name of the variable that will store a + * match result of the expression represented by the node in generated + * code. + * + * * Some nodes will have a |posVar| property. It will contain a name of the + * variable that will store a parse position in generated code. + * + * * All "rule" nodes will contain |resultVars| and |posVars| properties. + * They will contain a list of values of |resultVar| and |posVar| + * properties used in rule's subnodes. (This is useful to declare + * variables in generated code.) + */ + computeVarNames: function(ast) { + function resultVar(index) { return "result" + index; } + function posVar(index) { return "pos" + index; } + + function computeLeaf(node, index) { + node.resultVar = resultVar(index.result); + + return { result: 0, pos: 0 }; } - return ast; + function computeFromExpression(delta) { + return function(node, index) { + var depth = compute( + node.expression, + { + result: index.result + delta.result, + pos: index.pos + delta.pos + } + ); + + node.resultVar = resultVar(index.result); + if (delta.pos !== 0) { + node.posVar = posVar(index.pos); + } + + return { + result: depth.result + delta.result, + pos: depth.pos + delta.pos + }; + }; + } + + var compute = buildNodeVisitor({ + grammar: + function(node, index) { + each(node.rules, function(node) { + compute(node, index); + }); + }, + + rule: + function(node, index) { + var depth = compute(node.expression, index); + + node.resultVar = resultVar(index.result); + node.resultVars = map(range(depth.result + 1), resultVar); + node.posVars = map(range(depth.pos), posVar); + }, + + choice: + function(node, index) { + var depths = map(node.alternatives, function(alternative) { + return compute(alternative, index); + }); + + node.resultVar = resultVar(index.result); + + return { + result: Math.max.apply(null, pluck(depths, "result")), + pos: Math.max.apply(null, pluck(depths, "pos")) + }; + }, + + sequence: + function(node, index) { + var depths = map(node.elements, function(element, i) { + return compute( + element, + { result: index.result + i, pos: index.pos + 1 } + ); + }); + + node.resultVar = resultVar(index.result); + node.posVar = posVar(index.pos); + + return { + result: + node.elements.length > 0 + ? Math.max.apply( + null, + map(depths, function(d, i) { return i + d.result; }) + ) + : 0, + + pos: + node.elements.length > 0 + ? 1 + Math.max.apply(null, pluck(depths, "pos")) + : 1 + }; + }, + + labeled: computeFromExpression({ result: 0, pos: 0 }), + simple_and: computeFromExpression({ result: 0, pos: 1 }), + simple_not: computeFromExpression({ result: 0, pos: 1 }), + semantic_and: computeLeaf, + semantic_not: computeLeaf, + optional: computeFromExpression({ result: 0, pos: 0 }), + zero_or_more: computeFromExpression({ result: 1, pos: 0 }), + one_or_more: computeFromExpression({ result: 1, pos: 0 }), + action: computeFromExpression({ result: 0, pos: 1 }), + rule_ref: computeLeaf, + literal: computeLeaf, + any: computeLeaf, + "class": computeLeaf + }); + + compute(ast, { result: 0, pos: 0 }); + }, + + /* + * This pass walks through the AST and tracks what labels are visible at each + * point. For "action", "semantic_and" and "semantic_or" nodes it computes + * parameter names and values for the function used in generated code. (In the + * emitter, user's code is wrapped into a function that is immediately + * executed. Its parameter names correspond to visible labels and its + * parameter values to their captured values). Implicitly, this pass defines + * scoping rules for labels. + * + * After running this pass, all "action", "semantic_and" and "semantic_or" + * nodes will have a |params| property containing an object mapping parameter + * names to the expressions that will be used as their values. + */ + computeParams: function(ast) { + var envs = []; + + function scoped(f) { + envs.push({}); + f(); + envs.pop(); + } + + function nop() {} + + function computeForScopedExpression(node) { + scoped(function() { compute(node.expression); }); + } + + function computeParams(node) { + var env = envs[envs.length - 1], params = {}, name; + + for (name in env) { + params[name] = env[name]; + } + node.params = params; + } + + var compute = buildNodeVisitor({ + grammar: + function(node) { + each(node.rules, compute); + }, + + rule: computeForScopedExpression, + + choice: + function(node) { + scoped(function() { each(node.alternatives, compute); }); + }, + + sequence: + function(node) { + var env = envs[envs.length - 1], name; + + function fixup(name) { + each(pluck(node.elements, "resultVar"), function(resultVar, i) { + if ((new RegExp("^" + resultVar + "(\\[\\d+\\])*$")).test(env[name])) { + env[name] = node.resultVar + "[" + i + "]" + + env[name].substr(resultVar.length); + } + }); + } + + each(node.elements, compute); + + for (name in env) { + fixup(name); + } + }, + + labeled: + function(node) { + envs[envs.length - 1][node.label] = node.resultVar; + + scoped(function() { compute(node.expression); }); + }, + + simple_and: computeForScopedExpression, + simple_not: computeForScopedExpression, + semantic_and: computeParams, + semantic_not: computeParams, + optional: computeForScopedExpression, + zero_or_more: computeForScopedExpression, + one_or_more: computeForScopedExpression, + + action: + function(node) { + scoped(function() { + compute(node.expression); + computeParams(node); + }); + }, + + rule_ref: nop, + literal: nop, + any: nop, + "class": nop + }); + + compute(ast); } }; /* Emits the generated code for the AST. */ -PEG.compiler.emitter = function(ast) { +PEG.compiler.emitter = function(ast, options) { + options = options || {}; + if (options.cache === undefined) { + options.cache = false; + } + if (options.trackLineAndColumn === undefined) { + options.trackLineAndColumn = false; + } + /* - * Takes parts of code, interpolates variables inside them and joins them with - * a newline. + * Codie 1.1.0 * - * Variables are delimited with "${" and "}" and their names must be valid - * identifiers (i.e. they must match [a-zA-Z_][a-zA-Z0-9_]*). Variable values - * are specified as properties of the last parameter (if this is an object, - * otherwise empty variable set is assumed). Undefined variables result in - * throwing |Error|. + * https://github.com/dmajda/codie * - * There can be a filter specified after the variable name, prefixed with "|". - * The filter name must be a valid identifier. The only recognized filter - * right now is "string", which quotes the variable value as a JavaScript - * string. Unrecognized filters result in throwing |Error|. - * - * If any part has multiple lines and the first line is indented by some - * amount of whitespace (as defined by the /\s+/ JavaScript regular - * expression), second to last lines are indented by the same amount of - * whitespace. This results in nicely indented multiline code in variables - * without making the templates look ugly. - * - * Examples: - * - * formatCode("foo", "bar"); // "foo\nbar" - * formatCode("foo", "${bar}", { bar: "baz" }); // "foo\nbaz" - * formatCode("foo", "${bar}"); // throws Error - * formatCode("foo", "${bar|string}", { bar: "baz" }); // "foo\n\"baz\"" - * formatCode("foo", "${bar|eeek}", { bar: "baz" }); // throws Error - * formatCode("foo", "${bar}", { bar: " baz\nqux" }); // "foo\n baz\n qux" + * Copyright (c) 2011-2012 David Majda + * Licensend under the MIT license. */ - function formatCode() { - function interpolateVariablesInParts(parts) { - return map(parts, function(part) { - return part.replace( - /\$\{([a-zA-Z_][a-zA-Z0-9_]*)(\|([a-zA-Z_][a-zA-Z0-9_]*))?\}/g, - function(match, name, dummy, filter) { - var value = vars[name]; - if (value === undefined) { - throw new Error("Undefined variable: \"" + name + "\"."); - } + var Codie = (function(undefined) { - if (filter !== undefined && filter != "") { // JavaScript engines differ here. - if (filter === "string") { - return quote(value); - } else { - throw new Error("Unrecognized filter: \"" + filter + "\"."); - } - } else { - return value; - } + function stringEscape(s) { + function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } + + /* + * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a + * string literal except for the closing quote character, backslash, + * carriage return, line separator, paragraph separator, and line feed. + * Any character may appear in the form of an escape sequence. + * + * For portability, we also escape escape all control and non-ASCII + * characters. Note that "\0" and "\v" escape sequences are not used + * because JSHint does not like the first and IE the second. + */ + return s + .replace(/\\/g, '\\\\') // backslash + .replace(/"/g, '\\"') // closing double quote + .replace(/\x08/g, '\\b') // backspace + .replace(/\t/g, '\\t') // horizontal tab + .replace(/\n/g, '\\n') // line feed + .replace(/\f/g, '\\f') // form feed + .replace(/\r/g, '\\r') // carriage return + .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); }); + } + + function push(s) { return '__p.push(' + s + ');'; } + + function pushRaw(template, length, state) { + function unindent(code, level, unindentFirst) { + return code.replace( + new RegExp('^.{' + level +'}', "gm"), + function(str, offset) { + if (offset === 0) { + return unindentFirst ? '' : str; + } else { + return ""; } - ); - }); - } - - function indentMultilineParts(parts) { - return map(parts, function(part) { - if (!/\n/.test(part)) { return part; } - - var firstLineWhitespacePrefix = part.match(/^\s*/)[0]; - var lines = part.split("\n"); - var linesIndented = [lines[0]].concat( - map(lines.slice(1), function(line) { - return firstLineWhitespacePrefix + line; - }) - ); - return linesIndented.join("\n"); - }); - } - - var args = Array.prototype.slice.call(arguments); - var vars = args[args.length - 1] instanceof Object ? args.pop() : {}; - - return indentMultilineParts(interpolateVariablesInParts(args)).join("\n"); - }; - - /* Unique ID generator. */ - var UID = { - _counters: {}, - - next: function(prefix) { - this._counters[prefix] = this._counters[prefix] || 0; - return prefix + this._counters[prefix]++; - }, - - reset: function() { - this._counters = {}; - } - }; - - var emit = buildNodeVisitor({ - grammar: function(node) { - var initializerCode = node.initializer !== null - ? emit(node.initializer) - : ""; - - var parseFunctionTableItems = []; - for (var name in node.rules) { - parseFunctionTableItems.push(quote(name) + ": parse_" + name); - } - parseFunctionTableItems.sort(); - - var parseFunctionDefinitions = []; - for (var name in node.rules) { - parseFunctionDefinitions.push(emit(node.rules[name])); - } - - return formatCode( - "(function(){", - " /* Generated by PEG.js 0.6.2 (http://pegjs.majda.cz/). */", - " ", - " var result = {", - " /*", - " * Parses the input with a generated parser. If the parsing is successfull,", - " * returns a value explicitly or implicitly specified by the grammar from", - " * which the parser was generated (see |PEG.buildParser|). If the parsing is", - " * unsuccessful, throws |PEG.parser.SyntaxError| describing the error.", - " */", - " parse: function(input, startRule) {", - " var parseFunctions = {", - " ${parseFunctionTableItems}", - " };", - " ", - " if (startRule !== undefined) {", - " if (parseFunctions[startRule] === undefined) {", - " throw new Error(\"Invalid rule name: \" + quote(startRule) + \".\");", - " }", - " } else {", - " startRule = ${startRule|string};", - " }", - " ", - " var pos = 0;", - " var reportMatchFailures = true;", - " var rightmostMatchFailuresPos = 0;", - " var rightmostMatchFailuresExpected = [];", - " var cache = {};", - " ", - /* This needs to be in sync with |padLeft| in utils.js. */ - " function padLeft(input, padding, length) {", - " var result = input;", - " ", - " var padLength = length - input.length;", - " for (var i = 0; i < padLength; i++) {", - " result = padding + result;", - " }", - " ", - " return result;", - " }", - " ", - /* This needs to be in sync with |escape| in utils.js. */ - " function escape(ch) {", - " var charCode = ch.charCodeAt(0);", - " ", - " if (charCode <= 0xFF) {", - " var escapeChar = 'x';", - " var length = 2;", - " } else {", - " var escapeChar = 'u';", - " var length = 4;", - " }", - " ", - " return '\\\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length);", - " }", - " ", - /* This needs to be in sync with |quote| in utils.js. */ - " function quote(s) {", - " /*", - " * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a", - " * string literal except for the closing quote character, backslash,", - " * carriage return, line separator, paragraph separator, and line feed.", - " * Any character may appear in the form of an escape sequence.", - " */", - " return '\"' + s", - " .replace(/\\\\/g, '\\\\\\\\') // backslash", - " .replace(/\"/g, '\\\\\"') // closing quote character", - " .replace(/\\r/g, '\\\\r') // carriage return", - " .replace(/\\n/g, '\\\\n') // line feed", - " .replace(/[\\x80-\\uFFFF]/g, escape) // non-ASCII characters", - " + '\"';", - " }", - " ", - " function matchFailed(failure) {", - " if (pos < rightmostMatchFailuresPos) {", - " return;", - " }", - " ", - " if (pos > rightmostMatchFailuresPos) {", - " rightmostMatchFailuresPos = pos;", - " rightmostMatchFailuresExpected = [];", - " }", - " ", - " rightmostMatchFailuresExpected.push(failure);", - " }", - " ", - " ${parseFunctionDefinitions}", - " ", - " function buildErrorMessage() {", - " function buildExpected(failuresExpected) {", - " failuresExpected.sort();", - " ", - " var lastFailure = null;", - " var failuresExpectedUnique = [];", - " for (var i = 0; i < failuresExpected.length; i++) {", - " if (failuresExpected[i] !== lastFailure) {", - " failuresExpectedUnique.push(failuresExpected[i]);", - " lastFailure = failuresExpected[i];", - " }", - " }", - " ", - " switch (failuresExpectedUnique.length) {", - " case 0:", - " return 'end of input';", - " case 1:", - " return failuresExpectedUnique[0];", - " default:", - " return failuresExpectedUnique.slice(0, failuresExpectedUnique.length - 1).join(', ')", - " + ' or '", - " + failuresExpectedUnique[failuresExpectedUnique.length - 1];", - " }", - " }", - " ", - " var expected = buildExpected(rightmostMatchFailuresExpected);", - " var actualPos = Math.max(pos, rightmostMatchFailuresPos);", - " var actual = actualPos < input.length", - " ? quote(input.charAt(actualPos))", - " : 'end of input';", - " ", - " return 'Expected ' + expected + ' but ' + actual + ' found.';", - " }", - " ", - " function computeErrorPosition() {", - " /*", - " * The first idea was to use |String.split| to break the input up to the", - " * error position along newlines and derive the line and column from", - " * there. However IE's |split| implementation is so broken that it was", - " * enough to prevent it.", - " */", - " ", - " var line = 1;", - " var column = 1;", - " var seenCR = false;", - " ", - " for (var i = 0; i < rightmostMatchFailuresPos; i++) {", - " var ch = input.charAt(i);", - " if (ch === '\\n') {", - " if (!seenCR) { line++; }", - " column = 1;", - " seenCR = false;", - " } else if (ch === '\\r' | ch === '\\u2028' || ch === '\\u2029') {", - " line++;", - " column = 1;", - " seenCR = true;", - " } else {", - " column++;", - " seenCR = false;", - " }", - " }", - " ", - " return { line: line, column: column };", - " }", - " ", - " ${initializerCode}", - " ", - " var result = parseFunctions[startRule]();", - " ", - " /*", - " * The parser is now in one of the following three states:", - " *", - " * 1. The parser successfully parsed the whole input.", - " *", - " * - |result !== null|", - " * - |pos === input.length|", - " * - |rightmostMatchFailuresExpected| may or may not contain something", - " *", - " * 2. The parser successfully parsed only a part of the input.", - " *", - " * - |result !== null|", - " * - |pos < input.length|", - " * - |rightmostMatchFailuresExpected| may or may not contain something", - " *", - " * 3. The parser did not successfully parse any part of the input.", - " *", - " * - |result === null|", - " * - |pos === 0|", - " * - |rightmostMatchFailuresExpected| contains at least one failure", - " *", - " * All code following this comment (including called functions) must", - " * handle these states.", - " */", - " if (result === null || pos !== input.length) {", - " var errorPosition = computeErrorPosition();", - " throw new this.SyntaxError(", - " buildErrorMessage(),", - " errorPosition.line,", - " errorPosition.column", - " );", - " }", - " ", - " return result;", - " },", - " ", - " /* Returns the parser source code. */", - " toSource: function() { return this._source; }", - " };", - " ", - " /* Thrown when a parser encounters a syntax error. */", - " ", - " result.SyntaxError = function(message, line, column) {", - " this.name = 'SyntaxError';", - " this.message = message;", - " this.line = line;", - " this.column = column;", - " };", - " ", - " result.SyntaxError.prototype = Error.prototype;", - " ", - " return result;", - "})()", - { - initializerCode: initializerCode, - parseFunctionTableItems: parseFunctionTableItems.join(",\n"), - parseFunctionDefinitions: parseFunctionDefinitions.join("\n\n"), - startRule: node.startRule } ); - }, + } - initializer: function(node) { - return node.code; - }, + var escaped = stringEscape(unindent( + template.substring(0, length), + state.indentLevel(), + state.atBOL + )); - rule: function(node) { - /* - * We want to reset variable names at the beginning of every function so - * that a little change in the source grammar does not change variables in - * all the generated code. This is desired especially when one has the - * generated grammar stored in a VCS (this is true e.g. for our - * metagrammar). - */ - UID.reset(); + return escaped.length > 0 ? push('"' + escaped + '"') : ''; + } - var resultVar = UID.next("result"); - if (node.displayName !== null) { - var setReportMatchFailuresCode = formatCode( - "var savedReportMatchFailures = reportMatchFailures;", - "reportMatchFailures = false;" - ); - var restoreReportMatchFailuresCode = formatCode( - "reportMatchFailures = savedReportMatchFailures;" - ); - var reportMatchFailureCode = formatCode( - "if (reportMatchFailures && ${resultVar} === null) {", - " matchFailed(${displayName|string});", - "}", - { - displayName: node.displayName, - resultVar: resultVar + var Codie = { + /* Codie version (uses semantic versioning). */ + VERSION: "1.1.0", + + /* + * Specifies by how many characters do #if/#else and #for unindent their + * content in the generated code. + */ + indentStep: 2, + + /* Description of #-commands. Extend to define your own commands. */ + commands: { + "if": { + params: /^(.*)$/, + compile: function(state, prefix, params) { + return ['if(' + params[0] + '){', []]; + }, + stackOp: "push" + }, + "else": { + params: /^$/, + compile: function(state) { + var stack = state.commandStack, + insideElse = stack[stack.length - 1] === "else", + insideIf = stack[stack.length - 1] === "if"; + + if (insideElse) { throw new Error("Multiple #elses."); } + if (!insideIf) { throw new Error("Using #else outside of #if."); } + + return ['}else{', []]; + }, + stackOp: "replace" + }, + "for": { + params: /^([a-zA-Z_][a-zA-Z0-9_]*)[ \t]+in[ \t]+(.*)$/, + init: function(state) { + state.forCurrLevel = 0; // current level of #for loop nesting + state.forMaxLevel = 0; // maximum level of #for loop nesting + }, + compile: function(state, prefix, params) { + var c = '__c' + state.forCurrLevel, // __c for "collection" + l = '__l' + state.forCurrLevel, // __l for "length" + i = '__i' + state.forCurrLevel; // __i for "index" + + state.forCurrLevel++; + if (state.forMaxLevel < state.forCurrLevel) { + state.forMaxLevel = state.forCurrLevel; } - ); - } else { - var setReportMatchFailuresCode = ""; - var restoreReportMatchFailuresCode = ""; - var reportMatchFailureCode = ""; - } - return formatCode( - "function parse_${name}() {", - " var cacheKey = '${name}@' + pos;", - " var cachedResult = cache[cacheKey];", - " if (cachedResult) {", - " pos = cachedResult.nextPos;", - " return cachedResult.result;", - " }", - " ", - " ${setReportMatchFailuresCode}", - " ${code}", - " ${restoreReportMatchFailuresCode}", - " ${reportMatchFailureCode}", - " ", - " cache[cacheKey] = {", - " nextPos: pos,", - " result: ${resultVar}", - " };", - " return ${resultVar};", - "}", - { - name: node.name, - setReportMatchFailuresCode: setReportMatchFailuresCode, - restoreReportMatchFailuresCode: restoreReportMatchFailuresCode, - reportMatchFailureCode: reportMatchFailureCode, - code: emit(node.expression, resultVar), - resultVar: resultVar - } - ); + return [ + c + '=' + params[1] + ';' + + l + '=' + c + '.length;' + + 'for(' + i + '=0;' + i + '<' + l + ';' + i + '++){' + + params[0] + '=' + c + '[' + i + '];', + [params[0], c, l, i] + ]; + }, + exit: function(state) { state.forCurrLevel--; }, + stackOp: "push" + }, + "end": { + params: /^$/, + compile: function(state) { + var stack = state.commandStack, exit; + + if (stack.length === 0) { throw new Error("Too many #ends."); } + + exit = Codie.commands[stack[stack.length - 1]].exit; + if (exit) { exit(state); } + + return ['}', []]; + }, + stackOp: "pop" + }, + "block": { + params: /^(.*)$/, + compile: function(state, prefix, params) { + var x = '__x', // __x for "prefix", + n = '__n', // __n for "lines" + l = '__l', // __l for "length" + i = '__i'; // __i for "index" + + /* + * Originally, the generated code used |String.prototype.replace|, but + * it is buggy in certain versions of V8 so it was rewritten. See the + * tests for details. + */ + return [ + x + '="' + stringEscape(prefix.substring(state.indentLevel())) + '";' + + n + '=(' + params[0] + ').toString().split("\\n");' + + l + '=' + n + '.length;' + + 'for(' + i + '=0;' + i + '<' + l + ';' + i + '++){' + + n + '[' + i +']=' + x + '+' + n + '[' + i + ']+"\\n";' + + '}' + + push(n + '.join("")'), + [x, n, l, i] + ]; + }, + stackOp: "nop" + } }, + /* + * Compiles a template into a function. When called, this function will + * execute the template in the context of an object passed in a parameter and + * return the result. + */ + template: function(template) { + var stackOps = { + push: function(stack, name) { stack.push(name); }, + replace: function(stack, name) { stack[stack.length - 1] = name; }, + pop: function(stack) { stack.pop(); }, + nop: function() { } + }; + + function compileExpr(state, expr) { + state.atBOL = false; + return [push(expr), []]; + } + + function compileCommand(state, prefix, name, params) { + var command, match, result; + + command = Codie.commands[name]; + if (!command) { throw new Error("Unknown command: #" + name + "."); } + + match = command.params.exec(params); + if (match === null) { + throw new Error( + "Invalid params for command #" + name + ": " + params + "." + ); + } + + result = command.compile(state, prefix, match.slice(1)); + stackOps[command.stackOp](state.commandStack, name); + state.atBOL = true; + return result; + } + + var state = { // compilation state + commandStack: [], // stack of commands as they were nested + atBOL: true, // is the next character to process at BOL? + indentLevel: function() { + return Codie.indentStep * this.commandStack.length; + } + }, + code = '', // generated template function code + vars = ['__p=[]'], // variables used by generated code + name, match, result, i; + + /* Initialize state. */ + for (name in Codie.commands) { + if (Codie.commands[name].init) { Codie.commands[name].init(state); } + } + + /* Compile the template. */ + while ((match = /^([ \t]*)#([a-zA-Z_][a-zA-Z0-9_]*)(?:[ \t]+([^ \t\n][^\n]*))?[ \t]*(?:\n|$)|#\{([^}]*)\}/m.exec(template)) !== null) { + code += pushRaw(template, match.index, state); + result = match[2] !== undefined && match[2] !== "" + ? compileCommand(state, match[1], match[2], match[3] || "") // #-command + : compileExpr(state, match[4]); // #{...} + code += result[0]; + vars = vars.concat(result[1]); + template = template.substring(match.index + match[0].length); + } + code += pushRaw(template, template.length, state); + + /* Check the final state. */ + if (state.commandStack.length > 0) { throw new Error("Missing #end."); } + + /* Sanitize the list of variables used by commands. */ + vars.sort(); + for (i = 0; i < vars.length; i++) { + if (vars[i] === vars[i - 1]) { vars.splice(i--, 1); } + } + + /* Create the resulting function. */ + return new Function("__v", [ + '__v=__v||{};', + 'var ' + vars.join(',') + ';', + 'with(__v){', + code, + 'return __p.join("").replace(/^\\n+|\\n+$/g,"");};' + ].join('')); + } + }; + + return Codie; + + })(); + + var templates = (function() { + var name, + templates = {}, + sources = { + grammar: [ + '(function(){', + ' /*', + ' * Generated by PEG.js 0.7.0.', + ' *', + ' * http://pegjs.majda.cz/', + ' */', + ' ', + /* This needs to be in sync with |quote| in utils.js. */ + ' function quote(s) {', + ' /*', + ' * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a', + ' * string literal except for the closing quote character, backslash,', + ' * carriage return, line separator, paragraph separator, and line feed.', + ' * Any character may appear in the form of an escape sequence.', + ' *', + ' * For portability, we also escape escape all control and non-ASCII', + ' * characters. Note that "\\0" and "\\v" escape sequences are not used', + ' * because JSHint does not like the first and IE the second.', + ' */', + ' return \'"\' + s', + ' .replace(/\\\\/g, \'\\\\\\\\\') // backslash', + ' .replace(/"/g, \'\\\\"\') // closing quote character', + ' .replace(/\\x08/g, \'\\\\b\') // backspace', + ' .replace(/\\t/g, \'\\\\t\') // horizontal tab', + ' .replace(/\\n/g, \'\\\\n\') // line feed', + ' .replace(/\\f/g, \'\\\\f\') // form feed', + ' .replace(/\\r/g, \'\\\\r\') // carriage return', + ' .replace(/[\\x00-\\x07\\x0B\\x0E-\\x1F\\x80-\\uFFFF]/g, escape)', + ' + \'"\';', + ' }', + ' ', + ' var result = {', + ' /*', + ' * Parses the input with a generated parser. If the parsing is successfull,', + ' * returns a value explicitly or implicitly specified by the grammar from', + ' * which the parser was generated (see |PEG.buildParser|). If the parsing is', + ' * unsuccessful, throws |PEG.parser.SyntaxError| describing the error.', + ' */', + ' parse: function(input, startRule) {', + ' var parseFunctions = {', + ' #for rule in node.rules', + ' #{string(rule.name) + ": parse_" + rule.name + (rule !== node.rules[node.rules.length - 1] ? "," : "")}', + ' #end', + ' };', + ' ', + ' if (startRule !== undefined) {', + ' if (parseFunctions[startRule] === undefined) {', + ' throw new Error("Invalid rule name: " + quote(startRule) + ".");', + ' }', + ' } else {', + ' startRule = #{string(node.startRule)};', + ' }', + ' ', + ' #{posInit("pos")};', + ' var reportFailures = 0;', // 0 = report, anything > 0 = do not report + ' #{posInit("rightmostFailuresPos")};', + ' var rightmostFailuresExpected = [];', + ' #if options.cache', + ' var cache = {};', + ' #end', + ' ', + /* This needs to be in sync with |padLeft| in utils.js. */ + ' function padLeft(input, padding, length) {', + ' var result = input;', + ' ', + ' var padLength = length - input.length;', + ' for (var i = 0; i < padLength; i++) {', + ' result = padding + result;', + ' }', + ' ', + ' return result;', + ' }', + ' ', + /* This needs to be in sync with |escape| in utils.js. */ + ' function escape(ch) {', + ' var charCode = ch.charCodeAt(0);', + ' var escapeChar;', + ' var length;', + ' ', + ' if (charCode <= 0xFF) {', + ' escapeChar = \'x\';', + ' length = 2;', + ' } else {', + ' escapeChar = \'u\';', + ' length = 4;', + ' }', + ' ', + ' return \'\\\\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), \'0\', length);', + ' }', + ' ', + ' #if options.trackLineAndColumn', + ' function clone(object) {', + ' var result = {};', + ' for (var key in object) {', + ' result[key] = object[key];', + ' }', + ' return result;', + ' }', + ' ', + ' function advance(pos, n) {', + ' var endOffset = pos.offset + n;', + ' ', + ' for (var offset = pos.offset; offset < endOffset; offset++) {', + ' var ch = input.charAt(offset);', + ' if (ch === "\\n") {', + ' if (!pos.seenCR) { pos.line++; }', + ' pos.column = 1;', + ' pos.seenCR = false;', + ' } else if (ch === "\\r" || ch === "\\u2028" || ch === "\\u2029") {', + ' pos.line++;', + ' pos.column = 1;', + ' pos.seenCR = true;', + ' } else {', + ' pos.column++;', + ' pos.seenCR = false;', + ' }', + ' }', + ' ', + ' pos.offset += n;', + ' }', + ' ', + ' #end', + ' function matchFailed(failure) {', + ' if (#{posOffset("pos")} < #{posOffset("rightmostFailuresPos")}) {', + ' return;', + ' }', + ' ', + ' if (#{posOffset("pos")} > #{posOffset("rightmostFailuresPos")}) {', + ' rightmostFailuresPos = #{posClone("pos")};', + ' rightmostFailuresExpected = [];', + ' }', + ' ', + ' rightmostFailuresExpected.push(failure);', + ' }', + ' ', + ' #for rule in node.rules', + ' #block emit(rule)', + ' ', + ' #end', + ' ', + ' function cleanupExpected(expected) {', + ' expected.sort();', + ' ', + ' var lastExpected = null;', + ' var cleanExpected = [];', + ' for (var i = 0; i < expected.length; i++) {', + ' if (expected[i] !== lastExpected) {', + ' cleanExpected.push(expected[i]);', + ' lastExpected = expected[i];', + ' }', + ' }', + ' return cleanExpected;', + ' }', + ' ', + ' #if !options.trackLineAndColumn', + ' function computeErrorPosition() {', + ' /*', + ' * The first idea was to use |String.split| to break the input up to the', + ' * error position along newlines and derive the line and column from', + ' * there. However IE\'s |split| implementation is so broken that it was', + ' * enough to prevent it.', + ' */', + ' ', + ' var line = 1;', + ' var column = 1;', + ' var seenCR = false;', + ' ', + ' for (var i = 0; i < Math.max(pos, rightmostFailuresPos); i++) {', + ' var ch = input.charAt(i);', + ' if (ch === "\\n") {', + ' if (!seenCR) { line++; }', + ' column = 1;', + ' seenCR = false;', + ' } else if (ch === "\\r" || ch === "\\u2028" || ch === "\\u2029") {', + ' line++;', + ' column = 1;', + ' seenCR = true;', + ' } else {', + ' column++;', + ' seenCR = false;', + ' }', + ' }', + ' ', + ' return { line: line, column: column };', + ' }', + ' #end', + ' ', + ' #if node.initializer', + ' #block emit(node.initializer)', + ' #end', + ' ', + ' var result = parseFunctions[startRule]();', + ' ', + ' /*', + ' * The parser is now in one of the following three states:', + ' *', + ' * 1. The parser successfully parsed the whole input.', + ' *', + ' * - |result !== null|', + ' * - |#{posOffset("pos")} === input.length|', + ' * - |rightmostFailuresExpected| may or may not contain something', + ' *', + ' * 2. The parser successfully parsed only a part of the input.', + ' *', + ' * - |result !== null|', + ' * - |#{posOffset("pos")} < input.length|', + ' * - |rightmostFailuresExpected| may or may not contain something', + ' *', + ' * 3. The parser did not successfully parse any part of the input.', + ' *', + ' * - |result === null|', + ' * - |#{posOffset("pos")} === 0|', + ' * - |rightmostFailuresExpected| contains at least one failure', + ' *', + ' * All code following this comment (including called functions) must', + ' * handle these states.', + ' */', + ' if (result === null || #{posOffset("pos")} !== input.length) {', + ' var offset = Math.max(#{posOffset("pos")}, #{posOffset("rightmostFailuresPos")});', + ' var found = offset < input.length ? input.charAt(offset) : null;', + ' #if options.trackLineAndColumn', + ' var errorPosition = #{posOffset("pos")} > #{posOffset("rightmostFailuresPos")} ? pos : rightmostFailuresPos;', + ' #else', + ' var errorPosition = computeErrorPosition();', + ' #end', + ' ', + ' throw new this.SyntaxError(', + ' cleanupExpected(rightmostFailuresExpected),', + ' found,', + ' offset,', + ' errorPosition.line,', + ' errorPosition.column', + ' );', + ' }', + ' ', + ' return result;', + ' },', + ' ', + ' /* Returns the parser source code. */', + ' toSource: function() { return this._source; }', + ' };', + ' ', + ' /* Thrown when a parser encounters a syntax error. */', + ' ', + ' result.SyntaxError = function(expected, found, offset, line, column) {', + ' function buildMessage(expected, found) {', + ' var expectedHumanized, foundHumanized;', + ' ', + ' switch (expected.length) {', + ' case 0:', + ' expectedHumanized = "end of input";', + ' break;', + ' case 1:', + ' expectedHumanized = expected[0];', + ' break;', + ' default:', + ' expectedHumanized = expected.slice(0, expected.length - 1).join(", ")', + ' + " or "', + ' + expected[expected.length - 1];', + ' }', + ' ', + ' foundHumanized = found ? quote(found) : "end of input";', + ' ', + ' return "Expected " + expectedHumanized + " but " + foundHumanized + " found.";', + ' }', + ' ', + ' this.name = "SyntaxError";', + ' this.expected = expected;', + ' this.found = found;', + ' this.message = buildMessage(expected, found);', + ' this.offset = offset;', + ' this.line = line;', + ' this.column = column;', + ' };', + ' ', + ' result.SyntaxError.prototype = Error.prototype;', + ' ', + ' return result;', + '})()' + ], + rule: [ + 'function parse_#{node.name}() {', + ' #if options.cache', + ' var cacheKey = "#{node.name}@" + #{posOffset("pos")};', + ' var cachedResult = cache[cacheKey];', + ' if (cachedResult) {', + ' pos = #{posClone("cachedResult.nextPos")};', + ' return cachedResult.result;', + ' }', + ' ', + ' #end', + ' #if node.resultVars.length > 0', + ' var #{node.resultVars.join(", ")};', + ' #end', + ' #if node.posVars.length > 0', + ' var #{node.posVars.join(", ")};', + ' #end', + ' ', + ' #if node.displayName !== null', + ' reportFailures++;', + ' #end', + ' #block emit(node.expression)', + ' #if node.displayName !== null', + ' reportFailures--;', + ' if (reportFailures === 0 && #{node.resultVar} === null) {', + ' matchFailed(#{string(node.displayName)});', + ' }', + ' #end', + ' #if options.cache', + ' ', + ' cache[cacheKey] = {', + ' nextPos: #{posClone("pos")},', + ' result: #{node.resultVar}', + ' };', + ' #end', + ' return #{node.resultVar};', + '}' + ], + choice: [ + '#block emit(alternative)', + '#block nextAlternativesCode' + ], + "choice.next": [ + 'if (#{node.resultVar} === null) {', + ' #block code', + '}' + ], + sequence: [ + '#{posSave(node)};', + '#block code' + ], + "sequence.iteration": [ + '#block emit(element)', + 'if (#{element.resultVar} !== null) {', + ' #block code', + '} else {', + ' #{node.resultVar} = null;', + ' #{posRestore(node)};', + '}' + ], + "sequence.inner": [ + '#{node.resultVar} = [#{pluck(node.elements, "resultVar").join(", ")}];' + ], + simple_and: [ + '#{posSave(node)};', + 'reportFailures++;', + '#block emit(node.expression)', + 'reportFailures--;', + 'if (#{node.resultVar} !== null) {', + ' #{node.resultVar} = "";', + ' #{posRestore(node)};', + '} else {', + ' #{node.resultVar} = null;', + '}' + ], + simple_not: [ + '#{posSave(node)};', + 'reportFailures++;', + '#block emit(node.expression)', + 'reportFailures--;', + 'if (#{node.resultVar} === null) {', + ' #{node.resultVar} = "";', + '} else {', + ' #{node.resultVar} = null;', + ' #{posRestore(node)};', + '}' + ], + semantic_and: [ + '#{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? ["pos.offset", "pos.line", "pos.column"] : ["pos"]).concat(values(node.params)).join(", ")}) ? "" : null;' + ], + semantic_not: [ + '#{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? ["pos.offset", "pos.line", "pos.column"] : ["pos"]).concat(values(node.params)).join(", ")}) ? null : "";' + ], + optional: [ + '#block emit(node.expression)', + '#{node.resultVar} = #{node.resultVar} !== null ? #{node.resultVar} : "";' + ], + zero_or_more: [ + '#{node.resultVar} = [];', + '#block emit(node.expression)', + 'while (#{node.expression.resultVar} !== null) {', + ' #{node.resultVar}.push(#{node.expression.resultVar});', + ' #block emit(node.expression)', + '}' + ], + one_or_more: [ + '#block emit(node.expression)', + 'if (#{node.expression.resultVar} !== null) {', + ' #{node.resultVar} = [];', + ' while (#{node.expression.resultVar} !== null) {', + ' #{node.resultVar}.push(#{node.expression.resultVar});', + ' #block emit(node.expression)', + ' }', + '} else {', + ' #{node.resultVar} = null;', + '}' + ], + action: [ + '#{posSave(node)};', + '#block emit(node.expression)', + 'if (#{node.resultVar} !== null) {', + ' #{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? [node.posVar + ".offset", node.posVar + ".line", node.posVar + ".column"] : [node.posVar]).concat(values(node.params)).join(", ")});', + '}', + 'if (#{node.resultVar} === null) {', + ' #{posRestore(node)};', + '}' + ], + rule_ref: [ + '#{node.resultVar} = parse_#{node.name}();' + ], + literal: [ + '#if node.value.length === 0', + ' #{node.resultVar} = "";', + '#else', + ' #if !node.ignoreCase', + ' #if node.value.length === 1', + ' if (input.charCodeAt(#{posOffset("pos")}) === #{node.value.charCodeAt(0)}) {', + ' #else', + ' if (input.substr(#{posOffset("pos")}, #{node.value.length}) === #{string(node.value)}) {', + ' #end', + ' #else', + /* + * One-char literals are not optimized when case-insensitive + * matching is enabled. This is because there is no simple way to + * lowercase a character code that works for character outside ASCII + * letters. Moreover, |toLowerCase| can change string length, + * meaning the result of lowercasing a character can be more + * characters. + */ + ' if (input.substr(#{posOffset("pos")}, #{node.value.length}).toLowerCase() === #{string(node.value.toLowerCase())}) {', + ' #end', + ' #if !node.ignoreCase', + ' #{node.resultVar} = #{string(node.value)};', + ' #else', + ' #{node.resultVar} = input.substr(#{posOffset("pos")}, #{node.value.length});', + ' #end', + ' #{posAdvance(node.value.length)};', + ' } else {', + ' #{node.resultVar} = null;', + ' if (reportFailures === 0) {', + ' matchFailed(#{string(string(node.value))});', + ' }', + ' }', + '#end' + ], + any: [ + 'if (input.length > #{posOffset("pos")}) {', + ' #{node.resultVar} = input.charAt(#{posOffset("pos")});', + ' #{posAdvance(1)};', + '} else {', + ' #{node.resultVar} = null;', + ' if (reportFailures === 0) {', + ' matchFailed("any character");', + ' }', + '}' + ], + "class": [ + 'if (#{regexp}.test(input.charAt(#{posOffset("pos")}))) {', + ' #{node.resultVar} = input.charAt(#{posOffset("pos")});', + ' #{posAdvance(1)};', + '} else {', + ' #{node.resultVar} = null;', + ' if (reportFailures === 0) {', + ' matchFailed(#{string(node.rawText)});', + ' }', + '}' + ] + }; + + for (name in sources) { + templates[name] = Codie.template(sources[name].join('\n')); + } + + return templates; + })(); + + function fill(name, vars) { + vars.string = quote; + vars.pluck = pluck; + vars.keys = keys; + vars.values = values; + vars.emit = emit; + vars.options = options; + + /* Position-handling macros */ + if (options.trackLineAndColumn) { + vars.posInit = function(name) { + return "var " + + name + + " = " + + "{ offset: 0, line: 1, column: 1, seenCR: false }"; + }; + vars.posClone = function(name) { return "clone(" + name + ")"; }; + vars.posOffset = function(name) { return name + ".offset"; }; + + vars.posAdvance = function(n) { return "advance(pos, " + n + ")"; }; + } else { + vars.posInit = function(name) { return "var " + name + " = 0"; }; + vars.posClone = function(name) { return name; }; + vars.posOffset = function(name) { return name; }; + + vars.posAdvance = function(n) { + return n === 1 ? "pos++" : "pos += " + n; + }; + } + vars.posSave = function(node) { + return node.posVar + " = " + vars.posClone("pos"); + }; + vars.posRestore = function(node) { + return "pos" + " = " + vars.posClone(node.posVar); + }; + + return templates[name](vars); + } + + function emitSimple(name) { + return function(node) { return fill(name, { node: node }); }; + } + + var emit = buildNodeVisitor({ + grammar: emitSimple("grammar"), + + initializer: function(node) { return node.code; }, + + rule: emitSimple("rule"), + /* * The contract for all code fragments generated by the following functions - * is as follows: + * is as follows. * - * * The code fragment should try to match a part of the input starting with - * the position indicated in |pos|. That position may point past the end of - * the input. + * The code fragment tries to match a part of the input starting with the + * position indicated in |pos|. That position may point past the end of the + * input. * - * * If the code fragment matches the input, it advances |pos| after the - * matched part of the input and sets variable with a name stored in - * |resultVar| to appropriate value, which is always non-null. + * * If the code fragment matches the input, it advances |pos| to point to + * the first chracter following the matched part of the input and sets + * variable with a name stored in |node.resultVar| to an appropriate + * value. This value is always non-|null|. * - * * If the code fragment does not match the input, it does not change |pos| - * and it sets a variable with a name stored in |resultVar| to |null|. + * * If the code fragment does not match the input, it returns with |pos| + * set to the original value and it sets a variable with a name stored in + * |node.resultVar| to |null|. + * + * The code can use variables with names stored in |resultVar| and |posVar| + * properties of the current node's subnodes. It can't use any other + * variables. */ - choice: function(node, resultVar) { - var code = formatCode( - "var ${resultVar} = null;", - { resultVar: resultVar } - ); + choice: function(node) { + var code, nextAlternativesCode; for (var i = node.alternatives.length - 1; i >= 0; i--) { - var alternativeResultVar = UID.next("result"); - code = formatCode( - "${alternativeCode}", - "if (${alternativeResultVar} !== null) {", - " var ${resultVar} = ${alternativeResultVar};", - "} else {", - " ${code};", - "}", - { - alternativeCode: emit(node.alternatives[i], alternativeResultVar), - alternativeResultVar: alternativeResultVar, - code: code, - resultVar: resultVar - } - ); + nextAlternativesCode = i !== node.alternatives.length - 1 + ? fill("choice.next", { node: node, code: code }) + : ''; + code = fill("choice", { + alternative: node.alternatives[i], + nextAlternativesCode: nextAlternativesCode + }); } return code; }, - sequence: function(node, resultVar) { - var savedPosVar = UID.next("savedPos"); - - var elementResultVars = map(node.elements, function() { - return UID.next("result") - }); - - var code = formatCode( - "var ${resultVar} = ${elementResultVarArray};", - { - resultVar: resultVar, - elementResultVarArray: "[" + elementResultVars.join(", ") + "]" - } - ); + sequence: function(node) { + var code = fill("sequence.inner", { node: node }); for (var i = node.elements.length - 1; i >= 0; i--) { - code = formatCode( - "${elementCode}", - "if (${elementResultVar} !== null) {", - " ${code}", - "} else {", - " var ${resultVar} = null;", - " pos = ${savedPosVar};", - "}", - { - elementCode: emit(node.elements[i], elementResultVars[i]), - elementResultVar: elementResultVars[i], - code: code, - savedPosVar: savedPosVar, - resultVar: resultVar - } - ); + code = fill("sequence.iteration", { + node: node, + element: node.elements[i], + code: code + }); } - return formatCode( - "var ${savedPosVar} = pos;", - "${code}", - { - code: code, - savedPosVar: savedPosVar - } - ); + return fill("sequence", { node: node, code: code }); }, - labeled: function(node, resultVar) { - return emit(node.expression, resultVar); - }, + labeled: function(node) { return emit(node.expression); }, - simple_and: function(node, resultVar) { - var savedPosVar = UID.next("savedPos"); - var savedReportMatchFailuresVar = UID.next("savedReportMatchFailuresVar"); - var expressionResultVar = UID.next("result"); + simple_and: emitSimple("simple_and"), + simple_not: emitSimple("simple_not"), + semantic_and: emitSimple("semantic_and"), + semantic_not: emitSimple("semantic_not"), + optional: emitSimple("optional"), + zero_or_more: emitSimple("zero_or_more"), + one_or_more: emitSimple("one_or_more"), + action: emitSimple("action"), + rule_ref: emitSimple("rule_ref"), + literal: emitSimple("literal"), + any: emitSimple("any"), - return formatCode( - "var ${savedPosVar} = pos;", - "var ${savedReportMatchFailuresVar} = reportMatchFailures;", - "reportMatchFailures = false;", - "${expressionCode}", - "reportMatchFailures = ${savedReportMatchFailuresVar};", - "if (${expressionResultVar} !== null) {", - " var ${resultVar} = '';", - " pos = ${savedPosVar};", - "} else {", - " var ${resultVar} = null;", - "}", - { - expressionCode: emit(node.expression, expressionResultVar), - expressionResultVar: expressionResultVar, - savedPosVar: savedPosVar, - savedReportMatchFailuresVar: savedReportMatchFailuresVar, - resultVar: resultVar - } - ); - }, + "class": function(node) { + var regexp; - simple_not: function(node, resultVar) { - var savedPosVar = UID.next("savedPos"); - var savedReportMatchFailuresVar = UID.next("savedReportMatchFailuresVar"); - var expressionResultVar = UID.next("result"); - - return formatCode( - "var ${savedPosVar} = pos;", - "var ${savedReportMatchFailuresVar} = reportMatchFailures;", - "reportMatchFailures = false;", - "${expressionCode}", - "reportMatchFailures = ${savedReportMatchFailuresVar};", - "if (${expressionResultVar} === null) {", - " var ${resultVar} = '';", - "} else {", - " var ${resultVar} = null;", - " pos = ${savedPosVar};", - "}", - { - expressionCode: emit(node.expression, expressionResultVar), - expressionResultVar: expressionResultVar, - savedPosVar: savedPosVar, - savedReportMatchFailuresVar: savedReportMatchFailuresVar, - resultVar: resultVar - } - ); - }, - - semantic_and: function(node, resultVar) { - return formatCode( - "var ${resultVar} = (function() {${actionCode}})() ? '' : null;", - { - actionCode: node.code, - resultVar: resultVar - } - ); - }, - - semantic_not: function(node, resultVar) { - return formatCode( - "var ${resultVar} = (function() {${actionCode}})() ? null : '';", - { - actionCode: node.code, - resultVar: resultVar - } - ); - }, - - optional: function(node, resultVar) { - var expressionResultVar = UID.next("result"); - - return formatCode( - "${expressionCode}", - "var ${resultVar} = ${expressionResultVar} !== null ? ${expressionResultVar} : '';", - { - expressionCode: emit(node.expression, expressionResultVar), - expressionResultVar: expressionResultVar, - resultVar: resultVar - } - ); - }, - - zero_or_more: function(node, resultVar) { - var expressionResultVar = UID.next("result"); - - return formatCode( - "var ${resultVar} = [];", - "${expressionCode}", - "while (${expressionResultVar} !== null) {", - " ${resultVar}.push(${expressionResultVar});", - " ${expressionCode}", - "}", - { - expressionCode: emit(node.expression, expressionResultVar), - expressionResultVar: expressionResultVar, - resultVar: resultVar - } - ); - }, - - one_or_more: function(node, resultVar) { - var expressionResultVar = UID.next("result"); - - return formatCode( - "${expressionCode}", - "if (${expressionResultVar} !== null) {", - " var ${resultVar} = [];", - " while (${expressionResultVar} !== null) {", - " ${resultVar}.push(${expressionResultVar});", - " ${expressionCode}", - " }", - "} else {", - " var ${resultVar} = null;", - "}", - { - expressionCode: emit(node.expression, expressionResultVar), - expressionResultVar: expressionResultVar, - resultVar: resultVar - } - ); - }, - - action: function(node, resultVar) { - /* - * In case of sequences, we splat their elements into function arguments - * one by one. Example: - * - * start: a:"a" b:"b" c:"c" { alert(arguments.length) } // => 3 - * - * This behavior is reflected in this function. - */ - - var expressionResultVar = UID.next("result"); - var actionResultVar = UID.next("result"); - var savedPosVar = UID.next("savedPos"); - - if (node.expression.type === "sequence") { - var formalParams = []; - var actualParams = []; - - var elements = node.expression.elements; - var elementsLength = elements.length; - for (var i = 0; i < elementsLength; i++) { - if (elements[i].type === "labeled") { - formalParams.push(elements[i].label); - actualParams.push(expressionResultVar + "[" + i + "]"); - } - } - } else if (node.expression.type === "labeled") { - var formalParams = [node.expression.label]; - var actualParams = [expressionResultVar]; - } else { - var formalParams = []; - var actualParams = []; - } - - return formatCode( - "var ${savedPosVar} = pos;", - "${expressionCode}", - "var ${actionResultVar} = ${expressionResultVar} !== null", - " ? (function(${formalParams}) {${actionCode}})(${actualParams})", - " : null;", - "if (${actionResultVar} !== null) {", - " var ${resultVar} = ${actionResultVar};", - "} else {", - " var ${resultVar} = null;", - " pos = ${savedPosVar};", - "}", - { - expressionCode: emit(node.expression, expressionResultVar), - expressionResultVar: expressionResultVar, - actionCode: node.code, - actionResultVar: actionResultVar, - formalParams: formalParams.join(", "), - actualParams: actualParams.join(", "), - savedPosVar: savedPosVar, - resultVar: resultVar - } - ); - }, - - rule_ref: function(node, resultVar) { - return formatCode( - "var ${resultVar} = ${ruleMethod}();", - { - ruleMethod: "parse_" + node.name, - resultVar: resultVar - } - ); - }, - - literal: function(node, resultVar) { - return formatCode( - "if (input.substr(pos, ${length}) === ${value|string}) {", - " var ${resultVar} = ${value|string};", - " pos += ${length};", - "} else {", - " var ${resultVar} = null;", - " if (reportMatchFailures) {", - " matchFailed(${valueQuoted|string});", - " }", - "}", - { - value: node.value, - valueQuoted: quote(node.value), - length: node.value.length, - resultVar: resultVar - } - ); - }, - - any: function(node, resultVar) { - return formatCode( - "if (input.length > pos) {", - " var ${resultVar} = input.charAt(pos);", - " pos++;", - "} else {", - " var ${resultVar} = null;", - " if (reportMatchFailures) {", - " matchFailed('any character');", - " }", - "}", - { resultVar: resultVar } - ); - }, - - "class": function(node, resultVar) { if (node.parts.length > 0) { - var regexp = "/^[" - + (node.inverted ? "^" : "") + regexp = '/^[' + + (node.inverted ? '^' : '') + map(node.parts, function(part) { return part instanceof Array ? quoteForRegexpClass(part[0]) - + "-" + + '-' + quoteForRegexpClass(part[1]) : quoteForRegexpClass(part); - }).join("") - + "]/"; + }).join('') + + ']/' + (node.ignoreCase ? 'i' : ''); } else { /* * Stupid IE considers regexps /[]/ and /[^]/ syntactically invalid, so * we translate them into euqivalents it can handle. */ - var regexp = node.inverted ? "/^[\\S\\s]/" : "/^(?!)/"; + regexp = node.inverted ? '/^[\\S\\s]/' : '/^(?!)/'; } - return formatCode( - "if (input.substr(pos).match(${regexp}) !== null) {", - " var ${resultVar} = input.charAt(pos);", - " pos++;", - "} else {", - " var ${resultVar} = null;", - " if (reportMatchFailures) {", - " matchFailed(${rawText|string});", - " }", - "}", - { - regexp: regexp, - rawText: node.rawText, - resultVar: resultVar - } - ); + return fill("class", { node: node, regexp: regexp }); } }); return emit(ast); }; -if (typeof module === "object") { - module.exports = PEG; -} else if (typeof window === "object") { - window.PEG = PEG; -} else { - throw new Error("Can't export PEG library (no \"module\" nor \"window\" object detected)."); -} +return PEG; })(); + +if (typeof module !== "undefined") { + module.exports = PEG; +} From f9ec6214e62a6e2628a765e18d2c3946ddea2e17 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 21 Jun 2012 16:33:21 -0600 Subject: [PATCH 20/30] Snippets parser can parse tab stops in the form of $1, $2, etc. It associates each tab stop with its position relative to the beginning of the snippet body, and strips the '$number' marker from the inserted snippet body. --- spec/extensions/snippets-spec.coffee | 16 +++++++++++++++ src/extensions/snippets/snippet.coffee | 21 +++++++++++++++++++ src/extensions/snippets/snippets.coffee | 2 +- src/extensions/snippets/snippets.pegjs | 27 ++++++++++++++++--------- 4 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 src/extensions/snippets/snippet.coffee diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 499d88b95..83aed2ac6 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -85,3 +85,19 @@ describe "Snippets extension", -> expect(snippet.prefix).toBe 't2' expect(snippet.description).toBe "Test snippet 2" expect(snippet.body).toBe "this is a test 2" + + it "can parse snippets with tabstops", -> + snippets = Snippets.snippetsParser.parse """ + # this line intentially left blank. + snippet t1 "Test snippet 1" + then go back to here: ($2) + first go here: ($1) + endsnippet + """ + + snippet = snippets['t1'] + expect(snippet.body).toBe """ + then go back to here: () + first go here: () + """ + expect(snippet.tabStops).toEqual [[1, 16], [0, 23]] diff --git a/src/extensions/snippets/snippet.coffee b/src/extensions/snippets/snippet.coffee new file mode 100644 index 000000000..9e938dfd3 --- /dev/null +++ b/src/extensions/snippets/snippet.coffee @@ -0,0 +1,21 @@ +_ = require 'underscore' + +module.exports = +class Snippet + constructor: ({@bodyPosition, @prefix, @description, body}) -> + @body = @extractTabStops(body) + + extractTabStops: (body) -> + tabStopsByIndex = {} + bodyText = [] + for element in body + if element.index + tabStopsByIndex[element.index] = element.position.subtract(@bodyPosition) + else + bodyText.push(element) + + @tabStops = [] + for index in _.keys(tabStopsByIndex).sort() + @tabStops.push tabStopsByIndex[index] + + bodyText.join('') diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index d69a8c00d..c39e2b7be 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -5,7 +5,7 @@ _ = require 'underscore' module.exports = name: 'Snippets' snippetsByExtension: {} - snippetsParser: PEG.buildParser(fs.read(require.resolve 'extensions/snippets/snippets.pegjs')) + snippetsParser: PEG.buildParser(fs.read(require.resolve 'extensions/snippets/snippets.pegjs'), trackLineAndColumn: true) activate: (@rootView) -> @loadSnippets() diff --git a/src/extensions/snippets/snippets.pegjs b/src/extensions/snippets/snippets.pegjs index f53683544..f615c134c 100644 --- a/src/extensions/snippets/snippets.pegjs +++ b/src/extensions/snippets/snippets.pegjs @@ -1,3 +1,8 @@ +{ + var Snippet = require('extensions/snippets/snippet'); + var Point = require('point'); +} + snippets = snippets:snippet+ ws? { var snippetsByPrefix = {}; snippets.forEach(function(snippet) { @@ -6,17 +11,21 @@ snippets = snippets:snippet+ ws? { return snippetsByPrefix; } -snippet = ws? start ws prefix:prefix ws description:string separator body:body end { - return { prefix: prefix, description: description, body: body }; +snippet = ws? start ws prefix:prefix ws description:string bodyPosition:beforeBody body:body end { + return new Snippet({ bodyPosition: bodyPosition, prefix: prefix, description: description, body: body }); } -separator = [ ]* '\n' start = 'snippet' prefix = prefix:[A-Za-z0-9_]+ { return prefix.join(''); } -body = body:bodyCharacter* { return body.join(''); } -bodyCharacter = !end char:. { return char; } +string = ['] body:[^']* ['] { return body.join(''); } + / ["] body:[^"]* ["] { return body.join(''); } +beforeBody = [ ]* '\n' { return new Point(line, 0); } // return start position of body: body begins on next line, so don't subtract 1 from line + +body = (tabStop / bodyText)* +bodyText = body:bodyCharacter+ { return body.join(''); } +bodyCharacter = !(end / tabStop) char:. { return char; } +tabStop = '$' index:[0-9]+ { return { index: index, position: new Point(line - 1, column - 1) }; } + end = '\nendsnippet' -string - = ['] body:[^']* ['] { return body.join(''); } - / ["] body:[^"]* ["] { return body.join(''); } -ws = [ \n]+ +ws = ([ \n] / comment)+ +comment = '#' [^\n]* From a936b6b7161901964591725a71418fd038457c34 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 21 Jun 2012 16:53:13 -0600 Subject: [PATCH 21/30] Tab advances between snippet tab stops. Still need to account for tab stops moving due to buffer changes. --- spec/extensions/snippets-spec.coffee | 14 ++++++++++++-- src/extensions/snippets/snippets.coffee | 20 ++++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 83aed2ac6..6b7117ead 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -23,7 +23,9 @@ describe "Snippets extension", -> endsnippet snippet t2 "Snippet with tab stops" - first go here:$1 then here:$2 + go here next:($2) and finally go here:($3) + go here first:($1) + endsnippet """ @@ -38,7 +40,15 @@ describe "Snippets extension", -> expect(editor.getCursorScreenPosition()).toEqual [0, 14] describe "when the snippet contains tab stops", -> - + it "places the cursor at the first tab-stop, and moves the cursor in response to 'next-tab-stop' events", -> + editor.insertText('t2') + editor.trigger keydownEvent('tab', target: editor[0]) + expect(buffer.lineForRow(0)).toBe "go here next:() and finally go here:()" + expect(buffer.lineForRow(1)).toBe "go here first:()" + expect(buffer.lineForRow(2)).toBe "var quicksort = function () {" + expect(editor.getCursorScreenPosition()).toEqual [1, 15] + editor.trigger keydownEvent('tab', target: editor[0]) + expect(editor.getCursorScreenPosition()).toEqual [0, 14] describe "when the letters preceding the cursor don't match a snippet", -> it "inserts a tab as normal", -> diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index c39e2b7be..5c3776f6e 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -28,8 +28,10 @@ module.exports = editSession.snippetsSession ?= new SnippetsSession(editSession, @snippetsByExtension) e.abortKeyBinding() unless editSession.snippetsSession.expandSnippet() - # this is currently disabled. soon we will jump tab stops if a snippet is active - editor.on 'snippets:next-tab-stop', (e) -> e.abortKeyBinding() + editor.on 'snippets:next-tab-stop', (e) -> + editSession = editor.activeEditSession + e.abortKeyBinding() unless editSession.snippetsSession?.goToNextTabStop() + class SnippetsSession constructor: (@editSession, @snippetsByExtension) -> @@ -37,9 +39,19 @@ class SnippetsSession expandSnippet: -> return unless snippets = @snippetsByExtension[@editSession.buffer.getExtension()] prefix = @editSession.getLastCursor().getCurrentWordPrefix() - if body = snippets[prefix]?.body + if @activeSnippet = snippets[prefix] @editSession.selectToBeginningOfWord() - @editSession.insertText(body) + @activeSnippetStartPosition = @editSession.getCursorBufferPosition() + @editSession.insertText(@activeSnippet.body) + @setTabStopIndex(0) if @activeSnippet.tabStops.length true else false + + goToNextTabStop: -> + return false unless @activeSnippet + @setTabStopIndex(@tabStopIndex + 1) + + setTabStopIndex: (@tabStopIndex) -> + tabStopPosition = @activeSnippet.tabStops[@tabStopIndex].subtract(@activeSnippetStartPosition) + @editSession.setCursorBufferPosition(tabStopPosition) From ae2b6868029760c669e3ef08ef71caab3575a4a7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 22 Jun 2012 11:44:58 -0600 Subject: [PATCH 22/30] Parse tab-stop positions correctly when there are multiple on a line --- spec/extensions/snippets-spec.coffee | 13 +++++++------ src/extensions/snippets/snippet.coffee | 19 +++++++++++++------ src/extensions/snippets/snippets.pegjs | 12 +++++++----- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 6b7117ead..bc7fd71d7 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -99,15 +99,16 @@ describe "Snippets extension", -> it "can parse snippets with tabstops", -> snippets = Snippets.snippetsParser.parse """ # this line intentially left blank. - snippet t1 "Test snippet 1" - then go back to here: ($2) - first go here: ($1) + snippet t1 "Snippet with tab stops" + go here next:($2) and finally go here:($3) + go here first:($1) endsnippet """ snippet = snippets['t1'] expect(snippet.body).toBe """ - then go back to here: () - first go here: () + go here next:() and finally go here:() + go here first:()\n """ - expect(snippet.tabStops).toEqual [[1, 16], [0, 23]] + + expect(snippet.tabStops).toEqual [[1, 15], [0, 14], [0, 37]] diff --git a/src/extensions/snippets/snippet.coffee b/src/extensions/snippets/snippet.coffee index 9e938dfd3..0e1c510d8 100644 --- a/src/extensions/snippets/snippet.coffee +++ b/src/extensions/snippets/snippet.coffee @@ -1,18 +1,25 @@ _ = require 'underscore' +Point = require 'point' module.exports = class Snippet constructor: ({@bodyPosition, @prefix, @description, body}) -> @body = @extractTabStops(body) - extractTabStops: (body) -> + extractTabStops: (bodyLines) -> tabStopsByIndex = {} bodyText = [] - for element in body - if element.index - tabStopsByIndex[element.index] = element.position.subtract(@bodyPosition) - else - bodyText.push(element) + + [row, column] = [0, 0] + for bodyLine in bodyLines + for segment in bodyLine + if _.isNumber(segment) + tabStopsByIndex[segment] = new Point(row, column) + else + bodyText.push(segment) + column += segment.length + bodyText.push('\n') + row++; column = 0 @tabStops = [] for index in _.keys(tabStopsByIndex).sort() diff --git a/src/extensions/snippets/snippets.pegjs b/src/extensions/snippets/snippets.pegjs index f615c134c..581d15f4b 100644 --- a/src/extensions/snippets/snippets.pegjs +++ b/src/extensions/snippets/snippets.pegjs @@ -19,13 +19,15 @@ start = 'snippet' prefix = prefix:[A-Za-z0-9_]+ { return prefix.join(''); } string = ['] body:[^']* ['] { return body.join(''); } / ["] body:[^"]* ["] { return body.join(''); } + beforeBody = [ ]* '\n' { return new Point(line, 0); } // return start position of body: body begins on next line, so don't subtract 1 from line -body = (tabStop / bodyText)* -bodyText = body:bodyCharacter+ { return body.join(''); } -bodyCharacter = !(end / tabStop) char:. { return char; } -tabStop = '$' index:[0-9]+ { return { index: index, position: new Point(line - 1, column - 1) }; } +body = bodyLine+ +bodyLine = content:(tabStop / bodyText)* '\n' { return content; } +bodyText = text:bodyChar+ { return text.join(''); } +bodyChar = !(end / tabStop) char:[^\n] { return char; } +tabStop = '$' index:[0-9]+ { return parseInt(index); } -end = '\nendsnippet' +end = 'endsnippet' ws = ([ \n] / comment)+ comment = '#' [^\n]* From 5c6e94ec7483bfda4801a6e56a8b51fc8c4e6d71 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 22 Jun 2012 12:07:20 -0600 Subject: [PATCH 23/30] Tab stops are associated with anchors so we can jump to them event when the buffer changes --- spec/extensions/snippets-spec.coffee | 7 ++++++- src/app/edit-session.coffee | 5 +++++ src/extensions/snippets/snippet.coffee | 9 +++++---- src/extensions/snippets/snippets.coffee | 9 +++++++-- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index bc7fd71d7..f1c10caec 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -47,8 +47,13 @@ describe "Snippets extension", -> expect(buffer.lineForRow(1)).toBe "go here first:()" expect(buffer.lineForRow(2)).toBe "var quicksort = function () {" expect(editor.getCursorScreenPosition()).toEqual [1, 15] + editor.trigger keydownEvent('tab', target: editor[0]) expect(editor.getCursorScreenPosition()).toEqual [0, 14] + editor.insertText 'abc' + + editor.trigger keydownEvent('tab', target: editor[0]) + expect(editor.getCursorScreenPosition()).toEqual [0, 40] describe "when the letters preceding the cursor don't match a snippet", -> it "inserts a tab as normal", -> @@ -108,7 +113,7 @@ describe "Snippets extension", -> snippet = snippets['t1'] expect(snippet.body).toBe """ go here next:() and finally go here:() - go here first:()\n + go here first:() """ expect(snippet.tabStops).toEqual [[1, 15], [0, 14], [0, 37]] diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index db90fa96c..78f58b24d 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -239,6 +239,11 @@ class EditSession @anchors.push(anchor) anchor + addAnchorAtBufferPosition: (bufferPosition) -> + anchor = @addAnchor() + anchor.setBufferPosition(bufferPosition) + anchor + removeAnchor: (anchor) -> _.remove(@anchors, anchor) diff --git a/src/extensions/snippets/snippet.coffee b/src/extensions/snippets/snippet.coffee index 0e1c510d8..0ef9691eb 100644 --- a/src/extensions/snippets/snippet.coffee +++ b/src/extensions/snippets/snippet.coffee @@ -11,18 +11,19 @@ class Snippet bodyText = [] [row, column] = [0, 0] - for bodyLine in bodyLines + for bodyLine, i in bodyLines + lineText = [] for segment in bodyLine if _.isNumber(segment) tabStopsByIndex[segment] = new Point(row, column) else - bodyText.push(segment) + lineText.push(segment) column += segment.length - bodyText.push('\n') + bodyText.push(lineText.join('')) row++; column = 0 @tabStops = [] for index in _.keys(tabStopsByIndex).sort() @tabStops.push tabStopsByIndex[index] - bodyText.join('') + bodyText.join('\n') diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index 5c3776f6e..32102afaa 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -34,6 +34,7 @@ module.exports = class SnippetsSession + tabStopAnchors: null constructor: (@editSession, @snippetsByExtension) -> expandSnippet: -> @@ -43,15 +44,19 @@ class SnippetsSession @editSession.selectToBeginningOfWord() @activeSnippetStartPosition = @editSession.getCursorBufferPosition() @editSession.insertText(@activeSnippet.body) + @placeTabStopAnchors() @setTabStopIndex(0) if @activeSnippet.tabStops.length true else false + placeTabStopAnchors: -> + @tabStopAnchors = @activeSnippet.tabStops.map (position) => + @editSession.addAnchorAtBufferPosition(position) + goToNextTabStop: -> return false unless @activeSnippet @setTabStopIndex(@tabStopIndex + 1) setTabStopIndex: (@tabStopIndex) -> - tabStopPosition = @activeSnippet.tabStops[@tabStopIndex].subtract(@activeSnippetStartPosition) - @editSession.setCursorBufferPosition(tabStopPosition) + @editSession.setCursorBufferPosition(@tabStopAnchors[@tabStopIndex].getBufferPosition()) From c4046bbdc550a917aeff82e5ab0fa170b93c1b26 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 22 Jun 2012 12:52:40 -0600 Subject: [PATCH 24/30] Place snippet tab relative to snippet start position. Terminate when pressing 'tab' at last tab-stop. --- spec/extensions/snippets-spec.coffee | 19 ++++++++++----- src/app/anchor.coffee | 3 +++ src/extensions/snippets/snippets.coffee | 31 +++++++++++++++++-------- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index f1c10caec..90b52f631 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -41,19 +41,26 @@ describe "Snippets extension", -> describe "when the snippet contains tab stops", -> it "places the cursor at the first tab-stop, and moves the cursor in response to 'next-tab-stop' events", -> + anchorCountBefore = editor.activeEditSession.getAnchors().length + editor.setCursorScreenPosition([2, 0]) editor.insertText('t2') editor.trigger keydownEvent('tab', target: editor[0]) - expect(buffer.lineForRow(0)).toBe "go here next:() and finally go here:()" - expect(buffer.lineForRow(1)).toBe "go here first:()" - expect(buffer.lineForRow(2)).toBe "var quicksort = function () {" - expect(editor.getCursorScreenPosition()).toEqual [1, 15] + expect(buffer.lineForRow(2)).toBe "go here next:() and finally go here:()" + expect(buffer.lineForRow(3)).toBe "go here first:()" + expect(buffer.lineForRow(4)).toBe " if (items.length <= 1) return items;" + expect(editor.getCursorScreenPosition()).toEqual [3, 15] editor.trigger keydownEvent('tab', target: editor[0]) - expect(editor.getCursorScreenPosition()).toEqual [0, 14] + expect(editor.getCursorScreenPosition()).toEqual [2, 14] editor.insertText 'abc' editor.trigger keydownEvent('tab', target: editor[0]) - expect(editor.getCursorScreenPosition()).toEqual [0, 40] + expect(editor.getCursorScreenPosition()).toEqual [2, 40] + + # terminate snippet + editor.trigger keydownEvent('tab', target: editor[0]) + expect(buffer.lineForRow(2)).toBe "go here next:(abc) and finally go here:( )" + expect(editor.activeEditSession.getAnchors().length).toBe anchorCountBefore describe "when the letters preceding the cursor don't match a snippet", -> it "inserts a tab as normal", -> diff --git a/src/app/anchor.coffee b/src/app/anchor.coffee index 676d3e97f..e8f1e50fd 100644 --- a/src/app/anchor.coffee +++ b/src/app/anchor.coffee @@ -56,4 +56,7 @@ class Anchor screenPosition = @editSession.screenPositionForBufferPosition(@bufferPosition, options) @setScreenPosition(screenPosition, bufferChange: options.bufferChange, clip: false, assignBufferPosition: false) + destroy: -> + @editSession.removeAnchor(this) + _.extend(Anchor.prototype, EventEmitter) diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index 32102afaa..a63d305d2 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -40,23 +40,34 @@ class SnippetsSession expandSnippet: -> return unless snippets = @snippetsByExtension[@editSession.buffer.getExtension()] prefix = @editSession.getLastCursor().getCurrentWordPrefix() - if @activeSnippet = snippets[prefix] + if snippet = snippets[prefix] @editSession.selectToBeginningOfWord() - @activeSnippetStartPosition = @editSession.getCursorBufferPosition() - @editSession.insertText(@activeSnippet.body) - @placeTabStopAnchors() - @setTabStopIndex(0) if @activeSnippet.tabStops.length + snippetStartPosition = @editSession.getCursorBufferPosition() + @editSession.insertText(snippet.body) + if snippet.tabStops.length + @placeTabStopAnchors(snippetStartPosition, snippet.tabStops) + @setTabStopIndex(0) true else false - placeTabStopAnchors: -> - @tabStopAnchors = @activeSnippet.tabStops.map (position) => - @editSession.addAnchorAtBufferPosition(position) + placeTabStopAnchors: (snippetStartPosition, tabStopPositions) -> + @tabStopAnchors = tabStopPositions.map (tabStopPosition) => + @editSession.addAnchorAtBufferPosition(snippetStartPosition.add(tabStopPosition)) goToNextTabStop: -> - return false unless @activeSnippet - @setTabStopIndex(@tabStopIndex + 1) + return false unless @tabStopAnchors + nextIndex = @tabStopIndex + 1 + if nextIndex < @tabStopAnchors.length + @setTabStopIndex(nextIndex) + true + else + @terminateActiveSnippet() + false setTabStopIndex: (@tabStopIndex) -> @editSession.setCursorBufferPosition(@tabStopAnchors[@tabStopIndex].getBufferPosition()) + + terminateActiveSnippet: -> + anchor.destroy() for anchor in @tabStopAnchors + From de9486320ddb5d91d563f0e5f54c5bc8334f4653 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 22 Jun 2012 22:10:14 -0600 Subject: [PATCH 25/30] Shift-tab moves through snippet tab stops backwards --- spec/extensions/snippets-spec.coffee | 15 ++++++++++++++- src/extensions/snippets/snippets.coffee | 9 ++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 90b52f631..cd0a46314 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -57,7 +57,20 @@ describe "Snippets extension", -> editor.trigger keydownEvent('tab', target: editor[0]) expect(editor.getCursorScreenPosition()).toEqual [2, 40] - # terminate snippet + # tab backwards + editor.trigger keydownEvent('tab', shiftKey: true, target: editor[0]) + expect(editor.getCursorScreenPosition()).toEqual [2, 17] + + editor.trigger keydownEvent('tab', shiftKey: true, target: editor[0]) + expect(editor.getCursorScreenPosition()).toEqual [3, 15] + + # shift-tab on first tab-stop does nothing + editor.trigger keydownEvent('tab', shiftKey: true, target: editor[0]) + expect(editor.getCursorScreenPosition()).toEqual [3, 15] + + # tab through all tab stops, then tab on last stop to terminate snippet + editor.trigger keydownEvent('tab', target: editor[0]) + editor.trigger keydownEvent('tab', target: editor[0]) editor.trigger keydownEvent('tab', target: editor[0]) expect(buffer.lineForRow(2)).toBe "go here next:(abc) and finally go here:( )" expect(editor.activeEditSession.getAnchors().length).toBe anchorCountBefore diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index a63d305d2..8b0a7bfc0 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -32,6 +32,9 @@ module.exports = editSession = editor.activeEditSession e.abortKeyBinding() unless editSession.snippetsSession?.goToNextTabStop() + editor.on 'snippets:previous-tab-stop', (e) -> + editSession = editor.activeEditSession + e.abortKeyBinding() unless editSession.snippetsSession?.goToPreviousTabStop() class SnippetsSession tabStopAnchors: null @@ -65,9 +68,13 @@ class SnippetsSession @terminateActiveSnippet() false + goToPreviousTabStop: -> + return false unless @tabStopAnchors + @setTabStopIndex(@tabStopIndex - 1) if @tabStopIndex > 0 + true + setTabStopIndex: (@tabStopIndex) -> @editSession.setCursorBufferPosition(@tabStopAnchors[@tabStopIndex].getBufferPosition()) terminateActiveSnippet: -> anchor.destroy() for anchor in @tabStopAnchors - From 34833175499bf0f3e49440bc7c6317508832ed8d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 22 Jun 2012 22:57:49 -0600 Subject: [PATCH 26/30] Subsequent lines of a snippet are indented based on the first line --- spec/extensions/snippets-spec.coffee | 16 +++++++++++++++- src/extensions/snippets/snippet.coffee | 5 +++++ src/extensions/snippets/snippets.coffee | 19 +++++++++++++------ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index cd0a46314..357a17417 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -22,11 +22,17 @@ describe "Snippets extension", -> this is a test endsnippet - snippet t2 "Snippet with tab stops" + snippet t2 "With tab stops" go here next:($2) and finally go here:($3) go here first:($1) endsnippet + + snippet t3 "With indented second line" + line 1 + line 2 + + endsnippet """ describe "when the letters preceding the cursor trigger a snippet", -> @@ -75,6 +81,14 @@ describe "Snippets extension", -> expect(buffer.lineForRow(2)).toBe "go here next:(abc) and finally go here:( )" expect(editor.activeEditSession.getAnchors().length).toBe anchorCountBefore + describe "when a the start of the snippet is indented", -> + it "indents the subsequent lines of the snippet to be even with the start of the first line", -> + editor.setCursorScreenPosition([2, Infinity]) + editor.insertText ' t3' + editor.trigger 'snippets:expand' + expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items; line 1" + expect(buffer.lineForRow(3)).toBe " line 2" + describe "when the letters preceding the cursor don't match a snippet", -> it "inserts a tab as normal", -> editor.insertText("xte") diff --git a/src/extensions/snippets/snippet.coffee b/src/extensions/snippets/snippet.coffee index 0ef9691eb..cb50dc1c5 100644 --- a/src/extensions/snippets/snippet.coffee +++ b/src/extensions/snippets/snippet.coffee @@ -3,6 +3,10 @@ Point = require 'point' module.exports = class Snippet + body: null + lineCount: null + tabStops: null + constructor: ({@bodyPosition, @prefix, @description, body}) -> @body = @extractTabStops(body) @@ -21,6 +25,7 @@ class Snippet column += segment.length bodyText.push(lineText.join('')) row++; column = 0 + @lineCount = row + 1 @tabStops = [] for index in _.keys(tabStopsByIndex).sort() diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index 8b0a7bfc0..523d4edae 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -45,18 +45,25 @@ class SnippetsSession prefix = @editSession.getLastCursor().getCurrentWordPrefix() if snippet = snippets[prefix] @editSession.selectToBeginningOfWord() - snippetStartPosition = @editSession.getCursorBufferPosition() + startPosition = @editSession.getCursorBufferPosition() @editSession.insertText(snippet.body) - if snippet.tabStops.length - @placeTabStopAnchors(snippetStartPosition, snippet.tabStops) - @setTabStopIndex(0) + @placeTabStopAnchors(startPosition, snippet.tabStops) + @indentSnippet(startPosition.row, snippet) true else false - placeTabStopAnchors: (snippetStartPosition, tabStopPositions) -> + placeTabStopAnchors: (startPosition, tabStopPositions) -> + return unless tabStopPositions.length @tabStopAnchors = tabStopPositions.map (tabStopPosition) => - @editSession.addAnchorAtBufferPosition(snippetStartPosition.add(tabStopPosition)) + @editSession.addAnchorAtBufferPosition(startPosition.add(tabStopPosition)) + @setTabStopIndex(0) + + indentSnippet: (startRow, snippet) -> + if snippet.lineCount > 1 + initialIndent = @editSession.lineForBufferRow(startRow).match(/^\s*/)[0] + for row in [startRow + 1...startRow + snippet.lineCount] + @editSession.buffer.insert([row, 0], initialIndent) goToNextTabStop: -> return false unless @tabStopAnchors From c354f016b929352586726f6fbe2654d4067aeee8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 22 Jun 2012 23:03:29 -0600 Subject: [PATCH 27/30] Ensure tab-stops are correctly placed on indented snippet lines --- spec/extensions/snippets-spec.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 357a17417..cac817bfc 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -30,7 +30,7 @@ describe "Snippets extension", -> snippet t3 "With indented second line" line 1 - line 2 + line 2$1 endsnippet """ @@ -88,6 +88,7 @@ describe "Snippets extension", -> editor.trigger 'snippets:expand' expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items; line 1" expect(buffer.lineForRow(3)).toBe " line 2" + expect(editor.getCursorBufferPosition()).toEqual [3, 12] describe "when the letters preceding the cursor don't match a snippet", -> it "inserts a tab as normal", -> From 65991c686ab56b60e4c921ee3ed83c24d40012d1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Jun 2012 17:29:32 -0600 Subject: [PATCH 28/30] Snippets can contain placeholder text (but can't nest yet) Snippet placeholders are managed by adding an "anchor range" to the edit session. An anchor range basically tracks two anchors for the start and the end of the range. --- spec/extensions/snippets-spec.coffee | 14 +++++++++++++- src/app/anchor-range.coffee | 21 +++++++++++++++++++++ src/app/edit-session.coffee | 8 ++++++++ src/app/editor.coffee | 1 + src/app/range.coffee | 3 +++ src/extensions/snippets/snippet.coffee | 8 +++++--- src/extensions/snippets/snippets.coffee | 23 ++++++++++++----------- src/extensions/snippets/snippets.pegjs | 8 +++++++- 8 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 src/app/anchor-range.coffee diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index cac817bfc..6ec314ed8 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -33,6 +33,11 @@ describe "Snippets extension", -> line 2$1 endsnippet + + snippet t4 "With tab stop placeholders" + go here ${1:first} and then here ${2:second} + + endsnippet """ describe "when the letters preceding the cursor trigger a snippet", -> @@ -81,6 +86,13 @@ describe "Snippets extension", -> expect(buffer.lineForRow(2)).toBe "go here next:(abc) and finally go here:( )" expect(editor.activeEditSession.getAnchors().length).toBe anchorCountBefore + describe "when the tab stops have placeholder text", -> + it "auto-fills the placeholder text and highlights it when navigating to that tab stop", -> + editor.insertText 't4' + editor.trigger 'snippets:expand' + expect(buffer.lineForRow(0)).toBe 'go here first and then here second' + expect(editor.getSelectedBufferRange()).toEqual [[0, 8], [0, 13]] + describe "when a the start of the snippet is indented", -> it "indents the subsequent lines of the snippet to be even with the start of the first line", -> editor.setCursorScreenPosition([2, Infinity]) @@ -151,4 +163,4 @@ describe "Snippets extension", -> go here first:() """ - expect(snippet.tabStops).toEqual [[1, 15], [0, 14], [0, 37]] + expect(snippet.tabStops).toEqual [[[1, 15], [1, 15]], [[0, 14], [0, 14]], [[0, 37], [0, 37]]] diff --git a/src/app/anchor-range.coffee b/src/app/anchor-range.coffee new file mode 100644 index 000000000..ac236f2cf --- /dev/null +++ b/src/app/anchor-range.coffee @@ -0,0 +1,21 @@ +Range = require 'range' + +module.exports = +class AnchorRange + start: null + end: null + + constructor: (@editSession, bufferRange) -> + bufferRange = Range.fromObject(bufferRange) + @startAnchor = @editSession.addAnchorAtBufferPosition(bufferRange.start) + @endAnchor = @editSession.addAnchorAtBufferPosition(bufferRange.end) + + getBufferRange: -> + new Range(@startAnchor.getBufferPosition(), @endAnchor.getBufferPosition()) + + getScreenRange: -> + new Range(@startAnchor.getScreenPosition(), @endAnchor.getScreenPosition()) + + destroy: -> + @startAnchor.destroy() + @endAnchor.destroy() \ No newline at end of file diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 78f58b24d..7d4b5cd30 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -5,6 +5,7 @@ DisplayBuffer = require 'display-buffer' Cursor = require 'cursor' Selection = require 'selection' EventEmitter = require 'event-emitter' +AnchorRange = require 'anchor-range' _ = require 'underscore' module.exports = @@ -22,6 +23,7 @@ class EditSession scrollLeft: 0 displayBuffer: null anchors: null + anchorRanges: null cursors: null selections: null autoIndent: true @@ -34,6 +36,7 @@ class EditSession @displayBuffer = new DisplayBuffer(@buffer, { @tabText }) @tokenizedBuffer = @displayBuffer.tokenizedBuffer @anchors = [] + @anchorRanges = [] @cursors = [] @selections = [] @addCursorAtScreenPosition([0, 0]) @@ -244,6 +247,11 @@ class EditSession anchor.setBufferPosition(bufferPosition) anchor + addAnchorRange: (range) -> + anchorRange = new AnchorRange(this, range) + @anchorRanges.push(anchorRange) + anchorRange + removeAnchor: (anchor) -> _.remove(@anchors, anchor) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 9c16bd265..dd4003999 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -186,6 +186,7 @@ class Editor extends View getSelectionsOrderedByBufferPosition: -> @activeEditSession.getSelectionsOrderedByBufferPosition() getLastSelectionInBuffer: -> @activeEditSession.getLastSelectionInBuffer() getSelectedText: -> @activeEditSession.getSelectedText() + getSelectedBufferRange: -> @activeEditSession.getSelectedBufferRange() setSelectedBufferRange: (bufferRange, options) -> @activeEditSession.setSelectedBufferRange(bufferRange, options) setSelectedBufferRanges: (bufferRanges, options) -> @activeEditSession.setSelectedBufferRanges(bufferRanges, options) addSelectionForBufferRange: (bufferRange, options) -> @activeEditSession.addSelectionForBufferRange(bufferRange, options) diff --git a/src/app/range.coffee b/src/app/range.coffee index 41f1136c7..c50b766fd 100644 --- a/src/app/range.coffee +++ b/src/app/range.coffee @@ -40,6 +40,9 @@ class Range inspect: -> "[#{@start.inspect()} - #{@end.inspect()}]" + add: (point) -> + new Range(@start.add(point), @end.add(point)) + intersectsWith: (otherRange) -> if @start.isLessThanOrEqual(otherRange.start) @end.isGreaterThanOrEqual(otherRange.start) diff --git a/src/extensions/snippets/snippet.coffee b/src/extensions/snippets/snippet.coffee index cb50dc1c5..c2bb1f48c 100644 --- a/src/extensions/snippets/snippet.coffee +++ b/src/extensions/snippets/snippet.coffee @@ -1,5 +1,5 @@ _ = require 'underscore' -Point = require 'point' +Range = require 'range' module.exports = class Snippet @@ -18,8 +18,10 @@ class Snippet for bodyLine, i in bodyLines lineText = [] for segment in bodyLine - if _.isNumber(segment) - tabStopsByIndex[segment] = new Point(row, column) + if segment.index + { index, placeholderText } = segment + tabStopsByIndex[index] = new Range([row, column], [row, column + placeholderText.length]) + lineText.push(placeholderText) else lineText.push(segment) column += segment.length diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index 523d4edae..c2720d554 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -37,7 +37,7 @@ module.exports = e.abortKeyBinding() unless editSession.snippetsSession?.goToPreviousTabStop() class SnippetsSession - tabStopAnchors: null + tabStopAnchorRanges: null constructor: (@editSession, @snippetsByExtension) -> expandSnippet: -> @@ -47,16 +47,17 @@ class SnippetsSession @editSession.selectToBeginningOfWord() startPosition = @editSession.getCursorBufferPosition() @editSession.insertText(snippet.body) - @placeTabStopAnchors(startPosition, snippet.tabStops) + @placeTabStopAnchorRanges(startPosition, snippet.tabStops) @indentSnippet(startPosition.row, snippet) true else false - placeTabStopAnchors: (startPosition, tabStopPositions) -> - return unless tabStopPositions.length - @tabStopAnchors = tabStopPositions.map (tabStopPosition) => - @editSession.addAnchorAtBufferPosition(startPosition.add(tabStopPosition)) + placeTabStopAnchorRanges: (startPosition, tabStopRanges) -> + return unless tabStopRanges.length + @tabStopAnchorRanges = tabStopRanges.map (tabStopRange) => + { start, end } = tabStopRange + @editSession.addAnchorRange([startPosition.add(start), startPosition.add(end)]) @setTabStopIndex(0) indentSnippet: (startRow, snippet) -> @@ -66,9 +67,9 @@ class SnippetsSession @editSession.buffer.insert([row, 0], initialIndent) goToNextTabStop: -> - return false unless @tabStopAnchors + return false unless @tabStopAnchorRanges nextIndex = @tabStopIndex + 1 - if nextIndex < @tabStopAnchors.length + if nextIndex < @tabStopAnchorRanges.length @setTabStopIndex(nextIndex) true else @@ -76,12 +77,12 @@ class SnippetsSession false goToPreviousTabStop: -> - return false unless @tabStopAnchors + return false unless @tabStopAnchorRanges @setTabStopIndex(@tabStopIndex - 1) if @tabStopIndex > 0 true setTabStopIndex: (@tabStopIndex) -> - @editSession.setCursorBufferPosition(@tabStopAnchors[@tabStopIndex].getBufferPosition()) + @editSession.setSelectedBufferRange(@tabStopAnchorRanges[@tabStopIndex].getBufferRange()) terminateActiveSnippet: -> - anchor.destroy() for anchor in @tabStopAnchors + anchor.destroy() for anchor in @tabStopAnchorRanges diff --git a/src/extensions/snippets/snippets.pegjs b/src/extensions/snippets/snippets.pegjs index 581d15f4b..2d2950130 100644 --- a/src/extensions/snippets/snippets.pegjs +++ b/src/extensions/snippets/snippets.pegjs @@ -26,7 +26,13 @@ body = bodyLine+ bodyLine = content:(tabStop / bodyText)* '\n' { return content; } bodyText = text:bodyChar+ { return text.join(''); } bodyChar = !(end / tabStop) char:[^\n] { return char; } -tabStop = '$' index:[0-9]+ { return parseInt(index); } +tabStop = simpleTabStop / tabStopWithPlaceholder +simpleTabStop = '$' index:[0-9]+ { + return { index: parseInt(index), placeholderText: '' }; +} +tabStopWithPlaceholder = '${' index:[0-9]+ ':' placeholderText:[^}]* '}' { + return { index: parseInt(index), placeholderText: placeholderText.join('') }; +} end = 'endsnippet' ws = ([ \n] / comment)+ From 01993f1be257c752b5994c0572392539d6c104fb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Jun 2012 17:55:40 -0600 Subject: [PATCH 29/30] Editor selects text that was typed at a tab-stop when shift-tabbing back to it --- spec/extensions/snippets-spec.coffee | 10 +++++----- src/app/anchor-range.coffee | 4 ++-- src/app/anchor.coffee | 9 +++++++-- src/app/edit-session.coffee | 8 ++++---- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 6ec314ed8..12a7bc420 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -59,21 +59,21 @@ describe "Snippets extension", -> expect(buffer.lineForRow(2)).toBe "go here next:() and finally go here:()" expect(buffer.lineForRow(3)).toBe "go here first:()" expect(buffer.lineForRow(4)).toBe " if (items.length <= 1) return items;" - expect(editor.getCursorScreenPosition()).toEqual [3, 15] + expect(editor.getSelectedBufferRange()).toEqual [[3, 15], [3, 15]] editor.trigger keydownEvent('tab', target: editor[0]) - expect(editor.getCursorScreenPosition()).toEqual [2, 14] + expect(editor.getSelectedBufferRange()).toEqual [[2, 14], [2, 14]] editor.insertText 'abc' editor.trigger keydownEvent('tab', target: editor[0]) - expect(editor.getCursorScreenPosition()).toEqual [2, 40] + expect(editor.getSelectedBufferRange()).toEqual [[2, 40], [2, 40]] # tab backwards editor.trigger keydownEvent('tab', shiftKey: true, target: editor[0]) - expect(editor.getCursorScreenPosition()).toEqual [2, 17] + expect(editor.getSelectedBufferRange()).toEqual [[2, 14], [2, 17]] # should highlight text typed at tab stop editor.trigger keydownEvent('tab', shiftKey: true, target: editor[0]) - expect(editor.getCursorScreenPosition()).toEqual [3, 15] + expect(editor.getSelectedBufferRange()).toEqual [[3, 15], [3, 15]] # shift-tab on first tab-stop does nothing editor.trigger keydownEvent('tab', shiftKey: true, target: editor[0]) diff --git a/src/app/anchor-range.coffee b/src/app/anchor-range.coffee index ac236f2cf..a83c49984 100644 --- a/src/app/anchor-range.coffee +++ b/src/app/anchor-range.coffee @@ -7,7 +7,7 @@ class AnchorRange constructor: (@editSession, bufferRange) -> bufferRange = Range.fromObject(bufferRange) - @startAnchor = @editSession.addAnchorAtBufferPosition(bufferRange.start) + @startAnchor = @editSession.addAnchorAtBufferPosition(bufferRange.start, ignoreEqual: true) @endAnchor = @editSession.addAnchorAtBufferPosition(bufferRange.end) getBufferRange: -> @@ -18,4 +18,4 @@ class AnchorRange destroy: -> @startAnchor.destroy() - @endAnchor.destroy() \ No newline at end of file + @endAnchor.destroy() diff --git a/src/app/anchor.coffee b/src/app/anchor.coffee index e8f1e50fd..c5aee8862 100644 --- a/src/app/anchor.coffee +++ b/src/app/anchor.coffee @@ -8,12 +8,17 @@ class Anchor bufferPosition: null screenPosition: null - constructor: (@editSession) -> + constructor: (@editSession, options = {}) -> + { @ignoreEqual } = options handleBufferChange: (e) -> { oldRange, newRange } = e position = @getBufferPosition() - return if position.isLessThan(oldRange.end) + + if @ignoreEqual + return if position.isLessThanOrEqual(oldRange.end) + else + return if position.isLessThan(oldRange.end) newRow = newRange.end.row newColumn = newRange.end.column diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 7d4b5cd30..34bc774d6 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -237,13 +237,13 @@ class EditSession getAnchors: -> new Array(@anchors...) - addAnchor: -> - anchor = new Anchor(this) + addAnchor: (options) -> + anchor = new Anchor(this, options) @anchors.push(anchor) anchor - addAnchorAtBufferPosition: (bufferPosition) -> - anchor = @addAnchor() + addAnchorAtBufferPosition: (bufferPosition, options) -> + anchor = @addAnchor(options) anchor.setBufferPosition(bufferPosition) anchor From 6db42114f9cb42e7092ca656b56c09f8b21543f8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 26 Jun 2012 22:42:06 -0600 Subject: [PATCH 30/30] If the user attempts to switch tab stops while the cursor is not *on* a tab stop, the snippet is terminated --- spec/extensions/snippets-spec.coffee | 22 ++++++++++++++++++++++ src/app/anchor-range.coffee | 3 +++ src/extensions/snippets/snippets.coffee | 19 ++++++++++++++++--- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/spec/extensions/snippets-spec.coffee b/spec/extensions/snippets-spec.coffee index 12a7bc420..2c63ed15c 100644 --- a/spec/extensions/snippets-spec.coffee +++ b/spec/extensions/snippets-spec.coffee @@ -93,6 +93,28 @@ describe "Snippets extension", -> expect(buffer.lineForRow(0)).toBe 'go here first and then here second' expect(editor.getSelectedBufferRange()).toEqual [[0, 8], [0, 13]] + describe "when the cursor is moved beyond the bounds of a tab stop", -> + fit "terminates the snippet on the next tab", -> + editor.setCursorScreenPosition([2, 0]) + editor.insertText('t2') + editor.trigger keydownEvent('tab', target: editor[0]) + + editor.moveCursorRight() + editor.trigger keydownEvent('tab', target: editor[0]) + expect(buffer.lineForRow(3)).toBe "go here first:() " + expect(editor.getCursorBufferPosition()).toEqual [3, 18] + + # test we can terminate with shift-tab + editor.setCursorScreenPosition([4, 0]) + editor.insertText('t2') + editor.trigger keydownEvent('tab', target: editor[0]) + editor.trigger keydownEvent('tab', target: editor[0]) + + editor.moveCursorRight() + editor.trigger keydownEvent('tab', shiftKey: true, target: editor[0]) + expect(editor.getCursorBufferPosition()).toEqual [4, 15] + + describe "when a the start of the snippet is indented", -> it "indents the subsequent lines of the snippet to be even with the start of the first line", -> editor.setCursorScreenPosition([2, Infinity]) diff --git a/src/app/anchor-range.coffee b/src/app/anchor-range.coffee index a83c49984..6340945e7 100644 --- a/src/app/anchor-range.coffee +++ b/src/app/anchor-range.coffee @@ -16,6 +16,9 @@ class AnchorRange getScreenRange: -> new Range(@startAnchor.getScreenPosition(), @endAnchor.getScreenPosition()) + containsBufferPosition: (bufferPosition) -> + @getBufferRange().containsPoint(bufferPosition) + destroy: -> @startAnchor.destroy() @endAnchor.destroy() diff --git a/src/extensions/snippets/snippets.coffee b/src/extensions/snippets/snippets.coffee index c2720d554..92f1ff7c9 100644 --- a/src/extensions/snippets/snippets.coffee +++ b/src/extensions/snippets/snippets.coffee @@ -39,6 +39,7 @@ module.exports = class SnippetsSession tabStopAnchorRanges: null constructor: (@editSession, @snippetsByExtension) -> + @editSession.on 'move-cursor', => @terminateIfCursorIsOutsideTabStops() expandSnippet: -> return unless snippets = @snippetsByExtension[@editSession.buffer.getExtension()] @@ -67,7 +68,7 @@ class SnippetsSession @editSession.buffer.insert([row, 0], initialIndent) goToNextTabStop: -> - return false unless @tabStopAnchorRanges + return false unless @ensureValidTabStops() nextIndex = @tabStopIndex + 1 if nextIndex < @tabStopAnchorRanges.length @setTabStopIndex(nextIndex) @@ -77,12 +78,24 @@ class SnippetsSession false goToPreviousTabStop: -> - return false unless @tabStopAnchorRanges + return false unless @ensureValidTabStops() @setTabStopIndex(@tabStopIndex - 1) if @tabStopIndex > 0 true + ensureValidTabStops: -> + @tabStopAnchorRanges? and @terminateIfCursorIsOutsideTabStops() + setTabStopIndex: (@tabStopIndex) -> @editSession.setSelectedBufferRange(@tabStopAnchorRanges[@tabStopIndex].getBufferRange()) + terminateIfCursorIsOutsideTabStops: -> + return unless @tabStopAnchorRanges + position = @editSession.getCursorBufferPosition() + for anchorRange in @tabStopAnchorRanges + return true if anchorRange.containsBufferPosition(position) + @terminateActiveSnippet() + false + terminateActiveSnippet: -> - anchor.destroy() for anchor in @tabStopAnchorRanges + anchorRange.destroy() for anchorRange in @tabStopAnchorRanges + @tabStopAnchorRanges = null