private OpResult TryParseStruct(ParserState state, LexerData data, ParsingIndex index) { if (!TryReadToken(data, TokenType.Identifier, out var namePos, out var structName)) { return(OpResult.Fail($"Struct name is not defined at {namePos}")); } if (_mapper != null) { structName = _mapper.RemapTypeName(TypeKind.Struct, structName, state.RemappedTypes); } var validationResult = ValidateTypeName(structName, namePos, index); if (validationResult.HasError) { return(validationResult); } if (!TryReadToken(data, TokenType.CurlyBraceLeft, out var bracePos, out _)) { return(OpResult.Fail($"Missing `{{` after struct `{structName}` declaration at {bracePos}")); } state.StartBlock(ParsingBlockType.Struct, structName); index.BeginStruct(structName); return(OpResult.Ok()); }
public LexerResult Read(Stream input) { var state = new LexerState(); var data = new LexerData(); var offset = 0; int bytesRead; while ((bytesRead = input.Read(_buffer, offset, _buffer.Length - offset)) > 0) { var length = offset + bytesRead; var span = _buffer.AsSpan(0, length); var opResult = ReadTokens(state, span, offset, data); if (opResult.HasError) { return(LexerResult.Fail(opResult.Error)); } offset = state.CurrBlockDataLen; if (offset == _buffer.Length) { Array.Resize(ref _buffer, _buffer.Length * 2); } } return(LexerResult.Ok(data)); }
private OpResult TryParseEnum(ParserState state, LexerData data, ParsingIndex index) { if (!TryReadToken(data, TokenType.Identifier, out var namePos, out var enumName)) { return(OpResult.Fail($"Enum name is not defined at {namePos}")); } if (_mapper != null) { enumName = _mapper.RemapTypeName(TypeKind.Enum, enumName, state.RemappedTypes); } var validationResult = ValidateTypeName(enumName, namePos, index); if (validationResult.HasError) { return(validationResult); } if (!TryReadToken(data, TokenType.Colon, out var colonPos, out _)) { return(OpResult.Fail($"Missing : at {colonPos}")); } if (!TryReadToken(data, TokenType.Identifier, out var undTypePos, out var underlyingType)) { return(OpResult.Fail($"Missing enum `{enumName}` type definition at {undTypePos}")); } if (!ParsingHelper.IsInteger(underlyingType)) { return(OpResult.Fail($"Enum `{enumName}` has invalid underlying type `{underlyingType}`")); } var isFlags = false; if (TryReadToken(data, TokenType.Identifier, out var flagsPos, out var keyword)) { if (keyword != FlagsId) { return(OpResult.Fail($"Unknown enum `{enumName}` optional keyword `{keyword}` at {flagsPos}." + " Only `flags` keyword is supported.")); } isFlags = true; } if (!TryReadToken(data, TokenType.CurlyBraceLeft, out var bracePos, out _)) { return(OpResult.Fail($"Missing `{{` after enum `{enumName}` declaration at {bracePos}")); } state.StartBlock(ParsingBlockType.Enum, enumName); index.BeginEnum(enumName, underlyingType, isFlags); return(OpResult.Ok()); }
private static OpResult ParseEnumContent(ParserState state, LexerData data, ParsingIndex index) { var enumName = state.CurrentBlock.Name; if (TryReadToken(data, TokenType.CurlyBraceRight, out _, out _)) { state.EndBlock(); index.EndEnum(enumName); return(index.IsEnumNonEmpty(enumName) ? OpResult.Ok() : OpResult.Fail($"Enum `{enumName}` is empty. No default value is available")); } if (!TryReadToken(data, TokenType.Identifier, out var namePos, out var name)) { return(OpResult.Fail($"Failed to read enum name at {namePos}")); } if (!ParsingHelper.IsNameValid(name)) { return(OpResult.Fail($"Enum item `{enumName}.{name}` has invalid name at {namePos}")); } if (index.IsEnumContainsItem(enumName, name)) { return(OpResult.Fail($"Enum item `{enumName}.{name}` is defined second time at {namePos}")); } if (!TryReadToken(data, TokenType.Assignment, out var assignPos, out _)) { return(OpResult.Fail($"Missing `=` at {assignPos}")); } if (!TryReadToken(data, TokenType.Identifier, out var valPos, out var value)) { return(OpResult.Fail($"Missing enum item `{enumName}.{name}` default value at {valPos}")); } if (!IsEnumValueValid(enumName, value, index)) { return(OpResult.Fail($"Enum item `{enumName}.{name}` has invalid default value at {valPos}")); } if (!TryReadToken(data, TokenType.Semicolon, out var semicolonPos, out _)) { return(OpResult.Fail($"Missing ; at {semicolonPos}")); } index.PutEnumItem(enumName, name, value); return(OpResult.Ok()); }
public ParsingResult Parse(LexerData data) { var state = new ParserState(); var index = new ParsingIndex(); while (data.Tokens.Count > 0) { var opResult = ParseData(state, data, index); if (opResult.HasError) { return(ParsingResult.Fail(opResult.Error)); } } return(ParsingResult.Ok(index.BuildParsedData())); }
private OpResult TryParseType(string id, Position p, ParserState state, LexerData data, ParsingIndex index) { switch (id) { case EnumId: return(TryParseEnum(state, data, index)); case ArrayId: return(TryParseArray(state, data, index)); case StructId: return(TryParseStruct(state, data, index)); } return(OpResult.Fail($"Unknown datatype `{id}` declaration at {p}")); }
/// <summary> /// Constructs a Lexer using the necessary data stored in the LexerData instance. /// </summary> /// <param name="lexerData">The LexerData containing information necessary to construct the Lexer.</param> public Lexer(LexerData lexerData) { this.groupSymbolCodes = lexerData.GroupSymbolCodes; this.regex = lexerData.Regex; }
private OpResult ParseStructContent(ParserState state, LexerData data, ParsingIndex index) { var structName = state.CurrentBlock.Name; if (TryReadToken(data, TokenType.CurlyBraceRight, out _, out _)) { state.EndBlock(); index.EndStruct(structName); return(index.IsStructNonEmpty(structName) ? OpResult.Ok() : OpResult.Fail($"Struct `{structName}` is zero-sized")); } if (!TryReadToken(data, TokenType.Identifier, out var typePos, out var fieldType)) { return(OpResult.Fail($"Missing field type definition at {typePos}")); } if (_mapper != null) { fieldType = _mapper.RemapMemberType(fieldType, state.RemappedTypes); } if (!TryReadToken(data, TokenType.Identifier, out var namePos, out var fieldName)) { return(OpResult.Fail($"Missing field name definition at {namePos}")); } if (!ParsingHelper.IsNameValid(fieldName)) { return(OpResult.Fail($"Field `{structName}.{fieldName}` has invalid name at {namePos}")); } if (index.IsStructContainsField(structName, fieldName)) { return(OpResult.Fail($"Field `{structName}.{fieldName}` is defined second time at {namePos}")); } if (!IsTypeKnown(fieldType, index)) { return(OpResult.Fail($"Field `{structName}.{fieldName}` has unknown type `{fieldType}` at {typePos}")); } string fieldDefaultValue = null; if (TryReadToken(data, TokenType.Assignment, out _, out _)) { if (!TryReadToken(data, TokenType.Identifier, out var valuePos, out fieldDefaultValue)) { return(OpResult.Fail($"Missing default value of field `{structName}.{fieldName}` at {valuePos}")); } if (_mapper != null) { fieldDefaultValue = _mapper.RemapMemberDefaultValue(fieldType, fieldDefaultValue); } if (!IsDefaultValueValid(fieldType, fieldDefaultValue, index)) { return(OpResult.Fail($"Invalid default value `{fieldDefaultValue}` is defined for " + $"field `{structName}.{fieldName}` at {valuePos}")); } } if (!TryReadToken(data, TokenType.Semicolon, out var semicolonPos, out _)) { return(OpResult.Fail($"Missing ; after field `{structName}.{fieldName}` declaration at {semicolonPos}")); } index.PutStructField(structName, fieldType, fieldName, fieldDefaultValue); return(OpResult.Ok()); }
private OpResult TryParseArray(ParserState state, LexerData data, ParsingIndex index) { if (!TryReadToken(data, TokenType.Identifier, out var namePos, out var arrayName)) { return(OpResult.Fail($"Array name is not defined at {namePos}")); } if (_mapper != null) { arrayName = _mapper.RemapTypeName(TypeKind.Array, arrayName, state.RemappedTypes); } var validationResult = ValidateTypeName(arrayName, namePos, index); if (validationResult.HasError) { return(validationResult); } if (!TryReadToken(data, TokenType.Identifier, out var typePos, out var itemType)) { return(OpResult.Fail($"Array `{arrayName}` items type is not defined at {typePos}")); } if (_mapper != null) { itemType = _mapper.RemapMemberType(itemType, state.RemappedTypes); } if (!IsTypeKnown(itemType, index)) { return(OpResult.Fail($"Array `{arrayName}` has unknown items type `{itemType}` at {typePos}")); } if (!TryReadToken(data, TokenType.SquareBraceLeft, out var lBracePos, out _)) { return(OpResult.Fail($"Missing [ at {lBracePos}")); } if (!TryReadToken(data, TokenType.Identifier, out var lengthPos, out var lengthString)) { return(OpResult.Fail($"Array `{arrayName}` length is not defined at {lengthPos}")); } if (!int.TryParse(lengthString, out var length)) { return(OpResult.Fail($"Failed to parse `{arrayName}` length at {lengthPos}")); } if (length <= 0) { return(OpResult.Fail($"Array `{arrayName}` has invalid length {length}")); } if (!TryReadToken(data, TokenType.SquareBraceRight, out var rBracePos, out _)) { return(OpResult.Fail($"Missing ] at {rBracePos}")); } string defValue = null; if (TryReadToken(data, TokenType.Assignment, out _, out _)) { if (!TryReadToken(data, TokenType.Identifier, out var valuePos, out defValue)) { return(OpResult.Fail($"Missing default item value of array `{arrayName}` at {valuePos}")); } if (_mapper != null) { defValue = _mapper.RemapMemberDefaultValue(itemType, defValue); } if (!IsDefaultValueValid(itemType, defValue, index)) { return(OpResult.Fail($"Invalid default value `{defValue}` is defined for array `{arrayName}` at {valuePos}")); } } if (!TryReadToken(data, TokenType.Semicolon, out var semicolonPos, out _)) { return(OpResult.Fail($"Missing ; at {semicolonPos}")); } index.PutArray(arrayName, itemType, length, defValue); return(OpResult.Ok()); }
public static Grammar ParseGrammar(string specificationPath, out IList<string> warningMessages) { #if BOOTSTRAP GrammarLexer lexer = new GrammarLexer(); lexer.SourceString = File.ReadAllText(specificationPath); Token token = lexer.GetNextToken(); //sem si budeme ukládat chyby a warningy; pokud se vyskytne nějaká chyba, čteme dál a //až na konci vyhodíme výjimku se všemi chybami a warningami; pokud vše proběhne bez //závažnějších chyb, tak seznam warningů pošlem zpátky volajícímu List<string> errorMessages = new List<string>(); warningMessages = new List<string>(); //the code to be inserted at the start of the generated code string headerCode = null; //seznam jmen všech symbolů; slouží potom jako převodní tabulka z kódu symbolu na jeho jméno List<string> symbolNames = new List<string>(); //"inverzní tabulka" k symbolNames, která nám pro jméno symbolu řekne jeho kód Dictionary<string, int> symbolCodes = new Dictionary<string, int>(); //seznam regulárních výrazů definujících terminální symboly; netvoříme z nich rovnou výsledný //lexerův regex, ale ukládáme si je zvlášť, abychom v případě chyby při kompilaci celkového regexu //mohli jednodušše otestovat, které výrazy jsou na vině List<string> regexes = new List<string>(); //pro každý výraz v regexes si pamatujeme pozici, kde jsme ho našli, abychom mohli vydat podrobnější //zprávu List<int> regexLines = new List<int>(); List<int> regexColumns = new List<int>(); //pro každý výraz v regexes si také pamatujeme kód symbolu, který je popisován oním výrazem, //v případě, že výraz má matchovat řetězce, které chceme ignorovat, je v tomto poli hodnota -1; //jedná se o runtime data, která pak přímo používá náš lexer List<int> groupSymbolCodes = new List<int>(); //výsledný regulární výraz, pomocí kterého lexer scanuje tokeny; druhá část runtime dat pro náš lexer Regex regex = null; //jméno pseudoterminálu, jehož tokeny se nemají posílat parseru, ale zahazovat string nullTerminal = ""; //globální optiony .NETímu regex stroji (case insensitive, multiline...) string regexOpts = null; if (token.SymbolCode == CODE_HEADER) { token = lexer.GetNextToken(); headerCode = token.Value; token = lexer.GetNextToken(); } // skipping LEXER token = lexer.GetNextToken(); if (token.SymbolCode == CODE_NULL) { token = lexer.GetNextToken(); nullTerminal = token.Value; token = lexer.GetNextToken(); } if (token.SymbolCode == CODE_REGEXOPTS) { regexOpts = token.Value; token = lexer.GetNextToken(); } symbolNames.Add("$end"); symbolCodes["$end"] = 0; while (token.SymbolCode == CODE_IDENTIFIER) { string symbol = token.Value; token = lexer.GetNextToken(); // skipping EQUALS token = lexer.GetNextToken(); string capturingRegex = token.Value; token = lexer.GetNextToken(); if (symbol == nullTerminal) { groupSymbolCodes.Add(-1); } else { if (!symbolCodes.ContainsKey(symbol)) { symbolNames.Add(symbol); symbolCodes[symbol] = symbolNames.Count - 1; } groupSymbolCodes.Add(symbolCodes[symbol]); } regexes.Add(capturingRegex); regexLines.Add(token.LineNumber); regexColumns.Add(token.ColumnNumber); } StringBuilder pattern = new StringBuilder(); if (regexOpts != null) pattern.Append(regexOpts); for (int i = 0; i < regexes.Count; i++) { //všechny uživatelovi regulární výrazy oddělíme ořítky a zapíšeme je v pořadí, v jakém nám //je zadal (v .NETím Regex enginu mají výrazy v ořítku víc nalevo přednost) a každý výraz //strčíme do capture groupy pojmenované __i, kde i je pořadové číslo výrazu, počítáno od 0 if (i != 0) pattern.Append('|'); pattern.AppendFormat("(?<{0}>{1})", "__" + i.ToString(), regexes[i]); } try { regex = new Regex(pattern.ToString(), RegexOptions.Compiled); } catch (ArgumentException) { try { new Regex(regexOpts); } catch (ArgumentException) { // FIXME: We no longer have the line and column data on regexOpts. errorMessages.Add(string.Format("{0},{1}: The RegEx options are invalid.", -1, -1)); } for (int i = 0; i < regexes.Count; i++) { try { new Regex(regexes[i]); } catch (ArgumentException) { errorMessages.Add(string.Format("{0},{1}: This regular expression is invalid.", regexLines[i], regexColumns[i])); } } } int numTerminals = symbolNames.Count; // skipping PARSER token = lexer.GetNextToken(); //neterminály, které se objevily na levé straně nějakého pravidla HashSet<int> reducibleNonterminals = new HashSet<int>(); //neterminály, které se objevily na pravé straně nějakého pravidla HashSet<int> usedNonterminals = new HashSet<int>(); bool[] terminalUsed = new bool[numTerminals]; List<ProductionWithAction> productions = new List<ProductionWithAction>(); symbolNames.Add("$start"); symbolCodes["$start"] = numTerminals; //semhle dáme <$start> výjimečně, protože ani nechceme, //aby ho někdo dával na pravou stranu nějakého pravidla usedNonterminals.Add(symbolCodes["$start"]); // skipping START token = lexer.GetNextToken(); // skipping LANGLE token = lexer.GetNextToken(); string startSymbol = token.Value; if (symbolCodes.ContainsKey(startSymbol) && symbolCodes[startSymbol] < numTerminals) errorMessages.Add(string.Format("{0},{1}: The nonterminal <{2}> shares it's name with a terminal symbol.", token.LineNumber, token.ColumnNumber, startSymbol)); token = lexer.GetNextToken(); // skipping RANGLE token = lexer.GetNextToken(); symbolNames.Add(startSymbol); symbolCodes[startSymbol] = symbolNames.Count - 1; string userObjectType = null; if (token.SymbolCode == CODE_USEROBJECT) { token = lexer.GetNextToken(); if (token.SymbolCode == CODE_QUOTED) { userObjectType = token.Value.Substring(1, token.Value.Length - 2); token = lexer.GetNextToken(); } else { StringBuilder typeBuilder = new StringBuilder(token.Value); token = lexer.GetNextToken(); while (token.SymbolCode == CODE_DOT) { typeBuilder.Append("."); token = lexer.GetNextToken(); typeBuilder.Append(token.Value); token = lexer.GetNextToken(); } userObjectType = typeBuilder.ToString(); } } //naše 0. pravidlo, které výstižně popisuje způsob, jakým si gramatiku upravujeme productions.Add(new ProductionWithAction(new Production( symbolCodes["$start"], new int[] { symbolCodes[startSymbol], symbolCodes["$end"] }), "{ return _1; }")); reducibleNonterminals.Add(symbolCodes["$start"]); usedNonterminals.Add(symbolCodes[startSymbol]); terminalUsed[symbolCodes["$end"]] = true; var typeMappings = new Dictionary<string, string>(); //zpracování pravidel while (token.SymbolCode != CODE_END) { if (token.SymbolCode == CODE_TYPE) { token = lexer.GetNextToken(); // skipping LANGLE token = lexer.GetNextToken(); string nonterminal = token.Value; if (symbolCodes.ContainsKey(nonterminal) && symbolCodes[nonterminal] < numTerminals) errorMessages.Add(string.Format("{0},{1}: The nonterminal <{2}> shares it's name with a terminal symbol.", token.LineNumber, token.ColumnNumber, nonterminal)); if (!symbolCodes.ContainsKey(nonterminal)) { symbolNames.Add(nonterminal); symbolCodes[nonterminal] = symbolNames.Count - 1; } token = lexer.GetNextToken(); // skipping RANGLE token = lexer.GetNextToken(); if (token.SymbolCode == CODE_QUOTED) { // QUOTED typeMappings.Add(nonterminal, token.Value.Substring(1, token.Value.Length - 2)); token = lexer.GetNextToken(); } else { StringBuilder typeBuilder = new StringBuilder(token.Value); token = lexer.GetNextToken(); while (token.SymbolCode == CODE_DOT) { typeBuilder.Append("."); token = lexer.GetNextToken(); typeBuilder.Append(token.Value); token = lexer.GetNextToken(); } typeMappings.Add(nonterminal, typeBuilder.ToString()); } } else { //extrahujeme symbol na levé straně a zpracujeme ho // skipping LANGLE token = lexer.GetNextToken(); string lhsSymbol = token.Value; if (symbolCodes.ContainsKey(lhsSymbol) && symbolCodes[lhsSymbol] < numTerminals) errorMessages.Add(string.Format("{0},{1}: The nonterminal <{2}> shares it's name with a terminal symbol.", token.LineNumber, token.ColumnNumber, lhsSymbol)); if (!symbolCodes.ContainsKey(lhsSymbol)) { symbolNames.Add(lhsSymbol); symbolCodes[lhsSymbol] = symbolNames.Count - 1; } if (!reducibleNonterminals.Contains(symbolCodes[lhsSymbol])) reducibleNonterminals.Add(symbolCodes[lhsSymbol]); token = lexer.GetNextToken(); //skipping RANGLE token = lexer.GetNextToken(); int lhsSymbolCode = symbolCodes[lhsSymbol]; //Zpracujeme výraz na pravé straně, který může sestávat z několika seznamů symbolů oddělenými //ořítky. Každý z těchto seznamů pak tvoří jedno pravidlo bez ořítek. while ((token.SymbolCode == CODE_DERIVES) || (token.SymbolCode == CODE_OR)) { token = lexer.GetNextToken(); List<int> rhsSymbols = new List<int>(); while (token.SymbolCode != CODE_CODE) { int rhsSymbolCode = -1; if (token.SymbolCode == CODE_LANGLE) { //skipping LANGLE token = lexer.GetNextToken(); string rhsSymbol = token.Value; if (symbolCodes.ContainsKey(rhsSymbol) && symbolCodes[rhsSymbol] < numTerminals) errorMessages.Add(string.Format("{0},{1}: The nonterminal <{2}> shares it's name with a terminal symbol.", token.LineNumber, token.ColumnNumber, rhsSymbol)); if (!symbolCodes.ContainsKey(rhsSymbol)) { symbolNames.Add(rhsSymbol); symbolCodes[rhsSymbol] = symbolNames.Count - 1; } if (!usedNonterminals.Contains(symbolCodes[rhsSymbol])) usedNonterminals.Add(symbolCodes[rhsSymbol]); token = lexer.GetNextToken(); //skipping RANGLE token = lexer.GetNextToken(); rhsSymbolCode = symbolCodes[rhsSymbol]; } else { string rhsSymbol = token.Value; if (!symbolCodes.ContainsKey(rhsSymbol)) errorMessages.Add(string.Format("{0},{1}: The terminal '{2}' is used but not defined.", token.LineNumber, token.ColumnNumber, rhsSymbol)); else { rhsSymbolCode = symbolCodes[rhsSymbol]; terminalUsed[rhsSymbolCode] = true; } token = lexer.GetNextToken(); } rhsSymbols.Add(rhsSymbolCode); } string code = token.Value; token = lexer.GetNextToken(); productions.Add(new ProductionWithAction(new Production(lhsSymbolCode, rhsSymbols), code)); } } } //ToArray voláme proto, aby se líná metoda Intersect vyhodnotila a nedošlo by pak při vykonávání //dalšího příkazu k chybě int[] theGoodOnes = usedNonterminals.Intersect(reducibleNonterminals).ToArray(); usedNonterminals.ExceptWith(theGoodOnes); reducibleNonterminals.ExceptWith(theGoodOnes); foreach (int nonterminal in usedNonterminals) warningMessages.Add(string.Format("Warning: The nonterminal <{0}> isn't reducible.", symbolNames[nonterminal])); foreach (int nonterminal in reducibleNonterminals) warningMessages.Add(string.Format("Warning: The nonterminal <{0}> is defined but never used.", symbolNames[nonterminal])); for (int terminal = 0; terminal < numTerminals; terminal++) if (!terminalUsed[terminal]) warningMessages.Add(string.Format("Warning: The terminal '{0}' is defined but never used.", symbolNames[terminal])); if (errorMessages.Count > 0) throw new InvalidSpecificationException(errorMessages.Concat(warningMessages)); //už máme vše načte a zkontrolováno, teď už jen setřídíme pravidla podle levé strany, //přečíslujeme je a pro každý neterminál dopočítáme indexy, na kterých začínají pravidla //s daným neterminálem Production[] productionsArray = new Production[productions.Count]; productionsArray[0] = productions[0].Production; string[] actions = new string[productions.Count]; actions[0] = productions[0].Action; IEnumerable<ProductionWithAction> sortedProductions = productions.GetRange(1, productions.Count - 1).OrderBy((prod => prod.Production.LHSSymbol)); int k = 1; foreach (ProductionWithAction productionWithAction in sortedProductions) { productionsArray[k] = productionWithAction.Production; productionsArray[k].ProductionCode = k; actions[k] = productionWithAction.Action; k++; } int numNonterminals = symbolCodes.Count - numTerminals; int[] nonterminalProductionOffset = new int[numNonterminals + 1]; int offset = 0; for (int nonterminal = 0; nonterminal < numNonterminals; nonterminal++) { nonterminalProductionOffset[nonterminal] = offset; while ((offset < productionsArray.Length) && (productionsArray[offset].LHSSymbol == numTerminals + nonterminal)) offset++; } nonterminalProductionOffset[nonterminalProductionOffset.Length - 1] = offset; string[] nonterminalTypes = new string[numNonterminals]; foreach (var typeMapping in typeMappings) { nonterminalTypes[symbolCodes[typeMapping.Key] - numTerminals] = typeMapping.Value; } //a teď už to jen zabalíme a pošleme GrammarDefinition grammarDefinition = new GrammarDefinition(symbolNames.ToArray(), productionsArray, nonterminalProductionOffset, numTerminals); LexerData lexerData = new LexerData(regex, groupSymbolCodes); GrammarCode grammarCode = new GrammarCode(headerCode, actions, nonterminalTypes, userObjectType); Grammar grammar = new Grammar(grammarDefinition, lexerData, grammarCode); return grammar; #else LexerData lexerData; ParserData parserData; Grammar.ReadRuntimeDataFromStream( new MemoryStream(YetAnotherParserGenerator.Properties.Resources.SpecificationGrammar), out lexerData, out parserData); GrammarLexer lexer = new GrammarLexer(); Parser parser = new Parser(parserData); GrammarParserLocals locals = new GrammarParserLocals(specificationPath, out warningMessages); lexer.SourceString = File.ReadAllText(specificationPath); return (Grammar)parser.Parse(lexer, locals); #endif }