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.
330 lines
5.8 KiB
Ruby
330 lines
5.8 KiB
Ruby
#!/usr/bin/ruby
|
|
|
|
def error(msg)
|
|
puts "ERROR: #{msg}"
|
|
exit 0
|
|
end
|
|
|
|
TokenTypes = {
|
|
:blackspace => /\S+/,
|
|
:whitespace => /\s+/
|
|
}
|
|
|
|
BasicTokens = {
|
|
:open_brace => /{/,
|
|
:closed_brace => /}/,
|
|
:open_paren => /\(/,
|
|
:closed_paren => /\)/,
|
|
:open_bracket => /\[/,
|
|
:closed_bracket => /\]/,
|
|
:comma => /,/,
|
|
:dot => /\./,
|
|
:double_quote => /\"/,
|
|
:single_quote => /\'/,
|
|
:plus => /\+/,
|
|
:minus => /\-/,
|
|
:mult => /\*/,
|
|
:div => /\//,
|
|
:number => /[0-9]+/,
|
|
:colon => /:/,
|
|
:semicolon => /;/,
|
|
|
|
:text => /\S/,
|
|
:whitespace => /\s+/,
|
|
:doubleq => /\"/,
|
|
}
|
|
|
|
KeywordTokens = {
|
|
:intf_decl => /@interface/,
|
|
:impl_decl => /@implementation/,
|
|
:end => /@end/,
|
|
:property => /@property/,
|
|
:synthezize => /@synthesize/,
|
|
}
|
|
|
|
class Token
|
|
attr_reader :str
|
|
attr_accessor :type
|
|
attr_accessor :matcher
|
|
|
|
def initialize(str, hash)
|
|
@matcher = Matcher.new(hash)
|
|
@str, @type = str, @matcher.match(str)
|
|
end
|
|
|
|
def to_s
|
|
"#{@type}: #{@str}"
|
|
end
|
|
|
|
def combine(token)
|
|
@str << token.str
|
|
end
|
|
|
|
def match_with(hash)
|
|
return self unless Token.new(str, hash).matcher.match(@str)
|
|
Token.new(str, hash)
|
|
end
|
|
end
|
|
|
|
class Matcher
|
|
def initialize(hash)
|
|
@hash = hash
|
|
end
|
|
|
|
def match(val)
|
|
@hash.each {|k,v|
|
|
return k if val =~ v
|
|
}
|
|
|
|
return nil
|
|
end
|
|
end
|
|
|
|
class ModeMatcher
|
|
def initialize(hash)
|
|
@hash = hash
|
|
end
|
|
|
|
def match(val)
|
|
@hash.each {|k,v|
|
|
return k if val == v
|
|
}
|
|
|
|
return nil
|
|
end
|
|
end
|
|
|
|
Modes = {
|
|
:intf_decl => :intf_decl,
|
|
:impl_decl => :impl_decl,
|
|
:end => :end,
|
|
}
|
|
|
|
class VarDecl
|
|
def initialize(type, name)
|
|
@type = type
|
|
@name = name
|
|
end
|
|
|
|
def to_s
|
|
"#{@type} #{@name};"
|
|
end
|
|
end
|
|
|
|
class VariableDecl
|
|
def initialize(stream)
|
|
@stream = stream
|
|
@modes = [:type, :var, :semicolon]
|
|
@mode = :type
|
|
end
|
|
|
|
def new_from(type, vars)
|
|
vars.map { |v|
|
|
VarDecl.new(type, v)
|
|
}
|
|
end
|
|
|
|
def parse
|
|
vars = []
|
|
|
|
until @stream.empty?
|
|
token = @stream.shift
|
|
@mode = token.type if token.type == :semicolon
|
|
|
|
case @mode
|
|
when :type
|
|
type = token
|
|
@mode = var
|
|
when :var
|
|
vars << token
|
|
when :semicolon
|
|
return [VariableDecl.new_from(type, vars), @stream]
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
class InterfaceDeclHeader
|
|
def initialize(name, superclass)
|
|
@name, @superclass = name, superclass
|
|
@type = :intf_decl_header
|
|
@expect = [:intf_struct_member, :intf_property, :intf_method]
|
|
@microexpect = {
|
|
:open_brace => :intf_struct_member,
|
|
:kw_property => :intf_property,
|
|
:minus => :intf_method,
|
|
:plus => :class_method,
|
|
:end => :any,
|
|
}
|
|
end
|
|
|
|
def self.new_from(par)
|
|
first = par.shift
|
|
name = par.shift
|
|
par.shift
|
|
superclass = par.shift
|
|
InterfaceDeclHeader.new(name, superclass)
|
|
end
|
|
|
|
def parse(stream)
|
|
token = stream.shift
|
|
expected = @microexpect[token]
|
|
end
|
|
end
|
|
|
|
class InterfaceStructMember
|
|
def initialize(type, name)
|
|
@vartype, @name = vartype, name
|
|
@type = :intf_struct_member
|
|
@expect = [:intf_struct_member, :intf_property, :intf_method]
|
|
end
|
|
end
|
|
|
|
class InterfaceMethod
|
|
def initialize(return_type, name, args)
|
|
@return_type, @name, @args = return_type, name, args
|
|
@type = :intf_method
|
|
@expect [:intf_method, :end]
|
|
end
|
|
end
|
|
|
|
class InterfaceDecl
|
|
def initialize(name, superclass)
|
|
@name, @superclass = name, superclass
|
|
end
|
|
|
|
def self.new_from(args)
|
|
first = args.shift
|
|
puts first if first != "@interface"
|
|
name = args.shift
|
|
args.shift
|
|
superclass = args.shift
|
|
InterfaceDecl.new(name, superclass)
|
|
end
|
|
|
|
def to_s
|
|
"@interface #{@name.str} : #{@superclass.str}"
|
|
end
|
|
end
|
|
|
|
class ModeSwitch
|
|
def initialize(state)
|
|
@state = state
|
|
end
|
|
|
|
def switch(mode)
|
|
@state.mode = mode if @state.modes.include? mode
|
|
end
|
|
end
|
|
|
|
class SyntaxTape
|
|
attr_accessor :processed
|
|
attr_accessor :remainder
|
|
def initialize(processed, remainder)
|
|
@processed, @remainder = processed, remainder
|
|
end
|
|
|
|
def process(parser)
|
|
#this should return yet another syntaxtape
|
|
parser.parse(@remainder)
|
|
end
|
|
end
|
|
|
|
class StateMachineA
|
|
attr_accessor :mode
|
|
attr_reader :modes
|
|
|
|
def initialize(stream)
|
|
@stream = stream
|
|
@modes = [:intf_decl, :impl_decl, :end]
|
|
@mode = nil
|
|
end
|
|
|
|
def parse
|
|
matcher = ModeSwitch.new(self)
|
|
parameters = []
|
|
class_type = nil
|
|
until @stream.empty?
|
|
token = @stream.shift
|
|
matcher.switch(token.type)
|
|
|
|
case @mode
|
|
when :intf_decl
|
|
class_type = InterfaceDeclHeader
|
|
parameters << token unless token.type == :whitespace
|
|
when :impl_decl
|
|
class_type = ImplDeclHeader
|
|
parameters << token unless token.type == :whitespace
|
|
when :end
|
|
parameters << token
|
|
return SyntaxTape.new(class_type.new_from(parameters), @stream)
|
|
else
|
|
puts "unknown state: #{@mode}"
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
class Lexer
|
|
include Enumerable
|
|
|
|
def keywords
|
|
[/@interface/]
|
|
end
|
|
|
|
def initialize(file)
|
|
error("expected input file") if file.nil?
|
|
@source = File.read(file)
|
|
end
|
|
|
|
def each(&block)
|
|
@source.each_char(&block)
|
|
end
|
|
|
|
def blackmap
|
|
self.map {|c|
|
|
Token.new(c, c=~/\s/ ? :whitespace : :blackspace)
|
|
}
|
|
end
|
|
|
|
def matchmap
|
|
tokens = self.map {|c|
|
|
Token.new(c, BasicTokens)
|
|
}
|
|
|
|
bleed = 0
|
|
|
|
tokens.filter_map.with_index {|token, idx|
|
|
#skips iterations that would result in repeated segments of string
|
|
if bleed != 0
|
|
bleed -= 1
|
|
next
|
|
end
|
|
|
|
next token unless token.type == :text
|
|
#combine text into single token
|
|
while tokens[idx += 1].type == :text
|
|
token.combine(tokens[idx])
|
|
bleed += 1
|
|
next
|
|
end
|
|
|
|
next token
|
|
}.map { |token|
|
|
token.match_with(KeywordTokens)
|
|
}
|
|
end
|
|
|
|
def each_token
|
|
token = ""
|
|
|
|
end
|
|
end
|
|
|
|
source = Lexer.new(ARGV[0])
|
|
a = StateMachineA.new source.matchmap
|
|
tape = a.parse
|
|
puts tape.processed
|