ceml 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +4 -0
- data/Makefile +5 -0
- data/README.markdown +86 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/editors/CEML.tmbundle/Syntaxes/ceml.tmLanguage +322 -0
- data/editors/CEML.tmbundle/info.plist +17 -0
- data/examples/breakfast-exchange.ceml +15 -0
- data/examples/citizen-investigation.ceml +24 -0
- data/examples/high-fives.ceml +19 -0
- data/lib/ceml/casting.rb +52 -0
- data/lib/ceml/engine.rb +138 -0
- data/lib/ceml/instructions.rb +31 -0
- data/lib/ceml/script.rb +103 -0
- data/lib/ceml/tt/casting.rb +618 -0
- data/lib/ceml/tt/casting.treetop +50 -0
- data/lib/ceml/tt/instructions.rb +320 -0
- data/lib/ceml/tt/instructions.treetop +42 -0
- data/lib/ceml/tt/lexer.rb +400 -0
- data/lib/ceml/tt/lexer.treetop +34 -0
- data/lib/ceml/tt/scripts.rb +345 -0
- data/lib/ceml/tt/scripts.treetop +32 -0
- data/lib/ceml.rb +35 -0
- data/test/helper.rb +55 -0
- data/test/test_casting.rb +78 -0
- data/test/test_engine.rb +30 -0
- data/test/test_instructions.rb +27 -0
- data/test/test_scripts.rb +57 -0
- metadata +107 -0
data/lib/ceml/engine.rb
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
module CEML
|
|
4
|
+
class Dummy
|
|
5
|
+
def method_missing(meth, *args, &blk)
|
|
6
|
+
# puts "#{meth}: #{args.to_s.inspect}"
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Engine
|
|
11
|
+
attr_reader :script, :parts
|
|
12
|
+
def this; @parts[@current_id]; end
|
|
13
|
+
def roles; this[:roles] ||= Set.new; end
|
|
14
|
+
def got; this[:received]; end
|
|
15
|
+
def recognized; this[:recognized]; end
|
|
16
|
+
def pc; this[:pc] ||= 0; end
|
|
17
|
+
|
|
18
|
+
def initialize(script_text, delg = Dummy.new)
|
|
19
|
+
@script = CEML.parse(:script, script_text)
|
|
20
|
+
@delg = delg
|
|
21
|
+
@parts = {}
|
|
22
|
+
@seq = {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def add(id, *roles)
|
|
26
|
+
obj = Hash === roles[-1] ? roles.pop : {}
|
|
27
|
+
parts[id] = obj.merge :roles => Set.new(roles)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def run
|
|
31
|
+
:loop while parts.keys.any? do |@current_id|
|
|
32
|
+
# puts "trying: #{@current_id}: #{seq[pc]}"
|
|
33
|
+
next unless seq[pc] and send(*seq[pc])
|
|
34
|
+
@delg.send(*seq[pc] + [@current_id])
|
|
35
|
+
this[:pc]+=1
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def seq
|
|
40
|
+
@seq[roles] ||= begin
|
|
41
|
+
bytecode = [[:start]]
|
|
42
|
+
instrs = script.instructions_for(roles)
|
|
43
|
+
instrs.each do |inst|
|
|
44
|
+
case inst.cmd
|
|
45
|
+
when :ask
|
|
46
|
+
bytecode << [:ask_q, inst]
|
|
47
|
+
bytecode << [:answered_q, inst]
|
|
48
|
+
when :tell
|
|
49
|
+
if script.title
|
|
50
|
+
bytecode << [:assign, inst]
|
|
51
|
+
bytecode << [:complete_assign, inst]
|
|
52
|
+
else
|
|
53
|
+
bytecode << [:send_msg, inst]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
if instrs.empty? and script.title
|
|
58
|
+
bytecode << [:null_assign]
|
|
59
|
+
bytecode << [:complete_assign]
|
|
60
|
+
end
|
|
61
|
+
bytecode << [:finish]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def say x, params = {}
|
|
66
|
+
this[:said] = x
|
|
67
|
+
this.merge! params
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def qs_answers
|
|
71
|
+
this[:qs_answers] ||= Hash.new
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def expand(role, var)
|
|
75
|
+
role = nil if role == 'otherguy'
|
|
76
|
+
role = role.to_sym if role
|
|
77
|
+
parts.each do |key, thing|
|
|
78
|
+
next if key == @current_id
|
|
79
|
+
next if role and not thing[:roles].include? role
|
|
80
|
+
value = (thing[:qs_answers]||{})[var] and return value
|
|
81
|
+
end
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# ==============
|
|
86
|
+
# = basic flow =
|
|
87
|
+
# ==============
|
|
88
|
+
|
|
89
|
+
def start
|
|
90
|
+
roles.include? :agent or return false
|
|
91
|
+
true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def ask_q q
|
|
95
|
+
text = q.interpolate(self) or return false
|
|
96
|
+
say :ask, :q => text
|
|
97
|
+
true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def answered_q q
|
|
101
|
+
got or return false
|
|
102
|
+
qs_answers[q.key] = got
|
|
103
|
+
true
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def send_msg a
|
|
107
|
+
text = a.interpolate(self) or return false
|
|
108
|
+
say :message, :msg => text
|
|
109
|
+
true
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def assign a
|
|
113
|
+
text = a.interpolate(self) or return false
|
|
114
|
+
say :assignment, :msg => text
|
|
115
|
+
true
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def complete_assign a = nil
|
|
119
|
+
got or return false
|
|
120
|
+
if recognized == :done
|
|
121
|
+
say :ok
|
|
122
|
+
true
|
|
123
|
+
else
|
|
124
|
+
@delg.send :did_report
|
|
125
|
+
false
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def null_assign
|
|
130
|
+
say :proceed
|
|
131
|
+
true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def finish
|
|
135
|
+
true
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module CEML
|
|
2
|
+
module Instructions
|
|
3
|
+
def validate!(allowed_roles)
|
|
4
|
+
extra_roles = roles - allowed_roles
|
|
5
|
+
raise "unrecognized rolenames: #{extra_roles.inspect}" unless extra_roles.empty?
|
|
6
|
+
|
|
7
|
+
# max one tell per role
|
|
8
|
+
roles.each{ |r| tell([r]) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def roles
|
|
12
|
+
elements.map{ |s| s.role.to_sym }.uniq
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def for(roles)
|
|
16
|
+
elements.select{ |s| roles.include?(s.role.to_sym) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def asks(roles)
|
|
20
|
+
elements.select do |s|
|
|
21
|
+
s.text_value =~ /^ask/ && roles.include?(s.role.to_sym)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def tell(roles)
|
|
26
|
+
ss = elements.select{ |s| s.text_value =~ /^tell/ && roles.include?(s.role.to_sym) }
|
|
27
|
+
raise "multiple assignments for role: #{role}" if ss.size > 1
|
|
28
|
+
ss.first
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/ceml/script.rb
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module CEML
|
|
2
|
+
module Script
|
|
3
|
+
|
|
4
|
+
def to_hash *fields
|
|
5
|
+
fields.inject({}){ |h, s| x = send(s); h[s] = x if x; h }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def script
|
|
9
|
+
text_value
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def title
|
|
13
|
+
super && !super.empty? && super.value
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def name
|
|
17
|
+
title || label
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def radius
|
|
21
|
+
casting_statement.empty? ? nil : casting_statement.radius
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def roles
|
|
25
|
+
return [:agents] if casting_statement.empty?
|
|
26
|
+
return casting_statement.roles
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def dramatis_personae
|
|
30
|
+
casting_statement.empty? ? nil : casting_statement
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
alias_method :dp, :dramatis_personae
|
|
34
|
+
|
|
35
|
+
def simple?
|
|
36
|
+
(roles - [:agents, :organizer]).empty?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def expand_roles(roles)
|
|
40
|
+
roles.map{ |r| r == :agent ? [:agent, :agents] : r }.flatten.concat([:both, :all, :everyone])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def instructions_for(roles)
|
|
44
|
+
return [] if !instructions or instructions.empty?
|
|
45
|
+
return instructions.for(expand_roles(roles))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def asks(roles)
|
|
49
|
+
return [] if instructions.empty?
|
|
50
|
+
instructions.asks([*roles])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def params
|
|
54
|
+
asks(:organizer).map do |ask|
|
|
55
|
+
[ask.var, ask.var.capitalize, ask.text]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def tell(roles)
|
|
60
|
+
return nil if instructions.empty?
|
|
61
|
+
instructions.tell([*roles])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def type
|
|
65
|
+
return 'mission' if title
|
|
66
|
+
return 'unknown' if instructions.empty?
|
|
67
|
+
return 'question' if not instructions.asks.empty?
|
|
68
|
+
return 'message' if instructions.tell(:agents)
|
|
69
|
+
return 'unknown'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def message?
|
|
73
|
+
type == 'message'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def label
|
|
77
|
+
return title || begin
|
|
78
|
+
return "unknown" unless simple?
|
|
79
|
+
if q = instructions.asks(:agents).first
|
|
80
|
+
"Question: #{q.text}"
|
|
81
|
+
elsif tell = instructions.tell(:agents)
|
|
82
|
+
"Message: #{tell}" if tell
|
|
83
|
+
else
|
|
84
|
+
"unknown"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def validate!
|
|
90
|
+
instructions.validate!(allowed_roles) unless instructions.empty?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def allowed_roles
|
|
94
|
+
allowed_roles = [:organizer, :agents]
|
|
95
|
+
allowed_roles += casting_statement.roles unless casting_statement.empty?
|
|
96
|
+
allowed_roles
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def concludes_immediately?
|
|
100
|
+
!title and instructions.asks([:agents]).empty?
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|