/// <summary> /// Gets a list of styles for a specific pattern context member. /// </summary> private List <IroStyle> GetPatternStyles(List <string> styleNames, IroPrecompileData data) { //Check that styles exist, and try to get the first one out. if (styleNames.Count == 0) { Error.Compile("No style was defined for a pattern. All styles must have patterns."); return(null); } //Get all the patterns out. var styles = new List <IroStyle>(); foreach (var style in styleNames) { //Get the styles form the style list. int index = data.Styles.FindIndex(x => x.Name == style); if (index == -1) { Error.Compile("A style '" + style + "' is referenced in a pattern, but it is not defined in the style map."); return(null); } styles.Add(data.Styles[index]); } //Make sure all the patterns have textmate scopes. if (styles.Where(x => x.TextmateScope != null).Count() != styles.Count) { Error.Compile("One or more styles for a pattern does not have a textmate scope defined."); return(null); } return(styles); }
/// <summary> /// Adds a single pattern to the list of patterns. /// </summary> private void AddPattern(ref StringBuilder text, ContextMember pattern, IroPrecompileData data) { //What type is it? switch (pattern.Type) { case ContextMemberType.Include: text.AppendLine("<dict>"); text.AppendLine("<key>include</key>"); text.AppendLine("<string>#" + pattern.Data + "</string>"); text.AppendLine("</dict>"); break; case ContextMemberType.Pattern: AddPatternRaw(ref text, (PatternContextMember)pattern, data); break; case ContextMemberType.InlinePush: AddInlinePush(ref text, (InlinePushContextMember)pattern, data); break; case ContextMemberType.Push: throw new NotImplementedException(); case ContextMemberType.Pop: throw new NotImplementedException(); default: Error.CompileWarning("Failed to add pattern, unrecognized context member type '" + pattern.Type.ToString() + "'."); return; } }
/// <summary> /// Adds a single context to the builder. /// </summary> private void AddContext(ref StringBuilder text, IroContext context, IroPrecompileData data) { text.AppendLine("<key>" + context.Name + "</key>"); text.AppendLine("<dict>"); //Define all the patterns in this context. text.AppendLine("<key>patterns</key>"); text.AppendLine("<array>"); foreach (var pattern in context.Members) { AddPattern(ref text, pattern, data); } text.AppendLine("</array>"); text.AppendLine("</dict>"); }
/// <summary> /// Compiles a set of Algo variables given targets. /// </summary> public static List <CompileResult> Compile(Dictionary <string, IroVariable> vars, params ICompileTarget[] targets) { //Set locals for this compile. Variables = vars; var pcd = new IroPrecompileData(); //Parse all the top level flags. ParseTopLevelFlags(vars, ref pcd); //todo: parse other top level flags Error.CompileWarning("Some top-level flags are missing and/or not implemented yet."); //Find constants. var constants = vars.Select(x => x.Value.Type == VariableType.Value && x.Key.StartsWith("__")).Cast <IroValue>(); //Check that the "styles" and "contexts" system sets are there. if (!vars.ContainsKey("styles") || vars["styles"].Type != VariableType.Set) { Error.Compile("The 'styles' set does not exist, you must define a list of styles to use for your grammar."); return(null); } if (!vars.ContainsKey("contexts") || vars["contexts"].Type != VariableType.Set) { Error.Compile("The 'contexts' set does not exist, you must define a list of contexts to pattern match your styles to."); return(null); } //Crawl through the styles set and define them in the list. var stylesSet = (IroSet)vars["styles"]; foreach (var style in stylesSet) { var thisStyle = new IroStyle(style.Key); //Make sure this is an object, can't have any attributes in the styles list. if (style.Value.Type != VariableType.Set) { //Skip the value. Error.CompileWarning("A non-set value '" + style.Key + "' is defined in the styles map. Only style sets should be defined in the 'styles' map."); continue; } var styleDefinition = (IroSet)style.Value; if (styleDefinition.SetType != "style") { Error.CompileWarning("A set in the styles map is defined as having type '" + styleDefinition.SetType + "', which is not a style. Skipping."); continue; } //Switch on all the values in that set and define style based on the keys. foreach (var styleProperty in styleDefinition) { //Is the value a string? if (styleProperty.Value.Type != VariableType.Value) { Error.CompileWarning("Failed to create style with name '" + style.Key + ", non-string value defined in the style object."); continue; } string value = ((IroValue)styleProperty.Value).Value; //Switch on the property. switch (styleProperty.Key) { case "color": case "colour": thisStyle.Colour = value; break; case "background_colour": case "background_color": case "ace_scope": thisStyle.AceScope = value; break; case "textmate_scope": thisStyle.TextmateScope = value; break; case "pygments_scope": thisStyle.PygmentsScope = value; break; case "highlight_js_scope": thisStyle.HighlightJSScope = value; break; case "bold": if (value == "true") { thisStyle.Bold = true; } else if (value == "false") { thisStyle.Bold = false; } else { Error.CompileWarning("Unrecognized value for boolean property 'bold', not 'true' or 'false'. Assumed 'false'."); thisStyle.Bold = false; } break; case "italic": if (value == "true") { thisStyle.Italic = true; } else if (value == "false") { thisStyle.Italic = false; } else { Error.CompileWarning("Unrecognized value for boolean property 'bold', not 'true' or 'false'. Assumed 'false'."); thisStyle.Italic = false; } break; default: Error.CompileWarning("Invalid property in style set '" + style.Key + "' with name '" + styleProperty.Key + "'."); continue; } } //Add the style to the list. pcd.Styles.Add(thisStyle); } //Styles are done processing, start processing the contexts. var contextsSet = (IroSet)vars["contexts"]; foreach (var context in contextsSet) { //Is the set a context? if (context.Value.Type != VariableType.Set) { Error.CompileWarning("Could not create context '" + context.Key + "', contexts must be sets of type 'context'."); continue; } if (((IroSet)context.Value).SetType != "context") { Error.CompileWarning("Could not create context '" + context.Key + "', values in the contexts array must be sets of type 'context'."); continue; } pcd.Contexts.Add(ProcessContext(context.Key, (IroSet)context.Value, contextsSet)); } //Use precompile data to process the given targets. var results = new List <CompileResult>(); foreach (var target in targets) { results.Add(target.Compile(pcd)); } return(results); }
/// <summary> /// Parses all the possible top level flags for Iro. /// </summary> private static void ParseTopLevelFlags(Dictionary <string, IroVariable> vars, ref IroPrecompileData pcd) { //Verify that the required keys exist. if (!vars.ContainsKey("name")) { Error.Compile("No 'name' variable defined to name the grammar."); return; } if (!vars.ContainsKey("file_extensions")) { Error.Compile("No 'file_extensions' variable defined to name the file extensions compatible with this grammar."); return; } if (vars["name"].Type != VariableType.Value) { Error.Compile("The 'name' variable must be a string."); return; } if (vars["file_extensions"].Type != VariableType.Array) { Error.Compile("The 'file_extensions' variable must define an array of file extensions (currently not an array)."); return; } //Set name. pcd.Name = ((IroValue)vars["name"]).Value; //Set file extensions. IroList fileExts = (IroList)vars["file_extensions"]; foreach (var ext in fileExts.Contents) { if (ext.Type != VariableType.Value) { Error.Compile("All file extensions must be string values (detected type " + ext.Type.ToString() + " in array)."); return; } pcd.FileExtensions.Add(((IroValue)ext).Value); } //Parsing other top-level flags. //TEXTMATE UUID GetTopLevelProperty("textmate_uuid", ref pcd.UUID, ref vars); //DESCRIPTION GetTopLevelProperty("description", ref pcd.Description, ref vars); //COLOUR GetTopLevelProperty("color", ref pcd.Colour, ref vars); //BACKGROUND COLOUR GetTopLevelProperty("background_color", ref pcd.BackgroundColour, ref vars); }
/// <summary> /// Adds a PatternContextMember to the text, rather than a pattern that could possibly be any ContextMember. /// </summary> private void AddPatternRaw(ref StringBuilder text, PatternContextMember pattern, IroPrecompileData data) { //Get styles from the pattern. var styles = GetPatternStyles(pattern.Styles, data); //Is the amount of patterns equal to the amount of context groups? //Use a hack of replacing bracket groups with normal letters. if (!Compiler.GroupsMatch(pattern.Data, styles.Count)) { Error.Compile("Mismatch between capture groups and number of styles for pattern with regex '" + pattern.Data + "'."); return; } //Add the initial match. text.AppendLine("<dict>"); text.AppendLine("<key>match</key>"); text.AppendLine("<string>" + pattern.Data + "</string>"); //Only one style? Just use the 'name' property. if (pattern.Styles.Count == 1) { text.AppendLine("<key>name</key>"); text.AppendLine("<string>" + styles[0].TextmateScope + "." + data.Name + "</string>"); } else { //Multiple styles, define capture groups. text.AppendLine("<key>captures</key>"); text.AppendLine("<dict>"); for (int i = 0; i < styles.Count; i++) { text.AppendLine("<key>" + (i + 1) + "</key>"); text.AppendLine("<dict>"); text.AppendLine("<key>name</key>"); text.AppendLine("<string>" + styles[i].TextmateScope + "." + data.Name + "</string>"); text.AppendLine("</dict>"); } text.AppendLine("</dict>"); } text.AppendLine("</dict>"); }
/// <summary> /// Compiles a set of pre compile data into a textmate file. /// </summary> public CompileResult Compile(IroPrecompileData data) { var text = new StringBuilder(); //Add pre-baked headers. text.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); text.AppendLine("<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"); text.AppendLine("<plist version=\"1.0\">"); text.AppendLine("<!-- Generated via. Iro4CLI -->"); //Start the dictionary. text.AppendLine("<dict>"); //Input the file types. if (data.FileExtensions == null || data.FileExtensions.Count == 0) { Error.Compile("No file extensions provided to map grammar against. Use 'file_extensions' to define them."); return(null); } text.AppendLine("<key>fileTypes</key>"); text.AppendLine("<array>"); foreach (var type in data.FileExtensions) { text.AppendLine("<string>" + type + "</string>"); } text.AppendLine("</array>"); //The name of the grammar. text.AppendLine("<key>name</key>"); text.AppendLine("<string>" + data.Name + "</string>"); //Pattern array, just including the main context here. if (data.Contexts.FindIndex(x => x.Name == "main") == -1) { Error.Compile("No entrypoint context named 'main' exists. You need to make a context named 'main' to start the grammar state at."); return(null); } text.AppendLine("<key>patterns</key>"); text.AppendLine("<array>"); text.AppendLine("<dict>"); text.AppendLine("<key>include</key>"); text.AppendLine("<string>#main</string>"); text.AppendLine("</dict>"); text.AppendLine("</array>"); //Name of the source. text.AppendLine("<key>scopeName</key>"); text.AppendLine("<string>source." + data.Name + "</string>"); //UUID text.AppendLine("<key>uuid</key>"); text.AppendLine("<string>" + data.UUID + "</string>"); //Main repository for all contexts. text.AppendLine("<key>repository</key>"); text.AppendLine("<dict>"); //Define all the contexts inside the array. foreach (var context in data.Contexts) { //Add the pre-existing context. AddContext(ref text, context, data); } //Contexts done parsing, complete all queued contexts. foreach (var queued in pendingContexts) { AddContext(ref text, new IroContext(queued.Key) { Members = queued.Value }, data); } pendingContexts = new Dictionary <string, List <ContextMember> >(); //Close the textmate scopes. text.AppendLine("</dict>"); text.AppendLine("</dict>"); text.AppendLine("</plist>"); return(new CompileResult() { GeneratedFile = FormatXml(text.ToString()), Target = Target.Textmate }); }
/// <summary> /// Adds a InlinePushContextMember to the text. /// </summary> private void AddInlinePush(ref StringBuilder text, InlinePushContextMember pattern, IroPrecompileData data) { //Get styles from the pattern. var styles = GetPatternStyles(pattern.Styles, data); text.AppendLine("<dict>"); //Patterns match up with context groups? if (!Compiler.GroupsMatch(pattern.Data, styles.Count)) { Error.Compile("Mismatch between capture groups and number of styles for inline push with regex '" + pattern.Data + "'."); return; } //Begin capture regex. text.AppendLine("<key>begin</key>"); text.AppendLine("<string>" + pattern.Data + "</string>"); //Begin capture styles. text.AppendLine("<key>beginCaptures</key>"); text.AppendLine("<dict>"); for (int i = 0; i < styles.Count; i++) { text.AppendLine("<key>" + (i + 1) + "</key>"); text.AppendLine("<dict>"); text.AppendLine("<key>name</key>"); text.AppendLine("<string>" + styles[i].TextmateScope + "." + data.Name + "</string>"); text.AppendLine("</dict>"); } text.AppendLine("</dict>"); //Is a default style assigned? int defaultStyleIndex = pattern.Patterns.FindIndex(x => x.Type == ContextMemberType.DefaultStyle); if (defaultStyleIndex != -1) { //Are other patterns defined? if (pattern.Patterns.FindIndex(x => x.Type == ContextMemberType.Pattern) != -1) { //Warn the user. Error.CompileWarning("You cannot define unique patterns when a 'default_style' attribute is applied. Ignoring them."); } //Get the style out. var style = GetPatternStyles(new List <string>() { pattern.Patterns[defaultStyleIndex].Data }, data); //Add default content name. text.AppendLine("<key>contentName</key>"); text.AppendLine("<string>" + style[0].TextmateScope + "." + data.Name + "</string>"); } else { //Actual patterns defined, not just default. //Begin patterns, capture all "pattern" sets and includes and queue them. text.AppendLine("<key>patterns</key>"); text.AppendLine("<array>"); //Include the queued context. if (pattern.Patterns.Count != 0) { string helperName = "helper_" + ShortId.Generate(7); text.AppendLine("<dict>"); text.AppendLine("<key>include</key>"); text.AppendLine("<string>#" + helperName + "</string>"); text.AppendLine("</dict>"); //Queue it. QueueContext(helperName, pattern.Patterns); } text.AppendLine("</array>"); } //Patterns done, pop condition & styles. var popStyles = GetPatternStyles(pattern.PopStyles, data); //Patterns match up with context groups? if (!Compiler.GroupsMatch(pattern.Data, styles.Count)) { Error.Compile("Mismatch between capture groups and number of styles for pop with regex '" + pattern.PopData + "'."); return; } //Okay, add pop data. text.AppendLine("<key>end</key>"); text.AppendLine("<string>" + pattern.PopData + "</string>"); text.AppendLine("<key>endCaptures</key>"); text.AppendLine("<dict>"); for (int i = 0; i < popStyles.Count; i++) { text.AppendLine("<key>" + (i + 1) + "</key>"); text.AppendLine("<dict>"); text.AppendLine("<key>name</key>"); text.AppendLine("<string>" + popStyles[i].TextmateScope + "." + data.Name + "</string>"); text.AppendLine("</dict>"); } text.AppendLine("</dict>"); //Close the inline push. text.AppendLine("</dict>"); }