From 2c67ba3da02a8203665699bf3e7e074a8ae705f0 Mon Sep 17 00:00:00 2001 From: DennisAng Date: Sun, 6 Mar 2016 23:33:36 +0800 Subject: [PATCH 1/3] Implemented import feature which imports the legacy Hypedyn 1 format. --- gradle/wrapper/gradle-wrapper.properties | 4 +- .../hypedyn/events/Event.scala | 4 + .../hypedyn/events/EventBus.scala | 6 +- .../narrativeviewer/NarrativeViewer.scala | 20 +- .../hypedyn/events/CoreEventDispatcher.scala | 22 +- .../hypedyn/story/StoryController.scala | 7 +- .../hypedyn/utils/parsing/SchemeParser.scala | 782 ++++++++++++++++++ .../hypedyn/storyviewer/StoryViewer.scala | 18 +- .../org/narrativeandplay/hypedyn/Main.scala | 34 +- .../hypedyn/dialogs/LegacyFileDialog.scala | 23 + .../hypedyn/events/UiEventDispatcher.scala | 29 +- .../hypedyn/uicomponents/Menubar.scala | 18 +- 12 files changed, 912 insertions(+), 55 deletions(-) create mode 100644 hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala create mode 100644 hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/dialogs/LegacyFileDialog.scala diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 03f9566..009f91b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu May 07 14:49:40 SGT 2015 +#Sat Mar 05 10:59:06 SGT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip diff --git a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/Event.scala b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/Event.scala index 468354c..981a5c1 100644 --- a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/Event.scala +++ b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/Event.scala @@ -86,6 +86,7 @@ sealed case class SaveAsRequest(src: String) extends Request sealed case class LoadRequest(src: String) extends Request sealed case class ExportRequest(src: String) extends Request +sealed case class ImportRequest(src: String) extends Request sealed case class RunRequest(src: String) extends Request sealed case class CutNodeRequest(id: NodeId, src: String) extends Request @@ -118,6 +119,7 @@ sealed case class SaveResponse(loadedFile: Option[File], src: String) extends Re sealed case class SaveAsResponse(src: String) extends Response sealed case class LoadResponse(src: String) extends Response +sealed case class ImportResponse(src: String) extends Response sealed case class ExportResponse(src: String) extends Response sealed case class RunResponse(fileToRun: File, src: String) extends Response @@ -140,6 +142,7 @@ sealed case class RedoResponse(src: String) extends Response sealed trait Action extends Event sealed case class CreateNode(node: Nodal, src: String) extends Action sealed case class UpdateNode(node: Nodal, updatedNode: Nodal, src: String) extends Action +sealed case class MoveNode(node: Nodal, x:Double, y:Double, src: String) extends Action sealed case class DestroyNode(node: Nodal, src: String) extends Action sealed case class CreateFact(fact: Fact, src: String) extends Action @@ -150,6 +153,7 @@ sealed case class SaveData(pluginName: String, data: AstElement, src: String) ex sealed case class SaveToFile(file: File, src: String) extends Action sealed case class LoadFromFile(file: File, src: String) extends Action +sealed case class ImportFromFile(file: File, src: String) extends Action sealed case class ExportToFile(dir: File, filename: String, src: String) extends Action sealed case class RunStory(fileToRun: File, src: String) extends Action diff --git a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/EventBus.scala b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/EventBus.scala index ef3aa84..d1ec0b3 100644 --- a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/EventBus.scala +++ b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/EventBus.scala @@ -24,8 +24,6 @@ object EventBus { eventBus.onNext(event) } - - /** * Event stream of all `Event`s */ @@ -49,6 +47,7 @@ object EventBus { val SaveAsRequests = eventBus collect { case e: SaveAsRequest => e } val LoadRequests = eventBus collect { case e: LoadRequest => e } + val ImportRequests = eventBus collect { case e: ImportRequest => e } val ExportRequests = eventBus collect { case e: ExportRequest => e } val RunRequests = eventBus collect { case e: RunRequest => e } @@ -80,6 +79,7 @@ object EventBus { val SaveAsResponses = eventBus collect { case e: SaveAsResponse => e } val LoadResponses = eventBus collect { case e: LoadResponse => e } + val ImportResponses = eventBus collect { case e: ImportResponse => e } val ExportResponses = eventBus collect { case e: ExportResponse => e } val RunResponses = eventBus collect { case e: RunResponse => e } @@ -111,12 +111,14 @@ object EventBus { val SaveToFileEvents = eventBus collect { case e: SaveToFile => e } val LoadFromFileEvents = eventBus collect { case e: LoadFromFile => e } + val ImportFromFileEvents = eventBus collect { case e: ImportFromFile => e } val ExportToFileEvents = eventBus collect { case e: ExportToFile => e } val RunStoryEvents = eventBus collect { case e: RunStory => e } val CreateStoryEvents = eventBus collect { case e: CreateStory => e } val UpdateStoryPropertiesEvents = eventBus collect { case e: UpdateStoryProperties => e } + val MoveNodeEvents = eventBus collect { case e: MoveNode => e } /** * Event stream of all `Completion`s diff --git a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/plugins/narrativeviewer/NarrativeViewer.scala b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/plugins/narrativeviewer/NarrativeViewer.scala index 4e58143..85313ea 100644 --- a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/plugins/narrativeviewer/NarrativeViewer.scala +++ b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/plugins/narrativeviewer/NarrativeViewer.scala @@ -19,6 +19,7 @@ trait NarrativeViewer { * The plugin is automatically hooked into the appropriate event streams */ EventBus.NodeCreatedEvents foreach { n => onNodeCreated(n.node) } + EventBus.MoveNodeEvents foreach { n => onMoveNode(n.node, n.x, n.y) } EventBus.NodeUpdatedEvents foreach { n => onNodeUpdated(n.node, n.updatedNode) } EventBus.NodeDestroyedEvents foreach { n => onNodeDestroyed(n.node) } @@ -30,13 +31,22 @@ trait NarrativeViewer { def onNodeCreated(node: Nodal): Unit /** - * Defines what to do when a node is updated - * - * @param node The node to be updated - * @param updatedNode The same node with the updates already applied - */ + * Defines what to do when a node is updated + * + * @param node The node to be updated + * @param updatedNode The same node with the updates already applied + */ def onNodeUpdated(node: Nodal, updatedNode: Nodal): Unit + /** + * Defines what to do when a node should move + * + * @param node The node to be moved + * @param x The x coordinate + * @param y The y coordinate + */ + def onMoveNode(node: Nodal, x:Double, y:Double): Unit + /** * Defines what to do when a node is destroyed * diff --git a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/events/CoreEventDispatcher.scala b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/events/CoreEventDispatcher.scala index c842b59..e425601 100644 --- a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/events/CoreEventDispatcher.scala +++ b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/events/CoreEventDispatcher.scala @@ -1,16 +1,16 @@ package org.narrativeandplay.hypedyn.events import java.io.File -import java.net.URI import java.nio.file.Files import org.narrativeandplay.hypedyn.plugins.PluginsController -import org.narrativeandplay.hypedyn.serialisation.{IoController, Serialiser, AstMap, AstElement} import org.narrativeandplay.hypedyn.serialisation.serialisers._ +import org.narrativeandplay.hypedyn.serialisation.{AstElement, AstMap, IoController, Serialiser} +import org.narrativeandplay.hypedyn.story.StoryController import org.narrativeandplay.hypedyn.story.internal.Story import org.narrativeandplay.hypedyn.story.rules.{ActionDefinitions, ConditionDefinitions, Fact} import org.narrativeandplay.hypedyn.undo._ -import org.narrativeandplay.hypedyn.story.StoryController +import org.narrativeandplay.hypedyn.utils.parsing.SchemeParser /** * Main event dispatcher for the core @@ -128,6 +128,7 @@ object CoreEventDispatcher { EventBus.LoadRequests foreach { _ => EventBus.send(LoadResponse(CoreEventSourceIdentity)) } EventBus.ExportRequests foreach { _ => EventBus.send(ExportResponse(CoreEventSourceIdentity)) } + EventBus.ImportRequests foreach { _ => EventBus.send(ImportResponse(CoreEventSourceIdentity)) } EventBus.RunRequests foreach { _ => val tmpDir = Files.createTempDirectory("hypedyn2").toFile tmpDir.deleteOnExit() @@ -181,6 +182,21 @@ object CoreEventDispatcher { EventBus.send(FileLoaded(loadedFile, CoreEventSourceIdentity)) } + EventBus.ImportFromFileEvents foreach { evt => + val dataToImport = IoController read evt.file + val story:Story = SchemeParser.parse(dataToImport) + + StoryController.load(story) + + loadedFile = None + + UndoController.clearHistory() + UndoController.markCurrentPosition() + + EventBus.send(StoryLoaded(StoryController.story, CoreEventSourceIdentity)) + EventBus.send(FileLoaded(loadedFile, CoreEventSourceIdentity)) + } + EventBus.ExportToFileEvents foreach { evt => val exportDirectory = new File(evt.dir, evt.filename.stripSuffix(".dyn2") + "-export") diff --git a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/story/StoryController.scala b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/story/StoryController.scala index 9c62b37..39d1ca0 100644 --- a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/story/StoryController.scala +++ b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/story/StoryController.scala @@ -1,12 +1,9 @@ package org.narrativeandplay.hypedyn.story -import org.narrativeandplay.hypedyn.story.internal.NodeContent.Ruleset -import org.narrativeandplay.hypedyn.story.internal.Story.Metadata -import org.narrativeandplay.hypedyn.story.internal.{NodeContent, Node, Story} import org.narrativeandplay.hypedyn.story.InterfaceToImplementationConversions._ -import org.narrativeandplay.hypedyn.story.rules.RuleLike.{ParamValue, ParamName} +import org.narrativeandplay.hypedyn.story.internal.{Node, NodeContent, Story} +import org.narrativeandplay.hypedyn.story.rules.RuleLike.ParamValue import org.narrativeandplay.hypedyn.story.rules._ -import org.narrativeandplay.hypedyn.story.rules.internal.Rule /** * Controller for handling story-related actions diff --git a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala new file mode 100644 index 0000000..cf6d7cc --- /dev/null +++ b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala @@ -0,0 +1,782 @@ +package org.narrativeandplay.hypedyn.utils.parsing + +import org.narrativeandplay.hypedyn.events._ +import org.narrativeandplay.hypedyn.story.NodalContent.{RulesetId, RulesetIndexes, TextIndex} +import org.narrativeandplay.hypedyn.story._ +import org.narrativeandplay.hypedyn.story.internal.NodeContent.Ruleset +import org.narrativeandplay.hypedyn.story.internal.{Node, NodeContent, Story} +import org.narrativeandplay.hypedyn.story.rules.Actionable.ActionType +import org.narrativeandplay.hypedyn.story.rules.Conditional.ConditionType +import org.narrativeandplay.hypedyn.story.rules.RuleLike.ParamValue.{StringInput, SelectedListValue, UnionValueSelected} +import org.narrativeandplay.hypedyn.story.rules.RuleLike.{ParamValue, ParamName} +import org.narrativeandplay.hypedyn.story.rules.internal.{Action, _} +import org.narrativeandplay.hypedyn.story.rules.{BooleanOperator, _} + +import scala.collection.mutable +import scala.util.parsing.combinator.JavaTokenParsers + +object SchemeParser extends JavaTokenParsers { + var isParsing = false + + def boolean: Parser[Boolean] = ("#t" | "#f" | "true" | "false") ^^ { case "#t" => true; case "true" => true; case "#f" => false; case "false" => false } + + def int = wholeNumber ^^ { s => BigInt.apply(s) } + + def string: Parser[String] = stringLiteral ^^ { str => StringContext treatEscapes str.substring(1, str.length - 1) } + + def id = "([A-Za-z-!:?0-9])+".r ^^ { + _.toString + } + + def double = "[0-9]+[.][0-9]+".r ^^ { + _.toDouble + } + + var startNode: BigInt = BigInt.int2bigInt(-1) + + def list: Parser[List[Any]] = "(" ~> rep(expression) <~ ")" ^^ { s: List[Any] => s } + + var nodes: mutable.Map[NodeId, Node] = new mutable.HashMap[NodeId, Node]() + var nodesX: mutable.Map[NodeId, Double] = new mutable.HashMap[NodeId, Double]() + var nodesY: mutable.Map[NodeId, Double] = new mutable.HashMap[NodeId, Double]() + var latestNode: Node = null + var story: Story = null + var isWithinNode = true + + def expression: Parser[Any] = list | boolean | double | int | id | string + + EventBus.StoryLoadedEvents foreach { n => { + if (isParsing) { + isParsing = false + + for ((k, v) <- nodes) + { + EventBus.send(MoveNode(v, nodesX.get(v.id).get, nodesY.get(v.id).get, "SchemeParser")) + } + } + } + } + + def parse(s: String) = { + story = new Story() + nodes.clear() + nodesX.clear() + nodesY.clear() + isWithinNode = true + latestNode = null + startNode = BigInt.int2bigInt(-1) + + val list: List[Any] = this.parseAll(this.list, s).get + process(list) + isParsing = true + + for ((k, v) <- nodes) { + story = story.addNode(v) + } + + story + // EventBus.send(NewStoryRequest("SchemeParser")) + } + + def process(l: List[Any]): Unit = { + val iterator = l.iterator + while (iterator.hasNext) { + val current = iterator.next() + current match { + case list: List[Any] => process(current.asInstanceOf[List[Any]]) + case currentString: String => + currentString match { + case "begin" => processSection(iterator) + case _ => + } + } + } + } + + def processHeader(iterator: Iterator[Any]): Unit = { + while (iterator.hasNext) { + val current = iterator.next() + current match { + case list: List[Any] => + val i = current.asInstanceOf[List[Any]].iterator + val firstToken = i.next() + firstToken match { + case instruction: String => + instruction match { + case "create-node" => + processCreateNode(i) + isWithinNode = true + case "create-link" => + processCreateLink(i) + isWithinNode = false + case "begin" => + processSubSection(i) + case _ => //Console.println("Header: " + instruction) + } + } + case _ => + } + } + } + + def processCreateTypedRule3(i: Iterator[Any]): Unit = { + val ruleName = i.next().asInstanceOf[String] + //val ruleType = + i.next().asInstanceOf[List[Any]].lift(2) + val stringOperator: String = i.next().asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] + val boolOperator = if (stringOperator == "or") BooleanOperator.Or else BooleanOperator.And + //val negate = + i.next().asInstanceOf[Boolean] + val linkId = i.next().asInstanceOf[BigInt] + //val stringFixedId = + i.next().asInstanceOf[String] + val fixedId = i.next().asInstanceOf[BigInt] + //val stringFallthrough = + i.next().asInstanceOf[String] + val fallThrough = i.next().asInstanceOf[Boolean] + + val node = latestNode + + if (node != null) { + val rule = new Rule(new RuleId(fixedId), ruleName, !fallThrough, boolOperator, List(), List()) + if (isWithinNode) { + + val newRules: List[Rule] = node.rules.:+(rule) + val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) + + nodes.remove(node.id) + nodes.put(newNode.id, newNode) + latestNode = newNode + } else { + node.content.rulesets.filter(_.id == new RulesetId(linkId)).foreach(ruleSet => { + + val newRules: List[Rule] = ruleSet.rules.:+(rule) + + val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) + val newFullSet: List[Ruleset] = node.content.rulesets.map(x => if (x == ruleSet) newSet else x) + val newContent = node.content.copy(node.content.text, newFullSet) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) + + nodes.remove(node.id) + nodes.put(newNode.id, newNode) + latestNode = newNode + }) + } + } + } + + def processFollowLink(expression: List[Any], parentRuleId: BigInt): Unit = { + val newActionType = "LinkTo" + //val linkId = expression.lift(2).get.asInstanceOf[BigInt] + //val ruleId = expression.lift(3).get.asInstanceOf[BigInt] + val toNodeId = expression.lift(5).get.asInstanceOf[BigInt] + + val node = latestNode + if (node != null) { + node.content.rulesets.foreach(ruleSet => { + ruleSet.rules.foreach(rule => { + if (rule.id == new RuleId(parentRuleId)) { + val definitions = ActionDefinitions.apply() + definitions.foreach(definition => { + + if (definition.actionType == new ActionType(newActionType)) { + val params = Map( + new ParamName("node") -> ParamValue.Node.apply(new NodeId(toNodeId)) + ) + + val newAction = new Action(new ActionType(newActionType), params) + val newActions: List[Action] = rule.actions.:+(newAction) + val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) + val newRules: List[Rule] = ruleSet.rules.map { x => if (x == rule) newRule else x } + + val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) + val newFullSet: List[Ruleset] = node.content.rulesets.map(x => if (x == ruleSet) newSet else x) + val newContent = node.content.copy(node.content.text, newFullSet) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) + + nodes.remove(node.id) + nodes.put(newNode.id, newNode) + latestNode = newNode + } + }) + } + }) + }) + } + } + + def processSetNumberFact(expression: List[Any], ruleId: BigInt): Action = { + val factId = expression.lift(2).get.asInstanceOf[BigInt] + val setOption = expression.lift(3).get.asInstanceOf[String] // Input, Random, Fact, Math + + var params = new mutable.ListMap[ParamName, ParamValue]() + + setOption match { + case "Input" => + val setFactId = expression.lift(2).get.asInstanceOf[BigInt] + //val setFactMode = expression.lift(3).get.asInstanceOf[String] + val setFactValue = expression.lift(4).get.asInstanceOf[String] + + params += (new ParamName("updateValue") -> ParamValue.UnionValueSelected("inputValue")) + params += (new ParamName("fact") -> ParamValue.IntegerFact(new FactId(setFactId))) + params += (new ParamName("inputValue") -> ParamValue.IntegerInput(BigInt.apply(setFactValue))) + case "Math" => + val optionExpression = expression.lift(4).get.asInstanceOf[List[Any]] + val eOperator = optionExpression.lift(1).get.asInstanceOf[String] + val value1 = optionExpression.lift(2).get.asInstanceOf[String] // FactId / Input + val eOption1 = optionExpression.lift(3).get.asInstanceOf[String] // Fact, Input + val value2 = optionExpression.lift(4).get.asInstanceOf[String] // FactId / Input + val eOption2 = optionExpression.lift(5).get.asInstanceOf[String] // Fact, Input + + val operand1IsFact = if (eOption1 == "Fact") true else false + val operand2IsFact = if (eOption2 == "Fact") true else false + + val operand1 = if (operand1IsFact) "factOperand1" else "userOperand1" + val operand2 = if (operand2IsFact) "factOperand2" else "userOperand2" + + params += (new ParamName("fact") -> ParamValue.IntegerFact(new FactId(factId))) + params += (new ParamName("updateValue") -> ParamValue.UnionValueSelected("computation")) + params += (new ParamName("operand1") -> ParamValue.UnionValueSelected(operand1)) + if (operand1IsFact) { + params += (new ParamName(operand1) -> ParamValue.IntegerFact(new FactId(BigInt.apply(value1)))) + } else { + params += (new ParamName(operand1) -> ParamValue.IntegerInput(BigInt.apply(value1))) + } + params += (new ParamName("operator") -> ParamValue.SelectedListValue(eOperator)) + params += (new ParamName("operand2") -> ParamValue.UnionValueSelected(operand2)) + if (operand2IsFact) { + params += (new ParamName(operand2) -> ParamValue.IntegerFact(new FactId(BigInt.apply(value2)))) + } else { + params += (new ParamName(operand2) -> ParamValue.IntegerInput(BigInt.apply(value2))) + } + params += (new ParamName("computation") -> ParamValue.ProductValue(List("operand1", "operator", "operand2"))) + case "Fact" => + val setFromFactId = expression.lift(2).get.asInstanceOf[BigInt] + val setToFactId = expression.lift(4).get.asInstanceOf[BigInt] + params += (new ParamName("updateValue") -> ParamValue.UnionValueSelected("integerFactValue")) + params += (new ParamName("fact") -> ParamValue.IntegerFact(new FactId(setFromFactId))) + params += (new ParamName("integerFactValue") -> ParamValue.IntegerFact(new FactId(setToFactId))) + + case "Random" => + val optionExpression = expression.lift(4).get.asInstanceOf[List[Any]] + val value1 = optionExpression.lift(1).get.asInstanceOf[String] // FactId / Input + val eOption1 = optionExpression.lift(2).get.asInstanceOf[String] // Fact, Input + val value2 = optionExpression.lift(3).get.asInstanceOf[String] // FactId / Input + val eOption2 = optionExpression.lift(4).get.asInstanceOf[String] // Fact, Input + + val operand1IsFact = if (eOption1 == "Fact") true else false + val operand2IsFact = if (eOption2 == "Fact") true else false + + val operand1 = if (operand1IsFact) "startFact" else "startInput" + val operand2 = if (operand2IsFact) "endFact" else "endInput" + + params += (new ParamName("randomValue") -> ParamValue.ProductValue(List("start", "end"))) + params += (new ParamName("updateValue") -> ParamValue.UnionValueSelected("randomValue")) + params += (new ParamName("fact") -> ParamValue.IntegerFact(new FactId(factId))) + + params += (new ParamName("start") -> ParamValue.UnionValueSelected(operand1)) + if (operand1IsFact) { + params += (new ParamName(operand1) -> ParamValue.IntegerFact(new FactId(BigInt.apply(value1)))) + } else { + params += (new ParamName(operand1) -> ParamValue.IntegerInput(BigInt.apply(value1))) + } + + params += (new ParamName("end") -> ParamValue.UnionValueSelected(operand2)) + if (operand2IsFact) { + params += (new ParamName(operand2) -> ParamValue.IntegerFact(new FactId(BigInt.apply(value2)))) + } else { + params += (new ParamName(operand2) -> ParamValue.IntegerInput(BigInt.apply(value2))) + } + } + + val action = new Action(new ActionType("UpdateIntegerFacts"), params.toMap) + + action + } + + def processActionSetFact(expression: List[Any], parentRuleId: BigInt, boolValue: Boolean): Action = { + val factId = expression.lift(2).get.asInstanceOf[BigInt] + val params = Map( + new ParamName("fact") -> new ParamValue.BooleanFact(new FactId(factId)), + new ParamName("value") -> new SelectedListValue(if (boolValue) "true" else "false") + ) + + val action = new Action(new ActionType("UpdateBooleanFact"), params) + + action + } + + def processAnywhereCheck(expression: List[Any], parentRuleId: BigInt): Action = { + //val factId = expression.lift(2).get.asInstanceOf[BigInt] + val action = new Action(new ActionType("EnableAnywhereLinkToHere"), Map()) + action + } + + def processDisplayedNode(expression: List[Any], parentRuleId: BigInt): Action = { + //val instruction = expression.lift(1).get.asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] + + val factType = expression.lift(2).get.asInstanceOf[String] + val value = expression.lift(3).get + //val actionId = expression.lift(4).get.asInstanceOf[BigInt] + + var params: Map[ParamName, ParamValue] = null + + factType match { + case "alternative text" => + params = Map( + new ParamName("textInput") -> new StringInput(value.asInstanceOf[String]), + new ParamName("text") -> new UnionValueSelected("textInput") + ) + case "text fact" => + params = Map( + new ParamName("stringFactValue") -> new ParamValue.StringFact(new FactId(value.asInstanceOf[BigInt])), + new ParamName("text") -> new UnionValueSelected("stringFactValue") + ) + case "number fact" => + params = Map( + new ParamName("NumberFactValue") -> new ParamValue.IntegerFact(new FactId(value.asInstanceOf[BigInt])), + new ParamName("text") -> new UnionValueSelected("NumberFactValue") + ) + } + + new Action(new ActionType("UpdateText"), params) + } + + def processSetValue(expression: List[Any], parentRuleId: BigInt): Action = { + val factId = expression.lift(2).get.asInstanceOf[BigInt] + val value = expression.lift(3).get.asInstanceOf[String] + + val params = Map( + new ParamName("fact") -> new ParamValue.StringFact(new FactId(factId)), + new ParamName("value") -> new StringInput(value) + ) + + new Action(new ActionType("UpdateStringFact"), params) + } + + def processCreateAction(i: Iterator[Any]): Unit = { + //val actionName = + i.next().asInstanceOf[String] + val actionType = i.next().asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] + val expression = i.next().asInstanceOf[List[Any]] + val parentRuleId = i.next().asInstanceOf[BigInt] + //val actionId = + i.next().asInstanceOf[BigInt] + //var newActionType = actionType + + actionType match { + case "clicked-link" => + val linkActionType = expression.lift(1).get.asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] + + var action: Action = null + + linkActionType match { + case "follow-link" => processFollowLink(expression, parentRuleId) + case "assert" => val boolValue = true; action = processActionSetFact(expression, parentRuleId, boolValue) + case "retract" => val boolValue = false; action = processActionSetFact(expression, parentRuleId, boolValue) + case "set-number-fact" => action = processSetNumberFact(expression, parentRuleId) + case "anywhere-check" => action = processAnywhereCheck(expression, parentRuleId) + case "set-value!" => action = processSetValue(expression, parentRuleId) + case _ => //Console.println("Link Action: " + linkActionType + " " + expression) + } + + if (action != null) { + val node = latestNode + + if (node != null) { + node.content.rulesets.foreach(ruleSet => { + ruleSet.rules.filter(_.id == new RuleId(parentRuleId)).foreach(rule => { + + val newActions = rule.actions.:+(action) + val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) + val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } + val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) + val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) + val newContent = node.content.copy(node.content.text, newFullSet) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) + + nodes.remove(node.id) + nodes.put(newNode.id, newNode) + latestNode = newNode + }) + }) + } + } + case "entered-node" => + val linkActionType = expression.lift(1).get.asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] + var action: Action = null + linkActionType match { + case "retract" => val boolValue = false; action = processActionSetFact(expression, parentRuleId, boolValue) + case "assert" => val boolValue = true; action = processActionSetFact(expression, parentRuleId, boolValue) + case "set-number-fact" => action = processSetNumberFact(expression, parentRuleId) + case "anywhere-check" => action = processAnywhereCheck(expression, parentRuleId) + case "set-value!" => action = processSetValue(expression, parentRuleId) + case _ => //Console.println("Link Action: " + linkActionType + " " + expression) + } + + if (action != null) { + val node = latestNode + + if (node != null) { + node.rules.filter(_.id == new RuleId(parentRuleId)).foreach(rule => { + val newActions = rule.actions.:+(action) + val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) + val newRules: List[Rule] = node.rules.map { i => if (i == rule) newRule else i } + val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) + + nodes.remove(node.id) + nodes.put(newNode.id, newNode) + latestNode = newNode + }) + } + } + case "anywhere-check" => + val action = processAnywhereCheck(expression, parentRuleId) + + if (action != null) { + val node = latestNode + + if (node != null) { + node.rules.filter(_.id == new RuleId(parentRuleId)).foreach(rule => { + val newActions = rule.actions.:+(action) + val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) + val newRules: List[Rule] = node.rules.map { i => if (i == rule) newRule else i } + val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) + + nodes.remove(node.id) + nodes.put(newNode.id, newNode) + latestNode = newNode + }) + } + } + case "displayed-node" => + val action = processDisplayedNode(expression, parentRuleId) + + if (action != null) { + val node = latestNode + + if (node != null) { + node.content.rulesets.foreach(ruleSet => { + ruleSet.rules.filter(_.id == new RuleId(parentRuleId)).foreach(rule => { + + val newActions = rule.actions.:+(action) + val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) + val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } + val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) + val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) + val newContent = node.content.copy(node.content.text, newFullSet) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) + + nodes.remove(node.id) + nodes.put(newNode.id, newNode) + latestNode = newNode + }) + }) + } + } + case _ => //Console.println("Action: " + actionType + " " + expression) + } + + } + + def processCreateTypedCondition2(i: Iterator[Any]): Unit = { + //val conditionName = + i.next().asInstanceOf[String] + val conditionType = i.next().asInstanceOf[BigInt] + + var newConditionType = "" + + conditionType.toInt match { + case 0 => newConditionType = "NodeCondition" + case 1 => newConditionType = "LinkCondition" + case 2 => newConditionType = "BooleanFactValue" + case 3 => newConditionType = "IntegerFactComparison" + case _ => //Console.println("Condition Type: " + conditionType) + } + + val targetId = i.next().asInstanceOf[BigInt] // Node/Link/Fact ID + + var status = i.next() + + val ruleId = i.next().asInstanceOf[BigInt] + //val fixedIdString = + i.next().asInstanceOf[String] + //val conditionId = + i.next().asInstanceOf[BigInt] + //val numFactArgsString = + i.next().asInstanceOf[String] + val numFactArgs = i.next() + + val node = latestNode + if (node != null) { + if (isWithinNode) { + node.rules.foreach(rule => { + if (rule.id == new RuleId(ruleId)) { + val definitions = ConditionDefinitions.apply() + definitions.foreach(definition => { + if (definition.conditionType == new ConditionType(newConditionType)) { + var params: Map[ParamName, ParamValue] = Map() + newConditionType match { + case "NodeCondition" => + params = Map( + new ParamName("node") -> new ParamValue.Node(new NodeId(targetId)), + new ParamName("status") -> new SelectedListValue(status.asInstanceOf[BigInt].toInt match { case 0 => "not visited"; case 1 => "visited"; case 2 => "is previous"; case 3 => "is not previous"; case 4 => "current" }) + ) + + case "LinkCondition" => + params = Map( + new ParamName("link") -> new ParamValue.Link(new RulesetId(targetId)), + new ParamName("status") -> new SelectedListValue(if (status.asInstanceOf[BigInt] == BigInt.apply(1)) "followed" else "not followed") + ) + + case "BooleanFactValue" => + + if (status.isInstanceOf[BigInt]) { + status = if (status == BigInt.apply(1)) true else false + } + params = Map( + new ParamName("fact") -> new ParamValue.BooleanFact(new FactId(targetId)), + new ParamName("state") -> new SelectedListValue(if (status.asInstanceOf[Boolean]) "true" else "false") + ) + + case "IntegerFactComparison" => + val numFactArguments = numFactArgs.asInstanceOf[List[Any]].iterator + //val listString = + numFactArguments.next().asInstanceOf[String] + val operator = numFactArguments.next().asInstanceOf[String] + val mode = numFactArguments.next().asInstanceOf[String] + val value = numFactArguments.next().asInstanceOf[String] + val paramV = if (mode == "Fact") new ParamValue.IntegerFact(new FactId(BigInt.apply(value))) else new ParamValue.IntegerInput(BigInt.apply(value)) + params = Map( + new ParamName("fact") -> new ParamValue.IntegerFact(new FactId(targetId)), + new ParamName(if (mode == "Fact") "otherFact" else "input") -> paramV, + new ParamName("operator") -> new SelectedListValue(operator), + new ParamName("comparisonValue") -> new UnionValueSelected(if (mode == "Fact") "otherFact" else "input") + ) + + case _ => + } + + val newCondition = new Condition(new ConditionType(newConditionType), params) + val newConditions = newCondition :: rule.conditions + val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, newConditions, rule.actions) + val newRules = node.rules.map { i => if (i == rule) newRule else i } + + val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) + + nodes.remove(node.id) + nodes.put(newNode.id, newNode) + latestNode = newNode + } + }) + } + }) + } else { + node.content.rulesets.foreach(ruleSet => { + ruleSet.rules.foreach(rule => { + if (rule.id == new RuleId(ruleId)) { + val definitions = ConditionDefinitions.apply() + + definitions.foreach(definition => { + if (definition.conditionType == new ConditionType(newConditionType)) { + var params: Map[ParamName, ParamValue] = Map() + newConditionType match { + case "NodeCondition" => + params = Map( + new ParamName("node") -> new ParamValue.Node(new NodeId(targetId)), + new ParamName("status") -> new SelectedListValue(status.asInstanceOf[BigInt].toInt match { case 0 => "not visited"; case 1 => "visited"; case 2 => "is previous"; case 3 => "is not previous"; case 4 => "current" }) + ) + + case "LinkCondition" => + params = Map( + new ParamName("link") -> new ParamValue.Link(new RulesetId(targetId)), + new ParamName("status") -> new SelectedListValue(if (status.asInstanceOf[BigInt] == BigInt.apply(1)) "followed" else "not followed") + ) + + case "BooleanFactValue" => + + if (status.isInstanceOf[BigInt]) { + status = if (status == BigInt.apply(1)) true else false + } + + params = Map( + new ParamName("fact") -> new ParamValue.BooleanFact(new FactId(targetId)), + new ParamName("state") -> new SelectedListValue(if (status.asInstanceOf[Boolean]) "true" else "false") + ) + + case "IntegerFactComparison" => + val numFactArguments = numFactArgs.asInstanceOf[List[Any]].iterator + //val listString = + numFactArguments.next().asInstanceOf[String] + val operator = numFactArguments.next().asInstanceOf[String] + val mode = numFactArguments.next().asInstanceOf[String] + val value = numFactArguments.next().asInstanceOf[String] + val paramV = if (mode == "Fact") new ParamValue.IntegerFact(new FactId(BigInt.apply(value))) else new ParamValue.IntegerInput(BigInt.apply(value)) + params = Map( + new ParamName("fact") -> new ParamValue.IntegerFact(new FactId(targetId)), + new ParamName(if (mode == "Fact") "otherFact" else "input") -> paramV, + new ParamName("operator") -> new SelectedListValue(operator), + new ParamName("comparisonValue") -> new UnionValueSelected(if (mode == "Fact") "otherFact" else "input") + ) + + case _ => + } + + val newCondition = new Condition(new ConditionType(newConditionType), params) + val newConditions: List[Condition] = newCondition :: rule.conditions + val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, newConditions, rule.actions) + val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } + + val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) + val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) + val newContent = node.content.copy(node.content.text, newFullSet) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) + + nodes.remove(node.id) + nodes.put(newNode.id, newNode) + latestNode = newNode + } + }) + } + }) + }) + } + } + } + + def processSubSection(iterator: Iterator[Any]): Unit = { + while (iterator.hasNext) { + val current = iterator.next() + current match { + case list: List[Any] => + val i = current.asInstanceOf[List[Any]].iterator + val firstToken = i.next() + firstToken match { + case str: String => + val instruction = firstToken.asInstanceOf[String] + instruction match { + case "create-typed-rule3" => processCreateTypedRule3(i) + case "create-action" => processCreateAction(i) + case "create-typed-condition2" => processCreateTypedCondition2(i) + case _ => + } + } + } + } + } + + def processCreateLink(i: Iterator[Any]): Unit = { + val linkName = i.next().asInstanceOf[String] + val fromNodeID = i.next().asInstanceOf[BigInt] + //val toNodeID = + i.next().asInstanceOf[BigInt] + val startIndex = i.next().asInstanceOf[BigInt] + val endIndex = i.next().asInstanceOf[BigInt] + //val useDestination = + i.next().asInstanceOf[Boolean] + //val useAltDestination = + i.next().asInstanceOf[Boolean] + //val useAltText = + i.next().asInstanceOf[Boolean] + //val altDestination = + i.next().asInstanceOf[BigInt] + //val altText = + i.next().asInstanceOf[String] + //val updateDisplay = + i.next().asInstanceOf[Boolean] + val linkId = i.next().asInstanceOf[BigInt] + + val node = nodes.get(new NodeId(fromNodeID)).get + + if (node != null) { + val newSet = new Ruleset(new RulesetId(linkId), linkName, new RulesetIndexes(new TextIndex(startIndex), new TextIndex(endIndex)), List()) + val newContent = node.content.copy(node.content.text, node.content.rulesets.:+(newSet)) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) + + nodes.remove(node.id) + nodes.put(newNode.id, newNode) + + latestNode = newNode + } + } + + def processCreateNode(i: Iterator[Any]): Unit = { + val nameValue = i.next().asInstanceOf[String] + val contentText = i.next().asInstanceOf[String] + var next = i.next() + val x = next match { + case bigInt: BigInt => bigInt.doubleValue; + case _ => next.asInstanceOf[Double] + } + next = i.next() + val y = next match { + case bigInt: BigInt => bigInt.doubleValue; + case _ => next.asInstanceOf[Double] + } + //val isAnywhere = + i.next().asInstanceOf[Boolean] + //val updateDisplay = + i.next() + val nodeId = i.next().asInstanceOf[BigInt] + + val ruleSet: List[Rule] = List() + + val node = new Node(new NodeId(nodeId), nameValue, new NodeContent(contentText, List()), if (nodeId == startNode) true else false, ruleSet) + + nodes.put(node.id, node) + nodesX.put(node.id, x) + nodesY.put(node.id, y) + + latestNode = node + } + + def processCreateFact(i: Iterator[Any]) = { + while (i.hasNext) { + val factName = i.next().asInstanceOf[String] + val factType = i.next().asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] + val factId = new FactId(i.next().asInstanceOf[BigInt]) + + var fact: Fact = null + + factType match { + case "boolean" => val boolValue = false; fact = BooleanFact.apply(factId, factName, boolValue) + case "string" => fact = StringFact.apply(factId, factName, "") + case "number" => fact = IntegerFact.apply(factId, factName, 0) + } + + story = story.addFact(fact) + } + } + + def processSection(iterator: Iterator[Any]): Unit = { + val headers = new scala.collection.mutable.Queue[List[Any]] + while (iterator.hasNext) { + val current = iterator.next() + current match { + case list: List[Any] => + val i = current.asInstanceOf[List[Any]].iterator + val firstToken = i.next() + firstToken match { + case instruction: String => + instruction match { + case "make-hypertext" => processHeader(i) + case "set-story-title!" => story = story.copy(i.next().asInstanceOf[String]) + case "set-author-name!" => story = story.changeAuthor(i.next().asInstanceOf[String]) + case "set-story-comment!" => story = story.updateMetadata(story.metadata.copy(i.next().asInstanceOf[String])) + case "set-disable-back-button!" => story = story.updateMetadata(story.metadata.copy(story.metadata.comments, story.metadata.readerStyle, i.next().asInstanceOf[Boolean])) + case "set-disable-restart-button!" => story = story.updateMetadata(story.metadata.copy(story.metadata.comments, story.metadata.readerStyle, story.metadata.isBackButtonDisabled, i.next().asInstanceOf[Boolean])) + case "set-start-node!" => startNode = i.next().asInstanceOf[BigInt] + case "create-fact" => processCreateFact(i) + case "begin" => headers.enqueue(current.asInstanceOf[List[Any]]) + case _ => //Console.println("Base: " + instruction) + } + } + } + } + + while (headers.nonEmpty) { + val i = headers.dequeue().iterator + processHeader(i) + } + } +} \ No newline at end of file diff --git a/hypedyn-default-story-viewer/src/main/scala/org/narrativeandplay/hypedyn/storyviewer/StoryViewer.scala b/hypedyn-default-story-viewer/src/main/scala/org/narrativeandplay/hypedyn/storyviewer/StoryViewer.scala index cac538a..01ff003 100644 --- a/hypedyn-default-story-viewer/src/main/scala/org/narrativeandplay/hypedyn/storyviewer/StoryViewer.scala +++ b/hypedyn-default-story-viewer/src/main/scala/org/narrativeandplay/hypedyn/storyviewer/StoryViewer.scala @@ -82,16 +82,26 @@ class StoryViewer extends ScrollPane with Plugin with NarrativeViewer with Savea } /** - * Defines what to do when a node is created - * - * @param node The created node - */ + * Defines what to do when a node is created + * + * @param node The created node + */ override def onNodeCreated(node: Nodal): Unit = { val createdNode = viewer.addNode(node) nodeLocations get createdNode.id foreach (moveNode(createdNode.id, _)) } + /** + * Defines what to do when a node is to be moved + * + * @param node The created node + */ + override def onMoveNode(node: Nodal, x:Double, y:Double): Unit = { + moveNode(node.id, new Vector2(x.toDouble, y.toDouble)) + sizeToChildren() + } + /** * Defines what to do when a node is updated * diff --git a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/Main.scala b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/Main.scala index 1b63c3a..e2b2aa3 100644 --- a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/Main.scala +++ b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/Main.scala @@ -1,32 +1,29 @@ package org.narrativeandplay.hypedyn import java.io.File -import javafx.beans.value.ObservableValue - -import scalafx.Includes._ -import scalafx.application.{Platform, JFXApp} -import scalafx.application.JFXApp.PrimaryStage -import scalafx.beans.property.StringProperty -import scalafx.scene.Scene -import scalafx.scene.control.Alert -import scalafx.scene.image.{ImageView, Image} -import scalafx.scene.layout.{VBox, BorderPane} import com.sun.glass.ui import com.sun.glass.ui.Application.EventHandler import org.fxmisc.easybind.EasyBind -import rx.lang.scala.subjects.{PublishSubject, SerializedSubject} - import org.narrativeandplay.hypedyn.dialogs._ import org.narrativeandplay.hypedyn.events._ import org.narrativeandplay.hypedyn.logging.Logger import org.narrativeandplay.hypedyn.plugins.PluginsController -import org.narrativeandplay.hypedyn.story.{Narrative, Nodal} import org.narrativeandplay.hypedyn.story.rules.{ActionDefinition, ConditionDefinition, Fact} +import org.narrativeandplay.hypedyn.story.{Narrative, Nodal} import org.narrativeandplay.hypedyn.uicomponents._ import org.narrativeandplay.hypedyn.undo.UndoController -import org.narrativeandplay.hypedyn.utils.Scala2JavaFunctionConversions._ import org.narrativeandplay.hypedyn.utils.{System => Sys} +import rx.lang.scala.subjects.{PublishSubject, SerializedSubject} + +import scalafx.Includes._ +import scalafx.application.JFXApp.PrimaryStage +import scalafx.application.{JFXApp, Platform} +import scalafx.beans.property.StringProperty +import scalafx.scene.Scene +import scalafx.scene.control.Alert +import scalafx.scene.image.{Image, ImageView} +import scalafx.scene.layout.{BorderPane, VBox} /** * Entry point for the application @@ -54,10 +51,15 @@ object Main extends JFXApp { def refreshRecent = refreshStream /** - * Returns a new file dialog - */ + * Returns a new file dialog + */ def fileDialog = new FileDialog(stage) + /** + * Returns a new legacy file dialog + */ + def legacyFileDialog = new LegacyFileDialog(stage) + /** * Returns a new directory selection dialog */ diff --git a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/dialogs/LegacyFileDialog.scala b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/dialogs/LegacyFileDialog.scala new file mode 100644 index 0000000..6111c55 --- /dev/null +++ b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/dialogs/LegacyFileDialog.scala @@ -0,0 +1,23 @@ +package org.narrativeandplay.hypedyn.dialogs + +import java.io.File + +import scalafx.Includes._ +import scalafx.stage.FileChooser.ExtensionFilter +import scalafx.stage.{FileChooser, Window} + +/** + * A wrapper over the ScalaFX file chooser, for correcting the ScalaFX API + * + * @param ownerWindow The parent dialog of the file chooser, for inheriting icons + */ +class LegacyFileDialog(ownerWindow: Window) extends FileChooser { + extensionFilters += new ExtensionFilter("HypeDyn 1 Story", "*.dyn") + + /** + * Shows a new open file dialog + * + * @return An option containing the selected file, or None if no file was selected + */ + def showOpenFileDialog(): Option[File] = Option(super.showOpenDialog(ownerWindow)) +} diff --git a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/events/UiEventDispatcher.scala b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/events/UiEventDispatcher.scala index 2ea31e6..e1e9d2c 100644 --- a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/events/UiEventDispatcher.scala +++ b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/events/UiEventDispatcher.scala @@ -2,22 +2,19 @@ package org.narrativeandplay.hypedyn.events import java.io.File -import scala.collection.mutable.ArrayBuffer - -import scalafx.Includes._ -import scalafx.beans.property.{ObjectProperty, BooleanProperty} -import scalafx.scene.control.{ButtonType, Alert} - -import rx.lang.scala.Observable - -import org.narrativeandplay.hypedyn.story.rules.Fact import org.narrativeandplay.hypedyn.Main import org.narrativeandplay.hypedyn.dialogs.NodeEditor +import org.narrativeandplay.hypedyn.story.InterfaceToUiImplementation._ +import org.narrativeandplay.hypedyn.story.rules.Fact import org.narrativeandplay.hypedyn.story.{Nodal, NodeId} import org.narrativeandplay.hypedyn.uicomponents.FactViewer -import org.narrativeandplay.hypedyn.story.InterfaceToUiImplementation._ import org.narrativeandplay.hypedyn.utils.HypedynPreferences +import scala.collection.mutable.ArrayBuffer +import scalafx.Includes._ +import scalafx.beans.property.{BooleanProperty, ObjectProperty} +import scalafx.scene.control.{Alert, ButtonType} + /** * Dispatcher for UI events * @@ -97,6 +94,14 @@ object UiEventDispatcher { } } + EventBus.ImportResponses foreach { evt => + val fileToImport = Main.legacyFileDialog.showOpenFileDialog() + + fileToImport foreach { f => + EventBus.send(ImportFromFile(f, UiEventSourceIdentity)) + } + } + EventBus.ExportResponses foreach { evt => // get location to export to (a directory) val dirToSaveTo = Main.directoryDialog.showDialog() @@ -110,7 +115,6 @@ object UiEventDispatcher { EventBus.send(StoryRan(UiEventSourceIdentity)) } - EventBus.StoryLoadedEvents foreach { evt => FactViewer.facts.clear() evt.story.facts foreach { f => FactViewer.facts += f } @@ -217,6 +221,9 @@ object UiEventDispatcher { def requestExport(): Unit = { EventBus.send(ExportRequest(UiEventSourceIdentity)) } + def requestImport(): Unit = { + EventBus.send(ImportRequest(UiEventSourceIdentity)) + } def requestRunStory(): Unit = { EventBus.send(RunRequest(UiEventSourceIdentity)) } diff --git a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/uicomponents/Menubar.scala b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/uicomponents/Menubar.scala index db5f383..4b21ac2 100644 --- a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/uicomponents/Menubar.scala +++ b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/uicomponents/Menubar.scala @@ -1,17 +1,16 @@ package org.narrativeandplay.hypedyn.uicomponents -import scalafx.Includes._ -import scalafx.application.Platform -import scalafx.beans.property.ReadOnlyBooleanProperty -import scalafx.event.ActionEvent -import scalafx.scene.control._ - import org.narrativeandplay.hypedyn.Main import org.narrativeandplay.hypedyn.events.UiEventDispatcher import org.narrativeandplay.hypedyn.keycombinations.KeyCombinations import org.narrativeandplay.hypedyn.logging.Logger import org.narrativeandplay.hypedyn.utils.{HypedynPreferences, System} +import scalafx.Includes._ +import scalafx.application.Platform +import scalafx.beans.property.ReadOnlyBooleanProperty +import scalafx.scene.control._ + /** * Menu bar for the application */ @@ -34,7 +33,7 @@ class Menubar(mainStageFocused: ReadOnlyBooleanProperty) extends MenuBar { * File Menu */ private lazy val fileMenu = new Menu("File") { - items.addAll(newStory, openStory, openRecentStory, saveStory, saveAs, export, + items.addAll(newStory, openStory, openRecentStory, saveStory, saveAs, importLegacy, export, new SeparatorMenuItem(), editStoryProperties, new SeparatorMenuItem(), @@ -93,6 +92,11 @@ class Menubar(mainStageFocused: ReadOnlyBooleanProperty) extends MenuBar { } } + private lazy val importLegacy = new MenuItem("Import...") { + onAction = { _ => + UiEventDispatcher.requestImport() + } + } private lazy val export = new MenuItem("Export...") { //accelerator = KeyCombinations.Export From 2c4baa932c7c9135256384aa665b6a8ad2e5570f Mon Sep 17 00:00:00 2001 From: DennisAng Date: Thu, 10 Mar 2016 20:30:22 +0800 Subject: [PATCH 2/3] Swapped out Scala parser combinators library for FastParse --- .../hypedyn/events/Event.scala | 1 - .../hypedyn/events/EventBus.scala | 2 - .../narrativeviewer/NarrativeViewer.scala | 20 +- hypedyn-core/build.gradle | 2 + .../hypedyn/events/CoreEventDispatcher.scala | 8 +- .../hypedyn/utils/parsing/SchemeParser.scala | 911 +++++++++++------- .../hypedyn/storyviewer/StoryViewer.scala | 10 - .../org/narrativeandplay/hypedyn/Main.scala | 21 +- .../hypedyn/dialogs/LegacyFileDialog.scala | 23 - .../hypedyn/events/UiEventDispatcher.scala | 7 +- 10 files changed, 602 insertions(+), 403 deletions(-) delete mode 100644 hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/dialogs/LegacyFileDialog.scala diff --git a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/Event.scala b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/Event.scala index 5958194..1c7e653 100644 --- a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/Event.scala +++ b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/Event.scala @@ -142,7 +142,6 @@ sealed case class RedoResponse(src: String) extends Response sealed trait Action extends Event sealed case class CreateNode(node: Nodal, src: String) extends Action sealed case class UpdateNode(node: Nodal, updatedNode: Nodal, src: String) extends Action -sealed case class MoveNode(node: Nodal, x:Double, y:Double, src: String) extends Action sealed case class DestroyNode(node: Nodal, src: String) extends Action sealed case class CreateFact(fact: Fact, src: String) extends Action diff --git a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/EventBus.scala b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/EventBus.scala index 2eea170..aa29365 100644 --- a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/EventBus.scala +++ b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/events/EventBus.scala @@ -118,8 +118,6 @@ object EventBus { val CreateStoryEvents = eventBus collect { case e: CreateStory => e } val UpdateStoryPropertiesEvents = eventBus collect { case e: UpdateStoryProperties => e } - val MoveNodeEvents = eventBus collect { case e: MoveNode => e } - /** * Event stream of all `Completion`s */ diff --git a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/plugins/narrativeviewer/NarrativeViewer.scala b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/plugins/narrativeviewer/NarrativeViewer.scala index 85313ea..4e58143 100644 --- a/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/plugins/narrativeviewer/NarrativeViewer.scala +++ b/hypedyn-api/src/main/scala/org/narrativeandplay/hypedyn/plugins/narrativeviewer/NarrativeViewer.scala @@ -19,7 +19,6 @@ trait NarrativeViewer { * The plugin is automatically hooked into the appropriate event streams */ EventBus.NodeCreatedEvents foreach { n => onNodeCreated(n.node) } - EventBus.MoveNodeEvents foreach { n => onMoveNode(n.node, n.x, n.y) } EventBus.NodeUpdatedEvents foreach { n => onNodeUpdated(n.node, n.updatedNode) } EventBus.NodeDestroyedEvents foreach { n => onNodeDestroyed(n.node) } @@ -31,22 +30,13 @@ trait NarrativeViewer { def onNodeCreated(node: Nodal): Unit /** - * Defines what to do when a node is updated - * - * @param node The node to be updated - * @param updatedNode The same node with the updates already applied - */ + * Defines what to do when a node is updated + * + * @param node The node to be updated + * @param updatedNode The same node with the updates already applied + */ def onNodeUpdated(node: Nodal, updatedNode: Nodal): Unit - /** - * Defines what to do when a node should move - * - * @param node The node to be moved - * @param x The x coordinate - * @param y The y coordinate - */ - def onMoveNode(node: Nodal, x:Double, y:Double): Unit - /** * Defines what to do when a node is destroyed * diff --git a/hypedyn-core/build.gradle b/hypedyn-core/build.gradle index 2698bde..45139b7 100644 --- a/hypedyn-core/build.gradle +++ b/hypedyn-core/build.gradle @@ -5,6 +5,8 @@ dependencies { // For serialisation to JSON (save/load/export) compile "org.json4s:json4s-native_${rootProject.majorScalaVersion}:3.3.0" + compile "com.lihaoyi:fastparse_2.11:0.3.6" + compile "commons-io:commons-io:2.4" compile "org.fxmisc.easybind:easybind:1.0.3" diff --git a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/events/CoreEventDispatcher.scala b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/events/CoreEventDispatcher.scala index d8f712b..fbfe31a 100644 --- a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/events/CoreEventDispatcher.scala +++ b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/events/CoreEventDispatcher.scala @@ -12,7 +12,6 @@ import org.narrativeandplay.hypedyn.story.internal.Story import org.narrativeandplay.hypedyn.story.rules.{ActionDefinitions, ConditionDefinitions, Fact} import org.narrativeandplay.hypedyn.undo._ import org.narrativeandplay.hypedyn.utils.parsing.SchemeParser - /** * Main event dispatcher for the core */ @@ -189,7 +188,11 @@ object CoreEventDispatcher { EventBus.ImportFromFileEvents foreach { evt => val dataToImport = IoController read evt.file - val story:Story = SchemeParser.parse(dataToImport) + val parseResult = SchemeParser.parse(dataToImport) + val story = parseResult("story").asInstanceOf[Story] + val pluginData = parseResult("plugins").asInstanceOf[AstMap] + + Console.println(pluginData.toString) StoryController.load(story) @@ -199,6 +202,7 @@ object CoreEventDispatcher { UndoController.markCurrentPosition() EventBus.send(StoryLoaded(StoryController.story, CoreEventSourceIdentity)) + EventBus.send(DataLoaded(pluginData.toMap, CoreEventSourceIdentity)) EventBus.send(FileLoaded(loadedFile, CoreEventSourceIdentity)) } diff --git a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala index cf6d7cc..6a118d9 100644 --- a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala +++ b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala @@ -1,99 +1,108 @@ package org.narrativeandplay.hypedyn.utils.parsing -import org.narrativeandplay.hypedyn.events._ +import scala.collection.mutable + +import fastparse.all._ + +import org.narrativeandplay.hypedyn.serialisation._ import org.narrativeandplay.hypedyn.story.NodalContent.{RulesetId, RulesetIndexes, TextIndex} import org.narrativeandplay.hypedyn.story._ import org.narrativeandplay.hypedyn.story.internal.NodeContent.Ruleset import org.narrativeandplay.hypedyn.story.internal.{Node, NodeContent, Story} import org.narrativeandplay.hypedyn.story.rules.Actionable.ActionType import org.narrativeandplay.hypedyn.story.rules.Conditional.ConditionType -import org.narrativeandplay.hypedyn.story.rules.RuleLike.ParamValue.{StringInput, SelectedListValue, UnionValueSelected} -import org.narrativeandplay.hypedyn.story.rules.RuleLike.{ParamValue, ParamName} +import org.narrativeandplay.hypedyn.story.rules.RuleLike.ParamValue.{SelectedListValue, StringInput, UnionValueSelected} +import org.narrativeandplay.hypedyn.story.rules.RuleLike.{ParamName, ParamValue} import org.narrativeandplay.hypedyn.story.rules.internal.{Action, _} import org.narrativeandplay.hypedyn.story.rules.{BooleanOperator, _} -import scala.collection.mutable -import scala.util.parsing.combinator.JavaTokenParsers +object SchemeParser { -object SchemeParser extends JavaTokenParsers { - var isParsing = false + /** + * Keeps track of node positions + */ + private var nodePositions = AstList() - def boolean: Parser[Boolean] = ("#t" | "#f" | "true" | "false") ^^ { case "#t" => true; case "true" => true; case "#f" => false; case "false" => false } + /** + * Parses a string into a Story + * + * @param s The input string to parse + * @return The parsed story + */ + def parse(s: String): Map[String, Any] = { - def int = wholeNumber ^^ { s => BigInt.apply(s) } + val spaceValue = P(CharsWhile(" \r\n".contains(_: Char)).?) + val digitValue = P("-".? ~ CharsWhile('0' to '9' contains (_: Char)).!).map(x => if (x.nonEmpty) BigInt(x.toString)) + val doubleValue = P((digitValue ~ "." ~ digitValue).!).map(x => x.toString.toDouble) - def string: Parser[String] = stringLiteral ^^ { str => StringContext treatEscapes str.substring(1, str.length - 1) } + val trueValue = P(P("#t") | P("true")).map(_ => true) + val falseValue = P(P("#f") | P("false")).map(_ => false) + val booleanValue = trueValue | falseValue - def id = "([A-Za-z-!:?0-9])+".r ^^ { - _.toString - } + val escape = P(P("\\") ~ CharIn("\"/\\bfnrt").!) - def double = "[0-9]+[.][0-9]+".r ^^ { - _.toDouble - } + val stringCharsValue = P(CharsWhile(!"\"\\".contains(_: Char)).!) + val identifier = P(CharsWhile(!"\" )".contains(_: Char)).!) - var startNode: BigInt = BigInt.int2bigInt(-1) + val stringLiteralValue = P(spaceValue ~ + "\"" ~ (stringCharsValue | escape).rep.! ~ "\"").map(StringContext treatEscapes _.toString) - def list: Parser[List[Any]] = "(" ~> rep(expression) <~ ")" ^^ { s: List[Any] => s } + lazy val listValue = P("(" ~ expressionValue.rep.? ~ ")").map(x => if (x.nonEmpty) x.get.toList) + lazy val expressionValue: P[Any] = P(spaceValue ~ + P(listValue | booleanValue | doubleValue | digitValue | identifier | stringLiteralValue) ~ spaceValue) - var nodes: mutable.Map[NodeId, Node] = new mutable.HashMap[NodeId, Node]() - var nodesX: mutable.Map[NodeId, Double] = new mutable.HashMap[NodeId, Double]() - var nodesY: mutable.Map[NodeId, Double] = new mutable.HashMap[NodeId, Double]() - var latestNode: Node = null - var story: Story = null - var isWithinNode = true + var story = Story() - def expression: Parser[Any] = list | boolean | double | int | id | string + val list: List[Any] = expressionValue.parse(s).get.value.asInstanceOf[List[Any]] - EventBus.StoryLoadedEvents foreach { n => { - if (isParsing) { - isParsing = false + story = process(list, story) - for ((k, v) <- nodes) - { - EventBus.send(MoveNode(v, nodesX.get(v.id).get, nodesY.get(v.id).get, "SchemeParser")) - } - } - } - } + val mapFields: AstMap = AstMap("zoomLevel" -> AstFloat(1.0), "nodes" -> nodePositions) + val pluginData: AstElement = AstMap("Default Story Viewer" -> mapFields) - def parse(s: String) = { - story = new Story() - nodes.clear() - nodesX.clear() - nodesY.clear() - isWithinNode = true - latestNode = null - startNode = BigInt.int2bigInt(-1) - - val list: List[Any] = this.parseAll(this.list, s).get - process(list) - isParsing = true - - for ((k, v) <- nodes) { - story = story.addNode(v) - } + val map = Map("story" -> story, "plugins" -> pluginData) - story - // EventBus.send(NewStoryRequest("SchemeParser")) + map } - def process(l: List[Any]): Unit = { + /** + * Processes a list of tokens into a Story + * + * @param l The list of tokens to be parsed + * @param story The existing story to parse the tokens into + * @return The updated story + */ + private def process(l: List[Any], story: Story): Story = { + var newStory = story + val iterator = l.iterator while (iterator.hasNext) { val current = iterator.next() current match { - case list: List[Any] => process(current.asInstanceOf[List[Any]]) + case list: List[Any] => newStory = process(current.asInstanceOf[List[Any]], newStory) case currentString: String => currentString match { - case "begin" => processSection(iterator) + case "begin" => newStory = processFirstLevel(iterator, newStory) case _ => } } } + + newStory } - def processHeader(iterator: Iterator[Any]): Unit = { + /** + * Processes the header tokens + * + * @param iterator The token iterator + * @param story The story to be updated + * @return The updated story + */ + private def processHeader(iterator: Iterator[Any], story: Story): Story = { + var newStory = story + var currentNode = Option.empty[Node] + var isWithinNode = true + while (iterator.hasNext) { val current = iterator.next() current match { @@ -104,22 +113,52 @@ object SchemeParser extends JavaTokenParsers { case instruction: String => instruction match { case "create-node" => - processCreateNode(i) + val nodeOption = Option(processCreateNode(i)) + if (nodeOption.nonEmpty) newStory = newStory.addNode(nodeOption.get) + currentNode = nodeOption isWithinNode = true case "create-link" => - processCreateLink(i) + val nodeOption = processCreateLink(i, newStory) + + if (nodeOption.nonEmpty) { + newStory.nodes.filter(_.id == nodeOption.get.id).foreach(node => { + newStory = newStory.removeNode(node) + }) + newStory = newStory.addNode(nodeOption.get) + } + currentNode = nodeOption isWithinNode = false case "begin" => - processSubSection(i) + val nodeOption = processSecondLevel(i, currentNode, isWithinNode) + if (nodeOption.nonEmpty) { + newStory.nodes.filter(_.id == nodeOption.get.id).foreach(node => { + newStory = newStory.removeNode(node) + }) + newStory = newStory.addNode(nodeOption.get) + } + currentNode = nodeOption case _ => //Console.println("Header: " + instruction) } } case _ => } } + + newStory } - def processCreateTypedRule3(i: Iterator[Any]): Unit = { + /** + * Process the tokens for create-typed-rule-3 rule + * + * @param i The token iterator + * @param currentNode The latest node processed + * @param isWithinNode Flag that determines if the rule resides at the Node or Fragment level + * @return The modified node + */ + private def processCreateTypedRule3(i: Iterator[Any], + currentNode: Option[Node], + isWithinNode: Boolean): Option[Node] = { + val ruleName = i.next().asInstanceOf[String] //val ruleType = i.next().asInstanceOf[List[Any]].lift(2) @@ -135,20 +174,17 @@ object SchemeParser extends JavaTokenParsers { i.next().asInstanceOf[String] val fallThrough = i.next().asInstanceOf[Boolean] - val node = latestNode - - if (node != null) { - val rule = new Rule(new RuleId(fixedId), ruleName, !fallThrough, boolOperator, List(), List()) + val rule = Rule(RuleId(fixedId), ruleName, !fallThrough, boolOperator, List(), List()) + if (currentNode.nonEmpty) { + val node = currentNode.get if (isWithinNode) { val newRules: List[Rule] = node.rules.:+(rule) val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) - nodes.remove(node.id) - nodes.put(newNode.id, newNode) - latestNode = newNode + return Option(newNode) } else { - node.content.rulesets.filter(_.id == new RulesetId(linkId)).foreach(ruleSet => { + node.content.rulesets.filter(_.id == RulesetId(linkId)).foreach(ruleSet => { val newRules: List[Rule] = ruleSet.rules.:+(rule) @@ -157,36 +193,54 @@ object SchemeParser extends JavaTokenParsers { val newContent = node.content.copy(node.content.text, newFullSet) val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - nodes.remove(node.id) - nodes.put(newNode.id, newNode) - latestNode = newNode + return Option(newNode) }) } } + + Option.empty[Node] } - def processFollowLink(expression: List[Any], parentRuleId: BigInt): Unit = { + /** + * Process the tokens for the follow-link rule + * + * @param expression The combination of tokens that make up an expression + * @param parentRuleId The parent rule identifier + * @param currentNode The latest node processed + * @return + */ + private def processFollowLink(expression: List[Any], + parentRuleId: BigInt, + currentNode: Option[Node]): Option[Node] = { val newActionType = "LinkTo" //val linkId = expression.lift(2).get.asInstanceOf[BigInt] //val ruleId = expression.lift(3).get.asInstanceOf[BigInt] val toNodeId = expression.lift(5).get.asInstanceOf[BigInt] - val node = latestNode - if (node != null) { + + if (currentNode.nonEmpty) { + val node = currentNode.get node.content.rulesets.foreach(ruleSet => { ruleSet.rules.foreach(rule => { - if (rule.id == new RuleId(parentRuleId)) { - val definitions = ActionDefinitions.apply() + if (rule.id == RuleId(parentRuleId)) { + val definitions = ActionDefinitions() definitions.foreach(definition => { - if (definition.actionType == new ActionType(newActionType)) { + if (definition.actionType == ActionType(newActionType)) { val params = Map( - new ParamName("node") -> ParamValue.Node.apply(new NodeId(toNodeId)) + ParamName("node") -> ParamValue.Node(NodeId(toNodeId)) ) - val newAction = new Action(new ActionType(newActionType), params) + val newAction = Action(ActionType(newActionType), params) val newActions: List[Action] = rule.actions.:+(newAction) - val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) + val newRule = rule.copy( + rule.id, + rule.name, + rule.stopIfTrue, + rule.conditionsOp, + rule.conditions, + newActions + ) val newRules: List[Rule] = ruleSet.rules.map { x => if (x == rule) newRule else x } val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) @@ -194,18 +248,25 @@ object SchemeParser extends JavaTokenParsers { val newContent = node.content.copy(node.content.text, newFullSet) val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - nodes.remove(node.id) - nodes.put(newNode.id, newNode) - latestNode = newNode + return Option(newNode) } }) } }) }) } + + Option.empty[Node] } - def processSetNumberFact(expression: List[Any], ruleId: BigInt): Action = { + /** + * Processes the tokens for the set-number-fact action + * + * @param expression The combination of tokens that make up an expression + * @param ruleId The rule identifier + * @return The created action + */ + private def processSetNumberFact(expression: List[Any], ruleId: BigInt): Action = { val factId = expression.lift(2).get.asInstanceOf[BigInt] val setOption = expression.lift(3).get.asInstanceOf[String] // Input, Random, Fact, Math @@ -217,9 +278,9 @@ object SchemeParser extends JavaTokenParsers { //val setFactMode = expression.lift(3).get.asInstanceOf[String] val setFactValue = expression.lift(4).get.asInstanceOf[String] - params += (new ParamName("updateValue") -> ParamValue.UnionValueSelected("inputValue")) - params += (new ParamName("fact") -> ParamValue.IntegerFact(new FactId(setFactId))) - params += (new ParamName("inputValue") -> ParamValue.IntegerInput(BigInt.apply(setFactValue))) + params += (ParamName("updateValue") -> ParamValue.UnionValueSelected("inputValue")) + params += (ParamName("fact") -> ParamValue.IntegerFact(FactId(setFactId))) + params += (ParamName("inputValue") -> ParamValue.IntegerInput(BigInt(setFactValue))) case "Math" => val optionExpression = expression.lift(4).get.asInstanceOf[List[Any]] val eOperator = optionExpression.lift(1).get.asInstanceOf[String] @@ -234,28 +295,28 @@ object SchemeParser extends JavaTokenParsers { val operand1 = if (operand1IsFact) "factOperand1" else "userOperand1" val operand2 = if (operand2IsFact) "factOperand2" else "userOperand2" - params += (new ParamName("fact") -> ParamValue.IntegerFact(new FactId(factId))) - params += (new ParamName("updateValue") -> ParamValue.UnionValueSelected("computation")) - params += (new ParamName("operand1") -> ParamValue.UnionValueSelected(operand1)) + params += (ParamName("fact") -> ParamValue.IntegerFact(FactId(factId))) + params += (ParamName("updateValue") -> ParamValue.UnionValueSelected("computation")) + params += (ParamName("operand1") -> ParamValue.UnionValueSelected(operand1)) if (operand1IsFact) { - params += (new ParamName(operand1) -> ParamValue.IntegerFact(new FactId(BigInt.apply(value1)))) + params += (ParamName(operand1) -> ParamValue.IntegerFact(FactId(BigInt(value1)))) } else { - params += (new ParamName(operand1) -> ParamValue.IntegerInput(BigInt.apply(value1))) + params += (ParamName(operand1) -> ParamValue.IntegerInput(BigInt(value1))) } - params += (new ParamName("operator") -> ParamValue.SelectedListValue(eOperator)) - params += (new ParamName("operand2") -> ParamValue.UnionValueSelected(operand2)) + params += (ParamName("operator") -> ParamValue.SelectedListValue(eOperator)) + params += (ParamName("operand2") -> ParamValue.UnionValueSelected(operand2)) if (operand2IsFact) { - params += (new ParamName(operand2) -> ParamValue.IntegerFact(new FactId(BigInt.apply(value2)))) + params += (ParamName(operand2) -> ParamValue.IntegerFact(FactId(BigInt(value2)))) } else { - params += (new ParamName(operand2) -> ParamValue.IntegerInput(BigInt.apply(value2))) + params += (ParamName(operand2) -> ParamValue.IntegerInput(BigInt(value2))) } - params += (new ParamName("computation") -> ParamValue.ProductValue(List("operand1", "operator", "operand2"))) + params += (ParamName("computation") -> ParamValue.ProductValue(List("operand1", "operator", "operand2"))) case "Fact" => val setFromFactId = expression.lift(2).get.asInstanceOf[BigInt] val setToFactId = expression.lift(4).get.asInstanceOf[BigInt] - params += (new ParamName("updateValue") -> ParamValue.UnionValueSelected("integerFactValue")) - params += (new ParamName("fact") -> ParamValue.IntegerFact(new FactId(setFromFactId))) - params += (new ParamName("integerFactValue") -> ParamValue.IntegerFact(new FactId(setToFactId))) + params += (ParamName("updateValue") -> ParamValue.UnionValueSelected("integerFactValue")) + params += (ParamName("fact") -> ParamValue.IntegerFact(FactId(setFromFactId))) + params += (ParamName("integerFactValue") -> ParamValue.IntegerFact(FactId(setToFactId))) case "Random" => val optionExpression = expression.lift(4).get.asInstanceOf[List[Any]] @@ -270,91 +331,127 @@ object SchemeParser extends JavaTokenParsers { val operand1 = if (operand1IsFact) "startFact" else "startInput" val operand2 = if (operand2IsFact) "endFact" else "endInput" - params += (new ParamName("randomValue") -> ParamValue.ProductValue(List("start", "end"))) - params += (new ParamName("updateValue") -> ParamValue.UnionValueSelected("randomValue")) - params += (new ParamName("fact") -> ParamValue.IntegerFact(new FactId(factId))) + params += (ParamName("randomValue") -> ParamValue.ProductValue(List("start", "end"))) + params += (ParamName("updateValue") -> ParamValue.UnionValueSelected("randomValue")) + params += (ParamName("fact") -> ParamValue.IntegerFact(FactId(factId))) - params += (new ParamName("start") -> ParamValue.UnionValueSelected(operand1)) + params += (ParamName("start") -> ParamValue.UnionValueSelected(operand1)) if (operand1IsFact) { - params += (new ParamName(operand1) -> ParamValue.IntegerFact(new FactId(BigInt.apply(value1)))) + params += (ParamName(operand1) -> ParamValue.IntegerFact(FactId(BigInt(value1)))) } else { - params += (new ParamName(operand1) -> ParamValue.IntegerInput(BigInt.apply(value1))) + params += (ParamName(operand1) -> ParamValue.IntegerInput(BigInt(value1))) } - params += (new ParamName("end") -> ParamValue.UnionValueSelected(operand2)) + params += (ParamName("end") -> ParamValue.UnionValueSelected(operand2)) if (operand2IsFact) { - params += (new ParamName(operand2) -> ParamValue.IntegerFact(new FactId(BigInt.apply(value2)))) + params += (ParamName(operand2) -> ParamValue.IntegerFact(FactId(BigInt(value2)))) } else { - params += (new ParamName(operand2) -> ParamValue.IntegerInput(BigInt.apply(value2))) + params += (ParamName(operand2) -> ParamValue.IntegerInput(BigInt(value2))) } } - val action = new Action(new ActionType("UpdateIntegerFacts"), params.toMap) + val action = Action(ActionType("UpdateIntegerFacts"), params.toMap) action } - def processActionSetFact(expression: List[Any], parentRuleId: BigInt, boolValue: Boolean): Action = { + /** + * Processes the tokens that set a boolean fact + * + * @param expression The combination of tokens that make up an expression + * @param parentRuleId The parent rule identifier + * @param boolValue The value to set the boolean fact to + * @return The created action + */ + private def processActionSetFact(expression: List[Any], parentRuleId: BigInt, boolValue: Boolean): Action = { val factId = expression.lift(2).get.asInstanceOf[BigInt] val params = Map( - new ParamName("fact") -> new ParamValue.BooleanFact(new FactId(factId)), - new ParamName("value") -> new SelectedListValue(if (boolValue) "true" else "false") + ParamName("fact") -> ParamValue.BooleanFact(FactId(factId)), + ParamName("value") -> SelectedListValue(if (boolValue) "true" else "false") ) - val action = new Action(new ActionType("UpdateBooleanFact"), params) + val action = Action(ActionType("UpdateBooleanFact"), params) action } - def processAnywhereCheck(expression: List[Any], parentRuleId: BigInt): Action = { + /** + * Processes the tokens that enables anywhere links to a node + * + * @param expression The combination of tokens that make up an expression + * @param parentRuleId The parent rule identifier + * @return The created action + */ + private def processAnywhereCheck(expression: List[Any], parentRuleId: BigInt): Action = { //val factId = expression.lift(2).get.asInstanceOf[BigInt] - val action = new Action(new ActionType("EnableAnywhereLinkToHere"), Map()) + val action = Action(ActionType("EnableAnywhereLinkToHere"), Map()) action } - def processDisplayedNode(expression: List[Any], parentRuleId: BigInt): Action = { + /** + * Processes the tokens for the displayed-node action + * + * @param expression The combination of tokens that make up an expression + * @param parentRuleId The parent rule identifier + * @return The created action + */ + private def processDisplayedNode(expression: List[Any], parentRuleId: BigInt): Action = { //val instruction = expression.lift(1).get.asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] val factType = expression.lift(2).get.asInstanceOf[String] val value = expression.lift(3).get //val actionId = expression.lift(4).get.asInstanceOf[BigInt] - var params: Map[ParamName, ParamValue] = null + var params: Map[ParamName, ParamValue] = Map() factType match { case "alternative text" => params = Map( - new ParamName("textInput") -> new StringInput(value.asInstanceOf[String]), - new ParamName("text") -> new UnionValueSelected("textInput") + ParamName("textInput") -> StringInput(value.asInstanceOf[String]), + ParamName("text") -> UnionValueSelected("textInput") ) case "text fact" => params = Map( - new ParamName("stringFactValue") -> new ParamValue.StringFact(new FactId(value.asInstanceOf[BigInt])), - new ParamName("text") -> new UnionValueSelected("stringFactValue") + ParamName("stringFactValue") -> ParamValue.StringFact(FactId(value.asInstanceOf[BigInt])), + ParamName("text") -> UnionValueSelected("stringFactValue") ) case "number fact" => params = Map( - new ParamName("NumberFactValue") -> new ParamValue.IntegerFact(new FactId(value.asInstanceOf[BigInt])), - new ParamName("text") -> new UnionValueSelected("NumberFactValue") + ParamName("NumberFactValue") -> ParamValue.IntegerFact(FactId(value.asInstanceOf[BigInt])), + ParamName("text") -> UnionValueSelected("NumberFactValue") ) } - new Action(new ActionType("UpdateText"), params) + Action(ActionType("UpdateText"), params) } - def processSetValue(expression: List[Any], parentRuleId: BigInt): Action = { + /** + * Processes the tokens for the set-value! action + * + * @param expression The combination of tokens that make up an expression + * @param parentRuleId The parent rule identifier + * @return The created action + */ + private def processSetValue(expression: List[Any], parentRuleId: BigInt): Action = { val factId = expression.lift(2).get.asInstanceOf[BigInt] val value = expression.lift(3).get.asInstanceOf[String] val params = Map( - new ParamName("fact") -> new ParamValue.StringFact(new FactId(factId)), - new ParamName("value") -> new StringInput(value) + ParamName("fact") -> ParamValue.StringFact(FactId(factId)), + ParamName("value") -> StringInput(value) ) - new Action(new ActionType("UpdateStringFact"), params) + Action(ActionType("UpdateStringFact"), params) } - def processCreateAction(i: Iterator[Any]): Unit = { + /** + * Processes the tokens for the create-action rule + * + * @param i The token iterator + * @param currentNode The latest node processed + * @return The modified node + */ + private def processCreateAction(i: Iterator[Any], currentNode: Option[Node]): Option[Node] = { //val actionName = i.next().asInstanceOf[String] val actionType = i.next().asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] @@ -368,118 +465,144 @@ object SchemeParser extends JavaTokenParsers { case "clicked-link" => val linkActionType = expression.lift(1).get.asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] - var action: Action = null + var actionOption = Option.empty[Action] linkActionType match { - case "follow-link" => processFollowLink(expression, parentRuleId) - case "assert" => val boolValue = true; action = processActionSetFact(expression, parentRuleId, boolValue) - case "retract" => val boolValue = false; action = processActionSetFact(expression, parentRuleId, boolValue) - case "set-number-fact" => action = processSetNumberFact(expression, parentRuleId) - case "anywhere-check" => action = processAnywhereCheck(expression, parentRuleId) - case "set-value!" => action = processSetValue(expression, parentRuleId) + case "follow-link" => + return processFollowLink(expression, parentRuleId, currentNode) + case "assert" => + val boolValue = true + actionOption = Option(processActionSetFact(expression, parentRuleId, boolValue)) + case "retract" => + val boolValue = false + actionOption = Option(processActionSetFact(expression, parentRuleId, boolValue)) + case "set-number-fact" => actionOption = Option(processSetNumberFact(expression, parentRuleId)) + case "anywhere-check" => actionOption = Option(processAnywhereCheck(expression, parentRuleId)) + case "set-value!" => actionOption = Option(processSetValue(expression, parentRuleId)) case _ => //Console.println("Link Action: " + linkActionType + " " + expression) } - if (action != null) { - val node = latestNode + if (actionOption.nonEmpty) { + val action = actionOption.get - if (node != null) { + if (currentNode.nonEmpty) { + val node = currentNode.get node.content.rulesets.foreach(ruleSet => { - ruleSet.rules.filter(_.id == new RuleId(parentRuleId)).foreach(rule => { + ruleSet.rules.filter(_.id == RuleId(parentRuleId)).foreach(rule => { val newActions = rule.actions.:+(action) - val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) + val newRule = rule.copy( + rule.id, + rule.name, + rule.stopIfTrue, + rule.conditionsOp, + rule.conditions, newActions + ) val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) val newContent = node.content.copy(node.content.text, newFullSet) val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - nodes.remove(node.id) - nodes.put(newNode.id, newNode) - latestNode = newNode + return Option(newNode) }) }) } } case "entered-node" => val linkActionType = expression.lift(1).get.asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] - var action: Action = null + var action = Option.empty[Action] linkActionType match { - case "retract" => val boolValue = false; action = processActionSetFact(expression, parentRuleId, boolValue) - case "assert" => val boolValue = true; action = processActionSetFact(expression, parentRuleId, boolValue) - case "set-number-fact" => action = processSetNumberFact(expression, parentRuleId) - case "anywhere-check" => action = processAnywhereCheck(expression, parentRuleId) - case "set-value!" => action = processSetValue(expression, parentRuleId) + case "retract" => + val boolValue = false + action = Option(processActionSetFact(expression, parentRuleId, boolValue)) + case "assert" => + val boolValue = true + action = Option(processActionSetFact(expression, parentRuleId, boolValue)) + case "set-number-fact" => action = Option(processSetNumberFact(expression, parentRuleId)) + case "anywhere-check" => action = Option(processAnywhereCheck(expression, parentRuleId)) + case "set-value!" => action = Option(processSetValue(expression, parentRuleId)) case _ => //Console.println("Link Action: " + linkActionType + " " + expression) } - if (action != null) { - val node = latestNode - - if (node != null) { - node.rules.filter(_.id == new RuleId(parentRuleId)).foreach(rule => { - val newActions = rule.actions.:+(action) - val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) + if (action.nonEmpty) { + if (currentNode.nonEmpty) { + val node = currentNode.get + node.rules.filter(_.id == RuleId(parentRuleId)).foreach(rule => { + val newActions = rule.actions.:+(action.get) + val newRule = rule.copy( + rule.id, + rule.name, + rule.stopIfTrue, + rule.conditionsOp, + rule.conditions, + newActions + ) val newRules: List[Rule] = node.rules.map { i => if (i == rule) newRule else i } val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) - nodes.remove(node.id) - nodes.put(newNode.id, newNode) - latestNode = newNode + return Option(newNode) }) } } case "anywhere-check" => val action = processAnywhereCheck(expression, parentRuleId) - if (action != null) { - val node = latestNode - - if (node != null) { - node.rules.filter(_.id == new RuleId(parentRuleId)).foreach(rule => { - val newActions = rule.actions.:+(action) - val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) - val newRules: List[Rule] = node.rules.map { i => if (i == rule) newRule else i } - val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) + if (currentNode.nonEmpty) { + val node = currentNode.get + node.rules.filter(_.id == RuleId(parentRuleId)).foreach(rule => { + val newActions = rule.actions.:+(action) + val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) + val newRules: List[Rule] = node.rules.map { i => if (i == rule) newRule else i } + val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) - nodes.remove(node.id) - nodes.put(newNode.id, newNode) - latestNode = newNode - }) - } + return Option(newNode) + }) } case "displayed-node" => val action = processDisplayedNode(expression, parentRuleId) - if (action != null) { - val node = latestNode - - if (node != null) { - node.content.rulesets.foreach(ruleSet => { - ruleSet.rules.filter(_.id == new RuleId(parentRuleId)).foreach(rule => { + if (currentNode.nonEmpty) { + val node = currentNode.get + node.content.rulesets.foreach(ruleSet => { + ruleSet.rules.filter(_.id == RuleId(parentRuleId)).foreach(rule => { - val newActions = rule.actions.:+(action) - val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) - val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } - val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) - val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) - val newContent = node.content.copy(node.content.text, newFullSet) - val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - - nodes.remove(node.id) - nodes.put(newNode.id, newNode) - latestNode = newNode - }) + val newActions = rule.actions.:+(action) + val newRule = rule.copy( + rule.id, + rule.name, + rule.stopIfTrue, + rule.conditionsOp, + rule.conditions, + newActions + ) + val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } + val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) + val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) + val newContent = node.content.copy(node.content.text, newFullSet) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) + + return Option(newNode) }) - } + }) } - case _ => //Console.println("Action: " + actionType + " " + expression) + case _ => } + Option.empty[Node] } - def processCreateTypedCondition2(i: Iterator[Any]): Unit = { + /** + * Process the tokens for create-typed-condition-2 rule + * + * @param i The token iterator + * @param currentNode The latest node processed + * @param isWithinNode Flag that determines if the rule resides at the Node or Fragment level + * @return The modified node + */ + private def processCreateTypedCondition2(i: Iterator[Any], + currentNode: Option[Node], + isWithinNode: Boolean): Option[Node] = { //val conditionName = i.next().asInstanceOf[String] val conditionType = i.next().asInstanceOf[BigInt] @@ -507,36 +630,126 @@ object SchemeParser extends JavaTokenParsers { i.next().asInstanceOf[String] val numFactArgs = i.next() - val node = latestNode - if (node != null) { + + if (currentNode.nonEmpty) { + val node = currentNode.get + if (isWithinNode) { - node.rules.foreach(rule => { - if (rule.id == new RuleId(ruleId)) { - val definitions = ConditionDefinitions.apply() + node.rules.filter(_.id == RuleId(ruleId)).foreach(rule => { + val definitions = ConditionDefinitions() + definitions.foreach(definition => { + if (definition.conditionType == ConditionType(newConditionType)) { + var params: Map[ParamName, ParamValue] = Map() + newConditionType match { + case "NodeCondition" => + params = Map( + ParamName("node") -> ParamValue.Node(NodeId(targetId)), + ParamName("status") -> SelectedListValue( + status.asInstanceOf[BigInt].toInt match { + case 0 => "not visited"; + case 1 => "visited"; + case 2 => "is previous"; + case 3 => "is not previous"; + case 4 => "current" + } + ) + ) + + case "LinkCondition" => + params = Map( + ParamName("link") -> ParamValue.Link(RulesetId(targetId)), + ParamName("status") -> SelectedListValue( + if (status.asInstanceOf[BigInt] == BigInt(1)) "followed" else "not followed" + ) + ) + + case "BooleanFactValue" => + + if (status.isInstanceOf[BigInt]) { + status = if (status == BigInt(1)) true else false + } + params = Map( + ParamName("fact") -> ParamValue.BooleanFact(FactId(targetId)), + ParamName("state") -> + SelectedListValue(if (status.asInstanceOf[Boolean]) "true" else "false") + ) + + case "IntegerFactComparison" => + val numFactArguments = numFactArgs.asInstanceOf[List[Any]].iterator + //val listString = + numFactArguments.next().asInstanceOf[String] + val operator = numFactArguments.next().asInstanceOf[String] + val mode = numFactArguments.next().asInstanceOf[String] + val value = numFactArguments.next().asInstanceOf[String] + + val paramV = + if (mode == "Fact") + ParamValue.IntegerFact(FactId(BigInt(value))) + else + ParamValue.IntegerInput(BigInt(value)) + + params = Map( + ParamName("fact") -> ParamValue.IntegerFact(FactId(targetId)), + ParamName(if (mode == "Fact") "otherFact" else "input") -> paramV, + ParamName("operator") -> SelectedListValue(operator), + ParamName("comparisonValue") -> + UnionValueSelected(if (mode == "Fact") "otherFact" else "input") + ) + + case _ => + } + + val newCondition = Condition(ConditionType(newConditionType), params) + val newConditions = newCondition :: rule.conditions + val newRule = rule + .copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, newConditions, rule.actions) + val newRules = node.rules.map { i => if (i == rule) newRule else i } + + val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) + + return Option(newNode) + } + }) + }) + } else { + node.content.rulesets.foreach(ruleSet => { + ruleSet.rules.filter(_.id == RuleId(ruleId)).foreach(rule => { + val definitions = ConditionDefinitions() + definitions.foreach(definition => { - if (definition.conditionType == new ConditionType(newConditionType)) { + if (definition.conditionType == ConditionType(newConditionType)) { var params: Map[ParamName, ParamValue] = Map() newConditionType match { case "NodeCondition" => params = Map( - new ParamName("node") -> new ParamValue.Node(new NodeId(targetId)), - new ParamName("status") -> new SelectedListValue(status.asInstanceOf[BigInt].toInt match { case 0 => "not visited"; case 1 => "visited"; case 2 => "is previous"; case 3 => "is not previous"; case 4 => "current" }) + ParamName("node") -> ParamValue.Node(NodeId(targetId)), + ParamName("status") -> SelectedListValue(status.asInstanceOf[BigInt].toInt match { + case 0 => "not visited"; + case 1 => "visited"; + case 2 => "is previous"; + case 3 => "is not previous"; + case 4 => "current" + }) ) case "LinkCondition" => params = Map( - new ParamName("link") -> new ParamValue.Link(new RulesetId(targetId)), - new ParamName("status") -> new SelectedListValue(if (status.asInstanceOf[BigInt] == BigInt.apply(1)) "followed" else "not followed") + ParamName("link") -> ParamValue.Link(RulesetId(targetId)), + ParamName("status") -> SelectedListValue( + if (status.asInstanceOf[BigInt] == BigInt(1)) "followed" else "not followed" + ) ) case "BooleanFactValue" => if (status.isInstanceOf[BigInt]) { - status = if (status == BigInt.apply(1)) true else false + status = if (status == BigInt(1)) true else false } + params = Map( - new ParamName("fact") -> new ParamValue.BooleanFact(new FactId(targetId)), - new ParamName("state") -> new SelectedListValue(if (status.asInstanceOf[Boolean]) "true" else "false") + ParamName("fact") -> ParamValue.BooleanFact(FactId(targetId)), + ParamName("state") -> + SelectedListValue(if (status.asInstanceOf[Boolean]) "true" else "false") ) case "IntegerFactComparison" => @@ -546,105 +759,63 @@ object SchemeParser extends JavaTokenParsers { val operator = numFactArguments.next().asInstanceOf[String] val mode = numFactArguments.next().asInstanceOf[String] val value = numFactArguments.next().asInstanceOf[String] - val paramV = if (mode == "Fact") new ParamValue.IntegerFact(new FactId(BigInt.apply(value))) else new ParamValue.IntegerInput(BigInt.apply(value)) + + val paramV = + if (mode == "Fact") + ParamValue.IntegerFact(FactId(BigInt(value))) + else + ParamValue.IntegerInput(BigInt(value)) + params = Map( - new ParamName("fact") -> new ParamValue.IntegerFact(new FactId(targetId)), - new ParamName(if (mode == "Fact") "otherFact" else "input") -> paramV, - new ParamName("operator") -> new SelectedListValue(operator), - new ParamName("comparisonValue") -> new UnionValueSelected(if (mode == "Fact") "otherFact" else "input") + ParamName("fact") -> ParamValue.IntegerFact(FactId(targetId)), + ParamName(if (mode == "Fact") "otherFact" else "input") -> paramV, + ParamName("operator") -> SelectedListValue(operator), + ParamName("comparisonValue") -> + UnionValueSelected(if (mode == "Fact") "otherFact" else "input") ) case _ => } - val newCondition = new Condition(new ConditionType(newConditionType), params) - val newConditions = newCondition :: rule.conditions - val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, newConditions, rule.actions) - val newRules = node.rules.map { i => if (i == rule) newRule else i } - - val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) + val newCondition = Condition(ConditionType(newConditionType), params) + val newConditions: List[Condition] = newCondition :: rule.conditions + val newRule = rule.copy( + rule.id, + rule.name, + rule.stopIfTrue, + rule.conditionsOp, + newConditions, + rule.actions + ) + val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } + val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) + val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) + val newContent = node.content.copy(node.content.text, newFullSet) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - nodes.remove(node.id) - nodes.put(newNode.id, newNode) - latestNode = newNode + return Option(newNode) } }) - } - }) - } else { - node.content.rulesets.foreach(ruleSet => { - ruleSet.rules.foreach(rule => { - if (rule.id == new RuleId(ruleId)) { - val definitions = ConditionDefinitions.apply() - - definitions.foreach(definition => { - if (definition.conditionType == new ConditionType(newConditionType)) { - var params: Map[ParamName, ParamValue] = Map() - newConditionType match { - case "NodeCondition" => - params = Map( - new ParamName("node") -> new ParamValue.Node(new NodeId(targetId)), - new ParamName("status") -> new SelectedListValue(status.asInstanceOf[BigInt].toInt match { case 0 => "not visited"; case 1 => "visited"; case 2 => "is previous"; case 3 => "is not previous"; case 4 => "current" }) - ) - - case "LinkCondition" => - params = Map( - new ParamName("link") -> new ParamValue.Link(new RulesetId(targetId)), - new ParamName("status") -> new SelectedListValue(if (status.asInstanceOf[BigInt] == BigInt.apply(1)) "followed" else "not followed") - ) - - case "BooleanFactValue" => - - if (status.isInstanceOf[BigInt]) { - status = if (status == BigInt.apply(1)) true else false - } - - params = Map( - new ParamName("fact") -> new ParamValue.BooleanFact(new FactId(targetId)), - new ParamName("state") -> new SelectedListValue(if (status.asInstanceOf[Boolean]) "true" else "false") - ) - - case "IntegerFactComparison" => - val numFactArguments = numFactArgs.asInstanceOf[List[Any]].iterator - //val listString = - numFactArguments.next().asInstanceOf[String] - val operator = numFactArguments.next().asInstanceOf[String] - val mode = numFactArguments.next().asInstanceOf[String] - val value = numFactArguments.next().asInstanceOf[String] - val paramV = if (mode == "Fact") new ParamValue.IntegerFact(new FactId(BigInt.apply(value))) else new ParamValue.IntegerInput(BigInt.apply(value)) - params = Map( - new ParamName("fact") -> new ParamValue.IntegerFact(new FactId(targetId)), - new ParamName(if (mode == "Fact") "otherFact" else "input") -> paramV, - new ParamName("operator") -> new SelectedListValue(operator), - new ParamName("comparisonValue") -> new UnionValueSelected(if (mode == "Fact") "otherFact" else "input") - ) - - case _ => - } - - val newCondition = new Condition(new ConditionType(newConditionType), params) - val newConditions: List[Condition] = newCondition :: rule.conditions - val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, newConditions, rule.actions) - val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } - - val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) - val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) - val newContent = node.content.copy(node.content.text, newFullSet) - val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - - nodes.remove(node.id) - nodes.put(newNode.id, newNode) - latestNode = newNode - } - }) - } }) }) } } + + Option.empty[Node] } - def processSubSection(iterator: Iterator[Any]): Unit = { + /** + * Processes the second level of tokens + * + * @param iterator The token iterator + * @param currentNode The latest node to be processed + * @param isWithinNode Flag indicating if the action should occur at the Node level or Fragment level + */ + private def processSecondLevel(iterator: Iterator[Any], + currentNode: Option[Node], + isWithinNode: Boolean): Option[Node] = { + var newCurrentNode = currentNode + while (iterator.hasNext) { val current = iterator.next() current match { @@ -655,17 +826,29 @@ object SchemeParser extends JavaTokenParsers { case str: String => val instruction = firstToken.asInstanceOf[String] instruction match { - case "create-typed-rule3" => processCreateTypedRule3(i) - case "create-action" => processCreateAction(i) - case "create-typed-condition2" => processCreateTypedCondition2(i) + case "create-typed-rule3" => + newCurrentNode = processCreateTypedRule3(i, newCurrentNode, isWithinNode) + case "create-action" => + newCurrentNode = processCreateAction(i, newCurrentNode) + case "create-typed-condition2" => + newCurrentNode = processCreateTypedCondition2(i, newCurrentNode, isWithinNode) case _ => } } } } + + newCurrentNode } - def processCreateLink(i: Iterator[Any]): Unit = { + /** + * Processes the link creation tokens + * + * @param i The token iterator + * @return The updated story containing the node with the created link + */ + private def processCreateLink(i: Iterator[Any], story: Story): Option[Node] = { + val linkName = i.next().asInstanceOf[String] val fromNodeID = i.next().asInstanceOf[BigInt] //val toNodeID = @@ -686,21 +869,31 @@ object SchemeParser extends JavaTokenParsers { i.next().asInstanceOf[Boolean] val linkId = i.next().asInstanceOf[BigInt] - val node = nodes.get(new NodeId(fromNodeID)).get + story.nodes.filter(_.id == NodeId(fromNodeID)).foreach(node => { + + val newSet = Ruleset( + RulesetId(linkId), + linkName, + RulesetIndexes(TextIndex(startIndex), TextIndex(endIndex)), + List() + ) - if (node != null) { - val newSet = new Ruleset(new RulesetId(linkId), linkName, new RulesetIndexes(new TextIndex(startIndex), new TextIndex(endIndex)), List()) val newContent = node.content.copy(node.content.text, node.content.rulesets.:+(newSet)) val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - nodes.remove(node.id) - nodes.put(newNode.id, newNode) + return Option(newNode) + }) - latestNode = newNode - } + Option.empty[Node] } - def processCreateNode(i: Iterator[Any]): Unit = { + /** + * Processes node creation tokens + * + * @param i The token iterator + * @return The created node + */ + private def processCreateNode(i: Iterator[Any]): Node = { val nameValue = i.next().asInstanceOf[String] val contentText = i.next().asInstanceOf[String] var next = i.next() @@ -720,36 +913,67 @@ object SchemeParser extends JavaTokenParsers { val nodeId = i.next().asInstanceOf[BigInt] val ruleSet: List[Rule] = List() + val isStartNode = false + + val node = Node( + NodeId(nodeId), + nameValue, + NodeContent(contentText, List()), + isStartNode, + ruleSet + ) - val node = new Node(new NodeId(nodeId), nameValue, new NodeContent(contentText, List()), if (nodeId == startNode) true else false, ruleSet) - - nodes.put(node.id, node) - nodesX.put(node.id, x) - nodesY.put(node.id, y) + val newPosMap = AstMap("id" -> AstInteger(node.id.value), "x" -> AstFloat(x), "y" -> AstFloat(y)) + val newElems = nodePositions.toList.::(newPosMap) + nodePositions = AstList(newElems: _*) - latestNode = node + node } - def processCreateFact(i: Iterator[Any]) = { + /** + * Process the tokens for create-fact action + * + * @param i The token iterator + * @param story The existing story to process the tokens into + * @return The updated story + */ + private def processCreateFact(i: Iterator[Any], story: Story): Story = { + + var newStory = story + while (i.hasNext) { val factName = i.next().asInstanceOf[String] val factType = i.next().asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] - val factId = new FactId(i.next().asInstanceOf[BigInt]) + val factId = FactId(i.next().asInstanceOf[BigInt]) - var fact: Fact = null + var fact = Option.empty[Fact] factType match { - case "boolean" => val boolValue = false; fact = BooleanFact.apply(factId, factName, boolValue) - case "string" => fact = StringFact.apply(factId, factName, "") - case "number" => fact = IntegerFact.apply(factId, factName, 0) + case "boolean" => val boolValue = false; fact = Option(BooleanFact(factId, factName, boolValue)) + case "string" => fact = Option(StringFact(factId, factName, "")) + case "number" => fact = Option(IntegerFact(factId, factName, 0)) } - story = story.addFact(fact) + newStory = newStory.addFact(fact.get) } + + newStory } - def processSection(iterator: Iterator[Any]): Unit = { + /** + * Processes the first level of tokens + * + * @param iterator The token iterator + * @param story The existing story to process the tokens into + * @return The updated story + */ + private def processFirstLevel(iterator: Iterator[Any], story: Story): Story = { + + var newStory = story + var startNode = BigInt(-1) + val headers = new scala.collection.mutable.Queue[List[Any]] + while (iterator.hasNext) { val current = iterator.next() current match { @@ -759,16 +983,34 @@ object SchemeParser extends JavaTokenParsers { firstToken match { case instruction: String => instruction match { - case "make-hypertext" => processHeader(i) - case "set-story-title!" => story = story.copy(i.next().asInstanceOf[String]) - case "set-author-name!" => story = story.changeAuthor(i.next().asInstanceOf[String]) - case "set-story-comment!" => story = story.updateMetadata(story.metadata.copy(i.next().asInstanceOf[String])) - case "set-disable-back-button!" => story = story.updateMetadata(story.metadata.copy(story.metadata.comments, story.metadata.readerStyle, i.next().asInstanceOf[Boolean])) - case "set-disable-restart-button!" => story = story.updateMetadata(story.metadata.copy(story.metadata.comments, story.metadata.readerStyle, story.metadata.isBackButtonDisabled, i.next().asInstanceOf[Boolean])) + case "make-hypertext" => newStory = processHeader(i, newStory) + case "set-story-title!" => newStory = newStory.copy(i.next().asInstanceOf[String]) + case "set-author-name!" => newStory = newStory.changeAuthor(i.next().asInstanceOf[String]) + case "set-story-comment!" => + newStory = newStory.updateMetadata(newStory.metadata.copy(i.next().asInstanceOf[String])) + case "set-disable-back-button!" => + newStory = + newStory.updateMetadata( + newStory.metadata.copy( + newStory.metadata.comments, + newStory.metadata.readerStyle, + i.next().asInstanceOf[Boolean] + ) + ) + case "set-disable-restart-button!" => + newStory = + newStory.updateMetadata( + newStory.metadata.copy( + newStory.metadata.comments, + newStory.metadata.readerStyle, + newStory.metadata.isBackButtonDisabled, + i.next().asInstanceOf[Boolean] + ) + ) case "set-start-node!" => startNode = i.next().asInstanceOf[BigInt] - case "create-fact" => processCreateFact(i) + case "create-fact" => newStory = processCreateFact(i, newStory) case "begin" => headers.enqueue(current.asInstanceOf[List[Any]]) - case _ => //Console.println("Base: " + instruction) + case _ => // Console.println("Base: " + instruction) } } } @@ -776,7 +1018,16 @@ object SchemeParser extends JavaTokenParsers { while (headers.nonEmpty) { val i = headers.dequeue().iterator - processHeader(i) + newStory = processHeader(i, newStory) } + + newStory.nodes.filter(_.id == NodeId(startNode)).foreach(node => { + val isStartNode = true + val newNode = node.copy(node.id, node.name, node.content, isStartNode, node.rules) + newStory = newStory.removeNode(node) + newStory = newStory.addNode(newNode) + }) + + newStory } -} \ No newline at end of file +} diff --git a/hypedyn-default-story-viewer/src/main/scala/org/narrativeandplay/hypedyn/storyviewer/StoryViewer.scala b/hypedyn-default-story-viewer/src/main/scala/org/narrativeandplay/hypedyn/storyviewer/StoryViewer.scala index 01ff003..dff70d1 100644 --- a/hypedyn-default-story-viewer/src/main/scala/org/narrativeandplay/hypedyn/storyviewer/StoryViewer.scala +++ b/hypedyn-default-story-viewer/src/main/scala/org/narrativeandplay/hypedyn/storyviewer/StoryViewer.scala @@ -92,16 +92,6 @@ class StoryViewer extends ScrollPane with Plugin with NarrativeViewer with Savea nodeLocations get createdNode.id foreach (moveNode(createdNode.id, _)) } - /** - * Defines what to do when a node is to be moved - * - * @param node The created node - */ - override def onMoveNode(node: Nodal, x:Double, y:Double): Unit = { - moveNode(node.id, new Vector2(x.toDouble, y.toDouble)) - sizeToChildren() - } - /** * Defines what to do when a node is updated * diff --git a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/Main.scala b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/Main.scala index d7d900e..47c2d8d 100644 --- a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/Main.scala +++ b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/Main.scala @@ -1,16 +1,6 @@ package org.narrativeandplay.hypedyn import java.io.File -import javafx.beans.value.ObservableValue - -import scalafx.Includes._ -import scalafx.application.{Platform, JFXApp} -import scalafx.application.JFXApp.PrimaryStage -import scalafx.beans.property.StringProperty -import scalafx.scene.Scene -import scalafx.scene.control.{ButtonType, Alert} -import scalafx.scene.image.{ImageView, Image} -import scalafx.scene.layout.{VBox, BorderPane} import com.sun.glass.ui import com.sun.glass.ui.Application.EventHandler @@ -19,8 +9,6 @@ import org.narrativeandplay.hypedyn.dialogs._ import org.narrativeandplay.hypedyn.events._ import org.narrativeandplay.hypedyn.logging.Logger import org.narrativeandplay.hypedyn.plugins.PluginsController -import org.narrativeandplay.hypedyn.serialisation.serialisers.DeserialisationException -import org.narrativeandplay.hypedyn.story.{Narrative, Nodal} import org.narrativeandplay.hypedyn.story.rules.{ActionDefinition, ConditionDefinition, Fact} import org.narrativeandplay.hypedyn.story.{Narrative, Nodal} import org.narrativeandplay.hypedyn.uicomponents._ @@ -71,15 +59,10 @@ object Main extends JFXApp { def refreshRecent = refreshStream /** - * Returns a new file dialog - */ + * Returns a new file dialog + */ def fileDialog = new FileDialog(stage) - /** - * Returns a new legacy file dialog - */ - def legacyFileDialog = new LegacyFileDialog(stage) - /** * Returns a new directory selection dialog */ diff --git a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/dialogs/LegacyFileDialog.scala b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/dialogs/LegacyFileDialog.scala deleted file mode 100644 index 6111c55..0000000 --- a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/dialogs/LegacyFileDialog.scala +++ /dev/null @@ -1,23 +0,0 @@ -package org.narrativeandplay.hypedyn.dialogs - -import java.io.File - -import scalafx.Includes._ -import scalafx.stage.FileChooser.ExtensionFilter -import scalafx.stage.{FileChooser, Window} - -/** - * A wrapper over the ScalaFX file chooser, for correcting the ScalaFX API - * - * @param ownerWindow The parent dialog of the file chooser, for inheriting icons - */ -class LegacyFileDialog(ownerWindow: Window) extends FileChooser { - extensionFilters += new ExtensionFilter("HypeDyn 1 Story", "*.dyn") - - /** - * Shows a new open file dialog - * - * @return An option containing the selected file, or None if no file was selected - */ - def showOpenFileDialog(): Option[File] = Option(super.showOpenDialog(ownerWindow)) -} diff --git a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/events/UiEventDispatcher.scala b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/events/UiEventDispatcher.scala index 71df95f..58497cb 100644 --- a/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/events/UiEventDispatcher.scala +++ b/hypedyn-ui/src/main/scala/org/narrativeandplay/hypedyn/events/UiEventDispatcher.scala @@ -1,6 +1,7 @@ package org.narrativeandplay.hypedyn.events import java.io.File +import javafx.stage.FileChooser import org.narrativeandplay.hypedyn.Main import org.narrativeandplay.hypedyn.dialogs.NodeEditor @@ -95,7 +96,11 @@ object UiEventDispatcher { } EventBus.ImportResponses foreach { evt => - val fileToImport = Main.legacyFileDialog.showOpenFileDialog() + val dialog = Main.fileDialog + dialog.setTitle("Import") + dialog.getExtensionFilters.removeAll(dialog.getExtensionFilters) + dialog.getExtensionFilters.add(new FileChooser.ExtensionFilter("HypeDyn 1 Story", "*.dyn")) + val fileToImport = dialog.showOpenFileDialog() fileToImport foreach { f => EventBus.send(ImportFromFile(f, UiEventSourceIdentity)) From 2ead062174dff3d2eeb234d4fabc00d5d6f51f01 Mon Sep 17 00:00:00 2001 From: DennisAng Date: Sun, 24 Apr 2016 22:40:07 +0800 Subject: [PATCH 3/3] Address functional programming issues Removed all instances of vars Replaced iterators with map, filter and pattern matching Restructured parser combinators to use value class Removed all unneccessary type annotations --- .../hypedyn/utils/parsing/SchemeParser.scala | 1415 +++++++---------- 1 file changed, 578 insertions(+), 837 deletions(-) diff --git a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala index 8967813..95cf578 100644 --- a/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala +++ b/hypedyn-core/src/main/scala/org/narrativeandplay/hypedyn/utils/parsing/SchemeParser.scala @@ -1,289 +1,424 @@ package org.narrativeandplay.hypedyn.utils.parsing import fastparse.all._ +import fastparse.core.Parsed import org.narrativeandplay.hypedyn.serialisation._ import org.narrativeandplay.hypedyn.story.NodalContent.{RulesetId, RulesetIndexes, TextIndex} import org.narrativeandplay.hypedyn.story._ import org.narrativeandplay.hypedyn.story.internal.NodeContent.Ruleset +import org.narrativeandplay.hypedyn.story.internal.Story.Metadata import org.narrativeandplay.hypedyn.story.internal.{Node, NodeContent, Story} import org.narrativeandplay.hypedyn.story.rules.Actionable.ActionType import org.narrativeandplay.hypedyn.story.rules.Conditional.ConditionType -import org.narrativeandplay.hypedyn.story.rules.RuleLike.ParamValue.{SelectedListValue, StringInput, UnionValueSelected} +import org.narrativeandplay.hypedyn.story.rules.RuleLike.ParamValue.{SelectedListValue, UnionValueSelected} import org.narrativeandplay.hypedyn.story.rules.RuleLike.{ParamName, ParamValue} import org.narrativeandplay.hypedyn.story.rules.internal.{Action, _} import org.narrativeandplay.hypedyn.story.rules.{BooleanOperator, _} +import org.narrativeandplay.hypedyn.utils.parsing.SchemeParser.Scheme._ +/** + * Parser for Hypedyn 1 files + * + * Parses Hypedyn 1 files into the new Hypedyn 2 format + */ object SchemeParser { - - /** - * Keeps track of node positions - */ - private var nodePositions = AstList() - /** * Parses a string into a Story * * @param s The input string to parse * @return The parsed story */ - def parse(s: String): Map[String, Any] = { + def parse(s: String): Map[String, Object] = { + val Whitespace = NamedFunction(" \r\n\t".contains(_: Char), "Whitespace") + val Digits = NamedFunction('0' to '9' contains (_: Char), "Digits") + val StringLiteralChars = NamedFunction(!"\"\\".contains(_: Char), "StringLiteralChars") + val StringChars = NamedFunction(!" \r\n\t)".contains(_: Char), "StringChars") + + val space = P(CharsWhile(Whitespace).?) + val digits = P(CharsWhile(Digits)) + val fractional = P("." ~ digits) + val integral = P("0" | CharIn('1' to '9') ~ digits.?) + + val number = P(CharIn("+-").? ~ integral).!.map(x => Num(BigInt(x))) + val double = P(integral ~ fractional).!.map(x => Doub(x.toDouble)) + val trueValue = P("#t" | "true") map (v => Bool(true)) + val falseValue = P("#f" | "false") map (v => Bool(false)) + + val escape = P("\\" ~ CharIn("\"/\\bfnrt").!) + + val strLiteralChars = P(CharsWhile(StringLiteralChars)) + val strChars = P(CharsWhile(StringChars)) + val string = P(space ~ strChars.!).map(Str) + val stringLiteral = P(space ~ "\"" ~ (strLiteralChars | escape).rep.?.! ~ "\"") + .map(s => Str(StringContext treatEscapes s)) + lazy val quotedVal = P("(quote" ~ expression ~ ")") + + lazy val obj = P("(" ~ string ~ expression.rep ~ ")").map(Block) + lazy val expression: Parser[Val] = P( + space ~ (quotedVal | obj | trueValue | falseValue | double | number | stringLiteral | string) ~ space + ) - val spaceValue = P(CharsWhile(" \r\n".contains(_: Char)).?) - val digitValue = P("-".? ~ CharsWhile('0' to '9' contains (_: Char)).!).map(x => if (x.nonEmpty) BigInt(x.toString)) - val doubleValue = P((digitValue ~ "." ~ digitValue).!).map(x => x.toString.toDouble) + val mainBlock = P("(begin" ~ (space ~ obj).rep ~ space ~ ")") - val trueValue = P(P("#t") | P("true")).map(_ => true) - val falseValue = P(P("#f") | P("false")).map(_ => false) - val booleanValue = trueValue | falseValue + val (nodePositions, story) = mainBlock.parse(s) match { + case f: Parsed.Failure => (AstList(), Story()) + case s: Parsed.Success[Seq[Block]] => processBlocks(s.value) + } - val escape = P(P("\\") ~ CharIn("\"/\\bfnrt").!) + val mapFields: AstMap = AstMap("zoomLevel" -> AstFloat(1.0), "nodes" -> nodePositions) + val pluginData: AstElement = AstMap("Default Story Viewer" -> mapFields) - val stringCharsValue = P(CharsWhile(!"\"\\".contains(_: Char)).!) - val identifier = P(CharsWhile(!"\" )".contains(_: Char)).!) + Map("story" -> story, "plugins" -> pluginData) + } - val stringLiteralValue = P(spaceValue ~ - "\"" ~ (stringCharsValue | escape).rep.! ~ "\"").map(StringContext treatEscapes _.toString) + /** + * Builds node rules given a sequence of blocks + * + * @param story Story to add the built nodes to + * @param blocks Sequence of blocks to read from + * @return Story containing built node rules + */ + private def buildNodeRules(story: Story, blocks: Seq[Seq[(Str, Seq[Block])]]) = { + val relevantBlocks = blocks.filter(_.head._1.as[String] == "create-node") + + val isNodeRule = true + relevantBlocks.foldLeft(story)((r, params) => { + val nodeParams = params.head._2 + val nodeId = nodeParams(6).as[BigInt] + buildRules(r, params.filter(_._1.as[String] == "begin"), nodeId, isNodeRule) + }) + } - lazy val listValue = P("(" ~ expressionValue.rep.? ~ ")").map(x => if (x.nonEmpty) x.get.toList) - lazy val expressionValue: P[Any] = P(spaceValue ~ - P(listValue | booleanValue | doubleValue | digitValue | identifier | stringLiteralValue) ~ spaceValue) + /** + * Build rules given a sequence of blocks + * + * @param story Story to add the built rules to + * @param blocks Sequence of blocks to read from + * @param nodeId The identifier of the node which the rules belong under + * @param isNodeRule Flag that indicates if the rule is directly under the node (true) or under the node's content + * @return Story containing built node rules + */ + private def buildRules(story: Story, blocks: Seq[(Str, Seq[Block])], nodeId: BigInt, isNodeRule: Boolean): Story = { + val relevantBlocks = blocks.filter(_._1.as[String] == "begin").map(_._2) + relevantBlocks.foldLeft(story)((embR, embC) => + embC.foldLeft(embR)((embR2, embC2) => { + val block = embC2.as[(Str, Seq[Val])] + val linkParams = block._2 + block._1.as[String] match { + case "create-typed-rule3" => createTypedRule3(nodeId, linkParams, embR2, isNodeRule) + case "create-action" => createAction(nodeId, linkParams, embR2, isNodeRule) + case "create-typed-condition2" => createTypedCondition2(nodeId, linkParams, embR2, isNodeRule) + case _ => embR2 + } + }) + ) + } - val list: List[Any] = expressionValue.parse(s).get.value.asInstanceOf[List[Any]] + /** + * Build links given a seqeuence of blocks + * + * @param story Story to add the built rules to + * @param blocks Sequence of blocks to read from + * @return Story containing built links + */ + private def buildLinks(story: Story, blocks: Seq[Seq[(Str, Seq[Block])]]): Story = { + val linkOptions = blocks.filter(_.head._1.as[String] == "create-link") + linkOptions.foldLeft(story)((r, c) => { + val linkParams = c.head._2 + val name = linkParams.head.as[String] + val nodeId = linkParams(1).as[BigInt] + val startIndex = linkParams(3).as[BigInt] + val endIndex = linkParams(4).as[BigInt] + val linkId = linkParams(11).as[BigInt] + + r.nodes.find(_.id == NodeId(nodeId)) match { + case None => r + case Some(node) => + val newSet = Ruleset( + RulesetId(linkId), + name, + RulesetIndexes(TextIndex(startIndex), TextIndex(endIndex)), + List.empty + ) + + val newContent = node.content.copy(node.content.text, node.content.rulesets.:+(newSet)) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - val story = process(list, Story()) + val newR = r.removeNode(node).addNode(newNode) - val mapFields: AstMap = AstMap("zoomLevel" -> AstFloat(1.0), "nodes" -> nodePositions) - val pluginData: AstElement = AstMap("Default Story Viewer" -> mapFields) + val isNodeRule = false + buildRules(newR, c, nodeId, isNodeRule) + } + }) + } + + /** + * Produces a story given a sequence of blocks + * + * @param blocks Sequence of blocks to read from + * @return Story produced from the sequence of blocks provided + */ + private def processBlocks(blocks: Seq[Block]): (AstList, Story) = { + val comments = getParam("set-story-comment!", blocks).getOrElse(Str()).as[String] + val isBackButtonDisabled = getParam("set-disable-back-button!", blocks).getOrElse(Bool()).as[Boolean] + val isRestartButtonDisabled = getParam("set-disable-restart-button!", blocks).getOrElse(Bool()).as[Boolean] + + val metaData = Metadata(comments, Narrative.ReaderStyle.Standard, isBackButtonDisabled, isRestartButtonDisabled) - val map = Map("story" -> story, "plugins" -> pluginData) + val setAuthorName = getParam("set-author-name!", blocks).getOrElse(Str()).as[String] + val setStoryTitle = getParam("set-story-title!", blocks).getOrElse(Str()).as[String] + val startNodeId = getParam("set-start-node!", blocks).map(x => x.as[BigInt]) - map + val storyMeta = Story(setStoryTitle.toString).changeAuthor(setAuthorName).updateMetadata(metaData) + val storyFact = buildFacts(storyMeta, blocks) + val relevantBlocks = blocks.filter(_.value._1.value == "begin").map(_.value._2.map(_.as[(Str, Seq[Block])])) + val (nodePositions, storyNodes) = buildNodes(storyFact, relevantBlocks, startNodeId) + val storyNodeRules = buildNodeRules(storyNodes, relevantBlocks) + + (nodePositions, buildLinks(storyNodeRules, relevantBlocks)) } /** - * Processes a list of tokens into a Story + * Gets parameter given a sequence of blocks * - * @param l The list of tokens to be parsed - * @param story The existing story to parse the tokens into - * @return The updated story + * @param name Name of parameter + * @param blocks Sequence of blocks to read from + * @return Option with parameter value or empty if parameter is not found */ - private def process(l: List[Any], story: Story): Story = { - var newStory = story - - val iterator = l.iterator - while (iterator.hasNext) { - val current = iterator.next() - current match { - case list: List[Any] => newStory = process(current.asInstanceOf[List[Any]], newStory) - case currentString: String => - currentString match { - case "begin" => newStory = processFirstLevel(iterator, newStory) - case _ => - } - } - } - - newStory + private def getParam(name: String, blocks: Seq[Block]): Option[Val] = { + blocks find (param => param.value._1.value == name) map (_.value._2.head) } /** - * Processes the header tokens + * Builds nodes given sequence of blocks * - * @param iterator The token iterator - * @param story The story to be updated - * @return The updated story + * @param story Story to add the built nodes to + * @param blocks Sequence of blocks to read from + * @param startNodeId Option containing the start node ID if any + * @return Story containing built nodes */ - private def processHeader(iterator: Iterator[Any], story: Story): Story = { - var newStory = story - var currentNode = Option.empty[Node] - var isWithinNode = true - - while (iterator.hasNext) { - val current = iterator.next() - current match { - case list: List[Any] => - val i = current.asInstanceOf[List[Any]].iterator - val firstToken = i.next() - firstToken match { - case instruction: String => - instruction match { - case "create-node" => - val nodeOption = Option(processCreateNode(i)) - if (nodeOption.nonEmpty) newStory = newStory.addNode(nodeOption.get) - currentNode = nodeOption - isWithinNode = true - case "create-link" => - val nodeOption = processCreateLink(i, newStory) - - if (nodeOption.nonEmpty) { - newStory.nodes.filter(_.id == nodeOption.get.id).foreach(node => { - newStory = newStory.removeNode(node) - }) - newStory = newStory.addNode(nodeOption.get) - } - currentNode = nodeOption - isWithinNode = false - case "begin" => - val nodeOption = processSecondLevel(i, currentNode, isWithinNode) - if (nodeOption.nonEmpty) { - newStory.nodes.filter(_.id == nodeOption.get.id).foreach(node => { - newStory = newStory.removeNode(node) - }) - newStory = newStory.addNode(nodeOption.get) - } - currentNode = nodeOption - case _ => //Console.println("Header: " + instruction) - } - } - case _ => + private def buildNodes(story: Story, blocks: Seq[Seq[(Str, Seq[Block])]], + startNodeId: Option[BigInt]): (AstList, Story) = { + val relevantBlocks = blocks.filter(_.head._1.as[String] == "create-node").map(_.head._2) + + relevantBlocks.foldLeft((AstList(), story))((r, params) => { + val name = params.head.as[String] + val content = params(1).as[String] + val x = params(2).as[Double] + val y = params(3).as[Double] + val nodeId = params(6).as[BigInt] + + val ruleSet: List[Rule] = List.empty + val isStartNode = startNodeId match { + case Some(v: BigInt) => v == nodeId + case _ => false } - } - newStory + val node = Node( + NodeId(nodeId), + name, + NodeContent(content, List.empty), + isStartNode, + ruleSet + ) + + val newPosMap = AstMap("id" -> AstInteger(node.id.value), "x" -> AstFloat(x), "y" -> AstFloat(y)) + val newElems = r._1.toList.::(newPosMap) + val nodePositions = AstList(newElems: _*) + + (nodePositions, r._2.addNode(node)) + }) } /** - * Process the tokens for create-typed-rule-3 rule + * Creates a rule * - * @param i The token iterator - * @param currentNode The latest node processed - * @param isWithinNode Flag that determines if the rule resides at the Node or Fragment level - * @return The modified node + * @param nodeId The identifier of the node which the rule belongs under + * @param params Rule parameters + * @param story Story to add the rule to + * @param isNodeRule Flag that indicates if the rule is directly under the node (true) or under the node's content + * @return Story containing built rule */ - private def processCreateTypedRule3(i: Iterator[Any], - currentNode: Option[Node], - isWithinNode: Boolean): Option[Node] = { - - val ruleName = i.next().asInstanceOf[String] - //val ruleType = - i.next().asInstanceOf[List[Any]].lift(2) - val stringOperator: String = i.next().asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] + private def createTypedRule3(nodeId: BigInt, params: Seq[Val], story: Story, isNodeRule: Boolean): Story = { + val ruleName = params.head.as[String] + val stringOperator = params(2).as[String] val boolOperator = if (stringOperator == "or") BooleanOperator.Or else BooleanOperator.And - //val negate = - i.next().asInstanceOf[Boolean] - val linkId = i.next().asInstanceOf[BigInt] - //val stringFixedId = - i.next().asInstanceOf[String] - val fixedId = i.next().asInstanceOf[BigInt] - //val stringFallthrough = - i.next().asInstanceOf[String] - val fallThrough = i.next().asInstanceOf[Boolean] - - val rule = Rule(RuleId(fixedId), ruleName, !fallThrough, boolOperator, List(), List()) - if (currentNode.nonEmpty) { - val node = currentNode.get - if (isWithinNode) { - - val newRules: List[Rule] = node.rules.:+(rule) - val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) - - return Option(newNode) - } else { - node.content.rulesets.filter(_.id == RulesetId(linkId)).foreach(ruleSet => { - - val newRules: List[Rule] = ruleSet.rules.:+(rule) - - val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) - val newFullSet: List[Ruleset] = node.content.rulesets.map(x => if (x == ruleSet) newSet else x) - val newContent = node.content.copy(node.content.text, newFullSet) - val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) + val linkId = params(4).as[BigInt] + val fixedId = params(6).as[BigInt] + val fallThrough = params(8).as[Boolean] + + val rule = Rule(RuleId(fixedId), ruleName, !fallThrough, boolOperator, List.empty, List.empty) + + story.nodes.find(_.id == NodeId(nodeId)) match { + case Some(node) => + if (isNodeRule) { + val newRules: List[Rule] = node.rules.:+(rule) + val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) + story.removeNode(node).addNode(newNode) + } else { + node.content.rulesets.find(_.id == RulesetId(linkId)) match { + case Some(ruleSet) => + val newRules: List[Rule] = ruleSet.rules.:+(rule) - return Option(newNode) - }) - } + val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) + val newFullSet: List[Ruleset] = node.content.rulesets.map(x => if (x == ruleSet) newSet else x) + val newContent = node.content.copy(node.content.text, newFullSet) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) + story.removeNode(node).addNode(newNode) + + case None => story + } + } + case None => story } + } + + /** + * Creates an rule action + * + * @param nodeId The identifier of the node which the rule action belongs under + * @param params Rule action parameters + * @param story Story to add the rule action to + * @param isNodeRule Flag indicates if the rule action is directly under the node (true) or under the node's content + * @return Story containing built rule action + */ + private def createAction(nodeId: BigInt, params: Seq[Val], story: Story, isNodeRule: Boolean): Story = { + val actionType = params(1).as[String] + val expression = params(2).as[(Str, Seq[Val])]._2 + val parentRuleId = params(3).as[BigInt] + + story.nodes.find(_.id == NodeId(nodeId)) match { + case Some(node) => + val actionOption = actionType match { + case "clicked-link" => + expression.head.as[String] match { + case "follow-link" => + processFollowLink(expression, parentRuleId, node) + case "assert" => + val boolVal = true + Option(processActionSetFact(expression, parentRuleId, boolVal)) + case "retract" => + val boolVal = false + Option(processActionSetFact(expression, parentRuleId, boolVal)) + case "set-number-fact" => Option(processSetNumberFact(expression, parentRuleId)) + case "anywhere-check" => Option(processAnywhereCheck(expression, parentRuleId)) + case "set-value!" => Option(processSetValue(expression, parentRuleId)) + case "show-in-popup" => Option(processShowInPopup(expression, parentRuleId)) + case _ => Option.empty[Action] + } + case "entered-node" => + expression.head.as[String] match { + case "retract" => + val boolValue = false + Option(processActionSetFact(expression, parentRuleId, boolValue)) + case "assert" => + val boolValue = true + Option(processActionSetFact(expression, parentRuleId, boolValue)) + case "set-number-fact" => Option(processSetNumberFact(expression, parentRuleId)) + case "anywhere-check" => Option(processAnywhereCheck(expression, parentRuleId)) + case "set-value!" => Option(processSetValue(expression, parentRuleId)) + case _ => Option.empty[Action] + } + case "anywhere-check" => + Option(processAnywhereCheck(expression, parentRuleId)) + case "displayed-node" => + Option(processDisplayedNode(expression, parentRuleId)) + } + + actionOption match { + case Some(action: Action) => + if (isNodeRule) { + node.rules.find(_.id == RuleId(parentRuleId)) match { + case Some(rule) => + val newActions = rule.actions.:+(action) + val newRule = rule.copy( + rule.id, + rule.name, + rule.stopIfTrue, + rule.conditionsOp, + rule.conditions, + newActions + ) + + val newRules: List[Rule] = node.rules.map { i => if (i.id == rule.id) newRule else i } - Option.empty[Node] + val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) + story.removeNode(node).addNode(newNode) + case None => story + } + } else { + val newRuleSets = node.content.rulesets map { + ruleSet => + ruleSet.rules.find(_.id == RuleId(parentRuleId)) match { + case Some(rule) => + val newActions = rule.actions.:+(action) + val newRule = rule.copy( + rule.id, + rule.name, + rule.stopIfTrue, + rule.conditionsOp, + rule.conditions, + newActions + ) + ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, + ruleSet.rules.map(r => if (r.id == rule.id) newRule else r)) + case None => ruleSet + } + } + val newNodeContent = node.content.copy(node.content.text, newRuleSets) + val newNode = node.copy(node.id, node.name, newNodeContent, node.isStartNode, node.rules) + story.removeNode(node).addNode(newNode) + } + case _ => story + } + case None => story + } } /** * Process the tokens for the follow-link rule * - * @param expression The combination of tokens that make up an expression + * @param expression The combination of tokens that make up an expression * @param parentRuleId The parent rule identifier - * @param currentNode The latest node processed + * @param currentNode The latest node processed * @return */ - private def processFollowLink(expression: List[Any], - parentRuleId: BigInt, - currentNode: Option[Node]): Option[Node] = { - val newActionType = "LinkTo" - //val linkId = expression.lift(2).get.asInstanceOf[BigInt] - //val ruleId = expression.lift(3).get.asInstanceOf[BigInt] - val toNodeId = expression.lift(5).get.asInstanceOf[BigInt] - - - if (currentNode.nonEmpty) { - val node = currentNode.get - node.content.rulesets.foreach(ruleSet => { - ruleSet.rules.foreach(rule => { - if (rule.id == RuleId(parentRuleId)) { - val definitions = ActionDefinitions() - definitions.foreach(definition => { - - if (definition.actionType == ActionType(newActionType)) { - val params = Map( - ParamName("node") -> ParamValue.Node(NodeId(toNodeId)) - ) - - val newAction = Action(ActionType(newActionType), params) - val newActions: List[Action] = rule.actions.:+(newAction) - val newRule = rule.copy( - rule.id, - rule.name, - rule.stopIfTrue, - rule.conditionsOp, - rule.conditions, - newActions - ) - val newRules: List[Rule] = ruleSet.rules.map { x => if (x == rule) newRule else x } - - val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) - val newFullSet: List[Ruleset] = node.content.rulesets.map(x => if (x == ruleSet) newSet else x) - val newContent = node.content.copy(node.content.text, newFullSet) - val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - - return Option(newNode) - } - }) - } - }) - }) - } - - Option.empty[Node] + private def processFollowLink(expression: Seq[Val], parentRuleId: BigInt, currentNode: Node): Option[Action] = { + Option( + Action( + ActionType("LinkTo"), + Map(ParamName("node") -> ParamValue.Node(NodeId(expression(4).as[BigInt]))) + ) + ) } /** * Processes the tokens for the set-number-fact action * * @param expression The combination of tokens that make up an expression - * @param ruleId The rule identifier + * @param ruleId The rule identifier * @return The created action */ - private def processSetNumberFact(expression: List[Any], ruleId: BigInt): Action = { - val factId = expression.lift(2).get.asInstanceOf[BigInt] - val setOption = expression.lift(3).get.asInstanceOf[String] // Input, Random, Fact, Math + private def processSetNumberFact(expression: Seq[Val], ruleId: BigInt): Action = { + val factId = expression(1).as[BigInt] + val setOption = expression(2).as[String] - var params:Map[ParamName, ParamValue] = Map() - - setOption match { + val params = setOption match { case "Input" => - val setFactId = expression.lift(2).get.asInstanceOf[BigInt] - //val setFactMode = expression.lift(3).get.asInstanceOf[String] - val setFactValue = expression.lift(4).get.asInstanceOf[String] + val setFactId = expression(1).as[BigInt] + val setFactValue = expression(3).as[String] - params = params.+ (ParamName("updateValue") -> ParamValue.UnionValueSelected("inputValue")) - params = params.+ (ParamName("fact") -> ParamValue.IntegerFact(FactId(setFactId))) - params = params.+ (ParamName("inputValue") -> ParamValue.IntegerInput(BigInt(setFactValue))) + Map(ParamName("updateValue") -> ParamValue.UnionValueSelected("inputValue"), + ParamName("fact") -> ParamValue.IntegerFact(FactId(setFactId)), + ParamName("inputValue") -> ParamValue.IntegerInput(BigInt(setFactValue))) case "Math" => - val optionExpression = expression.lift(4).get.asInstanceOf[List[Any]] - val eOperator = optionExpression.lift(1).get.asInstanceOf[String] - val value1 = optionExpression.lift(2).get.asInstanceOf[String] // FactId / Input - val eOption1 = optionExpression.lift(3).get.asInstanceOf[String] // Fact, Input - val value2 = optionExpression.lift(4).get.asInstanceOf[String] // FactId / Input - val eOption2 = optionExpression.lift(5).get.asInstanceOf[String] // Fact, Input + val optionExpression = expression(3).as[(Str, Seq[Val])]._2 + val eOperator = optionExpression.head.as[String] + val value1 = optionExpression(1).as[String] + val eOption1 = optionExpression(2).as[String] + val value2 = optionExpression(3).as[String] + val eOption2 = optionExpression(4).as[String] val operand1IsFact = if (eOption1 == "Fact") true else false val operand2IsFact = if (eOption2 == "Fact") true else false @@ -291,35 +426,36 @@ object SchemeParser { val operand1 = if (operand1IsFact) "factOperand1" else "userOperand1" val operand2 = if (operand2IsFact) "factOperand2" else "userOperand2" - params = params.+ (ParamName("fact") -> ParamValue.IntegerFact(FactId(factId))) - params = params.+ (ParamName("updateValue") -> ParamValue.UnionValueSelected("computation")) - params = params.+ (ParamName("operand1") -> ParamValue.UnionValueSelected(operand1)) - if (operand1IsFact) { - params = params.+ (ParamName(operand1) -> ParamValue.IntegerFact(FactId(BigInt(value1)))) - } else { - params = params.+ (ParamName(operand1) -> ParamValue.IntegerInput(BigInt(value1))) - } - params = params.+ (ParamName("operator") -> ParamValue.SelectedListValue(eOperator)) - params = params.+ (ParamName("operand2") -> ParamValue.UnionValueSelected(operand2)) - if (operand2IsFact) { - params = params.+ (ParamName(operand2) -> ParamValue.IntegerFact(FactId(BigInt(value2)))) - } else { - params = params.+ (ParamName(operand2) -> ParamValue.IntegerInput(BigInt(value2))) - } - params = params.+ (ParamName("computation") -> ParamValue.ProductValue(List("operand1", "operator", "operand2"))) + Map(ParamName("fact") -> ParamValue.IntegerFact(FactId(factId)), + ParamName("updateValue") -> ParamValue.UnionValueSelected("computation"), + ParamName("operand1") -> ParamValue.UnionValueSelected(operand1), + if (operand1IsFact) { + ParamName(operand1) -> ParamValue.IntegerFact(FactId(BigInt(value1))) + } else { + ParamName(operand1) -> ParamValue.IntegerInput(BigInt(value1)) + }, + ParamName("operator") -> ParamValue.SelectedListValue(eOperator), + ParamName("operand2") -> ParamValue.UnionValueSelected(operand2), + + if (operand2IsFact) { + ParamName(operand2) -> ParamValue.IntegerFact(FactId(BigInt(value2))) + } else { + ParamName(operand2) -> ParamValue.IntegerInput(BigInt(value2)) + }, + ParamName("computation") -> ParamValue.ProductValue(List("operand1", "operator", "operand2")) + ) case "Fact" => - val setFromFactId = expression.lift(2).get.asInstanceOf[BigInt] - val setToFactId = expression.lift(4).get.asInstanceOf[BigInt] - params = params.+ (ParamName("updateValue") -> ParamValue.UnionValueSelected("integerFactValue")) - params = params.+ (ParamName("fact") -> ParamValue.IntegerFact(FactId(setFromFactId))) - params = params.+ (ParamName("integerFactValue") -> ParamValue.IntegerFact(FactId(setToFactId))) - + val setFromFactId = expression(1).as[BigInt] + val setToFactId = expression(3).as[BigInt] + Map(ParamName("updateValue") -> ParamValue.UnionValueSelected("integerFactValue"), + ParamName("fact") -> ParamValue.IntegerFact(FactId(setFromFactId)), + ParamName("integerFactValue") -> ParamValue.IntegerFact(FactId(setToFactId))) case "Random" => - val optionExpression = expression.lift(4).get.asInstanceOf[List[Any]] - val value1 = optionExpression.lift(1).get.asInstanceOf[String] // FactId / Input - val eOption1 = optionExpression.lift(2).get.asInstanceOf[String] // Fact, Input - val value2 = optionExpression.lift(3).get.asInstanceOf[String] // FactId / Input - val eOption2 = optionExpression.lift(4).get.asInstanceOf[String] // Fact, Input + val optionExpression = expression(3).as[(Str, Seq[Block])]._2 + val value1 = optionExpression.head.as[String] + val eOption1 = optionExpression(1).as[String] + val value2 = optionExpression(2).as[String] + val eOption2 = optionExpression(3).as[String] val operand1IsFact = if (eOption1 == "Fact") true else false val operand2IsFact = if (eOption2 == "Fact") true else false @@ -327,95 +463,95 @@ object SchemeParser { val operand1 = if (operand1IsFact) "startFact" else "startInput" val operand2 = if (operand2IsFact) "endFact" else "endInput" - params = params.+ (ParamName("randomValue") -> ParamValue.ProductValue(List("start", "end"))) - params = params.+ (ParamName("updateValue") -> ParamValue.UnionValueSelected("randomValue")) - params = params.+ (ParamName("fact") -> ParamValue.IntegerFact(FactId(factId))) - - params = params.+ (ParamName("start") -> ParamValue.UnionValueSelected(operand1)) - if (operand1IsFact) { - params = params.+ (ParamName(operand1) -> ParamValue.IntegerFact(FactId(BigInt(value1)))) - } else { - params = params.+ (ParamName(operand1) -> ParamValue.IntegerInput(BigInt(value1))) - } - - params = params.+ (ParamName("end") -> ParamValue.UnionValueSelected(operand2)) - if (operand2IsFact) { - params = params.+ (ParamName(operand2) -> ParamValue.IntegerFact(FactId(BigInt(value2)))) - } else { - params = params.+ (ParamName(operand2) -> ParamValue.IntegerInput(BigInt(value2))) - } + Map(ParamName("randomValue") -> ParamValue.ProductValue(List("start", "end")), + ParamName("updateValue") -> ParamValue.UnionValueSelected("randomValue"), + ParamName("fact") -> ParamValue.IntegerFact(FactId(factId)), + ParamName("start") -> ParamValue.UnionValueSelected(operand1), + if (operand1IsFact) { + ParamName(operand1) -> ParamValue.IntegerFact(FactId(BigInt(value1))) + } else { + ParamName(operand1) -> ParamValue.IntegerInput(BigInt(value1)) + }, + ParamName("end") -> ParamValue.UnionValueSelected(operand2), + if (operand2IsFact) { + ParamName(operand2) -> ParamValue.IntegerFact(FactId(BigInt(value2))) + } else { + ParamName(operand2) -> ParamValue.IntegerInput(BigInt(value2)) + }) + case _ => Map.empty[ParamName, ParamValue] } - val action = Action(ActionType("UpdateIntegerFacts"), params) - - action + Action(ActionType("UpdateIntegerFacts"), params) } /** * Processes the tokens that set a boolean fact * - * @param expression The combination of tokens that make up an expression + * @param expression The combination of tokens that make up an expression * @param parentRuleId The parent rule identifier - * @param boolValue The value to set the boolean fact to + * @param boolValue The value to set the boolean fact to * @return The created action */ - private def processActionSetFact(expression: List[Any], parentRuleId: BigInt, boolValue: Boolean): Action = { - val factId = expression.lift(2).get.asInstanceOf[BigInt] + private def processActionSetFact(expression: Seq[Val], parentRuleId: BigInt, boolValue: Boolean): Action = { + val factId = expression(1).as[BigInt] val params = Map( ParamName("fact") -> ParamValue.BooleanFact(FactId(factId)), ParamName("value") -> SelectedListValue(if (boolValue) "true" else "false") ) - val action = Action(ActionType("UpdateBooleanFact"), params) - - action + Action(ActionType("UpdateBooleanFact"), params) } /** * Processes the tokens that enables anywhere links to a node * - * @param expression The combination of tokens that make up an expression + * @param expression The combination of tokens that make up an expression * @param parentRuleId The parent rule identifier * @return The created action */ - private def processAnywhereCheck(expression: List[Any], parentRuleId: BigInt): Action = { - //val factId = expression.lift(2).get.asInstanceOf[BigInt] - val action = Action(ActionType("EnableAnywhereLinkToHere"), Map()) - action + private def processAnywhereCheck(expression: Seq[Val], parentRuleId: BigInt): Action = { + Action(ActionType("EnableAnywhereLinkToHere"), Map.empty[ParamName, ParamValue]) } /** - * Processes the tokens for the displayed-node action + * Processes the tokens for the show-in-popup action * - * @param expression The combination of tokens that make up an expression + * @param expression The combination of tokens that make up an expression * @param parentRuleId The parent rule identifier * @return The created action */ - private def processDisplayedNode(expression: List[Any], parentRuleId: BigInt): Action = { - //val instruction = expression.lift(1).get.asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] - - val factType = expression.lift(2).get.asInstanceOf[String] - val value = expression.lift(3).get - //val actionId = expression.lift(4).get.asInstanceOf[BigInt] + private def processShowInPopup(expression: Seq[Val], parentRuleId: BigInt): Action = { + Action(ActionType("ShowPopupNode"), Map(ParamName("node") -> ParamValue.Node(NodeId(expression(1).as[BigInt])))) + } - var params: Map[ParamName, ParamValue] = Map() + /** + * Processes the tokens for the displayed-node action + * + * @param expression The combination of tokens that make up an expression + * @param parentRuleId The parent rule identifier + * @return The created action + */ + private def processDisplayedNode(expression: Seq[Val], parentRuleId: BigInt): Action = { + val factType = expression(1).as[String] + val value = expression(2) - factType match { + val params = factType match { case "alternative text" => - params = Map( - ParamName("textInput") -> StringInput(value.asInstanceOf[String]), + Map( + ParamName("textInput") -> ParamValue.StringInput(value.as[String]), ParamName("text") -> UnionValueSelected("textInput") ) case "text fact" => - params = Map( - ParamName("stringFactValue") -> ParamValue.StringFact(FactId(value.asInstanceOf[BigInt])), + Map( + ParamName("stringFactValue") -> ParamValue.StringFact(FactId(value.as[BigInt])), ParamName("text") -> UnionValueSelected("stringFactValue") ) case "number fact" => - params = Map( - ParamName("NumberFactValue") -> ParamValue.IntegerFact(FactId(value.asInstanceOf[BigInt])), + Map( + ParamName("NumberFactValue") -> ParamValue.IntegerFact(FactId(value.as[BigInt])), ParamName("text") -> UnionValueSelected("NumberFactValue") ) + case _ => Map.empty[ParamName, ParamValue] } Action(ActionType("UpdateText"), params) @@ -424,606 +560,211 @@ object SchemeParser { /** * Processes the tokens for the set-value! action * - * @param expression The combination of tokens that make up an expression + * @param expression The combination of tokens that make up an expression * @param parentRuleId The parent rule identifier * @return The created action */ - private def processSetValue(expression: List[Any], parentRuleId: BigInt): Action = { - val factId = expression.lift(2).get.asInstanceOf[BigInt] - val value = expression.lift(3).get.asInstanceOf[String] + private def processSetValue(expression: Seq[Val], parentRuleId: BigInt): Action = { + val factId = expression(1).as[BigInt] + val value = expression(2).as[String] val params = Map( ParamName("fact") -> ParamValue.StringFact(FactId(factId)), - ParamName("value") -> StringInput(value) + ParamName("value") -> ParamValue.StringInput(value) ) Action(ActionType("UpdateStringFact"), params) } + def matchCondition(newConditionType: String, targetId: BigInt, status: Val, + numFactArgs: Val): Map[ParamName, ParamValue] = { + newConditionType match { + case "NodeCondition" => + Map( + ParamName("node") -> ParamValue.Node(NodeId(targetId)), + ParamName("status") -> SelectedListValue(status.value match { + case 0 => "not visited" + case 1 => "visited" + case 2 => "is previous" + case 3 => "is not previous" + case 4 => "current" + }) + ) + case "LinkCondition" => + Map( + ParamName("link") -> ParamValue.Link(RulesetId(targetId)), + ParamName("status") -> SelectedListValue( + if (status.value == BigInt(1)) "followed" else "not followed" + ) + ) + case "BooleanFactValue" => + val boolStatus = status.value match { + case bigInt: BigInt if bigInt == BigInt(1) => true + case bigInt: BigInt if bigInt == BigInt(0) => false + case boolean: Boolean => boolean + case _ => false + } + Map( + ParamName("fact") -> ParamValue.BooleanFact(FactId(targetId)), + ParamName("state") -> + SelectedListValue(if (boolStatus) "true" else "false") + ) + case "IntegerFactComparison" => + val numFactArguments = numFactArgs.as[(Str, Seq[Block])]._2 + val operator = numFactArguments.head.as[String] + val mode = numFactArguments(1).as[String] + val value = numFactArguments(2).as[String] + + val paramV = + if (mode == "Fact") ParamValue.IntegerFact(FactId(BigInt(value))) else ParamValue.IntegerInput(BigInt(value)) + + Map( + ParamName("fact") -> ParamValue.IntegerFact(FactId(targetId)), + ParamName(if (mode == "Fact") "otherFact" else "input") -> paramV, + ParamName("operator") -> SelectedListValue(operator), + ParamName("comparisonValue") -> + UnionValueSelected(if (mode == "Fact") "otherFact" else "input") + ) + case _ => Map.empty[ParamName, ParamValue] + } + } + /** - * Processes the tokens for the create-action rule + * Process the tokens for create-typed-condition-2 rule * - * @param i The token iterator - * @param currentNode The latest node processed - * @return The modified node + * @param nodeId The identifier of the node which the rules belong under + * @param blocks Sequence of blocks to read from + * @param story Story to add the built rules to + * @param isNodeRule Flag indicates if the rule is directly under the node (true) or under the node's content + * @return */ - private def processCreateAction(i: Iterator[Any], currentNode: Option[Node]): Option[Node] = { - //val actionName = - i.next().asInstanceOf[String] - val actionType = i.next().asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] - val expression = i.next().asInstanceOf[List[Any]] - val parentRuleId = i.next().asInstanceOf[BigInt] - //val actionId = - i.next().asInstanceOf[BigInt] - //var newActionType = actionType - - actionType match { - case "clicked-link" => - val linkActionType = expression.lift(1).get.asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] - - var actionOption = Option.empty[Action] - - linkActionType match { - case "follow-link" => - return processFollowLink(expression, parentRuleId, currentNode) - case "assert" => - val boolValue = true - actionOption = Option(processActionSetFact(expression, parentRuleId, boolValue)) - case "retract" => - val boolValue = false - actionOption = Option(processActionSetFact(expression, parentRuleId, boolValue)) - case "set-number-fact" => actionOption = Option(processSetNumberFact(expression, parentRuleId)) - case "anywhere-check" => actionOption = Option(processAnywhereCheck(expression, parentRuleId)) - case "set-value!" => actionOption = Option(processSetValue(expression, parentRuleId)) - case _ => //Console.println("Link Action: " + linkActionType + " " + expression) - } - - if (actionOption.nonEmpty) { - val action = actionOption.get - - if (currentNode.nonEmpty) { - val node = currentNode.get - node.content.rulesets.foreach(ruleSet => { - ruleSet.rules.filter(_.id == RuleId(parentRuleId)).foreach(rule => { - - val newActions = rule.actions.:+(action) - val newRule = rule.copy( - rule.id, - rule.name, - rule.stopIfTrue, - rule.conditionsOp, - rule.conditions, newActions - ) - val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } - val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) - val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) - val newContent = node.content.copy(node.content.text, newFullSet) - val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - - return Option(newNode) - }) - }) - } - } - case "entered-node" => - val linkActionType = expression.lift(1).get.asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] - var action = Option.empty[Action] - linkActionType match { - case "retract" => - val boolValue = false - action = Option(processActionSetFact(expression, parentRuleId, boolValue)) - case "assert" => - val boolValue = true - action = Option(processActionSetFact(expression, parentRuleId, boolValue)) - case "set-number-fact" => action = Option(processSetNumberFact(expression, parentRuleId)) - case "anywhere-check" => action = Option(processAnywhereCheck(expression, parentRuleId)) - case "set-value!" => action = Option(processSetValue(expression, parentRuleId)) - case _ => //Console.println("Link Action: " + linkActionType + " " + expression) - } + private def createTypedCondition2(nodeId: BigInt, blocks: Seq[Val], story: Story, isNodeRule: Boolean): Story = { + val newConditionType = blocks(1).as[BigInt].toInt match { + case 0 => "NodeCondition" + case 1 => "LinkCondition" + case 2 => "BooleanFactValue" + case 3 => "IntegerFactComparison" + case _ => "" + } - if (action.nonEmpty) { - if (currentNode.nonEmpty) { - val node = currentNode.get - node.rules.filter(_.id == RuleId(parentRuleId)).foreach(rule => { - val newActions = rule.actions.:+(action.get) + val targetId = blocks(2).as[BigInt] + val status = blocks(3) + val ruleId = blocks(4).as[BigInt] + val numFactArgs = blocks(8) + + val params = matchCondition(newConditionType, targetId, status, numFactArgs) + val newCondition = Condition(ConditionType(newConditionType), params) + story.nodes.find(_.id == NodeId(nodeId)) match { + case Some(node) => + if (isNodeRule) { + node.rules.find(_.id == RuleId(ruleId)) match { + case Some(rule) => + val newConditions: List[Condition] = newCondition :: rule.conditions val newRule = rule.copy( rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, - rule.conditions, - newActions + newConditions, + rule.actions ) - val newRules: List[Rule] = node.rules.map { i => if (i == rule) newRule else i } + val newRules: List[Rule] = node.rules.map { i => if (i.id == rule.id) newRule else i } val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) - - return Option(newNode) - }) + story.removeNode(node).addNode(newNode) + case None => story } - } - case "anywhere-check" => - val action = processAnywhereCheck(expression, parentRuleId) - - if (currentNode.nonEmpty) { - val node = currentNode.get - node.rules.filter(_.id == RuleId(parentRuleId)).foreach(rule => { - val newActions = rule.actions.:+(action) - val newRule = rule.copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, rule.conditions, newActions) - val newRules: List[Rule] = node.rules.map { i => if (i == rule) newRule else i } - val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) - - return Option(newNode) - }) - } - case "displayed-node" => - val action = processDisplayedNode(expression, parentRuleId) - - if (currentNode.nonEmpty) { - val node = currentNode.get - node.content.rulesets.foreach(ruleSet => { - ruleSet.rules.filter(_.id == RuleId(parentRuleId)).foreach(rule => { - - val newActions = rule.actions.:+(action) - val newRule = rule.copy( - rule.id, - rule.name, - rule.stopIfTrue, - rule.conditionsOp, - rule.conditions, - newActions - ) - val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } - val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) - val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) - val newContent = node.content.copy(node.content.text, newFullSet) - val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - - return Option(newNode) - }) - }) - } - case _ => - } - - Option.empty[Node] - } - - /** - * Process the tokens for create-typed-condition-2 rule - * - * @param i The token iterator - * @param currentNode The latest node processed - * @param isWithinNode Flag that determines if the rule resides at the Node or Fragment level - * @return The modified node - */ - private def processCreateTypedCondition2(i: Iterator[Any], - currentNode: Option[Node], - isWithinNode: Boolean): Option[Node] = { - //val conditionName = - i.next().asInstanceOf[String] - val conditionType = i.next().asInstanceOf[BigInt] - - var newConditionType = "" - - conditionType.toInt match { - case 0 => newConditionType = "NodeCondition" - case 1 => newConditionType = "LinkCondition" - case 2 => newConditionType = "BooleanFactValue" - case 3 => newConditionType = "IntegerFactComparison" - case _ => //Console.println("Condition Type: " + conditionType) - } - - val targetId = i.next().asInstanceOf[BigInt] // Node/Link/Fact ID - - var status = i.next() - - val ruleId = i.next().asInstanceOf[BigInt] - //val fixedIdString = - i.next().asInstanceOf[String] - //val conditionId = - i.next().asInstanceOf[BigInt] - //val numFactArgsString = - i.next().asInstanceOf[String] - val numFactArgs = i.next() - - - if (currentNode.nonEmpty) { - val node = currentNode.get - - if (isWithinNode) { - node.rules.filter(_.id == RuleId(ruleId)).foreach(rule => { - val definitions = ConditionDefinitions() - definitions.foreach(f = definition => { - if (definition.conditionType == ConditionType(newConditionType)) { - var params: Map[ParamName, ParamValue] = Map() - newConditionType match { - case "NodeCondition" => - params = Map( - ParamName("node") -> ParamValue.Node(NodeId(targetId)), - ParamName("status") -> SelectedListValue( - status.asInstanceOf[BigInt].toInt match { - case 0 => "not visited"; - case 1 => "visited"; - case 2 => "is previous"; - case 3 => "is not previous"; - case 4 => "current" - } - ) - ) - - case "LinkCondition" => - params = Map( - ParamName("link") -> ParamValue.Link(RulesetId(targetId)), - ParamName("status") -> SelectedListValue( - if (status.asInstanceOf[BigInt] == BigInt(1)) "followed" else "not followed" - ) - ) - - case "BooleanFactValue" => - - if (status.isInstanceOf[BigInt]) { - status = if (status == BigInt(1)) true else false - } - params = Map( - ParamName("fact") -> ParamValue.BooleanFact(FactId(targetId)), - ParamName("state") -> - SelectedListValue(if (status.asInstanceOf[Boolean]) "true" else "false") - ) - - case "IntegerFactComparison" => - val numFactArguments = numFactArgs.asInstanceOf[List[Any]].iterator - //val listString = - numFactArguments.next().asInstanceOf[String] - val operator = numFactArguments.next().asInstanceOf[String] - val mode = numFactArguments.next().asInstanceOf[String] - val value = numFactArguments.next().asInstanceOf[String] - - val paramV = - if (mode == "Fact") - ParamValue.IntegerFact(FactId(BigInt(value))) - else - ParamValue.IntegerInput(BigInt(value)) - - params = Map( - ParamName("fact") -> ParamValue.IntegerFact(FactId(targetId)), - ParamName(if (mode == "Fact") "otherFact" else "input") -> paramV, - ParamName("operator") -> SelectedListValue(operator), - ParamName("comparisonValue") -> - UnionValueSelected(if (mode == "Fact") "otherFact" else "input") + } else { + node.content.rulesets.find(_.rules.exists(_.id == RuleId(ruleId))) match { + case Some(ruleSet) => + ruleSet.rules.find(_.id == RuleId(ruleId)) match { + case Some(rule) => + val newConditions: List[Condition] = newCondition :: rule.conditions + val newRule = rule.copy( + rule.id, + rule.name, + rule.stopIfTrue, + rule.conditionsOp, + newConditions, + rule.actions ) - case _ => - } - - val newCondition = Condition(ConditionType(newConditionType), params) - val newConditions = newCondition :: rule.conditions - val newRule = rule - .copy(rule.id, rule.name, rule.stopIfTrue, rule.conditionsOp, newConditions, rule.actions) - val newRules = node.rules.map { i => if (i == rule) newRule else i } + val newRules: List[Rule] = ruleSet.rules.map { i => if (i.id == rule.id) newRule else i } - val newNode = node.copy(node.id, node.name, node.content, node.isStartNode, newRules) + val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) + val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) + val newContent = node.content.copy(node.content.text, newFullSet) + val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - return Option(newNode) - } - }) - }) - } else { - node.content.rulesets.foreach(ruleSet => { - ruleSet.rules.filter(_.id == RuleId(ruleId)).foreach(rule => { - val definitions = ConditionDefinitions() - - definitions.foreach(definition => { - if (definition.conditionType == ConditionType(newConditionType)) { - var params: Map[ParamName, ParamValue] = Map() - newConditionType match { - case "NodeCondition" => - params = Map( - ParamName("node") -> ParamValue.Node(NodeId(targetId)), - ParamName("status") -> SelectedListValue(status.asInstanceOf[BigInt].toInt match { - case 0 => "not visited"; - case 1 => "visited"; - case 2 => "is previous"; - case 3 => "is not previous"; - case 4 => "current" - }) - ) - - case "LinkCondition" => - params = Map( - ParamName("link") -> ParamValue.Link(RulesetId(targetId)), - ParamName("status") -> SelectedListValue( - if (status.asInstanceOf[BigInt] == BigInt(1)) "followed" else "not followed" - ) - ) - - case "BooleanFactValue" => - - if (status.isInstanceOf[BigInt]) { - status = if (status == BigInt(1)) true else false - } - - params = Map( - ParamName("fact") -> ParamValue.BooleanFact(FactId(targetId)), - ParamName("state") -> - SelectedListValue(if (status.asInstanceOf[Boolean]) "true" else "false") - ) - - case "IntegerFactComparison" => - val numFactArguments = numFactArgs.asInstanceOf[List[Any]].iterator - //val listString = - numFactArguments.next().asInstanceOf[String] - val operator = numFactArguments.next().asInstanceOf[String] - val mode = numFactArguments.next().asInstanceOf[String] - val value = numFactArguments.next().asInstanceOf[String] - - val paramV = - if (mode == "Fact") - ParamValue.IntegerFact(FactId(BigInt(value))) - else - ParamValue.IntegerInput(BigInt(value)) - - params = Map( - ParamName("fact") -> ParamValue.IntegerFact(FactId(targetId)), - ParamName(if (mode == "Fact") "otherFact" else "input") -> paramV, - ParamName("operator") -> SelectedListValue(operator), - ParamName("comparisonValue") -> - UnionValueSelected(if (mode == "Fact") "otherFact" else "input") - ) - - case _ => - } - - val newCondition = Condition(ConditionType(newConditionType), params) - val newConditions: List[Condition] = newCondition :: rule.conditions - val newRule = rule.copy( - rule.id, - rule.name, - rule.stopIfTrue, - rule.conditionsOp, - newConditions, - rule.actions - ) - val newRules: List[Rule] = ruleSet.rules.map { i => if (i == rule) newRule else i } - val newSet: Ruleset = ruleSet.copy(ruleSet.id, ruleSet.name, ruleSet.indexes, newRules) - val newFullSet: List[Ruleset] = node.content.rulesets.map(i => if (i == ruleSet) newSet else i) - val newContent = node.content.copy(node.content.text, newFullSet) - val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) - - return Option(newNode) - } - }) - }) - }) - } - } - - Option.empty[Node] - } - - /** - * Processes the second level of tokens - * - * @param iterator The token iterator - * @param currentNode The latest node to be processed - * @param isWithinNode Flag indicating if the action should occur at the Node level or Fragment level - */ - private def processSecondLevel(iterator: Iterator[Any], - currentNode: Option[Node], - isWithinNode: Boolean): Option[Node] = { - var newCurrentNode = currentNode - - while (iterator.hasNext) { - val current = iterator.next() - current match { - case list: List[Any] => - val i = current.asInstanceOf[List[Any]].iterator - val firstToken = i.next() - firstToken match { - case str: String => - val instruction = firstToken.asInstanceOf[String] - instruction match { - case "create-typed-rule3" => - newCurrentNode = processCreateTypedRule3(i, newCurrentNode, isWithinNode) - case "create-action" => - newCurrentNode = processCreateAction(i, newCurrentNode) - case "create-typed-condition2" => - newCurrentNode = processCreateTypedCondition2(i, newCurrentNode, isWithinNode) - case _ => + story.removeNode(node).addNode(newNode) + case None => story } + case None => story } - } + } + case None => story } - - newCurrentNode } /** - * Processes the link creation tokens + * Builds node facts given a sequence of blocks * - * @param i The token iterator - * @return The updated story containing the node with the created link + * @param story Story to add the built nodes to + * @param blocks Sequence of blocks to read from + * @return Story containing built node facts */ - private def processCreateLink(i: Iterator[Any], story: Story): Option[Node] = { - - val linkName = i.next().asInstanceOf[String] - val fromNodeID = i.next().asInstanceOf[BigInt] - //val toNodeID = - i.next().asInstanceOf[BigInt] - val startIndex = i.next().asInstanceOf[BigInt] - val endIndex = i.next().asInstanceOf[BigInt] - //val useDestination = - i.next().asInstanceOf[Boolean] - //val useAltDestination = - i.next().asInstanceOf[Boolean] - //val useAltText = - i.next().asInstanceOf[Boolean] - //val altDestination = - i.next().asInstanceOf[BigInt] - //val altText = - i.next().asInstanceOf[String] - //val updateDisplay = - i.next().asInstanceOf[Boolean] - val linkId = i.next().asInstanceOf[BigInt] - - story.nodes.filter(_.id == NodeId(fromNodeID)).foreach(node => { - - val newSet = Ruleset( - RulesetId(linkId), - linkName, - RulesetIndexes(TextIndex(startIndex), TextIndex(endIndex)), - List() - ) - - val newContent = node.content.copy(node.content.text, node.content.rulesets.:+(newSet)) - val newNode = node.copy(node.id, node.name, newContent, node.isStartNode, node.rules) + private def buildFacts(story: Story, blocks: Seq[Block]): Story = { + val relevantBlocks = blocks.filter(_.value._1.value == "create-fact").map(_.value._2) + + relevantBlocks.foldLeft(story)((r, params) => { + val factName = params.head.as[String] + val factType = params(1).as[String] + val factId = FactId(params(2).as[BigInt]) + + val newFact = factType match { + case "boolean" => val boolValue = false; Option(BooleanFact(factId, factName, boolValue)) + case "string" => Option(StringFact(factId, factName, "")) + case "number" => Option(IntegerFact(factId, factName, 0)) + case _ => None + } - return Option(newNode) + newFact match { + case Some(fact) => r.addFact(fact) + case None => r + } }) - - Option.empty[Node] } /** - * Processes node creation tokens + * Wrapper for a named function * - * @param i The token iterator - * @return The created node + * @param f Anonymous function + * @param name Name of the function + * @tparam T Parameter type + * @tparam V Return type */ - private def processCreateNode(i: Iterator[Any]): Node = { - val nameValue = i.next().asInstanceOf[String] - val contentText = i.next().asInstanceOf[String] - var next = i.next() - val x = next match { - case bigInt: BigInt => bigInt.doubleValue; - case _ => next.asInstanceOf[Double] - } - next = i.next() - val y = next match { - case bigInt: BigInt => bigInt.doubleValue; - case _ => next.asInstanceOf[Double] - } - //val isAnywhere = - i.next().asInstanceOf[Boolean] - //val updateDisplay = - i.next() - val nodeId = i.next().asInstanceOf[BigInt] - - val ruleSet: List[Rule] = List() - val isStartNode = false - - val node = Node( - NodeId(nodeId), - nameValue, - NodeContent(contentText, List()), - isStartNode, - ruleSet - ) - - val newPosMap = AstMap("id" -> AstInteger(node.id.value), "x" -> AstFloat(x), "y" -> AstFloat(y)) - val newElems = nodePositions.toList.::(newPosMap) - nodePositions = AstList(newElems: _*) + case class NamedFunction[T, V](f: T => V, name: String) extends (T => V) { + def apply(t: T) = f(t) - node + override def toString() = name } /** - * Process the tokens for create-fact action - * - * @param i The token iterator - * @param story The existing story to process the tokens into - * @return The updated story + * Internal value class wrapper for data types */ - private def processCreateFact(i: Iterator[Any], story: Story): Story = { + object Scheme { + sealed trait Val extends Any { + def value: Any - var newStory = story - - while (i.hasNext) { - val factName = i.next().asInstanceOf[String] - val factType = i.next().asInstanceOf[List[Any]].lift(1).get.asInstanceOf[String] - val factId = FactId(i.next().asInstanceOf[BigInt]) - - var fact = Option.empty[Fact] - - factType match { - case "boolean" => val boolValue = false; fact = Option(BooleanFact(factId, factName, boolValue)) - case "string" => fact = Option(StringFact(factId, factName, "")) - case "number" => fact = Option(IntegerFact(factId, factName, 0)) - } - - newStory = newStory.addFact(fact.get) + def as[T] = value.asInstanceOf[T] } + case class Str(value: java.lang.String = "") extends AnyVal with Val + case class Block(value: (Str, Seq[Val])) extends AnyVal with Val { + def _1: Str = value._1 - newStory - } - - /** - * Processes the first level of tokens - * - * @param iterator The token iterator - * @param story The existing story to process the tokens into - * @return The updated story - */ - private def processFirstLevel(iterator: Iterator[Any], story: Story): Story = { - - var newStory = story - var startNode = BigInt(-1) - - val headers = new scala.collection.mutable.Queue[List[Any]] - - while (iterator.hasNext) { - val current = iterator.next() - current match { - case list: List[Any] => - val i = current.asInstanceOf[List[Any]].iterator - val firstToken = i.next() - firstToken match { - case instruction: String => - instruction match { - case "make-hypertext" => newStory = processHeader(i, newStory) - case "set-story-title!" => newStory = newStory.copy(i.next().asInstanceOf[String]) - case "set-author-name!" => newStory = newStory.changeAuthor(i.next().asInstanceOf[String]) - case "set-story-comment!" => - newStory = newStory.updateMetadata(newStory.metadata.copy(i.next().asInstanceOf[String])) - case "set-disable-back-button!" => - newStory = - newStory.updateMetadata( - newStory.metadata.copy( - newStory.metadata.comments, - newStory.metadata.readerStyle, - i.next().asInstanceOf[Boolean] - ) - ) - case "set-disable-restart-button!" => - newStory = - newStory.updateMetadata( - newStory.metadata.copy( - newStory.metadata.comments, - newStory.metadata.readerStyle, - newStory.metadata.isBackButtonDisabled, - i.next().asInstanceOf[Boolean] - ) - ) - case "set-start-node!" => startNode = i.next().asInstanceOf[BigInt] - case "create-fact" => newStory = processCreateFact(i, newStory) - case "begin" => headers.enqueue(current.asInstanceOf[List[Any]]) - case _ => // Console.println("Base: " + instruction) - } - } - } - } - - while (headers.nonEmpty) { - val i = headers.dequeue().iterator - newStory = processHeader(i, newStory) + def _2: Seq[Val] = value._2 } - - newStory.nodes.filter(_.id == NodeId(startNode)).foreach(node => { - val isStartNode = true - val newNode = node.copy(node.id, node.name, node.content, isStartNode, node.rules) - newStory = newStory.removeNode(node) - newStory = newStory.addNode(newNode) - }) - - newStory + case class Doub(value: Double) extends AnyVal with Val + case class Num(value: BigInt) extends AnyVal with Val + case class Bool(value: Boolean = false) extends AnyVal with Val } }