Implement abstract syntax tree parsing of XML layout

This commit is contained in:
Keavon Chambers
2020-05-27 04:08:52 -07:00
parent decff5681b
commit 0c7e6bc883
22 changed files with 391 additions and 62 deletions

View File

@@ -72,7 +72,7 @@ impl Application {
let gui_rect_pipeline = Pipeline::new(&device, swap_chain_descriptor.format, vec![], &mut shader_cache, ("shaders/shader.vert", "shaders/shader.frag"));
pipeline_cache.set("gui_rect", gui_rect_pipeline);
let gui_root_data = GuiNode::new(swap_chain_descriptor.width, swap_chain_descriptor.height, ColorPalette::get_color_srgb(ColorPalette::Accent));
let gui_root_data = GuiNode::new(swap_chain_descriptor.width, swap_chain_descriptor.height, ColorPalette::Accent.into_color_srgb());
let gui_root = rctree::Node::new(gui_root_data);
GuiLayout::new();

View File

@@ -23,7 +23,7 @@ pub enum ColorPalette {
impl ColorPalette {
#[allow(dead_code)]
pub fn get_color_srgb(self) -> Color {
pub fn into_color_srgb(&self) -> Color {
let grayscale = match self {
ColorPalette::Black => 0 * 17, // #000000
ColorPalette::NearBlack => 1 * 17, // #111111
@@ -58,11 +58,34 @@ impl ColorPalette {
}
#[allow(dead_code)]
pub fn get_color_linear(self) -> Color {
let standard_rgb = ColorPalette::get_color_srgb(self);
pub fn into_color_linear(&self) -> Color {
let standard_rgb = ColorPalette::into_color_srgb(self);
let linear = palette::Srgb::new(standard_rgb.r, standard_rgb.g, standard_rgb.b).into_linear();
Color::new(linear.red, linear.green, linear.blue, standard_rgb.a)
}
pub fn lookup_palette_color(name_in_palette: &str) -> ColorPalette {
match &name_in_palette.to_ascii_lowercase()[..] {
"black" => ColorPalette::Black,
"nearblack" => ColorPalette::NearBlack,
"mildblack" => ColorPalette::MildBlack,
"darkgray" => ColorPalette::DarkGray,
"dimgray" => ColorPalette::DimGray,
"dullgray" => ColorPalette::DullGray,
"lowergray" => ColorPalette::LowerGray,
"middlegray" => ColorPalette::MiddleGray,
"uppergray" => ColorPalette::UpperGray,
"palegray" => ColorPalette::PaleGray,
"softgray" => ColorPalette::SoftGray,
"lightgray" => ColorPalette::LightGray,
"brightgray" => ColorPalette::BrightGray,
"mildwhite" => ColorPalette::MildWhite,
"nearwhite" => ColorPalette::NearWhite,
"white" => ColorPalette::White,
"accent" => ColorPalette::Accent,
_ => panic!("Invalid color lookup of `{}` from the color palette", name_in_palette),
}
}
}

View File

@@ -1,6 +1,7 @@
use std::fs;
use std::io;
use crate::layout_parsed_node::*;
use crate::layout_abstract_syntax::*;
pub struct GuiLayout {
@@ -10,6 +11,7 @@ impl GuiLayout {
pub fn new() -> GuiLayout {
let parsed_layout_tree = Self::parse_xml_file("gui/window/main.xml").unwrap();
Self::interpret_abstract_syntax_tree(parsed_layout_tree);
Self {}
}
@@ -115,6 +117,15 @@ impl GuiLayout {
pub fn interpret_abstract_syntax_tree(root: rctree::Node<LayoutParsedNode>) {
for node in root.descendants() {
println!("{:?}", node);
match & *node.borrow() {
LayoutParsedNode::Tag(tag) => {
LayoutAbstractSyntaxNode::new(tag.namespace.clone(), tag.name.clone(), &tag.attributes);
}
LayoutParsedNode::Text(_) => {}
};
println!();
}
}
}

View File

@@ -0,0 +1,150 @@
use crate::layout_abstract_types::*;
use crate::color_palette::ColorPalette;
use crate::color::Color;
#[derive(Debug)]
pub enum Attribute {
VariableValue(VariableValue),
TypeValue(TypeValue),
}
pub fn parse_attribute(input: &str) -> Attribute {
// Match variables and typed values that can be in an attribute
let regex = regex::Regex::new(
r#"(?x)
^\s*(\w*)\s*(:)\s*(\()\s*(\w*\s*(?:\|\s*\w*\s*?)*)\s*(\))\s*(=)\s*(\w*)\s*$ | # Parameter ?: (? | ... | ?) = ?
^\s*(\{\{)\s*(\w*)\s*(\}\})\s*$ | # Argument {{?}}
^\s*(-?\d+)\s*$ | # Integer ?
^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$ | # Decimal ?
^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))([Pp][Xx])\s*$ | # AbsolutePx ?px
^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))(%)\s*$ | # Percent ?%
^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))(@)\s*$ | # PercentRemainder ?@
^\s*([Ii][Nn][Nn][Ee][Rr])\s*$ | # Inner inner
^\s*([Ww][Ii][Dd][Tt][Hh])\s*$ | # Width width
^\s*([Hh][Ee][Ii][Gg][Hh][Tt])\s*$ | # Height height
^\s*`(.*)`\s*$ | # TemplateString `? ... {{?}} ...`
^\s*(\[)(.*)(\])\s*$ | # Color [?]
^\s*([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])\s*$ | # Bool true/false
^\s*([Nn][Oo][Nn][Ee])\s*$ # None none
"#
).unwrap();
// Match with the regular expression
let captures = regex.captures(input).map(|captures|
captures
.iter()
.skip(1)
.flat_map(|c| c)
.map(|c| c.as_str())
.collect::<Vec<_>>()
);
// Match against the captured values as a slice
let slices = captures.as_ref().map(|c| c.as_slice());
match slices {
Some([name, ":", "(", types, ")", "=", default_value]) => {
// TODO: Extend to support a list of N types (like (AbsolutePx) (AbsolutePx) (AbsolutePx) (AbsolutePx))
let name = String::from(*name);
let split_types = types.split("|").map(|piece| piece.trim());
let valid_types = split_types.map(|type_name|
match &type_name.to_ascii_lowercase()[..] {
"xml" => TypeName::Xml,
"integer" => TypeName::Integer,
"decimal" => TypeName::Decimal,
"absolutepx" => TypeName::AbsolutePx,
"percent" => TypeName::Percent,
"percentremainder" => TypeName::PercentRemainder,
"inner" => TypeName::Inner,
"width" => TypeName::Width,
"height" => TypeName::Height,
"templatestring" => TypeName::TemplateString,
"color" => TypeName::Color,
"bool" => TypeName::Bool,
"none" => TypeName::None,
invalid => panic!("Invalid type `{}` specified in the attribute `{}` when parsing XML layout", invalid, input),
}
).collect::<Vec<_>>();
let default = match parse_attribute(default_value) {
Attribute::TypeValue(type_value) => type_value,
Attribute::VariableValue(variable_value) => panic!("Found the variable value `{:?}` in the attribute `{}` which only allows typed values, when parsing XML layout", variable_value, input),
};
Attribute::VariableValue(VariableValue::Parameter(VariableParameter {name, valid_types, default}))
}
Some(["{{", name, "}}"]) => {
let name = String::from(*name);
Attribute::VariableValue(VariableValue::Argument(name))
}
Some([value]) if regex::Regex::new(r"^\s*(-?\d+)\s*$").unwrap().is_match(value) => {
let integer = value.parse::<i64>().expect(&format!("Invalid value `{}` specified in the attribute `{}` when parsing XML layout", value, input)[..]);
Attribute::TypeValue(TypeValue::Integer(integer))
}
Some([value]) if regex::Regex::new(r"^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$").unwrap().is_match(value) => {
let decimal = value.parse::<f64>().expect(&format!("Invalid value `{}` specified in the attribute `{}` when parsing XML layout", value, input)[..]);
Attribute::TypeValue(TypeValue::Decimal(decimal))
}
Some([value, px]) if px.eq_ignore_ascii_case("px") => {
let pixels = value.parse::<f32>().expect(&format!("Invalid value `{}` specified in the attribute `{}` when parsing XML layout", value, input)[..]);
Attribute::TypeValue(TypeValue::AbsolutePx(pixels))
}
Some([value, "%"]) => {
let percent = value.parse::<f32>().expect(&format!("Invalid value `{}` specified in the attribute `{}` when parsing XML layout", value, input)[..]);
Attribute::TypeValue(TypeValue::Percent(percent))
}
Some([value, "@"]) => {
let percent_remainder = value.parse::<f32>().expect(&format!("Invalid value `{}` specified in the attribute `{}` when parsing XML layout", value, input)[..]);
Attribute::TypeValue(TypeValue::PercentRemainder(percent_remainder))
}
Some([inner]) if inner.eq_ignore_ascii_case("inner") => {
Attribute::TypeValue(TypeValue::Inner)
}
Some([width]) if width.eq_ignore_ascii_case("width") => {
Attribute::TypeValue(TypeValue::Width)
}
Some([height]) if height.eq_ignore_ascii_case("height") => {
Attribute::TypeValue(TypeValue::Height)
}
Some(["`", string, "`"]) => {
let mut segments = Vec::<TemplateStringSegment>::new();
let mut is_template = false;
let regex = regex::Regex::new(r"\{\{|\}\}").unwrap();
for part in regex.split(string) {
let segment = match is_template {
true => TemplateStringSegment::String(String::from(part)),
false => TemplateStringSegment::Argument(VariableArgument { name: String::from(part) }),
};
segments.push(segment);
is_template = !is_template;
}
Attribute::TypeValue(TypeValue::TemplateString(segments))
}
Some(["[", color_name, "]"]) => {
let regex = regex::Regex::new(r"\s*'(.*)'\s*").unwrap();
let color = match regex.captures(color_name) {
Some(captures) => {
let palette_color = captures.get(1).expect(&format!("Invalid palette color name `{}` specified in the attribute `{}` when parsing XML layout", color_name, input)[..]).as_str();
ColorPalette::lookup_palette_color(palette_color).into_color_srgb()
}
None => {
let parsed = color_name.parse::<css_color_parser::Color>();
let css_color = parsed.expect(&format!("Invalid CSS color name `{}` specified in the attribute `{}` when parsing XML layout", color_name, input)[..]);
Color::new(css_color.r as f32 / 255.0, css_color.g as f32 / 255.0, css_color.b as f32 / 255.0, css_color.a as f32 / 255.0)
}
};
Attribute::TypeValue(TypeValue::Color(color))
}
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");
Attribute::TypeValue(TypeValue::Bool(boolean))
}
Some([none]) if none.eq_ignore_ascii_case("none") => {
Attribute::TypeValue(TypeValue::None)
}
_ => panic!("Invalid attribute value `{}` when parsing XML layout", input),
}
}

View File

@@ -0,0 +1,23 @@
use crate::layout_abstract_attributes::*;
#[derive(Debug)]
pub struct LayoutAbstractSyntaxNode {
pub namespace: Option<String>,
pub name: String,
pub attributes: Vec<Attribute>,
}
impl LayoutAbstractSyntaxNode {
pub fn new(namespace: Option<String>, tag: String, attributes: &Vec<(String, String)>) -> Self {
for attribute in attributes {
let parsed = parse_attribute(&attribute.1[..]);
println!("{} : {:?} -> {:?}", attribute.0, attribute.1, parsed);
}
Self {
namespace,
name: tag,
attributes: Vec::new(),
}
}
}

View File

@@ -0,0 +1,64 @@
use crate::color::Color;
// Variable types
#[derive(Debug)]
pub enum VariableValue {
Parameter(VariableParameter),
Argument(String),
}
#[derive(Debug)]
pub struct VariableParameter {
pub name: String,
pub valid_types: Vec<TypeName>,
pub default: TypeValue,
// pub value: TypeValue,
}
#[derive(Debug)]
pub struct VariableArgument {
pub name: String,
}
// Value types
#[derive(Debug)]
pub enum TypeName {
Xml,
Integer,
Decimal,
AbsolutePx,
Percent,
PercentRemainder,
Inner,
Width,
Height,
TemplateString,
Color,
Bool,
None,
}
#[derive(Debug)]
pub enum TypeValue {
Xml(()), // TODO
Integer(i64),
Decimal(f64),
AbsolutePx(f32),
Percent(f32),
PercentRemainder(f32),
Inner,
Width,
Height,
TemplateString(Vec<TemplateStringSegment>),
Color(Color),
Bool(bool),
None,
}
#[derive(Debug)]
pub enum TemplateStringSegment {
String(String),
Argument(VariableArgument),
}

View File

@@ -17,17 +17,17 @@ impl LayoutParsedNode {
#[derive(Debug)]
pub struct LayoutParsedTag {
pub namespace: Option<String>,
pub tag: String,
pub name: String,
pub attributes: Vec<(String, String)>,
}
impl LayoutParsedTag {
pub fn new(namespace: String, tag: String) -> Self {
pub fn new(namespace: String, name: String) -> Self {
let namespace = if namespace.is_empty() { None } else { Some(namespace) };
Self {
namespace,
tag,
name,
attributes: Vec::new(),
}
}

View File

@@ -11,6 +11,9 @@ mod gui_attributes;
mod window_events;
mod gui_layout;
mod layout_parsed_node;
mod layout_abstract_types;
mod layout_abstract_attributes;
mod layout_abstract_syntax;
use application::Application;
use winit::event_loop::EventLoop;