Module: LanguageOperator::TypeCoercion

Defined in:
lib/language_operator/type_coercion.rb

Overview

Type coercion system for task inputs and outputs

Provides smart type coercion with automatic conversion for common cases and clear error messages when coercion is not possible. This enables flexible type handling while maintaining type safety.

Performance optimizations:

  • Fast-path checks for already-correct types
  • Bounded LRU memoization cache for expensive string coercions (prevents memory leaks)
  • Pre-compiled regexes for boolean parsing
  • Thread-safe cache operations with mutex protection

Supported types:

  • integer: Coerces String, Integer, Float to Integer
  • number: Coerces String, Integer, Float to Float
  • string: Coerces any value to String via to_s
  • boolean: Coerces String, Integer (0 or 1), Boolean to Boolean (explicit values only)
  • array: Strict validation (no coercion)
  • hash: Strict validation (no coercion)
  • any: No coercion, passes through any value

Examples:

Integer coercion

TypeCoercion.coerce("123", "integer")  # => 123
TypeCoercion.coerce(123, "integer")    # => 123
TypeCoercion.coerce("abc", "integer")  # raises ArgumentError

Boolean coercion

TypeCoercion.coerce("true", "boolean")   # => true
TypeCoercion.coerce("1", "boolean")      # => true
TypeCoercion.coerce(1, "boolean")        # => true
TypeCoercion.coerce(0, "boolean")        # => false
TypeCoercion.coerce("false", "boolean")  # => false
TypeCoercion.coerce("maybe", "boolean")  # raises ArgumentError
TypeCoercion.coerce(2, "boolean")        # raises ArgumentError

String coercion (never fails)

TypeCoercion.coerce(:symbol, "string")  # => "symbol"
TypeCoercion.coerce(123, "string")      # => "123"

Strict validation

TypeCoercion.coerce([1, 2], "array")    # => [1, 2]
TypeCoercion.coerce({a: 1}, "array")    # raises ArgumentError

Constant Summary collapse

DEFAULT_CACHE_SIZE =

Memory-safe LRU cache for expensive coercions with bounded size to prevent memory leaks

  • Cache automatically evicts least recently used entries when limit is reached
  • Default cache size: 1000 entries (configurable via TYPE_COERCION_CACHE_SIZE environment variable)
  • Thread-safe operations protected by mutex
  • Caches both successful and failed coercion attempts to avoid repeated expensive operations
1000
TRUTHY_PATTERNS =

Boolean patterns - pre-compiled for performance

%w[true 1 yes t y].freeze
FALSY_PATTERNS =
%w[false 0 no f n].freeze

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.cache_sizeObject (readonly)

Get current cache size limit



67
68
69
# File 'lib/language_operator/type_coercion.rb', line 67

def cache_size
  @cache_size
end

Class Method Details

.cache_statsObject

Get cache statistics for monitoring



130
131
132
133
134
135
136
137
138
139
140
# File 'lib/language_operator/type_coercion.rb', line 130

def cache_stats
  @cache_mutex.synchronize do
    {
      size: @coercion_cache.count,
      max_size: @cache_size,
      hits: @cache_hits,
      misses: @cache_misses,
      hit_rate: @cache_hits.zero? ? 0.0 : @cache_hits.to_f / (@cache_hits + @cache_misses)
    }
  end
end

.clear_cacheObject

Clear the cache (for testing or memory management)



143
144
145
146
147
148
149
# File 'lib/language_operator/type_coercion.rb', line 143

def clear_cache
  @cache_mutex.synchronize do
    @coercion_cache.clear
    @cache_hits = 0
    @cache_misses = 0
  end
end

.coerce(value, type) ⇒ Object

Coerce a value to the specified type

Examples:

TypeCoercion.coerce("345", "integer")  # => 345
TypeCoercion.coerce("true", "boolean") # => true

Parameters:

  • Value to coerce

  • Target type (see COERCION_RULES for valid types)

Returns:

  • Coerced value

Raises:

  • If coercion fails or type is unknown



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
121
122
123
124
125
126
127
# File 'lib/language_operator/type_coercion.rb', line 79

