From bfc7704ca172c7f2ec25b6dd11f1b4345ae9023d Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 24 Jul 2010 00:31:04 -0700 Subject: [PATCH] first bit of equals for symbology ... barely started on lexer.coffee --- src/cake.coffee | 34 ++++++------ src/coffee-script.coffee | 52 +++++++++--------- src/command.coffee | 112 +++++++++++++++++++-------------------- src/grammar.coffee | 28 +++++----- src/helpers.coffee | 48 ++++++++--------- src/index.coffee | 2 +- src/lexer.coffee | 52 +++++++++--------- 7 files changed, 164 insertions(+), 164 deletions(-) diff --git a/src/cake.coffee b/src/cake.coffee index 3e423fdb..88810bc9 100644 --- a/src/cake.coffee +++ b/src/cake.coffee @@ -7,17 +7,17 @@ # current directory's Cakefile. # External dependencies. -fs: require 'fs' -path: require 'path' -helpers: require('./helpers').helpers -optparse: require './optparse' -CoffeeScript: require './coffee-script' +fs = require 'fs' +path = require 'path' +helpers = require('./helpers').helpers +optparse = require './optparse' +CoffeeScript = require './coffee-script' # Keep track of the list of defined tasks, the accepted options, and so on. -tasks: {} -options: {} -switches: [] -oparse: null +tasks = {} +options = {} +switches = [] +oparse = null # Mixin the top-level Cake functions for Cakefiles to use directly. helpers.extend global, { @@ -25,8 +25,8 @@ helpers.extend global, { # Define a Cake task with a short name, an optional sentence description, # and the function to run as the action itself. task: (name, description, action) -> - [action, description]: [description, action] unless action - tasks[name]: {name, description, action} + [action, description] = [description, action] unless action + tasks[name] = {name, description, action} # Define an option that the Cakefile accepts. The parsed options hash, # containing all of the command-line options passed, will be made available @@ -47,20 +47,20 @@ helpers.extend global, { exports.run: -> path.exists 'Cakefile', (exists) -> throw new Error("Cakefile not found in ${process.cwd()}") unless exists - args: process.argv[2...process.argv.length] + args = process.argv[2...process.argv.length] CoffeeScript.run fs.readFileSync('Cakefile').toString(), {source: 'Cakefile'} - oparse: new optparse.OptionParser switches + oparse = new optparse.OptionParser switches return printTasks() unless args.length - options: oparse.parse(args) + options = oparse.parse(args) invoke arg for arg in options.arguments # Display the list of Cake tasks in a format similar to `rake -T` printTasks: -> puts '' for all name, task of tasks - spaces: 20 - name.length - spaces: if spaces > 0 then (' ' for i in [0..spaces]).join('') else '' - desc: if task.description then "# $task.description" else '' + spaces = 20 - name.length + spaces = if spaces > 0 then (' ' for i in [0..spaces]).join('') else '' + desc = if task.description then "# $task.description" else '' puts "cake $name$spaces $desc" puts oparse.help() if switches.length diff --git a/src/coffee-script.coffee b/src/coffee-script.coffee index fc6f0b69..aabc6b44 100644 --- a/src/coffee-script.coffee +++ b/src/coffee-script.coffee @@ -8,66 +8,66 @@ # Set up dependencies correctly for both the server and the browser. if process? - path: require 'path' - Lexer: require('./lexer').Lexer - parser: require('./parser').parser - helpers: require('./helpers').helpers + path = require 'path' + Lexer = require('./lexer').Lexer + parser = require('./parser').parser + helpers = require('./helpers').helpers helpers.extend global, require './nodes' if require.registerExtension require.registerExtension '.coffee', (content) -> compile content else - this.exports: this.CoffeeScript: {} - Lexer: this.Lexer - parser: this.parser - helpers: this.helpers + this.exports = this.CoffeeScript = {} + Lexer = this.Lexer + parser = this.parser + helpers = this.helpers # The current CoffeeScript version number. -exports.VERSION: '0.7.2' +exports.VERSION = '0.7.2' # Instantiate a Lexer for our use here. -lexer: new Lexer +lexer = new Lexer # Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison # compiler. -exports.compile: compile: (code, options) -> - options: or {} +exports.compile = compile = (code, options) -> + options = or {} try (parser.parse lexer.tokenize code).compile options catch err - err.message: "In $options.source, $err.message" if options.source + err.message = "In $options.source, $err.message" if options.source throw err # Tokenize a string of CoffeeScript code, and return the array of tokens. -exports.tokens: (code) -> +exports.tokens = (code) -> lexer.tokenize code # Tokenize and parse a string of CoffeeScript code, and return the AST. You can # then compile it by calling `.compile()` on the root, or traverse it by using # `.traverse()` with a callback. -exports.nodes: (code) -> +exports.nodes = (code) -> parser.parse lexer.tokenize code # Compile and execute a string of CoffeeScript (on the server), correctly # setting `__filename`, `__dirname`, and relative `require()`. -exports.run: ((code, options) -> - module.filename: __filename: options.source - __dirname: path.dirname(__filename) +exports.run = ((code, options) -> + module.filename = __filename = options.source + __dirname = path.dirname __filename eval exports.compile code, options ) # The real Lexer produces a generic stream of tokens. This object provides a # thin wrapper around it, compatible with the Jison API. We can then pass it # directly as a "Jison lexer". -parser.lexer: { +parser.lexer = { lex: -> - token: @tokens[@pos] or [""] - @pos: + 1 - this.yylineno: token[2] - this.yytext: token[1] + token = @tokens[@pos] or [""] + @pos = + 1 + this.yylineno = token[2] + this.yytext = token[1] token[0] setInput: (tokens) -> - @tokens: tokens - @pos: 0 + @tokens = tokens + @pos = 0 upcomingInput: -> "" } @@ -76,7 +76,7 @@ parser.lexer: { # on page load. Unfortunately, the text contents of remote scripts cannot be # accessed from the browser, so only inline script tags will work. if document? and document.getElementsByTagName - processScripts: -> + processScripts = -> for tag in document.getElementsByTagName('script') when tag.type is 'text/coffeescript' eval exports.compile tag.innerHTML if window.addEventListener diff --git a/src/command.coffee b/src/command.coffee index ca4b2b66..33851daf 100644 --- a/src/command.coffee +++ b/src/command.coffee @@ -5,22 +5,22 @@ # interactive REPL. # External dependencies. -fs: require 'fs' -path: require 'path' -optparse: require './optparse' -CoffeeScript: require './coffee-script' -{spawn, exec}: require('child_process') +fs = require 'fs' +path = require 'path' +optparse = require './optparse' +CoffeeScript = require './coffee-script' +{spawn, exec} = require 'child_process' # The help banner that is printed when `coffee` is called without arguments. -BANNER: ''' +BANNER = ''' coffee compiles CoffeeScript source files into JavaScript. Usage: coffee path/to/script.coffee - ''' + ''' # The list of all the valid option flags that `coffee` knows how to handle. -SWITCHES: [ +SWITCHES = [ ['-c', '--compile', 'compile to JavaScript and save as .js files'] ['-i', '--interactive', 'run an interactive CoffeeScript REPL'] ['-o', '--output [DIR]', 'set the directory for compiled JavaScript'] @@ -37,14 +37,14 @@ SWITCHES: [ ] # Top-level objects shared by all the functions. -options: {} -sources: [] -optionParser: null +options = {} +sources = [] +optionParser = null # Run `coffee` by parsing passed options and determining what action to take. # Many flags cause us to divert before compiling anything. Flags passed after # `--` will be passed verbatim to your script as arguments in `process.argv` -exports.run: -> +exports.run = -> parseOptions() return usage() if options.help return version() if options.version @@ -52,24 +52,24 @@ exports.run: -> return compileStdio() if options.stdio return compileScript 'console', sources[0] if options.eval return require './repl' unless sources.length - separator: sources.indexOf '--' - flags: [] + separator = sources.indexOf '--' + flags = [] if separator >= 0 - flags: sources[(separator + 1)...sources.length] - sources: sources[0...separator] + flags = sources[(separator + 1)...sources.length] + sources = sources[0...separator] if options.run - flags: sources[1..sources.length].concat flags - sources: [sources[0]] - process.ARGV: process.argv: flags + flags = sources[1..sources.length].concat flags + sources = [sources[0]] + process.ARGV = process.argv = flags compileScripts() # Asynchronously read in each CoffeeScript in a list of source files and # compile them. If a directory is passed, recursively compile all # '.coffee' extension source files in it and all subdirectories. -compileScripts: -> +compileScripts = -> for source in sources - base: source - compile: (source, topLevel) -> + base = source + compile = (source, topLevel) -> path.exists source, (exists) -> throw new Error "File not found: $source" unless exists fs.stat source, (err, stats) -> @@ -85,15 +85,15 @@ compileScripts: -> # Compile a single source script, containing the given code, according to the # requested options. If evaluating the script directly sets `__filename`, # `__dirname` and `module.filename` to be correct relative to the script's path. -compileScript: (source, code, base) -> - o: options - codeOpts: compileOptions source +compileScript = (source, code, base) -> + o = options + codeOpts = compileOptions source try if o.tokens then printTokens CoffeeScript.tokens code else if o.nodes then puts CoffeeScript.nodes(code).toString() else if o.run then CoffeeScript.run code, codeOpts else - js: CoffeeScript.compile code, codeOpts + js = CoffeeScript.compile code, codeOpts if o.print then print js else if o.compile then writeJs source, js, base else if o.lint then lint js @@ -103,18 +103,18 @@ compileScript: (source, code, base) -> # Attach the appropriate listeners to compile scripts incoming over **stdin**, # and write them back to **stdout**. -compileStdio: -> - code: '' - stdin: process.openStdin() +compileStdio = -> + code = '' + stdin = process.openStdin() stdin.on 'data', (buffer) -> - code: + buffer.toString() if buffer + code = + buffer.toString() if buffer stdin.on 'end', -> compileScript 'stdio', code # Watch a source CoffeeScript file using `fs.watchFile`, recompiling it every # time the file is updated. May be used in combination with other options, # such as `--lint` or `--print`. -watch: (source, base) -> +watch = (source, base) -> fs.watchFile source, {persistent: true, interval: 500}, (curr, prev) -> return if curr.mtime.getTime() is prev.mtime.getTime() fs.readFile source, (err, code) -> compileScript(source, code.toString(), base) @@ -122,13 +122,13 @@ watch: (source, base) -> # Write out a JavaScript source file with the compiled code. By default, files # are written out in `cwd` as `.js` files with the same name, but the output # directory can be customized with `--output`. -writeJs: (source, js, base) -> - filename: path.basename(source, path.extname(source)) + '.js' - srcDir: path.dirname source - baseDir: srcDir.substring base.length - dir: if options.output then path.join options.output, baseDir else srcDir - jsPath: path.join dir, filename - compile: -> +writeJs = (source, js, base) -> + filename = path.basename(source, path.extname(source)) + '.js' + srcDir = path.dirname source + baseDir = srcDir.substring base.length + dir = if options.output then path.join options.output, baseDir else srcDir + jsPath = path.join dir, filename + compile = -> fs.writeFile jsPath, js, (err) -> puts "Compiled $source" if options.compile and options.watch path.exists dir, (exists) -> @@ -136,43 +136,43 @@ writeJs: (source, js, base) -> # Pipe compiled JS through JSLint (requires a working `jsl` command), printing # any errors or warnings that arise. -lint: (js) -> - printIt: (buffer) -> print buffer.toString() - jsl: spawn 'jsl', ['-nologo', '-stdin'] +lint = (js) -> + printIt = (buffer) -> print buffer.toString() + jsl = spawn 'jsl', ['-nologo', '-stdin'] jsl.stdout.on 'data', printIt jsl.stderr.on 'data', printIt jsl.stdin.write js jsl.stdin.end() # Pretty-print a stream of tokens. -printTokens: (tokens) -> - strings: for token in tokens - [tag, value]: [token[0], token[1].toString().replace(/\n/, '\\n')] +printTokens = (tokens) -> + strings = for token in tokens + [tag, value] = [token[0], token[1].toString().replace(/\n/, '\\n')] "[$tag $value]" puts strings.join(' ') # Use the [OptionParser module](optparse.html) to extract all options from # `process.argv` that are specified in `SWITCHES`. -parseOptions: -> - optionParser: new optparse.OptionParser SWITCHES, BANNER - o: options: optionParser.parse(process.argv[2...process.argv.length]) - options.compile: or !!o.output - options.run: not (o.compile or o.print or o.lint) - options.print: !! (o.print or (o.eval or o.stdio and o.compile)) - sources: options.arguments +parseOptions = -> + optionParser = new optparse.OptionParser SWITCHES, BANNER + o = options = optionParser.parse(process.argv[2...process.argv.length]) + options.compile = or !!o.output + options.run = not (o.compile or o.print or o.lint) + options.print = !! (o.print or (o.eval or o.stdio and o.compile)) + sources = options.arguments # The compile-time options to pass to the CoffeeScript compiler. -compileOptions: (source) -> - o: {source} - o.noWrap: options['no-wrap'] +compileOptions = (source) -> + o = {source} + o.noWrap = options['no-wrap'] o # Print the `--help` usage message and exit. -usage: -> +usage = -> puts optionParser.help() process.exit 0 # Print the `--version` message and exit. -version: -> +version = -> puts "CoffeeScript version $CoffeeScript.VERSION" process.exit 0 diff --git a/src/grammar.coffee b/src/grammar.coffee index 29223115..4d2930f7 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -15,7 +15,7 @@ # from our rules and saves it into `lib/parser.js`. # The only dependency is on the **Jison.Parser**. -Parser: require('jison').Parser +Parser = require('jison').Parser # Jison DSL # --------- @@ -23,16 +23,16 @@ Parser: require('jison').Parser # Since we're going to be wrapped in a function by Jison in any case, if our # action immediately returns a value, we can optimize by removing the function # wrapper and just returning the value directly. -unwrap: /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/ +unwrap = /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/ # Our handy DSL for Jison grammar generation, thanks to # [Tim Caswell](http://github.com/creationix). For every rule in the grammar, # we pass the pattern-defining string, the action to run, and extra options, # optionally. If no action is specified, we simply pass the value of the # previous nonterminal. -o: (patternString, action, options) -> +o = (patternString, action, options) -> return [patternString, '$$ = $1;', options] unless action - action: if match: (action + '').match(unwrap) then match[1] else "($action())" + action = if match = (action + '').match(unwrap) then match[1] else "($action())" [patternString, "$$ = $action;", options] # Grammatical Rules @@ -48,7 +48,7 @@ o: (patternString, action, options) -> # `$1` would be the value of the first `Expression`, `$2` would be the token # for the `UNLESS` terminal, and `$3` would be the value of the second # `Expression`. -grammar: { +grammar = { # The **Root** is the top-level node in the syntax tree. Since we parse bottom-up, # all parsing must end here. @@ -248,8 +248,8 @@ grammar: { # Indexing into an object or array using bracket notation. Index: [ o "INDEX_START Expression INDEX_END", -> new IndexNode $2 - o "INDEX_SOAK Index", -> $2.soakNode: yes; $2 - o "INDEX_PROTO Index", -> $2.proto: yes; $2 + o "INDEX_SOAK Index", -> $2.soakNode = yes; $2 + o "INDEX_PROTO Index", -> $2.proto = yes; $2 ] # In CoffeeScript, an object literal is simply a list of assignments. @@ -420,14 +420,14 @@ grammar: { # Comprehensions can either be normal, with a block of expressions to execute, # or postfix, with a single expression. For: [ - o "Statement ForStart ForSource", -> $3.raw: $2.raw; new ForNode $1, $3, $2[0], $2[1] - o "Expression ForStart ForSource", -> $3.raw: $2.raw; new ForNode $1, $3, $2[0], $2[1] - o "ForStart ForSource Block", -> $2.raw: $1.raw; new ForNode $3, $2, $1[0], $1[1] + o "Statement ForStart ForSource", -> $3.raw = $2.raw; new ForNode $1, $3, $2[0], $2[1] + o "Expression ForStart ForSource", -> $3.raw = $2.raw; new ForNode $1, $3, $2[0], $2[1] + o "ForStart ForSource Block", -> $2.raw = $1.raw; new ForNode $3, $2, $1[0], $1[1] ] ForStart: [ o "FOR ForVariables", -> $2 - o "FOR ALL ForVariables", -> $3.raw: true; $3 + o "FOR ALL ForVariables", -> $3.raw = true; $3 ] # An array of all accepted values for a variable inside the loop. This @@ -604,9 +604,9 @@ operators: [ # our **Jison.Parser**. We do this by processing all of our rules, recording all # terminals (every symbol which does not appear as the name of a rule above) # as "tokens". -tokens: [] +tokens = [] for name, alternatives of grammar - grammar[name]: for alt in alternatives + grammar[name] = for alt in alternatives for token in alt[0].split ' ' tokens.push token unless grammar[token] alt[1] = "return ${alt[1]}" if name is 'Root' @@ -616,7 +616,7 @@ for name, alternatives of grammar # rules, and the name of the root. Reverse the operators because Jison orders # precedence from low to high, and we have it high to low # (as in [Yacc](http://dinosaur.compilertools.net/yacc/index.html)). -exports.parser: new Parser { +exports.parser = new Parser { tokens: tokens.join ' ' bnf: grammar operators: operators.reverse() diff --git a/src/helpers.coffee b/src/helpers.coffee index 27c0f3c0..4f41da64 100644 --- a/src/helpers.coffee +++ b/src/helpers.coffee @@ -3,11 +3,11 @@ # arrays, count characters, that sort of thing. # Set up exported variables for both **Node.js** and the browser. -this.exports: this unless process? -helpers: exports.helpers: {} +this.exports = this unless process? +helpers = exports.helpers = {} # Cross-browser indexOf, so that IE can join the party. -helpers.indexOf: indexOf: (array, item, from) -> +helpers.indexOf = indexOf = (array, item, from) -> return array.indexOf item, from if array.indexOf for other, index in array if other is item and (not from or (from <= index)) @@ -15,55 +15,55 @@ helpers.indexOf: indexOf: (array, item, from) -> -1 # Does a list include a value? -helpers.include: include: (list, value) -> +helpers.include = include = (list, value) -> indexOf(list, value) >= 0 # Peek at the beginning of a given string to see if it matches a sequence. -helpers.starts: starts: (string, literal, start) -> +helpers.starts = starts = (string, literal, start) -> string.substring(start, (start or 0) + literal.length) is literal # Peek at the end of a given string to see if it matches a sequence. -helpers.ends: ends: (string, literal, back) -> - start: string.length - literal.length - (back ? 0) +helpers.ends = ends = (string, literal, back) -> + start = string.length - literal.length - (back ? 0) string.substring(start, start + literal.length) is literal # Trim out all falsy values from an array. -helpers.compact: compact: (array) -> item for item in array when item +helpers.compact = compact = (array) -> item for item in array when item # Count the number of occurences of a character in a string. -helpers.count: count: (string, letter) -> - num: 0 - pos: indexOf string, letter +helpers.count = count = (string, letter) -> + num = 0 + pos = indexOf string, letter while pos isnt -1 - num: + 1 - pos: indexOf string, letter, pos + 1 + num = + 1 + pos = indexOf string, letter, pos + 1 num # Merge objects, returning a fresh copy with attributes from both sides. # Used every time `BaseNode#compile` is called, to allow properties in the # options hash to propagate down the tree without polluting other branches. -helpers.merge: merge: (options, overrides) -> - fresh: {} - (fresh[key]: val) for all key, val of options - (fresh[key]: val) for all key, val of overrides if overrides +helpers.merge = merge = (options, overrides) -> + fresh = {} + (fresh[key] = val) for all key, val of options + (fresh[key] = val) for all key, val of overrides if overrides fresh # Extend a source object with the properties of another object (shallow copy). # We use this to simulate Node's deprecated `process.mixin` -helpers.extend: extend: (object, properties) -> - (object[key]: val) for all key, val of properties +helpers.extend = extend = (object, properties) -> + (object[key] = val) for all key, val of properties # Return a completely flattened version of an array. Handy for getting a # list of `children` from the nodes. -helpers.flatten: flatten: (array) -> - memo: [] +helpers.flatten = flatten = (array) -> + memo = [] for item in array - if item instanceof Array then memo: memo.concat(item) else memo.push(item) + if item instanceof Array then memo = memo.concat(item) else memo.push(item) memo # Delete a key from an object, returning the value. Useful when a node is # looking for a particular method in an options hash. -helpers.del: del: (obj, key) -> - val: obj[key] +helpers.del = del = (obj, key) -> + val = obj[key] delete obj[key] val diff --git a/src/index.coffee b/src/index.coffee index 7a35a9d8..220f4e27 100644 --- a/src/index.coffee +++ b/src/index.coffee @@ -1,2 +1,2 @@ # Loader for CoffeeScript as a Node.js library. -(exports[key]: val) for key, val of require './coffee-script' \ No newline at end of file +(exports[key] = val) for key, val of require './coffee-script' \ No newline at end of file diff --git a/src/lexer.coffee b/src/lexer.coffee index 80da3fef..af584cf1 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -9,15 +9,15 @@ # Set up the Lexer for both Node.js and the browser, depending on where we are. if process? - {Rewriter}: require('./rewriter') - {helpers}: require('./helpers') + {Rewriter} = require('./rewriter') + {helpers} = require('./helpers') else - this.exports: this - Rewriter: this.Rewriter - helpers: this.helpers + this.exports = this + Rewriter = this.Rewriter + helpers = this.helpers # Import the helpers we need. -{include, count, starts, compact}: helpers +{include, count, starts, compact} = helpers # The Lexer Class # --------------- @@ -25,7 +25,7 @@ else # The Lexer class reads a stream of CoffeeScript and divvys it up into tagged # tokens. Some potential ambiguity in the grammar has been avoided by # pushing some extra smarts into the Lexer. -exports.Lexer: class Lexer +exports.Lexer = class Lexer # **tokenize** is the Lexer's main method. Scan by attempting to match tokens # one at a time, using a regular expression anchored at the start of the @@ -40,17 +40,17 @@ exports.Lexer: class Lexer # Before returning the token stream, run it through the [Rewriter](rewriter.html) # unless explicitly asked not to. tokenize: (code, options) -> - code : code.replace /(\r|\s+$)/g, '' - o : options or {} - @code : code # The remainder of the source code. - @i : 0 # Current character position we're parsing. - @line : o.line or 0 # The current line. - @indent : 0 # The current indentation level. - @outdebt : 0 # The under-outdentation of the last outdent. - @indents : [] # The stack of all current indentation levels. - @tokens : [] # Stream of parsed tokens in the form ['TYPE', value, line] + code = code.replace /(\r|\s+$)/g, '' + o = options or {} + @code = code # The remainder of the source code. + @i = 0 # Current character position we're parsing. + @line = o.line or 0 # The current line. + @indent = 0 # The current indentation level. + @outdebt = 0 # The under-outdentation of the last outdent. + @indents = [] # The stack of all current indentation levels. + @tokens = [] # Stream of parsed tokens in the form ['TYPE', value, line] while @i < @code.length - @chunk: @code.slice @i + @chunk = @code.slice @i @extractNextToken() @closeIndentation() return @tokens if o.rewrite is off @@ -81,17 +81,17 @@ exports.Lexer: class Lexer # referenced as property names here, so you can still do `jQuery.is()` even # though `is` means `===` otherwise. identifierToken: -> - return false unless id: @match IDENTIFIER, 1 - @i: + id.length - forcedIdentifier: @tagAccessor() or @match ASSIGNED, 1 - tag: 'IDENTIFIER' - tag: id.toUpperCase() if include(JS_KEYWORDS, id) or (not forcedIdentifier and include(COFFEE_KEYWORDS, id)) - tag: 'LEADING_WHEN' if tag is 'WHEN' and include LINE_BREAK, @tag() - tag: 'ALL' if id is 'all' and @tag() is 'FOR' + return false unless id = @match IDENTIFIER, 1 + @i = + id.length + forcedIdentifier = @tagAccessor() or @match ASSIGNED, 1 + tag = 'IDENTIFIER' + tag = id.toUpperCase() if include(JS_KEYWORDS, id) or (not forcedIdentifier and include(COFFEE_KEYWORDS, id)) + tag = 'LEADING_WHEN' if tag is 'WHEN' and include LINE_BREAK, @tag() + tag = 'ALL' if id is 'all' and @tag() is 'FOR' if include(JS_FORBIDDEN, id) if forcedIdentifier - tag: 'STRING' - id: "'$id'" + tag = 'STRING' + id = "'$id'" if forcedIdentifier is 'accessor' close_index: true @tokens.pop() if @tag() isnt '@'