You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
366 lines
6.4 KiB
Ruby
366 lines
6.4 KiB
Ruby
def error(msg)
|
|
puts "ERROR: #{msg}"
|
|
exit 1
|
|
end
|
|
|
|
class Program
|
|
def initialize()
|
|
|
|
end
|
|
end
|
|
|
|
#FunctionHeader + Semicolon = FunctionPrototype
|
|
#FunctionHeader + Bracket = FunctionDeclaration
|
|
#
|
|
#Type + Identifier + [Identifier] + Semicolon = VarDecl
|
|
#Type + Identifier + Equal + Expr = VarDeclWithValue
|
|
|
|
class Construct
|
|
def initialize(stream)
|
|
@stream, @back = stream, []
|
|
end
|
|
|
|
def spit
|
|
@stream.prepend @back.pop
|
|
end
|
|
|
|
def vomit
|
|
@back.reverse.each { spit }
|
|
end
|
|
|
|
def bite(flavor)
|
|
@back << @stream.shift
|
|
#bite flavor if @back.last.type == :white
|
|
taste_like? flavor
|
|
end
|
|
|
|
def bite_until(flavor)
|
|
until bite flavor
|
|
return if @stream.empty?
|
|
end
|
|
end
|
|
|
|
def discard_until(flavor)
|
|
null = nil
|
|
until null == flavor
|
|
h = @stream.shift
|
|
break if h.nil?
|
|
null = h.type
|
|
end
|
|
end
|
|
|
|
#lick to get flavor
|
|
def lick
|
|
return @stream.first.type if @stream.first.type == Token
|
|
return @stream.first.class
|
|
end
|
|
|
|
#lick until stopping point
|
|
def deep_lick(stop)
|
|
tongue = []
|
|
@stream.each {|s|
|
|
break if s.type == stop
|
|
tongue << s.type if s.class == Token
|
|
tongue << s.class if s.class != Token
|
|
}
|
|
return tongue
|
|
end
|
|
|
|
def deep_bite(stop)
|
|
tongue = []
|
|
@stream.each {|s|
|
|
break if s.type == stop
|
|
tongue << s
|
|
}
|
|
return tongue
|
|
end
|
|
|
|
def taste_like?(flavor)
|
|
return true if flavor == :any
|
|
return flavor.include? @back.last.class == Token ? @back.last.type : @back.last.class if flavor.class == Array
|
|
return @back.last.type == flavor if @back.last.class == Token
|
|
@back.last.class == flavor
|
|
end
|
|
|
|
#i could determine the type here
|
|
end
|
|
|
|
# int main(void) [;|{]
|
|
class FunctionHeader < Construct
|
|
def initialize(stream)
|
|
super
|
|
end
|
|
|
|
def undo
|
|
@stream.prepend @back
|
|
end
|
|
|
|
def consume
|
|
@back << @stream.shift
|
|
if [:int, :void].include? @back.last.type
|
|
@back << @stream.shift
|
|
end
|
|
|
|
if @back.last.type == :ident
|
|
@back << @stream.shift
|
|
end
|
|
if @back.last.type == :oparn
|
|
@back << @stream.shift
|
|
end
|
|
if [:int, :void].include? @back.last.type
|
|
@back << @stream.shift
|
|
end
|
|
if @back.last.type == :cparn
|
|
@back << @stream.shift
|
|
end
|
|
if @back.last.type == :obrac
|
|
@back << @stream.shift
|
|
return self
|
|
end
|
|
|
|
undo
|
|
return
|
|
end
|
|
end
|
|
|
|
class Token
|
|
attr_accessor :text
|
|
attr_accessor :type
|
|
|
|
def initialize(text, type)
|
|
@text, @type = text, type
|
|
end
|
|
|
|
def to_s
|
|
"#{@type}: #{@text}"
|
|
end
|
|
end
|
|
|
|
class Matcher
|
|
def initialize(hash)
|
|
@hash = hash
|
|
end
|
|
|
|
def self.match(hash, token)
|
|
hash.each {|k, v|
|
|
return Token.new(token, k) if token =~ v
|
|
}
|
|
|
|
return nil
|
|
end
|
|
|
|
#modify the token if its a keyword
|
|
def self.match_token(hash, token)
|
|
hash.each {|k,v|
|
|
token.type = k if token.text =~ v
|
|
}
|
|
|
|
return token
|
|
end
|
|
end
|
|
|
|
class StringLiteral
|
|
def initialize(str)
|
|
@text = str
|
|
@type = :string
|
|
end
|
|
|
|
def to_s
|
|
"#{@type}: #{@text}"
|
|
end
|
|
end
|
|
|
|
#this could just be a function
|
|
class StringSweeper < Construct
|
|
def initialize(stream)
|
|
super stream
|
|
@string_mode = false
|
|
end
|
|
|
|
def create_string(stream)
|
|
StringLiteral.new stream.map { |s| s.text }.join
|
|
end
|
|
|
|
def sweep
|
|
until @stream.empty?
|
|
bite_until [:quote, nil] #everything before string
|
|
@back << create_string(deep_bite(:quote))
|
|
back_up = @back.pop
|
|
@back.pop #remove extra quote
|
|
@back << back_up
|
|
discard_until :quote #replace all tokens with string
|
|
end
|
|
puts @back
|
|
end
|
|
end
|
|
|
|
class XMLBody < Construct
|
|
def initialize(stream)
|
|
|
|
end
|
|
end
|
|
|
|
class XMLClose < Construct
|
|
def initialize(stream)
|
|
|
|
end
|
|
end
|
|
|
|
class XMLOpen < Construct
|
|
def initialize(stream)
|
|
|
|
end
|
|
end
|
|
|
|
class XML < Construct
|
|
def initialize(stream)
|
|
super stream
|
|
end
|
|
|
|
def consume
|
|
bite_until :close
|
|
XMLOpen.new(@back)
|
|
end
|
|
end
|
|
|
|
class Expr < Construct
|
|
@@tokens = {
|
|
:addop => /\+/,
|
|
:subop => /\-/,
|
|
:mulop => /\*/,
|
|
:divop => /\//,
|
|
}
|
|
|
|
@@type = :exprn
|
|
|
|
def self.tokens
|
|
@@tokens
|
|
end
|
|
|
|
def initialize(stream)
|
|
super(stream)
|
|
end
|
|
|
|
#parse the expression first
|
|
#determine what kind of expression
|
|
#it is after
|
|
|
|
def consume
|
|
#first token could be
|
|
#ident, const, minus, oparn, func_call
|
|
#
|
|
|
|
if Lexer.keywords.include? lick
|
|
puts "we have a variable (or function) declaration"
|
|
elsif lick == :ident
|
|
puts "we may have an assignment expression"
|
|
elsif deep_lick(:semic).include? :equal
|
|
puts "we definitely have an assignment expression"
|
|
bite_until :semic
|
|
puts @back
|
|
else
|
|
puts "we have an invalid expression"
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
class Lexer
|
|
@@tokens = {
|
|
:ident => /[a-zA-Z_]\w*\b/,
|
|
:const => /[0-9]+\b/,
|
|
:equal => /\=/,
|
|
:oparn => /\(/,
|
|
:cparn => /\)/,
|
|
:obrac => /{/,
|
|
:cbrac => /}/,
|
|
:quote => /\"/,
|
|
:semic => /;/,
|
|
:white => /\s/,
|
|
}.merge Expr.tokens
|
|
|
|
@@xml_tokens = {
|
|
:ident => /[a-zA-Z_]\w*/,
|
|
:const => /[0-9]+/,
|
|
:open_ => /\</,
|
|
:close => /\>/,
|
|
:equal => /\=/,
|
|
:slash => /\//,
|
|
:quote => /\"/,
|
|
:white => /\s/,
|
|
}
|
|
|
|
@@keywords = {
|
|
:int => /int\b/,
|
|
:short => /short\b/,
|
|
:long => /long\b/,
|
|
:longlong => /long\ long\b/,
|
|
:void => /void\b/,
|
|
:return => /return\b/,
|
|
}
|
|
|
|
def self.keywords
|
|
@@keywords.keys
|
|
end
|
|
|
|
def initialize(file)
|
|
error "expected input file" if file.nil?
|
|
@source = File.read file
|
|
@xml = false
|
|
@xml = true if File.extname(file) == ".xml"
|
|
end
|
|
|
|
def combine_id(tokens)
|
|
skips = 0
|
|
tokens.filter_map.with_index {|token, idx|
|
|
if skips != 0
|
|
skips -= 1
|
|
next
|
|
end
|
|
|
|
next token unless token.type == :ident
|
|
|
|
while tokens[idx += 1].type == :ident
|
|
token.text << tokens[idx].text
|
|
skips += 1
|
|
end
|
|
|
|
next token
|
|
}
|
|
end
|
|
|
|
def no_whitespace(tokens)
|
|
tokens.filter { |tok| tok.type != :white }
|
|
end
|
|
|
|
def match_keywords(tokens)
|
|
tokens.map {|token|
|
|
Matcher.match_token(@@keywords, token)
|
|
}
|
|
end
|
|
|
|
def lex_xml
|
|
tokens = @source.chars.filter_map { |char|
|
|
Matcher.match(@@xml_tokens, char)
|
|
}
|
|
combine_id tokens
|
|
end
|
|
|
|
def lex
|
|
return lex_xml if @xml
|
|
|
|
tokens = @source.chars.filter_map { |char|
|
|
Matcher.match(@@tokens, char)
|
|
}
|
|
|
|
#might be bad for strings with spaces in them
|
|
no_whitespace match_keywords combine_id tokens
|
|
end
|
|
end
|
|
|
|
lexer = Lexer.new(ARGV[0])
|
|
stream = lexer.lex
|
|
swept = StringSweeper.new(stream)
|
|
swept.sweep
|
|
|