/// <summary> /// Tokenizes the source code and returns a list of tokens /// </summary> /// <param name="text">The CPS source code to tokenize (as an array of characters)</param> /// <param name="version">Version of the tokenizer to use.</param> /// <returns>A List of tokens</returns> public static List <Token> Tokenize(char[] text, TokenizerVersion version) { var tokenizer = new PropertySheetTokenizer(text, version); tokenizer.Tokenize(); return(tokenizer.Tokens); }
/// <summary> /// Tokenizes the source code and returns a list of tokens /// </summary> /// <param name="text">The CPS source code to tokenize (as an array of characters)</param> /// <param name="version">Version of the tokenizer to use.</param> /// <returns>A List of tokens</returns> public static new List<Token> Tokenize(char[] text, TokenizerVersion version) { var tokenizer = new PropertySheetTokenizer(text, version); tokenizer.Tokenize(); return tokenizer.Tokens; }
protected PropertySheet Parse() { var tokenStream = PropertySheetTokenizer.Tokenize(_propertySheetText); var state = ParseState.Global; var enumerator = tokenStream.GetEnumerator(); var importFilename = string.Empty; // var startFolder = System.Environment.CurrentDirectory; var startFolder = Path.GetDirectoryName(_filename.GetFullPath()); // first, check for a .user file to auto-import if (!string.IsNullOrEmpty(_filename)) { var userSheetFilename = Path.Combine(startFolder, Path.GetFileName(_filename.GetFullPath()) + ".user"); if (File.Exists(userSheetFilename)) { _propertySheet.UserOverride = ParseIncludedSheet(null, userSheetFilename, File.ReadAllText(userSheetFilename)); } } Token token; Rule rule = null; string ruleName = "*"; string ruleParameter = null; string ruleClass = null; string ruleId = null; // PropertyRule property = null; var sourceLocation = new SourceLocation { Row = 0, Column = 0, SourceFile = null, }; string propertyName = null; string propertyLabelText = null; string presentlyUnknownValue = null; // string collectionName = null; List <string> multidimensionalLambda = null; enumerator.MoveNext(); do { token = enumerator.Current; switch (token.Type) { // regardless where we are, skip over whitespace, etc. case TokenType.WhiteSpace: case TokenType.LineComment: case TokenType.MultilineComment: continue; } switch (state) { case ParseState.Global: sourceLocation = new SourceLocation { // will be the start of the next new rule. Row = token.Row, Column = token.Column, SourceFile = _filename, }; switch (token.Type) { case TokenType.Identifier: // look for identifier as the start of a selector if (token.Data == "@import") { // special case to handle @import rules state = ParseState.Import; continue; } state = ParseState.Selector; ruleName = token.Data; continue; case TokenType.Dot: state = ParseState.SelectorDot; ruleName = "*"; // take next identifier as the classname continue; case TokenType.Pound: state = ParseState.SelectorPound; ruleName = "*"; // take next identifier as the id continue; case TokenType.Semicolon: // tolerate extra semicolons. continue; default: throw new EndUserParseException(token, _filename, "PSP 100", "Expected one of '.' , '#' or identifier"); } case ParseState.Import: switch (token.Type) { case TokenType.StringLiteral: case TokenType.Identifier: state = ParseState.ImportFilename; importFilename = token.Data; continue; default: throw new EndUserParseException(token, _filename, "PSP 121", "Expected a string literal for filename (Missing semicolon?)"); } case ParseState.ImportFilename: switch (token.Type) { case TokenType.Semicolon: if (!Import(importFilename, startFolder)) { throw new EndUserParseException(token, _filename, "PSP 122", "Imported file '{0}' not found", importFilename); } state = ParseState.Global; continue; default: throw new EndUserParseException(token, _filename, "PSP 121", "Expected a string literal for filename"); } case ParseState.Selector: switch (token.Type) { case TokenType.Dot: state = ParseState.SelectorDot; continue; case TokenType.Pound: state = ParseState.SelectorPound; continue; case TokenType.SelectorParameter: ruleParameter = token.Data; if (ruleParameter.IndexOfAny("\r\n".ToCharArray()) >= 0) { throw new EndUserParseException(token, _filename, "PSP 123", "Selector parameter may not contain CR/LFs (missing close bracket?): {0} ", Rule.CreateSelectorString(ruleName, ruleParameter, ruleClass, ruleId)); } continue; case TokenType.OpenBrace: state = ParseState.InRule; rule = _propertySheet[ruleName, ruleParameter, ruleClass, ruleId]; ruleName = null; ruleParameter = null; ruleClass = null; ruleId = null; rule.SourceLocation = sourceLocation; continue; default: throw new EndUserParseException(token, _filename, "PSP 101", "Expected one of '.' , '#' , '[' or '{{' ."); } case ParseState.SelectorDot: switch (token.Type) { case TokenType.Identifier: ruleClass = token.Data; state = ParseState.Selector; continue; default: throw new EndUserParseException(token, _filename, "PSP 102", "Expected identifier"); } case ParseState.SelectorPound: switch (token.Type) { case TokenType.Identifier: ruleId = token.Data; state = ParseState.Selector; continue; default: throw new EndUserParseException(token, _filename, "PSP 103", "Expected identifier"); } case ParseState.InRule: switch (token.Type) { case TokenType.Semicolon: // extra semicolons are tolerated. continue; case TokenType.StringLiteral: case TokenType.Identifier: propertyName = token.Data; state = ParseState.HavePropertyName; sourceLocation = new SourceLocation { Row = token.Row, Column = token.Column, SourceFile = _filename, }; continue; case TokenType.CloseBrace: // this rule is DONE. rule = null; // set this to null, so that we don't accidentally add new stuff to this rule. state = ParseState.Global; continue; default: throw new EndUserParseException(token, _filename, "PSP 104", "In rule, expected semi-colon ';', close-brace '}}' or value."); } case ParseState.HavePropertyName: switch (token.Type) { case TokenType.Colon: state = ParseState.HavePropertySeparator; // property = rule.GetPropertyRule(propertyName); continue; default: throw new EndUserParseException(token, _filename, "PSP 105", "Found rule property name, expected colon ':'."); } case ParseState.HavePropertySeparator: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: state = ParseState.HavePropertyLabel; if ("@Literal" == token.RawData) { propertyLabelText = token.Data; } else { propertyLabelText = token.Data; } continue; case TokenType.Identifier: state = ParseState.HavePropertyLabel; propertyLabelText = token.Data; continue; case TokenType.OpenBrace: state = ParseState.InPropertyCollectionWithoutLabel; continue; default: throw new EndUserParseException(token, _filename, "PSP 106", "After rule property name, expected value, open-brace '{{' or open-parenthesis '('."); } case ParseState.InPropertyCollectionWithoutLabel: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: { // at this point it could be a collection, a label, or a value. presentlyUnknownValue = token.Data; state = ParseState.InPropertyCollectionWithoutLabelButHaveSomething; // we're going to peek ahead and see if there is any characters we want to accept. (#.-+) var peek = enumerator; var cont = true; var okToTakeIdentifierOrNumeric = false; do { if (!peek.MoveNext()) { break; } // if we find a character that we consider to be ok for labels here, we're going to add it in, and consume the token. switch (peek.Current.Type) { case TokenType.Pound: case TokenType.Dot: case TokenType.Minus: case TokenType.MinusMinus: case TokenType.Plus: case TokenType.PlusPlus: enumerator.MoveNext(); presentlyUnknownValue = presentlyUnknownValue + enumerator.Current.Data; okToTakeIdentifierOrNumeric = true; break; case TokenType.NumericLiteral: case TokenType.Identifier: if (!okToTakeIdentifierOrNumeric) { cont = false; break; } enumerator.MoveNext(); presentlyUnknownValue = presentlyUnknownValue + enumerator.Current.Data; okToTakeIdentifierOrNumeric = false; break; default: cont = false; break; } } while (cont); } continue; case TokenType.CloseBrace: state = ParseState.HavePropertyCompleted; // this makes the semicolon optional. //state = ParseState.InRule; continue; case TokenType.OpenParenthesis: state = ParseState.OpenBraceExpectingMultidimesionalLamda; multidimensionalLambda = new XList <string>(); continue; default: throw new EndUserParseException(token, _filename, "PSP 107", "In property collection, expected value or close brace '}}'"); } case ParseState.OpenBraceExpectingMultidimesionalLamda: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: // looks like we have the name of a collection for a multidimensional lambda multidimensionalLambda.Add(token.Data); state = ParseState.HasMultidimensionalLambdaIdentifier; continue; default: throw new EndUserParseException(token, _filename, "PSP 124", "In multidimensional lambda declaration, expected identifier, found '{0}'", token.Data); } case ParseState.HasMultidimensionalLambdaIdentifier: switch (token.Type) { case TokenType.Comma: state = ParseState.OpenBraceExpectingMultidimesionalLamda; continue; case TokenType.CloseParenthesis: state = ParseState.NextTokenBetterBeLambda; continue; default: throw new EndUserParseException(token, _filename, "PSP 125", "In multidimensional lambda declaration, expected close parenthesis or comma, found '{0}'", token.Data); } case ParseState.NextTokenBetterBeLambda: switch (token.Type) { case TokenType.Lambda: // we already knew that it was going to be this. // the collection has all the appropriate values. presentlyUnknownValue = null; state = ParseState.HasLambda; continue; default: throw new EndUserParseException(token, _filename, "PSP 125", "Expected lambda '=>' found '{0}'", token.Data); } case ParseState.InPropertyCollectionWithoutLabelButHaveSomething: switch (token.Type) { case TokenType.Lambda: multidimensionalLambda = new XList <string> { presentlyUnknownValue }; presentlyUnknownValue = null; state = ParseState.HasLambda; continue; case TokenType.Equal: // looks like it's gonna be a label = value type. propertyLabelText = presentlyUnknownValue; presentlyUnknownValue = null; state = ParseState.HasEqualsInCollection; continue; case TokenType.Comma: { // turns out its a simple collection item. var pv = rule.GetPropertyRule(propertyName).GetPropertyValue(string.Empty); pv.Add(presentlyUnknownValue); pv.SourceLocation = sourceLocation; presentlyUnknownValue = null; state = ParseState.InPropertyCollectionWithoutLabel; } continue; case TokenType.CloseBrace: { // turns out its a simple collection item. var pv = rule.GetPropertyRule(propertyName).GetPropertyValue(string.Empty); pv.Add(presentlyUnknownValue); pv.SourceLocation = sourceLocation; presentlyUnknownValue = null; // state = ParseState.HavePropertyCompleted; // this makes the semicolon optional. state = ParseState.InRule; } continue; case TokenType.OpenBracket: case TokenType.OpenBrace: case TokenType.OpenParenthesis: case TokenType.LessThan: // starting a new script block // presentlyUnknownValue is the script type // the content goes until the matching close token. continue; case TokenType.Identifier: case TokenType.NumericLiteral: case TokenType.StringLiteral: // starting a new script block // presentlyUnknownValue is the script type // the content of the string literal is the script content rule.AddScriptedPropertyRule(propertyName, presentlyUnknownValue, token.Data, token.Data); continue; default: string tokentext = token.RawData.ToString(); if (tokentext.Length == 1) { // starting a new script block // presentlyUnknownValue is the script type // the content goes until we see that same token again. continue; } throw new EndUserParseException(token, _filename, "PSP 114", "after an value or identifier in a collection expected a '=>' or '=' or ',' ."); } case ParseState.HasEqualsInCollection: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: { var pv = rule.GetPropertyRule(propertyName).GetPropertyValue(propertyLabelText); pv.Add(token.Data); pv.SourceLocation = sourceLocation; state = ParseState.InPropertyCollectionWithoutLabelWaitingForComma; } continue; case TokenType.OpenBrace: state = ParseState.InPropertyCollectionWithLabel; continue; default: throw new EndUserParseException(token, _filename, "PSP 119", "after an equals '=' in a collection, expected a value or identifier."); } case ParseState.HasLambda: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: propertyLabelText = token.Data; state = ParseState.HasLambdaAndLabel; continue; default: throw new EndUserParseException(token, _filename, "PSP 115", "After the '=>' in a collection, expected value or identifier."); } case ParseState.HasLambdaAndLabel: switch (token.Type) { case TokenType.Equal: state = ParseState.HasLambdaAndLabelAndEquals; continue; case TokenType.Semicolon: case TokenType.Comma: case TokenType.CloseBrace: { // assumes "${DEFAULTLAMBDAVALUE}" for the lamda value /* ORIG: * var pv = property.GetPropertyValue(propertyLabelText, multidimensionalLambda); * pv.Add("${DEFAULTLAMBDAVALUE}"); */ var pv = rule.GetPropertyRule(propertyName).GetPropertyValue("", multidimensionalLambda); pv.Add(propertyLabelText); pv.SourceLocation = sourceLocation; propertyLabelText = null; multidimensionalLambda = null; state = ParseState.InPropertyCollectionWithoutLabelWaitingForComma; } if (token.Type == TokenType.CloseBrace) { // and, we're closing out this property. // state = ParseState.HavePropertyCompleted; // this makes the semicolon optional. state = ParseState.InRule; } continue; default: throw new EndUserParseException(token, _filename, "PSP 116", "After the '{0} => {1}' in collection, expected '=' ", multidimensionalLambda.Aggregate("(", (current, each) => current + ", " + each) + ")", propertyLabelText); } case ParseState.HasLambdaAndLabelAndEquals: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: { var pv = rule.GetPropertyRule(propertyName).GetPropertyValue(propertyLabelText, multidimensionalLambda); pv.Add(token.Data); pv.SourceLocation = sourceLocation; propertyLabelText = null; multidimensionalLambda = null; state = ParseState.InPropertyCollectionWithoutLabelWaitingForComma; } continue; default: throw new EndUserParseException(token, _filename, "PSP 117", "After the '{0} => {1} = ' in collection, expected a value or identifier", multidimensionalLambda.Aggregate("(", (current, each) => current + ", " + each) + ")", propertyLabelText); } case ParseState.InPropertyCollectionWithoutLabelWaitingForComma: switch (token.Type) { case TokenType.Comma: case TokenType.Semicolon: state = ParseState.InPropertyCollectionWithoutLabel; continue; case TokenType.CloseBrace: // state = ParseState.HavePropertyCompleted; // this makes the semicolon optional. state = ParseState.InRule; continue; default: throw new EndUserParseException(token, _filename, "PSP 118", "After complete expression or value in a collection, expected ',' or '}}'."); } case ParseState.InPropertyCollectionWithLabel: switch (token.Type) { case TokenType.StringLiteral: case TokenType.NumericLiteral: case TokenType.Identifier: presentlyUnknownValue = token.Data; state = ParseState.HaveCollectionValue; continue; case TokenType.CloseBrace: //state = ParseState.HavePropertyCompleted; // this makes the semicolon optional. state = ParseState.InPropertyCollectionWithoutLabelWaitingForComma; continue; default: throw new EndUserParseException(token, _filename, "PSP 107", "In property collection, expected value or close brace '}}'"); } case ParseState.HaveCollectionValue: switch (token.Type) { case TokenType.Semicolon: case TokenType.Comma: { var pv = rule.GetPropertyRule(propertyName).GetPropertyValue(propertyLabelText); pv.Add(presentlyUnknownValue); pv.SourceLocation = sourceLocation; // propertyLabelText = null; state = ParseState.InPropertyCollectionWithLabel; } continue; case TokenType.CloseBrace: { var pv = rule.GetPropertyRule(propertyName).GetPropertyValue(propertyLabelText); pv.Add(presentlyUnknownValue); pv.SourceLocation = sourceLocation; propertyLabelText = null; state = ParseState.HavePropertyCompleted; // this makes the semicolon optional. // state = ParseState.InRule; } continue; default: throw new EndUserParseException(token, _filename, "PSP 108", "With property collection value, expected comma ',' or close-brace '}}'."); } case ParseState.HavePropertyLabel: switch (token.Type) { case TokenType.Equal: state = ParseState.HavePropertyEquals; continue; case TokenType.Dot: var t = SkipToNext(ref enumerator); if (!t.HasValue) { throw new EndUserParseException(token, _filename, "PSP 109", "Unexpected end of Token stream [HavePropertyLabel]"); } token = t.Value; if (token.Type == TokenType.Identifier || token.Type == TokenType.NumericLiteral) { propertyLabelText += "." + token.Data; } else { throw new EndUserParseException(token, _filename, "PSP 110", "Expected identifier or numeric literal after Dot '.'."); } continue; case TokenType.Semicolon: { // it turns out that what we thought the label was, is really the property value, // the label is an empty string var pv = rule.GetPropertyRule(propertyName).GetPropertyValue(string.Empty); pv.Add(propertyLabelText); pv.SourceLocation = sourceLocation; propertyName = propertyLabelText = null; state = ParseState.InRule; } continue; default: throw new EndUserParseException(token, _filename, "PSP 111", "After property value, expected semi-colon ';' or equals '='."); } case ParseState.HavePropertyEquals: switch (token.Type) { case TokenType.Identifier: case TokenType.StringLiteral: case TokenType.NumericLiteral: { // found our property-value. add it, and move along. var pv = rule.GetPropertyRule(propertyName).GetPropertyValue(propertyLabelText); pv.Add(token.Data); pv.SourceLocation = sourceLocation; propertyName = propertyLabelText = null; state = ParseState.HavePropertyCompleted; } continue; case TokenType.OpenBrace: // we're starting a new collection (where we have the label already). state = ParseState.InPropertyCollectionWithLabel; continue; default: throw new EndUserParseException(token, _filename, "PSP 112", "After equals in property, expected value or close-brace '{B{'."); } case ParseState.HavePropertyCompleted: switch (token.Type) { case TokenType.CloseBrace: case TokenType.Semicolon: state = ParseState.InRule; continue; default: throw new EndUserParseException(token, _filename, "PSP 113", "After property completed, expected semi-colon ';'."); } default: throw new EndUserParseException(token, _filename, "PSP 120", "CATS AND DOGS, LIVINGTOGETHER..."); } } while (enumerator.MoveNext()); return(_propertySheet); }