Module: Flexr::DSL

Included in:
Lexer
Defined in:
lib/flexr/dsl.rb,
lib/flexr/generated.rb

Constant Summary collapse

DSL_METHODS =
%i[rule state all_states on_eof emits backend token_kind encoding option accel].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#__flexr_compiledObject (readonly)

Returns the value of attribute __flexr_compiled.



154
155
156
# File 'lib/flexr/dsl.rb', line 154

def __flexr_compiled
  @__flexr_compiled
end

#__flexr_configObject (readonly)

Returns the value of attribute __flexr_config.



154
155
156
# File 'lib/flexr/dsl.rb', line 154

def __flexr_config
  @__flexr_config
end

#__flexr_generated_actionsObject (readonly)

Returns the value of attribute __flexr_generated_actions.



154
155
156
# File 'lib/flexr/dsl.rb', line 154

def __flexr_generated_actions
  @__flexr_generated_actions
end

#__flexr_rulesObject (readonly)

Returns the value of attribute __flexr_rules.



154
155
156
# File 'lib/flexr/dsl.rb', line 154

def __flexr_rules
  @__flexr_rules
end

#__flexr_statesObject (readonly)

Returns the value of attribute __flexr_states.



154
155
156
# File 'lib/flexr/dsl.rb', line 154

def __flexr_states
  @__flexr_states
end

Instance Method Details

#__flexr_add_generated_eof(state, action) ⇒ Object



156
157
158
# File 'lib/flexr/dsl.rb', line 156

def __flexr_add_generated_eof(state, action)
  __flexr_mutate! { @__flexr_eof_rules[state.to_sym] = action }
end

#__flexr_add_generated_rule(definition) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/flexr/generated.rb', line 132

def __flexr_add_generated_rule(definition)
  __flexr_mutate! do
    action = definition.fetch(:action)
    @__flexr_rules << IR::Rule.new(
      index: definition.fetch(:index), patterns: Array(definition.fetch(:patterns)),
      trailing: normalize_trailing(definition[:trailing]), action: action,
      states: Array(definition.fetch(:states)).map(&:to_sym),
      bol_only: definition.fetch(:bol_only, false), end_anchor: definition[:end_anchor],
      location: definition[:span] || definition[:location],
      pattern_conditions: Array(definition[:pattern_conditions]).map do |condition|
        next unless condition

        Automaton::Acceptance.new(rule_index: condition[0], pattern_index: condition[1],
                                  bol_only: condition[2], end_anchor: condition[3])
      end
    )
  end
end

#__flexr_bind_generated_action(index, action) ⇒ Object



171
172
173
174
175
# File 'lib/flexr/dsl.rb', line 171

def __flexr_bind_generated_action(index, action)
  raise FrozenSpecificationError, "generated actions can only be bound on a generated lexer" unless @__flexr_generated

  @__flexr_generated_actions[index] = action
end

#__flexr_generated?Boolean

Returns:

  • (Boolean)


177
178
179
# File 'lib/flexr/dsl.rb', line 177

def __flexr_generated?
  @__flexr_generated == true
end

#__flexr_mark_generated!Object



167
168
169
# File 'lib/flexr/dsl.rb', line 167

def __flexr_mark_generated!
  @__flexr_generated = true
end

#__flexr_reset!Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/flexr/dsl.rb', line 11

def __flexr_reset!
  @__flexr_rules = []
  @__flexr_states = { initial: IR::State.new(name: :initial, inclusive: true, id: 0) }
  @__flexr_state_stack = []
  @__flexr_eof_rules = {}
  @__flexr_config = IR::Config.new(
    backend: :table, token_kind: :array, encoding: Encoding::UTF_8,
    options: {}, declared_tokens: [], states: @__flexr_states
  )
  @__flexr_compiled = nil
  @__flexr_generated = false
  @__flexr_generated_actions = []
  @__flexr_specification_frozen = false
  @__flexr_compile_mutex = Monitor.new
end

#__flexr_set_compiled!(compiled) ⇒ Object



160
161
162
163
164
165
# File 'lib/flexr/dsl.rb', line 160

def __flexr_set_compiled!(compiled)
  @__flexr_compile_mutex.synchronize do
    @__flexr_compiled = freeze_compiled!(compiled)
    freeze_specification!
  end
end

#__flexr_specObject



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/flexr/dsl.rb', line 138

def __flexr_spec
  IR::Spec.new(
    class_name: name,
    superclass: superclass&.name,
    backend: @__flexr_config.backend,
    token_kind: @__flexr_config.token_kind,
    encoding: @__flexr_config.encoding,
    options: @__flexr_config.options,
    declared_tokens: @__flexr_config.declared_tokens,
    states: @__flexr_states,
    rules: @__flexr_rules,
    eof_rules: @__flexr_eof_rules,
    verbatim: nil
  )
end

#accel(value) ⇒ Object



114
115
116
# File 'lib/flexr/dsl.rb', line 114

def accel(value)
  __flexr_mutate! { @__flexr_config.options[:accel] = Configuration.accelerator!(value) }
