ceml 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/ceml.gemspec +6 -4
- data/lib/ceml/casting_criterion.rb +66 -20
- data/lib/ceml/{casting.rb → casting_statement.rb} +1 -1
- data/lib/ceml/delegate.rb +28 -0
- data/lib/ceml/incident.rb +22 -17
- data/lib/ceml/{instructions.rb → instruction_statements.rb} +5 -1
- data/lib/ceml/script.rb +6 -16
- data/lib/ceml/tt/casting.treetop +8 -2
- data/lib/ceml/tt/scripts.treetop +1 -1
- data/lib/ceml.rb +4 -4
- data/test/helper.rb +25 -9
- data/test/test_incident.rb +50 -22
- metadata +8 -6
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/ceml.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ceml}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Joe Edelman"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2011-01-09}
|
13
13
|
s.description = %q{a language for coordinating real world events}
|
14
14
|
s.email = %q{joe@citizenlogistics.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -31,11 +31,13 @@ Gem::Specification.new do |s|
|
|
31
31
|
"examples/breakfast-exchange.ceml",
|
32
32
|
"examples/citizen-investigation.ceml",
|
33
33
|
"examples/high-fives.ceml",
|
34
|
+
"examples/sample_delegate.rb",
|
34
35
|
"lib/ceml.rb",
|
35
|
-
"lib/ceml/casting.rb",
|
36
36
|
"lib/ceml/casting_criterion.rb",
|
37
|
+
"lib/ceml/casting_statement.rb",
|
38
|
+
"lib/ceml/delegate.rb",
|
37
39
|
"lib/ceml/incident.rb",
|
38
|
-
"lib/ceml/
|
40
|
+
"lib/ceml/instruction_statements.rb",
|
39
41
|
"lib/ceml/script.rb",
|
40
42
|
"lib/ceml/tt/casting.treetop",
|
41
43
|
"lib/ceml/tt/instructions.treetop",
|
@@ -4,11 +4,53 @@ require "forwardable"
|
|
4
4
|
module CEML
|
5
5
|
class Candidate < Struct.new :uid, :tags, :matchables, :lat, :lng
|
6
6
|
include Geokit::Mappable
|
7
|
+
attr_reader :criteria
|
8
|
+
|
9
|
+
def load(criteria)
|
10
|
+
@criteria = criteria.select{ |c| c =~ self }
|
11
|
+
if @criteria.empty? then nil else self end
|
12
|
+
end
|
13
|
+
|
14
|
+
def criteria_for_location(star)
|
15
|
+
criteria.select{ |c| c.fits?(self, star) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class CastingLocation < Struct.new :script, :hash, :created
|
20
|
+
attr_accessor :added
|
21
|
+
def star; hash.values.flatten.first; end
|
22
|
+
|
23
|
+
def criteria
|
24
|
+
@criteria ||= script.awaited_criteria.sort_by{ |c| [-c.complexity, c.min_match] }
|
25
|
+
end
|
26
|
+
|
27
|
+
def push candidate
|
28
|
+
matching_criteria = candidate.criteria_for_location(star)
|
29
|
+
return if matching_criteria.empty?
|
30
|
+
matching_criteria.each{ |c| (hash[c] ||= []) << candidate }
|
31
|
+
@added = true
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.create script, candidate
|
35
|
+
new(script, {}, true).tap{ |x| x.push candidate }
|
36
|
+
end
|
37
|
+
|
38
|
+
# this method will miss possible castings and does not handle ranges at all
|
39
|
+
def cast
|
40
|
+
{}.tap do |casting|
|
41
|
+
criteria.each do |c|
|
42
|
+
return nil unless folks = hash[c].dup
|
43
|
+
folks -= casting.keys
|
44
|
+
return nil unless folks.size >= c.min_match
|
45
|
+
c.role_counts.each do |role, minct|
|
46
|
+
folks.shift(minct).each{ |guy| casting[guy.uid] = role.to_sym }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
7
51
|
end
|
8
52
|
|
9
53
|
class CastingCriterion < Struct.new :script, :plus_tags, :minus_tags, :grouped_by, :radius, :role_counts
|
10
|
-
extend Forwardable
|
11
|
-
def_delegators :script, :locations
|
12
54
|
def min_match; role_counts.values.reduce(:+); end
|
13
55
|
def complexity; plus_tags.size; end
|
14
56
|
|
@@ -16,24 +58,28 @@ module CEML
|
|
16
58
|
(plus_tags - candidate.tags).empty? and (minus_tags & candidate.tags).empty?
|
17
59
|
end
|
18
60
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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 }
|
61
|
+
def fits?(candidate, star)
|
62
|
+
return true unless star
|
63
|
+
return unless grouped_by.all?{ |g| candidate.matchables[g] == star.matchables[g] }
|
64
|
+
p radius
|
65
|
+
!radius or candidate.distance_to(star, :meters) <= radius
|
37
66
|
end
|
38
67
|
end
|
39
68
|
end
|
69
|
+
|
70
|
+
# def satisfied_by_group?(people, already_used)
|
71
|
+
# if (people - already_used).size >= min_match
|
72
|
+
# already_used.concat((people - already_used).first(min_match))
|
73
|
+
# true
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
|
77
|
+
# this method and the one below will miss possible
|
78
|
+
# castings and do not handle ranges at all
|
79
|
+
# def complete?(loc)
|
80
|
+
# people_used = []
|
81
|
+
# @criteria.all? do |c|
|
82
|
+
# next unless folks = loc[c.hash]
|
83
|
+
# c.satisfied_by_group?(folks, people_used)
|
84
|
+
# end
|
85
|
+
# end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Kernel
|
2
|
+
def gen_code(size = 8)
|
3
|
+
rand(36**size).to_s(36)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module CEML
|
8
|
+
class Delegate
|
9
|
+
LOCATIONS = {}
|
10
|
+
PLAYERS = {}
|
11
|
+
|
12
|
+
# yields thing w. #keys, #each, #[], #[]=
|
13
|
+
def with_players id
|
14
|
+
yield PLAYERS[id] ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# yields a thing with #each and #<<
|
18
|
+
def with_locations script
|
19
|
+
yield LOCATIONS[script] ||= []
|
20
|
+
LOCATIONS[script].delete_if{ |loc| loc.cast and Incident.new(script, loc.cast).run }
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(meth, *args, &blk)
|
24
|
+
puts "#{meth}: #{args.map(&:to_s).join(', ')}"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/ceml/incident.rb
CHANGED
@@ -2,34 +2,39 @@ require 'set'
|
|
2
2
|
|
3
3
|
module CEML
|
4
4
|
class Incident
|
5
|
-
attr_reader :script
|
6
|
-
def this; players[@current_id]; end
|
5
|
+
attr_reader :script, :id, :players
|
6
|
+
def this; @players[@current_id]; end
|
7
7
|
def roles; this[:roles] ||= Set.new; end
|
8
8
|
def got; this[:received]; end
|
9
9
|
def recognized; this[:recognized]; end
|
10
10
|
def pc; this[:pc] ||= 0; end
|
11
11
|
def qs_answers; this[:qs_answers] ||= Hash.new; end
|
12
12
|
|
13
|
-
def initialize(
|
14
|
-
@
|
15
|
-
@
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@players ||= CEML.delegate.players(@id) || {}
|
13
|
+
def initialize(script, cast = {}, id = rand(36**10).to_s(36))
|
14
|
+
@id = id
|
15
|
+
@script = Script === script ? script : CEML.parse(:script, script)
|
16
|
+
run do
|
17
|
+
cast.each{ |guy,role| add guy, role }
|
18
|
+
end
|
20
19
|
end
|
21
20
|
|
22
21
|
def add(id, *roles)
|
23
22
|
obj = Hash === roles[-1] ? roles.pop : {}
|
24
|
-
players[id] = obj.merge :roles => Set.new(roles)
|
23
|
+
@players[id] = obj.merge :roles => Set.new(roles)
|
25
24
|
end
|
26
25
|
|
27
26
|
def run
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
CEML.delegate.with_players(@id) do |players|
|
28
|
+
@players = players
|
29
|
+
yield self if block_given?
|
30
|
+
:loop while players.keys.any? do |@current_id|
|
31
|
+
# puts "seq for roles: #{roles.inspect} #{seq.inspect}"
|
32
|
+
# puts "trying: #{@current_id}: #{seq[pc]}"
|
33
|
+
next unless seq[pc] and send(*seq[pc])
|
34
|
+
CEML.delegate.send(*seq[pc] + [@iid, @current_id])
|
35
|
+
this[:pc]+=1
|
36
|
+
end
|
37
|
+
@players = nil
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
@@ -68,7 +73,7 @@ module CEML
|
|
68
73
|
def expand(role, var)
|
69
74
|
role = nil if role == 'otherguy'
|
70
75
|
role = role.to_sym if role
|
71
|
-
players.each do |key, thing|
|
76
|
+
@players.each do |key, thing|
|
72
77
|
next if key == @current_id
|
73
78
|
next if role and not thing[:roles].include? role
|
74
79
|
value = (thing[:qs_answers]||{})[var] and return value
|
@@ -81,7 +86,7 @@ module CEML
|
|
81
86
|
# ==============
|
82
87
|
|
83
88
|
def start
|
84
|
-
roles.include? :agent or return false
|
89
|
+
# roles.include? :agent or return false
|
85
90
|
true
|
86
91
|
end
|
87
92
|
|
@@ -1,9 +1,13 @@
|
|
1
1
|
module CEML
|
2
|
-
module
|
2
|
+
module InstructionStatements
|
3
3
|
def list
|
4
4
|
[instruction_stmt] + more.elements.map(&:instruction_stmt)
|
5
5
|
end
|
6
6
|
|
7
|
+
def instructions
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
7
11
|
def validate_instructions!(allowed_roles)
|
8
12
|
extra_roles = roles - allowed_roles
|
9
13
|
raise "unrecognized rolenames: #{extra_roles.inspect}" unless extra_roles.empty?
|
data/lib/ceml/script.rb
CHANGED
@@ -8,23 +8,13 @@ module CEML
|
|
8
8
|
# = casting =
|
9
9
|
# ===========
|
10
10
|
|
11
|
-
def locations
|
12
|
-
@locations ||= (delegate.locations(self) || [])
|
13
|
-
end
|
14
|
-
|
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
|
21
|
-
end
|
22
|
-
|
23
11
|
# public
|
24
12
|
def post candidate
|
25
|
-
|
26
|
-
|
27
|
-
|
13
|
+
return unless candidate = candidate.dup.load(awaited_criteria)
|
14
|
+
delegate.with_locations(self) do |locs|
|
15
|
+
locs.each{ |l| l.push candidate }
|
16
|
+
locs << CastingLocation.create(self, candidate) if locs.none?(&:added)
|
17
|
+
end
|
28
18
|
end
|
29
19
|
|
30
20
|
def awaited_criteria
|
@@ -115,8 +105,8 @@ module CEML
|
|
115
105
|
# ================
|
116
106
|
|
117
107
|
def instructions
|
118
|
-
return super if defined?(super)
|
119
108
|
return self if Instructions === self
|
109
|
+
return super if defined?(super)
|
120
110
|
nil
|
121
111
|
end
|
122
112
|
|
data/lib/ceml/tt/casting.treetop
CHANGED
@@ -34,8 +34,14 @@ grammar Casting
|
|
34
34
|
rule role
|
35
35
|
(rolename &and / range ws qualifier ws rolename / range ws rolename / qualifier ws rolename / rolename) {
|
36
36
|
def name; if respond_to? :rolename then rolename.text_value else text_value end; end
|
37
|
-
def min
|
38
|
-
|
37
|
+
def min
|
38
|
+
return range.min if respond_to? :range
|
39
|
+
name =~ /s$/ ? 2 : 1
|
40
|
+
end
|
41
|
+
def max
|
42
|
+
return range.max if respond_to? :range
|
43
|
+
name =~ /s$/ ? 10000 : 1
|
44
|
+
end
|
39
45
|
def qualifiers
|
40
46
|
return [qualifier.text_value] if respond_to? :qualifier
|
41
47
|
return []
|
data/lib/ceml/tt/scripts.treetop
CHANGED
data/lib/ceml.rb
CHANGED
@@ -1,22 +1,22 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
require 'treetop'
|
3
3
|
|
4
|
-
require 'ceml/casting'
|
5
|
-
require 'ceml/instructions'
|
6
4
|
require 'ceml/script'
|
5
|
+
require 'ceml/casting_statement'
|
6
|
+
require 'ceml/instruction_statements'
|
7
7
|
require 'ceml/tt/lexer'
|
8
8
|
require 'ceml/tt/casting'
|
9
9
|
require 'ceml/tt/instructions'
|
10
10
|
require 'ceml/tt/scripts'
|
11
11
|
|
12
|
+
require 'ceml/delegate'
|
12
13
|
require 'ceml/casting_criterion'
|
13
14
|
require 'ceml/incident'
|
14
15
|
|
15
16
|
module CEML
|
16
17
|
extend self
|
17
18
|
attr_accessor :delegate
|
18
|
-
@delegate =
|
19
|
-
# puts "#{meth}: #{args.to_s.inspect}"
|
19
|
+
@delegate = Delegate.new
|
20
20
|
end
|
21
21
|
|
22
22
|
module CEML
|
data/test/helper.rb
CHANGED
@@ -6,31 +6,47 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
6
6
|
require 'ceml'
|
7
7
|
|
8
8
|
class Test::Unit::TestCase
|
9
|
-
def play script
|
10
|
-
@e = CEML::Incident.new(script)
|
9
|
+
def play script = nil
|
10
|
+
@e = CEML::Incident.new(script) if script
|
11
|
+
yield
|
12
|
+
CEML::Delegate::PLAYERS.clear
|
11
13
|
end
|
12
14
|
|
13
15
|
def player id, *roles
|
14
|
-
@e.
|
15
|
-
|
16
|
+
@e.run do |script|
|
17
|
+
script.add id, *roles
|
18
|
+
end
|
16
19
|
end
|
17
20
|
|
18
21
|
def asked id, rx
|
19
|
-
|
22
|
+
assert g = CEML::Delegate::PLAYERS.values.find{ |game| game[id] }
|
23
|
+
p = g[id]
|
20
24
|
assert_equal :ask, p[:said]
|
21
25
|
assert_match rx, p[:q]
|
22
26
|
p.delete :said
|
23
27
|
end
|
24
28
|
|
29
|
+
def silent id
|
30
|
+
if g = CEML::Delegate::PLAYERS.values.find{ |game| game[id] }
|
31
|
+
p = g[id]
|
32
|
+
assert !p[:msg]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
25
36
|
def told id, rx
|
26
|
-
|
37
|
+
assert g = CEML::Delegate::PLAYERS.values.find{ |game| game[id] }
|
38
|
+
p = g[id]
|
27
39
|
assert_match rx, p[:msg]
|
28
40
|
p.delete :said
|
29
41
|
end
|
30
42
|
|
31
43
|
def says id, str
|
32
|
-
@e.
|
33
|
-
|
34
|
-
|
44
|
+
@e.run do |incident|
|
45
|
+
incident.players[id][:received] = str
|
46
|
+
end
|
47
|
+
|
48
|
+
CEML.delegate.with_players(@e.id) do |players|
|
49
|
+
players[id][:received] = nil
|
50
|
+
end
|
35
51
|
end
|
36
52
|
end
|
data/test/test_incident.rb
CHANGED
@@ -20,36 +20,64 @@ XXX
|
|
20
20
|
|
21
21
|
class TestIncident < Test::Unit::TestCase
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
23
|
+
def test_signup_1
|
24
|
+
s = CEML.parse(:script, "await 1 new signup\ntell signup: thanks")
|
25
|
+
play do
|
26
|
+
s.post CEML::Candidate.new('fred', ['new'], {})
|
27
|
+
told 'fred', /thanks/
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_signup_2
|
32
|
+
s = CEML.parse(:script, "await 2 new signups\ntell signups: thanks")
|
33
|
+
play do
|
34
|
+
s.post CEML::Candidate.new('fred', ['new'], {})
|
35
|
+
silent 'fred'
|
36
|
+
s.post CEML::Candidate.new('wilma', ['old'], {})
|
37
|
+
silent 'fred'
|
38
|
+
s.post CEML::Candidate.new('betty', ['new'], {})
|
39
|
+
told 'fred', /thanks/
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_await
|
44
|
+
s = CEML.parse(:script, "await a,b,c\ntell a: foo\ntell b: bar\ntell c: baz")
|
45
|
+
play do
|
46
|
+
s.post CEML::Candidate.new('fred', [], {})
|
47
|
+
silent 'fred'
|
48
|
+
s.post CEML::Candidate.new('wilma', [], {})
|
49
|
+
silent 'fred'
|
50
|
+
silent 'wilma'
|
51
|
+
s.post CEML::Candidate.new('betty', [], {})
|
52
|
+
told 'fred', /foo/
|
53
|
+
told 'betty', /baz/
|
54
|
+
end
|
29
55
|
end
|
30
56
|
|
31
57
|
def test_incident
|
32
|
-
play COMPLIMENT_SCRIPT
|
33
|
-
|
34
|
-
|
58
|
+
play COMPLIMENT_SCRIPT do
|
59
|
+
player :joe, :organizer, :agent
|
60
|
+
player :bill, :agent
|
35
61
|
|
36
|
-
|
37
|
-
|
62
|
+
asked :joe, /^Describe/
|
63
|
+
says :joe, 'red people'
|
38
64
|
|
39
|
-
|
65
|
+
told :bill, /^Look for red people/
|
66
|
+
end
|
40
67
|
end
|
41
68
|
|
42
69
|
def test_askchain
|
43
|
-
play ASKCHAIN_SCRIPT
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
70
|
+
play ASKCHAIN_SCRIPT do
|
71
|
+
player :joe, :players, :agent
|
72
|
+
player :bill, :players, :agent
|
73
|
+
|
74
|
+
asked :joe, /favorite color/
|
75
|
+
asked :bill, /favorite color/
|
76
|
+
says :joe, "red"
|
77
|
+
says :bill, "green"
|
78
|
+
asked :joe, /with the color green/
|
79
|
+
asked :bill, /with the color red/
|
80
|
+
end
|
53
81
|
end
|
54
82
|
|
55
83
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 4
|
8
|
+
- 0
|
9
|
+
version: 0.4.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Joe Edelman
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
17
|
+
date: 2011-01-09 00:00:00 -08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -53,11 +53,13 @@ files:
|
|
53
53
|
- examples/breakfast-exchange.ceml
|
54
54
|
- examples/citizen-investigation.ceml
|
55
55
|
- examples/high-fives.ceml
|
56
|
+
- examples/sample_delegate.rb
|
56
57
|
- lib/ceml.rb
|
57
|
-
- lib/ceml/casting.rb
|
58
58
|
- lib/ceml/casting_criterion.rb
|
59
|
+
- lib/ceml/casting_statement.rb
|
60
|
+
- lib/ceml/delegate.rb
|
59
61
|
- lib/ceml/incident.rb
|
60
|
-
- lib/ceml/
|
62
|
+
- lib/ceml/instruction_statements.rb
|
61
63
|
- lib/ceml/script.rb
|
62
64
|
- lib/ceml/tt/casting.treetop
|
63
65
|
- lib/ceml/tt/instructions.treetop
|