import { TokenPattern , tokenize } from './tokenizer' ; import * as fs from 'fs' ; import * as Q from 'q' ; abstract class Token { constructor ( readonly line , readonly column , readonly text ) { } } class Identity extends Token { } class Equals extends Token { } class Comma extends Token { } class Semicolon extends Token { } class VerticalBar extends Token { } class LSqBracket extends Token { } class RSqBracket extends Token { } class LParen extends Token { } class RParen extends Token { } class Unexpected extends Token { } export function getTokenName ( symbolClass ) { return symbolClass . name ; } const ebnfPatterns = [ { match : /[ \r\t\n]/ } , { match : /\w+/ , token : Identity } , { match : '=' , token : Equals } , { match : ';' , token : Semicolon } , { match : '|' , token : VerticalBar } , { match : '[' , token : LSqBracket } , { match : ']' , token : RSqBracket } , { match : '(*' , pushState : [ { match : '*)' , popState : true } , { match : /.|\s/ } , ] } , { match : '(' , token : LParen } , { match : ')' , token : RParen } , { match : /./ , token : Unexpected } , ] ; export type InputType = Optionals | string | Grouping ; export class Optionals { constructor ( readonly inputs ) { } } export class Grouping { constructor ( readonly inputs ) { } } export class Rule { constructor ( readonly result , readonly inputs ) { } } function throwError ( message , token ? ) { if ( token ) { throw new Error ( `${ message }: (${ token . line }, ${ token . column })` ) ; } else { throw new Error ( `${ message }: EOF` ) ; } } function peek ( tokens , validTypes , throwIfNot = true ) { if ( validTypes instanceof Function ) { validTypes = [ validTypes ] ; } const token = tokens [ 0 ] ; let match = null ; for ( const type of validTypes ) { if ( token instanceof type ) { match = type ; } } if ( ! match && throwIfNot ) { throwError ( `Expected one of ${ validTypes . map ( t => getTokenName ( t ) ) . join ( ',' ) }` , token ) ; } return match as Function ; } function next ( tokens , validTypes ) { peek ( tokens , validTypes ) ; return tokens . shift ( ) as Token ; } function parseGrouping ( tokens ) { next ( tokens , LParen ) ; const inputs = parseInputs ( tokens ) ; next ( tokens , RParen ) ; return new Grouping ( inputs ) ; } function parseOptionals ( tokens ) { next ( tokens , LSqBracket ) ; const inputs = parseInputs ( tokens ) ; next ( tokens , RSqBracket ) ; return new Optionals ( inputs ) ; } function parseInputs ( tokens ) { const inputsArr = [ ] ; let inputs = [ ] ; while ( true ) { const type = peek ( tokens , [ Identity , LParen , LSqBracket ] ) ; if ( type === Identity ) { inputs . push ( ( tokens . shift ( ) as Identity ) . text ) ; } else if ( type === LParen ) { const grouping = parseGrouping ( tokens ) ; inputs . push ( grouping ) ; } else if ( type === LSqBracket ) { const optionals = parseOptionals ( tokens ) ; inputs . push ( optionals ) ; } if ( ! peek ( tokens , [ Identity , LParen , LSqBracket ] , false ) ) { inputsArr . push ( inputs ) ; inputs = [ ] ; if ( ! peek ( tokens , VerticalBar , false ) ) { return inputsArr ; } next ( tokens , VerticalBar ) ; } } } function parseRules ( tokens ) { const rules = [ ] ; while ( tokens . length > 0 ) { const ident = next ( tokens , Identity ) ; next ( tokens , Equals ) ; const inputs = parseInputs ( tokens ) ; next ( tokens , Semicolon ) ; rules . push ( new Rule ( ident . text , inputs ) ) ; } return rules ; } export function parse ( fileName ) { const fileContents = fs . readFileSync ( fileName , { encoding : 'utf-8' } ) . split ( '\r\n' ) ; const tokens = tokenize ( fileContents , ebnfPatterns ) ; return parseRules ( tokens ) ; }