diff --git a/src/main/kotlin/automaton/constructor/controller/AlgorithmsController.kt b/src/main/kotlin/automaton/constructor/controller/AlgorithmsController.kt new file mode 100644 index 0000000..e0f1a4e --- /dev/null +++ b/src/main/kotlin/automaton/constructor/controller/AlgorithmsController.kt @@ -0,0 +1,29 @@ +package automaton.constructor.controller + +import automaton.constructor.controller.algorithms.ConversionToCFGController +import automaton.constructor.controller.algorithms.HellingsAlgoController +import automaton.constructor.model.automaton.Automaton +import automaton.constructor.model.automaton.FiniteAutomaton +import automaton.constructor.model.automaton.PushdownAutomaton +import automaton.constructor.utils.I18N +import tornadofx.Controller + +class AlgorithmsController( + private val openedAutomaton: Automaton +): Controller() { + fun convertToCFG() { + if (openedAutomaton !is PushdownAutomaton || openedAutomaton.stacks.size > 1) { + tornadofx.error(I18N.messages.getString("CFGView.Error")) + return + } + ConversionToCFGController(openedAutomaton).convertToCFG() + } + + fun executeHellingsAlgo() { + if (openedAutomaton !is FiniteAutomaton) { + tornadofx.error(I18N.messages.getString("HellingsAlgorithm.Error")) + return + } + HellingsAlgoController(openedAutomaton).getGrammar() + } +} \ No newline at end of file diff --git a/src/main/kotlin/automaton/constructor/controller/AutomatonGraphController.kt b/src/main/kotlin/automaton/constructor/controller/AutomatonGraphController.kt index 97dd9f6..604bfbb 100644 --- a/src/main/kotlin/automaton/constructor/controller/AutomatonGraphController.kt +++ b/src/main/kotlin/automaton/constructor/controller/AutomatonGraphController.kt @@ -1,14 +1,12 @@ package automaton.constructor.controller -import automaton.constructor.model.action.Action -import automaton.constructor.model.action.ActionAvailability -import automaton.constructor.model.action.ActionFailedException import automaton.constructor.model.automaton.Automaton import automaton.constructor.model.automaton.allowsBuildingBlocks import automaton.constructor.model.data.addContent import automaton.constructor.model.element.* import automaton.constructor.utils.* import automaton.constructor.view.* +import automaton.constructor.view.automaton.AutomatonGraphView import javafx.geometry.Point2D import javafx.scene.control.ContextMenu import javafx.scene.input.KeyCode @@ -16,8 +14,8 @@ import javafx.scene.input.MouseButton import javafx.scene.shape.Line import tornadofx.* -class AutomatonGraphController(val automaton: Automaton, val automatonViewContext: AutomatonViewContext) : - Controller() { +class AutomatonGraphController(automaton: Automaton, automatonViewContext: AutomatonViewContext) : + AutomatonRepresentationController(automaton, automatonViewContext) { private val settingsController by inject() private val newTransitionLine = Line().apply { isVisible = false } private var newTransitionSourceProperty = objectProperty(null).apply { @@ -32,9 +30,6 @@ class AutomatonGraphController(val automaton: Automaton, val automatonViewContex } } private var newTransitionSource by newTransitionSourceProperty - val lastSelectedElementProperty = objectProperty(null) - var lastSelectedElement by lastSelectedElementProperty - private val selectedElementsViews = mutableSetOf() fun select(elements: Set) { clearSelection() @@ -108,9 +103,11 @@ class AutomatonGraphController(val automaton: Automaton, val automatonViewContex clearSelection() } else if (event.code == KeyCode.A && event.isControlDown) { clearSelection() - selectedElementsViews.addAll(graphView.edgeViews.values.flatMap { it.transitionViews } + selectedElementsViews.addAll( + graphView.edgeViews.values.flatMap { it.transitionViews } .onEach { it.selected = true }) - selectedElementsViews.addAll(graphView.vertexToViewMap.values.onEach { it.selected = true }) + selectedElementsViews.addAll( + graphView.vertexToViewMap.values.onEach { it.selected = true }) } } } @@ -196,95 +193,6 @@ class AutomatonGraphController(val automaton: Automaton, val automatonViewContex } } - private fun registerTransitionView(transitionView: TransitionView) = registerAutomatonElementView(transitionView) - - private fun registerAutomatonElementView(automatonElementView: AutomatonElementView) { - automatonElementView.setOnMouseClicked { - it.consume() - automatonElementView.requestFocus() - if (it.button == MouseButton.PRIMARY) { - if (it.isStillSincePress) - automaton.isOutputOfTransformation?.let { transformation -> - transformation.step(automatonElementView.automatonElement) - return@setOnMouseClicked - } - lastSelectedElement = when { - !it.isControlDown -> { - clearSelection() - selectedElementsViews.add(automatonElementView) - automatonElementView.selected = true - automatonElementView - } - automatonElementView.selected -> { - selectedElementsViews.remove(automatonElementView) - automatonElementView.selected = false - null - } - else -> { - selectedElementsViews.add(automatonElementView) - automatonElementView.selected = true - automatonElementView - } - } - } else if (it.button == MouseButton.SECONDARY && it.isStillSincePress && automaton.allowsModificationsByUser) { - fun showActionsMenu(element: T, actions: List>) { - val actionsWithAvailability = actions.map { action -> - action to action.getAvailabilityFor(element) - } - - if (actionsWithAvailability.any { (_, availability) -> availability != ActionAvailability.HIDDEN }) { - ContextMenu().apply { - for ((action, availability) in actionsWithAvailability) { - item(action.displayName, action.keyCombination) { - action { - try { - if (automaton.allowsModificationsByUser) - action.performOn(element) - } catch (exc: ActionFailedException) { - error( - exc.message, - title = I18N.messages.getString("Dialog.error"), - owner = automatonViewContext.uiComponent.currentWindow - ) - } - - } - isVisible = availability != ActionAvailability.HIDDEN - isDisable = availability == ActionAvailability.DISABLED - } - } - show(automatonElementView.scene.window, it.screenX, it.screenY) - } - clearSelection() - selectedElementsViews.add(automatonElementView) - automatonElementView.selected = true - lastSelectedElement = automatonElementView - } - } - when (automatonElementView.automatonElement) { - is State -> showActionsMenu( - automatonElementView.automatonElement, - automaton.stateActions - ) - is BuildingBlock -> showActionsMenu( - automatonElementView.automatonElement, - automaton.buildingBlockActions - ) - is Transition -> showActionsMenu( - automatonElementView.automatonElement, - automaton.transitionActions - ) - } - } - } - clearSelection() - selectedElementsViews.add(automatonElementView) - automatonElementView.selected = true - lastSelectedElement = automatonElementView - } - - fun clearSelection() { - selectedElementsViews.onEach { it.selected = false }.clear() - lastSelectedElement = null - } + private fun registerTransitionView(transitionView: TransitionView) = + registerAutomatonElementView(transitionView) } diff --git a/src/main/kotlin/automaton/constructor/controller/AutomatonRepresentationController.kt b/src/main/kotlin/automaton/constructor/controller/AutomatonRepresentationController.kt new file mode 100644 index 0000000..e7c6f6a --- /dev/null +++ b/src/main/kotlin/automaton/constructor/controller/AutomatonRepresentationController.kt @@ -0,0 +1,121 @@ +package automaton.constructor.controller + +import automaton.constructor.model.action.Action +import automaton.constructor.model.action.ActionAvailability +import automaton.constructor.model.action.ActionFailedException +import automaton.constructor.model.automaton.Automaton +import automaton.constructor.model.element.AutomatonElement +import automaton.constructor.model.element.BuildingBlock +import automaton.constructor.model.element.State +import automaton.constructor.model.element.Transition +import automaton.constructor.utils.I18N +import automaton.constructor.view.AutomatonElementView +import automaton.constructor.view.AutomatonViewContext +import javafx.scene.control.ContextMenu +import javafx.scene.input.MouseButton +import tornadofx.* + +open class AutomatonRepresentationController( + val automaton: Automaton, + val automatonViewContext: AutomatonViewContext +): Controller() { + val lastSelectedElementProperty = objectProperty(null).also { + it.addListener { _, _, newValue -> + if (newValue == null) { + clearSelection() + } + } + } + var lastSelectedElement by lastSelectedElementProperty + val selectedElementsViews = mutableSetOf() + + fun registerAutomatonElementView(automatonElementView: AutomatonElementView) { + automatonElementView.setOnMouseClicked { + it.consume() + automatonElementView.requestFocus() + if (it.button == MouseButton.PRIMARY) { + if (it.isStillSincePress) + automaton.isOutputOfTransformation?.let { transformation -> + transformation.step(automatonElementView.automatonElement) + return@setOnMouseClicked + } + lastSelectedElement = when { + !it.isControlDown -> { + clearSelection() + selectedElementsViews.add(automatonElementView) + automatonElementView.selected = true + automatonElementView + } + automatonElementView.selected -> { + selectedElementsViews.remove(automatonElementView) + automatonElementView.selected = false + null + } + else -> { + selectedElementsViews.add(automatonElementView) + automatonElementView.selected = true + automatonElementView + } + } + } else if (it.button == MouseButton.SECONDARY && it.isStillSincePress && automaton.allowsModificationsByUser) { + fun showActionsMenu(element: T, actions: List>) { + val actionsWithAvailability = actions.map { action -> + action to action.getAvailabilityFor(element) + } + + if (actionsWithAvailability.any { (_, availability) -> availability != ActionAvailability.HIDDEN }) { + ContextMenu().apply { + for ((action, availability) in actionsWithAvailability) { + item(action.displayName, action.keyCombination) { + action { + try { + if (automaton.allowsModificationsByUser) + action.performOn(element) + } catch (exc: ActionFailedException) { + error( + exc.message, + title = I18N.messages.getString("Dialog.error"), + owner = automatonViewContext.uiComponent.currentWindow + ) + } + + } + isVisible = availability != ActionAvailability.HIDDEN + isDisable = availability == ActionAvailability.DISABLED + } + } + show(automatonElementView.scene.window, it.screenX, it.screenY) + } + clearSelection() + selectedElementsViews.add(automatonElementView) + automatonElementView.selected = true + lastSelectedElement = automatonElementView + } + } + when (automatonElementView.automatonElement) { + is State -> showActionsMenu( + automatonElementView.automatonElement, + automaton.stateActions + ) + is BuildingBlock -> showActionsMenu( + automatonElementView.automatonElement, + automaton.buildingBlockActions + ) + is Transition -> showActionsMenu( + automatonElementView.automatonElement, + automaton.transitionActions + ) + } + } + } + clearSelection() + selectedElementsViews.add(automatonElementView) + automatonElementView.selected = true + lastSelectedElement = automatonElementView + } + + fun clearSelection() { + selectedElementsViews.onEach { it.selected = false }.clear() + lastSelectedElement = null + } +} \ No newline at end of file diff --git a/src/main/kotlin/automaton/constructor/controller/FileController.kt b/src/main/kotlin/automaton/constructor/controller/FileController.kt index 97b15c8..f435799 100644 --- a/src/main/kotlin/automaton/constructor/controller/FileController.kt +++ b/src/main/kotlin/automaton/constructor/controller/FileController.kt @@ -13,7 +13,7 @@ import automaton.constructor.model.module.hasRegexes import automaton.constructor.model.serializers.AutomatonSerializer import automaton.constructor.model.serializers.automatonSerializers import automaton.constructor.utils.* -import automaton.constructor.view.TestsView +import automaton.constructor.view.tests.TestsView import javafx.beans.binding.Binding import javafx.concurrent.Task import javafx.geometry.Pos diff --git a/src/main/kotlin/automaton/constructor/controller/TestsController.kt b/src/main/kotlin/automaton/constructor/controller/TestsController.kt index 1664c95..45d99ef 100644 --- a/src/main/kotlin/automaton/constructor/controller/TestsController.kt +++ b/src/main/kotlin/automaton/constructor/controller/TestsController.kt @@ -9,9 +9,9 @@ import automaton.constructor.model.memory.Test import automaton.constructor.model.memory.TestsForSerializing import automaton.constructor.utils.* import automaton.constructor.utils.addOnSuccess -import automaton.constructor.view.TestAndResult -import automaton.constructor.view.TestsView -import automaton.constructor.view.TestsResultsFragment +import automaton.constructor.view.tests.TestAndResult +import automaton.constructor.view.tests.TestsView +import automaton.constructor.view.tests.TestsResultsFragment import javafx.concurrent.Task import javafx.scene.control.Alert import javafx.scene.control.ButtonType diff --git a/src/main/kotlin/automaton/constructor/controller/algorithms/ConversionToCFGController.kt b/src/main/kotlin/automaton/constructor/controller/algorithms/ConversionToCFGController.kt new file mode 100644 index 0000000..81352b1 --- /dev/null +++ b/src/main/kotlin/automaton/constructor/controller/algorithms/ConversionToCFGController.kt @@ -0,0 +1,16 @@ +package automaton.constructor.controller.algorithms + +import automaton.constructor.model.automaton.PushdownAutomaton +import automaton.constructor.utils.I18N +import automaton.constructor.view.algorithms.CFGView +import tornadofx.Controller + +class ConversionToCFGController(private val openedAutomaton: PushdownAutomaton): Controller() { + fun convertToCFG() { + val conversionToCFGWindow = find(mapOf( + CFGView::grammar to openedAutomaton.convertToCFG() + )) + conversionToCFGWindow.title = I18N.messages.getString("CFGView.Title") + conversionToCFGWindow.openWindow() + } +} \ No newline at end of file diff --git a/src/main/kotlin/automaton/constructor/controller/algorithms/HellingsAlgoController.kt b/src/main/kotlin/automaton/constructor/controller/algorithms/HellingsAlgoController.kt new file mode 100644 index 0000000..c2f5b2f --- /dev/null +++ b/src/main/kotlin/automaton/constructor/controller/algorithms/HellingsAlgoController.kt @@ -0,0 +1,85 @@ +package automaton.constructor.controller.algorithms + +import automaton.constructor.model.automaton.Automaton +import automaton.constructor.model.element.* +import automaton.constructor.utils.I18N +import automaton.constructor.utils.doNextIterationOfHellingsAlgo +import automaton.constructor.view.algorithms.CFGView +import automaton.constructor.view.algorithms.HellingsAlgoExecutionView +import automaton.constructor.view.algorithms.HellingsAlgoGrammarView +import javafx.beans.property.SimpleBooleanProperty +import javafx.collections.ObservableList +import tornadofx.* + +class HellingsTransition( + val nonterminal: Nonterminal, + val source: AutomatonVertex, + val target: AutomatonVertex, + var isNew: SimpleBooleanProperty = SimpleBooleanProperty(true) +) { + override fun toString() = "${nonterminal.value}, ${source.name}, ${target.name}" + + fun isEqual(transition: HellingsTransition) = + nonterminal == transition.nonterminal && source == transition.source && target == transition.target +} + +class HellingsAlgoController( + private val openedAutomaton: Automaton +): Controller() { + lateinit var grammar: ContextFreeGrammar + + fun getGrammar() { + find(mapOf(HellingsAlgoGrammarView::controller to this)).apply { + title = I18N.messages.getString("HellingsAlgorithm.Grammar.Title") + }.openWindow() + } + + fun prepareForExecution( + currentTransitions: ObservableList, + allTransitions: ObservableList + ) { + openedAutomaton.transitions.forEach { transition -> + val productions = grammar.productions.filter { + it.rightSide.size == 1 && it.rightSide[0] is Terminal && it.rightSide[0].getSymbol() == transition.propetiesText + } + productions.forEach { + val newHellingsTransition = HellingsTransition(it.leftSide, transition.source, + transition.target, SimpleBooleanProperty(false) + ) + currentTransitions.add(newHellingsTransition) + allTransitions.add(newHellingsTransition) + } + } + if (grammar.productions.any { it.leftSide == grammar.initialNonterminal && it.rightSide.isEmpty() }) { + openedAutomaton.vertices.forEach { + val newHellingsTransition = HellingsTransition(grammar.initialNonterminal, it, it, + SimpleBooleanProperty(false) + ) + currentTransitions.add(newHellingsTransition) + allTransitions.add(newHellingsTransition) + } + } + } + + fun execute() { + val currentTransitions = observableListOf() + val allTransitions = observableListOf() + prepareForExecution(currentTransitions, allTransitions) + + val hellingsAlgoExecutionWindow = find(mapOf( + HellingsAlgoExecutionView::currentTransitions to currentTransitions, + HellingsAlgoExecutionView::allTransitions to allTransitions + )).apply { title = I18N.messages.getString("HellingsAlgorithm.Execution.Title") } + hellingsAlgoExecutionWindow.openWindow() + find(mapOf(CFGView::grammar to grammar)).apply { + title = I18N.messages.getString("CFGView.Title") + }.openWindow() + + hellingsAlgoExecutionWindow.nextIterationButton.action { + doNextIterationOfHellingsAlgo(currentTransitions, allTransitions, grammar) + if (currentTransitions.isEmpty()) { + hellingsAlgoExecutionWindow.nextIterationButton.isVisible = false + } + } + } +} diff --git a/src/main/kotlin/automaton/constructor/model/automaton/AbstractAutomaton.kt b/src/main/kotlin/automaton/constructor/model/automaton/AbstractAutomaton.kt index f0e2e57..e5a4225 100644 --- a/src/main/kotlin/automaton/constructor/model/automaton/AbstractAutomaton.kt +++ b/src/main/kotlin/automaton/constructor/model/automaton/AbstractAutomaton.kt @@ -75,7 +75,7 @@ abstract class AbstractAutomaton( override val buildingBlocks = vertices.filteredSet { (it is BuildingBlock).toProperty() } as ObservableSet - private fun nextStateSuffix(): Int = nextVertexSuffix(GENERATED_STATE_NAME_REGEX) + fun nextStateSuffix(): Int = nextVertexSuffix(GENERATED_STATE_NAME_REGEX) private fun nextBuildingBlockSuffix(): Int = nextVertexSuffix(GENERATED_BUILDING_BLOCK_NAME_REGEX) private fun nextVertexSuffix(vertexNameRegex: Regex): Int { @@ -254,7 +254,7 @@ abstract class AbstractAutomaton( } companion object { - private const val STATE_NAME_PREFIX = "S" + const val STATE_NAME_PREFIX = "S" private const val BUILDING_BLOCK_NAME_PREFIX = "M" private val GENERATED_STATE_NAME_REGEX = Regex("$STATE_NAME_PREFIX(\\d+)") private val GENERATED_BUILDING_BLOCK_NAME_REGEX = Regex("$BUILDING_BLOCK_NAME_PREFIX(\\d+)") diff --git a/src/main/kotlin/automaton/constructor/model/automaton/PushdownAutomaton.kt b/src/main/kotlin/automaton/constructor/model/automaton/PushdownAutomaton.kt index 27976c0..31636e6 100644 --- a/src/main/kotlin/automaton/constructor/model/automaton/PushdownAutomaton.kt +++ b/src/main/kotlin/automaton/constructor/model/automaton/PushdownAutomaton.kt @@ -4,8 +4,15 @@ import automaton.constructor.model.action.transition.EliminateEpsilonTransitionA import automaton.constructor.model.automaton.flavours.AutomatonWithInputTape import automaton.constructor.model.automaton.flavours.AutomatonWithStacks import automaton.constructor.model.data.PushdownAutomatonData +import automaton.constructor.model.data.createAutomaton +import automaton.constructor.model.data.getData +import automaton.constructor.model.element.* import automaton.constructor.model.memory.StackDescriptor import automaton.constructor.model.memory.tape.InputTapeDescriptor +import automaton.constructor.model.module.finalVertices +import automaton.constructor.model.module.initialVertices +import automaton.constructor.model.property.EPSILON_VALUE +import automaton.constructor.model.property.FormalRegex import automaton.constructor.utils.I18N /** @@ -23,6 +30,7 @@ class PushdownAutomaton( I18N.messages.getString("PushdownAutomaton.Untitled") ), AutomatonWithInputTape, AutomatonWithStacks { + private var grammar: ContextFreeGrammar? = null init { require(stacks.isNotEmpty()) { "Illegal `stacks` argument when creating `PushdownAutomaton`" @@ -41,4 +49,206 @@ class PushdownAutomaton( companion object { val DISPLAY_NAME: String = I18N.messages.getString("PushdownAutomaton") } + + private fun pushOnlyOneSymbol() { + transitions.toList().forEach { transition -> + val pushedValue = transition[stacks.first().pushedValue] + if (pushedValue != EPSILON_VALUE && pushedValue.length > 1) { + var previousState = transition.source + for (i in pushedValue.indices) { + val nextState = if (i == pushedValue.lastIndex) { + transition.target + } else + addState() + val newTransition = addTransition(previousState, nextState) + if (i == 0) { + newTransition[inputTape.expectedChar] = transition[inputTape.expectedChar] + newTransition[stacks.first().expectedChar] = transition[stacks.first().expectedChar] + } + newTransition[stacks.first().pushedValue] = pushedValue[pushedValue.lastIndex - i].toString() + previousState = nextState + } + removeTransition(transition) + } + } + } + + private fun makeTheOnlyOneFinalState() { + if (finalVertices.size < 2 || stacks.first().acceptsByEmptyStack) { + return + } + val newFinalState = addState() + finalVertices.toList().forEach { + addTransition(it, newFinalState) + it.isFinal = false + } + newFinalState.isFinal = true + } + + private fun pushOrPopOnEachTransition() { + transitions.toList().forEach { transition -> + if (transition[stacks.first().expectedChar] == EPSILON_VALUE && + transition[stacks.first().pushedValue] == EPSILON_VALUE) { + val newState = addState() + val firstTransition = addTransition(transition.source, newState) + firstTransition[inputTape.expectedChar] = transition[inputTape.expectedChar] + firstTransition[stacks.first().pushedValue] = "a" + val secondTransition = addTransition(newState, transition.target) + secondTransition[stacks.first().expectedChar] = 'a' + removeTransition(transition) + } + if (transition[stacks.first().expectedChar] != EPSILON_VALUE && + transition[stacks.first().pushedValue] != EPSILON_VALUE) { + val newState = addState() + val firstTransition = addTransition(transition.source, newState) + firstTransition[inputTape.expectedChar] = transition[inputTape.expectedChar] + firstTransition[stacks.first().expectedChar] = transition[stacks.first().expectedChar] + val secondTransition = addTransition(newState, transition.target) + secondTransition[stacks.first().pushedValue] = transition[stacks.first().pushedValue] + removeTransition(transition) + } + } + } + + private fun clearTheStackAtTheEnd() { + val pushedSymbols = mutableSetOf() + transitions.forEach { it[stacks.first().pushedValue]?.let { symbol -> pushedSymbols.add(symbol) } } + pushedSymbols.forEach { addTransition(finalVertices.first(), finalVertices.first()).apply { + this[stacks.first().expectedChar] = it[0] + } } + } + + private fun prepareForConversionToCFG() { + pushOnlyOneSymbol() + makeTheOnlyOneFinalState() + pushOrPopOnEachTransition() + clearTheStackAtTheEnd() + } + + private fun prepareForConversionToAcceptingByFinalState() { + if (!stacks.first().acceptsByEmptyStack) { + return + } + val newInitialState = addState() + initialVertices.first().isInitial = false + newInitialState.isInitial = true + val newFinalState = addState() + finalVertices.toList().forEach { + it.isFinal = false + } + newFinalState.isFinal = true + } + + private fun makeTheOnlyOneInitialState() { + if (initialVertices.size < 2) { + return + } + val newInitialState = addState() + initialVertices.toList().forEach { + addTransition(newInitialState, it) + it.isInitial = false + } + newInitialState.isInitial = true + } + + private fun startWithEmptyStack() { + if (stacks.first().value.isEmpty()) { + return + } + val newInitialState = addState() + val oldInitialState = initialVertices.first() + oldInitialState.isInitial = false + newInitialState.isInitial = true + val transition = addTransition(newInitialState, oldInitialState) + transition[stacks.first().pushedValue] = stacks.first().value + } + + fun convertToCFG(): ContextFreeGrammar { + val initialNonterminal = Nonterminal("S") + val newGrammar = ContextFreeGrammar(initialNonterminal) + val nonterminals = mutableListOf>() + var biggestNonterminal: Nonterminal? = null + val automatonCopy = getData().createAutomaton() as PushdownAutomaton + + if (automatonCopy.initialVertices.isEmpty()) { + grammar = newGrammar + return newGrammar + } + automatonCopy.makeTheOnlyOneInitialState() + automatonCopy.startWithEmptyStack() + val oldInitialState = automatonCopy.initialVertices.first() + automatonCopy.prepareForConversionToAcceptingByFinalState() + if (automatonCopy.finalVertices.isEmpty()) { + grammar = newGrammar + return newGrammar + } + automatonCopy.prepareForConversionToCFG() + automatonCopy.vertices.forEach { vertex1 -> + val list = mutableListOf() + automatonCopy.vertices.forEach { vertex2 -> + val newNonterminal = newGrammar.addNonterminal() + list.add(newNonterminal) + if (vertex1.isInitial && vertex2.isFinal) { + biggestNonterminal = newNonterminal + } + } + nonterminals.add(list) + } + + for (i in nonterminals.indices) { + newGrammar.productions.add(Production(nonterminals[i][i], mutableListOf())) + } + for (i in nonterminals.indices) { + for (j in nonterminals.indices) { + for (k in nonterminals.indices) { + newGrammar.productions.add(Production(nonterminals[i][j], + mutableListOf(nonterminals[i][k], nonterminals[k][j]) + )) + } + } + } + automatonCopy.transitions.forEach { transition1 -> + automatonCopy.transitions.forEach { transition2 -> + if (transition1[automatonCopy.stacks.first().pushedValue] != EPSILON_VALUE && + transition1[automatonCopy.stacks.first().pushedValue]!![0] == transition2[automatonCopy.stacks.first().expectedChar]) { + val indexOfSource1 = automatonCopy.vertices.indexOf(transition1.source) + val indexOfSource2 = automatonCopy.vertices.indexOf(transition2.source) + val indexOfTarget1 = automatonCopy.vertices.indexOf(transition1.target) + val indexOfTarget2 = automatonCopy.vertices.indexOf(transition2.target) + + val rightSideOfNewProduction = mutableListOf() + val transitionTapeChar1 = transition1[automatonCopy.inputTape.expectedChar] + if (transitionTapeChar1 != EPSILON_VALUE && transitionTapeChar1 is FormalRegex.Singleton) { + rightSideOfNewProduction.add(Terminal(transitionTapeChar1.char)) + } + rightSideOfNewProduction.add(nonterminals[indexOfTarget1][indexOfSource2]) + val transitionTapeChar2 = transition2[automatonCopy.inputTape.expectedChar] + if (transitionTapeChar2 != EPSILON_VALUE && transitionTapeChar2 is FormalRegex.Singleton) { + rightSideOfNewProduction.add(Terminal(transitionTapeChar2.char)) + } + newGrammar.productions.add( + Production(nonterminals[indexOfSource1][indexOfTarget2], rightSideOfNewProduction)) + } + } + } + if (automatonCopy.stacks.first().acceptsByEmptyStack) { + vertices.forEach { vertex -> + val indexOfSource1 = automatonCopy.vertices.indexOf(automatonCopy.initialVertices.first()) + val indexOfSource2 = automatonCopy.vertices.indexOf(automatonCopy.vertices.find { it.name == vertex.name }) + val indexOfTarget1 = automatonCopy.vertices.indexOf(oldInitialState) + val indexOfTarget2 = automatonCopy.vertices.indexOf(automatonCopy.finalVertices.first()) + newGrammar.productions.add(Production(nonterminals[indexOfSource1][indexOfTarget2], + mutableListOf(nonterminals[indexOfTarget1][indexOfSource2]))) + } + } + + if (biggestNonterminal != null) { + newGrammar.productions.add(Production(initialNonterminal, mutableListOf(biggestNonterminal!!))) + } + newGrammar.removeUselessNonterminals() + newGrammar.convertToCNF() + newGrammar.removeUselessNonterminals() + grammar = newGrammar + return newGrammar + } } diff --git a/src/main/kotlin/automaton/constructor/model/element/AutomatonElement.kt b/src/main/kotlin/automaton/constructor/model/element/AutomatonElement.kt index 602549b..0f9e4b3 100644 --- a/src/main/kotlin/automaton/constructor/model/element/AutomatonElement.kt +++ b/src/main/kotlin/automaton/constructor/model/element/AutomatonElement.kt @@ -72,4 +72,18 @@ sealed class AutomatonElement(propertyDescriptorGroups: List + sideEffects.joinToString(separator = ",") { it.displayValue } + }.filter { it.isNotEmpty() }.joinToString(separator = ";") + } + val sideEffectsText by sideEffectsTextBinding } diff --git a/src/main/kotlin/automaton/constructor/model/element/ContextFreeGrammar.kt b/src/main/kotlin/automaton/constructor/model/element/ContextFreeGrammar.kt new file mode 100644 index 0000000..fba860a --- /dev/null +++ b/src/main/kotlin/automaton/constructor/model/element/ContextFreeGrammar.kt @@ -0,0 +1,270 @@ +package automaton.constructor.model.element + +interface CFGSymbol { + fun getSymbol(): String +} +class Terminal(var value: Char): CFGSymbol { + override fun getSymbol() = value.toString() +} +class Nonterminal(var value: String): CFGSymbol { + override fun getSymbol() = value +} +class Production(val leftSide: Nonterminal, val rightSide: MutableList) { + override fun toString() = leftSide.value + ";" + rightSide.joinToString(separator = ",") { it.getSymbol() } +} + +class ContextFreeGrammar(newInitialNonterminal: Nonterminal? = null) { + val nonterminals = mutableListOf() + val productions = mutableListOf() + private var nonterminalsCount = 0 // count of all ever used nonterminals, used for naming to avoid collisions + private val nonterminalsValues = mutableSetOf() + var initialNonterminal: Nonterminal + + init { + initialNonterminal = if (newInitialNonterminal == null) { + addNonterminal("S") + } else { + addNonterminal(newInitialNonterminal) + newInitialNonterminal + } + } + + fun addNonterminal(value: String = "A"): Nonterminal { + val newNonterminal = if (nonterminalsValues.contains(value)) { + Nonterminal(value + nonterminalsCount.toString()) + } else { + Nonterminal(value) + } + nonterminals.add(newNonterminal) + nonterminalsCount++ + nonterminalsValues.add(newNonterminal.value) + return newNonterminal + } + + fun addNonterminal(nonterminal: Nonterminal): Boolean { + if (nonterminalsValues.contains(nonterminal.value)) + return false + nonterminals.add(nonterminal) + nonterminalsCount++ + nonterminalsValues.add(nonterminal.value) + return true + } + + fun removeNonterminal(nonterminal: Nonterminal) { + if (nonterminal == initialNonterminal) { + return + } + nonterminals.remove(nonterminal) + nonterminalsValues.remove(nonterminal.value) + } + + private fun removeEpsilonProductions() { + var areThereNullableNonterminals = true + val nulledNonterminals = mutableSetOf() + while (areThereNullableNonterminals) { + val nullableNonterminals = mutableSetOf() + productions.forEach { + if (it.rightSide.isEmpty() && it.leftSide != initialNonterminal && !nulledNonterminals.contains(it.leftSide)) { + nullableNonterminals.add(it.leftSide) + nulledNonterminals.add(it.leftSide) + } + } + productions.removeAll { it.rightSide.isEmpty() && it.leftSide != initialNonterminal } + nullableNonterminals.forEach { nonterminal -> + val productionsToAdd = mutableListOf() + productions.forEach { + fun addProductions(production: Production, start: Int) { + for (i in start..production.rightSide.lastIndex) { + if (production.rightSide[i] == nonterminal) { + val newRightSide = production.rightSide.toMutableList() + newRightSide.removeAt(i) + val newProduction = Production(production.leftSide, newRightSide) + if (newRightSide.isNotEmpty()) { + productionsToAdd.add(newProduction) + addProductions(newProduction, i) + } else if (newProduction.leftSide != nonterminal) { + productionsToAdd.add(newProduction) + } + } + } + } + + addProductions(it, 0) + } + productions.addAll(productionsToAdd) + } + areThereNullableNonterminals = nullableNonterminals.isNotEmpty() + } + } + + private fun removeUnitProductions() { + val unitProductions = mutableMapOf>() + val productionsByNonterminals = mutableMapOf>>() + productions.forEach { + if (it.rightSide.size == 1 && it.rightSide[0] is Nonterminal) { + if (it.leftSide != it.rightSide[0]) { + if (!unitProductions.containsKey(it.leftSide)) { + unitProductions[it.leftSide] = mutableSetOf() + } + unitProductions[it.leftSide]!!.add(it.rightSide[0] as Nonterminal) + } + } else { + if (!productionsByNonterminals.containsKey(it.leftSide)) { + productionsByNonterminals[it.leftSide] = mutableSetOf() + } + productionsByNonterminals[it.leftSide]!!.add(it.rightSide) + } + } + productions.removeAll { it.rightSide.size == 1 && it.rightSide[0] is Nonterminal } + unitProductions.keys.forEach { leftNonterminal -> + var rightSidesOfNewProductions = unitProductions[leftNonterminal]!! + while (rightSidesOfNewProductions.isNotEmpty()) { + val newRightSides = mutableSetOf() + rightSidesOfNewProductions.forEach { rightNonterminal -> + productionsByNonterminals[rightNonterminal]?.forEach { + if (!productionsByNonterminals.containsKey(leftNonterminal)) { + productionsByNonterminals[leftNonterminal] = mutableSetOf() + } + if (!productionsByNonterminals[leftNonterminal]!!.contains(it)) { + productionsByNonterminals[leftNonterminal]!!.add(it) + productions.add(Production(leftNonterminal, it)) + } + } + if (unitProductions.containsKey(rightNonterminal)) { + newRightSides.addAll(unitProductions[rightNonterminal]!!) + } + } + newRightSides.remove(leftNonterminal) + newRightSides.removeAll { unitProductions[leftNonterminal]!!.contains(it) } + rightSidesOfNewProductions = newRightSides + } + } + } + + private fun removeMixOfTerminalsAndNonterminals() { + val newNonterminals = mutableMapOf() + val productionsToAdd = mutableListOf() + productions.forEach { + if (!(it.rightSide.size == 1 && it.rightSide[0] is Terminal || it.rightSide.isEmpty())) { + for (i in it.rightSide.indices) { + if (it.rightSide[i] is Terminal) { + var replacementNonterminal = newNonterminals[it.rightSide[i].getSymbol()[0]] + if (replacementNonterminal == null) { + replacementNonterminal = addNonterminal("U") + newNonterminals[it.rightSide[i].getSymbol()[0]] = replacementNonterminal + productionsToAdd.add(Production(replacementNonterminal, mutableListOf(it.rightSide[i]))) + } + it.rightSide[i] = replacementNonterminal + } + } + } + } + productions.addAll(productionsToAdd) + } + + private fun removeLongRightSides() { + val productionsToAdd = mutableListOf() + productions.forEach { + if (it.rightSide.size > 2) { + while (it.rightSide.size > 2) { + val newNonterminal = addNonterminal("Y") + productionsToAdd.add(Production(newNonterminal, mutableListOf(it.rightSide[0], it.rightSide[1]))) + it.rightSide.removeFirst() + it.rightSide[0] = newNonterminal + } + } + } + productions.addAll(productionsToAdd) + } + + fun removeInitialNonterminalFromRightSides() { + val isThereInitialNonterminalInRightSide = productions.any { production -> + production.rightSide.any { it == initialNonterminal } + } + if (isThereInitialNonterminalInRightSide) { + val newInitialNonterminal = addNonterminal("S") + productions.add(Production(newInitialNonterminal, mutableListOf(initialNonterminal))) + initialNonterminal = newInitialNonterminal + } + } + + fun convertToCNF() { + removeInitialNonterminalFromRightSides() + removeEpsilonProductions() + removeUnitProductions() + removeMixOfTerminalsAndNonterminals() + removeLongRightSides() + } + + private fun removeUnreachableNonterminals() { + val productionsByNonterminals = mutableMapOf>>() + productions.forEach { + if (!productionsByNonterminals.containsKey(it.leftSide)) { + productionsByNonterminals[it.leftSide] = mutableListOf() + } + productionsByNonterminals[it.leftSide]!!.add(it.rightSide) + } + val queue = ArrayDeque() + queue.add(initialNonterminal) + val reachableNonterminals = mutableSetOf(initialNonterminal) + while (queue.isNotEmpty()) { + val currentNonterminal = queue.removeFirst() + productionsByNonterminals[currentNonterminal]?.forEach { rightSide -> + rightSide.forEach { + if (it is Nonterminal && !reachableNonterminals.contains(it)) { + queue.add(it) + reachableNonterminals.add(it) + } + } + } + } + + nonterminals.filter { !reachableNonterminals.contains(it) }.forEach { removeNonterminal(it) } + productions.filter { production -> + var areAllNonterminalsReachable = reachableNonterminals.contains(production.leftSide) + production.rightSide.forEach { + if (it is Nonterminal && !reachableNonterminals.contains(it)) + areAllNonterminalsReachable = false + } + !areAllNonterminalsReachable + }.forEach { productions.remove(it) } + } + + private fun removeNonconvertibleNonterminals() { + val convertibleIntoTerminals = mutableSetOf() + productions.forEach { production -> + if (production.rightSide.all { it is Terminal }) { + convertibleIntoTerminals.add(production.leftSide) + } + } + var areThereNewConvertible = true + while (areThereNewConvertible) { + val newConvertible = productions.filter { production -> + var areAllNonterminalsConvertible = true + production.rightSide.forEach { + if (it is Nonterminal && !convertibleIntoTerminals.contains(it)) { + areAllNonterminalsConvertible = false + } + } + areAllNonterminalsConvertible && !convertibleIntoTerminals.contains(production.leftSide) + }.map { it.leftSide }.toList() + convertibleIntoTerminals.addAll(newConvertible) + areThereNewConvertible = newConvertible.isNotEmpty() + } + + nonterminals.filter { !convertibleIntoTerminals.contains(it) }.forEach { removeNonterminal(it) } + productions.filter { production -> + var areAllNonterminalsConvertible = convertibleIntoTerminals.contains(production.leftSide) + production.rightSide.forEach { + if (it is Nonterminal && !convertibleIntoTerminals.contains(it)) + areAllNonterminalsConvertible = false + } + !areAllNonterminalsConvertible + }.forEach { productions.remove(it) } + } + + fun removeUselessNonterminals() { + removeNonconvertibleNonterminals() + removeUnreachableNonterminals() + } +} \ No newline at end of file diff --git a/src/main/kotlin/automaton/constructor/utils/CFGUtils.kt b/src/main/kotlin/automaton/constructor/utils/CFGUtils.kt new file mode 100644 index 0000000..d88a771 --- /dev/null +++ b/src/main/kotlin/automaton/constructor/utils/CFGUtils.kt @@ -0,0 +1,55 @@ +package automaton.constructor.utils + +import automaton.constructor.controller.algorithms.HellingsTransition +import automaton.constructor.model.element.ContextFreeGrammar +import javafx.collections.ObservableList + +fun doNextIterationOfHellingsAlgo( + currentTransitions: ObservableList, + allTransitions: ObservableList, + grammar: ContextFreeGrammar +) { + currentTransitions.forEach { it.isNew.set(false) } + allTransitions.forEach { it.isNew.set(false) } + if (currentTransitions.isEmpty()) { + return + } + val mTransition = currentTransitions.removeFirst() + val rToAdd = mutableListOf() + do { + allTransitions.addAll(rToAdd) + rToAdd.clear() + allTransitions.filter { + it.target == mTransition.source + }.forEach { rTransition -> + grammar.productions.filter { + it.rightSide == mutableListOf(rTransition.nonterminal, mTransition.nonterminal) + }.forEach { production -> + if (allTransitions.none { it.isEqual(HellingsTransition(production.leftSide, rTransition.source, mTransition.target)) } && + rToAdd.none { it.isEqual(HellingsTransition(production.leftSide, rTransition.source, mTransition.target)) }) { + val newTransition = HellingsTransition(production.leftSide, rTransition.source, mTransition.target) + currentTransitions.add(newTransition) + rToAdd.add(newTransition) + } + } + } + } while (rToAdd.isNotEmpty()) + do { + allTransitions.addAll(rToAdd) + rToAdd.clear() + allTransitions.filter { + it.source == mTransition.target + }.forEach { rTransition -> + grammar.productions.filter { + it.rightSide == mutableListOf(mTransition.nonterminal, rTransition.nonterminal) + }.forEach { production -> + if (allTransitions.none { it.isEqual(HellingsTransition(production.leftSide, mTransition.source, rTransition.target)) } && + rToAdd.none { it.isEqual(HellingsTransition(production.leftSide, mTransition.source, rTransition.target)) }) { + val newTransition = HellingsTransition(production.leftSide, mTransition.source, rTransition.target) + currentTransitions.add(newTransition) + rToAdd.add(newTransition) + } + } + } + } while (rToAdd.isNotEmpty()) +} \ No newline at end of file diff --git a/src/main/kotlin/automaton/constructor/view/AdjacencyMatrixTransitionView.kt b/src/main/kotlin/automaton/constructor/view/AdjacencyMatrixTransitionView.kt new file mode 100644 index 0000000..4a30baf --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/AdjacencyMatrixTransitionView.kt @@ -0,0 +1,14 @@ +package automaton.constructor.view + +import automaton.constructor.model.element.Transition +import javafx.scene.paint.Color +import tornadofx.label + +class AdjacencyMatrixTransitionView(transition: Transition): TableTransitionView(transition) { + init { + label { + textProperty().bind(transition.propertiesTextBinding) + textFill = Color.BLACK + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/automaton/constructor/view/AutomatonBasicVertexView.kt b/src/main/kotlin/automaton/constructor/view/AutomatonBasicVertexView.kt new file mode 100644 index 0000000..31deb62 --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/AutomatonBasicVertexView.kt @@ -0,0 +1,33 @@ +package automaton.constructor.view + +import automaton.constructor.model.element.AutomatonVertex +import automaton.constructor.model.element.BuildingBlock +import automaton.constructor.model.module.hasProblems +import automaton.constructor.model.module.hasProblemsBinding +import automaton.constructor.utils.I18N +import automaton.constructor.utils.Setting +import automaton.constructor.utils.SettingGroup +import javafx.beans.property.SimpleIntegerProperty +import javafx.beans.property.SimpleStringProperty +import javafx.scene.control.CheckBox +import javafx.scene.control.TextField +import javafx.scene.paint.Color +import tornadofx.* + +open class AutomatonBasicVertexView(val vertex: AutomatonVertex) : AutomatonElementView(vertex) { + override fun getSettings() = listOf( + SettingGroup( + I18N.messages.getString("StateView.State").toProperty(), listOf( + Setting(I18N.messages.getString("StateView.Name"), + TextField().apply { textProperty().bindBidirectional(vertex.nameProperty) }), + Setting( + I18N.messages.getString("StateView.Initial"), + CheckBox().apply { selectedProperty().bindBidirectional(vertex.isInitialProperty) }) + ) + if (vertex.alwaysEffectivelyFinal) emptyList() else listOf( + Setting( + I18N.messages.getString("StateView.Final"), + CheckBox().apply { selectedProperty().bindBidirectional(vertex.isFinalProperty) }) + ) + ) + ) + super.getSettings() +} diff --git a/src/main/kotlin/automaton/constructor/view/AutomatonTableVertexView.kt b/src/main/kotlin/automaton/constructor/view/AutomatonTableVertexView.kt new file mode 100644 index 0000000..4d3df1c --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/AutomatonTableVertexView.kt @@ -0,0 +1,92 @@ +package automaton.constructor.view + +import automaton.constructor.model.element.AutomatonVertex +import automaton.constructor.model.element.BuildingBlock +import automaton.constructor.model.module.hasProblems +import automaton.constructor.model.module.hasProblemsBinding +import javafx.beans.property.SimpleIntegerProperty +import javafx.beans.property.SimpleStringProperty +import javafx.scene.paint.Color +import tornadofx.* + +class AutomatonTableVertexView(vertex: AutomatonVertex): AutomatonBasicVertexView(vertex) { + val colourProperty = SimpleStringProperty("none") + private var colour by colourProperty + init { + hbox { + label { + textProperty().bind(vertex.nameProperty) + textFill = Color.BLACK + } + val startFinalCount = SimpleIntegerProperty(0) + if (vertex.isInitial) { + startFinalCount.set(1) + } + if (vertex.isFinal) { + startFinalCount.set(startFinalCount.value + 1) + } + val startFinalLabel = label { + if (startFinalCount.value == 1) { + if (vertex.isInitial) { + text = " (start)" + } else { + text = " (final)" + } + text = if (vertex.isInitial) { + " (start)" + } else { + " (final)" + } + } + if (startFinalCount.value == 2) { + text = " (start, final)" + } + textFill = Color.BLACK + isVisible = vertex.isInitial || vertex.isFinal + } + vertex.isInitialProperty.addListener { _, _, newValue -> + if (newValue) { + startFinalCount.set(startFinalCount.value + 1) + } else { + startFinalCount.set(startFinalCount.value - 1) + } + } + vertex.isFinalProperty.addListener { _, _, newValue -> + if (newValue) { + startFinalCount.set(startFinalCount.value + 1) + } else { + startFinalCount.set(startFinalCount.value - 1) + } + } + startFinalCount.addListener { _, _, newValue -> + when (newValue) { + 0 -> startFinalLabel.isVisible = false + 1 -> { + startFinalLabel.text = if (vertex.isInitial) { + " (start)" + } else { + " (final)" + } + startFinalLabel.isVisible = true + } + else -> { + startFinalLabel.text = " (start, final)" + startFinalLabel.isVisible = true + } + } + } + } + if (vertex is BuildingBlock) { + if (vertex.subAutomaton.hasProblems) { + colour = "red" + } + vertex.subAutomaton.hasProblemsBinding.addListener(ChangeListener { _, _, newValue -> + colour = if (newValue) { + "red" + } else { + "none" + } + }) + } + } +} diff --git a/src/main/kotlin/automaton/constructor/view/AutomatonVertexView.kt b/src/main/kotlin/automaton/constructor/view/AutomatonVertexView.kt index 08d2f61..55cfd79 100644 --- a/src/main/kotlin/automaton/constructor/view/AutomatonVertexView.kt +++ b/src/main/kotlin/automaton/constructor/view/AutomatonVertexView.kt @@ -7,15 +7,12 @@ import automaton.constructor.model.element.State import automaton.constructor.model.module.hasProblems import automaton.constructor.model.module.hasProblemsBinding import automaton.constructor.utils.* -import automaton.constructor.utils.I18N.messages import automaton.constructor.view.AutomatonVertexView.ShapeType.CIRCLE import automaton.constructor.view.AutomatonVertexView.ShapeType.SQUARE import javafx.beans.property.Property import javafx.geometry.Point2D import javafx.geometry.VPos import javafx.scene.Node -import javafx.scene.control.CheckBox -import javafx.scene.control.TextField import javafx.scene.paint.Color import javafx.scene.shape.Shape import javafx.scene.text.Font.font @@ -24,7 +21,7 @@ import tornadofx.* import kotlin.math.abs import kotlin.math.max -class AutomatonVertexView(val vertex: AutomatonVertex) : AutomatonElementView(vertex) { +class AutomatonVertexView(vertex: AutomatonVertex) : AutomatonBasicVertexView(vertex) { val positionProperty: Property = vertex.position.toProperty().apply { bind(vertex.positionProperty) } val colorProperty: Property = DEFAULT_COLOR.toProperty().apply { val colorBinding = @@ -106,20 +103,6 @@ class AutomatonVertexView(val vertex: AutomatonVertex) : AutomatonElementView(ve } } - override fun getSettings() = listOf( - SettingGroup( - messages.getString("StateView.State").toProperty(), listOf( - Setting(messages.getString("StateView.Name"), - TextField().apply { textProperty().bindBidirectional(vertex.nameProperty) }), - Setting(messages.getString("StateView.Initial"), - CheckBox().apply { selectedProperty().bindBidirectional(vertex.isInitialProperty) }) - ) + if (vertex.alwaysEffectivelyFinal) emptyList() else listOf( - Setting(messages.getString("StateView.Final"), - CheckBox().apply { selectedProperty().bindBidirectional(vertex.isFinalProperty) }) - ) - ) - ) + super.getSettings() - private fun placeShape(radius: Double, op: Shape.() -> Unit) = when (shapeType) { CIRCLE -> circle { this.radius = radius diff --git a/src/main/kotlin/automaton/constructor/view/AutomatonView.kt b/src/main/kotlin/automaton/constructor/view/AutomatonView.kt index 36ef054..0363262 100644 --- a/src/main/kotlin/automaton/constructor/view/AutomatonView.kt +++ b/src/main/kotlin/automaton/constructor/view/AutomatonView.kt @@ -7,8 +7,13 @@ import automaton.constructor.model.module.problems import automaton.constructor.utils.I18N import automaton.constructor.utils.SettingsEditor import automaton.constructor.utils.customizedZoomScrollPane -import automaton.constructor.utils.nonNullObjectBinding +import automaton.constructor.view.automaton.AutomatonAdjacencyMatrixView +import automaton.constructor.view.automaton.AutomatonGraphView +import automaton.constructor.view.automaton.AutomatonTransitionTableView import javafx.beans.binding.Bindings.not +import javafx.beans.property.SimpleDoubleProperty +import javafx.scene.control.ScrollPane +import javafx.scene.control.TabPane import javafx.scene.input.KeyEvent import javafx.scene.layout.Pane import javafx.scene.paint.Color @@ -19,6 +24,12 @@ import tornadofx.* // TODO extract AutomatonDescriptionProviderView and ProblemDetectorView class AutomatonView(val automaton: Automaton, automatonViewContext: AutomatonViewContext) : Pane() { val automatonGraphView = AutomatonGraphView(automaton, automatonViewContext) + private val tablePrefWidth = SimpleDoubleProperty().also { it.bind(this.widthProperty()) } + val tablePrefHeight = SimpleDoubleProperty().also { it.bind(this.heightProperty() - 48.0) } + private val automatonTransitionTableView = AutomatonTransitionTableView( + automaton, automatonViewContext, tablePrefWidth, tablePrefHeight) + private val automatonAdjacencyMatrixView = AutomatonAdjacencyMatrixView( + automaton, automatonViewContext, tablePrefWidth, tablePrefHeight) val undoRedoController = UndoRedoController(this) init { @@ -30,13 +41,55 @@ class AutomatonView(val automaton: Automaton, automatonViewContext: AutomatonVie } event.consume() } - customizedZoomScrollPane { add(automatonGraphView) } + + automatonGraphView.controller.lastSelectedElementProperty.addListener(ChangeListener { _, _, newValue -> + automatonTransitionTableView.controller.lastSelectedElement = newValue + automatonAdjacencyMatrixView.controller.lastSelectedElement = newValue + }) + automatonTransitionTableView.controller.lastSelectedElementProperty.addListener(ChangeListener { _, _, newValue -> + automatonGraphView.controller.lastSelectedElement = newValue + automatonAdjacencyMatrixView.controller.lastSelectedElement = newValue + }) + automatonAdjacencyMatrixView.controller.lastSelectedElementProperty.addListener(ChangeListener { _, _, newValue -> + automatonGraphView.controller.lastSelectedElement = newValue + automatonTransitionTableView.controller.lastSelectedElement = newValue + }) + val graphPane = customizedZoomScrollPane { add(automatonGraphView) } + val tablePane = ScrollPane().also { it.add(automatonTransitionTableView) } + val matrixPane = ScrollPane().also { it.add(automatonAdjacencyMatrixView) } + tabpane { + tabClosingPolicy = TabPane.TabClosingPolicy.UNAVAILABLE + tab(I18N.messages.getString("AutomatonView.Graph")) { + add(graphPane) + } + val tableTab = tab(I18N.messages.getString("AutomatonView.Table")) { + add(tablePane) + } + val matrixTab = tab(I18N.messages.getString("AutomatonView.Matrix")) { + add(matrixPane) + } + selectionModel.selectedItemProperty().addListener { _, _, newValue -> + if (newValue == tableTab) { + automatonTransitionTableView.enableProperResizing() + } + if (newValue == matrixTab) { + automatonAdjacencyMatrixView.enableProperResizing() + } + } + } val settingsEditor = SettingsEditor().apply { - settingsProperty.bind(automatonGraphView.controller.lastSelectedElementProperty.nonNullObjectBinding { - it?.getSettings() + automatonGraphView.controller.lastSelectedElementProperty.addListener(ChangeListener { _, _, newValue -> + settingsProperty.set(newValue?.getSettings()) + }) + automatonTransitionTableView.controller.lastSelectedElementProperty.addListener(ChangeListener { _, _, newValue -> + settingsProperty.set(newValue?.getSettings()) + }) + automatonAdjacencyMatrixView.controller.lastSelectedElementProperty.addListener(ChangeListener { _, _, newValue -> + settingsProperty.set(newValue?.getSettings()) }) editingDisabledProperty.bind(not(automaton.allowsModificationsByUserProperty)) visibleWhen(automaton.isOutputOfTransformationProperty.booleanBinding { it == null }) + layoutY = 23.8 } add(settingsEditor) label { diff --git a/src/main/kotlin/automaton/constructor/view/MainWindow.kt b/src/main/kotlin/automaton/constructor/view/MainWindow.kt index ac41706..acdc1af 100644 --- a/src/main/kotlin/automaton/constructor/view/MainWindow.kt +++ b/src/main/kotlin/automaton/constructor/view/MainWindow.kt @@ -51,6 +51,10 @@ class MainWindow( TestsController(it) } private val testsController: TestsController by testsControllerBinding + private val algorithmsControllerBinding = fileController.openedAutomatonProperty.nonNullObjectBinding { + AlgorithmsController(it) + } + private val algorithmsController by algorithmsControllerBinding override val root = borderpane { top = menubar { @@ -62,7 +66,9 @@ class MainWindow( fileController.onOpen() } item(I18N.messages.getString("MainView.Examples")).action { - find(mapOf(ExamplesView::fileController to fileController)).openModal() + find(mapOf(ExamplesView::fileController to fileController)).apply { + title = I18N.automatonExamples.getString("ExamplesFragment.Title") + }.openModal() } shortcutItem(I18N.messages.getString("MainView.File.Save"), "Shortcut+S") { fileController.onSave() @@ -154,6 +160,18 @@ class MainWindow( testsController.createTests() } } + menu(I18N.messages.getString("MainView.Algorithms")) { + menu(I18N.messages.getString("MainView.Algorithms.FiniteAutomaton")) { + item(I18N.messages.getString("MainView.Algorithms.FiniteAutomaton.Hellings")).action { + algorithmsController.executeHellingsAlgo() + } + } + menu(I18N.messages.getString("MainView.Algorithms.PushdownAutomaton")) { + item(I18N.messages.getString("MainView.Algorithms.PushdownAutomaton.CFG")).action { + algorithmsController.convertToCFG() + } + } + } menu(I18N.messages.getString("MainView.Settings")) { menu(I18N.messages.getString("MainView.Language")) { settingsController.availableLocales.forEach { locale -> diff --git a/src/main/kotlin/automaton/constructor/view/TableTransitionView.kt b/src/main/kotlin/automaton/constructor/view/TableTransitionView.kt new file mode 100644 index 0000000..7fd1c07 --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/TableTransitionView.kt @@ -0,0 +1,25 @@ +package automaton.constructor.view + +import automaton.constructor.model.element.Transition +import automaton.constructor.utils.I18N +import automaton.constructor.utils.Setting +import automaton.constructor.utils.SettingGroup +import automaton.constructor.utils.createUnmodifiableSettingControl +import tornadofx.toProperty + +open class TableTransitionView(val transition: Transition): AutomatonElementView(transition) { + override fun getSettings() = listOf( + SettingGroup( + I18N.messages.getString("TransitionView.Transition").toProperty(), listOf( + Setting( + I18N.messages.getString("TransitionView.Source"), + createUnmodifiableSettingControl(transition.source.nameProperty) + ), + Setting( + I18N.messages.getString("TransitionView.Target"), + createUnmodifiableSettingControl(transition.target.nameProperty) + ) + ) + ) + ) + super.getSettings() +} \ No newline at end of file diff --git a/src/main/kotlin/automaton/constructor/view/TransitionTableTransitionView.kt b/src/main/kotlin/automaton/constructor/view/TransitionTableTransitionView.kt new file mode 100644 index 0000000..199eb47 --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/TransitionTableTransitionView.kt @@ -0,0 +1,25 @@ +package automaton.constructor.view + +import automaton.constructor.model.element.Transition +import javafx.scene.paint.Color +import tornadofx.* + +class TransitionTableTransitionView(transition: Transition): TableTransitionView(transition) { + init { + hbox { + label { + textProperty().bind(transition.filtersTextBinding) + textFill = Color.BLACK + } + if (transition.sideEffectsText.isNotEmpty()) { + label("→") { + textFill = Color.BLACK + } + label { + textProperty().bind(transition.sideEffectsTextBinding) + textFill = Color.BLACK + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/automaton/constructor/view/TransitionView.kt b/src/main/kotlin/automaton/constructor/view/TransitionView.kt index 86d4588..7b1e162 100644 --- a/src/main/kotlin/automaton/constructor/view/TransitionView.kt +++ b/src/main/kotlin/automaton/constructor/view/TransitionView.kt @@ -2,7 +2,6 @@ package automaton.constructor.view import automaton.constructor.model.element.Transition import automaton.constructor.utils.* -import automaton.constructor.utils.I18N.messages import javafx.beans.binding.Binding import javafx.beans.property.DoubleProperty import javafx.scene.paint.Color @@ -24,13 +23,13 @@ class TransitionView( override fun getSettings() = listOf( SettingGroup( - messages.getString("TransitionView.Transition").toProperty(), listOf( + I18N.messages.getString("TransitionView.Transition").toProperty(), listOf( Setting( - messages.getString("TransitionView.Source"), + I18N.messages.getString("TransitionView.Source"), createUnmodifiableSettingControl(transition.source.nameProperty) ), Setting( - messages.getString("TransitionView.Target"), + I18N.messages.getString("TransitionView.Target"), createUnmodifiableSettingControl(transition.target.nameProperty) ) ) diff --git a/src/main/kotlin/automaton/constructor/view/algorithms/CFGView.kt b/src/main/kotlin/automaton/constructor/view/algorithms/CFGView.kt new file mode 100644 index 0000000..36c129f --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/algorithms/CFGView.kt @@ -0,0 +1,93 @@ +package automaton.constructor.view.algorithms + +import automaton.constructor.model.element.CFGSymbol +import automaton.constructor.model.element.ContextFreeGrammar +import automaton.constructor.model.element.Nonterminal +import automaton.constructor.model.element.Production +import automaton.constructor.utils.I18N +import javafx.geometry.Insets +import javafx.scene.control.TableCell +import javafx.scene.control.TableColumn +import javafx.scene.control.cell.PropertyValueFactory +import javafx.scene.layout.HBox +import javafx.scene.text.Font +import tornadofx.* + +class LeftSideCell: TableCell() { + override fun updateItem(item: Nonterminal?, empty: Boolean) { + super.updateItem(item, empty) + graphic = if (item != null) { + CFGView.getLabelsForNonterminal(item) + } else { + null + } + } +} + +class RightSideCell: TableCell>() { + override fun updateItem(item: List?, empty: Boolean) { + super.updateItem(item, empty) + graphic = if (item != null) { + HBox().apply { + item.forEach { + if (it is Nonterminal) { + add(CFGView.getLabelsForNonterminal(it)) + } else { + add(label(it.getSymbol())) + } + } + if (item.isEmpty()) { + add(label("ε")) + } + } + } else { + null + } + } +} + +class CFGView: Fragment() { + val grammar: ContextFreeGrammar by param() + private val productionsTableView = tableview(grammar.productions.toObservable()) + private val leftSideColumn = TableColumn(I18N.messages.getString("CFGView.LeftSide")) + private val rightSideColumn = TableColumn>(I18N.messages.getString("CFGView.RightSide")) + + init { + leftSideColumn.cellValueFactory = PropertyValueFactory("leftSide") + leftSideColumn.setCellFactory { LeftSideCell() } + rightSideColumn.cellValueFactory = PropertyValueFactory("rightSide") + rightSideColumn.setCellFactory { RightSideCell() } + leftSideColumn.minWidth = 190.0 + rightSideColumn.minWidth = 190.0 + productionsTableView.columns.addAll(leftSideColumn, rightSideColumn) + if (productionsTableView.items.isEmpty()) { + productionsTableView.items.add(Production(grammar.initialNonterminal, mutableListOf())) + } + } + + override val root = vbox { + label(I18N.messages.getString("CFGView.Note")) { + padding = Insets(5.0, 5.0, 5.0, 5.0) + } + hbox { + label(I18N.messages.getString("CFGView.InitialNonterminal") + " = ") { + padding = Insets(5.0, 0.0, 5.0, 5.0) + } + add(getLabelsForNonterminal(grammar.initialNonterminal).apply { + padding = Insets(5.0, 5.0, 5.0, 0.0) + }) + } + add(productionsTableView) + } + + companion object { + fun getLabelsForNonterminal(nonterminal: Nonterminal): HBox { + return HBox().apply { + this.label(nonterminal.value[0].toString()) + this.label(nonterminal.value.subSequence(1, nonterminal.value.length).toString()) { + font = Font(9.0) + } + } + } + } +} diff --git a/src/main/kotlin/automaton/constructor/view/algorithms/HellingsAlgoExecutionView.kt b/src/main/kotlin/automaton/constructor/view/algorithms/HellingsAlgoExecutionView.kt new file mode 100644 index 0000000..30c220d --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/algorithms/HellingsAlgoExecutionView.kt @@ -0,0 +1,72 @@ +package automaton.constructor.view.algorithms + +import automaton.constructor.controller.algorithms.HellingsTransition +import automaton.constructor.utils.I18N +import javafx.beans.property.SimpleBooleanProperty +import javafx.collections.ObservableList +import javafx.geometry.Insets +import javafx.scene.control.Button +import javafx.scene.control.ListCell +import javafx.scene.control.ListView +import javafx.scene.paint.Color +import tornadofx.* + +class HellingsTransitionCell: ListCell() { + private val isNew = SimpleBooleanProperty() + override fun updateItem(item: HellingsTransition?, empty: Boolean) { + super.updateItem(item, empty) + if (item != null) { + isNew.bind(item.isNew) + this.style = if (item.isNew.value) { + "-fx-background-color: aqua;" + } else { + "-fx-background-color: white;" + } + isNew.addListener(ChangeListener { _, _, newValue -> + this.style = if (newValue) { + "-fx-background-color: aqua;" + } else { + "-fx-background-color: white;" + } + }) + graphic = label(item.nonterminal.value + ", " + item.source.name + ", " + item.target.name) { + textFill = Color.BLACK + } + } else { + this.style = "-fx-background-color: white;" + graphic = null + } + } +} + +class HellingsAlgoExecutionView: Fragment() { + val currentTransitions: ObservableList by param() + val allTransitions: ObservableList by param() + private val currentTransitionsListView = ListView(currentTransitions).apply { this.setCellFactory { HellingsTransitionCell() } } + private val allTransitionsListView = ListView(allTransitions).apply { this.setCellFactory { HellingsTransitionCell() } } + val nextIterationButton = Button(I18N.messages.getString("HellingsAlgorithm.Execution.NextIteration")) + + override val root = vbox { + label (I18N.messages.getString("HellingsAlgorithm.Execution.Description")) { + padding = Insets(5.0, 5.0, 5.0, 5.0) + } + hbox { + vbox { + label(I18N.messages.getString("HellingsAlgorithm.Execution.CurrentTransitions")) { + padding = Insets(0.0, 0.0, 0.0, 5.0) + } + add(currentTransitionsListView) + } + vbox { + label(I18N.messages.getString("HellingsAlgorithm.Execution.AllTransitions")) { + padding = Insets(0.0, 0.0, 0.0, 5.0) + } + add(allTransitionsListView) + } + } + hbox { + add(nextIterationButton) + padding = Insets(5.0, 5.0, 5.0, 5.0) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/automaton/constructor/view/algorithms/HellingsAlgoGrammarView.kt b/src/main/kotlin/automaton/constructor/view/algorithms/HellingsAlgoGrammarView.kt new file mode 100644 index 0000000..dbc7313 --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/algorithms/HellingsAlgoGrammarView.kt @@ -0,0 +1,255 @@ +package automaton.constructor.view.algorithms + +import automaton.constructor.controller.algorithms.HellingsAlgoController +import automaton.constructor.model.element.* +import automaton.constructor.utils.I18N +import javafx.beans.property.SimpleIntegerProperty +import javafx.beans.property.SimpleObjectProperty +import javafx.beans.property.SimpleStringProperty +import javafx.collections.ObservableList +import javafx.geometry.Insets +import javafx.scene.control.* +import javafx.scene.control.cell.PropertyValueFactory +import javafx.scene.layout.HBox +import tornadofx.* + +open class EditableCFGSymbol(open val cfgSymbol: CFGSymbol, var wasEdited: Boolean = false) + +class EditableNonterminal( + nonterminal: Nonterminal, wasEdited: Boolean = false +): EditableCFGSymbol(nonterminal, wasEdited) { + override val cfgSymbol: Nonterminal = nonterminal +} + +class EditableProduction(val leftSide: EditableNonterminal, val rightSide: SimpleObjectProperty>) + +class HellingsLeftSideCell( + private val blankFieldsCount: SimpleIntegerProperty, + private val indexesOfSelectedProductions: MutableSet +): TableCell() { + override fun updateItem(item: EditableNonterminal?, empty: Boolean) { + super.updateItem(item, empty) + graphic = if (item != null) { + HBox().apply { + checkbox().apply { + action { + if (isSelected) { + indexesOfSelectedProductions.add(index) + } else { + indexesOfSelectedProductions.remove(index) + } + } + } + textfield { + promptText = "N" + if (item.wasEdited) { + text = item.cfgSymbol.value + } + textProperty().addListener { _, _, newValue -> + item.cfgSymbol.value = newValue + item.wasEdited = true + if (newValue.isEmpty()) { + blankFieldsCount.set(blankFieldsCount.value + 1) + } else { + blankFieldsCount.set(blankFieldsCount.value - 1) + } + } + } + spacing = 3.0 + } + } else { + null + } + } +} + +class HellingsRightSideCell( + private val grammar: ContextFreeGrammar, + private val productions: ObservableList, + private val blankFieldsCount: SimpleIntegerProperty +): TableCell>() { + private fun getTextField(symbol: EditableCFGSymbol): TextField { + return TextField().apply { + if (symbol.wasEdited) { + text = symbol.cfgSymbol.getSymbol() + } + if (symbol.cfgSymbol is Terminal) { + promptText = "T" + textProperty().addListener { _, _, newValue -> + if (newValue.length > 1) { + this.textProperty().set(newValue[0].toString()) + } + if (newValue.isNotEmpty()) { + (symbol.cfgSymbol as Terminal).value = newValue[0] + } + } + } + if (symbol.cfgSymbol is Nonterminal) { + promptText = "N" + textProperty().addListener { _, _, newValue -> + (symbol.cfgSymbol as Nonterminal).value = newValue + } + } + prefWidth = 73.0 + contextMenu = contextmenu { + item(I18N.messages.getString("HellingsAlgorithm.Grammar.Delete")).setOnAction { + val productionRightSide = productions[index].rightSide.value + if (text.isEmpty()) { + blankFieldsCount.set(blankFieldsCount.value - 1) + } + productions[index].rightSide.set((productionRightSide - symbol).toMutableList()) + } + } + textProperty().addListener { _, _, newValue -> + symbol.wasEdited = true + if (newValue.isEmpty()) { + blankFieldsCount.set(blankFieldsCount.value + 1) + } else { + blankFieldsCount.set(blankFieldsCount.value - 1) + } + } + } + } + + override fun updateItem(item: MutableList?, empty: Boolean) { + super.updateItem(item, empty) + graphic = if (item != null) { + HBox().apply { + hbox(3) { + item.forEachIndexed { index, _ -> + add(getTextField(item[index])) + } + padding = Insets(0.0, 3.0, 0.0, 0.0) + } + add(ChoiceBox().apply { + items = observableListOf( + I18N.messages.getString("HellingsAlgorithm.Grammar.Terminal"), + I18N.messages.getString("HellingsAlgorithm.Grammar.Nonterminal") + ) + value = "+" + setOnAction { + val productionRightSide = productions[index].rightSide.value + if (value == items[0]) { + productions[index].rightSide.set(( + productionRightSide + EditableCFGSymbol(Terminal('T'))).toMutableList()) + } + if (value == items[1]) { + productions[index].rightSide.set(( + productionRightSide + EditableCFGSymbol(grammar.addNonterminal())).toMutableList()) + } + blankFieldsCount.set(blankFieldsCount.value + 1) + } + prefWidth = 55.0 + }) + } + } else { + null + } + } +} + +class HellingsAlgoGrammarView: Fragment() { + val controller: HellingsAlgoController by param() + private val grammar = ContextFreeGrammar() + private val productions = observableListOf() + private val initialNonterminalValue = SimpleStringProperty() + private var blankFieldsCount = SimpleIntegerProperty(1) + override val root = borderpane { + top = vbox(5.0) { + label(I18N.messages.getString("HellingsAlgorithm.Grammar.Info")) + hbox { + label(I18N.messages.getString("CFGView.InitialNonterminal") + " = ") { + padding = Insets(4.0, 0.0, 0.0, 0.0) + } + textfield { + promptText = "N" + textProperty().bindBidirectional(initialNonterminalValue) + textProperty().addListener { _, _, newValue -> + if (newValue.isEmpty()) { + blankFieldsCount.set(blankFieldsCount.value + 1) + } else { + blankFieldsCount.set(blankFieldsCount.value - 1) + } + } + prefWidth = 73.0 + } + } + padding = Insets(5.0, 5.0, 5.0, 5.0) + } + + val grammarTableView = tableview(productions) + val indexesOfSelectedProductions = mutableSetOf() + center = grammarTableView + val leftSideColumn = TableColumn(I18N.messages.getString("CFGView.LeftSide")) + val rightSideColumn = TableColumn>(I18N.messages.getString("CFGView.RightSide")) + leftSideColumn.cellValueFactory = PropertyValueFactory("leftSide") + leftSideColumn.setCellFactory { HellingsLeftSideCell(blankFieldsCount, indexesOfSelectedProductions) } + rightSideColumn.setCellValueFactory { p0 -> + p0!!.value.rightSide + } + rightSideColumn.setCellFactory { HellingsRightSideCell(grammar, productions, blankFieldsCount) } + leftSideColumn.prefWidth = 80.0 + rightSideColumn.prefWidth = 430.0 + grammarTableView.columns.addAll(leftSideColumn, rightSideColumn) + + bottom = borderpane { + left = hbox(5) { + button(I18N.messages.getString("HellingsAlgorithm.Grammar.Add")).action { + productions.add(EditableProduction(EditableNonterminal( + grammar.addNonterminal()), SimpleObjectProperty(mutableListOf()))) + blankFieldsCount.set(blankFieldsCount.value + 1) + } + button(I18N.messages.getString("HellingsAlgorithm.Grammar.Delete")).action { + val productionsToDelete = indexesOfSelectedProductions.map { productions[it] } + productionsToDelete.forEach { production -> + val productionBlankFieldsCount = (production.rightSide.value + production.leftSide).count { + !it.wasEdited || it.cfgSymbol.getSymbol().isEmpty() + } + blankFieldsCount -= productionBlankFieldsCount + } + productions.removeAll(productionsToDelete) + indexesOfSelectedProductions.clear() + } + button(I18N.messages.getString("HellingsAlgorithm.Grammar.OK")).action { + if (blankFieldsCount.value > 0 || productions.isEmpty()) { + error(I18N.messages.getString("HellingsAlgorithm.Grammar.Error")) + } else { + controller.grammar = fixGrammar() + controller.execute() + close() + } + } + padding = Insets(5.0, 5.0, 5.0, 5.0) + } + } + + prefWidth = 510.0 + } + + private fun fixGrammar(): ContextFreeGrammar { + val initialNonterminal = Nonterminal(initialNonterminalValue.value) + val fixedGrammar = ContextFreeGrammar(initialNonterminal) + productions.forEach { production -> + fixedGrammar.addNonterminal(production.leftSide.cfgSymbol) + production.rightSide.value.forEach { + if (it.cfgSymbol is Nonterminal) { + fixedGrammar.addNonterminal(it.cfgSymbol as Nonterminal) + } + } + } + productions.forEach { production -> + val newLeftSide = fixedGrammar.nonterminals.find { it.value == production.leftSide.cfgSymbol.value }!! + val newRightSide = mutableListOf() + production.rightSide.value.forEach { symbol -> + if (symbol.cfgSymbol is Nonterminal) { + newRightSide.add(fixedGrammar.nonterminals.find { it.value == (symbol.cfgSymbol as Nonterminal).value }!!) + } else { + newRightSide.add(symbol.cfgSymbol) + } + } + fixedGrammar.productions.add(Production(newLeftSide, newRightSide)) + } + fixedGrammar.convertToCNF() + return fixedGrammar + } +} diff --git a/src/main/kotlin/automaton/constructor/view/automaton/AutomatonAdjacencyMatrixView.kt b/src/main/kotlin/automaton/constructor/view/automaton/AutomatonAdjacencyMatrixView.kt new file mode 100644 index 0000000..ce1bdcb --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/automaton/AutomatonAdjacencyMatrixView.kt @@ -0,0 +1,120 @@ +package automaton.constructor.view.automaton + +import automaton.constructor.model.automaton.Automaton +import automaton.constructor.model.element.AutomatonVertex +import automaton.constructor.model.element.Transition +import automaton.constructor.utils.I18N +import automaton.constructor.view.AdjacencyMatrixTransitionView +import automaton.constructor.view.AutomatonViewContext +import javafx.beans.property.ReadOnlyDoubleProperty +import javafx.beans.property.SimpleObjectProperty +import javafx.collections.ListChangeListener +import javafx.collections.SetChangeListener +import javafx.scene.control.TableColumn +import kotlin.math.max + +class AdjacencyMatrixTransitionMap( + val source: AutomatonVertex, + val transitions: MutableMap>> = mutableMapOf() +): TransitionMap + +class AutomatonAdjacencyMatrixView( + automaton: Automaton, automatonViewContext: AutomatonViewContext, + tablePrefWidth: ReadOnlyDoubleProperty, tablePrefHeight: ReadOnlyDoubleProperty +): AutomatonTableView( + automaton, automatonViewContext, tablePrefWidth, tablePrefHeight +) { + private val transitionsColumns = TableColumn>( + I18N.messages.getString("AutomatonAdjacencyMatrixView.Targets")) + init { + automaton.vertices.addListener(SetChangeListener { + if (it.wasAdded()) { + registerVertex(it.elementAdded) + } + }) + transitionsByVertices.addListener(ListChangeListener { + while (it.next()) { + if (it.wasAdded()) { + val addedMap = it.addedSubList.first() + automaton.vertices.forEach { addedMap.transitions[it] = SimpleObjectProperty(listOf()) } + } + } + }) + transitionsByVertices.forEach { map -> + automaton.vertices.forEach { map.transitions[it] = SimpleObjectProperty(listOf()) } + } + sourceColumn.text = I18N.messages.getString("AutomatonAdjacencyMatrixView.State") + sourceColumn.minWidth = 200.0 + automaton.vertices.forEach { registerVertex(it) } + automaton.transitions.forEach { registerTransition(it) } + table.prefWidthProperty().addListener { _, _, _ -> + resizeColumns() + } + table.columns.add(transitionsColumns) + } + + private fun registerVertex(vertex: AutomatonVertex) { + if (transitionsByVertices.none { it.source == vertex }) { + transitionsByVertices.add(AdjacencyMatrixTransitionMap(vertex)) + val newColumn = TableColumn>(vertex.name) + newColumn.textProperty().bind(vertex.nameProperty) + registerColumn(newColumn) + } + } + + override fun unregisterVertex(vertex: AutomatonVertex) { + transitionsByVertices.removeAll { it.source == vertex } + unregisterColumn( + transitionsColumns.columns.find { it.text == vertex.name } as TableColumn>) + } + + override fun registerTransition(transition: Transition) { + val transitionView = AdjacencyMatrixTransitionView(transition) + controller.registerAutomatonElementView(transitionView) + transitionToViewMap[transition] = transitionView + transitionsByVertices.find { it.source == transition.source }.apply { + val list = this!!.transitions[transition.target]!!.value + this.transitions[transition.target]!!.set(list + transition) + } + } + + override fun unregisterTransition(transition: Transition) { + transitionsByVertices.find { it.source == transition.source }.apply { + val list = this!!.transitions[transition.target]!!.value + this.transitions[transition.target]!!.set(list - transition) + } + transitionToViewMap.remove(transition) + } + + private fun registerColumn(addedColumn: TableColumn>) { + val vertex = automaton.vertices.find { it.name == addedColumn.text }!! + transitionsByVertices.forEach { + it.transitions[vertex] = SimpleObjectProperty(listOf()) + } + addedColumn.setCellValueFactory { p0 -> + p0!!.value.transitions[vertex]!! + } + addedColumn.setCellFactory { TransitionsCell(transitionToViewMap) } + addedColumn.minWidth = 200.0 + if (transitionsColumns.columns.none { it.text == addedColumn.text }) { + transitionsColumns.columns.add(addedColumn) + } + resizeColumns() + } + + private fun unregisterColumn(removedColumn: TableColumn>) { + transitionsColumns.columns.remove(removedColumn) + resizeColumns() + } + + private fun resizeColumns() { + if (transitionsColumns.columns.isEmpty()) { + table.columns.forEach { it.prefWidth = table.prefWidth / 2 } + } else { + sourceColumn.prefWidth = max(sourceColumn.minWidth, table.prefWidth / (transitionsColumns.columns.size + 1)) + transitionsColumns.columns.forEach { + it.prefWidth = max(it.minWidth, table.prefWidth / (transitionsColumns.columns.size + 1)) + } + } + } +} diff --git a/src/main/kotlin/automaton/constructor/view/AutomatonGraphView.kt b/src/main/kotlin/automaton/constructor/view/automaton/AutomatonGraphView.kt similarity index 90% rename from src/main/kotlin/automaton/constructor/view/AutomatonGraphView.kt rename to src/main/kotlin/automaton/constructor/view/automaton/AutomatonGraphView.kt index 5cf8a2d..6464d90 100644 --- a/src/main/kotlin/automaton/constructor/view/AutomatonGraphView.kt +++ b/src/main/kotlin/automaton/constructor/view/automaton/AutomatonGraphView.kt @@ -1,4 +1,4 @@ -package automaton.constructor.view +package automaton.constructor.view.automaton import automaton.constructor.controller.AutomatonGraphController import automaton.constructor.model.automaton.Automaton @@ -9,6 +9,9 @@ import automaton.constructor.model.element.BuildingBlock import automaton.constructor.model.element.State import automaton.constructor.utils.hoverableTooltip import automaton.constructor.utils.subPane +import automaton.constructor.view.AutomatonEdgeView +import automaton.constructor.view.AutomatonVertexView +import automaton.constructor.view.AutomatonViewContext import automaton.constructor.view.module.executor.executionStatesTooltip import javafx.collections.MapChangeListener import javafx.collections.SetChangeListener @@ -17,7 +20,7 @@ import tornadofx.add import tornadofx.fitToParentSize import kotlin.collections.set -class AutomatonGraphView(val automaton: Automaton, val automatonViewContext: AutomatonViewContext) : Pane() { +class AutomatonGraphView(val automaton: Automaton, private val automatonViewContext: AutomatonViewContext) : Pane() { private val edgePane = subPane() val edgeViews = mutableMapOf, AutomatonEdgeView>() val vertexToViewMap = mutableMapOf() @@ -55,6 +58,7 @@ class AutomatonGraphView(val automaton: Automaton, val automatonViewContext: Aut maxHeight = this@AutomatonGraphView.scene.window.height / 1.5 val subAutomatonView = automatonViewContext.getAutomatonView(vertex.subAutomaton) add(subAutomatonView) + subAutomatonView.tablePrefHeight.bind(subAutomatonView.heightProperty()) subAutomatonView.fitToParentSize() } } @@ -89,7 +93,7 @@ class AutomatonGraphView(val automaton: Automaton, val automatonViewContext: Aut edgeView.oppositeEdge?.oppositeEdge = null } - fun transitionLayoutBounds() = edgeViews.values.flatMap { it.transitionViews }.associate { + fun transitionLayoutBounds() = edgeViews.values.flatMap { it.transitionViews }.associate { it.transition to it.layoutBounds } diff --git a/src/main/kotlin/automaton/constructor/view/automaton/AutomatonTableView.kt b/src/main/kotlin/automaton/constructor/view/automaton/AutomatonTableView.kt new file mode 100644 index 0000000..477d8fb --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/automaton/AutomatonTableView.kt @@ -0,0 +1,245 @@ +package automaton.constructor.view.automaton + +import automaton.constructor.controller.AutomatonRepresentationController +import automaton.constructor.model.automaton.Automaton +import automaton.constructor.model.automaton.allowsBuildingBlocks +import automaton.constructor.model.data.addContent +import automaton.constructor.model.element.AutomatonVertex +import automaton.constructor.model.element.BuildingBlock +import automaton.constructor.model.element.Transition +import automaton.constructor.utils.I18N +import automaton.constructor.utils.addOnSuccess +import automaton.constructor.utils.hoverableTooltip +import automaton.constructor.view.AutomatonTableVertexView +import automaton.constructor.view.AutomatonViewContext +import automaton.constructor.view.TableTransitionView +import javafx.beans.property.ReadOnlyDoubleProperty +import javafx.beans.property.SimpleObjectProperty +import javafx.beans.property.SimpleStringProperty +import javafx.collections.SetChangeListener +import javafx.geometry.Insets +import javafx.scene.control.ListCell +import javafx.scene.control.TableCell +import javafx.scene.control.TableColumn +import javafx.scene.control.TableView +import javafx.scene.control.cell.PropertyValueFactory +import javafx.scene.input.MouseButton +import javafx.scene.layout.Pane +import javafx.scene.layout.VBox +import javafx.scene.paint.Color +import tornadofx.* +import kotlin.random.Random + +interface TransitionMap + +class VertexCell( + private val table: AutomatonTableView +): TableCell() { + private val colourProperty = SimpleStringProperty("") + private var colour by colourProperty + + private fun registerVertex(vertex: AutomatonVertex): AutomatonTableVertexView { + val vertexView = AutomatonTableVertexView(vertex) + table.controller.registerAutomatonElementView(vertexView) + if (vertex is BuildingBlock) { + vertexView.hoverableTooltip(stopManagingOnInteraction = true) { + Pane().apply { + minWidth = table.scene.window.width / 1.5 + minHeight = table.scene.window.height / 1.5 + maxWidth = table.scene.window.width / 1.5 + maxHeight = table.scene.window.height / 1.5 + val subAutomatonView = table.automatonViewContext.getAutomatonView(vertex.subAutomaton) + add(subAutomatonView) + subAutomatonView.fitToParentSize() + } + } + } + return vertexView + } + + override fun updateItem(item: AutomatonVertex?, empty: Boolean) { + super.updateItem(item, empty) + if (item != null) { + val vertexView = registerVertex(item) + colourProperty.bind(vertexView.colourProperty) + this.style = "-fx-background-color: ${colour};" + colourProperty.addListener(ChangeListener { _, _, newValue -> + this.style = "-fx-background-color: ${newValue};" + }) + graphic = vertexView + } else { + this.style = "-fx-background-color: none;" + graphic = null + } + } +} + +class TransitionsCell( + private val transitionToViewMap: MutableMap +): TableCell>() { + override fun updateItem(item: List?, empty: Boolean) { + super.updateItem(item, empty) + graphic = if (item != null) { + VBox().apply { + item.forEach { + add(transitionToViewMap[it]!!) + } + } + } else { + null + } + } +} + +class NewTransitionPopup: Fragment() { + val automaton: Automaton by param() + val source = SimpleObjectProperty() + val target = SimpleObjectProperty() + + override val root = vbox(5.0) { + label(I18N.messages.getString("NewTransitionPopup.Question")) + borderpane { + class VertexCell: ListCell() { + override fun updateItem(item: AutomatonVertex?, empty: Boolean) { + super.updateItem(item, empty) + graphic = if (item != null) { + label(item.name) { + textFill = Color.BLACK + } + } else { + null + } + } + } + left = hbox(5.0) { + label(I18N.messages.getString("NewTransitionPopup.Source")) + val sourceBox = combobox(source, automaton.vertices.toList()) + sourceBox.setCellFactory { VertexCell() } + sourceBox.buttonCell = VertexCell() + } + right = hbox(2.0) { + label(I18N.messages.getString("NewTransitionPopup.Target")) + val targetBox = combobox(target, automaton.vertices.toList()) + targetBox.setCellFactory { VertexCell() } + targetBox.buttonCell = VertexCell() + } + } + button(I18N.messages.getString("NewTransitionPopup.Add")) { + action { + automaton.addTransition(source.value, target.value) + } + } + padding = Insets(5.0, 5.0, 5.0, 5.0) + minWidth = 280.0 + } +} + +abstract class AutomatonTableView( + val automaton: Automaton, + val automatonViewContext: AutomatonViewContext, + private val tablePrefWidth: ReadOnlyDoubleProperty, + private val tablePrefHeight: ReadOnlyDoubleProperty +): Pane() { + val transitionsByVertices = observableListOf() + val table = TableView(transitionsByVertices) + val sourceColumn = TableColumn() + val controller = AutomatonRepresentationController(automaton, automatonViewContext) + val transitionToViewMap = mutableMapOf() + init { + automaton.vertices.addListener(SetChangeListener { + if (it.wasRemoved()) { + unregisterVertex(it.elementRemoved) + } + }) + automaton.transitions.addListener(SetChangeListener { + if (it.wasAdded()) { + registerTransition(it.elementAdded) + } + if (it.wasRemoved()) { + unregisterTransition(it.elementRemoved) + } + }) + + vbox { + add(table) + hbox { + button(I18N.messages.getString("AutomatonTableView.AddState")) { + action { + if (automaton.allowsModificationsByUser) { + automaton.addState( + position = javafx.geometry.Point2D( + 500_000.0 + Random.nextDouble(-500.0, 500.0), + 500_000.0 + Random.nextDouble(-500.0, 500.0) + ) + ) + } + } + } + if (automaton.allowsBuildingBlocks) { + button(I18N.messages.getString("AutomatonTableView.AddBuildingBlock")) { + action { + if (automaton.allowsModificationsByUser) { + automaton.addBuildingBlock() + } + } + } + button(I18N.messages.getString("AutomatonTableView.CopyBuildingBlock")) { + action { + if (!automaton.allowsModificationsByUser) return@action + val file = automatonViewContext.fileController.chooseFile( + I18N.messages.getString("MainView.File.Open"), + FileChooserMode.Single + ) ?: return@action + automatonViewContext.fileController.loadAsync(file) addOnSuccess { (type, vertices, transitions, edges) -> + if (type != automaton.getTypeData()) error( + I18N.messages.getString("AutomatonGraphController.BuildingBlockLoadingFailed"), + I18N.messages.getString("AutomatonGraphController.IncompatibleAutomatonType"), + owner = automatonViewContext.uiComponent.currentWindow + ) + else { + automaton.addBuildingBlock().apply { + subAutomaton.addContent(vertices, transitions, edges) + name = file.nameWithoutExtension + } + } + } + } + } + } + button(I18N.messages.getString("AutomatonTableView.AddTransition")) { + action { + if (automaton.allowsModificationsByUser) { + val scope = Scope() + val newTransitionWindow = + find(scope, mapOf(NewTransitionPopup::automaton to automaton)) + newTransitionWindow.title = I18N.messages.getString("NewTransitionPopup.Title") + newTransitionWindow.openWindow() + } + } + } + } + } + + sourceColumn.cellValueFactory = PropertyValueFactory("source") + sourceColumn.setCellFactory { VertexCell(this) } + table.columns.add(sourceColumn) + table.setOnMouseClicked { + if (it.button == MouseButton.PRIMARY) controller.clearSelection() + } + + table.style { + fontSize = 16.0.px + } + } + + abstract fun unregisterVertex(vertex: AutomatonVertex) + + abstract fun registerTransition(transition: Transition) + + abstract fun unregisterTransition(transition: Transition) + + fun enableProperResizing() { + table.prefWidthProperty().bind(tablePrefWidth) + table.prefHeightProperty().bind(tablePrefHeight) + } +} diff --git a/src/main/kotlin/automaton/constructor/view/automaton/AutomatonTransitionTableView.kt b/src/main/kotlin/automaton/constructor/view/automaton/AutomatonTransitionTableView.kt new file mode 100644 index 0000000..93777c1 --- /dev/null +++ b/src/main/kotlin/automaton/constructor/view/automaton/AutomatonTransitionTableView.kt @@ -0,0 +1,85 @@ +package automaton.constructor.view.automaton + +import automaton.constructor.model.automaton.Automaton +import automaton.constructor.model.element.AutomatonVertex +import automaton.constructor.model.element.Transition +import automaton.constructor.utils.I18N +import automaton.constructor.view.AutomatonViewContext +import automaton.constructor.view.TransitionTableTransitionView +import javafx.beans.property.ReadOnlyDoubleProperty +import javafx.beans.property.SimpleObjectProperty +import javafx.scene.control.* +import javafx.scene.control.cell.PropertyValueFactory + +class TransitionTableTransitionMap( + val source: AutomatonVertex, + val target: AutomatonVertex, + val transitions: SimpleObjectProperty> = SimpleObjectProperty(listOf()) +): TransitionMap + +class AutomatonTransitionTableView( + automaton: Automaton, automatonViewContext: AutomatonViewContext, + tablePrefWidth: ReadOnlyDoubleProperty, tablePrefHeight: ReadOnlyDoubleProperty +): AutomatonTableView( + automaton, automatonViewContext, tablePrefWidth, tablePrefHeight +) { + private val targetColumn = TableColumn( + I18N.messages.getString("AutomatonTransitionTableView.ToState")) + private val transitionColumn = TableColumn>( + I18N.messages.getString("AutomatonTransitionTableView.Label")) + + init { + sourceColumn.text = I18N.messages.getString("AutomatonTransitionTableView.FromState") + targetColumn.cellValueFactory = PropertyValueFactory("target") + targetColumn.setCellFactory { VertexCell(this) } + transitionColumn.setCellValueFactory { p0 -> + p0!!.value.transitions + } + transitionColumn.setCellFactory { TransitionsCell(transitionToViewMap) } + table.columns.addAll(targetColumn, transitionColumn) + table.prefWidthProperty().addListener { _, _, newValue -> + table.columns.forEach { it.prefWidth = (newValue as Double) / 3 } + } + automaton.transitions.forEach { registerTransition(it) } + } + + override fun unregisterVertex(vertex: AutomatonVertex) { + transitionsByVertices.removeAll { it.source == vertex } + } + + override fun registerTransition(transition: Transition) { + val transitionView = TransitionTableTransitionView(transition) + controller.registerAutomatonElementView(transitionView) + transitionToViewMap[transition] = transitionView + addTransitionToTable(transition) + } + + override fun unregisterTransition(transition: Transition) { + deleteTransitionFromTable(transition) + transitionToViewMap.remove(transition) + } + + private fun addTransitionToTable(transition: Transition) { + var transitionMap = transitionsByVertices.find { + it.source == transition.source && it.target == transition.target + } + if (transitionMap == null) { + transitionMap = TransitionTableTransitionMap(transition.source, transition.target) + transitionsByVertices.add(transitionMap) + } + val list = transitionMap.transitions.get() + transitionMap.transitions.set(list + transition) + } + + private fun deleteTransitionFromTable(transition: Transition) { + transitionsByVertices.find { map -> + map.source == transition.source && map.target == transition.target + }.also { + val list = it!!.transitions.value + it.transitions.set(list - transition) + if (it.transitions.value.isEmpty()) { + transitionsByVertices.remove(it) + } + } + } +} diff --git a/src/main/kotlin/automaton/constructor/view/TestsResultsFragment.kt b/src/main/kotlin/automaton/constructor/view/tests/TestsResultsFragment.kt similarity index 98% rename from src/main/kotlin/automaton/constructor/view/TestsResultsFragment.kt rename to src/main/kotlin/automaton/constructor/view/tests/TestsResultsFragment.kt index 742bcd5..5274529 100644 --- a/src/main/kotlin/automaton/constructor/view/TestsResultsFragment.kt +++ b/src/main/kotlin/automaton/constructor/view/tests/TestsResultsFragment.kt @@ -1,4 +1,4 @@ -package automaton.constructor.view +package automaton.constructor.view.tests import automaton.constructor.model.memory.Test import automaton.constructor.utils.I18N diff --git a/src/main/kotlin/automaton/constructor/view/TestsView.kt b/src/main/kotlin/automaton/constructor/view/tests/TestsView.kt similarity index 98% rename from src/main/kotlin/automaton/constructor/view/TestsView.kt rename to src/main/kotlin/automaton/constructor/view/tests/TestsView.kt index 5940da6..01f3078 100644 --- a/src/main/kotlin/automaton/constructor/view/TestsView.kt +++ b/src/main/kotlin/automaton/constructor/view/tests/TestsView.kt @@ -1,4 +1,4 @@ -package automaton.constructor.view +package automaton.constructor.view.tests import automaton.constructor.controller.TestsController import automaton.constructor.model.memory.Test diff --git a/src/main/resources/examples.properties b/src/main/resources/examples.properties index 9763610..96104b5 100644 --- a/src/main/resources/examples.properties +++ b/src/main/resources/examples.properties @@ -1,3 +1,4 @@ +ExamplesFragment.Title=Examples ExamplesFragment.evenBinaryNumbersRecognizer=Even binary numbers recognizer ExamplesFragment.correctBracketSeqRecognizer=Correct bracket sequence recognizer ExamplesFragment.binaryNumberAdder=Binary number adder @@ -9,4 +10,4 @@ ExamplesFragment.correctBracketSeqRecognizerDescription=Implemented with pushdow ExamplesFragment.binaryNumberAdderDescription=Implemented with Turing machine.\nRunning on test example: ExamplesFragment.evenPalindromesRecognizerDescription=Implemented with pushdown automaton.\nRunning on test example: ExamplesFragment.threeZerosAndOneOneDescription=Implemented with register automaton.\nRunning on test example: -ExamplesFragment.zeroRemoverDescription=Implemented with Mealy/Moore machine.\nRunning on test example: \ No newline at end of file +ExamplesFragment.zeroRemoverDescription=Implemented with Mealy/Moore machine.\nRunning on test example: diff --git a/src/main/resources/examples_ru.properties b/src/main/resources/examples_ru.properties index 02968a4..b1c84e7 100644 --- a/src/main/resources/examples_ru.properties +++ b/src/main/resources/examples_ru.properties @@ -1,3 +1,4 @@ +ExamplesFragment.Title=Примеры ExamplesFragment.evenBinaryNumbersRecognizer=Распознаватель четных двоичных чисел ExamplesFragment.correctBracketSeqRecognizer=Распознаватель правильных скобочных последовательностей ExamplesFragment.binaryNumberAdder=Сумматор двоичных чисел diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index dce01da..12536ad 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -19,7 +19,11 @@ MainView.Help.UserDocumentation=User Documentation MainView.Help.README=README MainView.Tests=Tests MainView.Tests.Create=Create a set of tests -MainView.Tests.Open=Open a set of tests +MainView.Algorithms=Algorithms +MainView.Algorithms.FiniteAutomaton=Finite automaton +MainView.Algorithms.PushdownAutomaton=Pushdown automaton +MainView.Algorithms.FiniteAutomaton.Hellings=Hellings algorithm +MainView.Algorithms.PushdownAutomaton.CFG=Convert to context-free grammar MainView.Examples=Examples StateView.State=State StateView.Name=Name @@ -29,6 +33,9 @@ TransitionView.Transition=Transition TransitionView.Source=Source TransitionView.Target=Target AutomatonView.Problems=Problems:\n +AutomatonView.Graph=Graph representation +AutomatonView.Table=State-transition table representation +AutomatonView.Matrix=Adjacency matrix representation Hint.UseRightClickToAddElements=Right click to add state\nDrag right click to add transition Hint.DontShowAgain=Don't show again Error.FailedToOpenFile.Header=Failed to open a file "{0}" @@ -208,3 +215,37 @@ TestsResultsFragment.Description=Description TestsResultsFragment.Title=Tests results ExamplesFragment.Choose=Choose an example ExamplesFragment.UnableToFindResource=Unable to find resource {0} +CFGView.Error=Conversion is done only for pushdown automatons with a single stack! +CFGView.Title=Context-free grammar +CFGView.InitialNonterminal=Initial nonterminal +CFGView.LeftSide=Left side +CFGView.RightSide=Right side +CFGView.Note=NOTE. The settings of the automaton (stack contents, whether it\naccepts by final state/empty stack) affect the result. +HellingsAlgorithm.Error=Algorithm is implemented only for finite automatons (automaton is considered as input graph)! +HellingsAlgorithm.Grammar.Title=Input grammar +HellingsAlgorithm.Grammar.Add=Add +HellingsAlgorithm.Grammar.OK=OK +HellingsAlgorithm.Grammar.Terminal=Terminal +HellingsAlgorithm.Grammar.Nonterminal=Nonterminal +HellingsAlgorithm.Grammar.Error=Make sure you leave no blank fields and add at least one production. +HellingsAlgorithm.Grammar.Delete=Delete +HellingsAlgorithm.Grammar.Info=The automaton is considered as an input graph.\nProductions with empty right sides are considered as epsilon ones.\nGrammar will be automatically converted to Chomsky normal form. +HellingsAlgorithm.Execution.Title=Hellings algorithm +HellingsAlgorithm.Execution.Description=Each triple (N, S, T) means that a word composed of symbols on the path between vertices S and T is output in the grammar if we take N as the initial nonterminal.\nOnce the algorithm finishes, the "All transitions" list contains the solution. +HellingsAlgorithm.Execution.NextIteration=Next iteration +HellingsAlgorithm.Execution.CurrentTransitions=Current transitions +HellingsAlgorithm.Execution.AllTransitions=All transitions +AutomatonTableView.AddState=Add state +AutomatonTableView.AddTransition=Add transition +AutomatonTableView.AddBuildingBlock=Add empty building block +AutomatonTableView.CopyBuildingBlock=Copy building block from file +AutomatonTransitionTableView.FromState=From state +AutomatonTransitionTableView.ToState=To state +AutomatonTransitionTableView.Label=Label +AutomatonAdjacencyMatrixView.State=State +AutomatonAdjacencyMatrixView.Targets=Targets +NewTransitionPopup.Title=Add transition +NewTransitionPopup.Question=What transition would you like to add? +NewTransitionPopup.Source=Source vertex: +NewTransitionPopup.Target=Target vertex: +NewTransitionPopup.Add=Add diff --git a/src/main/resources/messages_ru.properties b/src/main/resources/messages_ru.properties index 530f83f..5cb7f37 100644 --- a/src/main/resources/messages_ru.properties +++ b/src/main/resources/messages_ru.properties @@ -19,7 +19,11 @@ MainView.Help.UserDocumentation=Пользовательская докумен MainView.Help.README=README MainView.Tests=Тесты MainView.Tests.Create=Создать набор тестов -MainView.Tests.Open=Открыть набор тестов +MainView.Algorithms=Алгоритмы +MainView.Algorithms.FiniteAutomaton=Для конечного автомата +MainView.Algorithms.PushdownAutomaton=Для магазинного автомата +MainView.Algorithms.FiniteAutomaton.Hellings=Алгоритм Хеллингса +MainView.Algorithms.PushdownAutomaton.CFG=Конвертировать в КС-грамматику MainView.Examples=Примеры StateView.State=Состояние StateView.Name=Имя @@ -29,6 +33,9 @@ TransitionView.Transition=Переход TransitionView.Source=Начало TransitionView.Target=Конец AutomatonView.Problems=Проблемы:\n +AutomatonView.Graph=Представление в виде графа +AutomatonView.Table=Представление в виде таблицы переходов +AutomatonView.Matrix=Представление в виде матрицы смежности Hint.UseRightClickToAddElements=Добавляйте состояния правым кликом\nДля добавления переходов потяните правой кнопкой мыши Hint.DontShowAgain=Больше не показывать Error.FailedToOpenFile.Header=Не удалось открыть файл "{0}" @@ -210,3 +217,39 @@ TestsResultsFragment.Description=Описание TestsResultsFragment.Title=Результаты тестов ExamplesFragment.Choose=Выберите пример ExamplesFragment.UnableToFindResource=Отсутствует ресурс +ExamplesFragment.evenBinaryNumbersRecognizer=Распознаватель четных двоичных чисел +ExamplesFragment.correctBracketSeqRecognizer=Распознаватель правильных скобочных последовательностей +CFGView.Error=Конверсия реализована только для магазинных автоматов с единственным стеком! +CFGView.Title=КC-грамматика +CFGView.InitialNonterminal=Стартовый нетерминал +CFGView.LeftSide=Левая часть +CFGView.RightSide=Правая часть +CFGView.Note=ВАЖНО. Настройки автомата (содержимое стека, принимает ли\nон по конечному состоянию/пустому стеку) влияют на результат. +HellingsAlgorithm.Error=Алгоритм реализован только для конечных автоматов (автомат рассматривается как входной граф)! +HellingsAlgorithm.Grammar.Title=Входная грамматика +HellingsAlgorithm.Grammar.Add=Добавить +HellingsAlgorithm.Grammar.OK=ОК +HellingsAlgorithm.Grammar.Terminal=Терминал +HellingsAlgorithm.Grammar.Nonterminal=Нетерминал +HellingsAlgorithm.Grammar.Error=Убедитесь, что вы не оставили пустых полей и добавили хотя бы одно правило. +HellingsAlgorithm.Grammar.Delete=Удалить +HellingsAlgorithm.Grammar.Info=Автомат рассматривается как входной граф.\nПравила с пустой правой частью рассматриваются как эпсилон-правила.\nГрамматика будет автоматически переведена в нормальную норму Хомского. +HellingsAlgorithm.Execution.Title=Алгоритм Хеллингса +HellingsAlgorithm.Execution.Description=Каждая тройка (N, S, T) означает, что слово, составленное из символов на пути между вершинами S и T, выводится в грамматике, если мы принимаем N в качестве начального нетерминала.\nПосле завершения алгоритма список "Все переходы" содержит решение. +HellingsAlgorithm.Execution.NextIteration=Следующая итерация +HellingsAlgorithm.Execution.CurrentTransitions=Текущие переходы +HellingsAlgorithm.Execution.AllTransitions=Все переходы +AutomatonTableView.AddState=Добавить состояние +AutomatonTableView.AddTransition=Добавить переход +AutomatonTableView.AddBuildingBlock=Добавить строительный блок +AutomatonTableView.CopyBuildingBlock=Скопировать строительный блок из файла +AutomatonTransitionTableView.FromState=Начало +AutomatonTransitionTableView.ToState=Конец +AutomatonTransitionTableView.Label=Переход +AutomatonAdjacencyMatrixView.State=Начало +AutomatonAdjacencyMatrixView.Targets=Конец +NewTransitionPopup.Title=Добавить переход +NewTransitionPopup.Question=Какой переход вы бы хотели добавить? +NewTransitionPopup.Source=Начало: +NewTransitionPopup.Target=Конец: +NewTransitionPopup.Add=Добавить diff --git a/src/test/kotlin/automaton/constructor/model/TestAutomatons.kt b/src/test/kotlin/automaton/constructor/model/TestAutomatons.kt index 71f24db..e174092 100644 --- a/src/test/kotlin/automaton/constructor/model/TestAutomatons.kt +++ b/src/test/kotlin/automaton/constructor/model/TestAutomatons.kt @@ -67,6 +67,13 @@ object TestAutomatons { val TRANSITION_FROM_FINAL_STATE get() = getAutomatonFromJson("/transition-from-final-state.atmtn") as TuringMachine val UNREACH_STATE get() = getAutomatonFromJson("/unreach-state.atmtn") as FiniteAutomaton val USELESS_STATE_REMOVED get() = getAutomatonFromJson("/useless-state-removed.atmtn") as FiniteAutomaton + val FROM_REFERENCE_COURSE get() = getAutomatonFromJson("/from-reference-course.atmtn") as FiniteAutomaton + val CAACBB get() = getAutomatonFromJson("/caacbb.atmtn") as FiniteAutomaton + val DFA_110011 get() = getAutomatonFromJson("/dfa-110011.atmtn") as FiniteAutomaton + val CORRECT_BRACKET_SEQUENCE_RECOGNISER get() = getAutomatonFromJson("/correct-bracket-sequence-recogniser.atmtn") as PushdownAutomaton + val SAME_NUMBER_OF_ZEROS_AND_ONES get() = getAutomatonFromJson("/same-number-of-zeros-and-ones.atmtn") as PushdownAutomaton + val DFA_0110011 get() = getAutomatonFromJson("/dfa-0110011.atmtn") as FiniteAutomaton + val SAME_NUMBER_OF_ZEROS_AND_ONES_BY_EMPTY_STACK get() = getAutomatonFromJson("/same-number-of-zeros-and-ones-by-empty-stack.atmtn") as PushdownAutomaton private fun getAutomatonFromJson(path: String): Automaton { val file = File(requireNotNull(javaClass.getResource(path)) { "Missing resource $path" }.file) @@ -128,5 +135,12 @@ object TestAutomatons { TRANSITION_FROM_FINAL_STATE, UNREACH_STATE, USELESS_STATE_REMOVED, + FROM_REFERENCE_COURSE, + CAACBB, + DFA_110011, + CORRECT_BRACKET_SEQUENCE_RECOGNISER, + SAME_NUMBER_OF_ZEROS_AND_ONES, + DFA_0110011, + SAME_NUMBER_OF_ZEROS_AND_ONES_BY_EMPTY_STACK ) } diff --git a/src/test/kotlin/automaton/constructor/model/TestingTests.kt b/src/test/kotlin/automaton/constructor/model/TestingTests.kt index 5d68a76..205a5ed 100644 --- a/src/test/kotlin/automaton/constructor/model/TestingTests.kt +++ b/src/test/kotlin/automaton/constructor/model/TestingTests.kt @@ -2,7 +2,7 @@ package automaton.constructor.model import automaton.constructor.AutomatonConstructorApp import automaton.constructor.utils.I18N -import automaton.constructor.view.TestAndResult +import automaton.constructor.view.tests.TestAndResult import javafx.application.Application import javafx.scene.Node import javafx.scene.control.TableView diff --git a/src/test/kotlin/automaton/constructor/model/algorithms/ConversionToCFGTests.kt b/src/test/kotlin/automaton/constructor/model/algorithms/ConversionToCFGTests.kt new file mode 100644 index 0000000..b395215 --- /dev/null +++ b/src/test/kotlin/automaton/constructor/model/algorithms/ConversionToCFGTests.kt @@ -0,0 +1,130 @@ +package automaton.constructor.model.algorithms + +import automaton.constructor.model.TestAutomatons +import automaton.constructor.model.element.ContextFreeGrammar +import automaton.constructor.model.element.Nonterminal +import automaton.constructor.model.element.Production +import automaton.constructor.model.element.Terminal +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.util.Collections.swap + +class ConversionToCFGTests { + fun nextPermutation(nums: List) { + var i = nums.size - 2 + while (i >= 0 && nums[i] >= nums[i + 1]) { + i-- + } + if (i >= 0) { + var j = nums.size - 1 + while (nums[j] <= nums[i]) { + j-- + } + swap(nums, i, j) + } + + reverse(nums, i + 1) + } + + fun reverse(nums: List, start: Int) { + var i = start + var j = nums.size - 1 + while (i < j) { + swap(nums, i, j) + i++ + j-- + } + } + + fun factorial(n: Int): Int { + var result = 1 + for (i in 2..n) { + result *= i + } + return result + } + + fun areGrammarsEqual(expected: ContextFreeGrammar, actual: ContextFreeGrammar): Boolean { + val expectedNonterminalsValues = expected.nonterminals.map { it.value }.sorted() + val expectedProductions = expected.productions.map { it.toString() }.toSet() + repeat(factorial(expectedNonterminalsValues.size)) { + for (i in actual.nonterminals.indices) { + actual.nonterminals[i].value = expectedNonterminalsValues[i] + } + val actualProductions = actual.productions.map { it.toString() }.toSet() + if (actualProductions.equals(expectedProductions)) { + return true + } + nextPermutation(expectedNonterminalsValues) + } + return false + } + + @Test + fun `correct bracket sequence recogniser test`() { + val actual = TestAutomatons.CORRECT_BRACKET_SEQUENCE_RECOGNISER.convertToCFG() + + val S = Nonterminal("S") + val expected = ContextFreeGrammar(S) + val A = expected.addNonterminal("A") + val U = expected.addNonterminal("U") + val U1 = expected.addNonterminal("U1") + val Y = expected.addNonterminal("Y") + expected.productions.add(Production(A, mutableListOf(A, A))) + expected.productions.add(Production(A, mutableListOf(Y, U1))) + expected.productions.add(Production(A, mutableListOf(U, U1))) + expected.productions.add(Production(S, mutableListOf())) + expected.productions.add(Production(S, mutableListOf(A, A))) + expected.productions.add(Production(S, mutableListOf(Y, U1))) + expected.productions.add(Production(S, mutableListOf(U, U1))) + expected.productions.add(Production(U, mutableListOf(Terminal('(')))) + expected.productions.add(Production(U1, mutableListOf(Terminal(')')))) + expected.productions.add(Production(Y, mutableListOf(U, A))) + + assertTrue(areGrammarsEqual(expected, actual)) + } + + @Test + fun `same number of zeros and ones test`() { + val actual = TestAutomatons.SAME_NUMBER_OF_ZEROS_AND_ONES.convertToCFG() + + val S = Nonterminal("S") + val expected = ContextFreeGrammar(S) + val A = expected.addNonterminal("A") + val U = expected.addNonterminal("U") + val U1 = expected.addNonterminal("U1") + val Y = expected.addNonterminal("Y") + expected.productions.add(Production(A, mutableListOf(Y, U1))) + expected.productions.add(Production(A, mutableListOf(U, U1))) + expected.productions.add(Production(S, mutableListOf())) + expected.productions.add(Production(S, mutableListOf(Y, U1))) + expected.productions.add(Production(S, mutableListOf(U, U1))) + expected.productions.add(Production(U, mutableListOf(Terminal('0')))) + expected.productions.add(Production(U1, mutableListOf(Terminal('1')))) + expected.productions.add(Production(Y, mutableListOf(U, A))) + + assertTrue(areGrammarsEqual(expected, actual)) + } + + @Test + fun `same number of zeros and ones by empty stack test`() { + val actual = TestAutomatons.SAME_NUMBER_OF_ZEROS_AND_ONES_BY_EMPTY_STACK.convertToCFG() + + val S = Nonterminal("S") + val expected = ContextFreeGrammar(S) + val A = expected.addNonterminal("A") + val U = expected.addNonterminal("U") + val U1 = expected.addNonterminal("U1") + val Y = expected.addNonterminal("Y") + expected.productions.add(Production(A, mutableListOf(Y, U1))) + expected.productions.add(Production(A, mutableListOf(U, U1))) + expected.productions.add(Production(S, mutableListOf())) + expected.productions.add(Production(S, mutableListOf(Y, U1))) + expected.productions.add(Production(S, mutableListOf(U, U1))) + expected.productions.add(Production(U, mutableListOf(Terminal('0')))) + expected.productions.add(Production(U1, mutableListOf(Terminal('1')))) + expected.productions.add(Production(Y, mutableListOf(U, A))) + + assertTrue(areGrammarsEqual(expected, actual)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/automaton/constructor/model/algorithms/HellingsAlgoTests.kt b/src/test/kotlin/automaton/constructor/model/algorithms/HellingsAlgoTests.kt new file mode 100644 index 0000000..8a9d7de --- /dev/null +++ b/src/test/kotlin/automaton/constructor/model/algorithms/HellingsAlgoTests.kt @@ -0,0 +1,374 @@ +package automaton.constructor.model.algorithms + +import automaton.constructor.controller.algorithms.HellingsAlgoController +import automaton.constructor.controller.algorithms.HellingsTransition +import automaton.constructor.model.TestAutomatons +import automaton.constructor.model.automaton.FiniteAutomaton +import automaton.constructor.model.element.ContextFreeGrammar +import automaton.constructor.model.element.Nonterminal +import automaton.constructor.model.element.Production +import automaton.constructor.model.element.Terminal +import automaton.constructor.utils.doNextIterationOfHellingsAlgo +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import tornadofx.observableListOf +import kotlin.test.assertEquals + +class HellingsAlgoTests { + fun execute(automaton: FiniteAutomaton, grammar: ContextFreeGrammar): List { + val controller = HellingsAlgoController(automaton) + controller.grammar = grammar + val currentTransitions = observableListOf() + val allTransitions = observableListOf() + controller.prepareForExecution(currentTransitions, allTransitions) + do { + doNextIterationOfHellingsAlgo(currentTransitions, allTransitions, grammar) + } while(currentTransitions.isNotEmpty()) + return allTransitions + } + + @Test + fun `test from reference course` () { + val automaton = TestAutomatons.FROM_REFERENCE_COURSE + + val S = Nonterminal("S") + val grammar = ContextFreeGrammar(S) + val A = grammar.addNonterminal("A") + val B = grammar.addNonterminal("B") + val S1 = grammar.addNonterminal("S1") + grammar.productions.add(Production(S, mutableListOf(A, B))) + grammar.productions.add(Production(S, mutableListOf(A, S1))) + grammar.productions.add(Production(S1, mutableListOf(S, B))) + grammar.productions.add(Production(A, mutableListOf(Terminal('a')))) + grammar.productions.add(Production(B, mutableListOf(Terminal('b')))) + + val expected = mutableSetOf( + "A, S0, S1", + "A, S1, S2", + "A, S2, S0", + "B, S2, S3", + "B, S3, S2", + "S, S1, S3", + "S1, S1, S2", + "S, S0, S2", + "S1, S0, S3", + "S, S2, S3", + "S1, S2, S2", + "S, S1, S2", + "S1, S1, S3", + "S, S0, S3", + "S1, S0, S2", + "S, S2, S2", + "S1, S2, S3" + ) + val actual = mutableSetOf() + execute(automaton, grammar).forEach { actual.add(it.toString()) } + assertEquals(expected, actual) + } + + @Test + fun `test from reference course with different grammar`() { + val automaton = TestAutomatons.FROM_REFERENCE_COURSE + + val S = Nonterminal("S") + val grammar = ContextFreeGrammar(S) + grammar.productions.add(Production(S, mutableListOf(Terminal('a'), S, Terminal('b'), S))) + grammar.productions.add(Production(S, mutableListOf())) + grammar.convertToCNF() + + val expected = mutableSetOf( + "U3, S2, S3", + "U3, S3, S2", + "U, S1, S2", + "U, S2, S0", + "U, S0, S1", + "S, S1, S3", + "S1, S1, S3", + "Y6, S1, S3", + "Y, S0, S3", + "Y7, S0, S3", + "Y5, S0, S2", + "S1, S0, S2", + "S, S0, S2", + "Y, S2, S2", + "Y7, S2, S2", + "Y5, S2, S3", + "S1, S2, S3", + "S, S2, S3", + "Y, S1, S3", + "Y7, S1, S3", + "S1, S0, S3", + "S, S0, S3", + "Y, S1, S3", + "Y7, S1, S3", + "S1, S0, S3", + "S, S0, S3", + "Y5, S1, S2", + "S1, S1, S2", + "S, S1, S2", + "Y, S2, S3", + "Y7, S2, S3", + "Y, S0, S2", + "Y7, S0, S2", + "Y5, S2, S2", + "S1, S2, S2", + "S, S2, S2", + "Y5, S0, S3", + "Y, S1, S2", + "Y7, S1, S2", + "Y5, S1, S3", + "S1, S1, S1", + "S1, S3, S3", + "S1, S0, S0" + ) + val actual = mutableSetOf() + execute(automaton, grammar).forEach { actual.add(it.toString()) } + assertEquals(expected, actual) + } + + @Test + fun `result should be empty`() { + val automaton = TestAutomatons.FROM_REFERENCE_COURSE + + val S = Nonterminal("S") + val grammar = ContextFreeGrammar(S) + val A = grammar.addNonterminal("A") + val B = grammar.addNonterminal("B") + val S1 = grammar.addNonterminal("S1") + grammar.productions.add(Production(S, mutableListOf(A, B))) + grammar.productions.add(Production(S, mutableListOf(A, S1))) + grammar.productions.add(Production(S1, mutableListOf(S, B))) + grammar.productions.add(Production(A, mutableListOf(Terminal('c')))) + grammar.productions.add(Production(B, mutableListOf(Terminal('d')))) + + val expected = mutableSetOf() + val actual = mutableSetOf() + execute(automaton, grammar).forEach { actual.add(it.toString()) } + assertEquals(expected, actual) + } + + @Test + fun `caacbb test`() { + val automaton = TestAutomatons.CAACBB + + val S = Nonterminal("S") + val grammar = ContextFreeGrammar(S) + val A = grammar.addNonterminal("A") + val B = grammar.addNonterminal("B") + grammar.productions.add(Production(S, mutableListOf(A, S, B))) + grammar.productions.add(Production(A, mutableListOf(Terminal('a'), A, S))) + grammar.productions.add(Production(A, mutableListOf(Terminal('a')))) + grammar.productions.add(Production(A, mutableListOf())) + grammar.productions.add(Production(B, mutableListOf(S, Terminal('b'), S))) + grammar.productions.add(Production(B, mutableListOf(A))) + grammar.productions.add(Production(B, mutableListOf(Terminal('b'), Terminal('b')))) + grammar.productions.add(Production(S, mutableListOf(Terminal('c')))) + grammar.convertToCNF() + + assertTrue(execute(automaton, grammar).any { it.isEqual(HellingsTransition( + grammar.initialNonterminal, + automaton.vertices.find { it.name == "S0" }!!, + automaton.vertices.find { it.name == "S6" }!! + )) }) + } + + @Test + fun `test for grammar of even palindromes recogniser`() { + val automaton = TestAutomatons.DFA_110011 + + val S = Nonterminal("S") + val grammar = ContextFreeGrammar(S) + val A3 = grammar.addNonterminal("A3") + val A8 = grammar.addNonterminal("A8") + val A12 = grammar.addNonterminal("A12") + val A21 = grammar.addNonterminal("A21") + val A28 = grammar.addNonterminal("A28") + val A29 = grammar.addNonterminal("A29") + val A63 = grammar.addNonterminal("A63") + val A71 = grammar.addNonterminal("A71") + val A80 = grammar.addNonterminal("A80") + val A84 = grammar.addNonterminal("A84") + val A94 = grammar.addNonterminal("A94") + val A106 = grammar.addNonterminal("A106") + val A111 = grammar.addNonterminal("A111") + val A116 = grammar.addNonterminal("A116") + val A120 = grammar.addNonterminal("A120") + val A129 = grammar.addNonterminal("A129") + val A165 = grammar.addNonterminal("A165") + val A183 = grammar.addNonterminal("A183") + val A201 = grammar.addNonterminal("A201") + val A219 = grammar.addNonterminal("A219") + val A226 = grammar.addNonterminal("A226") + val A227 = grammar.addNonterminal("A227") + val A262 = grammar.addNonterminal("A262") + val A263 = grammar.addNonterminal("A263") + val A279 = grammar.addNonterminal("A279") + val A287 = grammar.addNonterminal("A287") + val U = grammar.addNonterminal("U") + val U326 = grammar.addNonterminal("U326") + grammar.productions.add(Production(A3, mutableListOf(A8, A129))) + grammar.productions.add(Production(A3, mutableListOf(A12, A201))) + grammar.productions.add(Production(A21, mutableListOf(A28, A165))) + grammar.productions.add(Production(A21, mutableListOf(A29, A183))) + grammar.productions.add(Production(A111, mutableListOf(A116, A129))) + grammar.productions.add(Production(A111, mutableListOf(A120, A201))) + grammar.productions.add(Production(A219, mutableListOf(A226, A165))) + grammar.productions.add(Production(A219, mutableListOf(A227, A183))) + grammar.productions.add(Production(A183, mutableListOf(A21, U326))) + grammar.productions.add(Production(A165, mutableListOf(A3, U326))) + grammar.productions.add(Production(A201, mutableListOf(A111, U))) + grammar.productions.add(Production(A129, mutableListOf(A219, U))) + grammar.productions.add(Production(A262, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A263, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A28, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A29, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A12, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A8, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A106, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A94, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A120, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A116, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A84, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A80, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A226, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A227, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A21, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A3, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A111, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A219, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(S, mutableListOf())) + grammar.productions.add(Production(A287, mutableListOf(A80, A129))) + grammar.productions.add(Production(A287, mutableListOf(A84, A201))) + grammar.productions.add(Production(A287, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A279, mutableListOf(A80, A129))) + grammar.productions.add(Production(A279, mutableListOf(A84, A201))) + grammar.productions.add(Production(A279, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A71, mutableListOf(A262, A165))) + grammar.productions.add(Production(A71, mutableListOf(A263, A183))) + grammar.productions.add(Production(A71, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A63, mutableListOf(A262, A165))) + grammar.productions.add(Production(A63, mutableListOf(A263, A183))) + grammar.productions.add(Production(A63, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(S, mutableListOf(A94, A63))) + grammar.productions.add(Production(S, mutableListOf(A106, A279))) + grammar.productions.add(Production(S, mutableListOf(A94, A71))) + grammar.productions.add(Production(S, mutableListOf(A106, A287))) + grammar.productions.add(Production(U, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(U326, mutableListOf(Terminal('1')))) + + val expectedWithSAsNonterminal = setOf( + "S, S0, S0", + "S, S2, S2", + "S, S3, S3", + "S, S5, S5", + "S, S6, S6", + "S, S4, S4", + "S, S1, S1", + "S, S4, S6", + "S, S2, S4", + "S, S0, S2", + "S, S1, S5", + "S, S0, S6" + ) + val actualWithSAsNonterminal = execute(automaton, grammar).filter { + it.nonterminal == S + }.map { it.toString() }.toSet() + assertEquals(expectedWithSAsNonterminal, actualWithSAsNonterminal) + } + + @Test + fun `test for grammar of pda accepting by empty stack`() { + val automaton = TestAutomatons.DFA_0110011 + + val S = Nonterminal("S") + val grammar = ContextFreeGrammar(S) + val A2 = grammar.addNonterminal("A2") + val A36 = grammar.addNonterminal("A36") + val A38 = grammar.addNonterminal("A38") + val A43 = grammar.addNonterminal("A43") + val A53 = grammar.addNonterminal("A53") + val A75 = grammar.addNonterminal("A75") + val A85 = grammar.addNonterminal("A85") + val A87 = grammar.addNonterminal("A87") + val A104 = grammar.addNonterminal("A104") + val A121 = grammar.addNonterminal("A121") + val A123 = grammar.addNonterminal("A123") + val A128 = grammar.addNonterminal("A128") + val A138 = grammar.addNonterminal("A138") + val A171 = grammar.addNonterminal("A171") + val A176 = grammar.addNonterminal("A176") + val A188 = grammar.addNonterminal("A188") + val A189 = grammar.addNonterminal("A189") + val A193 = grammar.addNonterminal("A193") + val A239 = grammar.addNonterminal("A239") + val A240 = grammar.addNonterminal("A240") + val A244 = grammar.addNonterminal("A244") + val A259 = grammar.addNonterminal("A259") + val A264 = grammar.addNonterminal("A264") + val A274 = grammar.addNonterminal("A274") + val U = grammar.addNonterminal("U") + val U291 = grammar.addNonterminal("U291") + grammar.productions.add(Production(A36, mutableListOf(A38, A53))) + grammar.productions.add(Production(A36, mutableListOf(A43, A138))) + grammar.productions.add(Production(A121, mutableListOf(A123, A53))) + grammar.productions.add(Production(A121, mutableListOf(A128, A138))) + grammar.productions.add(Production(A189, mutableListOf(A188, A2))) + grammar.productions.add(Production(A189, mutableListOf(A193, A87))) + grammar.productions.add(Production(A240, mutableListOf(A239, A2))) + grammar.productions.add(Production(A240, mutableListOf(A244, A87))) + grammar.productions.add(Production(A87, mutableListOf(A36, U291))) + grammar.productions.add(Production(A53, mutableListOf(A121, U))) + grammar.productions.add(Production(A138, mutableListOf(A189, U))) + grammar.productions.add(Production(A2, mutableListOf(A240, U291))) + grammar.productions.add(Production(A75, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A85, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A176, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A171, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A123, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A128, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A121, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A189, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A193, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A188, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A259, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A264, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A38, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A43, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A36, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A240, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A244, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(A239, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(S, mutableListOf())) + grammar.productions.add(Production(A274, mutableListOf(A171, A2))) + grammar.productions.add(Production(A274, mutableListOf(A176, A87))) + grammar.productions.add(Production(A274, mutableListOf(Terminal('0')))) + grammar.productions.add(Production(A104, mutableListOf(A259, A53))) + grammar.productions.add(Production(A104, mutableListOf(A264, A138))) + grammar.productions.add(Production(A104, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(S, mutableListOf(A75, A104))) + grammar.productions.add(Production(S, mutableListOf(A85, A274))) + grammar.productions.add(Production(U, mutableListOf(Terminal('1')))) + grammar.productions.add(Production(U291, mutableListOf(Terminal('0')))) + + val expectedWithSAsNonterminal = setOf( + "S, S1, S1", + "S, S2, S2", + "S, S6, S6", + "S, S3, S3", + "S, S0, S0", + "S, S7, S7", + "S, S5, S5", + "S, S4, S4", + "S, S1, S3", + "S, S3, S5", + "S, S5, S7", + "S, S0, S4", + "S, S2, S6", + "S, S1, S7" + ) + val actualWithSAsNonterminal = execute(automaton, grammar).filter { + it.nonterminal == S + }.map { it.toString() }.toSet() + assertEquals(expectedWithSAsNonterminal, actualWithSAsNonterminal) + } +} diff --git a/src/test/resources/caacbb.atmtn b/src/test/resources/caacbb.atmtn new file mode 100644 index 0000000..2733f10 --- /dev/null +++ b/src/test/resources/caacbb.atmtn @@ -0,0 +1,102 @@ +{ + "base": { + "type": "finite-automaton", + "inputTape": { + } + }, + "vertices": [ + { + "type": "state", + "id": 3, + "name": "S3", + "x": 500142.4715097697, + "y": 500052.4715097697 + }, + { + "type": "state", + "id": 1, + "name": "S1", + "x": 499559.9715097697, + "y": 500057.4715097697 + }, + { + "type": "state", + "id": 2, + "name": "S2", + "x": 499844.9715097697, + "y": 500054.9715097697 + }, + { + "type": "state", + "id": 4, + "name": "S4", + "x": 500419.9715097697, + "y": 500049.9715097697 + }, + { + "type": "state", + "id": 5, + "name": "S5", + "x": 500702.4715097697, + "y": 500047.4715097697 + }, + { + "type": "state", + "id": 6, + "name": "S6", + "x": 500997.4715097697, + "y": 500049.9715097697 + }, + { + "type": "state", + "id": 0, + "name": "S0", + "x": 499245.0, + "y": 500055.0 + } + ], + "transitions": [ + { + "source": 2, + "target": 3, + "properties": [ + "a" + ] + }, + { + "source": 3, + "target": 4, + "properties": [ + "c" + ] + }, + { + "source": 1, + "target": 2, + "properties": [ + "a" + ] + }, + { + "source": 4, + "target": 5, + "properties": [ + "b" + ] + }, + { + "source": 0, + "target": 1, + "properties": [ + "c" + ] + }, + { + "source": 5, + "target": 6, + "properties": [ + "b" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/correct-bracket-sequence-recogniser.atmtn b/src/test/resources/correct-bracket-sequence-recogniser.atmtn new file mode 100644 index 0000000..b34f556 --- /dev/null +++ b/src/test/resources/correct-bracket-sequence-recogniser.atmtn @@ -0,0 +1,76 @@ +{ + "base": { + "type": "pushdown-automaton", + "inputTape": { + }, + "stacks": [ + { + "acceptsByEmptyStack": false, + "value": "" + } + ] + }, + "vertices": [ + { + "type": "state", + "id": 1, + "name": "S1", + "x": 499567.5165449231, + "y": 500177.5165449231 + }, + { + "type": "state", + "id": 0, + "name": "S0", + "x": 499075.0, + "y": 500180.0, + "isInitial": true + }, + { + "type": "state", + "id": 2, + "name": "S2", + "x": 500077.5330898464, + "y": 500175.0330898464, + "isFinal": true + } + ], + "transitions": [ + { + "source": 1, + "target": 1, + "properties": [ + "(", + "ε", + "(" + ] + }, + { + "source": 0, + "target": 1, + "properties": [ + "ε", + "ε", + "$" + ] + }, + { + "source": 1, + "target": 1, + "properties": [ + ")", + "(", + "ε" + ] + }, + { + "source": 1, + "target": 2, + "properties": [ + "ε", + "$", + "ε" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/dfa-0110011.atmtn b/src/test/resources/dfa-0110011.atmtn new file mode 100644 index 0000000..8e359e5 --- /dev/null +++ b/src/test/resources/dfa-0110011.atmtn @@ -0,0 +1,119 @@ +{ + "base": { + "type": "finite-automaton", + "inputTape": { + } + }, + "vertices": [ + { + "type": "state", + "id": 0, + "name": "S0", + "x": 499109.97452972835, + "y": 500092.47452972835, + "isInitial": true + }, + { + "type": "state", + "id": 1, + "name": "S1", + "x": 499387.490869239, + "y": 500094.990869239 + }, + { + "type": "state", + "id": 2, + "name": "S2", + "x": 499657.47452972835, + "y": 500094.97452972835 + }, + { + "type": "state", + "id": 4, + "name": "S4", + "x": 500169.97452972835, + "y": 500092.47452972835 + }, + { + "type": "state", + "id": 6, + "name": "S6", + "x": 500642.47452972835, + "y": 500089.97452972835, + "isInitial": true + }, + { + "type": "state", + "id": 5, + "name": "S5", + "x": 500404.97452972835, + "y": 500089.97452972835 + }, + { + "type": "state", + "id": 7, + "name": "S7", + "x": 500867.47452972835, + "y": 500087.47452972835, + "isFinal": true + }, + { + "type": "state", + "id": 3, + "name": "S3", + "x": 499909.97452972835, + "y": 500094.97452972835 + } + ], + "transitions": [ + { + "source": 2, + "target": 3, + "properties": [ + "1" + ] + }, + { + "source": 6, + "target": 7, + "properties": [ + "1" + ] + }, + { + "source": 1, + "target": 2, + "properties": [ + "1" + ] + }, + { + "source": 5, + "target": 6, + "properties": [ + "1" + ] + }, + { + "source": 4, + "target": 5, + "properties": [ + "0" + ] + }, + { + "source": 0, + "target": 1, + "properties": [ + "0" + ] + }, + { + "source": 3, + "target": 4, + "properties": [ + "0" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/dfa-110011.atmtn b/src/test/resources/dfa-110011.atmtn new file mode 100644 index 0000000..6aa4ceb --- /dev/null +++ b/src/test/resources/dfa-110011.atmtn @@ -0,0 +1,104 @@ +{ + "base": { + "type": "finite-automaton", + "inputTape": { + } + }, + "vertices": [ + { + "type": "state", + "id": 3, + "name": "S3", + "x": 500172.5092677027, + "y": 500097.5092677027 + }, + { + "type": "state", + "id": 6, + "name": "S6", + "x": 501362.4807774724, + "y": 500104.9807774724, + "isFinal": true + }, + { + "type": "state", + "id": 0, + "name": "S0", + "x": 498915.0, + "y": 500095.0, + "isInitial": true + }, + { + "type": "state", + "id": 4, + "name": "S4", + "x": 500602.4807774724, + "y": 500099.9807774724 + }, + { + "type": "state", + "id": 1, + "name": "S1", + "x": 499337.4752168506, + "y": 500097.4752168506 + }, + { + "type": "state", + "id": 5, + "name": "S5", + "x": 500992.4807774724, + "y": 500102.4807774724 + }, + { + "type": "state", + "id": 2, + "name": "S2", + "x": 499772.5193594694, + "y": 500097.5193594694 + } + ], + "transitions": [ + { + "source": 2, + "target": 3, + "properties": [ + "0" + ] + }, + { + "source": 1, + "target": 2, + "properties": [ + "1" + ] + }, + { + "source": 3, + "target": 4, + "properties": [ + "0" + ] + }, + { + "source": 0, + "target": 1, + "properties": [ + "1" + ] + }, + { + "source": 5, + "target": 6, + "properties": [ + "1" + ] + }, + { + "source": 4, + "target": 5, + "properties": [ + "1" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/from-reference-course.atmtn b/src/test/resources/from-reference-course.atmtn new file mode 100644 index 0000000..1fc831b --- /dev/null +++ b/src/test/resources/from-reference-course.atmtn @@ -0,0 +1,74 @@ +{ + "base": { + "type": "finite-automaton", + "inputTape": { + } + }, + "vertices": [ + { + "type": "state", + "id": 0, + "name": "S0", + "x": 498994.9936153143, + "y": 500127.4936153143 + }, + { + "type": "state", + "id": 1, + "name": "S1", + "x": 498997.4936153143, + "y": 499679.9936153143 + }, + { + "type": "state", + "id": 2, + "name": "S2", + "x": 499437.4936153143, + "y": 499917.4936153143 + }, + { + "type": "state", + "id": 3, + "name": "S3", + "x": 499877.4936153143, + "y": 499922.4936153143 + } + ], + "transitions": [ + { + "source": 1, + "target": 2, + "properties": [ + "a" + ] + }, + { + "source": 0, + "target": 1, + "properties": [ + "a" + ] + }, + { + "source": 2, + "target": 0, + "properties": [ + "a" + ] + }, + { + "source": 3, + "target": 2, + "properties": [ + "b" + ] + }, + { + "source": 2, + "target": 3, + "properties": [ + "b" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/same-number-of-zeros-and-ones-by-empty-stack.atmtn b/src/test/resources/same-number-of-zeros-and-ones-by-empty-stack.atmtn new file mode 100644 index 0000000..8e6573e --- /dev/null +++ b/src/test/resources/same-number-of-zeros-and-ones-by-empty-stack.atmtn @@ -0,0 +1,68 @@ +{ + "base": { + "type": "pushdown-automaton", + "inputTape": { + }, + "stacks": [ + { + "acceptsByEmptyStack": true, + "value": "$" + } + ] + }, + "vertices": [ + { + "type": "state", + "id": 1, + "name": "S2", + "x": 500049.9733633101, + "y": 500174.9733633101 + }, + { + "type": "state", + "id": 0, + "name": "S1", + "x": 499575.0165449231, + "y": 500172.5165449231, + "isInitial": true + } + ], + "transitions": [ + { + "source": 0, + "target": 0, + "properties": [ + "0", + "ε", + "0" + ] + }, + { + "source": 0, + "target": 1, + "properties": [ + "ε", + "ε", + "ε" + ] + }, + { + "source": 1, + "target": 1, + "properties": [ + "ε", + "$", + "ε" + ] + }, + { + "source": 1, + "target": 1, + "properties": [ + "1", + "0", + "ε" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/same-number-of-zeros-and-ones.atmtn b/src/test/resources/same-number-of-zeros-and-ones.atmtn new file mode 100644 index 0000000..12dafe9 --- /dev/null +++ b/src/test/resources/same-number-of-zeros-and-ones.atmtn @@ -0,0 +1,93 @@ +{ + "base": { + "type": "pushdown-automaton", + "inputTape": { + "value": "0011" + }, + "stacks": [ + { + "acceptsByEmptyStack": false, + "value": "" + } + ] + }, + "vertices": [ + { + "type": "state", + "id": 1, + "name": "S1", + "x": 499575.0165449231, + "y": 500172.5165449231 + }, + { + "type": "state", + "id": 3, + "name": "S3", + "x": 500537.4899082334, + "y": 500172.4899082334, + "isFinal": true + }, + { + "type": "state", + "id": 0, + "name": "S0", + "x": 499082.5, + "y": 500177.5, + "isInitial": true + }, + { + "type": "state", + "id": 2, + "name": "S2", + "x": 500049.9733633101, + "y": 500174.9733633101 + } + ], + "transitions": [ + { + "source": 2, + "target": 3, + "properties": [ + "ε", + "$", + "ε" + ] + }, + { + "source": 1, + "target": 1, + "properties": [ + "0", + "ε", + "0" + ] + }, + { + "source": 0, + "target": 1, + "properties": [ + "ε", + "ε", + "$" + ] + }, + { + "source": 1, + "target": 2, + "properties": [ + "ε", + "ε", + "ε" + ] + }, + { + "source": 2, + "target": 2, + "properties": [ + "1", + "0", + "ε" + ] + } + ] +} \ No newline at end of file diff --git a/wiki/examples.gif b/wiki/examples.gif new file mode 100644 index 0000000..3e7fb8e Binary files /dev/null and b/wiki/examples.gif differ diff --git a/wiki/testing_panel.gif b/wiki/testing_panel.gif new file mode 100644 index 0000000..9a95696 Binary files /dev/null and b/wiki/testing_panel.gif differ diff --git a/wiki/visual_editor.png b/wiki/visual_editor.png index edcd57c..960d579 100644 Binary files a/wiki/visual_editor.png and b/wiki/visual_editor.png differ