ceml 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
+ lib/ceml/tt/*.rb
2
+
1
3
  ## MAC OS
2
4
  .DS_Store
3
5
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.3.1
data/beginners-guide.md CHANGED
@@ -6,11 +6,11 @@ Thanks for taking an interest in CEML, the Coordinated Event Modeling Language.
6
6
  A CEML _script_ is a recipe for action. Just like a recipe, it has three parts: a title, a list of ingredients, and then a section that says what to do with the ingredients. Here's an example:
7
7
 
8
8
  "Trade favorite colors"
9
- gather 1 answerer and 1 listener within 50 feet
10
- ask answerer re favorite_color:
9
+ gather 1 guy and 1 girl within 50 feet
10
+ ask guy re favorite_color:
11
11
  What's your favorite color?
12
- tell listener:
13
- Someone nearby likes the color |answerer.favorite_color|. Find them.
12
+ tell girl:
13
+ Someone nearby likes the color |guy.favorite_color|. Find them.
14
14
 
15
15
  In the example, the first line is the title, which is written in double quotes. The second line is the ingredients and how they are obtained. In CEML, the ingredients are usually people (although sometimes they can be places, things, or notes). The remaining lines are the instructions.
16
16
 
@@ -26,7 +26,7 @@ Whether you write a script compactly with short names for roles and answers or l
26
26
  Commands
27
27
  --------
28
28
 
29
- The above script only uses three CEML commands—*gather*, *ask*, and *tell*—but there aren't many more commands to learn. Only four more, actually: besides *gather*, the other ingredients commands are *await* and *nab*. Besides *ask* and *tell*, the other instructions commands are *assign* and *certify*.
29
+ The above script only uses three CEML commands--*gather*, *ask*, and *tell*--but there aren't many more commands to learn. Only four more, actually: besides *gather*, the other ingredients commands are *await* and *nab*. Besides *ask* and *tell*, the other instructions commands are *assign* and *certify*.
30
30
 
31
31
  As a sneak preview, here's an example that uses the other commands:
32
32
 
@@ -65,7 +65,7 @@ Texts may also contain hyperlinks, which if the player has a smartphone with a w
65
65
  Roles
66
66
  -----
67
67
 
68
- In the examples so far, the words like 'enemy', 'reporter', 'answerer', 'listener', 'a', 'b', 'signup', 'patient', and 'doctor' are *role names*. Most of the time, a role name can be any word you like. They are just a placeholder to connect *casting commands* like *gather* and *await*, with *coordination commands* like *tell*.
68
+ In the examples so far, the words like 'enemy', 'reporter', 'guy', 'girl', 'a', 'b', 'signup', 'patient', and 'doctor' are *role names*. Most of the time, a role name can be any word you like. They are just a placeholder to connect *casting commands* like *gather* and *await*, with *coordination commands* like *tell*.
69
69
 
70
70
  Squads on Groundcrew, however, can define special meanings for certain roles. So on a particular squad, a 'doctor' might mean someone who's been tagged/certified with the tag 'doctor', and a patient might mean anyone else.
71
71
 
@@ -120,7 +120,7 @@ Choosing Players: Await, Gather, and Nab.
120
120
 
121
121
  So what is really the difference between *await*, *gather*, and *nab*, you may be asking? Or perhaps you have already figured it out.
122
122
 
123
- *Await* defines a kind of trigger—as soon as the conditions awaited for are met, the script will run.
123
+ *Await* defines a kind of trigger--as soon as the conditions awaited for are met, the script will run.
124
124
 
125
125
  await 2-5 level1 players within 50ft
126
126
  assign: shout out "woo-hoo!"
data/ceml.gemspec ADDED
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{ceml}
8
+ s.version = "0.3.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Joe Edelman"]
12
+ s.date = %q{2010-12-26}
13
+ s.description = %q{a language for coordinating real world events}
14
+ s.email = %q{joe@citizenlogistics.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "Makefile",
24
+ "README.markdown",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "beginners-guide.md",
28
+ "ceml.gemspec",
29
+ "editors/CEML.tmbundle/Syntaxes/ceml.tmLanguage",
30
+ "editors/CEML.tmbundle/info.plist",
31
+ "examples/breakfast-exchange.ceml",
32
+ "examples/citizen-investigation.ceml",
33
+ "examples/high-fives.ceml",
34
+ "lib/ceml.rb",
35
+ "lib/ceml/casting.rb",
36
+ "lib/ceml/casting_criterion.rb",
37
+ "lib/ceml/incident.rb",
38
+ "lib/ceml/instructions.rb",
39
+ "lib/ceml/script.rb",
40
+ "lib/ceml/tt/casting.treetop",
41
+ "lib/ceml/tt/instructions.treetop",
42
+ "lib/ceml/tt/lexer.treetop",
43
+ "lib/ceml/tt/scripts.treetop",
44
+ "test/helper.rb",
45
+ "test/test_casting.rb",
46
+ "test/test_incident.rb",
47
+ "test/test_instructions.rb",
48
+ "test/test_scripts.rb",
49
+ "try"
50
+ ]
51
+ s.homepage = %q{http://github.com/citizenlogistics/ceml}
52
+ s.rdoc_options = ["--charset=UTF-8"]
53
+ s.require_paths = ["lib"]
54
+ s.rubygems_version = %q{1.3.6}
55
+ s.summary = %q{a language for coordinating real world events}
56
+ s.test_files = [
57
+ "test/helper.rb",
58
+ "test/test_casting.rb",
59
+ "test/test_incident.rb",
60
+ "test/test_instructions.rb",
61
+ "test/test_scripts.rb",
62
+ "examples/sample_delegate.rb"
63
+ ]
64
+
65
+ if s.respond_to? :specification_version then
66
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
67
+ s.specification_version = 3
68
+
69
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
70
+ s.add_runtime_dependency(%q<treetop>, [">= 0"])
71
+ else
72
+ s.add_dependency(%q<treetop>, [">= 0"])
73
+ end
74
+ else
75
+ s.add_dependency(%q<treetop>, [">= 0"])
76
+ end
77
+ end
78
+
@@ -0,0 +1,4 @@
1
+ class CEMLDelegate
2
+ def locations(script)
3
+ end
4
+ end
data/lib/ceml/casting.rb CHANGED
@@ -1,52 +1,34 @@
1
1
  module CEML
2
2
  module CastingStatement
3
3
  extend Forwardable
4
- def_delegators :roles_phrase, :roles, :[], :min
4
+ def_delegators :roles, :names, :[], :min
5
+ alias_method :rolenames, :names
6
+
7
+ def criteria(script)
8
+ return [] unless type == :await
9
+ group_by = if radius then [:city] else [] end
10
+ criteria_by_qualifiers = Hash.new do |h,k|
11
+ h[k] = CastingCriterion.new(script, k, [], group_by, radius, {})
12
+ end
13
+
14
+ roles.list.each{ |r| criteria_by_qualifiers[r.qualifiers].role_counts[r.name] = r.min }
15
+ criteria_by_qualifiers.values
16
+ end
5
17
 
6
18
  def type
7
19
  elements.first.text_value.split.first.to_sym
8
20
  end
9
21
 
10
22
  def max
11
- in_teams? ? 10000 : roles_phrase.max
23
+ roles.max
12
24
  end
13
25
 
14
26
  def radius
15
- within.empty? ? 1600 * 50 : within.distance.meters
16
- end
17
-
18
- def in_teams?
19
- type == :teams
27
+ within_phrase.empty? ? 1600 * 50 : within_phrase.distance.meters
20
28
  end
21
29
 
22
30
  def nab?
23
31
  type == :nab
24
32
  end
25
33
  end
26
-
27
-
28
- module CastingRoles
29
- def role_nodes
30
- return [role] if more_roles.empty?
31
- return [role] + more_roles.roles_phrase.role_nodes
32
- end
33
-
34
- def roles
35
- role_nodes.map{ |r| r.name.to_sym }
36
- end
37
-
38
- def [](x)
39
- role_nodes.detect{ |r| r.name.to_sym == x }
40
- end
41
-
42
- def min
43
- role_nodes.map(&:min).inject(0, &:+)
44
- end
45
-
46
- def max
47
- role_nodes.map(&:max).inject(0, &:+)
48
- end
49
- end
50
-
51
-
52
34
  end
@@ -0,0 +1,39 @@
1
+ require 'geokit'
2
+ require "forwardable"
3
+
4
+ module CEML
5
+ class Candidate < Struct.new :uid, :tags, :matchables, :lat, :lng
6
+ include Geokit::Mappable
7
+ end
8
+
9
+ class CastingCriterion < Struct.new :script, :plus_tags, :minus_tags, :grouped_by, :radius, :role_counts
10
+ extend Forwardable
11
+ def_delegators :script, :locations
12
+ def min_match; role_counts.values.reduce(:+); end
13
+ def complexity; plus_tags.size; end
14
+
15
+ def =~(candidate)
16
+ (plus_tags - candidate.tags).empty? and (minus_tags & candidate.tags).empty?
17
+ end
18
+
19
+ def satisfied_by_group?(people, already_used)
20
+ if (people - already_used).size >= min_match
21
+ already_used.concat((people - already_used).first(min_match))
22
+ true
23
+ end
24
+ end
25
+
26
+ def list_candidate(candidate)
27
+ locs = locations.select do |l|
28
+ star = l.values.flatten.first
29
+ next unless grouped_by.all?{ |g| candidate.matchables[g] == star.matchables[g] }
30
+ !radius or candidate.distance_to(star, :meters) <= radius
31
+ end
32
+ if locs.empty?
33
+ new_loc = {}
34
+ [locations, locs].each{ |l| l << new_loc }
35
+ end
36
+ locs.each{ |l| (l[hash] ||= []) << candidate }
37
+ end
38
+ end
39
+ end
@@ -1,29 +1,30 @@
1
1
  module CEML
2
2
  module Instructions
3
- def validate!(allowed_roles)
3
+ def list
4
+ [instruction_stmt] + more.elements.map(&:instruction_stmt)
5
+ end
6
+
7
+ def validate_instructions!(allowed_roles)
4
8
  extra_roles = roles - allowed_roles
5
9
  raise "unrecognized rolenames: #{extra_roles.inspect}" unless extra_roles.empty?
6
-
7
- # max one tell per role
8
- roles.each{ |r| tell([r]) }
9
10
  end
10
11
 
11
12
  def roles
12
- elements.map{ |s| s.role.to_sym }.uniq
13
+ list.map{ |s| s.role.to_sym }.uniq
13
14
  end
14
15
 
15
16
  def for(roles)
16
- elements.select{ |s| roles.include?(s.role.to_sym) }
17
+ list.select{ |s| roles.include?(s.role.to_sym) }
17
18
  end
18
19
 
19
- def asks(roles)
20
- elements.select do |s|
20
+ def i_asks(roles)
21
+ list.select do |s|
21
22
  s.text_value =~ /^ask/ && roles.include?(s.role.to_sym)
22
23
  end
23
24
  end
24
25
 
25
- def tell(roles)
26
- ss = elements.select{ |s| s.text_value =~ /^tell/ && roles.include?(s.role.to_sym) }
26
+ def i_tell(roles)
27
+ ss = list.select{ |s| s.text_value =~ /^tell/ && roles.include?(s.role.to_sym) }
27
28
  raise "multiple assignments for role: #{role}" if ss.size > 1
28
29
  ss.first
29
30
  end
data/lib/ceml/script.rb CHANGED
@@ -1,41 +1,100 @@
1
1
  module CEML
2
2
  module Script
3
+ extend Forwardable
4
+ attr_writer :delegate
5
+ def delegate; @delegate || CEML.delegate; end
3
6
 
4
- def to_hash *fields
5
- fields.inject({}){ |h, s| x = send(s); h[s] = x if x; h }
7
+ # ===========
8
+ # = casting =
9
+ # ===========
10
+
11
+ def locations
12
+ @locations ||= (delegate.locations(self) || [])
6
13
  end
7
14
 
8
- def script
9
- text_value
15
+ def launchable_location
16
+ criteria = awaited_criteria.sort_by{ |c| [-c.complexity, c.min_match] }
17
+ locations.find do |l|
18
+ people_used = []
19
+ criteria.all?{ |c| folks = l[c.hash] and c.satisfied_by_group?(folks, people_used) }
20
+ end
10
21
  end
11
22
 
12
- def title
13
- super && !super.empty? && super.value
23
+ # public
24
+ def post candidate
25
+ criteria = awaited_criteria.select{ |c| c =~ candidate }
26
+ criteria.each{ |c| c.list_candidate(candidate) }
27
+ not criteria.empty?
14
28
  end
15
29
 
16
- def name
17
- title || label
30
+ def awaited_criteria
31
+ return [] unless cast.type == :await
32
+ return cast.criteria(self)
18
33
  end
19
34
 
20
- def radius
21
- casting_statement.empty? ? nil : casting_statement.radius
35
+
36
+ # ===============
37
+ # = likelihoods =
38
+ # ===============
39
+
40
+ def color(odds)
41
+ return :red if odds < 0.1
42
+ return :yellow if odds < 0.4
43
+ return :green
22
44
  end
23
45
 
24
- def roles
25
- return [:agents] if casting_statement.empty?
26
- return casting_statement.roles
46
+ def likelihood(yesprob, needed, psize)
47
+ return 1 if needed <= 0
48
+ return 0 if psize < needed
49
+ return (needed..psize).inject(0) do |memo, yes_count|
50
+ memo + begin
51
+ no_count = psize - yes_count
52
+ ways_it_could_happen = psize.choose(yes_count)
53
+ prob_of_each = (yesprob ** yes_count) * ((1 - yesprob) ** no_count)
54
+ ways_it_could_happen * prob_of_each
55
+ end
56
+ end
27
57
  end
28
58
 
29
- def dramatis_personae
30
- casting_statement.empty? ? nil : casting_statement
59
+ def availabilities(potential_count, committed_count = 0)
60
+ return {} unless script.dramatis_personae
61
+ min = script.dramatis_personae.min
62
+ needed = min - committed_count
63
+ possible = potential_count + committed_count
64
+ yesprob = 0.7 # just make this up for now
65
+ odds = likelihood(yesprob, needed, potential_count)
66
+
67
+ {
68
+ :odds => odds, :color => color(odds),
69
+ :availability_counts => {
70
+ :total => possible,
71
+ :unknown => potential_count
72
+ },
73
+ :estimated_size => yesprob * potential_count + committed_count,
74
+ :needed => min
75
+ }
31
76
  end
32
77
 
33
- def max_to_invite
34
- dramatis_personae ? dramatis_personae.max : 0
78
+
79
+ # ========
80
+ # = cast =
81
+ # ========
82
+
83
+ DefaultDP = Struct.new :radius, :rolenames, :max, :type
84
+
85
+ def cast
86
+ return casting_statement if respond_to? :casting_statement
87
+ return DefaultDP.new nil, [:agents], 0, :nab
35
88
  end
36
89
 
90
+ def_delegators :cast, :radius
91
+ def_delegator :cast, :max, :max_to_invite
92
+ def_delegator :cast, :rolenames, :roles
93
+ alias_method :dramatis_personae, :cast
37
94
  alias_method :dp, :dramatis_personae
38
95
 
96
+ # casting_statement.roles.names
97
+
39
98
  def simple?
40
99
  (roles - [:agents, :organizer]).empty?
41
100
  end
@@ -44,14 +103,71 @@ module CEML
44
103
  roles.map{ |r| r == :agent ? [:agent, :agents] : r }.flatten.concat([:both, :all, :everyone])
45
104
  end
46
105
 
106
+ def allowed_roles
107
+ allowed_roles = [:organizer, :agents]
108
+ allowed_roles += cast.rolenames
109
+ allowed_roles.uniq
110
+ end
111
+
112
+
113
+ # ================
114
+ # = instructions =
115
+ # ================
116
+
117
+ def instructions
118
+ return super if defined?(super)
119
+ return self if Instructions === self
120
+ nil
121
+ end
122
+
47
123
  def instructions_for(roles)
48
- return [] if !instructions or instructions.empty?
124
+ return [] unless instructions
49
125
  return instructions.for(expand_roles(roles))
50
126
  end
51
127
 
52
128
  def asks(roles)
53
- return [] if instructions.empty?
54
- instructions.asks([*roles])
129
+ return [] unless instructions
130
+ instructions.i_asks([*roles])
131
+ end
132
+
133
+ def tell(roles)
134
+ return unless instructions
135
+ instructions.i_tell([*roles])
136
+ end
137
+
138
+ def validate!
139
+ return unless instructions
140
+ instructions.validate_instructions!(allowed_roles)
141
+ end
142
+
143
+ def concludes_immediately?
144
+ !title and instructions.asks([:agents]).empty?
145
+ end
146
+
147
+
148
+ # =======
149
+ # = etc =
150
+ # =======
151
+
152
+ def to_hash *fields
153
+ fields.inject({}){ |h, s| x = send(s); h[s] = x if x; h }
154
+ end
155
+
156
+ def script
157
+ text_value
158
+ end
159
+
160
+ def title
161
+ if defined?(super)
162
+ super.title_value
163
+ elsif respond_to? :title_value
164
+ self.title_value
165
+ else nil
166
+ end
167
+ end
168
+
169
+ def name
170
+ title || label
55
171
  end
56
172
 
57
173
  def params
@@ -60,11 +176,6 @@ module CEML
60
176
  end
61
177
  end
62
178
 
63
- def tell(roles)
64
- return nil if instructions.empty?
65
- instructions.tell([*roles])
66
- end
67
-
68
179
  def type
69
180
  return 'mission' if title
70
181
  return 'unknown' if instructions.empty?
@@ -89,58 +200,5 @@ module CEML
89
200
  end
90
201
  end
91
202
  end
92
-
93
- def validate!
94
- instructions.validate!(allowed_roles) unless instructions.empty?
95
- end
96
-
97
- def allowed_roles
98
- allowed_roles = [:organizer, :agents]
99
- allowed_roles += casting_statement.roles unless casting_statement.empty?
100
- allowed_roles
101
- end
102
-
103
- def concludes_immediately?
104
- !title and instructions.asks([:agents]).empty?
105
- end
106
-
107
- def color(odds)
108
- return :red if odds < 0.1
109
- return :yellow if odds < 0.4
110
- return :green
111
- end
112
-
113
- def likelihood(yesprob, needed, psize)
114
- return 1 if needed <= 0
115
- return 0 if psize < needed
116
- return (needed..psize).inject(0) do |memo, yes_count|
117
- memo + begin
118
- no_count = psize - yes_count
119
- ways_it_could_happen = psize.choose(yes_count)
120
- prob_of_each = (yesprob ** yes_count) * ((1 - yesprob) ** no_count)
121
- ways_it_could_happen * prob_of_each
122
- end
123
- end
124
- end
125
-
126
- def availabilities(potential_count, committed_count = 0)
127
- return {} unless script.dramatis_personae
128
- min = script.dramatis_personae.min
129
- needed = min - committed_count
130
- possible = potential_count + committed_count
131
- yesprob = 0.7 # just make this up for now
132
- odds = likelihood(yesprob, needed, potential_count)
133
-
134
- {
135
- :odds => odds, :color => color(odds),
136
- :availability_counts => {
137
- :total => possible,
138
- :unknown => potential_count
139
- },
140
- :estimated_size => yesprob * potential_count + committed_count,
141
- :needed => min
142
- }
143
- end
144
-
145
203
  end
146
204
  end
@@ -3,45 +3,42 @@ grammar Casting
3
3
  include Lexer
4
4
 
5
5
  rule casting_statement
6
- casting_method ws roles_phrase within:(ws 'within' ws distance)? nl <CastingStatement>
6
+ ('gather' / 'await' / 'nab') ws roles within_phrase:within_phrase? <CastingStatement>
7
7
  end
8
8
 
9
- rule casting_method
10
- 'gather' / 'teams of' / 'nab'
9
+ rule within_phrase
10
+ ws 'within' ws distance
11
11
  end
12
12
 
13
- rule roles_phrase
14
- role more_roles:(ws 'and' ws roles_phrase)? <CastingRoles>
13
+ rule roles
14
+ role more:(and role)* {
15
+ def list
16
+ [role] + more.elements.map{ |e| e.role }
17
+ end
18
+
19
+ def names; list.map{ |r| r.name.to_sym }; end
20
+ def [](x); list.detect{ |r| r.name.to_sym == x }; end
21
+ def min; list.map(&:min).inject(0, &:+); end
22
+ def max; list.map(&:max).inject(0, &:+); end
23
+ }
15
24
  end
16
25
 
17
- rule distance
18
- number ws? distance_unit:('miles' / 'mile' / 'mi' / 'km' / 'kilometers' / 'k' / 'meters' / 'm' / 'ft' / 'feet' / 'f' / 'blocks' / 'block')
19
- {
20
- def meters
21
- number.text_value.to_f * case distance_unit.text_value
22
- when /^mi/; 1600; when /^k/; 1000; when /^m/; 1;
23
- when /^f/; 0.35; when /^b/; 200; else 1; end
24
- end
25
- }
26
+ rule qualifier
27
+ id
26
28
  end
27
29
 
28
- rule role
29
- range:range? ws? id
30
- {
31
- def name; id.text_value; end
32
- def min; range.empty? ? 2 : range.min; end
33
- def max; range.empty? ? 10000 : range.max; end
34
- }
30
+ rule rolename
31
+ id
35
32
  end
36
33
 
37
- rule range
38
- min:number max:('+' / '-' number)?
39
- {
40
- def min; super.text_value.to_i; end
41
- def max
42
- return min if super.empty?
43
- return 10000 if super.text_value == '+'
44
- return super.number.text_value.to_i
34
+ rule role
35
+ (rolename &and / range ws qualifier ws rolename / range ws rolename / qualifier ws rolename / rolename) {
36
+ def name; if respond_to? :rolename then rolename.text_value else text_value end; end
37
+ def min; if respond_to? :range then range.min else 2 end; end
38
+ def max; if respond_to? :range then range.max else 10000 end; end
39
+ def qualifiers
40
+ return [qualifier.text_value] if respond_to? :qualifier
41
+ return []
45
42
  end
46
43
  }
47
44
  end
@@ -2,11 +2,7 @@ module CEML
2
2
  grammar Instructions
3
3
  include Lexer
4
4
 
5
- rule instructions
6
- instruction+ <Instructions>
7
- end
8
-
9
- rule instruction
5
+ rule instruction_stmt
10
6
  (ask_stmt / tell_stmt / assign_stmt) {
11
7
 
12
8
  INTERPOLATE_REGEX = /\|(\w+)\.?(\w+)?\|/
@@ -31,15 +27,15 @@ grammar Instructions
31
27
  end
32
28
 
33
29
  rule tell_stmt
34
- 'tell' ws id ':' ws? text nl
30
+ 'tell' ws id ':' ws? text
35
31
  end
36
32
 
37
33
  rule assign_stmt
38
- 'assign' ws id ':' ws? text nl
34
+ 'assign' ws id ':' ws? text
39
35
  end
40
36
 
41
37
  rule ask_stmt
42
- 'ask' ws id about:(ws 're' ws varname:id)? ':' ws? text nl
38
+ 'ask' ws id about:(ws 're' ws varname:id)? ':' ws? text
43
39
  end
44
40
 
45
41
  end