170 lines
3.6 KiB
TypeScript
170 lines
3.6 KiB
TypeScript
![]() |
import { LexerMatcher } from "./LexerMatcher";
|
||
|
import { LexerEvent } from "./LexerEvent";
|
||
|
|
||
|
export class Lexer
|
||
|
{
|
||
|
static readonly defaultMode = "default";
|
||
|
private _modes = new Map<string,LexerMatcher[]>();
|
||
|
|
||
|
get modes()
|
||
|
{
|
||
|
return this._modes;
|
||
|
}
|
||
|
|
||
|
get matchers():LexerMatcher[]
|
||
|
{
|
||
|
let output:LexerMatcher[] = [];
|
||
|
|
||
|
for ( let mode of this._modes )
|
||
|
{
|
||
|
let matchers = mode[ 1 ];
|
||
|
|
||
|
for ( let matcher of matchers )
|
||
|
{
|
||
|
output.push( matcher );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return output;
|
||
|
}
|
||
|
|
||
|
addMatcher( matcher:LexerMatcher )
|
||
|
{
|
||
|
let list:LexerMatcher[] = this._modes.get( matcher.mode );
|
||
|
|
||
|
if ( ! list )
|
||
|
{
|
||
|
list = [];
|
||
|
this._modes.set( matcher.mode, list );
|
||
|
}
|
||
|
|
||
|
list.push( matcher );
|
||
|
}
|
||
|
|
||
|
resolveMatches( source:string, events:LexerEvent[] )
|
||
|
{
|
||
|
events.forEach( e => e.getMatch( source ) );
|
||
|
}
|
||
|
|
||
|
addAllMatchers( ...matchers:LexerMatcher[] )
|
||
|
{
|
||
|
for ( let m of matchers )
|
||
|
{
|
||
|
this.addMatcher( m );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
public lex( source:string, callback:( e:LexerEvent) => void, offset:number = 0, mode:string = "" )
|
||
|
{
|
||
|
|
||
|
let lexerEvent = new LexerEvent( "", 0, -2 );
|
||
|
let lastMatcher = null;
|
||
|
|
||
|
while ( offset < source.length )
|
||
|
{
|
||
|
if ( ! this._modes.has( mode ) )
|
||
|
{
|
||
|
let errorMessage = "@Lexer-Error. Mode not found: '" + mode + "'";
|
||
|
console.log( errorMessage, "@", offset );
|
||
|
lexerEvent.set( errorMessage, offset, LexerEvent.LENGTH_ERROR_ID );
|
||
|
|
||
|
callback( lexerEvent );
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
let matchers = this._modes.get( mode );
|
||
|
let foundSomething = false;
|
||
|
|
||
|
for ( let i = 0; i < matchers.length; i++ )
|
||
|
{
|
||
|
let matcher = matchers[ i ];
|
||
|
let matchLength = matcher.matchLength( source, offset );
|
||
|
|
||
|
if ( matchLength > 0 )
|
||
|
{
|
||
|
lexerEvent.set( matcher.type, offset, matchLength );
|
||
|
callback( lexerEvent );
|
||
|
|
||
|
foundSomething = true;
|
||
|
|
||
|
i = matchers.length;
|
||
|
|
||
|
if ( matcher.nextMode )
|
||
|
{
|
||
|
mode = matcher.nextMode;
|
||
|
}
|
||
|
|
||
|
offset += matchLength;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ! foundSomething )
|
||
|
{
|
||
|
let errorMessage = "@Lexer-Error. No match: '" + mode + "'";
|
||
|
console.log( errorMessage, "@", offset );
|
||
|
lexerEvent.set( errorMessage, offset, LexerEvent.LENGTH_ERROR_ID );
|
||
|
|
||
|
callback( lexerEvent );
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
lexerEvent.set( mode, offset, LexerEvent.LENGTH_DONE_ID );
|
||
|
callback( lexerEvent );
|
||
|
}
|
||
|
|
||
|
lexTokens( source:string, offset:number = 0, mode:string = Lexer.defaultMode )
|
||
|
{
|
||
|
let events = this.lexToList( source, offset, mode );
|
||
|
|
||
|
this.resolveMatches( source, events );
|
||
|
|
||
|
return events;
|
||
|
}
|
||
|
|
||
|
lexToList( source:string, offset:number = 0, mode:string = Lexer.defaultMode )
|
||
|
{
|
||
|
var list:LexerEvent[] = [];
|
||
|
|
||
|
this.lex( source,
|
||
|
( token:LexerEvent )=>
|
||
|
{
|
||
|
list.push( token.clone() );
|
||
|
},
|
||
|
offset, mode
|
||
|
);
|
||
|
|
||
|
return list;
|
||
|
}
|
||
|
|
||
|
compress( tokens:LexerEvent[], compressTypes:Set<string> )
|
||
|
{
|
||
|
let lastToken:LexerEvent = null;
|
||
|
let compressedTokens:LexerEvent[] = [];
|
||
|
|
||
|
tokens.forEach(
|
||
|
t =>
|
||
|
{
|
||
|
if ( lastToken && t.type === lastToken.type && compressTypes.has( t.type ) )
|
||
|
{
|
||
|
lastToken.extendLength( t.length );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lastToken = t.clone();
|
||
|
compressedTokens.push( lastToken );
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
|
||
|
return compressedTokens;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|