UNPKG

imba

Version:

Intuitive and powerful language for building webapps that fly

1,431 lines 55.8 kB
import jison from '../../vendor/jison/jison.js';
function iter$(a){ return a ? (a.toArray ? a.toArray() : a) : []; };

// The Imba parser is generated by [Jison](http://github.com/zaach/jison)
// from this grammar file. Jison is a bottom-up parser generator, similar in
// style to [Bison](http://www.gnu.org/software/bison), implemented in JavaScript.
// It can recognize [LALR(1), LR(0), SLR(1), and LR(1)](http://en.wikipedia.org/wiki/LR_grammar)
// type grammars. To create the Jison parser, we list the pattern to match
// on the left-hand side, and the action to take (usually the creation of syntax
// tree nodes) on the right. As the parser runs, it
// shifts tokens from our token stream, from left to right, and
// [attempts to match](http://en.wikipedia.org/wiki/Bottom-up_parsing)
// the token sequence against the rules below. When a match can be made, it
// reduces into the [nonterminal](http://en.wikipedia.org/wiki/Terminal_and_nonterminal_symbols)
// (the enclosing name at the top), and we proceed from there.
//
// If you run the `cake build:parser` command, Jison constructs a parse table
// from our rules and saves it into `lib/parser.js`.

// The only dependency is on the **Jison.Parser**.

var Parser = jison.Parser;

// Jison DSL
// ---------

// 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.
var 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.

var o = function(patternString,action,options) {
	var match;
	patternString = patternString.replace(/\s{2,}/g,' ');
	var patternCount = patternString.split(' ').length;
	
	if (!action) { return [patternString,'$$ = $1;',options] };
	
	if (match = unwrap.exec(action)) {
		action = match[1];
	} else {
		action = ("(" + action + "())");
	};
	
	action = action.replace(/\bA(\d+)/g,'$$$1');
	action = action.replace(/\bnew /g,'$&yy.');
	action = action.replace(/\b(?:Block\.wrap|extend)\b/g,'yy.$&');
	action = action.replace(/\bAST\b/g,'yy');
	
	// really?
	// # should we always add locdata? does not work when statement)!
	// return [patternString, "$$ = #{loc(1, patternCount)}(#{action});", options]
	return [patternString,("$$ = " + action + ";"),options];
};

// Grammatical Rules
// -----------------