end

#all_statesObject

Raises:

  • (ArgumentError)


65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/flexr/dsl.rb', line 65

def all_states(&)
  raise ArgumentError, "all_states requires a block" unless block_given?

  __flexr_mutate! do
    names = @__flexr_states.keys
    @__flexr_state_stack.concat(names)
    begin
      class_eval(&)
    ensure
      names.length.times { @__flexr_state_stack.pop }
    end
  end
end

#backend(name) ⇒ Object



90
91
92
# File 'lib/flexr/dsl.rb', line 90

def backend(name)
  __flexr_mutate! { @__flexr_config.backend = Configuration.backend!(name) }
end

#compile!Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/flexr/dsl.rb', line 118

def compile!
  @__flexr_compile_mutex.synchronize do
    # The ivar is part of the generated/runtime class contract.
    # rubocop:disable Naming/MemoizedInstanceVariableName
    @__flexr_compiled ||= begin
      compiled = Automaton::Compiler.new(__flexr_spec).compile
      @__flexr_config.backend = auto_direct?(compiled) ? :direct : :table if @__flexr_config.backend == :auto
      freeze_specification!
      freeze_compiled!(compiled)
      compiled
    end
    # rubocop:enable Naming/MemoizedInstanceVariableName
  end
end

#dfaObject



133
134
135
136
# File 'lib/flexr/dsl.rb', line 133

def dfa
  compile!
  __flexr_compiled
end

#emits(*tokens) ⇒ Object



86
87
88
# File 'lib/flexr/dsl.rb', line 86

def emits(*tokens)
  __flexr_mutate! { @__flexr_config.declared_tokens.concat(tokens.flatten.map(&:to_sym)).uniq! }
end

#encoding(value) ⇒ Object



98
99
100
101
102
103
104
105
106
# File 'lib/flexr/dsl.rb', line 98

def encoding(value)
  encoding = value.is_a?(Encoding) ? value : Encoding.find(value.to_s)
  unless [Encoding::UTF_8, Encoding::BINARY].include?(encoding)
    diagnostic = Diagnostics.error("FLEXR-E011", "flexr supports UTF-8 and BINARY only")
    raise CompileError.new(diagnostic.message, diagnostic: diagnostic)
  end

  __flexr_mutate! { @__flexr_config.encoding = encoding }
end

#inherited(child) ⇒ Object



6
7
8
9
# File 'lib/flexr/dsl.rb', line 6

def inherited(child)
  super
  child.__flexr_reset!
end

#on_eof(&action) ⇒ Object



79
80
81
82
83
84
# File 'lib/flexr/dsl.rb', line 79

def on_eof(&action)
  __flexr_mutate! do
    state_name = @__flexr_state_stack.last || :initial
    @__flexr_eof_rules[state_name] = action
  end
end

#option(*values) ⇒ Object



108
109
110
111
112
# File 'lib/flexr/dsl.rb', line 108

def option(*values)
  __flexr_mutate! do
    values.each { |value| @__flexr_config.options[Configuration.option!(value)] = true }
  end
end

#rule(pattern, skip: false, emit: nil, followed_by: nil, &action) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/flexr/dsl.rb', line 27

def rule(pattern, skip: false, emit: nil, followed_by: nil, &action)
  __flexr_mutate! do
    patterns = normalize_patterns(pattern)
    rule_action = ActionResolver.resolve(
      skip: skip, emit: emit, block: action, default: proc { emit(nil, text) }
    )
    states = @__flexr_state_stack.empty? ? [:initial] : @__flexr_state_stack.dup
    @__flexr_rules << IR::Rule.new(
      index: @__flexr_rules.length, patterns: patterns, trailing: normalize_trailing(followed_by),
      action: rule_action, states: states, bol_only: false, end_anchor: nil
    )
  end
  nil
end

#state(*names, inclusive: false, &block) ⇒ Object

Raises:

  • (ArgumentError)


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/flexr/dsl.rb', line 42

def state(*names, inclusive: false, &block)
  raise ArgumentError, "state requires a block" unless block
  raise ArgumentError, "state requires a name" if names.empty?

  __flexr_mutate! do
    names.each do |name|
      symbol = name.to_sym
      existing = @__flexr_states[symbol]
      raise ArgumentError, "state #{symbol.inspect} already declared with inclusive: #{existing.inclusive}" if
        existing && existing.inclusive != inclusive
      @__flexr_states[symbol] ||= IR::State.new(
        name: symbol, inclusive: inclusive, id: @__flexr_states.length
      )
    end
    @__flexr_state_stack.concat(names.map(&:to_sym))
    begin
      class_eval(&block)
    ensure
      names.length.times { @__flexr_state_stack.pop }
    end
  end
end

#token_kind(name) ⇒ Object



94
95
96
# File 'lib/flexr/dsl.rb', line 94

def token_kind(name)
  __flexr_mutate! { @__flexr_config.token_kind = Configuration.token_kind!(name) }
end