import * as AST from './AST' ; import { Token , TokenType } from './Token' ; import transform from './transform' ; const rootLevelDirectives = [ '.data' , '.text' , ] ; const dataLevelDirectives = [ '.word' , '.asciiz' , '.ascii' , '.word' , '.byte' , '.space' , '.float' , '.double' , ] ; const IMM_RE = /^(-?)(0x[0-9A-Fa-f]+|0o[0-7]+|0b[0-1]+|[0-9]+)$/ ; export default class Parser { private tokens : Token [ ] ; private index : number ; private root : AST . Root ; private inData : boolean ; constructor ( tokens ) { this . tokens = tokens ; this . index = 0 ; this . root = new AST . Root ( ) ; this . inData = false ; } private peek ( n = 0 ) { return this . tokens [ this . index + n ] ; } private next ( ) { return this . tokens [ this . index ++ ] ; } private nextLine ( ) { const tokens = [ ] ; let token = this . next ( ) ; while ( token && token . type !== 'EOS' && token . type !== 'EOL' ) { tokens . push ( token ) ; token = this . next ( ) ; } return tokens ; } private addNode ( node ) { if ( this . inData ) { this . root . data . add ( node ) ; } else { this . root . text . add ( node ) ; } } private groupByComma ( tokens ) { const ret = [ ] ; let buf = [ ] ; tokens . forEach ( ( t , i ) => { if ( t . type === 'COMMA' ) { if ( buf . length ) { ret . push ( buf ) ; buf = [ ] ; } } else { buf . push ( t ) ; if ( i === tokens . length - 1 ) { ret . push ( buf ) ; } } } ) ; return ret ; } private normalizeDataValues ( tokens ) { const ret = [ ] ; const group = this . groupByComma ( tokens ) ; return group . map ( g => { if ( g . length > 1 ) { throw new Error ( 'TODO' ) ; } return g [ 0 ] . value ; } ) ; } private normalizeArgs ( tokens ) { const group = this . groupByComma ( tokens ) ; return group . map ( g => { let ret ; if ( g [ 0 ] . type !== 'TEXT' ) { throw new Error ( 'Unexpected token TODO' ) ; } if ( /^\$/ . test ( g [ 0 ] . value ) ) { ret = new AST . RegisterNode ( g [ 0 ] . value ) ; } else if ( IMM_RE . test ( g [ 0 ] . value ) ) { if ( g . length === 1 ) { ret = new AST . ImmediateNode ( g [ 0 ] . value ) ; } else { const [ imm , lp , r , rp ] = g ; ret = new AST . OffsetNode ( imm . value , r . value ) ; } } else { ret = new AST . AddressNode ( g [ 0 ] . value ) ; } return ret ; } ) ; } private logTokens ( tokens ) { console . log ( tokens . map ( t => `(${ t . type } => ${ t . value })` ) . join ( ' ' ) ) ; } private logGroup ( group ) { console . log ( 'GROUP: (START)' ) ; group . forEach ( tokens => this . logTokens ( tokens ) ) ; console . log ( 'GROUP: (END)' ) ; } public parse ( ) { let line = this . nextLine ( ) ; while ( line . length ) { let [ first , ... rest ] = line ; if ( first . type !== 'TEXT' ) { throw new Error ( 'Unexpected Token Error' ) ; } if ( /^\./ . test ( first . value ) ) { if ( ! ~ rootLevelDirectives . indexOf ( first . value ) ) { throw new Error ( 'Unknown directive' ) ; } if ( rest . length ) { throw new Error ( 'Unexpected tokens' ) ; } this . inData = first . value === '.data' ; } else { let node ; let label = '' ; let labelNode ; if ( rest . length && rest [ 0 ] . type === 'COLON' ) { label = first . value ; labelNode = new AST . LabelNode ( label ) ; if ( rest . length > 1 ) { first = rest [ 1 ] ; rest = rest . slice ( 2 ) ; } else { const nextLine = this . nextLine ( ) ; if ( ! nextLine . length ) { this . addNode ( labelNode ) ; continue ; } first = nextLine [ 0 ] ; rest = nextLine . slice ( 1 ) ; } } if ( this . inData ) { if ( first . type !== 'TEXT' && ! /^\./ . test ( first . value ) ) { throw new Error ( 'Bad Data' ) ; } const dataDirective = first . value ; const values = this . normalizeDataValues ( rest ) ; node = new AST . DataNode ( dataDirective , values ) ; } else { const opName = first . value ; const args = this . normalizeArgs ( rest ) ; node = new AST . OperationNode ( first . value , args ) ; } if ( labelNode ) { labelNode . add ( node ) ; this . addNode ( labelNode ) ; } else { this . addNode ( node ) ; } } line = this . nextLine ( ) ; } transform ( this . root ) ; return this . root ; } }