import * as vscode from 'vscode' ; import { Rules , Rule , Capture } from './rules' ; import { MatchPositions , UnmatchedText } from './matchPositions' ; import { MultiRegExp2 , MultiRegExp2Match } from './multiRegExp2' ; export interface Line { text : string ; matchPositions : MatchPositions ; elements : Element [ ] ; blockElements : BlockElement [ ] ; } export interface Element { type : ElementType ; text : string ; start : number ; end : number ; } export interface BlockElement { level : number ; index : number ; type : BlockElementType ; text : string ; start : number ; end : number ; } export enum ElementType { none , word , operater , punctRightSpace , punctLeftSpace , connector , asIs } export enum BlockElementType { blockStart , blockAgain , blockEnd } class Position { private _lineText : string ; private _match : string ; constructor ( public line , public position , match ) { this . _match = match ; } get positionAtMatchLeft ( ) : Position { let pos = new Position ( this . line , this . position - this . _match . length , "" ) ; return pos ; } get positionAtMatchRight ( ) : Position { let pos = new Position ( this . line , this . position + this . _match . length , "" ) ; return pos ; } } export class Analyst { private _lines : Line [ ] ; private _rules : Rules ; private _blockLevel : string [ ] = [ ] ; private _keepBlankElement = false ; constructor ( lines , rules , keepBlankElement ) { this . _lines = lines . map ( v => { return < Line > { text : v , matchPositions : new MatchPositions ( v ) , elements : [ ] , blockElements : [ ] } ; } ) ; this . _rules = rules ; this . _keepBlankElement = keepBlankElement ; } get lines ( ) : Line [ ] { return this . _lines ; } analysis ( ) { this . match ( this . _rules . rootRules ) ; this . _lines . map ( v => this . makeLineElements ( v ) ) ; } private makeLineElements ( line ) { if ( line . elements . length ) line . elements . sort ( ( a , b ) => a . start - b . start ) ; let pos = 0 ; let els = [ ] ; for ( let e of line . elements ) { let elText = line . text . substring ( pos , e . start ) ; if ( e . start > pos && ( this . _keepBlankElement || elText . trim ( ) ) ) els . push ( { type : ElementType . none , text : elText , start : pos , end : e . start - 1 } ) ; pos = e . end + 1 ; } let elText = line . text . substring ( pos , line . text . length ) ; if ( pos < line . text . length && ( this . _keepBlankElement || elText . trim ( ) ) ) { els . push ( { type : ElementType . none , text : elText , start : pos , end : line . text . length - 1 } ) ; } line . elements . push ( ... els ) ; if ( line . elements . length ) line . elements . sort ( ( a , b ) => a . start - b . start ) ; } private match ( rules , start ? , stopRule ? ) { let matchStartPos = new Position ( 0 , 0 , "" ) ; let blockEndPos ; if ( start ) matchStartPos = start . positionAtMatchRight ; let blockStartPos ; for ( let rule of rules ) { if ( rule . match ) { this . doMatch ( rule , matchStartPos , stopRule ) ; } else if ( rule . begin && rule . end ) { let blockIndex = 0 ; while ( blockStartPos = this . doBeginMatch ( rule , matchStartPos , stopRule , ++ blockIndex ) ) { blockEndPos = this . doEndMatch ( rule , matchStartPos . positionAtMatchRight , blockIndex ) ; if ( blockEndPos ) this . markElementsInBlock ( rule . patterns . type ? rule . patterns . type : ElementType . none , blockStartPos . positionAtMatchRight , blockEndPos . positionAtMatchLeft ) ; } } } return blockEndPos ; } private doMatch ( rule , start ? , stopRule ? ) { if ( ! rule . match ) return ; for ( let i = 0 ; i < this . _lines . length ; i ++ ) { if ( start && start . line > i ) continue ; let line = this . _lines [ i ] ; for ( let u of line . matchPositions . GetUnmatchedTexts ( ) ) { if ( start && start . line == i && start . position > u . offset + u . text . length - 1 ) continue ; if ( ! u . text . trim ( ) ) continue ; let matches = [ ] ; let shouldEndAt = u . text . length ; let hasEnd = false ; if ( stopRule ) { stopRule . end . regExp . lastIndex = 0 ; if ( matches = stopRule . end . exec ( u ) ) { shouldEndAt = matches [ 0 ] . start ; hasEnd = true ; } } rule . match . regExp . lastIndex = 0 ; while ( matches = rule . match . exec ( u ) ) { if ( matches [ 0 ] . end < shouldEndAt ) { this . markElement ( line , matches , rule . captures , u . offset ) ; } } if ( hasEnd ) return ; } } } private doBeginMatch ( rule , start , stopRule , blockIndex ) { if ( ! rule . begin || ! rule . end ) return ; let beginAt ; let hasFindBegin = false ; for ( let i = start ? start . line : 0 ; i < this . _lines . length ; i ++ ) { let line = this . _lines [ i ] ; for ( let u of line . matchPositions . GetUnmatchedTexts ( ) ) { rule . begin . regExp . lastIndex = 0 ; if ( rule . again ) rule . again . regExp . lastIndex = 0 ; rule . end . regExp . lastIndex = 0 ; if ( start && start . line == i && start . position > u . offset + u . text . length - 1 ) continue ; if ( ! u . text . trim ( ) ) continue ; let matches = [ ] ; let shouldEndAt = u . text . length ; let hasEnd = false ; let endMatch = "" ; if ( stopRule ) { stopRule . end . regExp . lastIndex = 0 ; if ( matches = stopRule . end . exec ( u ) ) { shouldEndAt = matches [ 0 ] . start ; hasEnd = true ; endMatch = matches [ 0 ] . capture ; } } if ( matches = rule . begin . exec ( u ) ) { if ( matches [ 0 ] . end < shouldEndAt ) { hasFindBegin = true ; beginAt = new Position ( i , matches [ 0 ] . start + u . offset , matches [ 0 ] . capture ) ; this . _blockLevel . push ( rule . comment ) ; this . markElement ( line , matches , rule . beginCaptures , u . offset ) ; this . markBlockElement ( line , rule , BlockElementType . blockStart , this . _blockLevel , blockIndex , matches , u . offset ) ; let blockRules = this . _rules . getPatternRules ( rule . patterns ) ; if ( blockRules . length ) { blockRules . unshift ( rule ) ; let lastEnd ; while ( lastEnd = this . match ( blockRules , beginAt , rule ) ) { if ( lastEnd ) beginAt = lastEnd ; } } } } if ( rule . comment === this . _blockLevel [ this . _blockLevel . length - 1 ] && ! beginAt && rule . again ) { if ( matches = rule . again . exec ( u ) ) { this . markElement ( line , matches , rule . beginCaptures , u . offset ) ; this . markBlockElement ( line , rule , BlockElementType . blockAgain , this . _blockLevel , blockIndex , matches , u . offset ) ; } } if ( hasEnd || hasFindBegin ) return beginAt ; } } return beginAt ; } private doEndMatch ( rule , start , blockIndex ) { if ( ! rule || ! rule . begin || ! rule . end ) return start ; for ( let i = start ? start . line : 0 ; i < this . _lines . length ; i ++ ) { let line = this . _lines [ i ] ; for ( let u of line . matchPositions . GetUnmatchedTexts ( ) ) { rule . end . regExp . lastIndex = 0 ; if ( start && start . line == i && start . position > u . offset + u . text . length - 1 ) continue ; let matches = [ ] ; if ( matches = rule . end . exec ( u ) ) { this . markElement ( line , matches , rule . endCaptures , u . offset ) ; this . markBlockElement ( line , rule , BlockElementType . blockEnd , this . _blockLevel , blockIndex , matches , u . offset ) ; let endAt = new Position ( i , matches [ 0 ] . start + u . offset , matches [ 0 ] . capture ) ; this . _blockLevel . pop ( ) ; return endAt ; } } } } private markElement ( line , matches , captures , offset ) { let mp = new MatchPositions ( matches [ 0 ] . capture ) ; let startOffset = - matches [ 0 ] . start ; line . matchPositions . AddPosition ( matches [ 0 ] . start , matches [ 0 ] . end , offset ) ; if ( captures ) { for ( let capture of captures ) { if ( matches [ capture . index ] && matches [ capture . index ] . capture ) { line . elements . push ( < Element > { type : capture . type , text : matches [ capture . index ] . capture , start : matches [ capture . index ] . start + offset , end : matches [ capture . index ] . end + offset , } ) ; mp . AddPosition ( matches [ capture . index ] . start , matches [ capture . index ] . end , startOffset ) ; } } } for ( let u of mp . GetUnmatchedTexts ( ) ) { if ( u . text . trim ( ) ) line . elements . push ( < Element > { type : ElementType . none , text : u . text , start : u . offset - startOffset + offset , end : u . text . length - 1 + u . offset - startOffset + offset , } ) ; } } private markElementsInBlock ( type , start , end ) { for ( let i = start ? start . line : 0 ; i <= end . line ; i ++ ) { let line = this . _lines [ i ] ; for ( let u of line . matchPositions . GetUnmatchedTexts ( ) ) { if ( start && start . line == i && start . position > u . offset + u . text . length - 1 ) continue ; if ( end && end . line == i && end . position < u . offset + u . text . length - 1 ) continue ; if ( type != ElementType . asIs && ! u . text . trim ( ) ) continue ; line . matchPositions . AddPosition ( 0 , u . text . length - 1 , u . offset ) ; line . elements . push ( < Element > { type : type , text : u . text , start : u . offset , end : u . text . length - 1 + u . offset } ) ; } } } private markBlockElement ( line , rule , type , blockLevel , blockIndex , matches , offset ) { if ( ! rule . isBlock ) return ; line . blockElements . push ( < BlockElement > { level : blockLevel . length - 1 , index : blockIndex , type : type , text : matches [ 0 ] . capture , start : matches [ 0 ] . start + offset , end : matches [ 0 ] . end + offset , } ) ; } }