// In all of the rules that follow, you'll see the name of the nonterminal as
// the key to a list of alternative matches. With each match's action, the
// dollar-sign variables are provided by Jison as references to the value of
// their numeric position, so in this rule:
//
//     "Expression UNLESS Expression"
//
// `A1` would be the value of the first `Expression`, `A2` would be the token
// for the `UNLESS` terminal, and `A3` would be the value of the second
// `Expression`.
var grammar = {
	
	// The **Root** is the top-level node in the syntax tree. Since we parse bottom-up,
	// all parsing must end here.
	Root: [
		o('',function() { return new Root([]); }),
		o('Body',function() { return new Root(A1); }),
		o('Block TERMINATOR')
	],
	
	// Any list of statements and expressions, separated by line breaks or semicolons.
	Body: [
		o('BODYSTART',function() { return new Block([]); }),
		o('Line',function() { return new Block([]).add(A1); }),
		// o 'HEADER Line' do Block.new([A2])
		// o 'LeadingTerminator' do Block.new([Terminator.new(A1)])
		o('Body Terminator Line',function() { return A1.break(A2).add(A3); }), // A3.prebreak(A2) # why not add as real nodes?!
		o('Body Terminator',function() { return A1.break(A2); })
	],
	
	Terminator: [
		o('TERMINATOR',function() { return new Terminator(A1); })
	],
	
	Type: [
		o('TYPE',function() { return new TypeAnnotation(A1); }),
		o('GENERICS',function() { return new Generics(A1); })
	],
	
	// An indented block of expressions. Note that the [Rewriter](rewriter.html)
	// will convert some postfix forms into blocks for us, by adjusting the
	// token stream.
	Block: [
		o('EMPTY_BLOCK',function() { return new Block([]); }),
		o('INDENT OUTDENT',function() { return new Block([]).indented(A1,A2); }),
		o('INDENT Body OUTDENT',function() { return A2.indented(A1,A3); }),
		// hacky way to support terminators at the start of blocks
		o('INDENT TERMINATOR Body OUTDENT',function() { return A3.prebreak(A2).indented(A1,A4); }),
		o('INDENT TERMINATOR OUTDENT',function() { return new Block([]).indented(A1,A3); })
	],
	
	// Block and statements, which make up a line in a body.
	Line: [
		o('CSSDeclaration'),
		o('Expression'),
		// ?
		o('VarDecl',function() { return A1.option('block',true); }),
		o('Comment'),
		o('Statement'),
		o('Decorators'),
		
		o('ImportDeclaration'),
		o('ExportDeclaration'),
		o('GLOBAL Line',function() { return AST.GLOBAL(A2,A1); }),
		o('DECLARE Line',function() { return AST.DECLARE(A2,A1); })
	// A2.set(declareOnly: A1)
	],
	
	
	
	
	// Pure statements which cannot be expressions.
	Statement: [
		o('Return'),
		o('Yield'),
		o('Throw'),
		o('STATEMENT',function() { return new Literal(A1); }),
		
		o('BREAK',function() { return new BreakStatement(A1); }),
		o('BREAK CALL_START Expression CALL_END',function() { return new BreakStatement(A1,A3); }),
		
		o('CONTINUE',function() { return new ContinueStatement(A1); }),
		o('CONTINUE CALL_START Expression CALL_END',function() { return new ContinueStatement(A1,A3); }),
		
		o('DEBUGGER',function() { return new DebuggerStatement(A1); })
	],
	
	ExtendObject: [
		// o 'CLASS ClassName ClassBody' do ClassDeclaration.new(A2, null, A3).set(keyword: A1)
		o('EXTEND Identifier ClassBody',function() {
			return new ExtendDeclaration(A2,null,A3).set({instanceOnly: true,extension: A1});
		})
	],
	
	ExportDeclaration: [
		o('ENV_FLAG ExportDeclaration',function() {
			return A2.addEnv(A1);
		}),
		o('EXPORT { ImportSpecifierList }',function() {
			return new ExportNamedDeclaration(A1,[A3]).setEnds(A1,A4);
		}),
		o('EXPORT { ImportSpecifierList } FROM String',function() {
			return new ExportNamedDeclaration(A1,[A3],A6).setEnds(A1,A6);
		}),
		o('EXPORT EXPORT_ALL FROM String',function() {
			return new ExportAllDeclaration(A1,[new ExportAllSpecifier(A2)],A4).setEnds(A1,A4);
		}),
		o('EXPORT EXPORT_ALL AS Identifier FROM String',function() {
			return new ExportAllDeclaration(A1,[new ExportAllSpecifier(A2,A4)],A6).setEnds(A1,A6);
		}),
		o('EXPORT Exportable',function() {
			return new Export(A2).set({keyword: A1}).setEnds(A1,A2);
		}),
		o('EXPORT DEFAULT DefaultExportable',function() {
			return new Export(A3).set({keyword: A1,'default': A2}).setEnds(A1,A3);
		})
	],
	
	Exportable: [
		o('MethodDeclaration'),
		o('Class'),
		o('CSSDeclaration'),
		o('TagDeclaration'),
		o('VarAssign'),
		o('GLOBAL Exportable',function() { return AST.GLOBAL(A2,A1); })
	],
	
	DefaultExportable: [
		o('Expression'),
		o('GLOBAL Exportable',function() { return AST.GLOBAL(A2,A1); })
	],
	
	ImportOrExport: [
		o('IMPORT'),
		o('EXPORT')
	],
	
	ImportDefaultSpecifier: [
		o('Identifier',function() { return new ImportDefaultSpecifier(A1); })
	],
	
	ImportDeclaration: [
		o('ENV_FLAG ImportDeclaration',function() {
			return A2.addEnv(A1);
		}),
		o('IMPORT String',function() {
			return new ImportDeclaration(A1,null,A2);
		}),
		o('IMPORT ImportDefaultSpecifier FROM String',function() {
			return new ImportDeclaration(A1,[A2],A4);
		}),
		o('IMPORT TYPEIMPORT ImportDefaultSpecifier FROM String',function() {
			return new ImportTypeDeclaration(A1,[A3],A5);
		}),
		o('IMPORT ImportNamespaceSpecifier FROM String',function() {
			return new ImportDeclaration(A1,[A2],A4);
		}),
		o('IMPORT { } FROM String',function() {
			return new ImportDeclaration(A1,null,A5);
		}),
		o('IMPORT { ImportSpecifierList } FROM String',function() {
			return new ImportDeclaration(A1,[A3],A6);
		}),
		o('IMPORT TYPEIMPORT { ImportSpecifierList } FROM String',function() {
			return new ImportTypeDeclaration(A1,[A4],A7);
		}),
		o('IMPORT ImportDefaultSpecifier IMPORT_COMMA ImportNamespaceSpecifier FROM String',function() {
			return new ImportDeclaration(A1,[A2,A4],A6);
		}),
		o('IMPORT ImportDefaultSpecifier IMPORT_COMMA { ImportSpecifierList } FROM String',function() {
			return new ImportDeclaration(A1,[A2,A5],A8);
		})
	],
	
	ImportFrom: [
		o('STRING')
	],
	
	ImportNamespaceSpecifier: [
		o('IMPORT_ALL AS Identifier',function() { return new ImportNamespaceSpecifier(new Literal(A1),A3); })
	],
	
	ImportSpecifierList: [
		o('ImportSpecifier',function() { return new ESMSpecifierList([]).add(A1); }),
		o('ImportSpecifierList , ImportSpecifier',function() { return A1.add(A3); }),
		o('ImportSpecifierList OptComma TERMINATOR ImportSpecifier',function() { return A1.add(A4); }),
		o('INDENT ImportSpecifierList OptComma OUTDENT',function() { return A2; }),
		o('INDENT ImportSpecifierList OptComma TERMINATOR OUTDENT',function() { return A2; }),
		o('ImportSpecifierList OptComma INDENT ImportSpecifierList OptComma OUTDENT',function() { return A1.concat(A4); })
	],
	
	ImportSpecifier: [
		o('Identifier',function() { return new ImportSpecifier(A1); }),
		o('DecoratorIdentifier',function() { return new ImportSpecifier(A1); }),
		o('StyleMixinIdentifier',function() { return new ImportSpecifier(A1); }),
		o('Identifier AS Identifier',function() { return new ImportSpecifier(A1,A3); }),
		o('DEFAULT',function() { return new ImportSpecifier(new Literal(A1)); }),
		o('DEFAULT AS Identifier',function() { return new ImportSpecifier(new Literal(A1),A3); })
	],
	
	Require: [
		o('REQUIRE RequireArg',function() { return new Require(A2).set({keyword: A1}); })
	],
	
	RequireArg: [
		o('Literal'),
		o('Parenthetical'),
		o('')
	],
	
	// All the different types of expressions in our language. The basic unit of
	// Imba is the **Expression** -- everything that can be an expression
	// is one. Blocks serve as the building blocks of many other rules, making
	// them somewhat circular.
	Expression: [
		o('Await'),
		o('Value'),
		o('Code'),
		o('Operation'),
		o('Assign'),
		o('If'),
		o('Ternary'),
		o('Try'),
		o('While'),
		o('For'),
		o('Switch'),
		o('ExtendObject'),
		o('Class'),
		o('TagDeclaration'),
		o('Tag')
	],
	
	ExpressionBlock: [
		o('Expression'),
		o('INDENT ExpressionBlock Outdent',function() { return A2.indented(A1,A3); })
	],
	
	// A literal identifier, a variable name or property.
	Identifier: [
		o('IDENTIFIER',function() { return new Identifier(A1); })
	],
	
	SymbolIdentifier: [
		o('SYMBOLID',function() { return new SymbolIdentifier(A1); }),
		o('# Interpolation',function() { return new InterpolatedSymbolIdentifier(A1,A2); })
	],
	
	DecoratorIdentifier: [
		o('DECORATOR',function() { return new DecoratorIdentifier(A1); })
	],
	
	StyleMixinIdentifier: [
		o('CSS_MIXIN',function() { return new MixinIdentifier(A1); })
	],
	
	Key: [
		o('KEY',function() { return new Identifier(A1); })
	],
	
	Argvar: [
		o('ARGVAR',function() { return new Argvar(A1).setEnds(A1,A1); })
	],
	
	Symbol: [
		o('SYMBOL',function() { return new Symbol(A1); })
	],
	
	Decorator: [
		o('DecoratorIdentifier',function() { return new Decorator(A1); }), // kinda hacky, should be defined as something else
		o('DecoratorIdentifier Arguments',function() { return new Decorator(A1).set({params: A2}); }),
		// o 'DecoratorIdentifier ( ArgList )' do Decorator.new(A1).set(params: A3)
		o('Decorator . Identifier',function() { return A1.add(A3); })
	],
	
	Decorators: [
		o('Decorator',function() { return [A1]; }),
		o('Decorators Decorator',function() { return A1.concat(A2); })
	],
	
	// Alphanumerics are separated from the other **Literal** matchers because
	// they can also serve as keys in object literals.
	AlphaNumeric: [
		o('NUMBER UNIT',function() { return new NumWithUnit(A1,A2); }),
		o('NUMBER',function() { return new Num(A1); }),
		o('STRING',function() { return new Str(A1); }),
		o('Symbol'),
		o('InterpolatedString')
	],
	
	String: [
		o('STRING',function() { return new Str(A1); })
	],
	
	InterpolatedString: [
		o('STRING_START',function() { return new InterpolatedString([],{open: A1}); }),
		o('InterpolatedString NEOSTRING',function() { return A1.add(A2); }),
		o('InterpolatedString Interpolation',function() { return A2 ? A1.add(A2) : A1; }),
		o('InterpolatedString STRING_END',function() { return A1.option('close',A2); })
	],
	
	// The list of arguments to a function call.
	Interpolation: [
		o('{{ }}',function() { return null; }),
		o('{{ Expression }}',function() { return A2; })
	],
	
	// All of our immediate values. Generally these can be passed straight
	// through and printed to JavaScript.
	Literal: [
		o('AlphaNumeric'),
		o('JS',function() { return new Literal(A1); }),
		o('REGEX',function() { return new RegExp(A1); }),
		o('BOOL',function() { return new Bool(A1); }),
		o('TRUE',function() { return new True(A1); }), // AST.TRUE # should not cheat like this
		o('FALSE',function() { return new False(A1); }), // AST.FALSE
		o('NULL',function() { return new Nil(A1); }), // AST.NIL
		o('UNDEFINED',function() { return new Undefined(A1); }) // AST.UNDEFINED
	// we loose locations for these
	],
	
	// A return statement from a function body.
	Return: [
		o('RETURN Expression',function() { return new Return(A2).set({keyword: A1}); }),
		o('RETURN Arguments',function() { return new Return(A2).set({keyword: A1}); }), // should probably force as array
		o('RETURN',function() { return new Return().set({keyword: A1}); })
	],
	
	Yield: [
		o('YIELD Expression',function() { return new Yield(A2).set({keyword: A1}); }),
		o('YIELD Arguments',function() { return new Yield(A2).set({keyword: A1}); }), // should probably force as array
		o('YIELD',function() { return new Yield().set({keyword: A1}); })
	],
	
	Selector: [
		o('SELECTOR_START',function() { return new Selector([],{type: A1,open: A1}); }),
		o('Selector SELECTOR_PART',function() { return A1.add(A2); }),
		o('Selector { Expression }',function() { return A1.add(A3); }),
		o('Selector SELECTOR_END',function() { return A1.option('close',A2); })
	],
	
	Tag: [
		o('TAG_START TagOptions TAG_END',function() { return A2.setEnds({open: A1,close: A3}).setEnds(A1,A3); }),
		o('TAG_START TagOptions TAG_END TagBody',function() { return A2.set({body: A4,open: A1,close: A3}); })
	],
	
	TagTypeName: [
		o('Self',function() { return A1; }),
		o('IDENTIFIER',function() { return new TagTypeIdentifier(A1); }),
		o('TAG_TYPE',function() { return new TagTypeIdentifier(A1); }),
		o('TagIdentifier',function() { return new ExpressionNode(A1); }),
		o('',function() { return new TagTypeIdentifier('div'); })
	],
	
	StyleBlockDeclaration: [
		o('CSS CSS_SEL StyleBody CSS_END',function() { return new StyleRuleSet(A2,A3).set({name: A1}); })
	],
	
	CSSDeclaration: [
		// o 'GLOBAL CSS CSS_SEL StyleBody CSS_END' do StyleRuleSet.new(A3,A4).set(global: A1)
		o('StyleBlockDeclaration',function() { return A1.set({toplevel: true}); }),
		// o 'GLOBAL CSSDeclaration' do A2.set(global: A1)
		o('LOCAL CSSDeclaration',function() { return A2.set({local: A1}); })
	],
	
	StyleBlockBody: [
		o('INDENT Terminator OUTDENT',function() { return new StyleBody([]).indented(A1,A3); }),
		o('INDENT StyleBody Outdent',function() { return A2.indented(A1,A3); })
	],
	
	OptStyleBody: [
		o('',function() { return new StyleBody([]); }),
		o('StyleBody')
	],
	
	StyleBody: [
		o('StyleNode',function() { return new StyleBody([A1]); }),
		o('StyleBody StyleDeclaration',function() { return A1.add(A2); }),
		// o 'Style2Body , Style2Node' do A1.add A3
		o('StyleBody Terminator StyleNode',function() { return A1.add(A3); }),
		o('INDENT StyleBody Outdent',function() { return A2.indented(A1,A3); })
	],
	
	StyleNode: [
		o('StyleDeclaration'),
		o('CSS_SEL StyleBlockBody CSS_END',function() { return new StyleRuleSet(A1,A2); })
	],
	
	StyleDeclaration: [
		o('StyleProperty : StyleExpressions',function() { return new StyleDeclaration(A1,A3.set({parens: false})); }),
		o('StyleProperty = StyleExpressions',function() { return new StyleDeclaration(A1,A3.set({parens: false})); })
	],
	
	StyleProperty: [
		o('CSSPROP',function() { return new StyleProperty([A1]); })
	],
	
	StyleOperator: [
		o('MATH'),
		o('+'),
		o('-')
	],
	
	StyleExpressions: [
		o('StyleExpression',function() { return new StyleExpressions([A1]); }),
		o('StyleExpressions , StyleExpression',function() { return A1.add(A3); })
	],
	
	// the values
	StyleExpression: [
		o('StyleTerm',function() { return new StyleExpression().add(A1); }),
		o('StyleExpression StyleOperator',function() { return A1.add(A2); }),
		o('StyleExpression StyleTerm',function() { return A1.add(A2); }),
		o('StyleExpression / StyleTerm',function() { return A1.addParam(A3,A2); })
	],
	
	StyleValue: [
		o('StyleTerm'),
		o('StyleOperation')
	],
	
	StyleOperation: [
		o('StyleTerm StyleOperator StyleTerm',function() { return new StyleOperation([A1,A2,A3]); }),
		o('StyleOperation StyleOperator StyleTerm',function() { return A1.add([A2,A3]); })
	],
	
	StyleFunctionArgs: [
		o('StyleFunctionArg',function() { return new StyleExpressions([A1]); }),
		o('StyleFunctionArgs , StyleFunctionArg',function() { return A1.add(A3); })
	],
	
	StyleFunctionArg: [
		o('StyleTerm',function() { return new StyleExpression().add(A1); }),
		o('StyleFunctionArg StyleOperator',function() { return A1.add(A2); }),
		o('StyleFunctionArg StyleTerm',function() { return A1.add(A2); }),
		o('StyleFunctionArg / StyleTerm',function() { return A1.addParam(A3,A2); })
	],
	
	StyleTermPlaceholder: [
		o('{ Expression }',function() { return new StyleInterpolationExpression(A2).setEnds(A1,A3); }),
		o('StyleTermPlaceholder CSSUNIT',function() { return A1.set({unit: A2}); })
	],
	
	StyleParens: [
		o('( StyleValue )',function() { return new StyleParens(A2).setEnds(A1,A3); }),
		o('StyleParens CSSUNIT',function() { return A1.set({unit: A2}); })
	],
	
	StyleColor: [
		o('COLOR',function() { return new StyleColor(A1); }),
		o('COLORMIX ( StyleFunctionArgs )',function() { return new StyleColorMix(A1,A3.setEnds(A2,A4)).set({params: A3}).setEnds(A1,A4); })
	],
	
	StyleTerm: [
		o('StyleParens'),
		o('CSSVAR',function() { return new StyleVar(A1); }),
		o('DIMENSION',function() { return new StyleDimension(A1); }),
		// o 'COLOR ( StyleFunctionArgs )' do StyleColor.new(A1,A3).set(params: A3)
		o('StyleColor'),
		o('PERCENTAGE',function() { return new StyleDimension(A1); }),
		o('NUMBER',function() { return new StyleNumber(A1); }),
		o('String',function() { return A1; }),
		o('StyleTermPlaceholder',function() { return A1; }),
		o('CSSURL',function() { return new StyleURL(A1); }),
		o('CSSFUNCTION ( StyleFunctionArgs )',function() { return new StyleFunction(A1,A3.setEnds(A2,A4)).setEnds(A1,A4); }),
		o('CSSIDENTIFIER',function() { return new StyleIdentifier(A1); }),
		o('COMPARE StyleTerm',function() { return A2.set({op: A1}); })
	],
	
	TagOptions: [
		o('TagTypeName TAG_REF',function() { return new Tag({type: A1,reference: A2}); }),
		o('TagTypeName',function() { return new Tag({type: A1}); }),
		o('TagOptions TAG_ID',function() { return A1.addPart(A2,AST.TagId); }),
		o('TagOptions TAG_SYMBOL_ID',function() { return A1.addPart(new IdentifierExpression(A2.cloneSlice(1)),AST.TagId); }),
		o('TagOptions SYMBOL_ID',function() { return A1.addPart(new IdentifierExpression(A2.cloneSlice(1)),AST.TagId); }),
		o('TagOptions TAG_FLAG',function() { return A1.addPart(A2,AST.TagFlag); }),
		o('TagOptions TAG_ATTR',function() { return A1.addPart(A2,AST.TagAttr); }),
		o('TagOptions STYLE_START STYLE_END',function() { return A1; }),
		o('TagOptions STYLE_START StyleBody STYLE_END',function() { return A1.addPart(new StyleRuleSet(null,A3),AST.TagFlag); }),
		o('TagOptions T. STYLE_START StyleBody STYLE_END',function() { return A1.addPart(new StyleRuleSet(null,A4),AST.TagFlag); }),
		o('TagOptions CSS_MIXIN',function() { return A1.addPart(new MixinIdentifier(A2),AST.TagFlag); }),
		o('TagOptions T: TagIdentifier',function() { return A1.addPart(A3,AST.TagHandler); }),
		o('TagOptions T@ TagIdentifier',function() { return A1.addPart(A3,AST.TagHandler); }),
		o('TagOptions T. @ TAG_LITERAL',function() { return A1.addPart(A4.prepend('_'),AST.TagFlag); }), // not supported anymore
		o('TagOptions T. UNARY TAG_LITERAL',function() { return A1.addPart(A4.prepend('!'),AST.TagFlag); }),
		o('TagOptions T. TagIdentifier',function() { return A1.addPart(A3,AST.TagFlag); }),
		
		o('TagOptions # TagIdentifier',function() { return A1.addPart(A3,AST.TagId); }),
		o('TagOptions TAG_WS TagIdentifier',function() { return A1.addPart(A2,AST.TagSep).addPart(A3,AST.TagAttr); }),
		o('TagOptions ( )',function() { return A1.addPart(new ArgList([]),AST.TagArgList); }),
		o('TagOptions ( ArgList )',function() { return A1.addPart(A3,AST.TagArgList); }),
		o('TagOptions CALL_START CALL_END',function() { return A1.addPart(null,AST.TagArgList); }),
		o('TagOptions CALL_START ArgList CALL_END',function() { return A1.addPart(A3,AST.TagArgList); }),
		o('TagOptions TAG_WS',function() { return A1.addPart(A2,AST.TagSep); }),
		o('TagOptions Comment',function() { return A1; }),
		o('TagOptions TERMINATOR',function() { return A1; }),
		// o 'TagOptions TagFlag' do A1.addPart(A2)
		o('TagOptions = TagAttrValue',function() { return A1.addPart(A3,AST.TagAttrValue,A2); })
	],
	
	TagIdentifier: [
		o('TAG_LITERAL',function() { return new IdentifierExpression(A1); }),
		o('{ Expression }',function() { return new IdentifierExpression(A2); }),
		o('TagIdentifier TAG_LITERAL',function() { return A1.add(A2); }),
		o('TagIdentifier { Expression }',function() { return A1.add(A3); })
	// o '{ Expression }' do TagIdentifier.new(A2)
	],
	
	TagFlag: [
		o('%',function() { return new TagFlag(); }),
		o('TagFlag TagPartIdentifier',function() { return A1.add(A2); })
	],
	
	TagAttrValue: [
		o('VALUE_START Expression VALUE_END',function() { return A2; })
	],
	
	TagBody: [
		o('INDENT OUTDENT',function() { return new TagBody([]).indented(A1,A2); }),
		o('INDENT TERMINATOR OUTDENT',function() { return new TagBody([]).indented(A1,A3); }),
		o('INDENT TagBodyList OUTDENT',function() { return A2.indented(A1,A3); }),
		o('CALL_START TagBodyList CALL_END',function() { return A2; }),
		o('Tag',function() { return new TagBody([A1]); })
	],
	
	TagBodyList: [
		o('TagBodyItem',function() { return new TagBody([]).add(A1); }),
		o('TagBodyList , TagBodyItem',function() { return A1.add(A3); }),
		o('TagBodyList OptComma Terminator TagBodyItem',function() { return A1.add(A3).add(A4); }),
		o('TagBodyList OptComma TERMINATOR SEPARATOR Terminator TagBodyItem',function() { return A1.add(A5).add(A6); }),
		o('INDENT TagBodyList OptComma Outdent',function() { return A2.indented(A1,A4); }),
		o('TagBodyList OptComma INDENT TagBodyList OptComma Outdent',function() { return A1.concat(A4); })
	],
	
	// Valid arguments are Blocks or Splats.
	TagBodyItem: [
		o('Expression'),
		o('... Expression',function() { return new Splat(A2).set({keyword: A1}); }),
		o('Splat'),
		o('LOGIC'),
		o('Comment'),
		o('StyleBlockDeclaration',function() { return A1.set({inTagTree: true}); })
	],
	
	// Class definitions have optional bodies of prototype property assignments,
	// and optional references to the superclass.
	TagDeclaration: [
		o('TagDeclarationBlock',function() { return A1; }),
		o('EXTEND TagDeclarationBlock',function() { return A2.set({extension: true}); }),
		o('LOCAL TagDeclarationBlock',function() { return A2.set({local: true}); })
	// o 'GLOBAL TagDeclarationBlock' do A2.set(global: A1)
	],
	
	TagDeclarationBlock: [
		o('TAG TagType',function() { return new TagDeclaration(A2).set({keyword: A1}); }),
		o('TAG TagType ClassBody',function() { return new TagDeclaration(A2,null,A3).set({keyword: A1}); }),
		o('TAG TagType COMPARE TagType',function() { return new TagDeclaration(A2,A4).set({keyword: A1,extends: A3}); }),
		o('TAG TagType COMPARE TagType ClassBody',function() { return new TagDeclaration(A2,A4,A5).set({keyword: A1,extends: A3}); })
	],
	
	// Going to move back to fewer custom tokens
	TagType: [
		o('TAG_TYPE',function() { return new TagTypeIdentifier(A1); })
	],
	
	TagId: [
		o('# IDENTIFIER',function() { return new TagIdRef(A2); })
	],
	
	// Assignment of a variable, property, or index to a value.
	Assign: [
		o('VarAssign'),
		o('Assignable = Expression',function() { return new Assign(A2,A1,A3).setEnds(A1,A3); }),
		o('Assignable = INDENT Expression Outdent',function() { return new Assign(A2,A1,A4.indented(A3,A5)); })
	],
	
	// Assignment when it happens within an object literal. The difference from
	// the ordinary **Assign** is that these allow numbers and strings as keys.
	AssignObj: [
		o('... Expression',function() { return new ObjRestAttr(A2).set({spread: A1}); }),
		o('MethodDeclaration',function() { return A1.set({inObject: true}); }),
		o('ObjAssignable',function() { return new ObjAttr(A1); }),
		o('ObjAssignable : Expression',function() { return new ObjAttr(A1,A3); }),
		o('ObjAssignable : INDENT Expression Outdent',function() { return new ObjAttr(A1,A4.indented(A3,A5)); }),
		o('SimpleObjAssignable = Expression',function() { return new ObjAttr(A1,null,A3); }),
		o('SimpleObjAssignable = INDENT Expression Outdent',function() { return new ObjAttr(A1,null,A4.indented(A3,A5)); }),
		o('Comment')
	],
	
	SimpleObjAssignable: [
		o('Identifier'),
		o('Identifier Type',function() { return AST.SETTYPE(A1,A2); }),
		o('SymbolIdentifier'),
		o('Key')
	],
	
	ObjAssignable: [
		o('SimpleObjAssignable'),
		// this is the interpolated string
		o('[ Expression ]',function() { return new IdentifierExpression(A2); }),
		o('( Expression )',function() { return new IdentifierExpression(A2); }),
		o('AlphaNumeric')
	],
	
	// A block comment.
	Comment: [
		o('HERECOMMENT',function() { return new Comment(A1,true); }),
		o('COMMENT',function() { return new Comment(A1,false); })
	],
	
	// The **Code** node is the function literal. It's defined by an indented block
	// of **Block** preceded by a function arrow, with an optional parameter
	// list.
	Code: [
		o('Method'),
		o('Do'),
		o('Begin')
	],
	
	Begin: [
		o('BEGIN Block',function() { return new Begin(A2); })
	],
	
	Do: [
		o('DO Block',function() { return new Lambda([],A2,null,null,{bound: true,keyword: A1}); }),
		o('DO BLOCK_PARAM_START ParamList BLOCK_PARAM_END Block',function() { return new Lambda(A3,A5,null,null,{bound: true,keyword: A1}); })
	],
	
	// FIXME clean up method
	Method: [
		o('MethodDeclaration',function() { return A1; }),
		// o 'GLOBAL MethodDeclaration' do A2.set(global: A1)
		o('STATIC MethodDeclaration',function() { return A2.set({static: A1}); })
	],
	
	MethodDeclaration: [
		o('DEF MethodScope MethodScopeType MethodIdentifier MethodParams MethodBody',function() {
			return new MethodDeclaration(A5,A6,A4,A2,A3).set({def: A1,keyword: A1,datatype: A4.option('datatype')});
		}),
		
		o('DEF MethodIdentifier MethodParams MethodBody',function() {
			return new MethodDeclaration(A3,A4,A2,null).set({def: A1,keyword: A1,datatype: A2.option('datatype')});
		})
	],
	
	MethodParams: [
		o('ParamList',function() { return A1; }),
		o('CALL_START ParamList CALL_END',function() { return A2; })
	],
	
	MethodScopeType: [
		o('.',function() { return {static: true}; }),
		o('#',function() { return {}; })
	],
	
	MethodIdentifier: [
		o('Identifier'),
		o('DecoratorIdentifier'),
		o('SymbolIdentifier',function() { return A1.set({as: 'property'}); }), // InterpolatedIdentifier.new(A1)
		o('[ Expression ]',function() { return new InterpolatedIdentifier(A2); }),
		o('MethodIdentifier Type',function() { return AST.SETTYPE(A1,A2); })
	],
	
	MethodBody: [
		o('DEF_BODY Block',function() { return A2; }),
		o('DEF_BODY DO Block',function() { return A3; }),
		o('DEF_EMPTY',function() { return new Block([]).set({end: A1._loc}); })
	],
	
	// should support much more
	MethodScope: [
		o('MethodIdentifier'),
		o('This'),
		o('Self')
	],
	
	// An optional, trailing comma.
	OptComma: [
		o(''),
		o(',')
	],
	
	OptSemicolon: [
		o(''),
		o(';')
	],
	
	// The list of parameters that a function accepts can be of any length.
	ParamList: [
		o('',function() { return []; }),
		o('Param',function() { return [A1]; }),
		o('ParamList , Param',function() { return A1.concat(A3); })
	],
	
	ParamExpression: [
		o('Value'),
		o('Code'),
		o('Operation'),
		o('Assign'),
		o('Ternary'),
		o('Tag')
	],
	
	ParamValue: [
		o('Expression')
	// o 'Expression'
	// o 'Parenthetical'
	// o 'Operation'
	// o 'Assign'
	// o 'Ternary'
	// o 'Tag'
	],
	
	// A single parameter in a function definition can be ordinary, or a splat
	// that hoovers up the remaining arguments.
	Param: [
		o('Object',function() { return new Param(A1); }),
		o('Array',function() { return new Param(A1); }),
		o('ParamVar'), // do RequiredParam.new A1
		o('... ParamVar',function() { return A2.set({splat: A1}); }),
		// BlockParam.new A2, null, A1
		o('BLOCK_ARG ParamVar',function() { return A2.set({blk: A1}); }),
		// BlockParam.new A2, null, A1
		o('ParamVar = ParamValue',function() { return new Param(A1.value(),A3).set({datatype: A1.option('datatype')}); }),
		o('Object = ParamValue',function() { return new Param(A1,A3); }),
		o('Array = ParamValue',function() { return new Param(A1,A3); }),
		o('...',function() { return new RestParam(A1); })
	],
	
	// Function Parameters
	ParamVar: [
		o('Identifier',function() { return new Param(A1); }),
		o('Identifier Type',function() { return AST.SETTYPE(new Param(A1),A2); })
	],
	
	// A splat that occurs outside of a parameter list.
	Splat: [
		o('SPLAT Expression',function() { return AST.SPLAT(A2); })
	],
	
	GlobalDeclaration: [
		o('GLOBAL GlobalAssignable = Expression',function() { return new GlobalDeclaration(A2,A4).set({keyword: A1,op: A3}); }),
		o('GLOBAL GlobalAssignable',function() { return new GlobalDeclaration(A2).set({keyword: A1}); })
	// o 'GlobalDeclaration = Expression' do A1.set(op: A2, value: A3)
	],
	
	GlobalAssignable: [
		o('Identifier'),
		o('Identifier Type',function() { return AST.SETTYPE(A1,A2); })
	],
	
	
	VarKeyword: [
		o('VAR'),
		o('LET'),
		o('CONST')
	],
	
	VarAssignable: [
		o('Identifier'),
		o('Identifier Type',function() { return AST.SETTYPE(A1,A2); }),
		o('Array'), // all kinds?
		o('Object') // not supported anymore
	],
	
	VarDecl: [
		o('VarKeyword VarAssignable',function() { return new VarReference(A2,A1); }),
		o('STATIC VarDecl',function() { return A2.set({static: A1}); })
	],
	
	VarAssign: [
		o('VarDecl = Expression',function() { return new Assign(A2,A1,A3); }),
		o('VarDecl = INDENT Expression Outdent',function() { return new Assign(A2,A1,A4.indented(A3,A5)); })
	],
	
	// Variables and properties that can be assigned to.
	SimpleAssignable: [
		o('ENV_FLAG',function() { return new EnvFlag(A1); }),
		o('Argvar'),
		o('Self'), // not sure if self should be assignable really
		o('Identifier',function() { return new VarOrAccess(A1); }), // LocalIdentifier.new(A1)
		o('SymbolIdentifier',function() { return new Access('.',null,A1); }), // LocalIdentifier.new(A1)
		o('Access'),
		// FIXME this isn really assignable?
		// o 'Value . NEW' do LegacyNew.new(A1).set(keyword: A3)
		// o 'Value . Super' do SuperAccess.new('.',A1,A3)
		o('SimpleAssignable Type',function() { return AST.SETTYPE(A1,A2); })
	],
	
	Access: [
		o('Value SoakableOp Identifier',function() { return AST.OP(A2,A1,A3); }), // PropertyAccess.new(A2,A1,A3) # TODO change to regular access
		o('Value SoakableOp SymbolIdentifier',function() { return new IndexAccess(A2,A1,A3); }), // AST.OP(A2,A1,A3) # PropertyAccess.new(A2,A1,A3) # TODO change to regular access
		o('Value INDEX_START IndexValue INDEX_END',function() { return new IndexAccess('.',A1,A3.setEnds(A2,A4)); }),
		o('Value ?. [ IndexValue ]',function() { return AST.OP(A2,A1,A4); })
	],
	
	SoakableOp: [
		'.',
		'?.'
	],
	
	Super: [
		o('SUPER',function() { return new Super(A1); })
	],
	
	// Everything that can be assigned to.
	Assignable: [
		o('SimpleAssignable'),
		o('Array'), //  do A1
		o('Object') // not supported anymore
	],
	
	TaggedTemplate: [
		o('SimpleAssignable InterpolatedString',function() { return new TaggedTemplate(A1,A2); }),
		o('SimpleAssignable String',function() { return new TaggedTemplate(A1,A2); })
	],
	
	Await: [
		o('AWAIT Expression',function() { return new Await(A2).set({keyword: A1}); })
	],
	
	// The types of things that can be treated as values -- assigned to, invoked
	// as functions, indexed into, named as a class, etc.
	Value: [
		o('Assignable'),
		o('Super'),
		o('Literal'),
		o('Literal Type',function() { return AST.SETTYPE(A1,A2); }),
		o('Parenthetical'),
		o('Range'),
		o('ARGUMENTS',function() { return AST.ARGUMENTS; }),
		o('This'),
		o('TagId'),
		o('Selector'),
		o('Invocation'),
		o('TaggedTemplate'),
		o('Require'),
		o('AMPER_REF',function() { return new AmperRef(A1); }),
		// TODO Makes no sense that this is not more specific
		o('Value BANG',function() { return new BangCall(A1).set({keyword: A2}); })
	],
	
	
	IndexValue: [
		o('Expression',function() { return new Index(A1); })
	],
	
	// In Imba, an object literal is simply a list of assignments.
	Object: [
		o('{ AssignList OptComma }',function() { return new Obj(A2,A1.generated).setEnds(A1,A4); })
	],
	
	// Assignment of properties within an object literal can be separated by
	// comma, as in JavaScript, or simply by newline.
	AssignList: [
		o('',function() { return new AssignList([]); }),
		o('AssignObj',function() { return new AssignList([A1]); }),
		o('AssignList , AssignObj',function() { return A1.add(A3); }),
		o('AssignList OptComma Terminator AssignObj',function() { return A1.add(A3).add(A4); }), // A4.prebreak(A3)
		o('AssignList OptComma INDENT AssignList OptComma Outdent',function() { return A1.concat(A4.indented(A3,A6)); })
	],
	
	ExpressionList: [
		o('Expression',function() { return new ExpressionList([]).add(A1); }),
		o('ExpressionList , Expression',function() { return A1.add(A3); }),
		o('ExpressionList OptComma Terminator Expression',function() { return A1.add(A3).add(A4); }),
		o('INDENT ExpressionList OptComma Outdent',function() { return A2.indented(A1,A4); }),
		o('ExpressionList OptComma INDENT ExpressionList OptComma Outdent',function() { return A1.concat(A4); })
	],
	
	// Class definitions have optional bodies of prototype property assignments,
	// and optional references to the superclass.
	// might as well handle this in the lexer instead
	
	Class: [
		o('ClassStart',function() { return A1; }),
		o('ABSTRACT ClassStart',function() { return A2.set({abstract: A1}); }),
		o('EXTEND ClassStart',function() { return A2.set({extension: A1}); })
	],
	
	ClassStart: [
		// o 'CLASS ClassName ClassConstructorParams ClassBody' do ClassDeclaration.new(A2, null, A4).set(keyword: A1,params: A3)
		// o 'CLASS ClassName ClassConstructorParams' do ClassDeclaration.new(A2, null, []).set(keyword: A1,params: A3)
		o('CLASS ClassName ClassBody',function() { return new ClassDeclaration(A2,null,A3).set({keyword: A1}); }),
		o('CLASS ClassName',function() { return new ClassDeclaration(A2,null,[]).set({keyword: A1}); }),
		o('CLASS ClassBody',function() { return new ClassDeclaration(null,null,A2).set({keyword: A1}); }),
		// o 'CLASS ClassName ClassConstructorParams COMPARE Expression ClassBody' do ClassDeclaration.new(A2, A5, A6).set(keyword: A1, params: A3)
		// o 'CLASS ClassName ClassConstructorParams COMPARE Expression' do ClassDeclaration.new(A2, A6, []).set(keyword: A1, params: A3)
		o('CLASS ClassName COMPARE Expression',function() { return new ClassDeclaration(A2,A4,[]).set({keyword: A1,extends: A3}); }),
		o('CLASS ClassName COMPARE Expression ClassBody',function() { return new ClassDeclaration(A2,A4,A5).set({keyword: A1,extends: A3}); }),
		o('CLASS COMPARE Expression ClassBody',function() { return new ClassDeclaration(null,A3,A4).set({keyword: A1,extends: A2}); })
	],
	
	Class2: [
		o('CLASS ClassStart',function() { return A2.set({keyword: A1}); }),
		o('ABSTRACT Class',function() { return A2.set({abstract: A1}); }),
		o('EXTEND Class',function() { return A2.set({extension: A1}); })
	],
	
	ClassStart2: [
		// o 'CLASS ClassName ClassConstructorParams ClassBody' do ClassDeclaration.new(A2, null, A4).set(keyword: A1,params: A3)
		// o 'CLASS ClassName ClassConstructorParams' do ClassDeclaration.new(A2, null, []).set(keyword: A1,params: A3)
		o('ClassName ClassBody',function() { return new ClassDeclaration(A1,null,A2); }),
		o('ClassName',function() { return new ClassDeclaration(A1,null,[]); }),
		o('ClassBody',function() { return new ClassDeclaration(null,null,A1); }),
		// o 'CLASS ClassName ClassConstructorParams COMPARE Expression ClassBody' do ClassDeclaration.new(A2, A5, A6).set(keyword: A1, params: A3)
		// o 'CLASS ClassName ClassConstructorParams COMPARE Expression' do ClassDeclaration.new(A2, A6, []).set(keyword: A1, params: A3)
		o('ClassName COMPARE Expression',function() { return new ClassDeclaration(A1,A3,[]).set({extends: A2}); }),
		o('ClassName COMPARE Expression ClassBody',function() { return new ClassDeclaration(A1,A3,A4).set({extends: A2}); }),
		o('COMPARE Expression ClassBody',function() { return new ClassDeclaration(null,A2,A3).set({extends: A1}); })
	],
	
	ClassName: [
		o('DecoratorIdentifier'),
		o('Identifier',function() { return new VarOrAccess(A1); }), // do ClassName.new(A1)
		o('SymbolIdentifier',function() { return new Access('.',null,A1); }), // IndexAccess.new(A2,A1,A3) # AST.OP(A2,A1,A3)
		o('ClassName . Identifier',function() { return AST.OP(A2,A1,A3); }),
		o('ClassName . SymbolIdentifier',function() { return new IndexAccess(A2,A1,A3); }), // AST.OP(A2,A1,A3)
		o('ClassName Type',function() { return AST.SETTYPE(A1,A2); })
	],
	
	ClassBody: [
		o('INDENT OUTDENT',function() { return new ClassBody([]).indented(A1,A2); }),
		o('INDENT ClassBodyBlock OUTDENT',function() { return A2.indented(A1,A3); }),
		o('INDENT TERMINATOR ClassBodyBlock OUTDENT',function() { return A3.prebreak(A2).indented(A1,A4); }),
		o('INDENT TERMINATOR OUTDENT',function() { return new ClassBody([]).indented(A1,A3); })
	],
	
	ClassBodyBlock: [
		o('ClassBodyLine',function() { return new ClassBody([]).add(A1); }),
		o('ClassBodyBlock Terminator ClassBodyLine',function() { return A1.break(A2).add(A3); }),
		o('ClassBodyBlock Terminator',function() { return A1.break(A2); })
	],
	
	ClassBodyLine: [
		o('Decorators'),
		o('ClassDeclLine'),
		o('Decorators ClassDeclLine',function() { return A1.concat([A2]); }),
		o('CSSDeclaration'),
		o('RELATION ClassName',function() { return new ClassRelation(A2).set({keyword: A1}); }),
		o('Comment'),
		o('Tag')
	],
	
	ClassDeclLine: [
		o('STATIC ClassFieldDeclaration',function() { return A2.set({static: A1}); }),
		o('STATIC MethodDeclaration',function() { return A2.set({static: A1}); }),
		o('MethodDeclaration'),
		o('ClassFieldDeclaration'),
		o('PROTECTED ClassDeclLine',function() { return A2.set({protected: A1}); }),
		o('DECLARE ClassDeclLine',function() { return AST.DECLARE(A2,A1); }),
		o('ENV_FLAG ClassDeclLine',function() { return A2.addEnv(A1); })
	],
	
	ClassFieldDeclaration: [
		o('ClassField ClassFieldOp Expression',function() { return A1.set({value: A3,op: A2}); }), // ClassField.new(A1, A3)
		o('ClassField',function() { return A1; }),
		o('ClassFieldDeclaration AS AccessorBody',function() { return A1.set({wrapper: A3}); }),
		o('ClassFieldDeclaration FieldDescriptorFull',function() { return A1.set({wrapper: A2}); })
	],
	
	FieldDescriptor: [
		o('DECORATOR',function() { return new Descriptor(A1); }), // kinda hacky, should be defined as something else
		o('@ CALL_START Expression CALL_END',function() { return new Descriptor(A3); }),
		// o 'FieldDescriptor ( )' do A1.add(ArgList.new([]))
		// o 'FieldDescriptor ( ArgList )' do A1.add(A3,'!')
		o('FieldDescriptor Arguments',function() { return A1.add(A2,'!'); }),
		o('FieldDescriptor INDEX_START ArgList INDEX_END',function() { return A1.add(A3,'='); }),
		o('FieldDescriptor . Identifier',function() { return A1.add(A3); })
	// o 'FieldDescriptor = Value' do A1.add(A3,'value')
	// o 'FieldDescriptor = DescriptorValue' do A1.add(A3,'value')
	],
	
	FieldDescriptorFull: [
		o('FieldDescriptor'),
		o('FieldDescriptor = Expression',function() { return A1.set({'default': A3}); }),
		o('FieldDescriptor Do',function() { return A1.set({callback: A2}); })
	],
	
	AccessorBody: [
		'FieldDescriptorFull',
		'Value'
	],
	
	ClassFieldDecoration: [
		o('ClassFieldDeclaration @ WatchBody',function() { return A1.set({watch: A3}); })
	],
	
	WatchBody: [
		o('Block'),
		o('Expression')
	],
	
	ClassFieldOp: [
		'=',
		'COMPOUND_ASSIGN'
	],
	
	ClassField: [
		o('ClassFieldIdentifier',function() { return new ClassField(A1); }),
		o('PROP ClassFieldIdentifier',function() { return new ClassProperty(A2).set({keyword: A1}); }),
		o('ATTR ClassFieldIdentifier',function() { return new ClassAttribute(A2).set({keyword: A1}); }),
		o('ClassField Type',function() { return AST.SETTYPE(A1,A2); }),
		o('ClassField CALL_START CALL_END',function() { return A1.set({controller: A2}); })
	],
	
	ClassFieldIdentifier: [
		o('Identifier'),
		o('REGEX',function() { return new RegExpIdentifier(A1); }),
		o('SymbolIdentifier')
	],
	
	ClassFieldBody: [
		o('WATCH Expression Terminator',function() { return [A1,A2]; })
	],
	
	// Ordinary function invocation, or a chained series of calls.
	Invocation: [
		o('Value OptFuncExist Arguments',function() { return new Call(A1,A3,A2); }),
		o('Value Do',function() { return A1.addBlock(A2); })
	],
	
	// An optional existence check on a function.
	OptFuncExist: [
		o('',function() { return false; }),
		o('FUNC_EXIST',function() { return true; })
	],
	
	// The list of arguments to a function call.
	Arguments: [
		o('CALL_START CALL_END',function() { return new ArgList([]).setEnds(A1,A2); }),
		o('CALL_START ArgList OptComma CALL_END',function() { return A2.setEnds(A1,A4); })
	],
	
	// A reference to the *this* current object.
	This: [
		o('THIS',function() { return new This(A1); }) // Value.new Literal.new 'this'
	],
	
	Self: [
		o('SELF',function() { return new Self(A1); })
	],
	
	// The array literal.
	Array: [
		o('[ ]',function() { return new Arr(new ArgList([])).setEnds(A1,A2); }),
		o('[ ArgList OptComma ]',function() { return new Arr(A2).setEnds(A1,A4); }),
		o('Array Type',function() { return AST.SETTYPE(A1,A2); })
	],
	
	// Inclusive and exclusive range dots.
	// should return the tokens instead
	RangeDots: [
		o('..',function() { return '..'; }),
		o('...',function() { return '...'; })
	],
	
	Range: [
		o('[ Expression RangeDots Expression ]',function() { return AST.OP(A3,A2,A4); }) // Range.new A2, A4, A3
	],
	
	// The **ArgList** is both the list of objects passed into a function call,
	// as well as the contents of an array literal
	// (i.e. comma-separated expressions). Newlines work as well.
	ArgList: [
		o('Arg',function() { return new ArgList([A1]); }),
		o('ArgList , Arg',function() { return A1.add(A3); }),
		o('ArgList OptComma Terminator Arg',function() { return A1.add(A3).add(A4); }),
		o('ArgList OptComma TERMINATOR SEPARATOR Terminator Arg',function() { return A1.add(A5).add(A6); }),
		o('INDENT ArgList OptComma Outdent',function() { return A2.indented(A1,A4); }),
		o('ArgList OptComma INDENT ArgList OptComma Outdent',function() { return A1.concat(A4); })
	],
	
	Outdent: [
		o('Terminator OUTDENT',function() { return A1; }), // we are going to change how this works
		o('OUTDENT',function() { return A1; })
	],
	
	// Valid arguments are Blocks or Splats.
	Arg: [
		o('Expression'),
		o('... Expression',function() { return new Splat(A2).set({keyword: A1}); }),
		o('Splat'),
		o('DO_PLACEHOLDER',function() { return new DoPlaceholder(A1); }),
		o('Comment')
	],
	
	// Just simple, comma-separated, required arguments (no fancy syntax). We need
	// this to be separate from the **ArgList** for use in **Switch** blocks, where
	// having the newlines wouldn't make sense.
	SimpleArgs: [
		o('Expression'),
		o('SimpleArgs , Expression',function() { return [].concat(A1,A3); })
	],
	
	// The variants of *try/catch/finally* exception handling blocks.
	Try: [
		o('TRY Block',function() { return new Try(A2); }),
		o('TRY Block Catch',function() { return new Try(A2,A3); }),
		o('TRY Block Finally',function() { return new Try(A2,null,A3); }),
		o('TRY Block Catch Finally',function() { return new Try(A2,A3,A4); })
	],
	
	Finally: [
		o('FINALLY Block',function() { return new Finally(A2); })
	],
	
	// A catch clause names its error and runs a block of code.
	Catch: [
		o('CATCH CATCH_VAR Block',function() { return new Catch(A3,A2); }),
		o('CATCH Block',function() { return new Catch(A2,null); })
	// o 'CATCH CATCH_VAR Expression' do Catch.new(A3,A2)
	],
	
	// Throw an exception object.
	Throw: [
		o('THROW Expression',function() { return new Throw(A2); })
	],
	
	// Parenthetical expressions. Note that the **Parenthetical** is a **Value**,
	// not an **Expression**, so if you need to use an expression in a place
	// where only values are accepted, wrapping it in parentheses will always do
	// the trick.
	Parenthetical: [
		o('( ExpressionList )',function() { return new Parens(A2,A1,A3); }),
		o('( ExpressionList ) UNIT',function() { return new ExpressionWithUnit(new Parens(A2,A1,A3),A4); }),
		o('Parenthetical Type',function() { return AST.SETTYPE(A1,A2); })
	// o '( INDENT ExpressionList OUTDENT )' do Parens.new(A3,A1,A5)
	],
	
	// The condition portion of a while loop.
	WhileSource: [
		o('WHILE Expression',function() { return new While(A2,{keyword: A1}); }),
		o('WHILE Expression WHEN Expression',function() { return new While(A2,{guard: A4,keyword: A1}); }),
		o('UNTIL Expression',function() { return new While(A2,{invert: true,keyword: A1}); }),
		o('UNTIL Expression WHEN Expression',function() { return new While(A2,{invert: true,guard: A4,keyword: A1}); })
	],
	
	// The while loop can either be normal, with a block of expressions to execute,
	// or postfix, with a single expression. There is no do..while.
	// should be solved by POST_WHILE instead
	While: [
		o('WhileSource Block',function() { return A1.addBody(A2); }),
		o('Statement  WhileSource',function() { return A2.addBody(Block.wrap([A1])); }),
		o('Expression WhileSource',function() { return A2.addBody(Block.wrap([A1])); }),
		o('Loop',function() { return A1; })
	],
	
	// should deprecate
	Loop: [
		o('LOOP Block',function() { return new While(new Literal('true',{keyword: A1})).addBody(A2); }),
		o('LOOP Expression',function() { return new While(new Literal('true',{keyword: A1})).addBody(Block.wrap([A2])); })
	],
	
	// Array, object, and range comprehensions, at the most generic level.
	// Comprehensions can either be normal, with a block of expressions to execute,
	// or postfix, with a single expression.
	For: [
		o('Statement  ForBody',function() { return A2.addBody([A1]); }),
		o('Expression ForBody',function() { return A2.addBody([A1]); }),
		o('ForBody    Block',function() { return A1.addBody(A2); }),
		o('ForBody    Block ELSE Block',function() { return A1.addBody(A2).addElse(A4); })
	],
	
	ForKeyword: [
		o('FOR'),
		o('POST_FOR')
	],
	
	ForBody: [
		o('ForKeyword Range',function() { return {source: new ValueNode(A2)}; }),
		o('ForStart ForSource',function() { return A2.configure({own: A1.own,await: (A1.await),name: A1[0],index: A1[1],keyword: A1.keyword,params: A1}); })
	],
	
	ForStart: [
		o('ForKeyword ForVariables',function() { return (A2.keyword = A1) && A2; }),
		// should link to the actual keyword instead
		o('ForKeyword AWAIT ForVariables',function() { return (A3.await = A2) && (A3.keyword = A1) && A3; }),
		o('ForKeyword OWN ForVariables',function() { return (A3.own = true) && (A3.keyword = A1) && A3; })
	],
	
	// An array of all accepted values for a variable inside the loop.
	// This enables support for pattern matching.
	ForValue: [
		o('Identifier'),
		o('Identifier Type',function() { return AST.SETTYPE(A1,A2); }),
		o('Array'), // do ValueNode.new A1
		o('Object') // do ValueNode.new A1
	],
	
	// An array or range comprehension has variables for the current element
	// and (optional) reference to the current index. Or, *key, value*, in the case
	// of object comprehensions.
	ForVariables: [
		o('ForValue',function() { return [A1]; }),
		o('ForValue , ForValue',function() { return [A1,A3]; }),
		o('ForValue , ForValue , ForValue',function() { return [A1,A3,A5]; })
	],
	
	// The source of a comprehension is an array or object with an optional guard
	// clause. If it's an array comprehension, you can also choose to step through
	// in fixed-size increments.
	ForSource: [
		o('FORIN Expression',function() { return new ForIn({source: A2}); }),
		o('FOROF Expression',function() { return new ForOf({source: A2,object: true}); }),
		o('FORIN Expression WHEN Expression',function() { return new ForIn({source: A2,guard: A4}); }),
		o('FOROF Expression WHEN Expression',function() { return new ForOf({source: A2,guard: A4,object: true}); }),
		o('FORIN Expression BY Expression',function() { return new ForIn({source: A2,step: A4}); }),
		o('FORIN Expression WHEN Expression BY Expression',function() { return new ForIn({source: A2,guard: A4,step: A6}); }),
		o('FORIN Expression BY Expression WHEN Expression',function() { return new ForIn({source: A2,step: A4,guard: A6}); })
	],
	
	Switch: [
		o('SWITCH Expression INDENT Whens OUTDENT',function() { return new Switch(A2,A4); }),
		o('SWITCH Expression INDENT Whens ELSE Block Outdent',function() { return new Switch(A2,A4,A6); }),
		o('SWITCH INDENT Whens OUTDENT',function() { return new Switch(null,A3); }),
		o('SWITCH INDENT Whens ELSE Block OUTDENT',function() { return new Switch(null,A3,A5); })
	],
	
	Whens: [
		o('When'),
		o('Whens When',function() { return A1.concat(A2); })
	],
	
	// An individual **When** clause, with action.
	When: [
		o('LEADING_WHEN SimpleArgs Block',function() { return [new SwitchCase(A2,A3)]; }),
		o('LEADING_WHEN SimpleArgs Block TERMINATOR',function() { return [new SwitchCase(A2,A3)]; })
	],
	
	// The most basic form of *if* is a condition and an action. The following
	// if-related rules are broken up along these lines in order to avoid
	// ambiguity.
	
	IfBlock: [
		o('IF Expression Block',function() { return new If(A2,A3,{type: A1}); }),
		o('IfBlock ELSE IF Expression Block',function() { return A1.addElse(new If(A4,A5,{type: A3})); }),
		
		// seems like this refers to the wrong blocks no?
		o('IfBlock ELIF Expression Block',function() {
			return A1.addElse(new If(A3,A4,{type: A2}));
		}),
		
		o('IfBlock ELSE Block',function() { return A1.addElse(A3.set({keyword: A2})); })
	],
	
	// The full complement of *if* expressions, including postfix one-liner
	// *if* and *unless*.
	If: [
		o('IfBlock'),
		o('Statement  POST_IF Expression',function() { return new If(A3,new Block([A1]),{type: A2,statement: true}); }),
		o('Expression POST_IF Expression',function() { return new If(A3,new Block([A1]),{type: A2}); }) // , statement: true # why is this a statement?!?
	],
	
	Ternary: [
		o('Expression ? Expression : Expression',function() { return AST.If.ternary(A1,A3,A5); })
	],
	
	// Arithmetic and logical operators, working on one or more operands.
	// Here they are grouped by order of precedence. The actual precedence rules
	// are defined at the bottom of the page. It would be shorter if we could
	// combine most of these rules into a single generic *Operand OpSymbol Operand*
	// -type rule, but in order to make the precedence binding possible, separate
	// rules are necessary.
	Operation: [
		o('NEW Expression',function() { return AST.Instantiation.for(A2,A1).setEnds(A1,A2); }),
		o('UNARY Expression',function() { return AST.OP(A1,A2).setEnds(A1,A2); }),
		o('--- Expression',function() { return AST.OP(A1,A2).setEnds(A1,A2); }),
		o('+++ Expression',function() { return AST.OP(A1,A2).setEnds(A1,A2); }),
		o('- Expression',function() { return AST.OP(A1,A2).setEnds(A1,A2); },{prec: 'UNARY'}),
		o('+ Expression',function() { return AST.OP(A1,A2).setEnds(A1,A2); },{prec: 'UNARY'}),
		o('-- SimpleAssignable',function() { return new UnaryOp(A1,null,A2).setEnds(A1,A2); }),
		o('++ SimpleAssignable',function() { return new UnaryOp(A1,null,A2).setEnds(A1,A2); }),
		o('SimpleAssignable --',function() { return new UnaryOp(A2,A1,null,true).setEnds(A1,A2); }),
		o('SimpleAssignable ++',function() { return new UnaryOp(A2,A1,null,true).setEnds(A1,A2); }),
		
		o('Expression + Expression',function() { return new Op(A2,A1,A3).setEnds(A1,A3); }),
		o('Expression - Expression',function() { return new Op(A2,A1,A3).setEnds(A1,A3); }),
		
		o('Expression EXP      Expression',function() { return AST.OP(A2,A1,A3).setEnds(A1,A3); }),
		o('Expression MATH     Expression',function() { return AST.OP(A2,A1,A3).setEnds(A1,A3); }),
		o('Expression SHIFT    Expression',function() { return AST.OP(A2,A1,A3).setEnds(A1,A3); }),
		o('Expression COMPARE  Expression',function() { return AST.OP(A2,A1,A3).setEnds(A1,A3); }),
		o('Expression LOGIC    Expression',function() { return AST.OP(A2,A1,A3).setEnds(A1,A3); }),
		
		o('Expression NOT RELATION Expression',function() {
			return AST.OP(A3,A1,A4).invert(A2);
		}),
		
		o('Expression RELATION Expression',function() {
			return AST.OP(A2,A1,A3).setEnds(A1,A3);
		}),
		
		o('SimpleAssignable COMPOUND_ASSIGN Expression',function() { return AST.OP(A2,A1,A3); }),
		o('SimpleAssignable COMPOUND_ASSIGN INDENT Expression Outdent',function() { return AST.OP(A2,A1,A4.indented(A3,A5)); })
	]
};

