Module: Stamina::ADL

Defined in:
lib/stamina-core/stamina/adl.rb,
lib/stamina-core/stamina/errors.rb

Overview

Specific errors of the ADL module.

Defined Under Namespace

Classes: ParseError

Class Method Summary collapse

Class Method Details

.parse_automaton(descr) ⇒ Object

Parses a given automaton description and returns an Automaton instance.

Raises:

  • ArgumentError unless descr is an IO object or a String.

  • ADL::ParseError if the ADL automaton format is not respected.

ADL provides a really simple grammar to describe automata. Here is a succint example (full documentation of the ADL automaton grammar can be found in the self-documenting example/adl/automaton.adl file).

# Some header comments: tool which has generated this automaton,
# maybe a date or other tool options ...
# here: 'this automaton accepts the a(ba)* regular language'
2 2
0 true false
1 false true
0 1 a
1 0 b


44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/stamina-core/stamina/adl.rb', line 44

def self.parse_automaton(descr)
  automaton = nil
  ADL::to_io(descr) do |io|
    state_count, edge_count = nil, nil
    state_read, edge_read = 0, 0
    states = {}
    mode = :header

    automaton = Automaton.new do |fa|
      # parse each description line
      line_number = 1
      io.each_line do |l|
        index = l.index('#')
        l = l[0,index] if index
        l = l.strip
        next if l.empty? or l[0,1]=='#'

        case mode
        when :header
          # looking for |state_count edge_count|
          raise(ADL::ParseError,
                "Parse error line #{line_number}: 'state_count edge_count' expected, "\
                "'#{l}' found.") unless /^(\d+)\s+(\d+)$/ =~ l
          state_count, edge_count = $1.to_i, $2.to_i
          mode = :states

        when :states
          # looking for |number initial accepting|
          raise(ADL::ParseError,
                "Parse error line #{line_number}: state definition expected, "\
                "'#{l}' found.") unless /^(\S+)\s+(true|false)\s+(true|false)(\s+(true|false))?$/ =~ l
          id, initial, accepting, error = $1, $2, $3, $5
          initial, accepting, error = ("true"==initial), ("true"==accepting), ("true"==error)

          state = fa.add_state(:initial => initial, :accepting => accepting, :error => error)
          state[:name]=id.to_s
          states[id] = state

          state_read += 1
          mode = (edge_count==0 ? :end : :edges) if state_read==state_count

        when :edges
          # looking for |source target symbol|
          raise(ADL::ParseError,
                "Parse error line #{line_number}: edge definition expected, "\
                "'#{l}' found.") unless /^(\S+)\s+(\S+)\s+(\S+)$/ =~ l
          source, target, symbol = $1, $2, $3
          raise(ADL::ParseError,
                "Parse error line #{line_number}: no such state #{source}") \
                unless states[source]
          raise(ADL::ParseError,
                "Parse error line #{line_number}: no such state #{target}") \
                unless states[target]

          fa.connect(states[source], states[target], {:symbol => symbol})

          edge_read += 1
          mode = :end if edge_read==edge_count

        when :end
          raise(ADL::ParseError,
                "Parse error line #{line_number}: trailing data found '#{l}")

        end # case mode

        line_number += 1
      end

      raise(ADL::ParseError, "Parse error: #{state_count} states annouced, "\
                           "#{state_read} found.") if state_count != state_read
      raise(ADL::ParseError, "Parse error: #{edge_count} edges annouced, "\
                           "#{edge_read} found.") if edge_count != edge_read

    end # Automaton.new
  end
  return automaton
end

.parse_automaton_file(f) ⇒ Object

Parses an automaton file f.

Shortcut for:

File.open(f, 'r') do |io|
  Stamina::ADL.parse_automaton(io)
end


130
131
132
133
134
135
136
# File 'lib/stamina-core/stamina/adl.rb', line 130

def self.parse_automaton_file(f)
  automaton = nil
  File.open(f) do |file|
    automaton = ADL::parse_automaton(file)
  end
  automaton
end

.parse_sample(descr, sample = nil) ⇒ Object

