diff options
| author | Luke Kanies <luke@madstop.com> | 2005-04-13 15:24:36 +0000 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2005-04-13 15:24:36 +0000 |
| commit | 6ee8b4e7a9731f969347e317456e8d9712fe2641 (patch) | |
| tree | 0e2aa758bb523b4a2f1c752a625bd2d95462edf4 | |
| parent | 5416017c05e44fc635ad14ffdf1ac1163a4cc6e5 (diff) | |
| download | puppet-6ee8b4e7a9731f969347e317456e8d9712fe2641.tar.gz puppet-6ee8b4e7a9731f969347e317456e8d9712fe2641.tar.xz puppet-6ee8b4e7a9731f969347e317456e8d9712fe2641.zip | |
reorganizing
git-svn-id: https://reductivelabs.com/svn/puppet/library/trunk@96 980ebf18-57e1-0310-9a29-db15c13687c0
| -rw-r--r-- | lib/blink.rb | 234 | ||||
| -rwxr-xr-x | lib/blink/attribute.rb | 137 | ||||
| -rwxr-xr-x | lib/blink/component.rb | 55 | ||||
| -rw-r--r-- | lib/blink/fact.rb | 27 | ||||
| -rw-r--r-- | lib/blink/functions.rb | 41 | ||||
| -rw-r--r-- | lib/blink/interface.rb | 108 | ||||
| -rwxr-xr-x | lib/blink/objects.rb | 431 | ||||
| -rwxr-xr-x | lib/blink/objects/file.rb | 214 | ||||
| -rw-r--r-- | lib/blink/objects/package.rb | 197 | ||||
| -rwxr-xr-x | lib/blink/objects/process.rb | 86 | ||||
| -rwxr-xr-x | lib/blink/objects/service.rb | 136 | ||||
| -rw-r--r-- | lib/blink/objects/symlink.rb | 107 | ||||
| -rw-r--r-- | lib/blink/oparse.rb | 336 | ||||
| -rw-r--r-- | lib/blink/parser/grammar.ra | 451 | ||||
| -rw-r--r-- | lib/blink/parser/interpreter.rb | 222 | ||||
| -rw-r--r-- | lib/blink/parser/lexer.rb | 182 | ||||
| -rw-r--r-- | lib/blink/parser/makefile | 5 | ||||
| -rw-r--r-- | lib/blink/parser/parser.rb | 683 | ||||
| -rw-r--r-- | lib/blink/selector.rb | 82 |
19 files changed, 3734 insertions, 0 deletions
diff --git a/lib/blink.rb b/lib/blink.rb new file mode 100644 index 000000000..70abb2a9c --- /dev/null +++ b/lib/blink.rb @@ -0,0 +1,234 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'digest/md5' +require 'etc' +require 'singleton' +require 'blink/component' +require 'blink/interface' +require 'blink/selector' +require 'blink/objects' +require 'blink/objects/service' +require 'blink/objects/file' +require 'blink/objects/symlink' + +PINK="[0;31m" +GREEN="[0;32m" +YELLOW="[0;33m" +SLATE="[0;34m" +ORANGE="[0;35m" +BLUE="[0;36m" +RESET="[0m" + +#------------------------------------------------------------ +# the top-level module +# +# all this really does is dictate how the whole system behaves, through +# preferences for things like debugging +# +# it's also a place to find top-level commands like 'debug' +module Blink + # the hash that determines how our system behaves + @@config = Hash.new(false) + + # produce debugging info + def Blink.debug(ary) + msg = "" + if ary.class == String + msg = ary + else + msg = ary.join(" ") + end + + if @@config[:debug] + Blink::Message.new( + :level => :debug, + :source => "Blink", + :message => msg + ) + end + end + + # set up our configuration + def Blink.init(args) + args.each {|p,v| + @@config[p] = v + } + end + + # just print any messages we get + # we should later behave differently depending on the message + def Blink.newmessage(msg) + puts msg + end + + # DISABLED + + # we've collected all data; now operate on it +# def Blink.run +# ops = Blink::Objects.genops() +# ops.find_all { |op| +# op.auto?() +# }.each { |op| +# Blink::Message.new( +# :level => :debug, +# :source => "Blink", +# :message => "Running op %s" % op +# ) +# op.check +# }.find_all { |op| +# puts "dirty? #{op}" +# op.dirty? +# }.collect { |op| +# puts "%s is dirty; %s instead of %s" % [op, op.state, op.should] +# op.fix +# }.each { |event| # this might need to support lists someday... +# #list.each { |event| +# puts event +# event.trigger +# #} +# } +# end +# +# def Blink.walk +# root = Blink::Objects.root +# root.check +# if root.dirty? +# Blink::Message.new( +# :message => "someone's dirty", +# :level => :notice, +# :source => root +# ) +# root.fix +# end +# end + + # configuration parameter access and stuff + def Blink.[](param) + return @@config[param] + end + + # configuration parameter access and stuff + def Blink.[]=(param,value) + @@config[param] = value + end + + # a simple class for creating callbacks + class Event + attr_reader :event, :object + attr_writer :event, :object + + def initialize(args) + @event = args[:event] + @object = args[:object] + + if @event.nil? or @object.nil? + raise "Event.new called incorrectly" + end + end + + def trigger + @object.trigger(@event) + end + end + + # a class for storing state + # not currently used + class State + include Singleton + @@config = "/var/tmp/blinkstate" + @@state = Hash.new + @@splitchar = " " + + def initialize + self.load + end + + def State.load + puts "loading state" + return unless File.exists?(@@config) + File.open(@@config) { |file| + file.gets { |line| + myclass, key, value = line.split(@@splitchar) + + unless defined? @@state[myclass] + @@state[myclass] = Hash.new + end + + @@state[myclass][key] = value + } + } + end + + def State.state(myclass) + unless defined? @@state[myclass] + @@state[myclass] = Hash.new + end + return @@state[myclass] + end + + def State.store + File.open(@@config, File::CREAT|File::WRONLY, 0644) { |file| + @@state.each { |key, value| + file.puts([self.class,key,value].join(@@splitchar)) + } + } + end + end + + #------------------------------------------------------------ + # provide feedback of various types to the user + # modeled after syslog messages + # each level of message prints in a different color + class Message + @@messages = Array.new + @@levels = [ :debug, :verbose, :notice, :warning, :error ] + @@colors = { + :debug => SLATE, + :verbose => ORANGE, + :notice => PINK, + :warning => GREEN, + :error => YELLOW + } + + attr_reader :level, :message + attr_writer :level, :message + + def initialize(args) + unless args.include?(:level) && args.include?(:message) && + args.include?(:source) + raise "Blink::Message called incorrectly" + end + + if args[:level].class == String + @level = args[:level].intern + elsif args[:level].class == Symbol + @level = args[:level] + else + raise "Level is not a string or symbol: #{args[:level].class}" + end + @message = args[:message] + @source = args[:source] + @time = Time.now + # this should include the host name, and probly lots of other + # stuff, at some point + unless @@levels.include?(level) + raise "Invalid message level #{level}" + end + + @@messages.push(self) + Blink.newmessage(self) + end + + def to_s + # this probably won't stay, but until this leaves the console, + # i'm going to use coloring... + #return "#{@time} #{@source} (#{@level}): #{@message}" + return @@colors[@level] + "%s %s (%s): %s" % [ + @time, @source, @level, @message + ] + RESET + end + end + #------------------------------------------------------------ +end diff --git a/lib/blink/attribute.rb b/lib/blink/attribute.rb new file mode 100755 index 000000000..e6c2d83bd --- /dev/null +++ b/lib/blink/attribute.rb @@ -0,0 +1,137 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +module Blink + # this is a virtual base class for attributes + # attributes are self-contained building blocks for objects + + # Attributes can currently only be used for comparing a virtual "should" value + # against the real state of the system. For instance, you could verify that + # a file's owner is what you want, but you could not create two file objects + # and use these methods to verify that they have the same owner + class Attribute + include Comparable + + attr_accessor :value + attr_accessor :should + attr_accessor :object + + #----------------------------------- + # every attribute class must tell us what it's name will be (as a symbol) + # this determines how we will refer to the attribute during usage + # e.g., the Owner attribute for Files might say its name is :owner; + # this means that we can say "file[:owner] = 'yayness'" + def Attribute.name + return @name + end + #----------------------------------- + + #----------------------------------- + # we aren't actually comparing the attributes themselves, we're only + # comparing the "should" value with the "real" value + def insync? + Blink.debug "%s value is %s, should be %s" % [self,self.value,self.should] + self.value == self.should + end + #----------------------------------- + + #----------------------------------- + def initialize(value) + @should = value + end + #----------------------------------- + + #----------------------------------- + # DISABLED: we aren't comparing attributes, just attribute values + # are we in sync? + # this could be a comparison between two attributes on two objects, + # or a comparison between an object and the live system -- we'll + # let the object decide that, rather than us + #def <=>(other) + # if (self.value.respond_to?(:<=>)) + # return self.value <=> other + # else + # fail TypeError.new("class #{self.value.class} does not respond to <=>") + # end + #end + #----------------------------------- + + #----------------------------------- + # each attribute class must define the name() method + def name + return self.class.name + end + #----------------------------------- + + #----------------------------------- + # retrieve the current state from the running system + def retrieve + raise "'retrieve' method was not overridden by %s" % self.class + end + #----------------------------------- + + #----------------------------------- + def to_s + return @object.name.to_s + " -> " + self.name.to_s + end + #----------------------------------- + + #----------------------------------- + # this class is for attributes that don't reflect on disk, + # like 'path' on files and 'name' on processes + # these are basically organizational attributes, not functional ones + # + # we provide stub methods, so that from the outside it looks like + # other attributes + # + # see objects.rb for how this is used + class Symbol + attr_reader :value + attr_reader :should + + def initialize(symbol) + @symbol = symbol + end + + def name + return @symbol.id2name + end + + def retrieve + true + end + + def insync? + true + end + + def should=(value) + @value = value + @should = value + end + + def sync + true + end + + def value=(value) + @value = value + @should = value + end + end + end + + # this class is supposed to be used to solve cases like file modes, + # where one command (stat) retrieves enough data to cover many attributes + # (e.g., setuid, setgid, world-read, etc.) + class MetaAttribute + include Comparable + attr_accessor :parent + attr_accessor :value + + def <=>(other) + raise "'<=>' method was not overridden by %s" % self.class + end + end +end diff --git a/lib/blink/component.rb b/lib/blink/component.rb new file mode 100755 index 000000000..bad1b0761 --- /dev/null +++ b/lib/blink/component.rb @@ -0,0 +1,55 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the object allowing us to build complex structures +# this thing contains everything else, including itself + +require 'blink/interface' + +module Blink + class Component < Blink::Interface + attr_accessor :name + + @objects = Hash.new(nil) + + #--------------------------------------------------------------- + # our components are effectively arrays, with a bit extra functionality + def each + @subobjects.each { |obj| + yield obj + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def initialize(*args) + args = Hash[*args] + + unless args.include?(:name) + fail TypeError.new("Components must be provided a name") + else + self.name = args[:name] + end + + Component[self.name] = self + + @subobjects = [] + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def push(*objs) + objs.each { |obj| + @subobjects.push(obj) + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def to_s + return self.name + end + #--------------------------------------------------------------- + end +end diff --git a/lib/blink/fact.rb b/lib/blink/fact.rb new file mode 100644 index 000000000..9adcc3b7a --- /dev/null +++ b/lib/blink/fact.rb @@ -0,0 +1,27 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# an interface for registering and retrieving facts +# this is an abstract interface, and should just be used to interact +# with another library + +# currently a very thin veneer on 'facter' + +require 'facter' + +module Blink + class Fact + # just pass the block to 'add' + # the block has to do things like set the interpreter, + # the code (which can be a ruby block), and maybe the + # os and osrelease + def Fact.add(name,&block) + Facter[name].add(&block) + end + + def Fact.[](name) + Facter[name] + end + end +end diff --git a/lib/blink/functions.rb b/lib/blink/functions.rb new file mode 100644 index 000000000..a352cfa4b --- /dev/null +++ b/lib/blink/functions.rb @@ -0,0 +1,41 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'blink' +require 'blink/fact' + +module Blink + class Function + @@functions = Hash.new(nil) + + #--------------------------------------------------------------- + def [](name) + return @@functions[name] + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def call(*args) + @code(*args) + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # we want a 'proc' item instead of a block, so that we can return + # from it + def initialize(name,code) + @name = name + @code = code + + @@functions[name] = self + end + #--------------------------------------------------------------- + end + + Function.new("retrieve", proc { |fact| + require 'blink/fact' + + return Fact[fact] + }) +end diff --git a/lib/blink/interface.rb b/lib/blink/interface.rb new file mode 100644 index 000000000..a1496f01b --- /dev/null +++ b/lib/blink/interface.rb @@ -0,0 +1,108 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# our duck type interface + +# all of our first-class objects (objects, attributes, and components) will +# respond to these methods +# although attributes don't inherit from Blink::Interface +# although maybe Blink::Attribute should... + +# the default behaviour that this class provides is to just call a given +# method on each contained object, e.g., in calling 'sync', we just run: +# object.each { |subobj| subobj.sync() } + +# to use this interface, just define an 'each' method and 'include Blink::Interface' +module Blink + class Interface + # each subclass of Blink::Interface must create a class-local @objects + # variable + @objects = Hash.new # this one won't be used, since this class is abstract + + # this is a bit of a hack, but it'll work for now + attr_accessor :performoperation + + #--------------------------------------------------------------- + def evaluate + self.retrieve + unless self.insync? + if @performoperation == :sync + self.sync + else + # we, uh, don't do anything + end + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # set up the "interface" methods + [:sync,:retrieve].each { |method| + self.send(:define_method,method) { + self.each { |subobj| + Blink.debug("sending '%s' to '%s'" % [method,subobj]) + subobj.send(method) + } + } + } + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # if all contained objects are in sync, then we're in sync + def insync? + insync = true + + self.each { |obj| + unless obj.insync? + Blink.debug("%s is not in sync" % obj) + insync = false + end + } + + Blink.debug("%s sync status is %s" % [self,insync]) + return insync + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # retrieve a named object + def Interface.[](name) + if @objects.has_key?(name) + return @objects[name] + else + raise "Object '#{name}' does not exist" + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is special, because it can be equivalent to running new + # this allows cool syntax like Blink::File["/etc/inetd.conf"] = ... + def Interface.[]=(name,object) + newobj = nil + if object.is_a?(Blink::Interface) + newobj = object + else + raise "must pass a Blink::Interface object" + end + + if @objects.has_key?(newobj.name) + puts @objects + raise "'#{newobj.name}' already exists in " + + "class '#{newobj.class}': #{@objects[newobj.name]}" + else + Blink.debug("adding %s of type %s to class list" % + [object.name,object.class]) + @objects[newobj.name] = newobj + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + def Interface.has_key?(name) + return @objects.has_key?(name) + end + #--------------------------------------------------------------- + end +end diff --git a/lib/blink/objects.rb b/lib/blink/objects.rb new file mode 100755 index 000000000..e00b7ac7c --- /dev/null +++ b/lib/blink/objects.rb @@ -0,0 +1,431 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'blink/attribute' +require 'blink/interface' + + +module Blink + class Objects < Blink::Interface + include Enumerable + @objects = Hash.new # a class instance variable + @@allobjects = Array.new # and then a hash for all objects + + #--------------------------------------------------------------- + # the class methods + + #----------------------------------- + # all objects total + def Objects.push(object) + @@allobjects.push object + Blink.debug("adding %s of type %s to master list" % [object.name,object.class]) + end + #----------------------------------- + + #----------------------------------- + # this should make it so our subclasses don't have to worry about + # defining these class instance variables + def Objects.inherited(sub) + sub.module_eval %q{ + @objects = Hash.new + @actions = Hash.new + } + end + #----------------------------------- + + #----------------------------------- + # some simple stuff to make it easier to get a name from everyone + def Objects.namevar + return @namevar + end + #----------------------------------- + + #----------------------------------- + # accessor for the list of acceptable params + def Objects.classparams + return @params + end + #----------------------------------- + + #----------------------------------- + # our param list is by class, so we need to convert it to names + # (see blink/objects/file.rb for an example of how params are defined) + def Objects.classparambyname + unless defined? @paramsbyname + @paramsbyname = Hash.new { |hash,key| + fail TypeError.new( + "Parameter %s is invalid for class %s" % + [key.to_s,self.class.to_s] + ) + } + @params.each { |param| + if param.is_a? Symbol + # store the Symbol class, not the symbol itself + symbolattr = Blink::Attribute::Symbol.new(param) + + @paramsbyname[param] = symbolattr + elsif param.respond_to?(:name) + # these are already classes + @paramsbyname[param.name] = param + else + fail TypeError.new( + "Parameter %s is invalid; it must be a class or symbol" % + param.to_s + ) + end + } + end + return @paramsbyname + end + #----------------------------------- + + #--------------------------------------------------------------- + # the instance methods + + #----------------------------------- + # parameter access and stuff + def [](param) + if @attributes.has_key?(param) + return @attributes[param].should + else + raise "Undefined parameter '#{param}' in #{self}" + end + end + #----------------------------------- + + #----------------------------------- + # because all object parameters are actually attributes, we + # have to do some shenanigans to make it look from the outside + # like @attributes is just a simple hash + # the Symbol stuff is especially a bit hackish + def []=(param,value) + if @attributes.has_key?(param) + @attributes[param].should = value + return + end + + attrclass = self.class.classparambyname[param] + + Blink.debug("creating attribute of type '%s'" % attrclass) + # any given object can normally only have one of any given attribute + # type, but it might have many Symbol attributes + # + # so, we need to make sure that the @attributes hash behaves + # the same whether it has a unique attribute or a bunch of Symbol + # attributes + if attrclass.is_a?(Blink::Attribute::Symbol) + attrclass.should = value + @attributes[param] = attrclass + else + attr = attrclass.new(value) + attr.object = self + if attr.is_a?(Array) + attr.each { |xattr| + @attributes[xattr.name] = attr + } + else + Blink.debug "Creating attr %s in %s" % [attr.name,self] + @attributes[attr.name] = attr + end + end + end + #----------------------------------- + + #----------------------------------- + # return action array + # these are actions to use for responding to events + # no, this probably isn't the best way, because we're providing + # access to the actual hash, which is silly + def action + if not defined? @actions + puts "defining action hash" + @actions = Hash.new + end + @actions + end + #----------------------------------- + + #----------------------------------- + # removing attributes + def delete(attr) + if @attributes.has_key?(attr) + @attributes.delete(attr) + else + raise "Undefined attribute '#{attr}' in #{self}" + end + end + #----------------------------------- + + #----------------------------------- + # XXX this won't work -- too simplistic + # a given object can be in multiple components + # which means... what? that i have to delete things from components? + # that doesn't seem right, somehow... + # do i really ever need to delete things? + #def delete + # self.class.delete[self.name] + #end + #----------------------------------- + + #----------------------------------- + # this can only be used with blocks that are + # valid on operations and objects, as it iterates over both of + # them + # essentially, the interface defined by Blink::Interface is used here + def each + ret = false + nodepth = 0 + unless block_given? + raise "'Each' was not given a block" + end + @attributes.each { |name,attr| + #Blink.debug "'%s' yielding '%s' of type '%s'" % [self,attr,attr.class] + yield(attr) + } + # DISABLED + # until we're clear on what 'enclosure' means, this is + # all disabled + + #if @encloses.length > 0 + # Blink.debug "#{self} encloses #{@encloses}" + ##end + #if defined? Blink['depthfirst'] + # self.eachobj { |enclosed| + # Blink.debug "yielding #{self} to object #{enclosed}" + # ret |= yield(enclosed) + # } + # nodepth = 1 + #end + #self.eachop { |op| + # Blink.debug "yielding #{self} to op #{op}" + # ret |= yield(op) + #} + #if ! defined? Blink['depthfirst'] and nodepth != 1 + # self.eachobj { |enclosed| + # Blink.debug "yielding #{self} to object #{enclosed}" + # ret |= yield(enclosed) + # } + #end + #return ret + end + #----------------------------------- + + #----------------------------------- + # this allows each object to act like both a node and + # a branch + # but each object contains two types of objects: operations and other + # objects.... + def eachobj + unless block_given? + raise "Eachobj was not given a block" + end + @encloses.each { |object| + yield(object) + } + end + #----------------------------------- + + #----------------------------------- + # store the object that immediately encloses us + def enclosedby(obj) + @enclosedby.push(obj) + end + #----------------------------------- + + #----------------------------------- + def enclosed? + defined? @enclosedby + end + #----------------------------------- + + #----------------------------------- + # store a enclosed object + def encloses(obj) + obj.enclosedby(self) + #obj.subscribe(self,'*') + @encloses.push(obj) + end + #----------------------------------- + + #----------------------------------- + # this is a wrapper, doing all of the work that should be done + # and none that shouldn't + def evaluate + end + #----------------------------------- + + #----------------------------------- + # call an event + # this is called on subscribers by the trigger method from the obj + # which sent the event + # event handling should probably be taking place in a central process, + # but.... + def event(event,obj) + Blink.debug "#{self} got event #{event} from #{obj}" + if @actions.key?(event) + Blink.debug "calling it" + @actions[event].call(self,obj,event) + else + p @actions + end + end + #----------------------------------- + + #----------------------------------- + # yay + def initialize(*args) + # params are for classes, attributes are for instances + # hokey but true + @attributes = Hash.new + @monitor = Array.new + + # default to always syncing + @performoperation = :sync + + begin + hash = Hash[*args] + rescue ArgumentError + fail TypeError.new("Incorrect number of arguments for %s" % + self.class.to_s) + end + + # if they passed in a list of attributes they're interested in, + # we mark them as "interesting" + # XXX maybe we should just consider params set to nil as 'interesting' + # + # this isn't used as much as it should be, but the idea is that + # the "interesting" attributes would be the ones retrieved during a + # 'retrieve' call + if hash.include?(:check) + @monitor = hash[:check].dup + hash.delete(:check) + end + + # we have to set the name of our object before anything else, + # because it might be used in creating the other attributes + if hash.has_key?(self.class.namevar) + self[self.class.namevar] = hash[self.class.namevar] + hash.delete(self.class.namevar) + else + raise TypeError.new("A name must be provided at initialization time") + end + + hash.each { |param,value| + @monitor.push(param) + Blink.debug("adding param '%s' with value '%s'" % + [param,value]) + self[param] = value + } + + # add this object to the specific class's list of objects + self.class[name] = self + + # and then add it to the master list + Blink::Objects.push(self) + + @notify = Hash.new + #@encloses = Array.new + #@enclosedby = Array.new + @actions = Hash.new + #@opsgenned = false + + # XXX i've no idea wtf is going on with enclosures + #if self.class == Blink::Objects::Root + # Blink.debug "not enclosing root (#{self.class}) in self" + #else + # Blink::Objects.root.encloses(self) + #end + end + # initialize + #----------------------------------- + + #----------------------------------- + def name + #namevar = self.class.namevar + #Blink.debug "namevar is '%s'" % namevar + #nameattr = @attributes[namevar] + #Blink.debug "nameattr is '%s'" % nameattr + #name = nameattr.value + #Blink.debug "returning %s from attr %s and namevar %s" % [name,nameattr,namevar] + #return name + return @attributes[self.class.namevar].value + end + #----------------------------------- + + #----------------------------------- + def newevent(args) + if args[:event].nil? + raise "newevent called wrong on #{self}" + end + + return Blink::Event.new( + :event => args[:event], + :object => self + ) + end + #----------------------------------- + + #----------------------------------- + # subscribe to an event or all events + # this entire event system is a hack job and needs to + # be replaced with a central event handler + def subscribe(args,&block) + obj = args[:object] + event = args[:event] || '*'.intern + if obj.nil? or event.nil? + raise "subscribe was called wrongly; #{obj} #{event}" + end + obj.action[event] = block + #events.each { |event| + unless @notify.key?(event) + @notify[event] = Array.new + end + unless @notify[event].include?(obj) + Blink.debug "pushing event '%s' for object '%s'" % [event,obj] + @notify[event].push(obj) + end + # } + #else + # @notify['*'.intern].push(obj) + end + #----------------------------------- + + #----------------------------------- + def to_s + self.name + end + #----------------------------------- + + #----------------------------------- + # initiate a response to an event + def trigger(event) + subscribers = Array.new + if @notify.include?('*') and @notify['*'].length > 0 + @notify['*'].each { |obj| subscribers.push(obj) } + end + if (@notify.include?(event) and (! @notify[event].empty?) ) + @notify[event].each { |obj| subscribers.push(obj) } + end + Blink.debug "triggering #{event}" + subscribers.each { |obj| + Blink.debug "calling #{event} on #{obj}" + obj.event(event,self) + } + end + #----------------------------------- + + #----------------------------------- + def validparam(param) + if (self.class.operparams.include?(param) or + self.class.staticparams.include?(param)) + return true + else + return false + end + end + #----------------------------------- + + #--------------------------------------------------------------- + end # Blink::Objects +end diff --git a/lib/blink/objects/file.rb b/lib/blink/objects/file.rb new file mode 100755 index 000000000..3f2a8dcfc --- /dev/null +++ b/lib/blink/objects/file.rb @@ -0,0 +1,214 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'digest/md5' +require 'etc' +require 'blink/attribute' + +module Blink + # we first define all of the attribute that our file will use + # because the objects must be defined for us to use them in our + # definition of the file object + class Attribute + class FileUID < Blink::Attribute + require 'etc' + attr_accessor :file + @name = :owner + + def retrieve + stat = nil + + begin + stat = File.stat(self.object[:path]) + rescue + # this isn't correct, but what the hell + raise "File '%s' does not exist: #{$!}" % self.object[:path] + end + + self.value = stat.uid + unless self.should.is_a?(Integer) + begin + user = Etc.getpwnam(self.should) + if user.gid == "" + raise "Could not retrieve uid for %s" % self.object + end + Blink.debug "converting %s to integer %d" % + [self.should,user.uid] + self.should = user.uid + rescue + raise "Could not get any info on user %s" % self.should + end + end + Blink.debug "chown state is %d" % self.value + end + + #def <=>(other) + # if other.is_a?(Integer) + # begin + # other = Etc.getpwnam(other).uid + # rescue + # raise "Could not get uid for #{@params[:uid]}" + # end + # end +# +# self.value <=> other +# end + + def sync + begin + File.chown(value,-1,self.object[:path]) + rescue + raise "failed to sync #{@params[:file]}: #{$!}" + end + + self.object.newevent(:event => :inode_changed) + end + end + + # this attribute should actually somehow turn into many attributes, + # one for each bit in the mode + # I think MetaAttributes are the answer, but I'm not quite sure + class FileMode < Blink::Attribute + require 'etc' + + @name = :mode + + def retrieve + stat = nil + + begin + stat = File.stat(self.object[:path]) + rescue => error + raise "File %s could not be stat'ed: %s" % [self.object[:path],error] + end + + self.value = stat.mode & 007777 + Blink.debug "chmod state is %o" % self.value + end + + def sync + begin + File.chmod(self.should,self.object[:path]) + rescue + raise "failed to chmod #{self.object[:path]}: #{$!}" + end + self.object.newevent(:event => :inode_changed) + end + end + + # not used until I can figure out how to solve the problem with + # metaattributes + class FileSetUID < Blink::Attribute + require 'etc' + + @parent = Blink::Attribute::FileMode + + @name = :setuid + + def <=>(other) + self.value <=> @parent.value[11] + end + + # this just doesn't seem right... + def sync + tmp = 0 + if self.value == true + tmp = 1 + end + @parent.value[11] = tmp + end + end + + class FileGroup < Blink::Attribute + require 'etc' + + @name = :group + + def retrieve + stat = nil + + begin + stat = File.stat(self.object[:path]) + rescue + # this isn't correct, but what the hell + raise "File #{self.object[:path]} does not exist: #{$!}" + end + + self.value = stat.gid + + # we probably shouldn't actually modify the 'should' value + # but i don't see a good way around it right now + # mmmm, should + unless self.should.is_a?(Integer) + begin + group = Etc.getgrnam(self.should) + # yeah, don't ask me + # this is retarded + #p group + if group.gid == "" + raise "Could not retrieve gid for %s" % self.object + end + Blink.debug "converting %s to integer %d" % + [self.should,group.gid] + self.should = group.gid + rescue + raise "Could not get any info on group %s" % self.should + end + end + Blink.debug "chgrp state is %d" % self.value + end + +# def <=>(other) +# # unless we're numeric... +# if other.is_a?(Integer) +# begin +# group = Etc.getgrnam(other) +# # yeah, don't ask me +# # this is retarded +# #p group +# other = group.gid +# if other == "" +# raise "Could not retrieve gid for %s" % other +# end +# rescue +# raise "Could not get any info on group %s" % other +# end +# end +# +# #puts self.should +# self.value <=> other +# end + + def sync + Blink.debug "setting chgrp state to %d" % self.should + begin + # set owner to nil so it's ignored + File.chown(nil,self.should,self.object[:path]) + rescue + raise "failed to chgrp %s to %s: %s" % + [self.object[:path], self.should, $!] + end + self.object.newevent(:event => :inode_changed) + end + end + end + class Objects + class File < Objects + attr_reader :stat, :path, :params + # class instance variable + @params = [ + Blink::Attribute::FileUID, + Blink::Attribute::FileGroup, + Blink::Attribute::FileMode, + Blink::Attribute::FileSetUID, + :path + ] + + @objects = Hash.new + @actions = Hash.new + @namevar = :path + end # Blink::Objects::File + end # Blink::Objects + +end diff --git a/lib/blink/objects/package.rb b/lib/blink/objects/package.rb new file mode 100644 index 000000000..f35368022 --- /dev/null +++ b/lib/blink/objects/package.rb @@ -0,0 +1,197 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +module Blink + class Objects + # packages are complicated because each package format has completely + # different commands. We need some way to convert specific packages + # into the general package object... + class Package < Objects + attr_reader :version, :format + # class instance variable + @params = [ + :install, + :format, + :version + ] + + @namevar = :name + + # this is already being done in objects.rb + #def Package.inhereted(sub) + # sub.module_eval %q{ + # @objects = Hash.new + # @actions = Hash.new + # } + #end + + def initialize(hash) + end + + def retrieve + end + + def insync? + end + + def sync + end + end # Blink::Objects::Package + + class PackagingType + attr_writer :list, :install, :remove, :check + + @@types = Hash.new(false) + + def PackagingType.[](name) + if @@types.include?(name) + return @@types[name] + else + raise "no such type %s" % name + end + end + + [:list, :install, :remove, :check].each { |method| + self.send(:define_method, method) { + # retrieve the variable + var = eval("@" + method.id2name) + if var.is_a?(Proc) + var.call() + else + raise "only blocks are supported right now" + end + } + } + + def initialize(name) + if block_given? + yield self + end + + @packages = Hash.new(false) + @@types[name] = self + end + + def retrieve + @packages.clear() + + @packages = self.list() + end + end + + PackagingType.new("dpkg") { |type| + type.list = proc { + packages = [] + + # dpkg only prints as many columns as you have available + # which means we don't get all of the info + # stupid stupid + oldcol = ENV["COLUMNS"] + ENV["COLUMNS"] = "500" + + # list out all of the packages + open("| dpkg -l") { |process| + # our regex for matching dpkg output + regex = %r{^(\S+)\s+(\S+)\s+(\S+)\s+(.+)$} + fields = [:status, :name, :version, :description] + hash = {} + + 5.times { process.gets } # throw away the header + + # now turn each returned line into a package object + process.each { |line| + if match = regex.match(line) + hash.clear + + fields.zip(match.captures) { |field,value| + hash[field] = value + } + packages.push Blink::Objects::Package.new(hash) + else + raise "failed to match dpkg line %s" % line + end + } + } + ENV["COLUMNS"] = oldcol + + return packages + } + + # we need package retrieval mechanisms before we can have package + # installation mechanisms... + #type.install = proc { |pkg| + # raise "installation not implemented yet" + #} + + type.remove = proc { |pkg| + cmd = "dpkg -r %s" % pkg.name + output = %x{#{cmd}} + if $? != 0 + raise output + end + } + } + + PackagingType.new("sunpkg") { |type| + type.list = proc { + packages = [] + hash = {} + names = { + "PKGINST" => :name, + "NAME" => nil, + "CATEGORY" => :category, + "ARCH" => :platform, + "VERSION" => :version, + "BASEDIR" => :root, + "HOTLINE" => nil, + "EMAIL" => nil, + "VENDOR" => :vendor, + "DESC" => :description, + "PSTAMP" => nil, + "INSTDATE" => nil, + "STATUS" => nil, + "FILES" => nil + } + + # list out all of the packages + open("| pkginfo -l") { |process| + # we're using the long listing, so each line is a separate piece + # of information + process.each { |line| + case line + when /^$/ then + packages.push Blink::Objects::Package.new(hash) + hash.clear + when /\s*(\w+):\s+(.+)/ + name = $1 + value = $2 + if names.include?(name) + hash[names[name]] = value + else + raise "Could not find %s" % name + end + when /\s+\d+.+/ + # nothing; we're ignoring the FILES info + end + } + } + return packages + } + + # we need package retrieval mechanisms before we can have package + # installation mechanisms... + #type.install = proc { |pkg| + # raise "installation not implemented yet" + #} + + type.remove = proc { |pkg| + cmd = "pkgrm -n %s" % pkg.name + output = %x{#{cmd}} + if $? != 0 + raise output + end + } + } + end # Blink::Objects +end diff --git a/lib/blink/objects/process.rb b/lib/blink/objects/process.rb new file mode 100755 index 000000000..182667d7c --- /dev/null +++ b/lib/blink/objects/process.rb @@ -0,0 +1,86 @@ +#!/usr/local/bin/ruby -w + +require 'blink/operation' +require 'blink/operation/processes' + +# DISABLED +# I'm only working on services, not processes, right now + +module Blink + class Objects + class BProcess < Objects + attr_reader :stat, :path + @params = [:start, :stop, :user, :pattern, :binary, :arguments] # class instance variable + + @objects = Hash.new + @namevar = :pattern + + Blink::Relation.new(self, Blink::Operation::Start, { + :user => :user, + :pattern => :pattern, + :binary => :binary, + :arguments => :arguments + }) + + Blink::Relation.new(self, Blink::Operation::Stop, { + :user => :user, + :pattern => :pattern + }) + + end # Blink::Objects::BProcess + end # Blink::Objects + + class Attribute + class ProcessRunning < Attribute + def retrieve + running = 0 + regex = Regexp.new(@params[:pattern]) + begin + # this ps is only tested on Solaris + # XXX yeah, this definitely needs to be fixed... + %x{ps -ef -U #{@params[:user]}}.split("\n").each { |process| + if regex.match(process) + running += 1 + end + } + rescue + # this isn't correct, but what the hell + Blink::Message.new( + :level => :error, + :source => self.object, + :message => "Failed to run ps" + ) + end + + self.state = running + Blink.debug "there are #{running} #{self.object} processes for start" + end + + def <=>(other) + self.state < 1 + end + + def fix + require 'etc' + # ruby is really cool + uid = 0 + if @params[:user].is_a? Integer + uid = @params[:user] + else + uid = Etc.getpwnam(@params[:user]).uid + end + Kernel.fork { + Process.uid = uid + Process.euid = uid + string = @params[:binary] + (@params[:arguments] || "") + Blink::Message.new( + :level => :notice, + :source => self.object, + :message => "starting" + ) + Kernel.exec(string) + } + end + end + end +end diff --git a/lib/blink/objects/service.rb b/lib/blink/objects/service.rb new file mode 100755 index 000000000..31fd68234 --- /dev/null +++ b/lib/blink/objects/service.rb @@ -0,0 +1,136 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# this is our main way of managing processes right now +# +# a service is distinct from a process in that services +# can only be managed through the interface of an init script +# which is why they have a search path for initscripts and such + +module Blink + class Attribute + class ServiceRunning < Attribute + @name = :running + + def retrieve + self.value = self.running() + Blink.debug "Running value for '%s' is '%s'" % + [self.object.name,self.value] + end + + # should i cache this info? + def running + begin + status = self.object.initcmd("status") + Blink.debug "initcmd status for '%s' is '%s'" % + [self.object.name,status] + + if status # the command succeeded + return 1 + else + return 0 + end + rescue SystemCallError + raise "Could not execute %s" % initscript + end + + end + + def sync + if self.running > 0 + status = 1 + else + status = 0 + end + Blink.debug "'%s' status is '%s' and should be '%s'" % + [self,status,should] + if self.should > 0 + if status < 1 + Blink.debug "Starting '%s'" % self + unless self.object.initcmd("start") + raise "Failed to start %s" % self.name + end + else + Blink.debug "'%s' is already running, yo" % self + #Blink.debug "Starting '%s'" % self + #unless self.object.initcmd("start") + # raise "Failed to start %s" % self.name + #end + end + elsif status > 0 + Blink.debug "Stopping '%s'" % self + unless self.object.initcmd("stop") + raise "Failed to stop %s" % self.name + end + else + Blink.debug "Not running '%s' and shouldn't be running" % self + end + end + end + end + class Objects + class Service < Objects + attr_reader :stat + @params = [ + Blink::Attribute::ServiceRunning, + :name, + :pattern + ] + + @objects = Hash.new + @actions = Hash.new + @namevar = :name + + @searchpaths = Array.new + + def Service.addpath(path) + unless @searchpaths.include?(path) + # XXX should we check to see if the path exists? + @searchpaths.push(path) + end + end + + def Service.search(name) + @searchpaths.each { |path| + # must specify that we want the top-level File, not Blink::...::File + fqname = ::File.join(path,name) + begin + stat = ::File.stat(fqname) + rescue + # should probably rescue specific errors... + Blink.debug("Could not find %s in %s" % [name,path]) + next + end + + # if we've gotten this far, we found a valid script + return fqname + } + end + + # it'd be nice if i didn't throw the output away... + def initcmd(cmd) + script = self.initscript + + Blink.debug "Running '%s %s' as initcmd for '%s'" % + [script,cmd,self] + + rvalue = Kernel.system("%s status" % + [self.initscript]) + + rvalue = Kernel.system("%s %s" % + [self.initscript,cmd]) + + rvalue + end + + def initscript + if defined? @initscript + return @initscript + else + @initscript = Service.search(self.name) + end + end + end # Blink::Objects::BProcess + end # Blink::Objects +end diff --git a/lib/blink/objects/symlink.rb b/lib/blink/objects/symlink.rb new file mode 100644 index 000000000..11187e2cf --- /dev/null +++ b/lib/blink/objects/symlink.rb @@ -0,0 +1,107 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'etc' +require 'blink/attribute' +require 'blink/objects/file' + +module Blink + # okay, how do we deal with parameters that don't have operations + # associated with them? + class Attribute + class SymlinkTarget < Blink::Attribute + require 'etc' + attr_accessor :file + + @name = :target + + def create + begin + Blink.debug("Creating symlink '%s' to '%s'" % + [self.object[:path],self.should]) + unless File.symlink(self.should,self.object[:path]) + raise TypeError.new("Could not create symlink '%s'" % + self.object[:path]) + end + rescue => detail + raise TypeError.new("Cannot create symlink '%s': %s" % + [self.object[:path],detail]) + end + end + + def remove + if FileTest.symlink?(self.object[:path]) + Blink.debug("Removing symlink '%s'" % self.object[:path]) + begin + File.unlink(self.object[:path]) + rescue + raise TypeError.new("Failed to remove symlink '%s'" % + self.object[:path]) + end + elsif FileTest.exists?(self.object[:path]) + raise TypeError.new("Cannot remove normal file '%s'" % + self.object[:path]) + else + Blink.debug("Symlink '%s' does not exist" % + self.object[:path]) + end + end + + def retrieve + stat = nil + + if FileTest.symlink?(self.object[:path]) + self.value = File.readlink(self.object[:path]) + Blink.debug("link value is '%s'" % self.value) + return + else + self.value = nil + return + end + end + + # this is somewhat complicated, because it could exist and be + # a file + def sync + if self.should.nil? + self.remove() + else # it should exist and be a symlink + if FileTest.symlink?(self.object[:path]) + path = File.readlink(self.object[:path]) + if path != self.should + self.remove() + self.create() + end + elsif FileTest.exists?(self.object[:path]) + raise TypeError.new("Cannot replace normal file '%s'" % + self.object[:path]) + else + self.create() + end + end + + self.object.newevent(:event => :inode_changed) + end + end + end + + class Objects + class Symlink < Objects + attr_reader :stat, :path, :params + # class instance variable + @params = [ + Blink::Attribute::FileUID, + Blink::Attribute::FileGroup, + Blink::Attribute::FileMode, + Blink::Attribute::SymlinkTarget, + :path + ] + + @objects = Hash.new + @actions = Hash.new + @namevar = :path + end # Blink::Objects::File + end # Blink::Objects + +end diff --git a/lib/blink/oparse.rb b/lib/blink/oparse.rb new file mode 100644 index 000000000..a96cf1800 --- /dev/null +++ b/lib/blink/oparse.rb @@ -0,0 +1,336 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# parse and write configuration files using objects with minimal parsing abilities + +require 'etc' +require 'blink/interface' + +module Blink + class OParse < Blink::Interface + include Enumerable + + attr_accessor :file, :splitchar, :childtype + + @@classes = Hash.new(nil) + + def OParse.[](name) + return @@classes[name] + end + + def OParse.childtype=(childtype) + @childtype = childtype + end + + def OParse.childtype + return @childtype + end + + def OParse.name=(name) + @name = name + end + + def OParse.regex + return @regex + end + + def OParse.splitchar=(splitchar) + @regex = %r{#{splitchar}} + @splitchar = splitchar + end + + def OParse.splitchar + return @splitchar + end + + def OParse.newtype(arghash) + options = [:name, :linesplit, :recordsplit, :fields, :namevar] + + #arghash = Hash[*args] + + unless arghash.include?(:linesplit) + arghash[:linesplit] = "\n" + end + + arghash.each { |key,value| + unless options.include?(key) + raise "Invalid argument %s on class %s" % + [key,self] + end + } + options.each { |option| + unless arghash.include?(option) + raise "Must pass %s to class %s" % + [option,self.class.to_s] + end + } + + if @@classes.include?(arghash[:name]) + raise "File type %s already exists" % arghash[:name] + end + + klassname = arghash[:name].capitalize + + # create the file type + module_eval " + class %s < OParse + end" % klassname + klass = eval(klassname) + + # now create the record type + klass.childtype = Blink::OLine.newtype( + :name => arghash[:name], + :splitchar => arghash[:recordsplit], + :fields => arghash[:fields], + :namevar => arghash[:namevar] + ) + klass.splitchar = arghash[:linesplit] + klass.name = arghash[:name] + + Blink.debug("adding class %s" % arghash[:name]) + @@classes[arghash[:name]] = klass + + return klass + end + + def [](name) + return @childhash[name] + end + + def []=(name,value) + end + + # we don't really have a 'less-than/greater-than' sense here + # so i'm sticking with 'equals' until those make sense + def ==(other) + unless self.children.length == other.children.length + Blink.debug("file has %s records instead of %s" % + [self.children.length, other.children.length]) + return self.children.length == other.children.length + end + equal = true + self.zip(other.children) { |schild,ochild| + unless schild == ochild + Blink.debug("%s has changed in %s" % + [schild.name,self.name]) + equal = false + break + end + } + + return equal + end + + def add(&block) + obj = self.class.childtype.new(&block) + Blink.debug("adding %s" % obj.name) + @childary.push(obj) + @childhash[obj.name] = obj + + return obj + end + + def children + return @childary + end + + def delete(name) + if @childhash.has_key?(name) + child = @childhash[name] + + @childhash.delete(child) + @childary.delete(child) + else + raise "No such entry %s" % name + end + end + + def each + @childary.each { |child| + yield child + } + end + + def initialize(file) + @file = file + + @childary = [] + @childhash = {} + end + + # this is where we're pretty different from other objects + # we can choose to either reparse the existing file and compare + # the objects, or we can write our file out and do an + # text comparison + def insync? + tmp = self.class.new(@file) + tmp.retrieve + + return self == tmp + end + + def retrieve + str = "" + File.open(@file) { |fname| + fname.each { |line| + str += line + } + } + + @childary = str.split(self.class.regex).collect { |record| + child = self.class.childtype.new + child.record = record + #puts "adding child %s" % child.name + child + } + + @childary.each { |child| + @childhash[child.name] = child + } + end + + def sync + #unless self.insync? + self.write + #end + end + + def to_s + return @childary.collect { |child| + child.to_s + }.join(self.class.splitchar) + self.class.splitchar + end + + def write + File.open(@file, "w") { |file| + file.write(self.to_s) + } + end + end + + class OLine < Blink::Interface + attr_accessor :fields, :namevar, :splitchar + + @@subclasses = {} + + def OLine.fields=(ary) + @fields = ary + end + + def OLine.fields + return @fields + end + + #def OLine.newtype(name,splitchar,fields,namevar) + def OLine.newtype(*args) + options = [:name, :splitchar, :fields, :namevar] + + arghash = Hash[*args] + arghash.each { |key,value| + unless options.include?(key) + raise "Invalid argument %s on class %s" % + [key,self.class.to_s] + end + } + options.each { |option| + unless arghash.include?(option) + raise "Must pass %s to class %s" % + [option,self.class.to_s] + end + } + klassname = arghash[:name].capitalize + + module_eval " + class %s < OLine + end" % klassname + klass = eval(klassname) + + klass.fields = arghash[:fields] + klass.splitchar = arghash[:splitchar] + klass.namevar = arghash[:namevar] + + return klass + end + + def OLine.namevar=(field) + @namevar = field + end + + def OLine.namevar + return @namevar + end + + def OLine.regex + return @regex + end + + def OLine.splitchar=(char) + @splitchar = char + @regex = %r{#{char}} + end + + def OLine.splitchar + return @splitchar + end + + def [](field) + @fields[field] + end + + def []=(field,value) + @fields[field] = value + end + + def ==(other) + unless self.class == other.class + return false + end + + unless self.name == other.name + return false + end + @fields.keys { |field| + unless self[field] == other[field] + Blink.debug("%s -> %s has changed" % [self.name, field]) + return false + end + } + return true + end + + + def initialize + @fields = {} + if block_given? + yield self + end + end + + def record=(record) + ary = record.split(self.class.regex) + self.class.fields.each { |field| + @fields[field] = ary.shift + #puts "%s => %s" % [field,@fields[field]] + } + end + + def name + if @fields.include?(self.class.namevar) + return @fields[self.class.namevar] + else + raise "No namevar for objects of type %s" % self.class.to_s + end + end + + def to_s + ary = self.class.fields.collect { |field| + if ! @fields.include?(field) + raise "Object %s is missing field %s" % [self.name,field] + else + @fields[field] + end + }.join(self.class.splitchar) + end + end +end diff --git a/lib/blink/parser/grammar.ra b/lib/blink/parser/grammar.ra new file mode 100644 index 000000000..ab429a662 --- /dev/null +++ b/lib/blink/parser/grammar.ra @@ -0,0 +1,451 @@ +#/usr/bin/ruby + +# $Id$ +# vim: syntax=ruby + +# the parser + +class Blink::Parser::Parser +token WORD LBRACK QTEXT RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE FALSE EQUALS +token QMARK LPAREN RPAREN +rule +program: statements { + if val[0].is_a?(Array) + result = val[0] + else + result = AST::ASTArray.new([val[0]]) + end + # this is mainly so we can test the parser separately from the + # interpreter + if Blink[:parseonly] + begin + puts result.tree(0) + rescue NoMethodError => detail + puts detail + exit(78) + end + else + require 'blink/parser/interpreter' + result = Blink::Parser::Interpreter.new(result) + end +} + +statements: statement + | statements statement { + if val[0].is_a?(Array) + val[0].push(val[1]) + result = val[0] + else + result = AST::ASTArray.new([val[0],val[1]]) + end +} + +statement: object + | assignment + | selector + | functioncall + +object: WORD LBRACK rvalue RBRACK LBRACE params endcomma RBRACE { + leaf = AST::Word.new( + :line => @lexer.line, + :value => val[0] + ) + result = AST::ObjectDef.new( + :pin => "[]", + :line => @lexer.line, + :object => leaf, + :name => val[2], + :params => val[5] + ) +} + +assignment: WORD EQUALS rvalue { + leaf = AST::Word.new( + :line => @lexer.line, + :value => val[0] + ) + result = AST::VarDef.new( + :pin => "=", + :line => @lexer.line, + :name => leaf, + :value => val[2] + ) +} + +params: param { result = val[0] } + | params COMMA param { + if val[0].is_a?(Array) + val[0].push(val[2]) + result = val[0] + else + result = [val[0],val[2]] + end +} + +param: QTEXT FARROW rvalue { + leaf = AST::String.new( + :line => @lexer.line, + :value => val[0] + ) + result = AST::ObjectParam.new( + :pin => "=>", + :line => @lexer.line, + :param => leaf, + :value => val[2] + ) +} + +rvalues: rvalue + | rvalues rvalue { + if val[0].is_a?(Array) + result = val[0].push(val[1]) + else + result = AST::Array.new(val[0],val[1]) + end +} + +rvalue: QTEXT { + result = AST::String.new( + :line => @lexer.line, + :value => val[0] + ) +} + | selector + | object + | functioncall + | WORD { # these are variable names + result = AST::Word.new( + :line => @lexer.line, + :value => val[0] + ) +} + +selector: WORD QMARK svalues { + leaf = AST::Word.new( + :line => @lexer.line, + :value => val[0] + ) + result = AST::Selector.new( + :pin => "?", + :line => @lexer.line, + :param => leaf, + :value => val[2] + ) +} + +# I'm probably going to regret reusing 'param' here... +svalues: param + | LBRACE sintvalues RBRACE { result = val[1] } + +sintvalues: param + | sintvalues param { + if val[0].is_a?(Array) + val[0].push(val[1]) + result = val[0] + else + result = AST::ASTArray.new([val[0],val[1]]) + end +} + +functioncall: WORD LPAREN rvalues RPAREN { + result = AST::FunctionCall.new( + :pin => '()', + :name => AST::Word.new(:value => val[0], :line => @lexer.line), + :values => val[2] + ) +} + | WORD LPAREN RPAREN { + result = FunctionDef.new( + :pin => '()', + :name => val[0] + ) +} + +endcomma: # nothing + | COMMA { result = nil } + +end +---- header ---- +require 'blink/parser/lexer' +#require 'blink/parser/interpreter' + +module Blink + class ParseError < Racc::ParseError; end +end + +---- inner ---- +def file=(file) + @lexer.file = file +end + +def initialize + @lexer = Blink::Parser::Lexer.new() + if Blink[:debug] + @yydebut = true + end +end + +def on_error(token,value,stack) + #puts "Parse stack:" + #puts stack + #on '%s' at '%s' in\n'%s'" % [token,value,stack] + error = "line %s: parse error after '%s'" % [@lexer.line,@lexer.last] + + if @lexer.file + error += (" in '%s'" % @lexer.file) + end + + raise Blink::ParseError.new(error) +end + +# how should I do error handling here? +def parse + yyparse(@lexer,:scan) + #begin + # yyparse(@lexer,:scan) + #rescue Racc::ParseError => detail + # raise Racc::ParseError.new("line %s: parse error after '%s'" % + # [@lexer.line,@lexer.last]) + #end +end + +def string=(string) + @lexer.string = string +end + +# the parent class for all of our syntactical objects +class AST + attr_accessor :line + @@pink = "[0;31m" + @@green = "[0;32m" + @@yellow = "[0;33m" + @@reset = "[0m" + + @@indent = " " * 4 + @@indline = @@pink + ("-" * 4) + @@reset + @@midline = @@yellow + ("-" * 4) + @@reset + + def AST.indention + return @@indent * @@indention + end + + def AST.midline + return @@midline + end + + def typewrap(string) + #return self.class.to_s.sub(/.+::/,'') + "(" + @@green + string +@@reset+ ")" + return @@green + string +@@reset+ "(" + self.class.to_s.sub(/.+::/,'') + ")" + end + + def initialize(*rest) + begin + args = Hash[*rest] + rescue ArgumentError + raise ArgumentError.new("Arguments must be passed as name => value pairs") + end + args.each { |param,value| + method = param.to_s + "=" + unless self.respond_to?(method) + raise "Invalid parameter %s to object class %s" % + [method,self.class.to_s] + end + + begin + #Blink.debug("sending %s to %s" % [method, self.class]) + self.send(method,value) + rescue => detail + # XXX this should be more normal error correction + raise "Could not set parameter %s on class %s: %s" % + [method,self.class.to_s,detail] + end + } + end + + class ASTArray < Array + def tree(indent = 0) + #puts((AST.indent * indent) + self.pin) + self.collect { |child| + child.tree(indent) + }.join("\n" + (AST.midline * (indent+1)) + "\n") + end + end + + # this differentiation is used by the interpreter + # XXX i now need a standard mechanism for descending into children + + # these objects have children + class Branch < AST + include Enumerable + attr_accessor :pin + + def each + @children.each { |child| + yield child + } + end + + def tree(indent = 0) + return ((@@indline * indent) + self.typewrap(self.pin)) + "\n" + + self.collect { |child| + child.tree(indent + 1) + }.join("\n") + end + end + + # and these ones don't + class Leaf < AST + attr_accessor :value, :type + + def tree(indent = 0) + return ((@@indent * indent) + self.typewrap(self.value)) + end + + def to_s + return @value + end + end + + class String < AST::Leaf + attr_accessor :value + end + + class Word < AST::Leaf + attr_accessor :value + end + + class ObjectDef < AST::Branch + attr_accessor :name, :object + attr_reader :params + + def []=(index,obj) + @params[index] = obj + end + + def [](index) + return @params[index] + end + + def each + #Blink.debug("each called on %s" % self) + [@object,@name,@params].flatten.each { |param| + #Blink.debug("yielding param %s" % param) + yield param + } + end + + def initialize(*args) + super(*args) + end + + def params=(params) + if params.is_a?(Array) + @params = params + else + @params = [params] + end + end + + def tree(indent = 0) + return [ + @object.tree(indent + 1), + @name.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @params.collect { |param| + begin + param.tree(indent + 1) + rescue NoMethodError => detail + puts "failed to tree" + puts @params + p param + raise + end + }.join("\n") + ].join("\n") + end + + def to_s + return "%s => { %s }" % [@name, + @params.collect { |param| + param.to_s + }.join("\n") + ] + end + end + + class ObjectParam < AST::Branch + attr_accessor :value, :param + + def each + [@param,@value].each { |child| yield child } + end + + def tree(indent = 0) + return [ + @param.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @value.tree(indent + 1) + ].join("\n") + end + + def to_s + return "%s => %s" % [@param,@value] + end + end + + class Selector < AST::Branch + attr_accessor :param, :value + + def tree(indent = 0) + return [ + @param.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @value.tree(indent + 1) + ].join("\n") + end + + def each + [@param,@value].each { |child| yield child } + end + end + + class VarDef < AST::Branch + attr_accessor :name, :value + + def each + [@name,@value].each { |child| yield child } + end + + def tree(indent = 0) + return [ + @name.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap(self.pin)), + @value.tree(indent + 1) + ].join("\n") + end + + def to_s + return "%s => %s" % [@name,@value] + end + end + + class FunctionCall < AST::Branch + attr_accessor :name, :values + + def each + [@name,@values].each { |child| yield child } + end + + def tree(indent = 0) + return [ + @name.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap(self.pin)), + @values.tree(indent + 1) + ].join("\n") + end + + def to_s + return "%s => %s" % [@name,@values] + end + end +end diff --git a/lib/blink/parser/interpreter.rb b/lib/blink/parser/interpreter.rb new file mode 100644 index 000000000..0e117fb61 --- /dev/null +++ b/lib/blink/parser/interpreter.rb @@ -0,0 +1,222 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the interpreter +# +# this builds our virtual pinball machine, into which we'll place our host-specific +# information and out of which we'll receive our host-specific configuration + +require 'strscan' +require 'blink' +require 'blink/parser/parser' + + +module Blink + class IntepreterError < RuntimeError; end + module Parser + #--------------------------------------------------------------- + class Interpreter + # just shorten the constant path a bit, using what amounts to an alias + AST = Blink::Parser::Parser::AST + + # make it a class method, since it's not an instance method... + def Interpreter.descend(root,depthfirst = true,&block) + #Blink.debug("root is %s of type %s" % [root,root.class]) + root.each_with_index { |thing,index| + # this is a problem... + # we want to descend into all syntactical objects, but + # we don't want to descend into Blink::Objects because + # that would mean operating directly on attributes, which + # we don't want + if depthfirst + if thing.is_a?(AST::Branch) + Blink.debug("descending thing %s of type %s" % + [thing,thing.class]) + Interpreter.descend(thing,&block) + end + block.call(thing,index,root) + else + block.call(thing,index,root) + if thing.is_a?(AST::Branch) + Blink.debug("descending thing %s of type %s" % + [thing,thing.class]) + Interpreter.descend(thing,&block) + end + end + } + end + + #------------------------------------------------------------ + def askfunc(name,*args) + if func = Blink::Function[name] + # XXX when we're remote, we'll need to do this differently... + func.call(*args) + else + raise "Undefined function %s" % name + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # when we have an 'eval' function, we should do that instead + # for now, we only support variables in strings + def strinterp(string) + regex = Regexp.new('\$\{(\w+)\}}') + while match = regex.match(string) do + string.sub!(regex,self.varvalue(match[0])) + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # basically just return the variable value from the symbol + # table + def varvalue(variable) + unless @symtable.include?(variable) + raise "Undefined variable %s" % variable + end + + return @symtable[variable] + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # create our interpreter + def initialize(tree) + @tree = tree + + @symtable = Hash.new(nil) + @factable = Hash.new(nil) + @objectable = Hash.new { |hash,key| + #hash[key] = IObject.new(key) + hash[key] = {:name => key} + } + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # execute all of the passes (probably just one, in the end) + def run + regex = %r{^pass} + self.methods.sort.each { |method,value| + if method =~ regex + Blink.debug("calling %s" % method) + self.send(method) + end + } + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # i don't know how to deal with evaluation here -- + # all Leafs need to be turned into real values, but only in + # those trees which i know are under 'true' branches + def pass1_umeverything + Interpreter.descend(@tree) { |object,index,parent| + case object + # handle the leaves first + when AST::String then + # interpolate all variables in the string in-place + self.strinterp(object.value) + when AST::Word then + if parent.is_a?(AST::VarDef) # if we're in an assignment + # um, we pretty much don't do anything + else + # this is where i interpolate the variable, right? + # replace the variable AST with a string AST, I guess + # unless, of course, the variable points to another + # object... + # crap, what if it does? + end + when AST::VarDef then + unless object.name.is_a?(AST::Word) + raise InterpreterError.new("invalid variable name") + end + + # this is quite probably more than a simple value... + case object.value + when AST::String then + @symtable[object.name.value] = object.value.value + when AST::Word then + # just copy whatever's already in the symtable + @symtable[object.name.value] = + @symtable[object.value.value] + else + # um, i have no idea what to do in other cases... + end + when AST::FunctionCall then + when AST::ObjectDef then + object.params.each { |param| + } + end + } + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # this pass creates the actual objects + # eventually it will probably be one of the last passes, but + # it's the easiest to create, so... + + # XXX this won't really work for the long term -- + # this will cause each operation on an object to be treated + # as an independent copy of the object, which will fail + # purposefully + def disabled_pass1_mkobjects + Interpreter.descend(@tree) { |object,index,parent| + case object + when Blink::Parser::Parser::AST::ObjectDef then # yuk + args = {} + object.each { |param| + # heh, this is weird + # the parameter object stores its value in @value + # and that's an object, so you have to call .value + # again + args[param.param] = param.value.value + } + + args[:name] = object.name.value + klass = "Blink::Objects::" + object.type.capitalize + newobj = eval(klass).new(args) + parent[index] = newobj + when Blink::Parser::Parser::AST::ObjectParam then + # nothing + end + } + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def disabled_pass2_exeobjects + Blink.debug("tree is %s" % @tree) + Blink.debug("tree type is %s" % @tree.class) + Interpreter.descend(@tree) { |object,index,parent| + #Blink.debug("object is %s" % object) + puts("object is %s" % object) + case + when object.is_a?(Blink::Objects) then + object.evaluate + end + } + end + + class IObject < Hash + attr_accessor :name + + @ohash = {} + @oarray = [] + + def initialize(name) + if @ohash.include?(name) + raise "%s already exists" % name + else + @ohash[name] = self + @oarray.push(self) + end + end + end + end + #--------------------------------------------------------------- + end +end diff --git a/lib/blink/parser/lexer.rb b/lib/blink/parser/lexer.rb new file mode 100644 index 000000000..66e75a315 --- /dev/null +++ b/lib/blink/parser/lexer.rb @@ -0,0 +1,182 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the scanner/lexer + +require 'strscan' +require 'blink' + + +module Blink + class LexError < RuntimeError; end + module Parser + #--------------------------------------------------------------- + class Lexer + attr_reader :line, :last, :file + + @@tokens = { + %r{#.+} => :COMMENT, + %r{\[} => :LBRACK, + %r{\]} => :RBRACK, + %r{\{} => :LBRACE, + %r{\}} => :RBRACE, + %r{\(} => :LPAREN, + %r{\)} => :RPAREN, + %r{"} => :DQUOTE, + %r{\n} => :RETURN, + %r{'} => :SQUOTE, + %r{=} => :EQUALS, + %r{,} => :COMMA, + %r{\?} => :QMARK, + %r{\\} => :BACKSLASH, + %r{=>} => :FARROW, + %r{\w+} => :WORD, + %r{:\w+} => :SYMBOL + } + + # scan the whole file + # basically just used for testing + def fullscan + array = [] + + self.scan { |token,str| + #Blink.debug("got token '%s' => '%s'" % [token,str]) + if token.nil? + return array + else + array.push([token,str]) + end + } + return array + end + + # this is probably pretty damned inefficient... + # it'd be nice not to have to load the whole file first... + def file=(file) + @file = file + File.open(file) { |of| + str = "" + of.each { |line| str += line } + @scanner = StringScanner.new(str) + } + end + + def initialize + @line = 1 + @last = "" + @scanner = nil + @file = nil + @skip = %r{\s+} + end + + def rest + @scanner.rest + end + + # this is the heart of the lexer + def scan + Blink.debug("entering scan") + if @scanner.nil? + raise TypeError.new("Invalid or empty string") + end + + @scanner.skip(@skip) + until @scanner.eos? do + yielded = false + sendbreak = false # gah, this is a nasty hack + stoken = nil + sregex = nil + value = "" + + # first find out which type of token we've got + @@tokens.each { |regex,token| + # we're just checking, which doesn't advance the scan + # pointer + tmp = @scanner.check(regex) + if tmp.nil? + #blink.debug("did not match %s to '%s'" % + # [regex,@scanner.rest]) + next + end + + # find the longest match + if tmp.length > value.length + value = tmp + stoken = token + sregex = regex + else + # we've already got a longer match + next + end + } + + # error out if we didn't match anything at all + if stoken.nil? + raise "Could not match '%s'" % @scanner.rest + end + + value = @scanner.scan(sregex) + + if value == "" + raise "Didn't match regex on token %s" % stoken + end + + # token-specific operations + # if this gets much more complicated, it should + # be moved up to where the tokens themselves are defined + # which will get me about 75% of the way to a lexer generator + case stoken + when :COMMENT then + # just throw comments away + when :RETURN then + Blink.debug("one more line") + @line += 1 + @scanner.skip(@skip) + when :DQUOTE then + #Blink.debug("searching '%s' after '%s'" % [self.rest,value]) + value = self.slurpstring(value) + yield [:QTEXT,value] + @last = value + #stoken = :QTEXT + Blink.debug("got string '%s' => '%s'" % [:QTEXT,value]) + when :SYMBOL then + value.sub!(/^:/,'') + yield [:QTEXT,value] + @last = value + Blink.debug("got token '%s' => '%s'" % [:QTEXT,value]) + else + yield [stoken,value] + @last = value + Blink.debug("got token '%s' => '%s'" % [stoken,value]) + end + @scanner.skip(@skip) + end + @scanner = nil + yield [false,false] + end + + # we've encountered an opening quote... + # slurp in the rest of the string and return it + def slurpstring(quote) + #Blink.debug("searching '%s'" % self.rest) + str = @scanner.scan_until(/[^\\]#{quote}/) + #str = @scanner.scan_until(/"/) + if str.nil? + raise Blink::LexError.new("Unclosed quote after '%s' in '%s'" % + [self.last,self.rest]) + else + str.sub!(/#{quote}$/,"") + str.gsub!(/\\#{quote}/,quote) + end + + return str + end + + def string=(string) + @scanner = StringScanner.new(string) + end + end + #--------------------------------------------------------------- + end +end diff --git a/lib/blink/parser/makefile b/lib/blink/parser/makefile new file mode 100644 index 000000000..eea119d56 --- /dev/null +++ b/lib/blink/parser/makefile @@ -0,0 +1,5 @@ +#parser.rb: grammar.ry +# ryacc --output parser grammar + +parser.rb: grammar.ra + racc -o$@ grammar.ra diff --git a/lib/blink/parser/parser.rb b/lib/blink/parser/parser.rb new file mode 100644 index 000000000..2c341f410 --- /dev/null +++ b/lib/blink/parser/parser.rb @@ -0,0 +1,683 @@ +# +# DO NOT MODIFY!!!! +# This file is automatically generated by racc 1.4.4 +# from racc grammer file "grammar.ra". +# + +require 'racc/parser' + + +require 'blink/parser/lexer' +#require 'blink/parser/interpreter' + +module Blink + class ParseError < Racc::ParseError; end +end + + +module Blink + + module Parser + + class Parser < Racc::Parser + +module_eval <<'..end grammar.ra modeval..id5273b1fd0f', 'grammar.ra', 171 +def file=(file) + @lexer.file = file +end + +def initialize + @lexer = Blink::Parser::Lexer.new() + if Blink[:debug] + @yydebut = true + end +end + +def on_error(token,value,stack) + #puts "Parse stack:" + #puts stack + #on '%s' at '%s' in\n'%s'" % [token,value,stack] + error = "line %s: parse error after '%s'" % [@lexer.line,@lexer.last] + + if @lexer.file + error += (" in '%s'" % @lexer.file) + end + + raise Blink::ParseError.new(error) +end + +# how should I do error handling here? +def parse + yyparse(@lexer,:scan) + #begin + # yyparse(@lexer,:scan) + #rescue Racc::ParseError => detail + # raise Racc::ParseError.new("line %s: parse error after '%s'" % + # [@lexer.line,@lexer.last]) + #end +end + +def string=(string) + @lexer.string = string +end + +# the parent class for all of our syntactical objects +class AST + attr_accessor :line + @@pink = "[0;31m" + @@green = "[0;32m" + @@yellow = "[0;33m" + @@reset = "[0m" + + @@indent = " " * 4 + @@indline = @@pink + ("-" * 4) + @@reset + @@midline = @@yellow + ("-" * 4) + @@reset + + def AST.indention + return @@indent * @@indention + end + + def AST.midline + return @@midline + end + + def typewrap(string) + #return self.class.to_s.sub(/.+::/,'') + "(" + @@green + string +@@reset+ ")" + return @@green + string +@@reset+ "(" + self.class.to_s.sub(/.+::/,'') + ")" + end + + def initialize(*rest) + begin + args = Hash[*rest] + rescue ArgumentError + raise ArgumentError.new("Arguments must be passed as name => value pairs") + end + args.each { |param,value| + method = param.to_s + "=" + unless self.respond_to?(method) + raise "Invalid parameter %s to object class %s" % + [method,self.class.to_s] + end + + begin + #Blink.debug("sending %s to %s" % [method, self.class]) + self.send(method,value) + rescue => detail + # XXX this should be more normal error correction + raise "Could not set parameter %s on class %s: %s" % + [method,self.class.to_s,detail] + end + } + end + + class ASTArray < Array + def tree(indent = 0) + #puts((AST.indent * indent) + self.pin) + self.collect { |child| + child.tree(indent) + }.join("\n" + (AST.midline * (indent+1)) + "\n") + end + end + + # this differentiation is used by the interpreter + # XXX i now need a standard mechanism for descending into children + + # these objects have children + class Branch < AST + include Enumerable + attr_accessor :pin + + def each + @children.each { |child| + yield child + } + end + + def tree(indent = 0) + return ((@@indline * indent) + self.typewrap(self.pin)) + "\n" + + self.collect { |child| + child.tree(indent + 1) + }.join("\n") + end + end + + # and these ones don't + class Leaf < AST + attr_accessor :value, :type + + def tree(indent = 0) + return ((@@indent * indent) + self.typewrap(self.value)) + end + + def to_s + return @value + end + end + + class String < AST::Leaf + attr_accessor :value + end + + class Word < AST::Leaf + attr_accessor :value + end + + class ObjectDef < AST::Branch + attr_accessor :name, :object + attr_reader :params + + def []=(index,obj) + @params[index] = obj + end + + def [](index) + return @params[index] + end + + def each + #Blink.debug("each called on %s" % self) + [@object,@name,@params].flatten.each { |param| + #Blink.debug("yielding param %s" % param) + yield param + } + end + + def initialize(*args) + super(*args) + end + + def params=(params) + if params.is_a?(Array) + @params = params + else + @params = [params] + end + end + + def tree(indent = 0) + return [ + @object.tree(indent + 1), + @name.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @params.collect { |param| + begin + param.tree(indent + 1) + rescue NoMethodError => detail + puts "failed to tree" + puts @params + p param + raise + end + }.join("\n") + ].join("\n") + end + + def to_s + return "%s => { %s }" % [@name, + @params.collect { |param| + param.to_s + }.join("\n") + ] + end + end + + class ObjectParam < AST::Branch + attr_accessor :value, :param + + def each + [@param,@value].each { |child| yield child } + end + + def tree(indent = 0) + return [ + @param.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @value.tree(indent + 1) + ].join("\n") + end + + def to_s + return "%s => %s" % [@param,@value] + end + end + + class Selector < AST::Branch + attr_accessor :param, :value + + def tree(indent = 0) + return [ + @param.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @value.tree(indent + 1) + ].join("\n") + end + + def each + [@param,@value].each { |child| yield child } + end + end + + class VarDef < AST::Branch + attr_accessor :name, :value + + def each + [@name,@value].each { |child| yield child } + end + + def tree(indent = 0) + return [ + @name.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap(self.pin)), + @value.tree(indent + 1) + ].join("\n") + end + + def to_s + return "%s => %s" % [@name,@value] + end + end + + class FunctionCall < AST::Branch + attr_accessor :name, :values + + def each + [@name,@values].each { |child| yield child } + end + + def tree(indent = 0) + return [ + @name.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap(self.pin)), + @values.tree(indent + 1) + ].join("\n") + end + + def to_s + return "%s => %s" % [@name,@values] + end + end +end +..end grammar.ra modeval..id5273b1fd0f + +##### racc 1.4.4 generates ### + +racc_reduce_table = [ + 0, 0, :racc_error, + 1, 18, :_reduce_1, + 1, 19, :_reduce_none, + 2, 19, :_reduce_3, + 1, 20, :_reduce_none, + 1, 20, :_reduce_none, + 1, 20, :_reduce_none, + 1, 20, :_reduce_none, + 8, 21, :_reduce_8, + 3, 22, :_reduce_9, + 1, 26, :_reduce_10, + 3, 26, :_reduce_11, + 3, 28, :_reduce_12, + 1, 29, :_reduce_none, + 2, 29, :_reduce_14, + 1, 25, :_reduce_15, + 1, 25, :_reduce_none, + 1, 25, :_reduce_none, + 1, 25, :_reduce_none, + 3, 23, :_reduce_19, + 1, 30, :_reduce_none, + 3, 30, :_reduce_21, + 1, 31, :_reduce_none, + 2, 31, :_reduce_23, + 4, 24, :_reduce_24, + 3, 24, :_reduce_25, + 0, 27, :_reduce_none, + 1, 27, :_reduce_27 ] + +racc_reduce_n = 28 + +racc_shift_n = 46 + +racc_action_table = [ + 17, 17, 19, 19, 21, 30, 11, 38, 11, 17, + 21, 19, 22, 29, 35, 27, 9, 10, 12, 10, + 12, 17, 17, 19, 19, 21, 33, 3, 13, 3, + 39, 21, 43, 44, 21 ] + +racc_action_check = [ + 28, 12, 28, 12, 32, 21, 3, 32, 17, 11, + 10, 11, 10, 13, 28, 12, 3, 3, 3, 17, + 17, 30, 9, 30, 9, 22, 25, 6, 5, 0, + 33, 39, 40, 42, 43 ] + +racc_action_pointer = [ + 27, nil, nil, 3, nil, 28, 25, nil, nil, 20, + 6, 7, -1, 13, nil, nil, nil, 5, nil, nil, + nil, -4, 21, nil, nil, 21, nil, nil, -2, nil, + 19, nil, 0, 24, nil, nil, nil, nil, nil, 27, + 22, nil, 26, 30, nil, nil ] + +racc_action_default = [ + -28, -5, -6, -28, -7, -28, -1, -2, -4, -28, + -28, -28, -28, -28, -3, -16, -18, -28, -9, -15, + -17, -28, -28, -20, -19, -28, -13, -25, -28, 46, + -28, -22, -28, -28, -14, -24, -12, -23, -21, -28, + -26, -10, -28, -27, -8, -11 ] + +racc_goto_table = [ + 23, 8, 18, 7, 25, 26, 5, 8, 2, 14, + 4, 40, 31, 42, 2, 6, 4, 28, 24, 32, + nil, 34, 37, 36, nil, nil, nil, nil, nil, 41, + nil, nil, nil, 45 ] + +racc_goto_check = [ + 11, 4, 8, 3, 8, 8, 1, 4, 6, 3, + 7, 9, 11, 10, 6, 2, 7, 12, 13, 14, + nil, 8, 11, 8, nil, nil, nil, nil, nil, 11, + nil, nil, nil, 11 ] + +racc_goto_pointer = [ + nil, 6, 15, 3, 1, nil, 8, 10, -7, -28, + -27, -10, 5, 8, -3 ] + +racc_goto_default = [ + nil, nil, nil, nil, 20, 1, 15, 16, nil, nil, + nil, nil, nil, nil, nil ] + +racc_token_table = { + false => 0, + Object.new => 1, + :WORD => 2, + :LBRACK => 3, + :QTEXT => 4, + :RBRACK => 5, + :LBRACE => 6, + :RBRACE => 7, + :SYMBOL => 8, + :FARROW => 9, + :COMMA => 10, + :TRUE => 11, + :FALSE => 12, + :EQUALS => 13, + :QMARK => 14, + :LPAREN => 15, + :RPAREN => 16 } + +racc_use_result_var = true + +racc_nt_base = 17 + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ +'$end', +'error', +'WORD', +'LBRACK', +'QTEXT', +'RBRACK', +'LBRACE', +'RBRACE', +'SYMBOL', +'FARROW', +'COMMA', +'TRUE', +'FALSE', +'EQUALS', +'QMARK', +'LPAREN', +'RPAREN', +'$start', +'program', +'statements', +'statement', +'object', +'assignment', +'selector', +'functioncall', +'rvalue', +'params', +'endcomma', +'param', +'rvalues', +'svalues', +'sintvalues'] + +Racc_debug_parser = false + +##### racc system variables end ##### + + # reduce 0 omitted + +module_eval <<'.,.,', 'grammar.ra', 31 + def _reduce_1( val, _values, result ) + if val[0].is_a?(Array) + result = val[0] + else + result = AST::ASTArray.new([val[0]]) + end + # this is mainly so we can test the parser separately from the + # interpreter + if Blink[:parseonly] + begin + puts result.tree(0) + rescue NoMethodError => detail + puts detail + exit(78) + end + else + require 'blink/parser/interpreter' + result = Blink::Parser::Interpreter.new(result) + end + result + end +.,., + + # reduce 2 omitted + +module_eval <<'.,.,', 'grammar.ra', 41 + def _reduce_3( val, _values, result ) + if val[0].is_a?(Array) + val[0].push(val[1]) + result = val[0] + else + result = AST::ASTArray.new([val[0],val[1]]) + end + result + end +.,., + + # reduce 4 omitted + + # reduce 5 omitted + + # reduce 6 omitted + + # reduce 7 omitted + +module_eval <<'.,.,', 'grammar.ra', 60 + def _reduce_8( val, _values, result ) + leaf = AST::Word.new( + :line => @lexer.line, + :value => val[0] + ) + result = AST::ObjectDef.new( + :pin => "[]", + :line => @lexer.line, + :object => leaf, + :name => val[2], + :params => val[5] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 73 + def _reduce_9( val, _values, result ) + leaf = AST::Word.new( + :line => @lexer.line, + :value => val[0] + ) + result = AST::VarDef.new( + :pin => "=", + :line => @lexer.line, + :name => leaf, + :value => val[2] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 74 + def _reduce_10( val, _values, result ) + result = val[0] + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 83 + def _reduce_11( val, _values, result ) + if val[0].is_a?(Array) + val[0].push(val[2]) + result = val[0] + else + result = [val[0],val[2]] + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 96 + def _reduce_12( val, _values, result ) + leaf = AST::String.new( + :line => @lexer.line, + :value => val[0] + ) + result = AST::ObjectParam.new( + :pin => "=>", + :line => @lexer.line, + :param => leaf, + :value => val[2] + ) + result + end +.,., + + # reduce 13 omitted + +module_eval <<'.,.,', 'grammar.ra', 105 + def _reduce_14( val, _values, result ) + if val[0].is_a?(Array) + result = val[0].push(val[1]) + else + result = AST::Array.new(val[0],val[1]) + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 112 + def _reduce_15( val, _values, result ) + result = AST::String.new( + :line => @lexer.line, + :value => val[0] + ) + result + end +.,., + + # reduce 16 omitted + + # reduce 17 omitted + + # reduce 18 omitted + +module_eval <<'.,.,', 'grammar.ra', 128 + def _reduce_19( val, _values, result ) + leaf = AST::Word.new( + :line => @lexer.line, + :value => val[0] + ) + result = AST::Selector.new( + :pin => "?", + :line => @lexer.line, + :param => leaf, + :value => val[2] + ) + result + end +.,., + + # reduce 20 omitted + +module_eval <<'.,.,', 'grammar.ra', 131 + def _reduce_21( val, _values, result ) + result = val[1] + result + end +.,., + + # reduce 22 omitted + +module_eval <<'.,.,', 'grammar.ra', 142 + def _reduce_23( val, _values, result ) + if val[0].is_a?(Array) + val[0].push(val[1]) + result = val[0] + else + result = AST::ASTArray.new([val[0],val[1]]) + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 150 + def _reduce_24( val, _values, result ) + result = AST::FunctionCall.new( + :pin => '()', + :name => AST::Word.new(:value => val[0], :line => @lexer.line), + :values => val[2] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 156 + def _reduce_25( val, _values, result ) + result = FunctionDef.new( + :pin => '()', + :name => val[0] + ) + result + end +.,., + + # reduce 26 omitted + +module_eval <<'.,.,', 'grammar.ra', 158 + def _reduce_27( val, _values, result ) + result = nil + result + end +.,., + + def _reduce_none( val, _values, result ) + result + end + + end # class Parser + + end # module Parser + +end # module Blink diff --git a/lib/blink/selector.rb b/lib/blink/selector.rb new file mode 100644 index 000000000..51e82b09d --- /dev/null +++ b/lib/blink/selector.rb @@ -0,0 +1,82 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +require 'blink' +require 'blink/fact' + +module Blink + #--------------------------------------------------------------- + # this class will provide something like a 'select' statement, but it will + # return a value + # it will be used something like this: + # value = Selector.new( + # proc { test() } => value, + # proc { test2() } => value2, + # ) + + # each test gets evaluated in turn; the first one to return true has its + # value returned as the value of the statement + # this will be used to provide abstraction in objects, but it's currently + # unused + + class Selector < Array + attr_accessor :default + + def add(value,&block) + option = Option.new(value,&block) + @ohash[value] = option + @oarray.push(option) + end + + def evaluate + @oarray.each { |option| + if option.true? + return option.value + end + } + return nil + end + + # we have to support providing different values based on + # different criteria, e.g., default is X, SunOS gets Y, and + # host Yayness gets Z. + # thus, no invariant + def initialize + @oarray = [] + @ohash = {} + + if block_given? + yield self + end + end + + def to_s + return self.evaluate() + end + + class Option + attr_accessor :value, :test, :invariant + + def initialize(value,&block) + @value = value + @test = block + end + + def to_s + if self.evaluate + return value + end + end + + def true? + unless @test.is_a?(Proc) + raise "Cannot yet evaluate non-code tests" + end + + return @test.call() + end + end + #--------------------------------------------------------------- + end +end |
