sandyx86 6 days ago
parent 609dbf39d2
commit 6c8e3c8c20

@ -0,0 +1,365 @@
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

@ -0,0 +1,3 @@
<root attr="hello world">
<subroot attr="yes">wow</subroot>
</root>

@ -0,0 +1,2 @@
int x = 2 + 4 * 3 - 8;
y = 23;
Loading…
Cancel
Save