// Precedence
// ----------

var operators = [
	['left','.','?.','::','.:'],
	['left','CALL_START','CALL_END'],
	// ['left',      '{{', '}}']
	// ['left', 'STRING_START','STRING_END']
	['nonassoc','++','--'],
	['right','UNARY','NEW','THROW','NOT'],
	['right','AWAIT'],
	['right','EXP'],
	['left','MATH'],
	['left','+','-','+++','---'],
	['left','SHIFT'],
	['left','RELATION'],
	['left','COMPARE'],
	['left','LOGIC'],
	['right','?'],
	['nonassoc','INDENT','OUTDENT'],
	['right','=',':','COMPOUND_ASSIGN','RETURN','YIELD','THROW','EXTENDS'],
	['right','FORIN','FOROF','BY','WHEN'],
	['right','TAG_END'],
	['right','IF','ELSE','FOR','DO','WHILE','UNTIL','LOOP','SUPER','CLASS','MIXIN','INTERFACE','MODULE','TAG','EVENT','TRIGGER','TAG_END','IMPORT','EXPORT'],
	['right','POST_IF','POST_FOR'],
	['right','NEW_TAG'],
	['right','TAG_ATTR_SET'],
	['right','SPLAT'],
	['left','SELECTOR_START'],
	['left','CSS']
];

// Wrapping Up
// -----------

// Finally, now that we have our **grammar** and our **operators**, we can create
// 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".

var tokens = [];
for (let name in grammar){
	let alternatives;
	alternatives = grammar[name];let res = [];
	for (let i = 0, items = iter$(alternatives), len = items.length, alt; i < len; i++) {
		alt = items[i];
		for (let j = 0, ary = iter$(alt[0].split(' ')), len = ary.length, token; j < len; j++) {
			token = ary[j];
			if (!grammar[token]) { tokens.push(token) };
		};
		if (name === 'Root') { alt[1] = ("return " + (alt[1])) };
		res.push(alt);
	};
	grammar[name] = res;
};

// Initialize the **Parser** with our list of terminal **tokens**, our **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)).

var parser = new Parser(
	{tokens: tokens.join(' '),
	bnf: grammar,
	operators: operators.reverse(),
	startSymbol: 'Root'}
);

export { parser };