Skip to content

Instantly share code, notes, and snippets.

@weepy
Created January 31, 2010 14:25
Show Gist options
  • Save weepy/291087 to your computer and use it in GitHub Desktop.
Save weepy/291087 to your computer and use it in GitHub Desktop.
class Interpolate
DQ = "\""
SQ = "'"
ESCAPE = "\\"
CODE = "#"
DEBUG = false
def quote? char
char == DQ || char == SQ
end
def escape? char
char == ESCAPE
end
def transist state, opts = {}
old_state = @state
self.send("#{@state}_to_#{state}", opts )
@state = state
@stream << [old_state, @buffer] if @buffer
@buffer = ""
end
#### HOOKS START
def init_to_code opts
end
def code_to_string opts
@quote = opts[:quote]
end
def string_to_code opts
@quote = nil
end
def string_to_interpolation opts
@braces = 1
@buffer = @buffer.slice(0,@buffer.length - 1)
end
def interpolation_to_string opts; end
def string_to_interpolation_simple opts
@buffer = @buffer.slice(0,@buffer.length - 1)
end
def interpolation_simple_to_string opts; end
def interpolation_to_string_in_interpolation opts
@interp_quote = opts[:quote]
end
def string_in_interpolation_to_interpolation opts
@interp_quote = nil
@buffer = @buffer.slice(1, @buffer.length)
end
####### HOOKES END
def build_stream s
@input = s
history = []
history[0] = nil
@stream = []
@state = :init
transist :code
@braces = 0
@input.split("").each do |char|
@char = char
case @state
when :code
if quote?(char) && !escape?(history[0])
transist(:string, :quote => char)
else
@buffer += char
end
when :string
if history[1] != ESCAPE && history[0] == CODE && char.match(/[a-zA-Z_\$\{]/)
if char == "{"
transist :interpolation
else
transist :interpolation_simple
@buffer += char
end
elsif char == @quote && !escape?(history[0])
transist :code
else
@buffer += char
end
when :interpolation
if @interpolation_quote
if char == @interpolation_quote && history[0] != ESCAPE
@interpolation_quote = nil
end
else
if char == "{"
@braces += 1
elsif char == "}"
@braces -= 1
elsif quote?(char) && history[0] != ESCAPE
@interpolation_quote = char
end
end
if @braces == 0
transist :string
else
@buffer += char
end
when :interpolation_simple
if char.match(/[a-zA-Z_\$0-9]/)
@buffer += char
else
transist :string
if char == @quote
transist :code
else
@buffer += char
end
end
end
history.unshift(char).slice(0,5)
end
@stream
end
def stringify_token back, token, fwd
type = token[0]
data = token[1]
case type
when :init
""
when :code
data
when :string
"'#{data}'"
when :interpolation
s = ""
s += " + " #if back[1].length > 0
s += data.match(/[^A-Za-z\$_0-9"' ]/) ? "(#{data})" : data
s += " + " #if fwd[1].length > 0
s
when :interpolation_simple
s = ""
s += " + " #if back[1].length > 0 #|| back[0] != :string
s += data
s += " + " #if fwd[1].length > 0 #|| fwd[0] != :string
s
when :string_in_interpolation
data
else
type
end
end
def stringify stream
ret = ""
stream.each_with_index do |token, i|
ret += stringify_token(stream[i-1] || [], stream[i], stream[i+1] || [])
end
return ret
end
def expand input
build_stream input
debug "tokens: " + @stream.inspect
stringify @stream
end
def run input
string = input
debug "---------------------------"
debug "input: " + input
while true
expanded = expand string
if expanded == string
debug "output: " + string
return expanded
end
debug "expanded: " + expanded
string = expanded
end
end
def debug x
puts x if DEBUG
end
end
coffee =<<EOF
just_some(code)
"1 + 2"
a: "1 + 2" + '1 + 2'
b: "$x$y$z"
b: "$x" + '${x}' + '${x}'
c: "abc $y def"
c: "${x ? 1 : 2}"
c: " ${x ? 1 : 2} "
c: " ${x ? 1 : "hello"} "
c: " ${x ? 1 : "hello ${xx} "} "
c: " ${x ? 1 : "hello ${fn('}')} "} "
c: " ${"nested ${"nested ${"nested ${"nested ${"nested"}"}"}"}"} "
"""
heredoc!
"""
EOF
coffee.gsub!("$", "#")
# per line:
coffee.split("\n").each do |line|
puts Interpolate.new.run(line)
end
# whole lot:
puts Interpolate.new.run(coffee)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment