SEATC 0.2.25 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/Automaton Analyzer.rb +57 -13
- data/lib/Grammar Analyzer.rb +31 -7
- data/lib/Grammar Reader.rb +20 -8
- data/lib/PDA.rb +61 -9
- data/lib/Regular Expression.rb +15 -2
- data/lib/Regular Grammar Analyzer.rb +47 -7
- data/lib/Turing Analyzer.rb +273 -229
- data/lib/Turing Reader.rb +4 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ee0f7663dc5ac8a81806784220d5baefe59928d
|
4
|
+
data.tar.gz: e63d166143186e066c83810bff146b916c86b4e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94f842f9b803d0466d77012e132973a52c6fb63ec78dff55f2eebef5036f8f46ec70e1c6af944b557ce4e8d8053296d356a2c1dd54a33b116a825a93870ef397
|
7
|
+
data.tar.gz: 18c030b8a471dc94354a2223618c6ce9b37dee08eb139a0d0a1fc60257586727fa3de7c83b79eea2975a07a39898a84bcd20b5b6f23017447ea7068bd036f769
|
data/lib/Automaton Analyzer.rb
CHANGED
@@ -1,52 +1,85 @@
|
|
1
|
+
# Automaton Analyzer State
|
1
2
|
class AutomatonState
|
2
3
|
@id
|
3
4
|
@nombre
|
4
5
|
@transitions
|
5
6
|
@final
|
7
|
+
# @param id [Integer] ID for the state
|
8
|
+
# @param nombre [String] Name of the state (JFlap Label)
|
9
|
+
# @param final [Boolean] Determine if the state is final state
|
6
10
|
def initialize(id, nombre, final)
|
7
11
|
@id = id
|
8
12
|
@nombre = nombre
|
9
13
|
@transitions = Hash.new
|
10
14
|
@final = final
|
11
15
|
end
|
16
|
+
# Add a transition to the state
|
17
|
+
# @param char [String] Transition value
|
18
|
+
# @param to [Integer] State where the transition goes
|
19
|
+
# @return [Void]
|
12
20
|
def addTrans(char, to)
|
13
21
|
if @transitions[char] == nil
|
14
22
|
@transitions[char] = Hash.new
|
15
23
|
end
|
16
24
|
@transitions[char][to] = to
|
17
25
|
end
|
26
|
+
# Return the list of transitions
|
27
|
+
# @param char [String] List of transitions for an specific input
|
28
|
+
# @return [Hash] List of transitions
|
18
29
|
def transAt(char)
|
19
30
|
ret = @transitions[char]
|
20
31
|
end
|
32
|
+
# Return all the transitions for the state
|
33
|
+
# @return [Hash] List of all transitions
|
21
34
|
def getTrans
|
22
35
|
return @transitions
|
23
36
|
end
|
37
|
+
# Return name of the state
|
38
|
+
# @return [String] Name of the state
|
24
39
|
def getName
|
25
40
|
return @nombre
|
26
41
|
end
|
42
|
+
# Return if the state is final state
|
43
|
+
# @return [Boolean] 1 if the state is final, 0 otherwise
|
27
44
|
def final()
|
28
45
|
ret = @final
|
29
46
|
end
|
30
47
|
end
|
31
48
|
|
49
|
+
# Analyzer for both kind of automatons: deterministic and non-deterministic
|
50
|
+
# First create the analyzer with new,
|
51
|
+
# then use the analyze method to know if the string is accepted or not
|
52
|
+
# @attr valid [Boolean] Determine if a string is valid or not in certain time of the instance
|
53
|
+
# @attr errno [Integer] Identifier for some errors on the analyzer
|
54
|
+
# @attr deterministic [Boolean] Determine if {#isDeterministic} check is needed
|
32
55
|
class AutomatonAnalyzer
|
33
56
|
attr_accessor :valid, :deterministic, :errno
|
57
|
+
# Return errno of the analyzer
|
58
|
+
# @return [Boolean] 0 errno is that the analyzer completed with no error, otherwise it is different to 0
|
34
59
|
def errno()
|
35
60
|
return @errno
|
36
61
|
end
|
37
|
-
|
62
|
+
# Initialize the analyzer
|
63
|
+
# @param deterministic [Boolean] Define if the Automaton will be deterministic or non-deterministic
|
64
|
+
def initialize(deterministic)
|
38
65
|
@deterministic = deterministic
|
39
66
|
@errno = 0
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
67
|
+
end
|
68
|
+
# Check if the automaton is deterministic
|
69
|
+
# @param states [Array] List of automaton's transitions
|
70
|
+
# @return [Boolean] 1 if the automaton is deterministic, 0 otherwise
|
71
|
+
def isDeterministic(states)
|
72
|
+
return false if !@deterministic
|
73
|
+
states.each do |estado|
|
74
|
+
transiciones = estado.getTrans
|
75
|
+
transiciones.each do |transition|
|
76
|
+
return false if estado.transAt(transition[0]).length > 1
|
48
77
|
end
|
49
78
|
end
|
79
|
+
end
|
80
|
+
# Return state id from a read line
|
81
|
+
# @param modified [String] Line where ID of the state is contained
|
82
|
+
# @return [Integer] State ID
|
50
83
|
def getStateID(modified)
|
51
84
|
modified.slice! "\t\t<state id=\""
|
52
85
|
lastQuota = true
|
@@ -60,7 +93,9 @@ class AutomatonAnalyzer
|
|
60
93
|
end
|
61
94
|
id = id
|
62
95
|
end
|
63
|
-
|
96
|
+
# Return state name from a read line
|
97
|
+
# @param modified [String] Line where name of the state is contained
|
98
|
+
# @return [String] State name
|
64
99
|
def getStateName(modified)
|
65
100
|
modified.slice! "\" name=\""
|
66
101
|
lastQuota = true
|
@@ -72,7 +107,9 @@ class AutomatonAnalyzer
|
|
72
107
|
end
|
73
108
|
id = id
|
74
109
|
end
|
75
|
-
|
110
|
+
# Return the next state of the transition
|
111
|
+
# @param modified [String] Line where next state is contained is contained
|
112
|
+
# @return [Integer] Next state of a transition
|
76
113
|
def readTransInt(modified)
|
77
114
|
lastQuota = true
|
78
115
|
id = 0
|
@@ -86,7 +123,11 @@ class AutomatonAnalyzer
|
|
86
123
|
id = id
|
87
124
|
|
88
125
|
end
|
89
|
-
|
126
|
+
# Return if a string is valid for an automaton
|
127
|
+
# @param initial [AutomatonState] Initial state
|
128
|
+
# @param states [Array[AutomatonState]] List of transitions
|
129
|
+
# @param line [String] String to be validated
|
130
|
+
# @return [Boolean] True if the string is accepted, false otherwise
|
90
131
|
def validateString(initial, states, line)
|
91
132
|
st = initial
|
92
133
|
usedLine = String.new line
|
@@ -106,7 +147,10 @@ class AutomatonAnalyzer
|
|
106
147
|
end
|
107
148
|
end
|
108
149
|
|
109
|
-
#Returns true if the automaton ends in final state, returns false otherwise
|
150
|
+
# Returns true if the automaton ends in final state, returns false otherwise
|
151
|
+
# @param sourceFile [String] JFLAP file location
|
152
|
+
# @param testString [String] String to be analyzed
|
153
|
+
# @return [Boolean] True if the string is accepted, False otherwise
|
110
154
|
def analyze(sourceFile, testString)
|
111
155
|
file = File.new(sourceFile, "r")
|
112
156
|
line = file.read
|
data/lib/Grammar Analyzer.rb
CHANGED
@@ -1,34 +1,50 @@
|
|
1
1
|
require 'thread'
|
2
2
|
load 'Grammar Reader.rb'
|
3
3
|
|
4
|
+
# Analyzer for grammars
|
5
|
+
# For using it first creates the analyzer with new,
|
6
|
+
# then use the analyze method to know if the string is accepted or not
|
7
|
+
# @attr mutex [Object] Mutex for managing concurrency
|
8
|
+
# @attr valid [Boolean] Determine if a string is valid or not in certain time of the instance
|
9
|
+
# @attr errno [Integer] Identifier for some errors on the analyzer
|
4
10
|
class GrammarAnalyzer
|
5
11
|
attr_accessor :mutex, :valid, :errno
|
6
|
-
|
12
|
+
# Return errno of the analyzer
|
13
|
+
# @return [Integer] 0 errno is that the analyzer completed with no error, otherwise it is different to 0
|
7
14
|
def errno()
|
8
15
|
return @errno
|
9
16
|
end
|
10
|
-
|
17
|
+
# Initialize the analyzer
|
11
18
|
def initialize
|
12
19
|
@mutex = Mutex.new
|
13
20
|
@valid = false
|
14
21
|
end
|
15
|
-
|
22
|
+
# Synchronized println in case of need
|
23
|
+
# @param s [String] String to be printed
|
24
|
+
# @return [Void]
|
16
25
|
def println(s)
|
17
26
|
@mutex.synchronize {
|
18
27
|
puts s
|
19
28
|
}
|
20
29
|
end
|
21
|
-
|
30
|
+
# Check if a char is non-terminal
|
31
|
+
# @param char Char to be checked
|
32
|
+
# @return [bool] True if the char is non-terminal, False otherwise
|
22
33
|
def isNT(char)
|
23
34
|
return "A" <= char && char <= "Z"
|
24
35
|
end
|
25
|
-
|
36
|
+
# Method for removing the first char of a string
|
37
|
+
# @param line [String] String to be modified
|
38
|
+
# @return [String] Modified string
|
26
39
|
def removeFirst(line)
|
27
40
|
line.reverse!
|
28
41
|
line.chop!
|
29
42
|
line.reverse!
|
30
43
|
end
|
31
|
-
|
44
|
+
# Determine if the analyzer must continue or not
|
45
|
+
# @param prod [String] Actual produced line
|
46
|
+
# @param line [String] Final line to be matched
|
47
|
+
# @return [Boolean] True if analyzer can continue, False otherwise
|
32
48
|
def canContinue(prod, line)
|
33
49
|
p = String.new prod
|
34
50
|
s = 0
|
@@ -38,6 +54,11 @@ class GrammarAnalyzer
|
|
38
54
|
return s <= line.length
|
39
55
|
end
|
40
56
|
|
57
|
+
# Threaded method to determine if the string is accepted or not
|
58
|
+
# @param prod [String] Actual produced line
|
59
|
+
# @param line [String] String to be matched
|
60
|
+
# @param prods [Hash] Productions of the grammar
|
61
|
+
# @return [Void]
|
41
62
|
def analyzer(prod, line, prods)
|
42
63
|
while canContinue(prod, line)
|
43
64
|
if prod.length == 0
|
@@ -65,7 +86,10 @@ class GrammarAnalyzer
|
|
65
86
|
end
|
66
87
|
end
|
67
88
|
end
|
68
|
-
|
89
|
+
# Determine if a string is valid for a Grammar
|
90
|
+
# @param file [String] JFLAP file location
|
91
|
+
# @param line [String] String to be checked
|
92
|
+
# @return [Boolean] True if the string can be matched, False otherwise
|
69
93
|
def analyze(file, line)
|
70
94
|
reader = GrammarReader.new
|
71
95
|
prods = reader.createProductions(reader.readFile(file))
|
data/lib/Grammar Reader.rb
CHANGED
@@ -1,16 +1,24 @@
|
|
1
|
+
# Reader for JFLAP Grammar file
|
2
|
+
# @attr error [Integer] Identifier for certain errors on reading
|
1
3
|
class GrammarReader
|
2
|
-
|
3
|
-
|
4
|
+
# Grammar Reader SYNTAX ERROR
|
5
|
+
SYNTAX_ERROR = 100
|
6
|
+
# Grammar Reader PRODUCTION ERROR
|
7
|
+
PRODUCTION_ERROR = 101
|
4
8
|
attr_accessor :error
|
5
|
-
|
6
9
|
def initialize()
|
7
10
|
@error = 0
|
8
11
|
end
|
12
|
+
# Return the left part of a production
|
13
|
+
# @param modified [String] String containing the left part of production
|
14
|
+
# @return [String] Left part of production
|
9
15
|
def getLeft(modified)
|
10
16
|
modified.slice! "\t\t<left>"
|
11
17
|
ret = modified[0]
|
12
18
|
end
|
13
|
-
|
19
|
+
# Return the right part of a production
|
20
|
+
# @param modified [String] String containing the right part of production
|
21
|
+
# @return [String] Right part of production
|
14
22
|
def getRight(modified)
|
15
23
|
modified.slice! "\t\t<right>"
|
16
24
|
ret = ""
|
@@ -22,7 +30,9 @@ class GrammarReader
|
|
22
30
|
return ret
|
23
31
|
end
|
24
32
|
|
25
|
-
#
|
33
|
+
# Returns an Array containing the lines from the file
|
34
|
+
# @param file [String] File location
|
35
|
+
# @return [Array[String]] Array containing lines in file
|
26
36
|
def readFile(file)
|
27
37
|
file = File.new(file, "r")
|
28
38
|
line = file.read
|
@@ -30,15 +40,17 @@ class GrammarReader
|
|
30
40
|
lines = line.split("\n")
|
31
41
|
end
|
32
42
|
|
33
|
-
#
|
43
|
+
# Returns a Hash containing the productions
|
44
|
+
# @param lines [Array[String]] Separated lines of the file
|
45
|
+
# @return [Hash] Hash containing in key the left part of the production, and in value an Array with the right parts of the production
|
34
46
|
def createProductions(lines)
|
35
47
|
productions = Hash.new
|
36
48
|
if !lines[0].include? "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><!--Created with JFLAP"
|
37
|
-
@error =
|
49
|
+
@error = SYNTAX_ERROR
|
38
50
|
return productions
|
39
51
|
end
|
40
52
|
if !lines[1].include? "\t<type>grammar</type>"
|
41
|
-
@error =
|
53
|
+
@error = SYNTAX_ERROR
|
42
54
|
return productions
|
43
55
|
end
|
44
56
|
counter = 3
|
data/lib/PDA.rb
CHANGED
@@ -1,51 +1,87 @@
|
|
1
|
+
# Transition of Pushdown Automaton
|
2
|
+
# @attr destination [Integer] Next state in the transition
|
3
|
+
# @attr push [String] Elements to push in stack
|
4
|
+
# @attr pop [String] Element to pop in stack
|
1
5
|
class PDATransition
|
2
6
|
attr_accessor :destination, :push, :pop
|
7
|
+
# @param destination [Integer] Destination of the transition
|
8
|
+
# @param push [String] String to be pushed on stack
|
9
|
+
# @param pop [String] String to be popped of the stack
|
3
10
|
def initialize(destination, push, pop)
|
4
11
|
@destination = destination
|
5
12
|
@push = push
|
6
13
|
@pop = pop
|
7
14
|
end
|
15
|
+
# Return the destination
|
16
|
+
# @return [Integer] Next destination
|
8
17
|
def getDestination()
|
9
18
|
return @destination
|
10
19
|
end
|
11
20
|
end
|
12
21
|
|
22
|
+
# Pushdown Automaton state
|
23
|
+
# @attr id [Integer] ID of the state
|
24
|
+
# @attr nombre [String] Name of the state
|
25
|
+
# @attr transitions [Hash] Transitions of the state
|
26
|
+
# @attr final [Boolean] Determine if the state is final or not
|
13
27
|
class PDAState
|
14
28
|
@id
|
15
29
|
@nombre
|
16
30
|
@transitions
|
17
31
|
@final
|
32
|
+
# @param id [Integer] ID of the state
|
33
|
+
# @param nombre [String] Name of the state
|
34
|
+
# @param final [Boolean] The state is final or not
|
18
35
|
def initialize(id, nombre, final)
|
19
36
|
@id = id
|
20
37
|
@nombre = nombre
|
21
38
|
@transitions = Hash.new
|
22
39
|
@final = final
|
23
40
|
end
|
41
|
+
# Add a transition with a determinated input
|
42
|
+
# @param char [String] Key where the transition will be added
|
43
|
+
# @param to [Integer] ID of the next state
|
44
|
+
# @param pop [String] Value to be popped of the stack
|
45
|
+
# @param push [String] Value to be pushed on the stack
|
46
|
+
# @return [Void]
|
24
47
|
def addTrans(char, to, pop, push)
|
25
48
|
if @transitions[char] == nil
|
26
49
|
@transitions[char] = Hash.new
|
27
50
|
end
|
28
51
|
@transitions[char][to] = PDATransition.new to, push, pop
|
29
52
|
end
|
53
|
+
# Return the transition for a determinated input
|
54
|
+
# @param char [String] Input to be searched
|
55
|
+
# @return [Hash] Transitions for the input
|
30
56
|
def transAt(char)
|
31
57
|
ret = @transitions[char]
|
32
58
|
end
|
59
|
+
|
60
|
+
# Determine if the state is final or not
|
61
|
+
# @return [Boolean] True if the state is final, False otherwise
|
33
62
|
def final()
|
34
63
|
ret = @final
|
35
64
|
end
|
36
65
|
end
|
37
66
|
|
67
|
+
# Pushdown Automaton Analyzer
|
68
|
+
# @attr valid [Boolean] Determine if the analyzer has been invalidated or not
|
69
|
+
# @attr errno [Integer] Identifier for errors
|
38
70
|
class PDAAnalyzer
|
39
71
|
@valid = false
|
40
72
|
attr_accessor :errno
|
73
|
+
# Determine error number
|
74
|
+
# @return [Integer] Identifier of the error (0 -> No error)
|
41
75
|
def errno()
|
42
76
|
return @errno
|
43
77
|
end
|
44
|
-
|
78
|
+
|
45
79
|
def initialize
|
46
80
|
@errno = 0
|
47
81
|
end
|
48
|
-
|
82
|
+
# Get a state ID
|
83
|
+
# @param modified [String] String containing the state ID
|
84
|
+
# @return [Integer] State ID
|
49
85
|
def getStateID(modified)
|
50
86
|
modified.slice! "\t\t<state id=\""
|
51
87
|
lastQuota = true
|
@@ -59,7 +95,9 @@ class PDAAnalyzer
|
|
59
95
|
end
|
60
96
|
id = id
|
61
97
|
end
|
62
|
-
|
98
|
+
# Get a state name
|
99
|
+
# @param modified [String] String containing the state name
|
100
|
+
# @return [String] State name
|
63
101
|
def getStateName(modified)
|
64
102
|
modified.slice! "\" name=\""
|
65
103
|
lastQuota = true
|
@@ -71,7 +109,9 @@ class PDAAnalyzer
|
|
71
109
|
end
|
72
110
|
id = id
|
73
111
|
end
|
74
|
-
|
112
|
+
# Get a transition push
|
113
|
+
# @param modified [String] String containing the transition push
|
114
|
+
# @return [String] Transtion push
|
75
115
|
def readPush(modified)
|
76
116
|
lastQuota = true
|
77
117
|
id = ""
|
@@ -82,7 +122,9 @@ class PDAAnalyzer
|
|
82
122
|
end
|
83
123
|
id = id
|
84
124
|
end
|
85
|
-
|
125
|
+
# Get a transition pop
|
126
|
+
# @param modified [String] String containing the transition pop
|
127
|
+
# @return [String] Transtion pop
|
86
128
|
def readPop(modified)
|
87
129
|
lastQuota = true
|
88
130
|
id = ""
|
@@ -93,7 +135,9 @@ class PDAAnalyzer
|
|
93
135
|
end
|
94
136
|
id = id
|
95
137
|
end
|
96
|
-
|
138
|
+
# Get a transition next state
|
139
|
+
# @param modified [String] String containing the transition next state
|
140
|
+
# @return [Integer] Transtion next state
|
97
141
|
def readTransInt(modified)
|
98
142
|
lastQuota = true
|
99
143
|
id = 0
|
@@ -105,9 +149,13 @@ class PDAAnalyzer
|
|
105
149
|
id = id + char.to_i
|
106
150
|
end
|
107
151
|
id = id
|
108
|
-
|
109
152
|
end
|
110
|
-
|
153
|
+
# Validate an input string
|
154
|
+
# @param initial [PDAState] Initial state
|
155
|
+
# @param states [Hash] Transitions of the automaton
|
156
|
+
# @param line [String] Line to be validated
|
157
|
+
# @param stack [Array] Stack of the automaton
|
158
|
+
# @return [Boolean] True if the string was accepted, False otherwise
|
111
159
|
def validateString(initial, states, line, stack)
|
112
160
|
st = initial
|
113
161
|
usedLine = String.new line
|
@@ -142,8 +190,12 @@ class PDAAnalyzer
|
|
142
190
|
end
|
143
191
|
end
|
144
192
|
|
145
|
-
#
|
193
|
+
# Analyze a JFLAP file for Pushdown automaton and determine if a string is valid for that automaton
|
194
|
+
# @param sourceFile [String] JFLAP File location
|
195
|
+
# @param testString [String] String to be validated
|
196
|
+
# @return [Boolean] True if the string was accepted, false otherwise
|
146
197
|
def analyze(sourceFile, testString)
|
198
|
+
@valid = false
|
147
199
|
file = File.new(sourceFile, "r")
|
148
200
|
line = file.read
|
149
201
|
file.close
|
data/lib/Regular Expression.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
+
# Regular Expression Analyzer
|
2
|
+
# @attr regex [Regexp] Regular Expression
|
3
|
+
# @attr errno [Integer] Identifier for errors
|
1
4
|
class RegularExpression
|
2
5
|
@regex
|
3
6
|
attr_accessor :errno
|
7
|
+
# Identifier for errors
|
8
|
+
# @return [Integer] If there are errors value is more than 0, otherwise is 0
|
4
9
|
def errno()
|
5
10
|
return @errno
|
6
11
|
end
|
@@ -8,14 +13,18 @@ class RegularExpression
|
|
8
13
|
def initialize
|
9
14
|
@errno = 0
|
10
15
|
end
|
11
|
-
|
16
|
+
# File reader
|
17
|
+
# @param file [String] File location
|
18
|
+
# @return [Array[String]] Array containing separated lines of the file
|
12
19
|
def readFile(file)
|
13
20
|
file = File.new(file, "r")
|
14
21
|
line = file.read
|
15
22
|
file.close
|
16
23
|
lines = line.split("\n")
|
17
24
|
end
|
18
|
-
|
25
|
+
# Regular expression creation
|
26
|
+
# @param lines [Array[String]] Lines of the file
|
27
|
+
# @return [Void]
|
19
28
|
def createRegex(lines)
|
20
29
|
regex = ""
|
21
30
|
# if lines[0].contains? "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>") != nil
|
@@ -39,6 +48,10 @@ class RegularExpression
|
|
39
48
|
regex.reverse!
|
40
49
|
@regex = Regexp.new "^" + regex + "$"
|
41
50
|
end
|
51
|
+
# Determine if a string is accepted by a regular expression
|
52
|
+
# @param file [String] JFLAP file location
|
53
|
+
# @param input [String] String to be validated
|
54
|
+
# @return [Boolean] True if string was accepted, False otherwise
|
42
55
|
def analyze(file, input)
|
43
56
|
createRegex(readFile(file))
|
44
57
|
return false if @errno != 0
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# Regular Grammar Production, since it is deprecated it is needed to add a Regular Grammar Checker to {#GrammarAnalyzer}
|
2
|
+
# @deprecated
|
3
|
+
# @attr left [String] Left part of production
|
4
|
+
# @attr right [Array] Right part of production
|
1
5
|
class RegularGrammarProduction
|
2
6
|
@left
|
3
7
|
@right
|
@@ -5,20 +9,32 @@ class RegularGrammarProduction
|
|
5
9
|
@left = left
|
6
10
|
@right = []
|
7
11
|
end
|
12
|
+
# Add element to right production
|
13
|
+
# @param right [String] New production to push
|
14
|
+
# @return [Void]
|
8
15
|
def addRight(right)
|
9
16
|
@right.push(right)
|
10
17
|
end
|
18
|
+
# Get elements from right production
|
19
|
+
# @return [Array[String]] productions
|
11
20
|
def getRight()
|
12
21
|
return @right
|
13
22
|
end
|
14
23
|
end
|
15
24
|
|
16
|
-
|
17
|
-
|
18
|
-
|
25
|
+
# Regular Grammar Analyzer, since it is deprecated it is needed to add a Regular Grammar Checker to {#GrammarAnalyzer}
|
26
|
+
# @deprecated
|
27
|
+
# @attr valid [Boolean] Determine if the string is valid in a certain time
|
28
|
+
# @attr errno [Integer] Identify errors
|
19
29
|
class RegularGrammar
|
20
30
|
@valid
|
21
31
|
attr_accessor :errno
|
32
|
+
# Regular Grammar SYNTAX ERROR Identificator
|
33
|
+
SYNTAX_ERROR = 1
|
34
|
+
# Regular Grammar PRODUCTION ERROR Identificator
|
35
|
+
PRODUCTION_ERROR = 2
|
36
|
+
#Identify errors
|
37
|
+
# @return [Integer] 0 if no errors, different from 0 otherwise
|
22
38
|
def errno()
|
23
39
|
return @errno
|
24
40
|
end
|
@@ -27,11 +43,16 @@ class RegularGrammar
|
|
27
43
|
@valid = false
|
28
44
|
@errno = 0
|
29
45
|
end
|
46
|
+
# Get left part of production
|
47
|
+
# @param modified [String] String containing left part of production
|
48
|
+
# @return [String] Left part of production
|
30
49
|
def getLeft(modified)
|
31
50
|
modified.slice! "\t\t<left>"
|
32
51
|
ret = modified[0]
|
33
52
|
end
|
34
|
-
|
53
|
+
# Get right part of production
|
54
|
+
# @param modified [String] String containing right part of production
|
55
|
+
# @return [String] Right part of production
|
35
56
|
def getRight(modified)
|
36
57
|
modified.slice! "\t\t<right>"
|
37
58
|
ret = ""
|
@@ -43,7 +64,9 @@ class RegularGrammar
|
|
43
64
|
return ret
|
44
65
|
end
|
45
66
|
|
46
|
-
#
|
67
|
+
# Returns an Array containing the lines from the file
|
68
|
+
# @param file [String] File location
|
69
|
+
# @return [Array[String]] Separated lines of the file
|
47
70
|
def readFile(file)
|
48
71
|
file = File.new(file, "r")
|
49
72
|
line = file.read
|
@@ -51,7 +74,9 @@ class RegularGrammar
|
|
51
74
|
lines = line.split("\n")
|
52
75
|
end
|
53
76
|
|
54
|
-
#
|
77
|
+
# Returns the Hash containing the productions
|
78
|
+
# @param lines [Array[String]] Separated lines of JFLAP file
|
79
|
+
# @return [Hash] Productions of the grammar
|
55
80
|
def createProductions(lines)
|
56
81
|
productions = Hash.new
|
57
82
|
if !lines[0].include? "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
|
@@ -87,18 +112,29 @@ class RegularGrammar
|
|
87
112
|
return productions
|
88
113
|
end
|
89
114
|
|
115
|
+
# Verify lambda production
|
116
|
+
# @param string [String] String to be verified
|
117
|
+
# @return [Void]
|
90
118
|
def chompLambda(string)
|
91
119
|
if !@valid
|
92
120
|
@valid = (string.length==0)
|
93
121
|
end
|
94
122
|
end
|
95
|
-
|
123
|
+
|
124
|
+
# Verify only terminal production
|
125
|
+
# @param string [String] String to be verified
|
126
|
+
# @return [Void]
|
96
127
|
def chompChar(string, char)
|
97
128
|
if !@valid
|
98
129
|
@valid = (string.length == 1 && string[0] == char)
|
99
130
|
end
|
100
131
|
end
|
101
132
|
|
133
|
+
# Analyze an input
|
134
|
+
# @param productions [Hash] Grammar productions
|
135
|
+
# @param line [String] String to be verified
|
136
|
+
# @param state [RegularGrammarState] Actual State
|
137
|
+
# @return [Boolean] True if string is accepted, False otherwise
|
102
138
|
def analyzeInput(productions, line, state)
|
103
139
|
# Right Grammar S -> aS | a
|
104
140
|
prods = productions[state].getRight()
|
@@ -174,6 +210,10 @@ class RegularGrammar
|
|
174
210
|
end
|
175
211
|
end
|
176
212
|
|
213
|
+
# Analyze a string from a JFLAP file
|
214
|
+
# @param file [String] JFLAP File location
|
215
|
+
# @param line [String] String to be verified
|
216
|
+
# @return [Boolean] True if string is accepted, False otherwise
|
177
217
|
def analyze(file, line)
|
178
218
|
productions = createProductions(readFile(file))
|
179
219
|
@valid = analyzeInput(productions, line, "S")
|
data/lib/Turing Analyzer.rb
CHANGED
@@ -1,262 +1,306 @@
|
|
1
1
|
require 'thread'
|
2
2
|
load 'Turing Reader.rb'
|
3
|
+
# Turing machine state
|
4
|
+
# @attr tag [String] Tag of the state
|
5
|
+
# @attr id [Integer] ID of the state
|
6
|
+
# @attr name [String] Name of the state
|
7
|
+
# @attr initial [Boolean] Determine if state is initial
|
8
|
+
# @attr final [Boolean] Determine if the state is final
|
9
|
+
# @attr transitions [Hash] List of transitions
|
3
10
|
class TuringState
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
11
|
+
attr_accessor :tag, :id, :name, :initial, :final, :transitions
|
12
|
+
def initialize
|
13
|
+
@transitions = Hash.new
|
14
|
+
end
|
8
15
|
end
|
9
16
|
|
17
|
+
# Turing Machine Transition
|
18
|
+
# @attr to [Integer] Next state
|
19
|
+
# @attr write [String] Element to write on the Turing machine tape
|
20
|
+
# @attr write [String] Position to move in the Turing machine tape
|
10
21
|
class TuringTransition
|
11
22
|
attr_accessor :to, :write, :move
|
12
23
|
end
|
13
24
|
|
25
|
+
# Turing Machine Analyzer
|
26
|
+
# @attr reader [TuringReader] File reader
|
27
|
+
# @attr lines [Array[String]] Separated lines of the file
|
28
|
+
# @attr states [Array[TuringState]] List of states
|
29
|
+
# @attr valid [Boolean] Determine if there is a valid string
|
30
|
+
# @attr errno [Integer] Identifier for errors
|
31
|
+
# @attr initial [TuringState] Initial State
|
14
32
|
class TuringAnalyzer
|
15
|
-
|
33
|
+
attr_accessor :reader, :lines, :states, :valid, :errno
|
34
|
+
@initial
|
16
35
|
|
36
|
+
# Determine the kind of error in the file
|
37
|
+
# @return [Integer] More than 0 for error, 0 for no error
|
17
38
|
def errno()
|
18
39
|
return @errno
|
19
|
-
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@reader = TuringReader.new
|
44
|
+
@states = Array.new
|
45
|
+
@initial = nil
|
46
|
+
@valid = false
|
47
|
+
@errno = 0
|
48
|
+
end
|
20
49
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
50
|
+
# Analyze a string using a JFLAP Turing machine
|
51
|
+
# @param file [String] JFLAP file location
|
52
|
+
# @param line [String] String to be validated
|
53
|
+
# @return [Boolean] True if the string is accepted, False otherwise
|
54
|
+
def analyze(file, line)
|
55
|
+
@valid = false
|
56
|
+
@lines = @reader.readFile file
|
57
|
+
validateJFlap
|
58
|
+
deleteLine
|
59
|
+
validateTuringMachine
|
60
|
+
deleteLine
|
61
|
+
createStates
|
62
|
+
position = 0
|
63
|
+
until line[position] != "B"
|
64
|
+
position = position + 1
|
28
65
|
end
|
66
|
+
validate line, @initial, position
|
67
|
+
return @valid
|
68
|
+
end
|
29
69
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
70
|
+
# Check if the file is from JFLAP
|
71
|
+
# @return [Boolean] True if file is from JFLAP, False otherwise
|
72
|
+
def validateJFlap
|
73
|
+
return true if @lines[0].match "JFLAP"
|
74
|
+
@errno = 1
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
|
78
|
+
# Check if the file is from JFLAP Turing Machine
|
79
|
+
# @return [Boolean] True if file is from JFLAP Turing machine, False otherwise
|
80
|
+
def validateTuringMachine
|
81
|
+
return true if @lines[0].match "turing"
|
82
|
+
@errno = 2
|
83
|
+
return false
|
84
|
+
end
|
85
|
+
|
86
|
+
# Create the list of states
|
87
|
+
# @return [Void]
|
88
|
+
def createStates
|
89
|
+
return false if !lines[0].match "automaton"
|
90
|
+
deleteLine
|
91
|
+
if lines[0].match "<!--The list of states.-->"
|
35
92
|
deleteLine
|
36
|
-
|
37
|
-
|
38
|
-
until line[position] != "B"
|
39
|
-
position = position + 1
|
40
|
-
end
|
41
|
-
validate line, @initial, position
|
42
|
-
return @valid
|
93
|
+
else
|
94
|
+
return false
|
43
95
|
end
|
44
|
-
|
45
|
-
|
46
|
-
return true if @lines[0].match "JFLAP"
|
47
|
-
@errno = 1
|
48
|
-
return false
|
96
|
+
while createState
|
97
|
+
i = 1
|
49
98
|
end
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
return false
|
99
|
+
if lines[0].match "<!--The list of transitions.-->"
|
100
|
+
deleteLine
|
101
|
+
else
|
102
|
+
return false
|
55
103
|
end
|
56
|
-
|
57
|
-
|
58
|
-
return false if !lines[0].match "automaton"
|
59
|
-
deleteLine
|
60
|
-
if lines[0].match "<!--The list of states.-->"
|
61
|
-
deleteLine
|
62
|
-
else
|
63
|
-
return false
|
64
|
-
end
|
65
|
-
while createState
|
66
|
-
end
|
67
|
-
if lines[0].match "<!--The list of transitions.-->"
|
68
|
-
deleteLine
|
69
|
-
else
|
70
|
-
return false
|
71
|
-
end
|
72
|
-
until createTransition == false
|
73
|
-
end
|
74
|
-
puts @states.to_s
|
104
|
+
until createTransition == false
|
105
|
+
i = 1
|
75
106
|
end
|
107
|
+
end
|
76
108
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
# Tags
|
100
|
-
return false if !lines[0].match "tag"
|
101
|
-
deleteLine
|
102
|
-
# X position
|
103
|
-
deleteLine
|
104
|
-
# Y position
|
105
|
-
deleteLine
|
106
|
-
# Initial?
|
107
|
-
state.initial = false
|
108
|
-
if lines[0].match "initial"
|
109
|
-
@initial = state.id if @initial == nil
|
110
|
-
state.initial = true
|
111
|
-
deleteLine
|
112
|
-
end
|
113
|
-
# Final?
|
114
|
-
state.final = false
|
115
|
-
if lines[0].match "final"
|
116
|
-
state.final = true
|
117
|
-
deleteLine
|
118
|
-
end
|
119
|
-
# Check closing bracket
|
120
|
-
return false if !lines[0].match "</block>"
|
121
|
-
deleteLine
|
122
|
-
@states[state.id] = state
|
123
|
-
return true
|
109
|
+
# Create a Turing state
|
110
|
+
# return [Boolean] False if there is an error, True otherwise
|
111
|
+
def createState
|
112
|
+
state = TuringState.new
|
113
|
+
return false if !lines[0].match "block"
|
114
|
+
deleteTabs
|
115
|
+
lines[0].slice! "<block "
|
116
|
+
# Create id
|
117
|
+
state.id = 0
|
118
|
+
lines[0].slice! "id=\""
|
119
|
+
until lines[0][0] == "\""
|
120
|
+
state.id = state.id * 10
|
121
|
+
state.id = state.id + lines[0][0].to_i
|
122
|
+
lines[0].slice! lines[0][0]
|
123
|
+
end
|
124
|
+
lines[0].slice! "\" "
|
125
|
+
# Create name
|
126
|
+
state.name = ""
|
127
|
+
lines[0].slice! "name=\""
|
128
|
+
until lines[0][0] == "\""
|
129
|
+
state.name = state.name + lines[0][0]
|
130
|
+
lines[0].slice! lines[0][0]
|
124
131
|
end
|
132
|
+
deleteLine
|
133
|
+
# Tags
|
134
|
+
return false if !lines[0].match "tag"
|
135
|
+
deleteLine
|
136
|
+
# X position
|
137
|
+
deleteLine
|
138
|
+
# Y position
|
139
|
+
deleteLine
|
140
|
+
# Initial?
|
141
|
+
state.initial = false
|
142
|
+
if lines[0].match "initial"
|
143
|
+
@initial = state.id if @initial == nil
|
144
|
+
state.initial = true
|
145
|
+
deleteLine
|
146
|
+
end
|
147
|
+
# Final?
|
148
|
+
state.final = false
|
149
|
+
if lines[0].match "final"
|
150
|
+
state.final = true
|
151
|
+
deleteLine
|
152
|
+
end
|
153
|
+
# Check closing bracket
|
154
|
+
return false if !lines[0].match "</block>"
|
155
|
+
deleteLine
|
156
|
+
@states[state.id] = state
|
157
|
+
return true
|
158
|
+
end
|
125
159
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
@states[from].transitions[read] = Array.new
|
191
|
-
end
|
192
|
-
trans = TuringTransition.new
|
193
|
-
trans.to = to
|
194
|
-
trans.write = write
|
195
|
-
trans.move = move
|
196
|
-
@states[from].transitions[read].push trans
|
197
|
-
deleteLine
|
198
|
-
if lines[0].match "</transition>"
|
199
|
-
deleteLine
|
200
|
-
return true
|
201
|
-
end
|
202
|
-
return false
|
160
|
+
# Create Turing transition
|
161
|
+
# return [Boolean] False if there is an error, True otherwise
|
162
|
+
def createTransition
|
163
|
+
from = 0
|
164
|
+
to = 0
|
165
|
+
if lines[0].match "<transition>"
|
166
|
+
deleteLine
|
167
|
+
else
|
168
|
+
return false
|
169
|
+
end
|
170
|
+
# From
|
171
|
+
deleteTabs
|
172
|
+
lines[0].slice! "<from>"
|
173
|
+
until lines[0][0] == "<"
|
174
|
+
from = from * 10
|
175
|
+
from = from + lines[0][0].to_i
|
176
|
+
lines[0].slice! lines[0][0]
|
177
|
+
end
|
178
|
+
deleteLine
|
179
|
+
# To
|
180
|
+
deleteTabs
|
181
|
+
lines[0].slice! "<to>"
|
182
|
+
until lines[0][0] == "<"
|
183
|
+
to = to * 10
|
184
|
+
to = to + lines[0][0].to_i
|
185
|
+
lines[0].slice! lines[0][0]
|
186
|
+
end
|
187
|
+
deleteLine
|
188
|
+
# Read
|
189
|
+
if lines[0].match "<read/>"
|
190
|
+
read = nil
|
191
|
+
else
|
192
|
+
deleteTabs
|
193
|
+
read = 0
|
194
|
+
lines[0].slice! "<read>"
|
195
|
+
until lines[0][0] == "<"
|
196
|
+
read = read * 10
|
197
|
+
read = read + lines[0][0].to_i
|
198
|
+
lines[0].slice! lines[0][0]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
deleteLine
|
202
|
+
# Write
|
203
|
+
if lines[0].match "<write/>"
|
204
|
+
write = nil
|
205
|
+
else
|
206
|
+
deleteTabs
|
207
|
+
write = 0
|
208
|
+
lines[0].slice! "<write>"
|
209
|
+
until lines[0][0] == "<"
|
210
|
+
write = write * 10
|
211
|
+
write = write + lines[0][0].to_i
|
212
|
+
lines[0].slice! lines[0][0]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
deleteLine
|
216
|
+
if lines[0].match "<move>R"
|
217
|
+
move = "R"
|
218
|
+
elsif lines[0].match "<move>L"
|
219
|
+
move = "L"
|
220
|
+
elsif lines[0].match "<move>S"
|
221
|
+
move = "S"
|
222
|
+
else
|
223
|
+
move = nil
|
203
224
|
end
|
225
|
+
if @states[from].transitions[read] == nil
|
226
|
+
@states[from].transitions[read] = Array.new
|
227
|
+
end
|
228
|
+
trans = TuringTransition.new
|
229
|
+
trans.to = to
|
230
|
+
trans.write = write
|
231
|
+
trans.move = move
|
232
|
+
@states[from].transitions[read].push trans
|
233
|
+
deleteLine
|
234
|
+
if lines[0].match "</transition>"
|
235
|
+
deleteLine
|
236
|
+
return true
|
237
|
+
end
|
238
|
+
return false
|
239
|
+
end
|
204
240
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
241
|
+
# Delete the first line
|
242
|
+
# @return [Void]
|
243
|
+
def deleteLine
|
244
|
+
size = @lines.size
|
245
|
+
newLines = Array.new
|
246
|
+
for i in 0...size-1
|
247
|
+
newLines[i] = @lines[i+1]
|
212
248
|
end
|
249
|
+
@lines = newLines
|
250
|
+
end
|
213
251
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
252
|
+
# Clear tabs of the first line
|
253
|
+
# @return [Void]
|
254
|
+
def deleteTabs
|
255
|
+
until !lines[0].match "\t"
|
256
|
+
lines[0].slice! "\t"
|
218
257
|
end
|
258
|
+
end
|
219
259
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
260
|
+
# Validate a string
|
261
|
+
# @param line [String] Tape for Turing machine
|
262
|
+
# @param actual [TuringState] Actual state on the turing machine
|
263
|
+
# @param position [Integer] Position on the tape
|
264
|
+
# @return [Boolean] True if string is accepted, false otherwise
|
265
|
+
def validate(line, actual, position)
|
266
|
+
transitions = @states[actual].transitions
|
267
|
+
# Launch no read transition
|
268
|
+
read = line[position]
|
269
|
+
if transitions[nil] != nil
|
270
|
+
transitions[nil].each do |trans|
|
271
|
+
if read == "B"
|
272
|
+
if trans.write == nil
|
273
|
+
line[position] = "B"
|
274
|
+
else
|
275
|
+
line[position] = trans.write
|
276
|
+
end
|
277
|
+
newPos = position - 1 if trans.move == "L"
|
278
|
+
newPos = position + 1 if trans.move == "R"
|
279
|
+
newPos = position if trans.move == "S"
|
280
|
+
nilThread = Thread.new { validate(line, trans.to, newPos)}
|
281
|
+
nilThread.join
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
# Launch read transition / End if no transition
|
286
|
+
read = line[position]
|
287
|
+
if transitions[read] == nil
|
288
|
+
if @valid == false && @states[actual].final == true
|
289
|
+
@valid = true
|
290
|
+
end
|
291
|
+
else
|
292
|
+
transitions[read].each do |trans|
|
293
|
+
if trans.write == nil
|
294
|
+
line[position] = "B"
|
246
295
|
else
|
247
|
-
|
248
|
-
if trans.write == nil
|
249
|
-
line[position] = "B"
|
250
|
-
else
|
251
|
-
line[position] = trans.write
|
252
|
-
end
|
253
|
-
newPos = position - 1 if trans.move == "L"
|
254
|
-
newPos = position + 1 if trans.move == "R"
|
255
|
-
newPos = position if trans.move == "S"
|
256
|
-
thread = Thread.new { validate(line, trans.to, newPos)}
|
257
|
-
thread.join
|
258
|
-
end
|
296
|
+
line[position] = trans.write
|
259
297
|
end
|
260
|
-
|
261
|
-
|
298
|
+
newPos = position - 1 if trans.move == "L"
|
299
|
+
newPos = position + 1 if trans.move == "R"
|
300
|
+
newPos = position if trans.move == "S"
|
301
|
+
thread = Thread.new { validate(line, trans.to, newPos)}
|
302
|
+
thread.join
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
262
306
|
end
|
data/lib/Turing Reader.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# JFLAP Turing Machine File reader
|
1
2
|
class TuringReader
|
2
|
-
|
3
|
+
# Lines of the file
|
4
|
+
# @param file [String] File location
|
5
|
+
# @return [Array[String]] Separated lines of the file
|
3
6
|
def readFile(file)
|
4
7
|
file = File.new file, "r"
|
5
8
|
line = file.read
|
6
9
|
file.close
|
7
10
|
lines = line.split "\n"
|
8
11
|
end
|
9
|
-
|
10
12
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: SEATC
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Salvador Guerra Delgado
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A gem for development on SEATC, can be used for other system that requires
|
14
14
|
automaton and grammars analysis
|
@@ -60,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
60
|
version: '0'
|
61
61
|
requirements: []
|
62
62
|
rubyforge_project:
|
63
|
-
rubygems_version: 2.
|
63
|
+
rubygems_version: 2.6.14
|
64
64
|
signing_key:
|
65
65
|
specification_version: 3
|
66
66
|
summary: SEATC
|