Module: Hecks::Literal

Defined in:
lib/hecks/literal.rb

Constant Summary collapse

ESCAPED =
{ '"' => '\\"', "\\" => "\\\\" }.freeze

Class Method Summary collapse

Class Method Details

.quote(text) ⇒ Object



80
# File 'lib/hecks/literal.rb', line 80

def quote(text) = "\"#{text.gsub(/["\\]/) { |char| ESCAPED[char] }}\""

.quoted?(raw) ⇒ Boolean

Returns:

  • (Boolean)


82
# File 'lib/hecks/literal.rb', line 82

def quoted?(raw) = raw.length >= 2 && raw.start_with?('"') && raw.end_with?('"')

.read(text) ⇒ Object

Read one back. Tolerant of a bare word on purpose: the language stores a closed set's members and a few hand-written fields as plain text that was never rendered, and those must stay the strings they are.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/hecks/literal.rb', line 62

def read(text)
  raw = text.to_s.strip
  return nil if raw.empty? || raw == "nil"
  return true if raw == "true"
  return false if raw == "false"
  return raw.to_i if raw.match?(/\A-?\d+\z/)
  return raw.to_f if raw.match?(/\A-?\d+\.\d+\z/)
  return raw[1..].to_sym if raw.start_with?(":")
  return StateRef.new(raw[7..-2].to_sym) if raw.match?(/\Astate\(:[A-Za-z_][A-Za-z0-9_]*\)\z/)
  return unquote(raw) if quoted?(raw)
  return read_hash(raw) if raw.start_with?("{") && raw.end_with?("}")
  return read_array(raw) if raw.start_with?("[") && raw.end_with?("]")

  raw
end

.read_array(raw) ⇒ Object



93
# File 'lib/hecks/literal.rb', line 93

def read_array(raw) = split_items(raw[1..-2]).map { |item| read(item) }

.read_hash(raw) ⇒ Object



86
87
88
89
90
91
# File 'lib/hecks/literal.rb', line 86

def read_hash(raw)
  split_items(raw[1..-2]).to_h do |item|
    key, _, held = item.partition(":")
    [key.strip.to_sym, read(held)]
  end
end

.render(value) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/hecks/literal.rb', line 43

def render(value)
  case value
  when nil            then "nil"
  when StateRef       then value.to_s
  when true, false    then value.to_s
  when Symbol         then ":#{value}"
  when Integer, Float then value.to_s
  when String         then quote(value)
  when Hash           then "{#{value.map { |key, held| "#{key}: #{render(held)}" }.join(', ')}}"
  when Array          then "[#{value.map { |held| render(held) }.join(', ')}]"
  else
    raise ArgumentError, "#{value.class} has no pinned literal spelling — teach Literal.render one " \
                         "rather than letting #to_s decide it"
  end
end

.split_items(body) ⇒ Object

Split on the commas that are actually SEPARATORS — never one inside a quoted string or a nested brace/bracket. Scanned rather than String#split(", "), which tore "a, b" in half and lost the second field of anything nested.



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
# File 'lib/hecks/literal.rb', line 99

def split_items(body)
  items = []
  current = +""
  depth = 0
  quoting = false
  escaping = false

  body.each_char do |char|
    current << char
    next (escaping = false) if escaping
    next (escaping = true) if quoting && char == "\\"
    next (quoting = !quoting) if char == '"'
    next if quoting

    depth += 1 if "{[".include?(char)
    depth -= 1 if "}]".include?(char)
    next unless char == "," && depth.zero?

    current.chop!
    items << current
    current = +""
  end
  items << current
  items.map(&:strip).reject(&:empty?)
end

.unquote(raw) ⇒ Object



84
# File 'lib/hecks/literal.rb', line 84

def unquote(raw) = raw[1..-2].gsub(/\\(.)/) { ::Regexp.last_match(1) }