using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace csharp_sccd_compiler { public class StateChartNode : Visitable { public bool is_basic { get; private set; } /// /// Gets a value indicating whether this is a parallel state. /// /// true if is_parallel; otherwise, false. public bool is_parallel { get; private set; } /// /// Gets a value indicating whether this is a composite state. /// /// true if is_composite; otherwise, false. public bool is_composite { get; private set; } public bool is_history { get; private set; } /// /// Gets a value indicating whether this is history state of type deep. /// /// true if is_history_deep; otherwise, false. public bool is_history_deep { get; private set; } public bool is_root { get; private set; } public bool save_state_on_exit { get; set; } public bool solves_conflict_outer { get; private set; } public string name { get; private set; } public string full_name { get; private set; } /// /// The children nodes of this node. /// public List children { get; private set; } /// /// The parent node of this node. In case of a root node, this property is null. /// public StateChartNode parent { get; private set; } public List transitions { get; private set; } public EnterAction enter_action { get; private set; } public ExitAction exit_action { get; private set; } public List defaults { get; private set; } /// /// Initializes a new instance of the class. /// /// The XML element that contains the information related to this node. /// The type of node. /// If set to true this node is orthogonal. (= child of a parallel state. /// The parent of this node. Defaults to null, which means the node is the root. public StateChartNode(XElement xml, StateChartNode parent = null) { this.parent = parent; this.children = new List(); this.is_root = false; this.is_basic = false; this.is_composite = false; this.is_history = false; this.is_history_deep = false; this.is_parallel = false; this.save_state_on_exit = false; if (xml.Name == "scxml") { this.is_root = true; this.is_composite = true; } else if (xml.Name == "parallel") { this.is_composite = true; this.is_parallel = true; } else if (xml.Name == "state") { if (xml.Element("state") != null || xml.Element("parallel") != null) this.is_composite = true; else this.is_basic = true; if (this.parent.is_parallel) { if (this.is_basic) throw new CompilerException("Orthogonal nodes (nodes that are immediate children of parallel nodes) can't be basic."); } } else if (xml.Name == "history") { this.is_history = true; XAttribute type_attribute = xml.Attribute("type"); if (type_attribute != null) { string history_type = type_attribute.Value.Trim(); if (history_type == "deep") this.is_history_deep = true; else if (history_type != "shallow") throw new CompilerException("Invalid history type."); } } else return; this.resolveName(xml); this.parseConflictAttribute(xml); this.parseEnterActions(xml); this.parseExitActions(xml); //Parse transitions this.transitions = new List(); foreach (XElement transition_xml in xml.Elements("transition")) this.transitions.Add(new StateChartTransition(transition_xml, this)); this.optimizeTransitions(); this.generateChildren(xml); this.calculateDefaults(xml); } private void resolveName(XElement xml) { if (this.is_root) { this.name = "Root"; this.full_name = "Root"; } else { XAttribute name_attribute = xml.Attribute("id"); if (name_attribute == null) throw new CompilerException("Currently states without id aren't allowed."); this.name = name_attribute.Value.Trim(); if (this.name == "") throw new CompilerException("Currently states need an non-empty id."); this.full_name = string.Format("{0}_{1}", this.parent.full_name, this.name); } } private void parseConflictAttribute(XElement xml) { XAttribute conflict_attribute = xml.Attribute("conflict"); if(conflict_attribute != null) { string conflict = conflict_attribute.Value.Trim(); if (conflict == "outer") { this.solves_conflict_outer = true; return; } else if (conflict == "inner") { this.solves_conflict_outer = false; return; } if (conflict != "" && conflict != "inherit") throw new CompilerException( string.Format("Unknown conflict attribute for {0}.", this.full_name)); } //Do our default inherit action if (this.is_root || this.parent.solves_conflict_outer) this.solves_conflict_outer = true; else this.solves_conflict_outer = false; } private void parseEnterActions(XElement xml) { XElement onentry_xml = xml.Element("onentry"); if (onentry_xml == null) this.enter_action = new EnterAction(this); else { this.enter_action = new EnterAction(this, onentry_xml); if (onentry_xml.ElementsAfterSelf("onentry").Any()) throw new CompilerException(string.Format("Multiple tags detected for {0}, only 1 allowed.", this.full_name)); } } private void parseExitActions(XElement xml) { XElement onexit_xml = xml.Element("onexit"); if (onexit_xml == null) this.exit_action = new ExitAction(this); else { this.exit_action = new ExitAction(this, onexit_xml); if (onexit_xml.ElementsAfterSelf("onexit").Any()) throw new CompilerException(string.Format("Multiple tags detected for {0}, only 1 allowed.", this.full_name)); } } /// /// If a transition with no trigger and no guard is found then it is considered as the only transition. /// Otherwise the list is ordered by placing transitions having guards only first. /// private void optimizeTransitions() { List with_trigger = new List(); List only_guard = new List(); List uc_and_no_guard = new List(); foreach( StateChartTransition transition in this.transitions) { if (transition.trigger.is_uc) { if (transition.guard == null) { if (uc_and_no_guard.Count > 0) throw new TransitionException("More than one transition found at a single node, that has no trigger and no guard."); uc_and_no_guard.Add(transition); } else { only_guard.Add(transition); } } else { with_trigger.Add(transition); } } if (uc_and_no_guard.Count > 0) this.transitions = uc_and_no_guard; else { only_guard.AddRange(with_trigger); this.transitions = only_guard; } } private void generateChildren(XElement xml) { List children_names = new List(); foreach (XElement child_xml in xml.Elements()) { StateChartNode child = new StateChartNode(child_xml, this); if (!child.is_composite && !child.is_basic && !child.is_history) continue; this.children.Add(child); //Check if the name of the child is valid if (children_names.Contains(child.name)) throw new CompilerException(string.Format("Found 2 equivalent state id's '{0}' as children of state '{1}'", child.name, this.full_name)); children_names.Add(child.name); } } private void calculateDefaults(XElement xml) { XAttribute initial_state_attribute = xml.Attribute("initial"); string initial_state = ""; if (initial_state_attribute != null) initial_state = initial_state_attribute.Value.Trim(); if (this.is_parallel) { this.defaults = (from child in this.children where !child.is_history select child).ToList(); if (initial_state != "") throw new CompilerException(string.Format("Component <{0}> contains an initial state while being parallel.", this.full_name)); } else if (initial_state == "") { if (!this.is_basic && !this.is_history) { if (this.children.Count == 1) this.defaults = this.children; else throw new CompilerException(string.Format("Component <{0}> contains no default state.", this.full_name)); } } else { if (this.is_basic) throw new CompilerException(string.Format("Component <{0}> contains a default state while being a basic state.", this.full_name)); this.defaults = new List(); foreach (StateChartNode child in this.children) { if (child.name == initial_state) this.defaults.Add(child); } if (this.defaults.Count < 1) throw new CompilerException(string.Format("Initial state '{0}' referred to, is missing in {1}.", initial_state, this.full_name)); else if (this.defaults.Count > 1) throw new CompilerException(string.Format("Multiple states with the name '{0}' found in {1} which is referred to as initial state.", initial_state, this.full_name)); } } /// /// Returns a list representing the containment hierarchy of this node. /// /// The ancestors with node being the first element and its outermost parent (root) being the last. public IEnumerable getAncestors() { StateChartNode current = this; while ( !current.is_root) { current = current.parent; yield return current; } } public bool isDescendantOf(StateChartNode anc) { StateChartNode current = this; while(! current.is_root) { current = current.parent; if (object.ReferenceEquals(current,anc)) return true; } return false; } public bool isDescendantOrAncestorOf(StateChartNode node) { return this.isDescendantOf(node) || node.isDescendantOf(this); } public override void accept(Visitor visitor) { visitor.visit (this); } } }