import { assert , Block , Choice , Code , Field , FieldID , Head , _Number , stringUnescape , SyntaxError , Text , Token , tokenize , TokenType , Value , Nil , Anything , Record , Workspace , Reference , Do , trap , Call , arrayLast , Try , Statement , With , Base , Entry , _Array , Loop , arrayRemove , MetaID , Character , OptionReference , OnUpdate , Updatable , Version , Container , Item , Selection , Link } from "./exports" ; /**
 * Recursive descent parser.
 *
 * Methods starting with 'parse': A succesful parse returns a defined value and
 * advances the cursor. A failed parse returns undefined, leaves the cursor
 * unchanged, and sets an error. An unrecoverable error throws a SyntaxError
 * exception.
 *
 * Methods starting with 'require' return a value and advance the cursor, or
 * else throw an error.
 *
 * Methods starting with 'match' will return a boolean and advance the cursor on
 * succcess, leaving the cursor unchanged on failure. They do not set an error.
 *
 * Methods starting with 'peek' return a boolean but do not advance the cursor
 * or set an error.
 */ export class Parser { throws constructor ( readonly source ) { this . tokens = tokenize ( this . source ) ; this . tokens . push ( new Token ( 'end' , source . length , source . length , source ) ) ; } private readonly tokens : Token [ ] ; /** advancing cursor in tokens */ private cursor = 0 ; /** token at cursor */ get cursorToken ( ) : Token { return this . tokens [ this . cursor ] ; } /** token before cursor */ get prevToken ( ) : Token { return this . tokens [ this . cursor - 1 ] ; } /** Farthest error so far */ error ? : SyntaxError ; param message param token returns setError ( message , token ? ) { if ( ! token ) { token = this . cursorToken ; } let start = token ? token . start : this . source . length ; if ( this . error && this . error . cursor > start ) { } else { this . error = new SyntaxError ( start , this . source , message ) ; } return this . error ; } space ! : Workspace ; /** require top-level Head definition */ requireHead ( head ) { this . space = head . workspace ; this . requireBlockBody ( head ) ; if ( ! this . peekToken ( 'end' ) ) { throw this . setError ( 'Expecting top-level item definition' ) ; } } /** Require block with curly brackets */ requireBlock < B extends Block > ( block ) { this . requireToken ( '{' ) ; this . requireBlockBody ( block ) ; this . requireToken ( '}' ) ; return block ; } param block requireBlockBody ( block ) { block . outlined = false ; while ( true ) { while ( true ) { if ( this . matchToken ( ',' ) ) continue ; if ( this . matchToken ( ';' ) ) continue ; if ( this . matchToken ( '\n' ) ) { block . outlined = true ; continue ; } break ; } if ( this . peekToken ( '}' , 'end' ) ) { if ( ! block . fields . length ) { throw this . setError ( 'empty block' , this . cursorToken ) ; } return ; } this . requireField ( block ) ; continue ; } } /** Define a field in a block */ requireField ( block ) { const field = block instanceof Code ? new Statement : new Field ; block . add ( field ) ; if ( block instanceof Code ) { assert ( field instanceof Statement ) ; if ( this . parseToken ( 'let' ) ) { field . dataflow = 'let' ; } else if ( this . parseToken ( 'check' ) ) { field . dataflow = 'check' ; if ( ! ( block instanceof Code ) ) { throw this . setError ( 'check only allowed in program blocks' , this . prevToken ) ; } } else if ( this . parseToken ( 'export' ) ) { field . dataflow = 'export' ; if ( ! ( block instanceof Code ) ) { throw this . setError ( 'export only from program blocks' , this . prevToken ) ; } else if ( block instanceof Try ) { throw this . setError ( 'export from Try must be inside clause' , this . prevToken ) ; } if ( this . parseToken ( '(' ) ) { let ref = this . parseReference ( ) ; if ( ! ref ) throw this . setError ( 'reference required' ) ; this . requireToken ( ')' ) ; if ( arrayLast ( ref . tokens ) . text !== '~' ) { throw this . setError ( 'export reference must end with ~' , this . prevToken ) ; } field . setMeta ( '^exportType' , ref ) ; } } } const cursor = this . cursor ; if ( this . parseToken ( 'name' ) && this . parseToken ( '::' , ':' , '=' , '=|>' ) ) { switch ( this . prevToken . type ) { case '::' : field . io = 'data' ; break ; case ':' : if ( this . matchToken ( 'register' ) ) { if ( ! ( block instanceof Code ) ) { throw this . setError ( 'Register only allowed in code' , this . prevToken ) ; } field . io = 'register' ; } else { field . io = 'input' ; } break ; case '=' : field . io = 'output' ; break ; case '=|>' : if ( block instanceof Code ) { throw this . setError ( 'Interface not allowed in code' , this . prevToken ) ; } field . io = 'interface' ; break ; default : trap ( ) ; } this . fieldID ( field , this . tokens [ cursor ] ) ; if ( block instanceof Choice ) { if ( field . io !== 'input' ) { throw this . setError ( 'Option must be an input (:)' , this . prevToken ) ; } field . conditional = true ; } } else { this . cursor = cursor ; field . io = 'output' ; if ( block instanceof Choice ) { throw this . setError ( 'Option must be an input (:)' , this . prevToken ) ; } this . fieldID ( field ) ; } this . requireFormula ( field ) ; return field ; } /** define ID of field. Anonymous if nameToken undefined */ fieldID ( field , nameToken ? ) { if ( ! nameToken ) { field . id = this . space . newFieldID ( undefined , Token . fake ( '=' , this . cursorToken ) ) ; return ; } let block = field . container ; let name = nameToken . text ; if ( name . startsWith ( '^' ) ) { throw this . setError ( 'Cannot define metadata' , nameToken ) ; } if ( name . startsWith ( '~' ) ) { throw this . setError ( 'Cannot define export' , nameToken ) ; } if ( name . endsWith ( '!' ) || name === '?' ) { throw this . setError ( 'Invalid name' , nameToken ) ; } if ( name . endsWith ( '?' ) ) { name = name . slice ( 0 , - 1 ) ; if ( block instanceof Code && ! ( block instanceof Try ) ) { throw this . setError ( "Names in programs can't end with ?" , nameToken ) ; } } else if ( block instanceof Choice ) { throw this . setError ( 'Option names must end in ?' , nameToken ) ; } else if ( block instanceof Try ) { throw this . setError ( 'Clause names must end in ?' , nameToken ) ; } if ( block . items . find ( item => item !== field && item . name === name ) ) { throw this . setError ( 'Duplicate name' , nameToken ) ; } field . id = this . space . newFieldID ( name , nameToken ) ; } /** peek formula terminator */ peekTerminator ( ) { return this . peekToken ( ',' , ';' , ')' , '}' , '\n' , 'end' , '->' ) ; } /** Require a formula defining a field. Sets formulaType and metadata */ requireFormula ( field ) { let startToken = this . cursorToken ; if ( ! this . parseTerm ( field , true ) ) { throw this . setError ( 'expecting a formula' ) ; } let block ; while ( ! this . peekTerminator ( ) ) { if ( ! block ) { block = new Code ; let first = new Statement ; first . id = this . space . newFieldID ( undefined , startToken ) ; if ( field . metadata ) { first . metadata = field . metadata ; field . metadata = undefined ; first . metadata . containingItem = first ; } let value = field . value ; if ( value ) { field . detachValue ( ) ; first . setValue ( value ) ; } block . add ( first ) ; first . formulaType = field . formulaType ; field . formulaType = 'code' ; field . setMeta ( '^code' , block ) ; } let term = new Statement ; term . id = this . space . newFieldID ( undefined , this . cursorToken ) ; block . add ( term ) ; if ( ! this . parseTerm ( term , false ) ) { throw this . setError ( 'expecting a formula' ) ; } continue ; } } /** parse a single term of a formula */ parseTerm ( field , first = false ) { let literal = this . parseLiteral ( ) ; if ( ! literal && this . matchToken ( 'selection' ) ) { literal = new Selection ( ) ; } if ( ! literal && this . matchToken ( 'link' ) ) { literal = new Link ( ) ; } if ( literal ) { if ( ( field . io === 'input' || field . io === 'register' ) && literal instanceof Base && ! ( literal instanceof Nil ) && ! ( literal instanceof Anything ) ) { field . formulaType = 'literal' ; field . setMeta ( '^literal' , literal ) ; } else { field . formulaType = 'none' ; field . setValue ( literal ) ; } if ( literal instanceof Selection ) { this . requireToken ( '{' ) ; if ( this . matchToken ( 'any' ) ) { if ( literal instanceof Link ) { throw this . setError ( 'link cannot be generic' ) ; } let generic = this . parseLiteral ( ) ; if ( ! ( generic instanceof _Array ) ) { throw this . setError ( 'selection any requires an array or table' ) ; } generic . tracked = true ; literal . containingItem . setMeta ( '^any' , generic ) ; literal . generic = true ; literal . tokens = [ generic . token ! ] ; } else { let ref = this . parseReference ( ) ; if ( ! ref || ref . dependent ) { throw this . setError ( 'selection requires a structural array reference' ) ; } literal . tokens = ref . tokens ; } if ( literal instanceof Link ) { this . requireToken ( 'via' ) ; literal . oppositeFieldName = this . requireToken ( 'name' ) ; } this . requireToken ( '}' ) ; } return true ; } if ( this . parseToken ( 'do' ) ) { field . setMeta ( '^code' , this . requireBlock ( new Do ) ) ; field . formulaType = 'code' ; return true ; } if ( this . parseToken ( 'with' ) ) { field . setMeta ( '^code' , this . requireBlock ( new With ) ) ; field . formulaType = 'code' ; return true ; } if ( this . parseToken ( 'extend' ) ) { field . setMeta ( '^extend' , this . requireBlock ( new Record ) ) ; field . formulaType = 'extend' ; let ref = new Reference ; ref . tokens = [ Token . fake ( 'that' , this . cursorToken ) ] ; field . setMeta ( '^reference' , ref ) ; return true ; } if ( this . parseToken ( 'updatable' ) ) { field . setMeta ( '^code' , this . requireBlock ( new Updatable ) ) ; field . formulaType = 'code' ; return true ; } if ( this . parseToken ( 'on-update' ) ) { field . formulaType = 'none' ; if ( field instanceof Statement ) { if ( field . dataflow ) { throw this . setError ( `update block cannot be ` + field . dataflow , this . prevToken ) ; } field . dataflow = 'on-update' ; } else { throw this . setError ( `update block must be in code block` , this . prevToken ) ; } let update = this . requireBlock ( new OnUpdate ) ; field . setValue ( update ) ; this . injectInput ( update , 'change: that' ) ; return true ; } if ( this . parseToken ( 'try' ) ) { field . setMeta ( '^code' , this . requireTry ( ) ) ; field . formulaType = 'code' ; return true ; } let loop = this . parseLoop ( ) ; if ( loop ) { field . formulaType = 'loop' ; field . setMeta ( '^loop' , loop ) ; return true ; } let ref = this . parseReference ( ) ; if ( ref ) { if ( this . matchToken ( ':=' ) ) { if ( ! ref . dependent ) { throw this . setError ( `expecting dot-path` , ref . tokens [ 0 ] ) ; } field . formulaType = 'update' ; field . setMeta ( '^target' , ref ) ; let payload = field . setMeta ( '^payload' , undefined ) ; this . requireFormula ( payload ) ; return true ; } if ( this . cursorToken . text . startsWith ( '::' ) ) { if ( ! ( field instanceof Version ) ) { throw this . setError ( `edits only allowed on versions` , this . cursorToken ) ; } if ( ! ref . dependent ) { throw this . setError ( `expecting dot-path` , ref . tokens [ 0 ] ) ; } field . setMeta ( '^target' , ref ) ; this . requireEdit ( field ) ; return true ; } let call = this . parseCall ( ref , first ) ; if ( call ) { field . formulaType = 'call' ; field . setMeta ( '^call' , call ) ; return true ; } else if ( ! first && ! ref . dependent ) { throw this . setError ( 'expecting call argument' ) ; } if ( arrayLast ( ref . tokens ) . isOperator ) { throw this . setError ( 'using operator as a value' ) ; } field . formulaType = 'reference' ; field . setMeta ( '^reference' , ref ) ; return true ; } if ( this . matchToken ( '#' ) ) { field . formulaType = 'choose' ; let optionRef = new OptionReference ; optionRef . tokens = [ Token . fake ( 'that' , this . cursorToken ) ] ; optionRef . optionToken = this . requireToken ( 'name' ) ; if ( optionRef . optionToken . text . endsWith ( '?' ) ) { throw this . setError ( `option name shouldn't have ?` , optionRef . optionToken ) ; } field . setMeta ( '^target' , optionRef ) ; this . requireToken ( '(' ) ; if ( ! this . matchToken ( ')' ) ) { let payload = field . setMeta ( '^payload' , undefined ) ; this . requireFormula ( payload ) ; this . requireToken ( ')' ) ; } return true ; } if ( this . matchToken ( 'write' ) ) { if ( ! ( field instanceof Statement ) ) { throw this . setError ( 'write must be in code block' , this . prevToken ) ; } field . formulaType = 'write' ; if ( field . dataflow ) { throw this . setError ( 'illegal dataflow attribute on write' , this . prevToken ) ; } /** set ^target from a reference */ const setTarget = ( ref ? ) => { if ( ! ref ) { throw this . setError ( 'expecting a reference to write' ) ; } if ( ref ! . dependent ) { throw this . setError ( 'write requires a structural reference' ) ; } field . setMeta ( '^target' , ref ) ; } ; if ( this . parseToken ( '->' ) ) { let writeValue = field . setMeta ( '^writeValue' , undefined ) ; writeValue . formulaType = 'reference' ; let thatRef = new Reference ; thatRef . tokens = [ Token . fake ( 'that' , this . prevToken ) ] ; writeValue . setMeta ( '^reference' , thatRef ) ; setTarget ( this . parseReference ( ) ) ; return true ; } let save = this . cursor ; let targetRef = this . parseReference ( ) ; if ( targetRef && this . parseToken ( '<-' ) ) { setTarget ( targetRef ) ; let payload = field . setMeta ( '^payload' , undefined ) ; this . requireFormula ( payload ) ; return true ; } this . cursor = save ; let writeValue = field . setMeta ( '^writeValue' , undefined ) ; this . requireFormula ( writeValue ) ; this . requireToken ( '->' ) ; setTarget ( this . parseReference ( ) ) ; return true ; } if ( this . matchToken ( 'include' ) ) { field . formulaType = 'include' ; this . requireToken ( 'name' ) ; return true ; } if ( this . matchToken ( 'builtin' ) ) { let name = new Text ; name . value = this . requireToken ( 'name' ) . text ; field . formulaType = 'builtin' ; field . setMeta ( '^builtin' , name ) ; return true ; } return false ; } /** requires an edit command */ requireEdit ( field ) { field . formulaType = this . cursorToken . text as any ; if ( this . matchToken ( '::delete' , '::nochange' ) ) { return ; } if ( this . matchToken ( '::replace' , '::convert' ) ) { let source = this . parseLiteral ( ) || this . parseReference ( ) ; if ( ! source || ( source instanceof Reference && ! source . dependent ) ) { throw this . setError ( `expecting literal or dot-path` , this . cursorToken ) ; } field . setMeta ( '^source' , source ) ; return ; } if ( this . matchToken ( '::insert' , '::append' ) ) { this . requireToken ( '{' ) ; let name = this . requireToken ( 'name' ) ; let block = new Record ; let newField = new Field ; field . setMeta ( '^source' , block ) ; block . add ( newField ) ; this . fieldID ( newField , name ) ; this . requireToken ( '::' ) ; let source = this . parseLiteral ( ) || this . parseReference ( ) ; if ( ! source || ( source instanceof Reference && ! source . dependent ) ) { throw this . setError ( `expecting literal or dot-path` , this . cursorToken ) ; } newField . io = 'data' ; newField . formulaType = 'none' ; newField . setValue ( source ) ; this . requireToken ( '}' ) ; return ; } if ( this . matchToken ( '::move' , '::move-insert' , '::move-append' ) ) { let source = this . parseReference ( ) ; if ( ! source || ! source . dependent ) { throw this . setError ( `expecting dot-path` , this . cursorToken ) ; } field . setMeta ( '^source' , source ) ; if ( field . formulaType == '::move-insert' || field . formulaType === '::move-append' ) { let id = this . space . newFieldID ( ) ; assert ( id . serial === field . id . serial + 1 ) ; } return ; } if ( this . matchToken ( '::wrap-record' ) ) { let id = this . space . newFieldID ( ) ; assert ( id . serial === field . id . serial + 1 ) ; return ; } if ( this . matchToken ( '::wrap-array' , '::unwrap' ) ) { return ; } trap ( ) ; } /** parse arguments following a reference. Param first rejects
     * syntax 'ref value x' and 'ref value('*/ parseCall ( ref , first = false ) { let startCursor = this . cursor ; let startToken = this . cursorToken ; let rightValue = this . parseLiteral ( ) || this . parseReference ( ) ; if ( first && ! arrayLast ( ref . tokens ) . isOperator && rightValue && ( this . parseReference ( ) || this . parseLiteral ( ) || this . parseToken ( '(' ) || this . parseToken ( '{' ) ) ) { this . cursor = startCursor ; return undefined ; } if ( ! rightValue && arrayLast ( ref . tokens ) . text === '&' && this . peekToken ( '{' ) ) { rightValue = this . requireBlock ( new With ) ; } if ( ! rightValue && ! this . matchToken ( '(' ) ) { return undefined ; } if ( ref . dependent ) { throw this . setError ( 'function is dependent' , ref . tokens [ 0 ] ) ; } let call = new Call ; call . token = arrayLast ( ref . tokens ) ; ref . tokens . push ( Token . fake ( 'call' , this . prevToken ) ) ; let prog = new Statement ; prog . id = this . space . newFieldID ( undefined , this . prevToken ) ; call . add ( prog ) ; prog . formulaType = 'reference' ; prog . setMeta ( '^reference' , ref ) ; let input = new Statement ; input . id = this . space . newFieldID ( undefined , this . prevToken ) ; call . add ( input ) ; input . formulaType = 'updateInput' ; let target = new Reference ; target . tokens = [ Token . fake ( 'that' , this . prevToken ) , Token . fake ( 'arg1' , this . prevToken ) ] ; input . setMeta ( '^target' , target ) ; let payloadRef = new Reference ; payloadRef . tokens = [ Token . fake ( 'input' , this . prevToken ) ] ; let payload = input . setMeta ( '^payload' ) ; payload . formulaType = 'reference' ; payload . setMeta ( '^reference' , payloadRef ) ; if ( rightValue ) { let arg = new Statement ; arg . formulaType = 'update' ; let target = new Reference ; target . tokens = [ Token . fake ( 'that' , startToken ) , Token . fake ( 'arg2' , startToken ) ] ; arg . setMeta ( '^target' , target ) ; let payload = arg . setMeta ( '^payload' ) ; if ( rightValue instanceof Reference ) { payload . formulaType = 'reference' ; payload . setMeta ( '^reference' , rightValue ) ; } else if ( rightValue instanceof With ) { payload . formulaType = 'code' ; payload . setMeta ( '^code' , rightValue ) ; } else { payload . formulaType = 'none' ; payload . setValue ( rightValue ) ; } arg . id = this . space . newFieldID ( undefined , startToken ) ; call . add ( arg ) ; return call ; } while ( ! this . parseToken ( ')' ) ) { let arg = new Statement ; let argToken = this . cursorToken ; this . requireFormula ( arg ) ; if ( arg . formulaType !== 'update' ) { if ( call . fields . length !== 2 ) { throw this . setError ( 'Only first argument can be anonymous' , argToken ) ; } let anon = arg ; arg = new Statement ; arg . formulaType = 'update' ; let target = new Reference ; target . tokens = [ Token . fake ( 'that' , argToken ) , Token . fake ( 'arg2' , argToken ) ] ; arg . setMeta ( '^target' , target ) ; let payloadValue = anon . value ; if ( payloadValue ) anon . detachValue ( ) ; let payload = arg . setMeta ( '^payload' , payloadValue ) ; payload . formulaType = anon . formulaType ; if ( anon . metadata ) { payload . metadata = anon . metadata ; payload . metadata . containingItem = payload ; } } arg . id = this . space . newFieldID ( undefined , argToken ) ; call . add ( arg ) ; this . matchToken ( ',' ) ; } return call ; } /** inject an input argument field at start of block with specified syntax */ injectInput ( block , syntax ) { let parser = new Parser ( syntax ) ; parser . space = this . space ; let field = parser . requireField ( block ) ; arrayRemove ( block . items , field ) ; block . items . splice ( 0 , 0 , field ) ; return field ; } /** parse a loop block */ parseLoop ( ) { let token = this . parseToken ( 'find?' , 'find!' , 'for-all' , 'such-that' , 'all?' , 'all!' , 'none?' , 'none!' , 'accumulate' , 'selecting' ) ; if ( ! token ) return undefined ; let loop = new Loop ; loop . token = token ; switch ( token . text ) { case 'find?' : case 'find!' : case 'for-all' : case 'such-that' : case 'all?' : case 'all!' : case 'none?' : case 'none!' : case 'accumulate' : case 'selecting' : loop . loopType = token . text ; break ; default : trap ( ) ; } let block = this . requireBlock ( new Do ) ; loop . createTemplate ( ) . setFrom ( block ) ; let inputField = block . items [ 0 ] ; if ( inputField . io === 'input' ) { let ref = inputField . getMaybe ( '^reference' ) ?. value ; if ( ref instanceof Reference && ref . tokens . length === 2 && ref . tokens [ 0 ] . type === 'that' && ref . tokens [ 1 ] . type === '[]' ) { } else { throw this . setError ( 'input must be []' , block . token ) ; } } else { this . injectInput ( block , 'item: []' ) ; } if ( loop . loopType === 'accumulate' && block . items [ 1 ] ?. io !== 'input' ) { throw this . setError ( 'accumulate requires two inputs' , block . token ) ; } return loop ; } /** Require try block */ requireTry ( ) { let block = new Try ; block . outlined = false ; while ( true ) { let endCursor = this . cursor ; while ( true ) { if ( this . matchToken ( '\n' ) ) { block . outlined = true ; continue ; } if ( block . fields . length && this . matchToken ( ',' ) ) continue ; if ( block . fields . length && this . matchToken ( ';' ) ) continue ; break ; } let nameToken = this . parseToken ( 'name' ) ; if ( nameToken && ! this . parseToken ( '=' ) ) { this . cursor = endCursor ; break ; } if ( ! block . fields . length ) { } else if ( this . matchToken ( 'else' ) ) { if ( this . matchToken ( 'reject' ) ) { block . conditional = true ; break ; } } else { this . cursor = endCursor ; break ; } let statement = new Statement ; block . add ( statement ) ; this . fieldID ( statement , nameToken ) ; let clause = new Code ; statement . formulaType = 'code' ; statement . setMeta ( '^code' , clause ) ; this . requireBlock ( clause ) ; if ( clause . outlined ) block . outlined = true ; continue ; } return block ; } /** Returns a Reference with tokens[] containing name tokens which may include
     * a leading ^/~ and trailing ?/!. Will contain leading 'that' for dependent
     * path. Also contains number tokens for testing. TODO: [formula] indexing
     * will return a ReferenceFormula instead, containing references and 'at'
     * calls.
     * */ parseReference ( ) { let tokens = [ ] ; if ( this . matchToken ( 'name' , 'that' ) ) { if ( this . prevToken . text [ 0 ] === '^' ) { throw this . setError ( "Reference can't start with ^" , this . cursorToken ) ; } if ( this . prevToken . type === 'name' && this . prevToken . text [ 0 ] === '~' ) { tokens . push ( Token . fake ( 'that' , this . prevToken ) ) ; } tokens . push ( this . prevToken ) ; } else if ( this . peekToken ( '.' , '[]' ) ) { tokens . push ( Token . fake ( 'that' , this . cursorToken ) ) ; } while ( true ) { if ( this . matchToken ( '.' ) ) { this . requireToken ( 'name' , 'number' ) ; if ( this . prevToken . text [ 0 ] === '^' ) { throw this . setError ( "^ can't follow ." , this . prevToken ) ; } tokens . push ( this . prevToken ) ; continue ; } else if ( this . matchToken ( '[]' ) ) { tokens . push ( this . prevToken ) ; continue ; } else if ( this . peekToken ( 'name' ) && this . cursorToken . text [ 0 ] === '^' ) { tokens . push ( this . cursorToken ) ; this . cursor ++ ; continue ; } else if ( this . peekToken ( 'name' ) && this . cursorToken . text [ 0 ] === '~' ) { let importToken = this . cursorToken ; this . cursor ++ ; tokens . push ( new Token ( 'name' , importToken . start , importToken . start + 1 , importToken . source ) ) ; if ( importToken . text . length > 1 ) { tokens . push ( new Token ( 'name' , importToken . start + 1 , importToken . end , importToken . source ) ) ; if ( importToken . text === '~?' || importToken . text === '~!' ) { throw this . setError ( "? or ! goes before ~ not after" ) ; } } continue ; } break ; } if ( ! tokens . length ) return undefined ; let ref = new Reference ; ref . tokens = tokens ; return ref ; } /** Parse a literal value */ parseLiteral ( ) { if ( this . matchToken ( 'number' ) ) { let num = new _Number ; num . token = this . prevToken ; num . value = Number ( this . prevToken . text ) ; assert ( Number . isFinite ( num . value ) ) ; return num ; } if ( this . matchToken ( '###' ) ) { let num = new _Number ; num . token = this . prevToken ; num . value = NaN ; return num ; } if ( this . matchToken ( 'string' ) ) { if ( this . prevToken . text . startsWith ( Token . characterLiteralStart ) ) { let char = new Character ; char . token = this . prevToken ; char . value = stringUnescape ( this . prevToken . text . slice ( Token . characterLiteralStart . length , - 1 ) ) ; return char ; } let text = new Text ; text . token = this . prevToken ; text . value = stringUnescape ( this . prevToken . text . slice ( 1 , - 1 ) ) ; return text ; } if ( this . matchToken ( 'nil' ) ) { let nil = new Nil ; nil . token = this . prevToken ; return nil ; } if ( this . matchToken ( 'anything' ) ) { let any = new Anything ; any . token = this . prevToken ; return any ; } if ( this . matchToken ( 'record' ) ) { return this . requireBlock ( new Record ) ; } if ( this . matchToken ( 'choice' ) ) { return this . requireBlock ( new Choice ) ; } let tracked = this . matchToken ( 'tracked' ) ; let isArray = this . matchToken ( 'array' ) ; if ( isArray || this . matchToken ( 'table' ) ) { let template ; if ( isArray ) { this . requireToken ( '{' ) ; template = this . parseLiteral ( ) ; if ( ! template ) throw this . setError ( 'expecting a template value' ) ; this . requireToken ( '}' ) ; } else { template = this . requireBlock ( new Record ) ; } let array = new _Array ; array . tracked = tracked ; array . createTemplate ( ) . setFrom ( template ) ; return array ; } else if ( tracked ) { throw this . setError ( 'expecting an array or table' ) ; } this . setError ( 'Expecting a value' ) ; return undefined ; } /** require one of several token types */ requireToken ( ... types ) { let token = this . parseToken ( ... types ) ; if ( ! token ) { throw this . error ; } return token ; } /** parse one of several token types */ parseToken ( ... types ) { if ( this . matchToken ( ... types ) ) { return this . prevToken ; } this . setError ( 'Expecting ' + types . join ( ' or ' ) ) ; return undefined ; } /** match one of several token types */ matchToken ( ... types ) { if ( this . peekToken ( ... types ) ) { this . cursor ++ ; return true ; } return false ; } /** peek one of several token types */ peekToken ( ... types ) { let cursorType = this . cursorToken . type ; for ( let type of types ) { if ( type === cursorType ) return true ; } return false ; } }