lib/pione/pnml/pione-model.rb
module Pione
module PNML
# `Perspective` is a meta class for PIONE's concepts overlayed in PNML.
class Perspective
TRANSFORMER_OPT = {package_name: "", editor: "", tag: "", filename: ""}
class << self
# Return true if the node is empty.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is empty
def empty?(env, node)
empty_place?(env, node) or empty_transition?(env, node)
end
# Return true if the node is an empty place.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is an empty place
def empty_place?(env, node)
match_place_parser?(env, node, :empty_place)
end
# Return true if the node is an empty transition.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is an empty transition
def empty_transition?(env, node)
match_transition_parser?(env, node, :empty_transition)
end
# Return true if the node is an expression place.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is an exression place
def expr_place?(env, node)
match_place_parser?(env, node, :expr_place)
end
# Return true if the node is a data place.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is a data place
def data_place?(env, node)
match_place_parser_with_type?(env, node, :data_place, :expr, Lang::TypeDataExpr)
end
# Return true if the node is a net input data place.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is a net input data place
def net_input_data_place?(env, node)
if data_place?(env, node)
return net_input_data_symbol?(data_modifier(env, node))
else
return false
end
end
# Return true if the node is a net output data place.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is a net output data place
def net_output_data_place?(env, node)
if data_place?(env, node)
return net_output_data_symbol?(data_modifier(env, node))
else
return false
end
end
# Return true if the node is a parameter.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is a parameter
def param_place?(env, node)
match_place_parser_with_type?(env, node, :expr_place, :expr, Lang::TypeParameterSet)
end
# Return true if the node is a parameter sentence transition.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @return [Boolean]
# true if the node is a parameter sentence transition
def param_transition?(env, node)
match_transition_parser?(env, node, :param_sentence)
end
# Evaluate the node as a parameter sentence.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @return [Object]
# evaluated result
def eval_param_sentence(env, node)
eval_transition(env, node, :param_sentence, :param_sentence)
end
# Return true if the node is a ticket place.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is a ticket place
def ticket_place?(env, node)
match_place_parser_with_type?(env, node, :expr_place, :expr, Lang::TypeTicketExpr)
end
# Return true if the node is a feature place.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is a feature place
def feature_place?(env, node)
match_place_parser_with_type?(env, node, :expr_place, :expr, Lang::TypeFeature)
end
# Return true if the node is a feature transition.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is a feature transition
def feature_transition?(env, node)
match_place_parser?(env, node, :feature_sentence)
end
# Return true if the node is a variable binding transition.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is a variable binding transition
def variable_binding_transition?(env, node)
match_transition_parser?(env, node, :variable_binding_sentence)
end
# Return true if the node is a rule.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [Boolean]
# true if the node is a rule.
def rule_transition?(env, node)
match_transition_parser?(env, node, :rule_transition)
end
# Return ture if the node is an internal rule.
#
# @param env [Lang::Environment]
# language environment
# @param [PNML::Node] node
# PNML node
# @return [Boolean]
# true if the node is an internal rule
def internal_rule_transition?(env, node)
match_transition_parser?(env, node, :internal_rule_transition)
end
# Return ture if the node is an external rule.
#
# @param env [Lang::Environment]
# language environment
# @param [PNML::Node] node
# PNML node
# @return [Boolean]
# true if the node is an external rule
def external_rule_transition?(env, node)
match_transition_parser?(env, node, :external_rule_transition)
end
# Return the modifier of node name.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# the node
# @return [String]
# modifier or nil
def data_modifier(env, node)
if node.kind_of?(Place) and not(node.name.nil?)
begin
parsed = Parser.new.data_place.parse(node.name)
if parsed.kind_of?(Hash)
return parsed[:modifier].to_s
end
rescue Parslet::ParseFailed
begin
parsed = Parser.new.empty_place.parse(node.name)
if parsed.kind_of?(Hash)
return parsed[:modifier].to_s
end
rescue Parslet::ParseFailed
end
end
end
return nil
end
# Return true if the node is a transition with keyword.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @return [Boolean]
# true if the node is a transition with keyword
def keyword_transition?(env, node)
match_transition_parser?(env, node, :keyword_transition)
end
# Return true if the node is a transition with keyword "if".
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @return [Boolean]
# true if the node is a transition with keyword "if"
def if_transition?(env, node)
match_transition_parser?(env, node, :if_transition)
end
# Return true if the node is a transition with keyword "then".
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @return [Boolean]
# true if the node is a transition with keyword "then"
def then_transition?(env, node)
match_transition_parser?(env, node, :then_transition)
end
# Return true if the node is a transition with keyword "else".
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @return [Boolean]
# true if the node is a transition with keyword "else"
def else_transition?(env, node)
match_transition_parser?(env, node, :else_transition)
end
# Return true if the node is a transition with keyword "case".
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @return [Boolean]
# true if the node is a transition with keyword "case"
def case_transition?(env, node)
match_transition_parser?(env, node, :case_transition)
end
# Return true if the node is a transition with keyword "when".
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @return [Boolean]
# true if the node is a transition with keyword "when"
def when_transition?(env, node)
match_transition_parser?(env, node, :when_transition)
end
# Return true if the node is a transition with keyword "constraint".
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @return [Boolean]
# true if the node is a transition with keyword "constraint"
def constraint_transition?(env, node)
match_transition_parser?(env, node, :constraint_transition)
end
private
# Return true if the string is net input data symbol.
#
# @param [String]
# string
# @return [Boolean]
# true if the string is net input data symbol
def net_input_data_symbol?(str)
return false if str.nil?
Parser.new.net_input_symbol.parse(str)
return true
rescue Parslet::ParseFailed
return false
end
# Return true if the string is net output data symbol.
#
# @param [String]
# string
# @return [Boolean]
# true if the string is net output data symbol
def net_output_data_symbol?(str)
return false if str.nil?
Parser.new.net_output_symbol.parse(str)
return true
rescue Parslet::ParseFailed
return false
end
# Return true if the node matches the place parser.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @param parser_name [String]
# place parser name
# @return [Boolean]
# true if the node is the place parser
def parse_place(env, node, parser_name)
if node.kind_of?(Place) and not(node.name.nil?)
begin
return Parser.new.send(parser_name).parse(node.name)
rescue Parslet::ParseFailed
end
end
end
# Return true if the node matches the place parser.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @param parser_name [String]
# place parser name
# @return [Boolean]
# true if the node is the place parser
def match_place_parser?(env, node, parser_name)
parsed = parse_place(env, node, parser_name)
if not(parsed.nil?)
if block_given?
return yield parsed
else
return true
end
else
return false
end
end
# Return true if the node matches the place parser and expected type.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @param parser_name [String]
# place parser name
# @param target_name [Symbol]
# target name that has expected type
# @param expected_type [Pione::Lang::Type]
# expected PIONE type
# @return [Boolean]
# true if the node is the place parser
def match_place_parser_with_type?(env, node, parser_name, target_name, expected_type)
parsed = parse_place(env, node, parser_name)
if parsed and parsed[target_name]
expr = Lang::DocumentTransformer.new.apply(parsed[target_name], TRANSFORMER_OPT)
return expr.pione_type(env) == expected_type
else
return false
end
end
# Return true if the node matches the transition parser.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @param parser_name [Symbol]
# place parser name
# @return [Boolean]
# true if the node is the transition parser
def parse_transition(env, node, parser_name)
if node.kind_of?(Transition) and not(node.name.nil?)
begin
return Parser.new.send(parser_name).parse(node.name)
rescue Parslet::ParseFailed
end
end
end
# Return true if the node matches the transition parser.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @param parser_name [Symbol]
# transition parser name
# @return [Boolean]
# true if the node is the keyword
def match_transition_parser?(env, node, parser_name)
not(parse_transition(env, node, parser_name).nil?)
end
# Evaluate the transition and return the result.
#
# @param env [Lang::Environment]
# language environment
# @param node [PNML::Node]
# PNML's node
# @param parser_name [Symbol]
# place parser name
# @param target_name [Symbol]
# target name that has expected type
# @param expected_type [Pione::Lang::Type]
# expected PIONE type
# @return [Boolean]
# true if the node is the place parser
def eval_transition(env, node, parser_name, target_name)
parsed = parse_transition(env, node, parser_name)
if parsed and parsed[target_name]
return Lang::DocumentTransformer.new.apply(parsed[target_name], TRANSFORMER_OPT)
else
return nil
end
end
end
end
class PioneModel
# Return an indented version of the string. Indentation size is calculated
# by the optional argument `:level`. If the level is zero, return the
# string as is.
#
# @param option [Hash]
# @option option [Integer] :level
# indentation level, this should be non-negative integer
def indent(str, option)
str.lines.map do |line|
(" " * (option[:level] || 0)) + line
end.join
end
end
# ConstituentRule is a class represents PIONE's constituent rule.
class ConstituentRule < PioneModel
attr_reader :type
attr_reader :name
attr_reader :params
# @param type [Symbol]
# rule type of either `:input` or `:output`
# @param name [String]
# rule name
def initialize(type, name)
@type = type
@name = name
@params = []
end
# Return a declaration of constituent rule.
#
# @return [String]
# a declaration string for PIONE's constituent rule
def as_declaration(option={})
indent("rule %s" % textize_rule_expr, option)
end
private
# Return a string of rule expression.
#
# @return [String]
# a string of rule expression
def textize_rule_expr
[@name, textize_params].compact.join(" ")
end
# Return a string of parameter set.
#
# @return [String]
# a string of parameter set
def textize_params
unless @params.empty?
@params.inject(Param.new){|res, param| res + param}.as_expr
end
end
end
# `DataCondition` is a class represents PIONE's input and output data condition.
class Data < PioneModel
attr_reader :data_expr
attr_accessor :input_distribution
attr_accessor :output_distribution
attr_accessor :priority
attr_accessor :input_nonexistable
attr_accessor :output_nonexistable
attr_accessor :output_for_this_flow
# @param nod [PNML::Node]
# data expression as a PIONE's expression string
def initialize(node)
@name = LabelExtractor.extract_data_expr(node.name)
@priority = LabelExtractor.extract_priority(node.name)
end
private
def textize_data_expr(type)
data_expr = "%s" % @name
if (type == :input and @input_nonexistable) or (type == :output and @output_nonexistable)
data_expr = data_expr + " or null"
end
if type == :input and @input_distribution
data_expr = "(%s).%s" % [data_expr, @input_distribution]
end
if type == :output and @output_distribution
data_expr = "(%s).%s" % [data_expr, @output_distribution]
end
return data_expr
end
end
class InputData < Data
# Return a declaration string of the data expression as input condition.
def as_declaration(option={})
indent("input %s" % textize_data_expr(:input), option)
end
end
class OutputData < Data
# Return a declaration string of the data expression as output condition.
def as_declaration(option={})
indent("output %s" % textize_data_expr(:output), option)
end
end
# Param is a class represents PIONE's paramter set.
class Param < PioneModel
# Create a parameter from the parameter set node.
#
# @param node [PNML::Node]
# parameter set node
# @return [Param]
# parameter
def self.set_of(node)
new(LabelExtractor.extract_data_from_param_set(node.name))
end
# Create a parameter from the parameter sentence node.
#
# @param node [PNML::Node]
# parameter sentence node
# @return [Param]
# parameter
def self.sentence_of(node)
new(LabelExtractor.extract_data_from_param_sentence(node.name))
end
attr_reader :data
# @param data [Hash]
# param set data
def initialize(data={})
@data = data
end
def as_expr
@data.map do |var, expr|
"%s: %s" % [var, expr]
end.join(", ").tap {|x| return "{%s}" % x}
end
def as_declarations(option={})
@data.map do |var, expr|
indent("param $%s := %s" % [var, expr], option)
end
end
def +(other)
self.class.new(@data.merge(other.data))
end
end
# Constraint represents a PIONE's constraint declaration.
class Constraint < PioneModel
attr_reader :expr
def initialize(expr)
@expr = expr
end
def as_declaration(option={})
indent("constraint " + @expr, option)
end
end
# Ticket represents a PIONE's ticket declaration.
class Ticket < PioneModel
attr_reader :name
def initialize(name)
@name = name
end
end
# Feature represents a feature declaration in PIONE.
class Feature < PioneModel
attr_reader :expr
def initialize(expr)
@expr = expr
end
def as_declaration(option={})
indent("feature " + @expr, option)
end
end
# ConditionalBranch is a class represents PIONE's conditional branch
# declaration.
class ConditionalBranch < PioneModel
attr_reader :condition
attr_reader :table
def initialize(type, condition)
@type = type
@condition = condition
@table = Hash.new {|h,k| h[k] = []}
end
def as_declaration(option={})
case @type
when :"if"
branch_then = @table[:then].map do |rule|
rule.as_declaration(option.merge(level: option[:level] + 1))
end.join("\n")
if @table[:else].empty?
indent(Util::Indentation.cut(TEMPLATE_IF) % [@condition, branch_then], option)
else
branch_else = @table[:else].map do |rule|
rule.as_declaration(option.merge(level: option[:level] + 1))
end.join("\n")
indent(Util::Indentation.cut(TEMPLATE_IF_ELSE) % [@condition, branch_then, branch_else], option)
end
when :"case"
branches = @table.each_with_object([]) do |(val, rules), lines|
lines << ((val == :else) ? "else" : "when %s" % val)
level = (option[:level] || 0) + 1
lines.concat(rules.map{|rule| rule.as_declaration(option.merge(level: level))})
end.join("\n")
indent(Util::Indentation.cut(TEMPLATE_CASE) % [@condition, branches], option)
end
end
TEMPLATE_IF = <<-TXT
if %s
%s
end
TXT
TEMPLATE_IF_ELSE = <<-TXT
if %s
%s
else
%s
end
TXT
TEMPLATE_CASE = <<-TXT
case %s
%s
end
TXT
end
class RuleDefinition < PioneModel
attr_accessor :type
attr_accessor :inputs
attr_accessor :outputs
attr_accessor :params
attr_accessor :constraints
attr_accessor :features
attr_accessor :source_tickets
attr_accessor :target_tickets
attr_accessor :conditions
attr_accessor :variable_bindings
attr_accessor :flow_elements
attr_accessor :action_content
def initialize(name, type, is_external, net_name, index, option={})
@name = name
@type = type
@is_external = is_external
@net_name = net_name
@index = index
@inputs = option[:inputs] || []
@outputs = option[:outputs] || []
@params = option[:params] || []
@constraints = option[:constraints] || []
@features = option[:features] || []
@source_tickets = option[:source_tickets] || []
@target_tickets = option[:target_tickets] || []
@variable_bindings = option[:variable_bindings] || []
@conditions = option[:conditions] || []
@flow_elements = option[:flow_elements] || []
@action_content = nil
end
def flow?
@type == :flow
end
def action?
@type == :action
end
def external?
@is_external
end
def name
external? ? generate_wrapper_name(@name) : @name
end
# Return the declaration form string.
def as_declaration(option={})
expr_source_tickets =
if @source_tickets.size > 0
"(%s) ==> " % @source_tickets.map {|ticket| "%s" % ticket.name}.join(" | ")
else
""
end
expr_target_tickets =
if @target_tickets.size > 0
" ==> (%s)" % @target_tickets.map {|ticket| "%s" % ticket.name}.join(" | ")
else
""
end
"rule %s%s%s" % [expr_source_tickets, name, expr_target_tickets]
end
# Make rule conditions.
#
# @return [Array<String>]
# rule condition lines
def rule_conditions
conditions = []
sort_data_list(@inputs).each do |input|
conditions << input.as_declaration
end
sort_data_list(@outputs).each do |output|
conditions << output.as_declaration
end
@params.each do |param|
if param.kind_of?(Param)
conditions += param.as_declarations
else
conditions << param
end
end
@constraints.each do |constraint|
if constraint.kind_of?(Constraint)
conditions << constraint.as_declaration
else
conditions << constraint
end
end
@features.each do |feature|
if feature.kind_of?(Feature)
conditions << feature.as_declaration
else
conditions << feature
end
end
conditions
end
def sort_data_list(data_list)
data_list.sort do |a, b|
priority_a = a.priority
priority_b = b.priority
if a.priority and b.priority
a.priority <=> b.priority
elsif a.priority
1
elsif b.priority
-1
else
0
end
end
end
def textize
ERB.new(template, nil, "-").result(binding)
end
def template
if external?
return Util::Indentation.cut(WRAPPER_TEMPLATE)
end
if flow?
return Util::Indentation.cut(FLOW_RULE_TEMPLATE)
end
if @action_content
return Util::Indentation.cut(LITERATE_ACTION_RULE_TEMPLATE)
else
return Util::Indentation.cut(ACTION_RULE_TEMPLATE)
end
end
# Generate a name for wrapper rule.
def generate_wrapper_name(name)
"__%s_%s_%s__" % [@net_name, @name, @index]
end
FLOW_RULE_TEMPLATE = <<-RULE
Rule <%= name %>
<%- rule_conditions.each do |condition| -%>
<%= condition %>
<%- end -%>
Flow
<%- @flow_elements.each do |element| -%>
<%= element.as_declaration(level: 1) %>
<%- end -%>
End
RULE
ACTION_RULE_TEMPLATE = <<-RULE
Rule <%= name %>
<%- rule_conditions.each do |condition| -%>
<%= condition %>
<%- end -%>
End
RULE
LITERATE_ACTION_RULE_TEMPLATE = <<-RULE
Rule <%= name %>
<%- rule_conditions.each do |condition| -%>
<%= condition %>
<%- end -%>
Action
<%= Util::Indentation.indent(@action_content, 2) -%>
End
RULE
WRAPPER_TEMPLATE = <<-RULE
Rule <%= name %>
<%- rule_conditions.each do |condition| -%>
<%= condition %>
<%- end -%>
Flow
rule <%= @name %>
End
RULE
end
end
end