# encoding: utf-8 # YNelson's user inteface. As the name suggests, represents an agent that # manipulates the YNelson world. In his introduction to zz structures, # Ted Nelson has remarks regarding the desired UI, such as: # # Selection is a pointer to a collection of cells. # View consists of selection and a coordinate system. # Field is a connected (contiguous) selection. # Coordinate system is a collection of oriented dimensions. # # Apart from these author's suggestions, the interface should offer similar # functionality as YPetri::Agent – that is, should allow constructing a Petri # net with the same commands as YPetri does. # class YNelson::Agent attr_reader :world ★ YPetri::Agent::PetriNetAspect ★ YPetri::Agent::SimulationAspect # Future module YNelson::Agent::SimulationRelated # Calls #finalize before invoking YPetri::Agent#new_simulation. # def new_simulation *args; finalize; super end # Initialization of a YNelson::Agent instance. For YNelson manipulators, the # world is always YNelson itself. # def initialize @world = ::YNelson super # A hash of sheets. (For the moment being, YNelson acts like a spreadsheet.) @sheets = {} # Default dimension of this manipulator. @default_dimension = YNelson.Dimension( :row ) # Zz object pointers of this manipulator. @primary_point = YNelson::ZzPoint.new @secondary_point = YNelson::ZzPoint.new # Zz dimension pointers of this manipulator. @primary_dimension_point = YNelson::DimensionPoint.new YNelson.Dimension( :row ) @secondary_dimension_point = YNelson::DimensionPoint.new YNelson.Dimension( :column ) @todo = [] # array of blocks end # Now the part related to the zz structure itself, like in # module YNelson::Manipulator::ZzRelatedMethods # blah blah # end # include YNelson::Manipulator::ZzRelatedMethods attr_reader :sheets # Dimension convenience constructor from delegate :Dimension, to: :world # Now let's look into the graph visualization. def default_dimension @default_dimension end def primary_point @primary_point end alias p1 primary_point def secondary_point @secondary_point end alias p2 secondary_point def primary_dimension_point @primary_dimension_point end alias d1 primary_dimension_point def secondary_dimension_point @secondary_dimension_point end alias d2 secondary_dimension_point # Define function to display it with kioclient. # def visualize *args, &block graphviz *args, &block end # Define graphviz places. # def graphviz dim1=primary_dimension_point, dim2=secondary_dimension_point γ = GraphViz.new :G, type: :digraph # Create a new graph # main = γ.add_nodes( "main", shape: "box" ) # parse = γ.add_nodes( "parse", fillcolor: "yellow", style: "rounded,filled", shape: "diamond" ) # execute = γ.add_nodes( "execute", shape: "record", label: "{ a | b | c }", style: "rounded" ) # init = γ.add_nodes( "init", fillcolor: "yellow", style: "filled" ) # set global node options γ.node[:color] = "#ddaa66" γ.node[:style] = "filled" γ.node[:shape] = "box" γ.node[:penwidth] = "1" γ.node[:fontname] = "Trebuchet MS" γ.node[:fontsize] = "8" γ.node[:fillcolor] = "#ffeecc" γ.node[:fontcolor] = "#775500" γ.node[:margin] = "0.0" # set global edge options γ.edge[:color] = "#999999" γ.edge[:weight] = "1" γ.edge[:fontsize] = "6" γ.edge[:fontcolor] = "#444444" γ.edge[:fontname] = "Verdana" γ.edge[:dir] = "forward" γ.edge[:arrowsize] = "0.5" # add zz objects place_nodes = places.map.with_object Hash.new do |place, ꜧ| ꜧ[place] = γ.add_nodes place.name.to_s end transition_nodes = transitions.map.with_object Hash.new do |transition, ꜧ| ꜧ[transition] = γ.add_nodes transition.name.to_s end nodes = place_nodes.merge transition_nodes # add edges for selected dimensions nodes.each { |instance, node| negward_neighbor = instance.( dim1 ).negward.neighbor nodes[ negward_neighbor ] << node if negward_neighbor # color 1 negward_neighbor = instance.( dim2 ).negward.neighbor nodes[ negward_neighbor ] << node if negward_neighbor # color 2 } γ.output png: "zz.png" # Generate output image YSupport::KDE.show_file_with_kioclient File.expand_path( '.', "zz.png" ) # main = γ.add_nodes( "main", shape: "box" ) # parse = γ.add_nodes( "parse", fillcolor: "yellow", style: "rounded,filled", shape: "diamond" ) # execute = γ.add_nodes( "execute", shape: "record", label: "{ a | b | c }", style: "rounded" ) # init = γ.add_nodes( "init", fillcolor: "yellow", style: "filled" ) end # graphviz [:domain, 0 ], [:codomain, 0] # Creation of a place governed by a single assignment transition. In other # words, creation of a place with a unary-output formula known well from # spreadsheets. The transition is named automatically by adding "_ϝ" # (digamma, resembles mathematical f used to denote functions) suffix to # the place's name, as soon as the place is named. For example, # # Fred = PAT Joe do |joe| joe * 2 end # # creates a place named "Fred" and an assignment transition named "Fred_ϝ" # that keeps Fred equal to 2 times Joe. # def PAT *domain, **named_args, &block Place().tap do |p| # place can be instantiated right away p.name = named_args.delete :name if named_args.has? :name, syn!: :ɴ @todo << -> { t = AT p, domain: domain, &block if p.name then t.name = "#{p.name}_ϝ" else # Rig the hook to name the transition as soon as the place is named. p.name_set_hook do |name| transition.name = "#{name}_ϝ" end end # Monkey-patch the place with default marking closure. p.define_singleton_method :default_marking do if has_default_marking? then local_variable_get :@default_marking else t.action_closure.( *t.domain.map( &:default_marking ) ) end end } end end alias ϝ PAT # Executes all @todo closures. # def finalize while not @todo.empty? @todo[-1].call # May raise errors in which case we don't want to pop. @todo.pop # That's why not @todo.pop.call while not @todo.empty? end end # ============================================================================ # === THE METHODS BELOW ARE NOT REVIEWED FOR THE NEW VERSION AND NOT === # === EXPOSED IN THE DSL YET === # ============================================================================ # Cell side referencers with r. to primary and secondary point def ξ_posward_side dim=nil; ::YTed::POINT.posward_side dim end alias :ξp :ξ_posward_side def ξ_negward_side dim=nil; ::YTed::POINT.negward_side dim end alias :ξn :ξ_negward_side def ξ2_posward_side dim=nil; ::YTed::POINT2.posward_side dim end alias :ξ2p :ξ2_posward_side def ξ2_negward_side dim=nil; ::YTed::POINT2.negward_side dim end alias :ξ2n :ξ2_negward_side # Point walkers def ξ_step_posward dim=nil; ::YTed::POINT.step_posward dim end alias :ξp! :ξ_step_posward def ξ_step_negward dim=nil; ::YTed::POINT.step_negward dim end alias :ξn! :ξ_step_negward def ξ2_step_posward dim=nil; ::YTed::POINT2.step_posward dim end alias :ξ2p! :ξ2_step_posward def ξ2_step_negward dim=nil; ::YTed::POINT2.step_negward dim end alias :ξ2n! :ξ2_step_negward # Point rank rewinders # (rewinders without exclamation mark expect block or return enumerator def ξ_rewind_posward *aa, &b; ::YTed::POINT.rewind_posward *aa, &b end alias :ξrp :ξ_rewind_posward def ξ_rewind_negward *aa, &b; ::YTed::POINT.rewind_negward *aa, &b end alias :ξpp :ξ_rewind_posward def ξ2_rewind_posward *aa, &b; ::YTed::POINT.rewind_posward *aa, &b end alias :ξ2rp :ξ2_rewind_posward def ξ2_rewind_negward *aa, &b; ::YTed::POINT.rewind_negward *aa, &b end alias :ξ2pp :ξ2_rewind_posward # Point rak rewinters with exclamation mark (no block or enum, just do it) def ξ_rewind_posward! dim=nil; ::YTed::POINT.rewind_posward! dim end alias :ξrn! :ξ_rewind_posward! alias :ξnn! :ξ_rewind_posward! def ξ_rewind_negward! dim=nil; ::YTed::POINT.rewind_negward! dim end alias :ξrn! :ξ_rewind_negward! alias :ξnn! :ξ_rewind_negward! def ξ2_rewind_posward! dim=nil; ::YTed::POINT2.rewind_posward! dim end alias :ξ2rp! :ξ2_rewind_posward! alias :ξ2pp! :ξ2_rewind_posward! def ξ2_rewind_negward! dim=nil; ::YTed::POINT2.rewind_negward! dim end alias :ξ2rn! :ξ2_rewind_negward! alias :ξ2nn! :ξ2_rewind_negward! # Neighbor referencers def ξ_posward_neighbor dim=nil; ::YTed::POINT.posward_neighbor dim end alias :ξP :ξ_posward_neighbor def ξ_negward_neighbor dim=nil; ::YTed::POINT.negward_neighbor dim end alias :ξN :ξ_negward_neighbor def ξ2_posward_neighbor dim=nil; ::YTed::POINT2.posward_neighbor dim end alias :ξ2P :ξ2_posward_neighbor def ξ2_negward_neighbor dim=nil; ::YTed::POINT2.negward_neighbor dim end alias :ξ2N :ξ2_negward_neighbor # Neighbor redefinition def ξ_redefine_posward_neighbor *args ::YTed::POINT.redefine_posward_neighbor *args end alias :ξP! :ξ_redefine_posward_neighbor def ξ_redefine_negward_neighbor *args ::YTed::POINT.redefine_negward_neighbor *args end alias :ξN! :ξ_redefine_negward_neighbor def ξ2_redefine_posward_neighbor *args ::YTed::POINT2.redefine_posward_neighbor *args end alias :ξ2P! :ξ2_redefine_posward_neighbor def ξ2_redefine_negward_neighbor *args ::YTed::POINT2.redefine_negward_neighbor *args end alias :ξ2N! :ξ2_redefine_negward_neighbor # FIXME these methods do not work well # #rank method creates a rank of connected cells along the given # dimension (named arg :dimension, alias :dΞ ), from values given as array # (named arg :array, alias :ᴀ). The rank is returned as a cell array. def zz_rank( array, dimension ) array.map{ |e| ZzCell( e ) }.each_consecutive_pair{ |a, b| a.redefine_neighbors( dimension: dimension, posward: b ) } end # Zips an array of ZzCells (receiver) with another array of ZzCells # in a given dimension. That is, each cell in the first array is made # to point (have posward neighbor) to the corresponding cell in the # second array. def zz_zip( cell_array, dimension ) cell_array.a℈_all_∈( ::YTed::ZzCell ) self.a℈_all_∈( ::YTed::ZzCell ).each.with_index{|cell, i| cell.redefine_neighbors( dimension: dimension, posward: cell_array[i] ) } end # All we need right now is use Zz as a spreadsheet, writing data in rows. # FIXME: Zz properites not used at all - classical Ruby data structures # are used instead. ξ is just a holder for sheet name # Creates a new row (rank along :row dimension) zipped to current row # along ξ.dim, using the supplied arguments as new row's cell values. def new_zz_row *args args = args[0].first if args.size == 1 and args[0].is_a? Hash previous_row = ( ::YTed::SHEETS[@current_sheet_name][-1] || [] ).dup row = args.map{ |a| ZzCell( a ) } row.each_consecutive_pair{ |a, b| a.P! x: b } # connect along :x row.each{ |e| e.N! y: previous_row.shift } # zip to previous row ::YTed::SHEETS[@current_sheet_name] << row if row[0].value.is_a? Symbol then ::YTed::SHEETS[@current_sheet_name] .define_singleton_method args[0] { row[1].aE_kind_of ::YPetri::Place } end end alias :→ :new_zz_row # #new_zz_sheet creates a new sheet with a single cell holding the sheet's # name and sets main point to it in :col dimenion sheet name as its value def new_zz_sheet( name ) @current_sheet_name = name ::YTed::SHEETS[name] = [] ::YTed::SHEETS .define_singleton_method name.to_sym do ::YTed::SHEETS[name] end return ::YTed::SHEETS[name] end end