copied everything over from 2012 and removed all of the actual robot code except the drivetrain stuff
git-svn-id: https://robotics.mvla.net/svn/frc971/2013/trunk/src@4078 f308d9b7-e957-4cde-b6ac-9a88185e7312
diff --git a/aos/build/queues/objects/errors.rb b/aos/build/queues/objects/errors.rb
new file mode 100644
index 0000000..d776a22
--- /dev/null
+++ b/aos/build/queues/objects/errors.rb
@@ -0,0 +1,43 @@
+class QError < Exception
+ def initialize(msg)
+ super()
+ @msg = msg
+ @qstacktrace = []
+ end
+ def self.set_name(name)
+ @pretty_name = name
+ end
+ def self.pretty_name()
+ @pretty_name
+ end
+ def to_s
+ msg = "Error:(#{self.class.pretty_name})\n\t"
+ msg += @msg
+ msg += "\n" if(msg[-1] != "\n")
+ @qstacktrace.each do |part|
+ part = part.q_stack_name if(part.respond_to?(:q_stack_name))
+ msg += "\tfrom: #{part}\n"
+ end
+ return msg
+ end
+ set_name("Base Level Exception.")
+ attr_accessor :qstacktrace
+end
+class QSyntaxError < QError
+ def initialize(msg)
+ super(msg)
+ end
+ set_name("Syntax Error")
+end
+class QNamespaceCollision < QError
+ def initialize(msg)
+ super(msg)
+ end
+ set_name("Namespace Collision")
+end
+class QImportNotFoundError < QError
+ def initialize(msg)
+ super(msg)
+ end
+ set_name("Couldn't Find Target of Import Statement")
+end
diff --git a/aos/build/queues/objects/interface.rb b/aos/build/queues/objects/interface.rb
new file mode 100644
index 0000000..52332b6
--- /dev/null
+++ b/aos/build/queues/objects/interface.rb
@@ -0,0 +1,69 @@
+class MessageElementReq
+ def initialize(type,name)
+ @type = type
+ @name = name
+ end
+ def self.parse(tokens)
+ type = tokens.expect(:tWord).data
+ name = tokens.expect(:tWord).data
+ tokens.expect(:tSemi)
+ return self.new(type,name)
+ end
+end
+class QueueReq
+ def initialize(name,type = nil)
+ @name = name
+ @type = type
+ end
+ def self.parse(tokens)
+ type_or_name = tokens.expect(:tWord).data
+ if(tokens.peak == :tSemi)
+ tokens.expect(:tSemi)
+ return self.new(type_or_name)
+ else
+ name = tokens.expect(:tWord).data
+ tokens.expect(:tSemi)
+ return self.new(name,type_or_name)
+ end
+ end
+end
+class InterfaceStmt < QStmt
+ def initialize(name,elements)
+ @name = name
+ @elements = elements
+ end
+ def q_eval(locals)
+
+ end
+ def self.check_type(tokens,new_type,old_type)
+ return new_type if(old_type == nil)
+ if(new_type != old_type)
+ tokens.qError(<<ERROR_MSG)
+error: intermixing queue definitions (a queue_group feature)
+ and type definitions (a message_group feature)
+ this results in an undefined type value.
+ Wot. Wot.
+ERROR_MSG
+ end
+ return old_type
+ end
+ def self.parse(tokens)
+ name = tokens.expect(:tWord).data
+ values = []
+ type = nil
+ tokens.expect(:tOpenB)
+ while(tokens.peak != :tCloseB)
+ if(tokens.peak.data == "queue")
+ tokens.expect(:tWord)
+ values << QueueReq.parse(tokens)
+ type = check_type(tokens,:queue_group,type)
+ else
+ values << MessageElementReq.parse(tokens)
+ type = check_type(tokens,:message_group,type)
+ end
+ end
+ tokens.expect(:tCloseB)
+ tokens.expect(:tSemi)
+ self.new(name,values)
+ end
+end
diff --git a/aos/build/queues/objects/namespaces.rb b/aos/build/queues/objects/namespaces.rb
new file mode 100644
index 0000000..b799d36
--- /dev/null
+++ b/aos/build/queues/objects/namespaces.rb
@@ -0,0 +1,210 @@
+class LocalSituation
+ attr_accessor :globals,:local
+ def initialize(globals)
+ @globals = globals
+ @local = nil
+ end
+ def package=(qualified)
+ if(@local)
+ raise QSyntaxError.new(<<ERROR_MSG)
+You are redefining the package path.
+ Stuff might break if you do that.
+ Other options include: using another header file, and just using the same namespace.
+ If you are confident that you need this you can remove this check at
+ #{__FILE__}:#{__LINE__}.
+ Or file a feature request.
+ But that would be weird...
+ Wot. Wot.
+ERROR_MSG
+ end
+ @local = @globals.space
+ qualified.names.each do |name|
+ @local = @local.get_make(name)
+ end
+ end
+ def register(value)
+ if(!@local)
+ raise QError.new(<<ERROR_MSG)
+There is no package path defined, This is a big problem because
+ we are kindof expecting you to have a package path...
+ use a :
+ package my_super.cool.project;
+ statement to remedy this situation. (or file a feature request)
+ Wot. Wot.
+ERROR_MSG
+ end
+ @local[value.name] = value
+ value.parent = @local if(value.respond_to?(:parent=))
+ value.loc = @local
+ end
+ def bind(bind_to)
+ return BoundSituation.new(self,bind_to)
+ end
+end
+class BoundSituation < LocalSituation
+ def initialize(locals,bind_to)
+ @globals = globals
+ @local = bind_to
+ end
+end
+class NameSpace
+ attr_accessor :parent,:name
+ def initialize(name = nil,parent = nil)
+ @name = name
+ @parent = parent
+ @spaces = {}
+ end
+ def []=(key,val)
+ if(old_val = @spaces[key])
+ old_val = old_val.created_by if(old_val.respond_to?(:created_by) && old_val.created_by)
+ if(old_val.respond_to?(:q_stack_name))
+ old_val = old_val.q_stack_name
+ else
+ old_val = "eh, it is a #{old_val.class} thats all I know..."
+ end
+ raise QNamespaceCollision.new(<<ERROR_MSG)
+Woah! The name #{queue_name(key).inspect} is already taken by some chap at #{old_val}.
+\tFind somewhere else to peddle your wares.
+\tWot. Wot.
+ERROR_MSG
+ end
+ @spaces[key] = val
+ end
+ def to_cpp_id(name)
+ txt = @name + "::" + name
+ return @parent.to_cpp_id(txt) if(@parent && parent.name)
+ return "::" + txt
+ end
+ def queue_name(queue)
+ get_name() + "." + queue
+ end
+ def [](key)
+ #puts "getting #{get_name}.#{key}"
+ @spaces[key]
+ end
+ def get_make(name)
+ @spaces[name] ||= self.class.new(name,self)
+ end
+ def get_name()
+ if(@parent)
+ return "#{@parent.get_name}.#{@name}"
+ else
+ return "#{@name}"
+ end
+ end
+ def create(cpp_tree)
+ if(@parent && @parent.name)
+ parent = cpp_tree.get(@parent)
+ else
+ parent = cpp_tree
+ end
+ return cpp_tree.add_namespace(@name,parent)
+ end
+ def to_s()
+ "<NameSpace: #{get_name()}>"
+ end
+ def inspect()
+ "<NameSpace: #{get_name()}>"
+ end
+ def root()
+ return self if(@parent == nil)
+ return @parent.root
+ end
+end
+class Globals
+ attr_accessor :space
+ def initialize()
+ @space = NameSpace.new()
+ @space.get_make("aos")
+ @include_paths = []
+ end
+ def paths()
+ @include_paths
+ end
+ def add_path(path)
+ @include_paths << path
+ end
+ def find_file(filename)
+ @include_paths.each do |path_name|
+ new_path = File.expand_path(path_name) + "/" + filename
+ if(File.exists?(new_path))
+ return new_path
+ end
+ end
+ raise QImportNotFoundError.new(<<ERROR_MSG)
+Problem Loading:#{filename.inspect} I looked in:
+\t#{(@include_paths.collect {|name| name.inspect}).join("\n\t")}
+\tbut alas, it was nowhere to be found.
+\tIt is popular to include the top of the repository as the include path start,
+\tand then reference off of that.
+\tI would suggest doing that and then trying to build again.
+\tWot. Wot.
+ERROR_MSG
+ end
+end
+class QualifiedName
+ attr_accessor :names,:off_root
+ def initialize(names,off_root = false)
+ @names = names
+ @off_root = off_root
+ end
+ def test_lookup(namespace)
+ @names.each do |name|
+ namespace = namespace[name]
+ return nil if(!namespace)
+ end
+ return namespace
+ end
+ def is_simple?()
+ return !@off_root && @names.length == 1
+ end
+ def to_simple()
+ return @names[-1]
+ end
+ def to_s()
+ if(@off_root)
+ return ".#{@names.join(".")}"
+ else
+ return @names.join(".")
+ end
+ end
+ def lookup(locals)
+ if(@off_root)
+ local = locals.globals.space
+ else
+ local = locals.local
+ end
+ target = nil
+ while(!target && local)
+ target = test_lookup(local)
+ local = local.parent
+ end
+ return target if(target)
+ if(@off_root)
+ raise QError.new(<<ERROR_MSG)
+I was looking for .#{@names.join(".")}, but alas, it was not under
+\tthe root namespace.
+\tI'm really sorry old chap.
+\tWot. Wot.
+ERROR_MSG
+ else
+ raise QError.new(<<ERROR_MSG)
+I was looking for #{@names.join(".")}, but alas, I could not find
+\tit in #{locals.local.get_name} or any parent namespaces.
+\tI'm really sorry old chap.
+\tWot. Wot.
+ERROR_MSG
+ end
+ end
+ def self.parse(tokens)
+ names = []
+ off_root = (tokens.peak == :tDot)
+ tokens.next if(off_root)
+ names << tokens.expect(:tWord).data
+ while(tokens.peak == :tDot)
+ tokens.next
+ names << tokens.expect(:tWord).data
+ end
+ return self.new(names,off_root)
+ end
+end
diff --git a/aos/build/queues/objects/q_file.rb b/aos/build/queues/objects/q_file.rb
new file mode 100644
index 0000000..f683dda
--- /dev/null
+++ b/aos/build/queues/objects/q_file.rb
@@ -0,0 +1,148 @@
+class QStmt
+ def set_line(val)
+ @file_line = val
+ return self
+ end
+ def q_stack_name()
+ @file_line
+ end
+ def self.method_added(name)
+ @wrapped ||= {}
+ return if(name != :q_eval && name != :q_eval_extern)
+ return if(@wrapped[name])
+ @wrapped[name] = true
+ method = self.instance_method(name)
+ define_method(name) do |*args,&blk|
+ begin
+ method.bind(self).call(*args,&blk)
+ rescue QError => e
+ e.qstacktrace << self
+ raise e
+ end
+ end
+ end
+
+end
+class ImportStmt < QStmt
+ def initialize(filename)
+ @filename = filename
+ end
+ def q_eval(locals)
+ filename = locals.globals.find_file(@filename)
+ #puts "importing #{filename.inspect}"
+ q_file = QFile.parse(filename)
+ q_output = q_file.q_eval_extern(locals.globals)
+ q_output.extern = true
+ return Target::QInclude.new(@filename + ".h")
+ end
+ def self.parse(tokens)
+ line = tokens.pos
+ to_import = (tokens.expect(:tString) do |token|
+ <<ERROR_MSG
+I found a #{token.humanize} at #{token.pos}.
+\tI was really looking for a "filename" for this import statement.
+\tSomething like: import "super_cool_file";
+\tWot.Wot
+ERROR_MSG
+ end).data
+ tokens.expect(:tSemi) do |token|
+ <<ERROR_MSG
+I found a #{token.humanize} at #{token.pos}.
+\tI was really looking for a ";" to finish off this import statement
+\tat line #{line};
+\tSomething like: import #{to_import.inspect};
+\tWot.Wot #{" "*to_import.inspect.length}^
+ERROR_MSG
+ end
+ return self.new(to_import).set_line(line)
+ end
+end
+class PackageStmt < QStmt
+ def initialize(name)
+ @name = name
+ end
+ def q_eval(locals)
+ locals.package = @name
+ return nil
+ end
+ def self.parse(tokens)
+ line = tokens.pos
+ qualified_name = QualifiedName.parse(tokens)
+ tokens.expect(:tSemi)
+ return self.new(qualified_name).set_line(line)
+ end
+end
+class QFile
+ attr_accessor :namespace
+ def initialize(filename,suite)
+ @filename,@suite = filename,suite
+ end
+ def q_eval(globals = Globals.new())
+ local_pos = LocalSituation.new(globals)
+ q_file = Target::QFile.new()
+ @suite.each do |name|
+ val = name.q_eval(local_pos)
+ if(val)
+ if(val.respond_to?(:create))
+ q_file.add_type(val)
+ end
+ end
+ end
+ @namespace = local_pos.local
+ return q_file
+ end
+ def q_eval_extern(globals)
+ local_pos = LocalSituation.new(globals)
+ q_file = Target::QFile.new()
+ @suite.each do |name|
+ if(name.respond_to?(:q_eval_extern))
+ val = name.q_eval_extern(local_pos)
+ else
+ val = name.q_eval(local_pos)
+ end
+ if(val)
+ if(val.respond_to?(:create))
+ q_file.add_type(val)
+ end
+ end
+ end
+ return q_file
+ end
+ def self.parse(filename)
+ tokens = Tokenizer.new(filename)
+ suite = []
+ while(tokens.peak != :tEnd)
+ token = tokens.expect(:tWord) do |token| #symbol
+ <<ERROR_MSG
+I found a #{token.humanize} at #{token.pos}.
+\tI was really looking for a "package", "import", "queue_group",
+\t"message", or "queue" statement to get things moving.
+\tSomething like: import "super_cool_file";
+\tWot.Wot
+ERROR_MSG
+ end
+ case token.data
+ when "package"
+ suite << PackageStmt.parse(tokens)
+ when "import"
+ suite << ImportStmt.parse(tokens)
+ when "queue_group"
+ suite << QueueGroupStmt.parse(tokens)
+ when "message"
+ suite << MessageStmt.parse(tokens)
+ when "queue"
+ suite << QueueStmt.parse(tokens)
+ when "interface"
+ suite << InterfaceStmt.parse(tokens)
+ else
+ tokens.qError(<<ERROR_MSG)
+expected a "package","import","queue","queue_group", or "message" statement rather
+ than a #{token.data.inspect}, (whatever that is?)
+ oh! no! a feature request!?
+ Wot. Wot.
+ERROR_MSG
+ end
+ end
+ return self.new(filename,suite)
+ end
+end
diff --git a/aos/build/queues/objects/queue.rb b/aos/build/queues/objects/queue.rb
new file mode 100644
index 0000000..5693486
--- /dev/null
+++ b/aos/build/queues/objects/queue.rb
@@ -0,0 +1,154 @@
+class MessageElementStmt < QStmt
+ attr_accessor :name
+ def initialize(type,name,length = nil) #lengths are for arrays
+ @type = type
+ @name = name
+ @length = length
+ end
+ CommonMistakes = {"short" => "int16_t","int" => "int32_t","long" => "int64_t"}
+ def check_type_error()
+ if(!(Sizes[@type] || (@length != nil && @type == "char")) )
+ if(correction = CommonMistakes[@type])
+ raise QError.new(<<ERROR_MSG)
+Hey! you have a \"#{@type}\" in your message statement.
+\tplease use #{correction} instead. Your type is not supported because we
+\twant to guarantee that the sizes of the messages stay the same across platforms.
+\tWot. Wot.
+ERROR_MSG
+ elsif(@type == "char")
+ raise QError.new(<<ERROR_MSG)
+Hey! you have a \"#{@type}\" in your message statement.
+\tyou need your declaration to be a char array like: char[10].
+\tor, please use int8_t or uint8_t.
+\tWot. Wot.
+ERROR_MSG
+ else
+ raise QError.new(<<ERROR_MSG)
+Hey! you have a \"#{@type}\" in your message statement.
+\tThat is not in the list of supported types.
+\there is the list of supported types:
+\tint{8,16,32,64}_t,uint{8,16,32,64}_t,bool,float,double#{len_comment}
+\tWot. Wot.
+ERROR_MSG
+ end
+ end
+ end
+
+ PrintFormat = {"bool" => "%c",
+ "float" => "%f",
+ "char" => "%c",
+ "double" => "%f",
+ "uint8_t" => "%\"PRIu8\"",
+ "uint16_t" => "%\"PRIu16\"",
+ "uint32_t" => "%\"PRIu32\"",
+ "uint64_t" => "%\"PRIu64\"",
+ "int8_t" => "%\"PRId8\"",
+ "int16_t" => "%\"PRId16\"",
+ "int32_t" => "%\"PRId32\"",
+ "int64_t" => "%\"PRId64\""}
+ def toPrintFormat()
+ if(format = PrintFormat[@type])
+ return format;
+ end
+ raise QError.new(<<ERROR_MSG)
+Somehow this slipped past me, but
+\tI couldn't find the print format of #{@type}. Really, my bad.
+\tWot. Wot.
+ERROR_MSG
+ end
+
+ Sizes = {"bool" => 1, "float" => 4,"double" => 8}
+ [8,16,32,64].each do |len|
+ Sizes["int#{len}_t"] = len / 8
+ Sizes["uint#{len}_t"] = len / 8
+ end
+ Zero = {"float" => "0.0f","double" => "0.0","bool" => "false"}
+ def size()
+ if(size = Sizes[@type]); return size; end
+ return 1 if(@type == "char")
+ raise QError.new(<<ERROR_MSG)
+Somehow this slipped past me, but
+\tI couldn't find the size of #{@type}. Really, my bad.
+\tWot. Wot.
+ERROR_MSG
+ end
+ def q_eval(locals)
+ check_type_error()
+ if(@length == nil)
+ member = Target::MessageElement.new(@type,@name)
+ else
+ member = Target::MessageArrayElement.new(@type,@name,@length)
+ end
+ member.size = size()
+ member.zero = Zero[@type] || "0";
+ member.printformat = toPrintFormat()
+ locals.local.add_member(member)
+ end
+ def self.parse(tokens)
+ line = tokens.pos
+ type = tokens.expect(:tWord).data
+ len = nil
+ if(tokens.peak == :tOpenB)
+ tokens.expect(:tOpenB)
+ len = tokens.expect(:tNumber).data
+ tokens.expect(:tCloseB)
+ end
+ name = tokens.expect(:tWord).data
+ tokens.expect(:tSemi)
+ return self.new(type,name,len).set_line(line)
+ end
+end
+class MessageStmt < QStmt
+ def initialize(name,suite)
+ @name = name
+ @suite = suite
+ end
+ def q_eval(locals)
+ group = Target::MessageDec.new(@name)
+ locals.register(group)
+ @suite.each do |stmt|
+ stmt.q_eval(locals.bind(group))
+ end
+ return group
+ end
+ def self.parse(tokens)
+ name = tokens.expect(:tWord).data
+ values = []
+ tokens.expect(:tOpenB)
+ while(tokens.peak != :tCloseB)
+ values << MessageElementStmt.parse(tokens)
+ end
+ names = {}
+ values.each do |val|
+ if(names[val.name])
+ raise QSyntaxError.new(<<ERROR_MSG)
+Hey! duplicate name #{val.name.inspect} in your message declaration statement (message #{name}).
+\tI found them at: #{names[val.name].q_stack_name()} and #{val.q_stack_name()}.
+\tWot. Wot.
+ERROR_MSG
+ end
+ names[val.name] = val
+ end
+ tokens.expect(:tCloseB)
+ tokens.expect(:tSemi)
+ self.new(name,values)
+ end
+end
+class QueueStmt < QStmt
+ def initialize(type,name)
+ @type,@name = type,name
+ end
+ def q_eval(locals)
+ queue = Target::QueueDec.new(@type.lookup(locals),@name)
+ locals.register(queue)
+ locals.local.add_queue(queue) if(locals.local.respond_to?(:add_queue))
+ return queue
+ end
+ def self.parse(tokens)
+ line = tokens.pos
+ type_name = QualifiedName.parse(tokens)
+ name = tokens.expect(:tWord).data
+ tokens.expect(:tSemi)
+ return self.new(type_name,name).set_line(line)
+ end
+end
diff --git a/aos/build/queues/objects/queue_group.rb b/aos/build/queues/objects/queue_group.rb
new file mode 100644
index 0000000..136834f
--- /dev/null
+++ b/aos/build/queues/objects/queue_group.rb
@@ -0,0 +1,115 @@
+class QueueGroupTypeStmt < QStmt
+ def initialize(name,suite)
+ @name,@suite = name,suite
+ end
+ def q_eval(locals)
+ group = Target::QueueGroupDec.new(@name)
+ group.created_by = self
+ locals.register(group)
+ @suite.each do |stmt|
+ stmt.q_eval(locals.bind(group))
+ end
+ return group
+ end
+end
+class ImplementsStmt < QStmt
+ def initialize(name)
+ @name = name
+ end
+ def q_eval(locals)
+
+ end
+ def self.parse(tokens)
+ name = QualifiedName.parse(tokens)
+ tokens.expect(:tSemi)
+ return self.new(name)
+ end
+end
+class QueueGroupStmt < QStmt
+ def initialize(type,name)
+ @type,@name = type,name
+ end
+ def q_eval(locals)
+ group = Target::QueueGroup.new(@type.lookup(locals),@name)
+ group.created_by = self
+ locals.register(group)
+ return group
+ end
+ def self.parse(tokens)
+ line = tokens.pos
+ type_name = QualifiedName.parse(tokens)
+ if(type_name.names.include?("queue_group"))
+ tokens.qError(<<ERROR_MSG)
+I was looking at the identifier you gave
+\tfor the queue group type between line #{line} and #{tokens.pos}
+\tThere shouldn't be a queue_group type called queue_group
+\tor including queue_group in it's path, it is a reserved keyword.
+\tWot. Wot.
+ERROR_MSG
+ end
+ if(tokens.peak == :tOpenB)
+ if(type_name.is_simple?())
+ type_name = type_name.to_simple
+ else
+ tokens.qError(<<ERROR_MSG)
+You gave the name: "#{type_name.to_s}" but you're only allowed to
+\thave simple names like "#{type_name.names[-1]}" in queue_group definitions
+\ttry something like:
+\tqueue_group ControlLoop { }
+\tWot. Wot.
+ERROR_MSG
+ end
+ tokens.expect(:tOpenB)
+ suite = []
+ while(tokens.peak != :tCloseB)
+ token = tokens.expect(:tWord) do |token|
+ <<ERROR_MSG
+I'm a little confused, I found a #{token.humanize} at #{token.pos}
+\tbut what I really wanted was an identifier signifying a nested
+\tmessage declaration, or queue definition, or an impliments statement.
+\tWot.Wot
+ERROR_MSG
+ end
+ case token.data
+ when "message"
+ suite << MessageStmt.parse(tokens)
+ when "queue"
+ suite << QueueStmt.parse(tokens)
+ when "implements"
+ suite << ImplementsStmt.parse(tokens)
+ else
+ tokens.qError(<<ERROR_MSG)
+expected a "queue","implements" or "message" statement rather
+\tthan a #{token.data.inspect}.
+\tWot. Wot.
+ERROR_MSG
+ end
+ end
+ tokens.expect(:tCloseB)
+ obj = QueueGroupTypeStmt.new(type_name,suite).set_line(line)
+ else
+ name = (tokens.expect(:tWord) do |token|
+ <<ERROR_MSG
+I found a #{token.humanize} at #{token.pos}
+\tbut I was in the middle of parsing a queue_group statement, and
+\twhat I really wanted was an identifier to store the queue group.
+\tSomething like: queue_group control_loops.Drivetrain my_cool_group;
+\tWot.Wot
+ERROR_MSG
+ end).data
+ obj = QueueGroupStmt.new(type_name,name).set_line(line)
+ if(tokens.peak == :tDot)
+ tokens.qError(<<ERROR_MSG)
+Hey! It looks like you're trying to use a complex identifier at: #{tokens.pos}
+\tThats not going to work. Queue Group definitions have to be of the form:
+\tqueue_group ComplexID SimpleID
+\tWot. Wot.
+ERROR_MSG
+ end
+ end
+ tokens.expect(:tSemi) do |token|
+ token.pos
+ end
+ return obj
+ end
+end
diff --git a/aos/build/queues/objects/tokenizer.rb b/aos/build/queues/objects/tokenizer.rb
new file mode 100644
index 0000000..2a33b90
--- /dev/null
+++ b/aos/build/queues/objects/tokenizer.rb
@@ -0,0 +1,213 @@
+class BufferedReader
+ def initialize(file)
+ @file = file
+ @line = 1
+ @chars = []
+ @col_nums = []
+ @col = 0
+ end
+ def filename
+ return File.basename(@file.path)
+ end
+ def pos()
+ "#{filename()}:#{lineno()},#{@col}"
+ end
+ def clear_comment()
+ @file.gets
+ @line += 1
+ end
+ def lineno()
+ return @line
+ return @file.lineno + 1
+ end
+ def pop_char()
+ val = @chars.pop() || @file.read(1)
+ @col_nums[@line] = @col += 1
+ if(val == "\n")
+ @line += 1
+ @col = 0
+ end
+
+ return val
+ end
+ def unpop_char(char)
+ if(char == "\n")
+ @line -= 1
+ @col = @col_nums[@line]
+ end
+ @col -= 1
+ @chars.push(char)
+ end
+end
+class Tokenizer
+ TOKEN_TYPES = {"{" => :tOpenB,"}"=> :tCloseB,";" => :tSemi,"," => :tComma,
+ "(" => :tOpenParan,")" => :tCloseParan,"=" => :tAssign,"." => :tDot,
+ "<<"=> :tLShift,"*" => :tMult,"+" => :tAdd,"[" => :tOpenB,
+ "]" => :tCloseB}
+ Humanize = TOKEN_TYPES.invert
+ class Token
+ attr_accessor :type,:data,:pos
+ def to_s
+ if(@type == :tString)
+ val = @data.inspect.to_s
+ elsif(@type == :tWord)
+ val = @data.to_s
+ else
+ val = @data.to_s
+ end
+ return "#{val.ljust(50)}:#{@type}"
+ end
+ def humanize()
+ if(@type == :tString)
+ return "#{@data.inspect.to_s} string"
+ elsif(@type == :tWord)
+ return "#{@data.inspect} identifier"
+ end
+ return Humanize[@type].inspect
+ end
+ def inspect()
+ data = ""
+ data = " #{@data.inspect}" if(@data)
+ "<Token :#{@type}#{data} at #{@pos}>"
+ end
+ def ==(other)
+ if(other.class == Symbol)
+ return @type == other
+ elsif(other.class == self.class)
+ return @type == other.type && @data == other.data
+ else
+ return nil
+ end
+ end
+ end
+ def initialize(file)
+ file = File.open(file,"r") if(file.class == String)
+ @read = BufferedReader.new(file)
+ end
+ def qError(error)
+ syntax_error(error)
+ end
+ def syntax_error(msg)
+ err = QSyntaxError.new(msg)
+ err.qstacktrace << "#{@read.lineno} of #{@read.filename}"
+ raise err
+ end
+ def peak_token()
+ @peak_token = next_token()
+ end
+ def peak()
+ peak_token()
+ end
+ def next()
+ next_token()
+ end
+ def pos()
+ @read.pos
+ end
+ def tokenize(string_token)
+ token = Token.new()
+ token.type = TOKEN_TYPES[string_token]
+ return token
+ end
+ def next_token()
+ if(token = @peak_token)
+ @peak_token = nil
+ return token
+ end
+ token = next_token_cache()
+ pos = self.pos()
+ token.pos = pos
+ return token
+ end
+ def next_token_cache()
+ token = Token.new()
+ token.data = ""
+ while (char = @read.pop_char())
+ #puts "#{char.inspect}:#{token.inspect}"
+ if(char == "/")
+ if(@read.pop_char == "/")
+ @read.clear_comment()
+ else
+ syntax_error("unexpected #{char.inspect}")
+ end
+ elsif(char == "#")
+ @read.clear_comment()
+ elsif(char =~ /[\s\r\n]/)
+ if(token.type)
+ return token
+ end
+ elsif(char =~ /\"/)
+ token.type = :tString
+ token.data = ""
+ while((char = @read.pop_char) != "\"")
+ token.data += char
+ end
+ return token
+ elsif(char =~ /[1-9]/)
+ token.type = :tNumber
+ token.data = char.to_i
+ while(char != ".")
+ char = @read.pop_char
+ if(char =~ /[0-9]/)
+ token.data = char.to_i + token.data * 10
+ elsif(char == ".")
+ else
+ @read.unpop_char(char)
+ return token
+ end
+ end
+ second_char = 0
+ man = 0
+ while(true)
+ char = @read.pop_char
+ if(char =~ /[0-9]/)
+ second_char = char.to_i + second_char * 10
+ man = man * 10
+ else
+ @read.unpop_char(char)
+ token.data += second_char / man.to_f
+ return token
+ end
+ end
+ elsif(char == ":")
+ if(@read.pop_char == "=")
+ return tokenize(":=")
+ end
+ syntax_error("unexpected \":\"")
+ elsif(char =~ /[;\{\}=()\",\*\+\.\[\]]/)
+ return(tokenize(char))
+ elsif(char =~ /[a-zA-Z_]/)
+ token.type = :tWord
+ token.data = char
+ while(true)
+ char = @read.pop_char()
+ if(char && char =~ /\w/)
+ token.data += char
+ else
+ @read.unpop_char(char)
+ return token
+ end
+ end
+ elsif(char == "<")
+ if((char = @read.pop_char()) == "<")
+ return tokenize("<<")
+ else
+ @read.unpop_char(char)
+ return tokenize("<")
+ end
+ else
+ syntax_error("unexpected #{char.inspect}")
+ end
+ end
+ token.type = :tEnd
+ return token
+ end
+ def expect(type,&blk)
+ token = self.next
+ if(token != type)
+ syntax_error(blk.call(token)) if(blk)
+ syntax_error("unexpected: #{token.type}")
+ end
+ return token
+ end
+end