def coerce(value, type)
  # Fast path - check cache first for expensive string coercions
  if value.is_a?(String) && %w[integer number boolean].include?(type)
    cache_key = [value, type]
    cached = @cache_mutex.synchronize { @coercion_cache[cache_key] }
    if cached
      @cache_hits += 1
      return cached[:result] if cached[:success]

      raise ArgumentError, cached[:error_message]
    end
    @cache_misses += 1
  end

  # Perform coercion
  result = case type
           when 'integer'
             coerce_integer(value)
           when 'number'
             coerce_number(value)
           when 'string'
             coerce_string(value)
           when 'boolean'
             coerce_boolean(value)
           when 'array'
             validate_array(value)
           when 'hash'
             validate_hash(value)
           when 'any'
             value
           else
             raise ArgumentError, "Unknown type: #{type}"
           end

  # Cache successful string coercion results
  if value.is_a?(String) && %w[integer number boolean].include?(type)
    cache_entry = { success: true, result: result }
    @cache_mutex.synchronize { @coercion_cache[[value, type]] = cache_entry }
  end

  result
rescue ArgumentError => e
  # Cache failed coercion attempts to avoid repeating expensive failures
  if value.is_a?(String) && %w[integer number boolean].include?(type)
    cache_entry = { success: false, error_message: e.message }
    @cache_mutex.synchronize { @coercion_cache[[value, type]] = cache_entry }
  end
  raise
end

.coerce_boolean(value) ⇒ Boolean

Coerce value to Boolean

Accepts: Boolean, Integer (0 or 1), String (explicit values only) Coercion: Case-insensitive string matching with optimized pattern lookup Truthy: "true", "1", "yes", "t", "y", 1 (integer) Falsy: "false", "0", "no", "f", "n", 0 (integer) Errors: Ambiguous values (e.g., "maybe", "unknown"), integers other than 0 or 1

Examples:

coerce_boolean(true)      # => true
coerce_boolean("true")    # => true
coerce_boolean("1")       # => true
coerce_boolean(1)         # => true
coerce_boolean("yes")     # => true
coerce_boolean(false)     # => false
coerce_boolean("false")   # => false
coerce_boolean("0")       # => false
coerce_boolean(0)         # => false
coerce_boolean("no")      # => false
coerce_boolean("maybe")   # raises ArgumentError
coerce_boolean(2)         # raises ArgumentError

Parameters:

  • Value to coerce

Returns:

  • Coerced boolean

Raises:

  • If coercion is ambiguous



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/language_operator/type_coercion.rb', line 242

def self.coerce_boolean(value)
  # Fast path for already-correct types
  return value if value.is_a?(TrueClass) || value.is_a?(FalseClass)

  # Handle integer 0 and 1 (common in many programming contexts)
  if value.is_a?(Integer)
    return true if value == 1
    return false if value.zero?

    raise ArgumentError, "Cannot coerce #{value.inspect} to boolean (only 0 and 1 are valid integers)"
  end

  # Only allow string values for coercion (not floats, symbols, or other types)
  raise ArgumentError, "Cannot coerce #{value.inspect} to boolean" unless value.is_a?(String)

  # Optimized pattern matching using pre-compiled arrays
  str = value.strip.downcase
  return true if TRUTHY_PATTERNS.include?(str)
  return false if FALSY_PATTERNS.include?(str)

  raise ArgumentError, "Cannot coerce #{value.inspect} to boolean"
end

.coerce_integer(value) ⇒ Integer

Coerce value to Integer

Accepts: String, Integer, Float Coercion: Uses Ruby's Integer() method with fast-path optimization Errors: Cannot parse as integer

Examples:

coerce_integer("123")   # => 123
coerce_integer(123)     # => 123
coerce_integer(123.0)   # => 123
coerce_integer("abc")   # raises ArgumentError

Parameters:

  • Value to coerce

Returns:

  • Coerced integer

Raises:

  • If coercion fails



167
168
169
170
171
172
173
174
# File 'lib/language_operator/type_coercion.rb', line 167

def self.coerce_integer(value)
  # Fast path for already-correct types
  return value if value.is_a?(Integer)

  Integer(value)
