From 58992ba8addb4b8a21e730cd1c325fc7ea2a6c0c Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Fri, 10 Jul 2020 02:37:55 -0700 Subject: [PATCH] Simplification and comments for XML parser --- src/layout_attribute_parser.rs | 31 +++---- src/layout_dom_node.rs | 12 +++ src/layout_system.rs | 160 +++++++++++++++++++++------------ src/resource_cache.rs | 2 +- src/shader_stage.rs | 2 +- 5 files changed, 132 insertions(+), 75 deletions(-) diff --git a/src/layout_attribute_parser.rs b/src/layout_attribute_parser.rs index 8f8f2ea08..7c508a3f4 100644 --- a/src/layout_attribute_parser.rs +++ b/src/layout_attribute_parser.rs @@ -13,6 +13,7 @@ pub struct AttributeParser { } impl AttributeParser { + // Prebuild all the regex patterns pub fn new() -> Self { let capture_attribute_declaration_parameter_regex: regex::Regex = regex::Regex::new( // Parameter: ?: (?, ... | ...) = ? @@ -85,44 +86,44 @@ impl AttributeParser { Some(["{{", name, "}}"]) => { let name = String::from(*name); TypeValueOrArgument::VariableArgument(VariableArgument::new(name)) - } + }, // Integer: ? Some([value]) if self.match_integer_regex.is_match(value) => { let integer = value.parse::().expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]); TypeValueOrArgument::TypeValue(TypeValue::Integer(integer)) - } + }, // Decimal: ? Some([value]) if self.match_decimal_regex.is_match(value) => { let decimal = value.parse::().expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]); TypeValueOrArgument::TypeValue(TypeValue::Decimal(decimal)) - } + }, // AbsolutePx: px Some([value, px]) if px.eq_ignore_ascii_case("px") => { let pixels = value.parse::().expect(&format!("Invalid value `{}` specified in the attribute type`{}` when parsing XML layout", value, attribute_type)[..]); TypeValueOrArgument::TypeValue(TypeValue::AbsolutePx(pixels)) - } + }, // Percent: ?% Some([value, "%"]) => { let percent = value.parse::().expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]); TypeValueOrArgument::TypeValue(TypeValue::Percent(percent)) - } + }, // PercentRemainder: ?@ Some([value, "@"]) => { let percent_remainder = value.parse::().expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]); TypeValueOrArgument::TypeValue(TypeValue::PercentRemainder(percent_remainder)) - } + }, // Inner: inner Some([inner]) if inner.eq_ignore_ascii_case("inner") => { TypeValueOrArgument::TypeValue(TypeValue::Inner) - } + }, // Width: width Some([width]) if width.eq_ignore_ascii_case("width") => { TypeValueOrArgument::TypeValue(TypeValue::Width) - } + }, // Height: height Some([height]) if height.eq_ignore_ascii_case("height") => { TypeValueOrArgument::TypeValue(TypeValue::Height) - } + }, // TemplateString: `? ... {{?}} ...` Some(["`", string, "`"]) => { let mut segments = Vec::::new(); @@ -138,7 +139,7 @@ impl AttributeParser { } TypeValueOrArgument::TypeValue(TypeValue::TemplateString(segments)) - } + }, // Color: [?] Some(["[", color_name, "]"]) => { let color = match self.capture_color_name_in_palette_regex.captures(color_name) { @@ -154,16 +155,16 @@ impl AttributeParser { }; TypeValueOrArgument::TypeValue(TypeValue::Color(color)) - } + }, // Bool: true/false Some([true_or_false]) if true_or_false.eq_ignore_ascii_case("true") || true_or_false.eq_ignore_ascii_case("false") => { let boolean = true_or_false.eq_ignore_ascii_case("true"); TypeValueOrArgument::TypeValue(TypeValue::Bool(boolean)) - } + }, // None: none Some([none]) if none.eq_ignore_ascii_case("none") => { TypeValueOrArgument::TypeValue(TypeValue::None) - } + }, // Unrecognized type pattern _ => panic!("Invalid attribute type `{}` when parsing XML layout", attribute_type), } @@ -216,13 +217,13 @@ impl AttributeParser { TypeValueOrArgument::TypeValue(type_value) => type_value, TypeValueOrArgument::VariableArgument(variable_value) => { panic!("Found the default variable value `{:?}` in the attribute declaration `{}` which only allows typed values, when parsing XML layout", variable_value, attribute_declaration); - } + }, } ).collect::>(); // Return the parameter AttributeValue::VariableParameter(VariableParameter::new(name, type_sequence_options, default_type_sequence)) - } + }, // Unrecognized type pattern _ => panic!("Invalid attribute attribute declaration `{}` when parsing XML layout", attribute_declaration), } diff --git a/src/layout_dom_node.rs b/src/layout_dom_node.rs index e69de29bb..af689e503 100644 --- a/src/layout_dom_node.rs +++ b/src/layout_dom_node.rs @@ -0,0 +1,12 @@ +// pub struct LayoutDomNode { +// pub namespace: String, +// pub name: String, +// pub placement: +// // pub body: Vec +// } + +// pub struct LayoutPlacement { +// pub width: f32, +// pub height: f32, +// pub x_align: +// } \ No newline at end of file diff --git a/src/layout_system.rs b/src/layout_system.rs index 1c037aafe..92c8298a1 100644 --- a/src/layout_system.rs +++ b/src/layout_system.rs @@ -24,6 +24,8 @@ impl LayoutSystem { let xml_path = self.layout_xml_path(namespace, name); let window_main = self.parse_xml_file(&xml_path[..]).unwrap(); + Self::print_layout_tree(&window_main); + // Keep track of it being loaded to prevent duplicate work let mut already_loaded_layouts = HashSet::new(); already_loaded_layouts.insert(format!("{}:{}", namespace, name)); @@ -58,19 +60,21 @@ impl LayoutSystem { // Recursively explore the newly loaded layout's tags self.explore_referenced_layouts(&new_loaded_layout, already_loaded_layouts); + // Save the loaded layout to the cache self.loaded_layouts.set(&key_copy[..], new_loaded_layout); - } + }, // Tag has already been loaded - Some(_) => {} + Some(_) => {}, } } - } + }, // Text nodes don't need to be loaded - LayoutAbstractNode::Text(_) => {} + LayoutAbstractNode::Text(_) => {}, }; } } + // Get the "namespace:name" format of string given a namespace and layout name fn layout_name(&self, namespace: &str, name: &str) -> String { if namespace.len() > 0 { format!("{}:{}", namespace, name) @@ -80,6 +84,7 @@ impl LayoutSystem { } } + // Get the XML file path given a namespace and layout name fn layout_xml_path(&self, namespace: &str, name: &str) -> String { if namespace.len() > 0 { format!("gui/{}/{}.xml", namespace, name) @@ -89,113 +94,152 @@ impl LayoutSystem { } } + // Get an abstract syntax tree root node representing a parsed XML layout file fn parse_xml_file(&self, path: &str) -> io::Result> { + // XML layout file markup source code let source = fs::read_to_string(path)?; - let parsed = xmlparser::Tokenizer::from(&source[..]); + // XML document parser that feeds token-by-token through the file + let parser = xmlparser::Tokenizer::from(&source[..]); + // Node stack used to collect descendant nodes while reading deeper into the tree until each reaches its closing tag let mut stack: Vec> = Vec::new(); - let mut current: Option> = None; - let mut result: Option> = None; + // Opening XML tag used to collect the tag name and its various attributes + let mut current_opening_tag: Option = None; + // Top-level node that is popped from the stack when the closing tag is reached at the end of the XML document + let mut final_result: Option> = None; - let mut parsing_root_tag_with_declarations = true; - - for token in parsed { + for token in parser { match token.unwrap() { + // Beginning of an opening tag ( { + // Get the supplied namespace and tag name as owned strings let namespace = String::from(prefix.as_str()); let tag_name = String::from(local.as_str()); - let new_parsed_layout_node = LayoutAbstractNode::new_tag(namespace, tag_name); + // Construct an AST tag node with the namespace and tag name + let abstract_tag_node = LayoutAbstractNode::new_tag(namespace, tag_name); - let new_node = rctree::Node::new(new_parsed_layout_node); - current = Some(new_node); - } + // Store the AST node while attributes are added until the opening (or self-closing) tag ends + current_opening_tag = Some(abstract_tag_node); + }, + // Any attributes within the current opening tag (... ATTRIBUTE="VALUE" ...) xmlparser::Token::Attribute { prefix, local, value, .. } => { + // Check if the attribute has an empty prefix (thus, only a colon) let colon_prefixed = prefix.start() > 0 && (prefix.start() == prefix.end()); + // Set the name to the given name, possibly with a prepended colon let name = if colon_prefixed { let slice = local.as_str(); let mut string = String::with_capacity(slice.len() + 1); string.push(':'); string.push_str(slice); string - } else { String::from(local.as_str()) }; + } else { + String::from(local.as_str()) + }; + // Set the value to an ordinary string slice of the given value let value = value.as_str(); - let attribute = if parsing_root_tag_with_declarations { + // Attributes on the root element are parameter declarations that list the names and types of permitted variables + let attribute = if stack.is_empty() { let parameter_declaration = self.attribute_parser.parse_attribute_declaration(value); Attribute::new(name, parameter_declaration) } + // Attributes on elements inside the root are arguments to the layout engine (no colon prefix) or the child layout (colon prefix) else { let parameter_types = self.attribute_parser.parse_attribute_types(value); Attribute::new(name, parameter_types) }; - match &mut current { - Some(current_node) => { - match &mut *current_node.borrow_mut() { - LayoutAbstractNode::Tag(tag) => { - // Add this attribute to the current node that has not yet reached its closing angle bracket - tag.add_attribute(attribute); - } - LayoutAbstractNode::Text(text) => { - panic!("Unexpected text attribute {} attemping to be added to tag when parsing XML layout in file: {}", text, path); - } - } - } + // Add the new attribute to the current yet-to-be-closed element + match &mut current_opening_tag { + // The opening tag is indeed a tag AST node + Some(LayoutAbstractNode::Tag(tag)) => { + tag.add_attribute(attribute); + }, + // Somehow the current opening tag is actually a text node (probably impossible) + Some(LayoutAbstractNode::Text(text)) => { + panic!("Unexpected text attribute {} attemping to be added to tag when parsing XML layout in file: {}", text, path); + }, + // Somehow there is no current opening tag to add this attribute to (probably impossible) None => { panic!("Error adding attribute to tag when parsing XML layout in file: {}", path); - } + }, } - } + }, + // Either the end of the opening tag (...>) or the end of a self-closing tag (.../>) or an entire closing tag () xmlparser::Token::ElementEnd { end, .. } => { match end { - // After adding any attributes, the opening tag ends + // After adding any attributes, this element's opening tag ends (...>) xmlparser::ElementEnd::Open => { - // After adding any attributes, we are now a layer deeper which the stack keeps track of - let node_to_push = current.take().expect(&format!("Invalid syntax when parsing XML layout in file {}", path)[..]); - stack.push(node_to_push); - parsing_root_tag_with_declarations = false; - } - // After adding any attributes, the self-closing tag ends + // After adding any attributes, we are now a layer deeper in the stack of yet-to-be-closed descendants + let current_abstract_node = current_opening_tag.take().expect(&format!("Invalid syntax when parsing XML layout in file {}", path)[..]); + let tree_node_with_descendants = rctree::Node::new(current_abstract_node); + stack.push(tree_node_with_descendants); + }, + // After adding any attributes, this element's self-closing tag ends (.../>) xmlparser::ElementEnd::Empty => { + // Because a self-closing element does not go deeper, attach this now-complete node directly to its parent let parent_node = stack.last_mut().expect(&format!("Invalid syntax when parsing XML layout in file: {}", path)[..]); - let new_child = current.take().expect(&format!("Invalid syntax when parsing XML layout in file: {}", path)[..]); - parent_node.append(new_child); - } - // The closing tag is reached + let current_abstract_node = current_opening_tag.take().expect(&format!("Invalid syntax when parsing XML layout in file: {}", path)[..]); + let tree_node = rctree::Node::new(current_abstract_node); + parent_node.append(tree_node); + }, + // After visiting any descendants inside the opening tag, finally the closing tag is reached () xmlparser::ElementEnd::Close(..) => { - let popped_node = stack.pop().expect(&format!("Encountered extra closing tag when parsing XML layout in file: {}", path)[..]); + // Pop the element now that descendants have been parsed and we make our way back up the tree one level + let closed_node_with_descendants = stack.pop().expect(&format!("Encountered extra closing tag when parsing XML layout in file: {}", path)[..]); + + // Append this now-complete node to its parent, unless there is no parent, in which case we save this root node as the final result match stack.last_mut() { + // If a parent node exists Some(parent_node) => { - parent_node.append(popped_node); - } + parent_node.append(closed_node_with_descendants); + }, + // If this is the root node None => { - match result { - None => result = Some(popped_node), + match final_result { + // Save the root element as the final result + None => final_result = Some(closed_node_with_descendants), + // There can only be one root element in the XML document, but this isn't the first one encountered Some(_) => panic!("Encountered multiple root-level tags when parsing XML layout in file: {}", path), } - } + }, } - } + }, } - } + }, + // A text node in the space between sibling elements (... SOME TEXT ...) xmlparser::Token::Text { text } => { - let parent_node = stack.last_mut().expect(&format!("Encountered text outside the root tag when parsing XML layout in file: {}", path)[..]); - let text_string = String::from(text.as_str()); + // Trim any whitespace from around the string + let text_string = String::from(text.as_str().trim()); + + // If the string isn't all whitespace, append a new text node to the parent + if !text_string.is_empty() { + // Get the tree node which contains this text + let parent_node = stack.last_mut().expect(&format!("Encountered text outside the root tag when parsing XML layout in file: {}", path)[..]); - if !text_string.trim().is_empty() { - let text_node = LayoutAbstractNode::new_text(text_string); - let new_node = rctree::Node::new(text_node); - parent_node.append(new_node); + // Construct an AST text node with the provided text + let abstract_text_node = LayoutAbstractNode::new_text(text_string); + // Put the AST text node in a new tree node + let new_tree_node = rctree::Node::new(abstract_text_node); + + // Attach the new text node on the parent in the tree which contains this text + parent_node.append(new_tree_node); } - } + }, _ => {} } } - match result { + match final_result { None => panic!("Invalid syntax when parsing XML layout in file: {}", path), - Some(tree) => Ok(tree) + Some(tree) => Ok(tree), + } + } + + fn print_layout_tree(tree_root: &rctree::Node) { + for node in tree_root.descendants() { + println!("{:?}", node); } } } \ No newline at end of file diff --git a/src/resource_cache.rs b/src/resource_cache.rs index 341f095f3..7935e41b1 100644 --- a/src/resource_cache.rs +++ b/src/resource_cache.rs @@ -46,7 +46,7 @@ impl ResourceCache { let id = CacheID::new(last_index); self.name_to_id.insert(String::from(name), id); self.resources.push(resource); - } + }, } } } \ No newline at end of file diff --git a/src/shader_stage.rs b/src/shader_stage.rs index cdc052bc7..7a70e8225 100644 --- a/src/shader_stage.rs +++ b/src/shader_stage.rs @@ -8,7 +8,7 @@ pub fn compile_from_glsl(device: &wgpu::Device, path: &str, shader_type: glsl_to Err(message) => { println!("Error compiling GLSL to SPIRV shader: {}", message); panic!("{}", message); - } + }, }; let compiled = wgpu::read_spirv(spirv)?; let shader = device.create_shader_module(&compiled);