Parses the sample provided by descr. When a block is provided, yields it with InputString instances and ignores the sample argument. Otherwise, fills the sample (any object responding to <<) with string, creating a fresh new one (as a Sample instance) if sample is nil.

ADL provides a really simple grammar to describe samples (here is a succint example, the full documentation of the sample grammar can be found in the self-documenting example/adl/sample.adl file):

#
# Some header comments: tool which has generated this sample,
# maybe a date or other tool options ...
# here: 'this sample is caracteristic for the a(ba)* regular language'
#
# Positive, Negative, Unlabeled strings become with +, -, ?, respectively
# Empty lines and lines becoming with # are simply ignored.
#
-
+ a
- a b
+ a b a

Raises:

  • ArgumentError unless descr argument is an IO object or a String.

  • ADL::ParseError if the ADL sample format is not respected.

  • InconsistencyError if the sample is not consistent (see Sample)



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/stamina-core/stamina/adl.rb', line 224

def self.parse_sample(descr, sample=nil)
  sample = Sample.new if (sample.nil? and not block_given?)
  ADL::to_io(descr) do |io|
    io.each_line do |l|
      l = l.strip
      next if l.empty? or l[0,1]=='#'
      if sample.nil? and block_given?
        yield parse_string(l)
      else
        sample << parse_string(l)
      end
    end
  end
  sample
end

.parse_sample_file(f, sample = nil) ⇒ Object

Parses an automaton file f.

Shortuct for:

File.open(f) do |file|
   sample = ADL::parse_sample(file, sample)
end


248
249
250
251
252
253
# File 'lib/stamina-core/stamina/adl.rb', line 248

def self.parse_sample_file(f, sample=nil)
  File.open(f) do |file|
    sample = ADL::parse_sample(file, sample)
  end
  sample
end

.parse_string(str) ⇒ Object

Parses an input string str and returns a InputString instance. Format of input strings is documented in parse_sample. str is required to be a ruby String.

Raises:

  • ADL::ParseError if the ADL string format is not respected.



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/stamina-core/stamina/adl.rb', line 179

def self.parse_string(str)
  symbols = str.split(' ')
  case symbols[0]
    when '+'
      symbols.shift
      InputString.new symbols, true, false
    when '-'
      symbols.shift
      InputString.new symbols, false, false
    when '?'
      symbols.shift
      InputString.new symbols, nil, false
    else
      raise ADL::ParseError, "Invalid string format #{str}", caller
  end
end

Prints an automaton to a buffer (responding to :&lt;&lt;) in ADL format. Returns the buffer itself.



142
143
144
145
146
147
148
149
150
151
# File 'lib/stamina-core/stamina/adl.rb', line 142

def self.print_automaton(fa, buffer="")
  buffer << "#{fa.state_count.to_s} #{fa.edge_count.to_s}" << "\n"
  fa.states.each do |s|
    buffer << "#{s.index.to_s} #{s.initial?} #{s.accepting?}" << (s.error? ? " true" : "") << "\n"
  end
  fa.edges.each do |e|
    buffer << "#{e.source.index.to_s} #{e.target.index.to_s} #{e.symbol.to_s}" << "\n"
  end
  buffer
end

Prints an automaton to a file whose path is provided.

Shortcut for:

 File.open(file, 'w') do |io|
   print_automaton(fa, io)
end


161
162
163
164
165
# File 'lib/stamina-core/stamina/adl.rb', line 161

def self.print_automaton_to_file(fa, file)
  File.open(file, 'w') do |io|
    print_automaton(fa, io)
  end
end

Prints a sample in ADL format on a buffer. Sample argument is expected to be an object responding to each, yielding InputString instances. Buffer is expected to be an object responding to <<.



260
261
262
263
264
# File 'lib/stamina-core/stamina/adl.rb', line 260

def self.print_sample(sample, buffer="")
  sample.each do |str|
    buffer << str.to_s << "\n"
  end
end

Prints a sample in a file.

Shortcut for:

File.open(file, 'w') do |io|
  print_sample(sample, f)
end


274
275
276
277
278
# File 'lib/stamina-core/stamina/adl.rb', line 274

def self.print_sample_in_file(sample, file)
  File.open(file, 'w') do |f|
    print_sample(sample, f)
  end
end