rescue ArgumentError, TypeError => e
  raise ArgumentError, "Cannot coerce #{value.inspect} to integer: #{e.message}"
end

.coerce_number(value) ⇒ Float

Coerce value to Float (number)

Accepts: String, Integer, Float Coercion: Uses Ruby's Float() method with fast-path optimization Errors: Cannot parse as number

Examples:

coerce_number("3.14")      # => 3.14
coerce_number(3)           # => 3.0
coerce_number(3.14)        # => 3.14
coerce_number("not a num") # raises ArgumentError

Parameters:

  • Value to coerce

Returns:

  • Coerced number

Raises:

  • If coercion fails



191
192
193
194
195
196
197
198
# File 'lib/language_operator/type_coercion.rb', line 191

def self.coerce_number(value)
  # Fast path for already-correct types
  return value.to_f if value.is_a?(Numeric)

  Float(value)
rescue ArgumentError, TypeError => e
  raise ArgumentError, "Cannot coerce #{value.inspect} to number: #{e.message}"
end

.coerce_string(value) ⇒ String

Coerce value to String

Accepts: Any object with to_s method (all Ruby objects) Coercion: Uses to_s method Errors: Never (everything has to_s)

Examples:

coerce_string(:symbol)  # => "symbol"
coerce_string(123)      # => "123"
coerce_string(nil)      # => ""

Parameters:

  • Value to coerce

Returns:

  • Coerced string



213
214
215
# File 'lib/language_operator/type_coercion.rb', line 213

def self.coerce_string(value)
  value.to_s
end

.coercion_rulesHash

Coercion rules table

rubocop:disable Metrics/MethodLength

Returns:

  • Mapping of types to their coercion behavior



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/language_operator/type_coercion.rb', line 307

def self.coercion_rules
  {
    'integer' => {
      accepts: 'String, Integer, Float',
      method: 'Integer(value)',
      errors: 'Cannot parse as integer'
    },
    'number' => {
      accepts: 'String, Integer, Float',
      method: 'Float(value)',
      errors: 'Cannot parse as number'
    },
    'string' => {
      accepts: 'Any object',
      method: 'value.to_s',
      errors: 'Never (everything has to_s)'
    },
    'boolean' => {
      accepts: 'Boolean, Integer (0 or 1), String (explicit values)',
      method: 'Pattern matching (true/1/yes/t/y or false/0/no/f/n)',
      errors: 'Ambiguous values, integers other than 0 or 1'
    },
    'array' => {
      accepts: 'Array only',
      method: 'No coercion (strict)',
      errors: 'Not an array'
    },
    'hash' => {
      accepts: 'Hash only',
      method: 'No coercion (strict)',
      errors: 'Not a hash'
    },
    'any' => {
      accepts: 'Any value',
      method: 'No coercion (pass-through)',
      errors: 'Never'
    }
  }
end

.validate_array(value) ⇒ Array

Validate value is an Array (no coercion)

Accepts: Array Coercion: None (strict validation) Errors: Not an array

Examples:

validate_array([1, 2, 3])  # => [1, 2, 3]
validate_array({a: 1})     # raises ArgumentError

Parameters:

  • Value to validate

Returns:

  • Validated array

Raises:

  • If not an array



278
279
280
281
282
# File 'lib/language_operator/type_coercion.rb', line 278

def self.validate_array(value)
  raise ArgumentError, "Expected array, got #{value.class}" unless value.is_a?(Array)

  value
end

.validate_hash(value) ⇒ Hash

Validate value is a Hash (no coercion)

Accepts: Hash Coercion: None (strict validation) Errors: Not a hash

Examples:

validate_hash({a: 1, b: 2})  # => {a: 1, b: 2}
validate_hash([1, 2])         # raises ArgumentError

Parameters:

  • Value to validate

Returns:

  • Validated hash

Raises:

  • If not a hash



297
298
299
300
301
# File 'lib/language_operator/type_coercion.rb', line 297

def self.validate_hash(value)
  raise ArgumentError, "Expected hash, got #{value.class}" unless value.is_a?(Hash)

  value
end