├── Rakefile
├── spec
├── javascripts
│ └── support
│ │ ├── jasmine.yml
│ │ └── jasmine_runner.rb
├── integrationSpec.js
├── standardSpec.js
├── languageSpec.js
└── codeSpec.js
├── LICENSE
├── README.md
└── lib
└── evolu-lang.js
/Rakefile:
--------------------------------------------------------------------------------
1 |
2 | require 'jasmine'
3 | load 'jasmine/tasks/jasmine.rake'
4 |
--------------------------------------------------------------------------------
/spec/javascripts/support/jasmine.yml:
--------------------------------------------------------------------------------
1 | src_files:
2 | - evolu-lang.js
3 |
4 | spec_files:
5 | - **/*[sS]pec.js
6 |
7 | helpers:
8 |
9 | stylesheets:
10 |
11 | src_dir: lib
12 |
13 | spec_dir: spec
14 |
--------------------------------------------------------------------------------
/spec/javascripts/support/jasmine_runner.rb:
--------------------------------------------------------------------------------
1 | $:.unshift(ENV['JASMINE_GEM_PATH']) if ENV['JASMINE_GEM_PATH'] # for gem testing purposes
2 |
3 | require 'rubygems'
4 | require 'jasmine'
5 | jasmine_config_overrides = File.expand_path(File.join(File.dirname(__FILE__), 'jasmine_config.rb'))
6 | require jasmine_config_overrides if File.exist?(jasmine_config_overrides)
7 | if Jasmine::rspec2?
8 | require 'rspec'
9 | else
10 | require 'spec'
11 | end
12 |
13 | jasmine_config = Jasmine::Config.new
14 | spec_builder = Jasmine::SpecBuilder.new(jasmine_config)
15 |
16 | should_stop = false
17 |
18 | if Jasmine::rspec2?
19 | RSpec.configuration.after(:suite) do
20 | spec_builder.stop if should_stop
21 | end
22 | else
23 | Spec::Runner.configure do |config|
24 | config.after(:suite) do
25 | spec_builder.stop if should_stop
26 | end
27 | end
28 | end
29 |
30 | spec_builder.start
31 | should_stop = true
32 | spec_builder.declare_suites
--------------------------------------------------------------------------------
/spec/integrationSpec.js:
--------------------------------------------------------------------------------
1 | describe('evolu.lang', function() {
2 |
3 | it('should calculate odd or even signals was be received', function() {
4 | evolu.lang.add('ODD-EVEN', function() {
5 | this.add(evolu.lang.standard.input('tick', 'result'))
6 | this.add(evolu.lang.standard.output('odd', 'even'))
7 | this.add(evolu.lang.standard.variables)
8 | })
9 |
10 | // rule 0:
11 | // var_up 0
12 | // rule 1:
13 | // ? if_signal tick
14 | // ? if_var_more_0 0
15 | // var_down 0
16 | // var_up 1
17 | // rule 2:
18 | // ? if_signal tick
19 | // ? if_var_more_0 1
20 | // var_down 1
21 | // var_up 0
22 | // rule 3:
23 | // ? if_signal result
24 | // ? if_var_more_0 0
25 | // send_signal even
26 | // rule 4:
27 | // ? if_signal result
28 | // ? if_var_more_0 1
29 | // send_signal odd
30 |
31 | var code = evolu.lang.compile('EVOLU:ODD-EVEN:' +
32 | '\x04\x80\x00' +
33 | '\x01\x80\x03\x80\x05\x80\x04\x81\x00' +
34 | '\x01\x80\x03\x81\x05\x81\x04\x80\x00' +
35 | '\x01\x81\x03\x80\x02\x81\x00' +
36 | '\x01\x81\x03\x81\x02\x80')
37 |
38 | var result = ''
39 | code.listen('receive_signal', function(signal) {
40 | result += signal + ' '
41 | })
42 |
43 | code.init()
44 | code.signal('result')
45 | expect(result).toEqual('even ')
46 |
47 | code.signal('tick').signal('tick').signal('tick').signal('result')
48 | expect(result).toEqual('even odd ')
49 |
50 | code.signal('tick').signal('result')
51 | expect(result).toEqual('even odd even ')
52 | })
53 |
54 | })
55 |
--------------------------------------------------------------------------------
/spec/standardSpec.js:
--------------------------------------------------------------------------------
1 | describe('evolu.lang.standard', function() {
2 | it('should add variables', function() {
3 | var lang = evolu.lang.add('LNG', function() {
4 | this.add(evolu.lang.standard.variables)
5 | })
6 | expect(lang._list.length).toEqual(4)
7 |
8 | var code = new evolu.lang.Code(lang)
9 | code.rule(['var_up', 2],
10 | ['var_up' ])
11 | code.rule(['if_var_more_0', 1],
12 | ['var_down', 1])
13 | code.rule(['if_var_more_0', 2],
14 | ['var_down', 2],
15 | ['var_up', 1],
16 | ['var_up', 1])
17 |
18 | expect(code._variables).toEqual({ 1: 0, 2: 0 })
19 |
20 | evolu.lang.Rule.prototype._originalRun = evolu.lang.Rule.prototype.run
21 | var runned = ''
22 | evolu.lang.Rule.prototype.run = function() {
23 | runned += this.id
24 | this._originalRun()
25 | }
26 |
27 | code.init()
28 | expect(code._variables).toEqual({ 1: 0, 2: 1 })
29 | code.run()
30 | expect(code._variables).toEqual({ 1: 2, 2: 0 })
31 | code.run()
32 | expect(code._variables).toEqual({ 1: 1, 2: 0 })
33 | code.run()
34 | expect(code._variables).toEqual({ 1: 0, 2: 0 })
35 | code.run()
36 | expect(code._variables).toEqual({ 1: 0, 2: 0 })
37 | code.run()
38 |
39 | expect(runned).toEqual('0211')
40 |
41 | evolu.lang.Rule.prototype.run = evolu.lang.Rule.prototype._originalRun
42 | })
43 |
44 | it('should fire event when change variable', function() {
45 | var code = new evolu.lang.Code(evolu.lang.add('LNG', function() {
46 | this.add(evolu.lang.standard.variables)
47 | }))
48 |
49 | code.rule(['var_up', 2], ['var_down'], ['var_down', 1])
50 |
51 | var changes = []
52 | code.listen('var_changed', function(name, value, diff) {
53 | changes.push([name, value, diff])
54 | })
55 |
56 | code.init()
57 |
58 | expect(changes).toEqual([[2, 1, 1], [1, -1, -1]])
59 | })
60 |
61 | it('should allow add variables command', function() {
62 | var pack = evolu.lang.add('PACK', function() {
63 | this.add(evolu.lang.standard.variables)
64 | })
65 | var separated = evolu.lang.add('SEP', function() {
66 | this.add(evolu.lang.standard.variables.moreZero)
67 | this.add(evolu.lang.standard.variables.increase)
68 | this.add(evolu.lang.standard.variables.decrease)
69 | })
70 |
71 | expect(pack._list.length).toEqual(separated._list.length)
72 | })
73 |
74 | it('should add input signals', function() {
75 | var lang = evolu.lang.add('LNG', function() {
76 | this.add(evolu.lang.standard.input('a', 'b'))
77 | })
78 | expect(lang._list.length).toEqual(2)
79 |
80 | var code = new evolu.lang.Code(lang)
81 | code.rule(['if_signal', 'a'])
82 | code.rule(['if_signal', 'a'], ['if_signal', 'b'])
83 | code.rule(['if_signal', 'b'], ['if_signal', 'b'])
84 |
85 | evolu.lang.Rule.prototype._originalRun = evolu.lang.Rule.prototype.run
86 | var runned = ''
87 | evolu.lang.Rule.prototype.run = function() {
88 | runned += this.id
89 | this._originalRun()
90 | }
91 |
92 | code.signal('a')
93 | code.signal('b')
94 | code.run()
95 |
96 | expect(runned).toEqual('0112')
97 |
98 | evolu.lang.Rule.prototype.run = evolu.lang.Rule.prototype._originalRun
99 | })
100 |
101 | it('should add output signals', function() {
102 | var lang = evolu.lang.add('LNG', function() {
103 | this.add(evolu.lang.standard.output('a', 'b'))
104 | })
105 | expect(lang._list.length).toEqual(2)
106 |
107 | var code = new evolu.lang.Code(lang)
108 | code.rule(['send_signal', 'a'],
109 | ['send_signal', 'a'],
110 | ['send_signal', 'b'])
111 |
112 | var output = ''
113 | code.listen('receive_signal', function(signal) {
114 | output += signal
115 | })
116 | code.init()
117 |
118 | expect(output).toEqual('aab')
119 | })
120 |
121 | it('should convert bytes to signal names', function() {
122 | var lang = evolu.lang.add('LNG', function() {
123 | this.add(evolu.lang.standard.input('in_a', 'in_b'))
124 | this.add(evolu.lang.standard.output('out_a', 'out_b'))
125 | })
126 | code = lang.compile([1, 128, 1, 129, 1, 130,
127 | 2, 128, 2, 129, 2, 130])
128 | rule = code.rules[0]
129 |
130 | expect(rule.lines[0].param).toEqual('in_a')
131 | expect(rule.lines[1].param).toEqual('in_b')
132 | expect(rule.lines[2].param).toEqual('in_a')
133 | expect(rule.lines[3].param).toEqual('out_a')
134 | expect(rule.lines[4].param).toEqual('out_b')
135 | expect(rule.lines[5].param).toEqual('out_a')
136 | })
137 | })
138 |
--------------------------------------------------------------------------------
/spec/languageSpec.js:
--------------------------------------------------------------------------------
1 | describe('evolu.lang.Language', function() {
2 | it('should create new language', function() {
3 | evolu.lang._languages = {}
4 | expect(evolu.lang.get('LNG')).not.toBeDefined()
5 |
6 | var lang = evolu.lang.add('LNG', function() { this.a = 1 })
7 |
8 | expect(evolu.lang.get('LNG')).toBe(lang)
9 | expect(lang instanceof evolu.lang.Language).toBeTruthy()
10 | expect(lang.name).toEqual('LNG')
11 | expect(lang.a).toEqual(1)
12 | })
13 |
14 | it('should ignore case in language name', function() {
15 | evolu.lang.add('LNG', function() { this.a = 1 })
16 | expect(evolu.lang.get('lng')).toBe(evolu.lang.get('LNG'))
17 | })
18 |
19 | it('should recreate language', function() {
20 | evolu.lang.add('LNG', function() { this.a = 1 })
21 | evolu.lang.add('lng', function() { this.b = 2 })
22 |
23 | expect(evolu.lang.get('LNG').a).not.toBeDefined()
24 | expect(evolu.lang.get('LNG').b).toEqual(2)
25 | })
26 |
27 | it('should add commands', function() {
28 | var func = function() { }
29 | var lang = evolu.lang.add('LNG', function() {
30 | this.command('a', func).
31 | command('c', { c: 3 }).
32 | command('b', { b: 2, position: 2 })
33 | })
34 |
35 | expect(lang.commands).toEqual({
36 | separator: lang._separator,
37 | a: { name: 'a', run: func },
38 | b: { name: 'b', b: 2 },
39 | c: { name: 'c', c: 3 }
40 | })
41 | expect(lang._list).toEqual([lang._separator,
42 | { name: 'a', run: func },
43 | { name: 'b', b: 2 },
44 | { name: 'c', c: 3 }])
45 |
46 | var another = evolu.lang.add('ANZ', function() { })
47 | expect(another.commands).toEqual({ separator: another._separator })
48 | expect(another._list).toEqual([ another._separator ])
49 | })
50 |
51 | it('should add condition', function() {
52 | var func = function() { }
53 | var lang = evolu.lang.add('LNG', function() {
54 | this.condition('if_b', { b: 2 }).
55 | condition('if_a', { a: 1, position: 1 })
56 | })
57 |
58 | expect(lang._list).toEqual([
59 | lang._separator,
60 | { name: 'if_a', a: 1, condition: true, init: lang._initCondition },
61 | { name: 'if_b', b: 2, condition: true, init: lang._initCondition }
62 | ])
63 | })
64 |
65 | it('should find language for compile', function() {
66 | evolu.lang._languages = {}
67 |
68 | expect(function() {
69 | evolu.lang.compile('NO PROGRAM')
70 | }).toThrow("It isn't Evolu program, because it hasn't EVOLU mark " +
71 | "at beginning.")
72 |
73 | expect(function() {
74 | evolu.lang.compile('EVOLU:LNG:abc')
75 | }).toThrow('Unknown Evolu language `LNG`')
76 |
77 | var lang = evolu.lang.add('LNG', function() { })
78 |
79 | spyOn(lang, 'compile')
80 | evolu.lang.compile('EVOLU:LNG:abc')
81 | expect(lang.compile).toHaveBeenCalledWith([97, 98, 99])
82 | })
83 |
84 | it('should compile bytes to rules', function() {
85 | var lang = evolu.lang.add('LNG', function() {
86 | this.command('a', { params: ['one', 'two'] }).
87 | command('b', { init: function() {} })
88 | })
89 |
90 | var code = lang.compile([128, 5, 0, 1, 128, 131, 2, 130, 128])
91 |
92 | expect(code instanceof evolu.lang.Code).toBeTruthy()
93 | expect(code.language).toEqual(lang)
94 |
95 | expect(code.rules).toEqual([
96 | {
97 | id: 0,
98 | lines: [{ command: lang.commands.b }],
99 | code: code,
100 | required: 0,
101 | initializer: true,
102 | on: evolu.lang.Rule.prototype.on,
103 | off: evolu.lang.Rule.prototype.off,
104 | run: evolu.lang.Rule.prototype.run
105 | },
106 | {
107 | id: 1,
108 | lines: [{ command: lang.commands.a, param: 'two' },
109 | { command: lang.commands.b, param: 256 }],
110 | code: code,
111 | on: code._ruleOn,
112 | off: code._ruleOff,
113 | required: 0,
114 | initializer: true,
115 | on: evolu.lang.Rule.prototype.on,
116 | off: evolu.lang.Rule.prototype.off,
117 | run: evolu.lang.Rule.prototype.run
118 | }
119 | ])
120 | })
121 |
122 | it('install commands to code', function() {
123 | var result = ''
124 | var lang = evolu.lang.add('LNG', function() {
125 | this.command('a', {
126 | install: function() {
127 | this.a = 1
128 | result += '1'
129 | }
130 | })
131 | })
132 | var code = new evolu.lang.Code(lang)
133 | code.rule('a', 'a')
134 |
135 | expect(result).toEqual('1')
136 | expect(code.a).toEqual(1)
137 | })
138 |
139 | it('should call initializers on compiling', function() {
140 | var currentCode, currentRule, currentLine, bCalls = 0
141 | var lang = evolu.lang.add('LNG', function() {
142 | this.command('a', {
143 | init: function() { }, params: ['one', 'two']
144 | }).
145 | command('b', {
146 | init: function() {
147 | bCalls += 1
148 | currentCode = this
149 | currentRule = this.currentRule
150 | currentLine = this.currentLine
151 | }
152 | })
153 | })
154 |
155 | spyOn(lang.commands.a, 'init')
156 |
157 | var code = lang.compile([1, 128, 2])
158 |
159 | expect(lang.commands.a.init).toHaveBeenCalledWith('one')
160 | expect(bCalls).toEqual(1)
161 | expect(currentCode).toBe(code)
162 | expect(currentRule).toBe(code.rules[0])
163 | expect(currentLine).toBe(code.rules[0].lines[1])
164 | })
165 |
166 | it('should add package changes', function() {
167 | var result
168 | var lang = evolu.lang.add('LNG', function() {
169 | result = this.add(function(lng) { lng.one = 1 })
170 | })
171 | expect(lang.one).toEqual(1)
172 | expect(result).toEqual(lang)
173 | })
174 | })
175 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
167 |
--------------------------------------------------------------------------------
/spec/codeSpec.js:
--------------------------------------------------------------------------------
1 | describe('evolu.lang.Code', function() {
2 | it('should return original program bytes', function() {
3 | evolu.lang.add('LNG', function() { })
4 | var code = evolu.lang.compile('EVOLU:LNG:abc')
5 | expect(code.bytes).toEqual([97, 98, 99])
6 | expect(code.toSource()).toEqual('EVOLU:LNG:abc')
7 |
8 | code.bytes = [98, 99, 100]
9 | expect(code.toSource()).toEqual('EVOLU:LNG:bcd')
10 | })
11 |
12 | it('should add new rule by DSL', function() {
13 | var lang = evolu.lang.add('LNG', function() {
14 | this.condition('a').command('b', { params: ['one', 'two'] })
15 | })
16 | var code = new evolu.lang.Code(lang)
17 | code.rule('a', ['a', 0], ['b', 'two'])
18 |
19 | expect(code.rules).toEqual([{
20 | lines: [{ command: lang.commands.a },
21 | { command: lang.commands.a, param: 0 },
22 | { command: lang.commands.b, param: 'two' }],
23 | code: code,
24 | on: evolu.lang.Rule.prototype.on,
25 | off: evolu.lang.Rule.prototype.off,
26 | run: evolu.lang.Rule.prototype.run,
27 | required: 2,
28 | id: 0
29 | }])
30 | expect(code.bytes).toEqual([1, 1, 128, 2, 129, 0])
31 | expect(code.toSource()).toEqual('EVOLU:LNG:\x01\x01\x80\x02\x81\x00')
32 | })
33 |
34 | it('should init command', function() {
35 | var result = ''
36 | var code = new evolu.lang.Code(evolu.lang.add('LNG', function() {
37 | this.command('a', {
38 | init: function(param) {
39 | result += 'a'
40 | expect(this).toBe(code)
41 | expect(this.currentRule.lines.length).toEqual(2)
42 | expect(this.currentLine.command.name).toEqual('a')
43 | expect(param).toEqual('one')
44 | }
45 | })
46 | }))
47 | code.rule(['a', 'one'], ['a', 'one'])
48 | expect(result).toEqual('aa')
49 | })
50 |
51 | it('should init condition', function() {
52 | var code = new evolu.lang.Code(evolu.lang.add('LNG', function() {
53 | this.condition('a', function() { this.inited = 1 })
54 | }))
55 |
56 | var one = code.rule(['a', 1], ['a'])
57 | var two = code.rule(['a', 1])
58 |
59 | expect(one.required).toEqual(2)
60 | expect(two.required).toEqual(1)
61 | expect(code._conditions).toEqual({ 'a 1': [one, two], 'a': [one] })
62 |
63 | expect(code.inited).toEqual(1)
64 | })
65 |
66 | it('should have condition index', function() {
67 | var code = new evolu.lang.Code(evolu.lang.add('LNG', function() {
68 | this.condition('a').condition('b')
69 | }))
70 | code.rule(['a'], ['b', 1])
71 | code.rule(['a', 2], ['b', 1])
72 |
73 | expect(code.conditions('a')).toEqual([code.rules[0]])
74 | expect(code.conditions('a', 2)).toEqual([code.rules[1]])
75 | expect(code.conditions('b', 1)).toEqual([code.rules[0], code.rules[1]])
76 | expect(code.conditions('b', 2)).toEqual([])
77 | })
78 |
79 | it('should enable/disable rule', function() {
80 | var code = new evolu.lang.Code(evolu.lang.add('LNG', function() {
81 | this.condition('if_a').condition('if_b')
82 | }))
83 | var rule = code.rule('if_a', 'if_b')
84 |
85 | expect(code._changes).toEqual([])
86 | expect(rule.required).toEqual(2)
87 |
88 | rule.on()
89 | expect(code._changes).toEqual([])
90 | expect(rule.required).toEqual(1)
91 |
92 | rule.off()
93 | expect(code._changes).toEqual([])
94 | expect(rule.required).toEqual(2)
95 |
96 | rule.on(2)
97 | rule.off(2)
98 | rule.on(2)
99 | expect(code._changes).toEqual({ 0: ['add', rule] })
100 | expect(rule.required).toEqual(0)
101 |
102 | code.run()
103 | expect(code._running).toEqual({ 0: rule })
104 | expect(code._changes).toEqual([])
105 |
106 | rule.off(2)
107 | rule.on(2)
108 | rule.off(2)
109 | expect(code._changes).toEqual({ 0: ['del', rule] })
110 | expect(code._running).toEqual({ 0: rule })
111 |
112 | code.run()
113 | expect(code._running).toEqual([])
114 | })
115 |
116 | it('should enable/disable rules by condition', function() {
117 | var code = new evolu.lang.Code(evolu.lang.add('LNG', function() {
118 | this.condition('if_a').condition('if_b')
119 | }))
120 | var rule0 = code.rule(['if_a'])
121 | var rule1 = code.rule(['if_a', 1], ['if_b', 2])
122 |
123 | var changes = ''
124 | rule0.on = rule1.on = function(count) {
125 | changes += this.id + '+' + (count || 1) + ' '
126 | }
127 | rule0.off = rule1.off = function(count) {
128 | changes += this.id + '-' + (count || 1) + ' '
129 | }
130 |
131 | code.on('if_a')
132 | code.on('if_a', 1)
133 | code.on('if_b', 2)
134 | code.off('if_a')
135 |
136 | expect(changes).toEqual('0+1 1+1 1+1 0-1 ')
137 | })
138 |
139 | it('should run rules', function() {
140 | var currentCode, currentRule, currentLine, result = ''
141 | var code = new evolu.lang.Code(evolu.lang.add('LNG', function() {
142 | this.condition('if_a').
143 | condition('if_b').
144 | command('one', function() {
145 | result += '1'
146 | currentCode = this
147 | currentRule = this.currentRule
148 | currentLine = this.currentLine
149 | }).
150 | command('two', function(param) {
151 | result += '2'
152 | expect(param).toEqual('TWO')
153 | })
154 | }))
155 | code.rule(['if_b'], ['two', 'NO'])
156 | var rule = code.rule(['if_a'], ['one'], ['two', 'TWO'])
157 |
158 | code.on('if_a')
159 | code.run()
160 |
161 | expect(result).toEqual('12')
162 | expect(currentCode).toBe(code)
163 | expect(currentRule).toBe(rule)
164 | expect(currentLine).toBe(rule.lines[1])
165 | })
166 |
167 | it('should change running list after run', function() {
168 | var result = ''
169 | var code = new evolu.lang.Code(evolu.lang.add('LNG', function() {
170 | this.condition('if_a').
171 | condition('if_b').
172 | command('one', function() {
173 | result += '1'
174 | this.off('if_b')
175 | }).
176 | command('two', function(param) {
177 | result += '2'
178 | })
179 | }))
180 | code.rule(['if_a'], ['one'])
181 | code.rule(['if_b'], ['two'])
182 |
183 | code.on('if_a')
184 | code.on('if_b')
185 | code.run()
186 | code.run()
187 |
188 | expect(result).toEqual('121')
189 | })
190 |
191 | it('should initialize code', function() {
192 | var result = ''
193 | var code = new evolu.lang.Code(evolu.lang.add('LNG', function() {
194 | this.condition('if_a').
195 | command('one', function() { result += '1' }).
196 | command('two', function() { result += '2' })
197 | }))
198 | var first = code.rule(['if_a'], ['one'])
199 | var second = code.rule(['two'])
200 | var third = code.rule(['one'], ['two'])
201 |
202 | expect(first.initializer).not.toEqual(true)
203 | expect(second.initializer).toEqual(true)
204 | expect(third.initializer).toEqual(true)
205 |
206 | code.init()
207 | expect(result).toEqual('212')
208 | })
209 |
210 | it('should allow to listen events', function() {
211 | var code = new evolu.lang.Code(evolu.lang.add('LNG', function() { }))
212 | var runned = ''
213 |
214 | code.listen('a', function(one, two) {
215 | runned += 'a'
216 | expect(one).toEqual(1)
217 | expect(two).toEqual(2)
218 | expect(this).toBe(code)
219 | })
220 | code.listen('a', function() { runned += 'A' })
221 | code.listen('b', function() { runned += 'b' })
222 |
223 | code.fire('a', [1, 2])
224 | code.fire('no')
225 |
226 | expect(runned).toEqual('aA')
227 | })
228 | })
229 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Evolu Lang
2 |
3 | Evolu Lang is a programming language to automatically generate programs by
4 | evolution (genetic programming). Generator (genetic algorithm, particle swarm
5 | optimization or other) will use Evolu Lang to compile bytes with random
6 | mutations (gene) to program, run and test it.
7 |
8 | It is created to be readable by human beings (instead of
9 | artificial neural networks) and easily editable and mixable for genetic
10 | algorithm (instead of tree structure and modern production languages).
11 |
12 | ## How It Works
13 |
14 | A developer defines commands by Evolu Lang to create a business specific
15 | language (or uses the standard commands pack) and defines tests (*fitness*),
16 | to determine what program he or she wants to create.
17 |
18 | In the next step he or she uses a generator, which uses a genetic algorithm,
19 | particle swarm optimization or other evolutionary algorithms.
20 | In the simplest case:
21 | 1. Generator creates an array (*population*) with random bytes (*genes*).
22 | 2. It adds random changes (*mutation*) to each byte stream in this array.
23 | 3. It compiles each of these random byte streams by Evolu Lang and runs
24 | obtained programs with tests.
25 | 4. Bad programs will be deleted and best programs will be copied to the
26 | population.
27 | 5. Generator returns to step 2 until an obtained program passes all of the
28 | tests.
29 |
30 | ## Features
31 |
32 | * It is similar to usual programming languages with variables, commands, blocks
33 | and conditions.
34 | * Simple and explicit code. If you change one byte of code, you will change one
35 | command or parameter in program. If you just join two half parts of two
36 | different programs, you will get algorithm with properties of both parts.
37 | * Program is coded to a byte stream, so you can use a lot of libraries to mutate
38 | programs. Of course, you can use the string form for debug and research.
39 | * You are able to extend standard commands and conditions for the purposes of
40 | your task.
41 | * It has an interpreter in JavaScript, so you can create a distributed cluster
42 | from site visitors with a simple web page.
43 |
44 | ## Language Philosophy
45 |
46 | * **Explicit code.** To control mutation, we must know, that when we change one
47 | byte, the algorithm will change slightly. When we copy a part of one algorithm
48 | to another, we expect, that the second algorithm will get some properties from
49 | the first one.
50 | * **Everything makes sense.** A mutation doesn’t know about syntax and formats.
51 | Interpreter must try to get maximum sense, from any byte stream. For example,
52 | if a byte can code 2 values, we must read even bytes as first value and odd
53 | values as second. So any byte value makes sense, not just the first two.
54 | * **Simple structures.** We can’t demand on the mutation placing all conditions
55 | in the beginning of a block. A better way is to mark conditions and expect
56 | them in any place of a block.
57 |
58 | ## Description
59 |
60 | ### Program
61 |
62 | Each Evolu program starts with an `EVOLU:` prefix to check, that the file or
63 | stream contains a program.
64 |
65 | Like XML, Evolu Lang is just a syntax format. So you need to have
66 | business-specific languages and mark, what language is used in this Evolu
67 | program. So, after the `EVOLU:` prefix, stream must contain language name and a
68 | colon.
69 |
70 | ::= "EVOLU:" ":"
71 |
72 | Language name is case insensitive and may contain any chars, except colon and
73 | space.
74 |
75 | The genetic algorithm shouldn’t change these prefixes, they should be used only
76 | to store and transfer Evolu programs.
77 |
78 | ### Rules
79 |
80 | An Evolu program is split to separated blocks, *rules*, by *separator*.
81 | The separator is a built-in command and may be coded in different bytes
82 | (depending on command count, see “Commands and Parameters” section below).
83 | But in any languages `0x00` byte is a separator.
84 |
85 | ::= ( )*
86 | ::= 0x00 |
87 |
88 | ### Commands and Parameters
89 |
90 | A rule contains pairs of *commands* and an optional *parameter*. Command byte
91 | begins with `0` bit and command number is encoded by next 7 bits. Any other
92 | bytes (beginning with `1`) after command encode parameter number. For example,
93 | 2 bytes `1aaaaaaa` and `1bbbbbbb` encode parameter with `aaaaaaabbbbbbb` value.
94 |
95 | ::= ( ( )? )*
96 | ::= 0xxxxxxx
97 | ::= ( 1xxxxxxx )*
98 |
99 | There are 127 different commands number in one command byte, but language may
100 | have less commands. A mutation can generate any bytes and Evolu Lang must try to
101 | decode any of them. So, commands are marked numbers in a circle: if language
102 | have 3 commands (`separator`, `a`, `b`), 0 will be encode `separator`, 1 – `a`,
103 | 2 – `b`, but 3 will encode `separator` again, 4 – `a`, etc.
104 |
105 | In language description commands may specify format of it’s parameter.
106 | Parameters can be unsigned integers (simple encoded by bits in parameter bytes)
107 | or list of values (encode in cycle, like commands).
108 |
109 | ### Conditions
110 |
111 | There is special command type – *condition*. If all conditions in a rule are
112 | true, the rule’s commands will execute.
113 |
114 | If a rule doesn’t have any conditions it will run once at start as constructor.
115 |
116 | ### Standard Commands Pack
117 |
118 | You can create your own language with Evolu Lang, but for common tasks it has
119 | the standard commands pack to create Turing completeness languages.
120 |
121 | Conditions:
122 |
123 | * `if_signal` will be true, when program receives input signal (its name will
124 | be taken from parameter). If the rule contains several these conditions with
125 | different signals, all `if_signal` conditions will be true by any of these
126 | signals (because, program may receive only one signal at a moment).
127 | * `if_var_more_0` will be true if variable (its name will be taken from
128 | condition parameter) will be more, than zero.
129 |
130 | Commands:
131 |
132 | * `send_signal` will send output signal (its name will be taken from parameter).
133 | * `var_up` will increase variable from parameter.
134 | * `var_down` will decrease variable from parameter.
135 |
136 | The developer must define, what input and output signals will be in the
137 | language, but variables can be added dynamically by mutation.
138 |
139 | ## How To
140 |
141 | For example, we will generate program (by genetic programming), which calculates
142 | `tick` signals and on `result` signal it sends whether an `even` or an `odd`
143 | tick count it received.
144 |
145 | ### Language
146 |
147 | Like XML, Evolu Lang is just a syntax format. So you need to define a language
148 | for your task using the `evolu.lang.add(name, initializer)` function.
149 | It receives a language name (to use it as a prefix in the source code for
150 | storing and transferring the program) and function (which adds the language
151 | commands to `this`), and returns a new language.
152 |
153 | For the common cases you can use the standard commands pack, and you only need
154 | to define the input/output signals.
155 |
156 | var lang = evolu.lang.add('EVEN-ODD', function() {
157 | this.add(evolu.lang.standard.input('tick', 'result'))
158 | this.add(evolu.lang.standard.output('even', 'odd'))
159 | lang.add(evolu.lang.standard.variables)
160 | })
161 |
162 | ### Population
163 |
164 | Get any genetic algorithm library or write it by yourself. Use a byte array
165 | (array of integers from `0` to `255`, for example `[0, 255, 13, 68, 145]`) as
166 | genes.
167 |
168 | var population = []
169 | // Add 100 genes to the first population
170 | for (var i = 0; i < 100; i++) {
171 | var gene = []
172 | // Each gene will have random length
173 | while (Math.random < 0.9) {
174 | // Add a random byte to the current gene
175 | gene.push(Math.round(255 * Math.random()))
176 | }
177 | }
178 |
179 | ### Mutation
180 |
181 | *Note that the integers in an array must be from `0` to `255`.*
182 |
183 | In the genetic algorithm you can use any types of mutation for a byte stream
184 | (a lot of libraries contain them). You can add, change, delete and
185 | move bytes in the array.
186 |
187 | You can use crossover to mix arrays or just move a part of bytes from one array
188 | to another (like horizontal gene transfer).
189 |
190 | ### Selection
191 |
192 | To calculate fitness for each gene in the population, you need to compile
193 | each byte array:
194 |
195 | var program = lang.compile(population[i])
196 |
197 | Send the data to the program and check its output data to calculate fitness.
198 | It’s like automatic unit testing, but your test must return a score,
199 | not just a pass/fail result.
200 |
201 | If you use the standard commands pack, you can use the `receive_signal` event
202 | to listen output signals and the `signal` function to send input signals:
203 |
204 | output = []
205 | program.listen('receive_signal', function(signal) {
206 | output.push(signal)
207 | })
208 |
209 | program.signal('tick').signal('tick').signal('result')
210 | // Some hypothetical API
211 | check(output).to_contain('even')
212 |
213 | output = []
214 | program.signal('tick').signal('result')
215 | check(output).to_contain('odd')
216 |
217 | ### Saving
218 |
219 | When you generate a program for your demands, you can save it to a disk or send
220 | to a server:
221 |
222 | var source = bestProgram.toSource()
223 |
224 | Source is a string with `EVOLU:` and a language name in prefix. For example,
225 | `"EVOLU:EVEN-ODD:\x04\x80\x00\x01\x80\x03\x80\x05…"`.
226 |
227 | Use `evolu.lang.compile(string)` to automatically find a language (using the source
228 | prefix) and compile the bytes into a program:
229 |
230 | bestProgram == evolu.lang.compile(bestProgram.toSource())
231 |
232 | ## Testing
233 |
234 | 1. Install Rake (Ruby make) and RubyGems (Ruby package manager).
235 | For example, on Ubuntu:
236 |
237 | sudo apt-get install rake rubygems
238 |
239 | 2. Install `jasmin` gem:
240 |
241 | gem install jasmin
242 |
243 | 3. Run test server:
244 |
245 | rake jamsin
246 |
247 | 4. Open .
248 |
249 | ## License
250 |
251 | Evolu Lang is licensed under the GNU Lesser General Public License version 3.
252 | See the LICENSE file or http://www.gnu.org/licenses/lgpl.html.
253 |
--------------------------------------------------------------------------------
/lib/evolu-lang.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 Andrey “A.I.” Sitnik
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Lesser General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Lesser General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU Lesser General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | ;(function() {
19 |
20 | if ('undefined' == typeof(evolu)) evolu = { }
21 |
22 | evolu.lang = {
23 |
24 | version: '0.1',
25 |
26 | /** Hash of language names (in lower case) to language objects. */
27 | _languages: { },
28 |
29 | /**
30 | * Create/recreate Evolu language with _name_: create new
31 | * `evolu.lang.Language` instance and call `initializer` on it and put
32 | * to available languages list.
33 | *
34 | * Use `evolu.lang.compile` to compile programs by your language or use
35 | * `evolu.lang.get` to get language object.
36 | *
37 | * evolu.lang.add('LNG', function() {
38 | * this.condition('if_receive_signal')
39 | * this.command('send_signal', function() { … })
40 | * })
41 | *
42 | * @param name {String} Evolu language name
43 | * @param initializer {Function} will be receive new language as first
44 | * argument. Just syntax sugar to create language.
45 | * return {evolu.lang.Language} new Evolu language
46 | */
47 | add: function(name, initializer) {
48 | var lower = name.toLowerCase()
49 | var lang = new this.Language(name)
50 | initializer.call(lang)
51 | this._languages[lower] = lang
52 | return lang
53 | },
54 |
55 | /**
56 | * Return Evolu language with `name`, which was be added by
57 | * `evolu.lang.add(name, initializer)`.
58 | *
59 | * var lang = evolu.lang.get('LNG')
60 | *
61 | * @param name {String} Evolu language name. Search will be case
62 | insensitive
63 | * return {evolu.lang.Language} Evolu language, with `name`
64 | */
65 | get: function(name) {
66 | var lower = name.toLowerCase()
67 | return this._languages[lower]
68 | },
69 |
70 | /**
71 | * Compile Evolu program to `evolu.lang.Code` object. `program` must be
72 | * a string, started by “EVOLU:lang:”, where “lang” is a language name.
73 | *
74 | * evolu.lang.add('LNG', function() { … })
75 | *
76 | * var code = evolu.lang.compile('EVOLU:LNG:123')
77 | *
78 | * @param name {String} full version of Evolu Lang program with
79 | * prefixes
80 | * return {evolu.lang.Code} compiled program as object
81 | */
82 | compile: function(program) {
83 | var blocks = program.split(':', 3)
84 | if ('EVOLU' != blocks[0])
85 | throw "It isn't Evolu program, because it hasn't EVOLU " +
86 | "mark at beginning."
87 |
88 | var lang = this.get(blocks[1])
89 | if (!lang) throw 'Unknown Evolu language `' + blocks[1] + '`'
90 |
91 | var bytes = [], string = blocks[2]
92 | for (var i = 0; i < string.length; i++)
93 | bytes.push(string.charCodeAt(i))
94 |
95 | return lang.compile(bytes)
96 | }
97 |
98 | }
99 |
100 | /**
101 | * Sublanguage of Evolu Lang.
102 | *
103 | * As XML, Evolu Lang is a just syntax format. You can set your own commands
104 | * and condition based on your task.
105 | *
106 | * For common tasks Evolu Lang has Turing completeness standard commands
107 | * pack with variables (increase, decrease and check for zero) and
108 | * input/output signals. See `evolu.lang.standard` object.
109 | *
110 | * @param name {String} new language name
111 | * @constructor
112 | */
113 | evolu.lang.Language = function(name) {
114 | this.name = name
115 | this._list = [this._separator]
116 | this.commands = { separator: this._separator }
117 | }
118 | evolu.lang.Language.prototype = {
119 |
120 | /** Language name. */
121 | name: '',
122 |
123 | /** Hash name to language commands. */
124 | commands: { },
125 |
126 | /** Build-in separator command. */
127 | _separator: { name: 'separator', separator: true },
128 |
129 | /** Array of language commands to convert number to commands. */
130 | _list: [],
131 |
132 | /**
133 | * Execute `package` with this language instance in first argument.
134 | * It is short way to use several language changed (commands, code
135 | * properties) as library.
136 | *
137 | * send_signal = function(lang) {
138 | * lang.command('send_signal', { … })
139 | * }
140 | *
141 | * evolu.lang.add('LNG', function() { this.add(send_signal) })
142 | *
143 | * @param package {Function} function, that add some changes to language
144 | * @return {evolu.lang.Language} current language
145 | */
146 | add: function(package) {
147 | package(this)
148 | return this
149 | },
150 |
151 | /**
152 | * Add command `name` to language.
153 | *
154 | * Command could has properties:
155 | * * install: function, which will be run after compile new program to
156 | * add methods and variables to it.
157 | * * init: function(param), which will be run, when parser will read
158 | * next rule with this command.
159 | * * run: function(param), which will be run, when this rule with
160 | * this command will be execute.
161 | * * params: array of supported command parameters.
162 | *
163 | * In call functions `this` will be current `evolu.lang.Code` instance.
164 | * In `run` and `init` functions you can get current rule and current
165 | * rule line by `this.currentRule` and `this.currentLine` properties.
166 | *
167 | * If you want to set only `run`, you can set function as `command`.
168 | *
169 | * WARNING: command and condition order is important, different order is
170 | * different language. You may set `position` in command object to
171 | * specify specify command number.
172 | *
173 | * Note, that first command is build-in rule separator.
174 | *
175 | * @param name {String} new command name
176 | * @param name {Object} new command properties
177 | * @return {evolu.lang.Language} current language
178 | */
179 | command: function(name, command) {
180 | if ('function' == typeof(command))
181 | command = { run: command }
182 |
183 | if (undefined == command) command = { }
184 | command.name = name
185 |
186 | this.commands[name] = command
187 | if (undefined != command.position) {
188 | var pos = command.position
189 | delete command.position
190 | this._list.splice(pos, 0, command)
191 | } else {
192 | this._list.push(command)
193 | }
194 |
195 | return this
196 | },
197 |
198 | /**
199 | * Add condition-command `if_postfix` to language. Condition is regular
200 | * command, with `condition: true` in object and special `init` (which
201 | * add rule to index).
202 | *
203 | * Expect of `command` this method will set function in second argument
204 | * as `nit` property.
205 | *
206 | * WARNING: command and condition order is important, different order is
207 | * different language. You may set `position` in command object to
208 | * specify specify command number.
209 | *
210 | * @param name {String} new condition name
211 | * @param name {Object} new command (condition is a type of command)
212 | * properties
213 | * @return {evolu.lang.Language} current language
214 | */
215 | condition: function(name, condition) {
216 | if ('function' == typeof(condition))
217 | condition = { init: condition }
218 | if (undefined == condition) condition = { }
219 | condition.condition = true
220 |
221 | if (condition.init) {
222 | var current = condition.init
223 | var glob = this._initCondition
224 | condition.init = function() {
225 | glob.apply(this, arguments)
226 | current.apply(this, arguments)
227 | }
228 | } else {
229 | condition.init = this._initCondition
230 | }
231 |
232 | return this.command(name, condition)
233 | },
234 |
235 | /**
236 | * Compile array of `bytes` number of this language commands and
237 | * parameters to `evolu.lang.Code` object.
238 | *
239 | * It is a bit faster, than `evolu.lang.compile`, so it’s useful in
240 | * genetic programming cycle (or in another case, when you already know
241 | * language and work with array of numbers).
242 | *
243 | * var bytes = mutate(bestCode.bytes)
244 | * var newCode = lang.compile(bytes)
245 | *
246 | * @param bytes {Array of Numbers} bytes (numbers between 0 and 255) of
247 | * Evolu Lang program source
248 | * @return {evolu.lang.Code} compiled program
249 | */
250 | compile: function(bytes) {
251 | var code = new evolu.lang.Code(this)
252 | var count = this._list.length
253 |
254 | code.bytes = bytes
255 | bytes = bytes.concat([0, 0]) // Add separators to end last rule
256 |
257 | var param, command, lines = []
258 | for (var i = 0; i < bytes.length; i++) {
259 | var byte = bytes[i]
260 | if (128 <= byte) {
261 | byte -= 128
262 | param = (undefined == param ? byte : param * 128 + byte)
263 | } else {
264 | if (undefined != command) {
265 | command = this._list[command % count]
266 | if (this._separator == command) {
267 | code._add(lines)
268 | lines = []
269 | } else {
270 | if (undefined != param) {
271 | if (command.params) {
272 | param = command.params[
273 | param % command.params.length]
274 | }
275 | lines.push({ command: command, param: param })
276 | } else {
277 | lines.push({ command: command })
278 | }
279 | }
280 | }
281 | param = undefined
282 | command = byte
283 | }
284 | }
285 |
286 | return code
287 | },
288 |
289 | /** Command `init` property for all conditions. */
290 | _initCondition: function(param) {
291 | this.currentRule.required += 1
292 |
293 | var index = this._index(this.currentLine.command.name, param)
294 | var condition = this._conditions[index]
295 | if (condition) {
296 | condition.push(this.currentRule)
297 | } else {
298 | this._conditions[index] = [this.currentRule]
299 | }
300 | }
301 |
302 | }
303 |
304 | /**
305 | * Program on Evolu language.
306 | *
307 | * @param language {evolu.lang.Language} Evolu language object of this
308 | * program
309 | * @constructor
310 | */
311 | evolu.lang.Code = function(language) {
312 | this.language = language
313 | this.rules = []
314 | this._initializers = []
315 | this._conditions = { }
316 | this._running = { }
317 | this._changes = { }
318 | this._listeners = { }
319 |
320 | var commands = this.language.commands
321 | for (name in commands)
322 | if (commands[name].install) commands[name].install.call(this)
323 | }
324 | evolu.lang.Code.prototype = {
325 |
326 | /** Code language object. */
327 | language: undefined,
328 |
329 | /** Original bytes of program. */
330 | bytes: undefined,
331 |
332 | /** Program rules. */
333 | rules: [],
334 |
335 | /** Rules without conditions to run as constructor. */
336 | _initializers: [],
337 |
338 | /** Condition to rule index. */
339 | _conditions: { },
340 |
341 | /** List of rules with all completed conditions. */
342 | _running: { },
343 |
344 | /**
345 | * Hash of rule ID to `['add', rule]` or `['del', rule]` to change
346 | * `_running` list on next `run` call.
347 | */
348 | _changes: { },
349 |
350 | /** Hash of event name to array of listeners. */
351 | _listeners: { },
352 |
353 | /**
354 | * Add new rule with list of command name or `[command, param]` in
355 | * arguments.
356 | *
357 | * code.rule('if_variable_is_zero', ['send_signal', 'empty'])
358 | *
359 | * @return {evolu.lang.Rule} new rule
360 | */
361 | rule: function() {
362 | if (undefined == this.bytes) this.bytes = []
363 |
364 | var command, param, j, lines = []
365 | for (var i = 0; i < arguments.length; i++) {
366 | if ('string' == typeof arguments[i]) {
367 | command = arguments[i]
368 | param = undefined
369 | } else {
370 | command = arguments[i][0]
371 | param = arguments[i][1]
372 | }
373 |
374 | for (j = 0; j < this.language._list.length; j++) {
375 | if (this.language._list[j].name == command) {
376 | command = this.language._list[j]
377 | break
378 | }
379 | }
380 | this.bytes.push(j)
381 |
382 | if (undefined == param) {
383 | lines.push({ command: command })
384 | } else {
385 | lines.push({ command: command, param: param })
386 | if (command.params) {
387 | for (j = 0; j < command.params.length; j++) {
388 | if (command.params[j] == param) {
389 | param = j
390 | break
391 | }
392 | }
393 | }
394 | this.bytes.push(128 + param)
395 | }
396 | }
397 | this.bytes.push(0)
398 |
399 | return this._add(lines)
400 | },
401 |
402 | /**
403 | * Register `callback` for `event`.
404 | *
405 | * code.listen('var_changed', function(name, value) {
406 | * console.log('Change ' + name + ' to ' + value)
407 | * })
408 | *
409 | * @param event {String} event name
410 | * @param callback {Function} event callback
411 | */
412 | listen: function(event, callback) {
413 | var list = this._listeners[event]
414 | if (!list) list = this._listeners[event] = []
415 | list.push(callback)
416 | },
417 |
418 | /**
419 | * Call with `params` all listeners for `event`.
420 | *
421 | * code.fire('var_changed', ['a', 1])
422 | *
423 | * @param event {String} event name
424 | * @param params {Array} arguments for event callbacks
425 | */
426 | fire: function(event, params) {
427 | var list = this._listeners[event]
428 | if (list) {
429 | for (var i = 0; i < list.length; i++)
430 | list[i].apply(this, params)
431 | }
432 | },
433 |
434 | /**
435 | * Initialize program. Run all rules without conditions. You should add
436 | * any listeners before use it and take any actions with code (send
437 | * signals, change conditions) only after call `init`.
438 | */
439 | init: function() {
440 | for (var i = 0; i < this._initializers.length; i++)
441 | this._initializers[i].run()
442 | },
443 |
444 | /**
445 | * Mark `condition` with this name and `param` as completed and insert
446 | * all necessary rules to running list.
447 | *
448 | * For example, when variable “a” is set to zero:
449 | *
450 | * code.on('if_variable_is_zero', 'a')
451 | *
452 | * All rules have own `on` method:
453 | *
454 | * code.rules[0].on()
455 | *
456 | * @param condition {String} condition name
457 | * @param param {String} condition parameter (on program code)
458 | * @return {Array of evolu.lang.Rule} rules with this condition and
459 | * parameter
460 | */
461 | on: function(condition, param) {
462 | var rules = this.conditions(condition, param)
463 | for (var i = 0; i < rules.length; i++) rules[i].on()
464 | return rules
465 | },
466 |
467 | /**
468 | * Mark `condition` with this name and `param` as uncompleted and
469 | * remove all unnecessary rules from running list.
470 | *
471 | * For example, when variable “a” is set to non-zero value:
472 | *
473 | * code.off('if_variable_is_zero', 'a')
474 | *
475 | * All rules have own `off` method:
476 | *
477 | * code.rules[0].off()
478 | *
479 | * @param condition {String} condition name
480 | * @param param {String} condition parameter (on program code)
481 | * @return {Array of evolu.lang.Rule} rules with this condition and
482 | * parameter
483 | */
484 | off: function(condition, param) {
485 | var rules = this.conditions(condition, param)
486 | for (var i = 0; i < rules.length; i++) rules[i].off()
487 | return rules
488 | },
489 |
490 | /**
491 | * Run all rules from running list (with only completed conditions).
492 | * This method take only one step: all rules, which will be enabled by
493 | * running rules, will be execute on next `run` call.
494 | *
495 | * For example, when code receive signal “get_result”:
496 | *
497 | * code.on('if_receive_signal', 'get_result')
498 | * code.run()
499 | * code.off('if_receive_signal', 'get_result')
500 | */
501 | run: function() {
502 | for (id in this._changes) {
503 | if ('add' == this._changes[id][0]) {
504 | this._running[id] = this._changes[id][1]
505 | } else {
506 | delete this._running[id]
507 | }
508 | }
509 | this._changes = { }
510 |
511 | for (var id in this._running) this._running[id].run()
512 | },
513 |
514 | /**
515 | * Return all rules with this `condition` name and `param`.
516 | *
517 | * @param condition {String} condition name
518 | * @param param {String} condition parameter (on program code)
519 | * @return {Array of evolu.lang.Rule} rules with this condition and
520 | * parameter
521 | */
522 | conditions: function(condition, param) {
523 | return this._conditions[this._index(condition, param)] || []
524 | },
525 |
526 | /**
527 | * Return original string representation of program bytes with
528 | * Evolu Lang and language marks at beginning.
529 | *
530 | * code = evolu.lang.compile('EVOLU:LNG:123')
531 | * code.toSource() // => "EVOLU:LNG:123"
532 | *
533 | * @return {String} program source code
534 | */
535 | toSource: function() {
536 | return 'EVOLU:' + this.language.name + ':' +
537 | String.fromCharCode.apply(this, this.bytes)
538 | },
539 |
540 | /** Add new rule with array `lines` with `[command, param]`. */
541 | _add: function(lines) {
542 | var rule = new evolu.lang.Rule(this, lines)
543 | this.rules.push(rule)
544 |
545 | var command, initializer = true
546 | for (var i = 0; i < lines.length; i++) {
547 | command = lines[i].command
548 | if (command.condition) initializer = false
549 | if (command.init) {
550 | this.currentRule = rule
551 | this.currentLine = lines[i]
552 | command.init.call(this, lines[i].param)
553 | }
554 | }
555 | if (initializer) {
556 | rule.initializer = true
557 | this._initializers.push(rule)
558 | }
559 |
560 | return rule
561 | },
562 |
563 | /**
564 | * Return index name for `condition` name and param to use in
565 | * `_conditions`.
566 | */
567 | _index: function(condition, param) {
568 | if (undefined == param) {
569 | return condition
570 | } else {
571 | return condition + ' ' + param
572 | }
573 | }
574 |
575 | }
576 |
577 | /** Separated part of code’s commands. */
578 | evolu.lang.Rule = function(code, lines) {
579 | this.code = code
580 | this.id = code.rules.length
581 | this.lines = lines
582 | }
583 | evolu.lang.Rule.prototype = {
584 |
585 | /** Rule owner instance of `evolu.lang.Code`. */
586 | code: undefined,
587 |
588 | /** Array of rule’s lines of commands and params. */
589 | lines: [],
590 |
591 | /** Rule ID, that is unique in code. */
592 | id: undefined,
593 |
594 | /** Count of uncompleted conditions. */
595 | required: 0,
596 |
597 | /**
598 | * Set all `currentRule` and `currentLine` property to code and run all
599 | * commands in rule.
600 | */
601 | run: function() {
602 | this.code.currentRule = this
603 | var line
604 | for (var i = 0; i < this.lines.length; i++) {
605 | line = this.code.currentLine = this.lines[i]
606 | if (line.command.run)
607 | line.command.run.call(this.code, line.param)
608 | }
609 | },
610 |
611 | /**
612 | * Make `count` conditions as completed. If all rule’s conditions are
613 | * completed, rule will be execute on next `run` call.
614 | *
615 | * @param {Number} optional completed conditions number (default is 1)
616 | * @return {Number} count of uncompleted conditions
617 | */
618 | on: function(count) {
619 | this.required -= (count || 1)
620 | if (0 == this.required) this.code._changes[this.id] = ['add', this]
621 | return this.required
622 | },
623 |
624 | /**
625 | * Make `count` conditions as uncompleted and remove rule from
626 | * executing.
627 | *
628 | * @param {Number} optional completed conditions number (default is 1)
629 | * @return {Number} count of uncompleted conditions
630 | */
631 | off: function(count) {
632 | if (0 == this.required) this.code._changes[this.id] = ['del', this]
633 | this.required += (count || 1)
634 | return this.required
635 | }
636 |
637 | }
638 |
639 | /** Standard command pack for Evolu Lang. */
640 | evolu.lang.standard = { }
641 |
642 | /**
643 | * Add commands to increase/decrease variables and condition, that variable
644 | * is more than zero.
645 | *
646 | * evolu.lang.add('LNG', function() {
647 | * lang.add(evolu.lang.standard.variables)
648 | * })
649 | *
650 | * On changing variable, event `var_changed` will be send with variable
651 | * name, new variable value and change value.
652 | *
653 | * code.listen('var_changed', function(name, value, diff) {
654 | * console.log('Change ' + name +
655 | * ' from ' + (value + diff) + ' to ' + value)
656 | * })
657 | */
658 | evolu.lang.standard.variables = function(lang) {
659 | lang.add(evolu.lang.standard.variables.moreZero)
660 | lang.add(evolu.lang.standard.variables.increase)
661 | lang.add(evolu.lang.standard.variables.decrease)
662 | }
663 |
664 | /** Create hash with variables. */
665 | evolu.lang.standard.variables._install = function() {
666 | this._variables = { }
667 | }
668 |
669 | /** Initialize variable to zero. */
670 | evolu.lang.standard.variables._init = function(variable) {
671 | if (undefined != variable)
672 | this._variables[variable] = 0
673 | }
674 |
675 | /** Condition, that variable is more, that zero. */
676 | evolu.lang.standard.variables.moreZero = function(lang) {
677 | lang.condition('if_var_more_0', {
678 | install: function() {
679 | this.listen('var_changed', function(variable, value, diff) {
680 | if (0 == value && -1 == diff) {
681 | this.off('if_var_more_0', variable)
682 | } else if (1 == value && 1 == diff) {
683 | this.on('if_var_more_0', variable)
684 | }
685 | })
686 | }
687 | })
688 | }
689 |
690 | /** Command to increase variable, which number will be in line parameter. */
691 | evolu.lang.standard.variables.increase = function(lang) {
692 | lang.command('var_up', {
693 | install: evolu.lang.standard.variables._install,
694 | init: evolu.lang.standard.variables._init,
695 | run: function(variable) {
696 | if (undefined == variable) return
697 |
698 | var value = this._variables[variable] + 1
699 | this._variables[variable] = value
700 |
701 | this.fire('var_changed', [variable, value, +1])
702 | }
703 | })
704 | }
705 |
706 | /** Command to decrease variable, which number will be in line parameter. */
707 | evolu.lang.standard.variables.decrease = function(lang) {
708 | lang.command('var_down', {
709 | install: evolu.lang.standard.variables._install,
710 | init: evolu.lang.standard.variables._init,
711 | run: function(variable) {
712 | if (undefined == variable) return
713 |
714 | var value = this._variables[variable] - 1
715 | this._variables[variable] = value
716 |
717 | this.fire('var_changed', [variable, value, -1])
718 | }
719 | })
720 | }
721 |
722 | /**
723 | * Allow program to get information from outside: add conditions to
724 | * check input signals and method `signal(name)` to send input signal.
725 | *
726 | * You must set supported signals for you task:
727 | *
728 | * evolu.lang.add('LNG', function() {
729 | * this.add(evolu.lang.standard.input('tick', 'result'))
730 | * })
731 | * code = evolu.lang.compile('EVOLU:LNG:…')
732 | * code.init()
733 | * code.signal('tick').signal('result')
734 | *
735 | * Method `signal` execute `run` once. So, if you want to execute all
736 | * rules, which are enabled by commands in rule with signal condition,
737 | * you must call `run` again.
738 | *
739 | * If rule contain conditions for “A” and “B” signals, both conditions
740 | * will be enabled on signal “A” OR “B” (because program can’t receive
741 | * several signal at one moment).
742 | */
743 | evolu.lang.standard.input = function() {
744 | var signals = arguments
745 | return function(lang) {
746 | lang.condition('if_signal', {
747 | params: signals,
748 | init: function() {
749 | var rule = this.currentRule
750 | if (undefined == rule.signal_conditions) {
751 | rule.signal_conditions = 1
752 | } else {
753 | rule.signal_conditions += 1
754 | }
755 | },
756 | install: function() {
757 | this.signal = function(signal) {
758 | var rules = this.conditions('if_signal', signal)
759 | for (var i = 0; i < rules.length; i++)
760 | rules[i].on(rules[i].signal_conditions)
761 | this.run()
762 | for (var i = 0; i < rules.length; i++)
763 | rules[i].off(rules[i].signal_conditions)
764 | return this
765 | }
766 | }
767 | })
768 | }
769 | },
770 |
771 | /**
772 | * Allow program to send information to outside: add command to send
773 | * output signals and event `receive_signal` to catch it from you code.
774 | *
775 | * You must set supported signals for you task:
776 | *
777 | * evolu.lang.add('LNG', function() {
778 | * this.add(evolu.lang.standard.output('odd', 'even'))
779 | * })
780 | * code = evolu.lang.compile('EVOLU:LNG:…')
781 | * code.listen('send_signal', function(signal) {
782 | * // Print “odd” or “even”.
783 | * console.log('Count is ' + signal)
784 | * })
785 | */
786 | evolu.lang.standard.output = function() {
787 | var signals = arguments
788 | return function(lang) {
789 | lang.command('send_signal', {
790 | params: signals,
791 | run: function(signal) {
792 | if (undefined == signal) return
793 | this.fire('receive_signal', [signal])
794 | }
795 | })
796 | }
797 | }
798 | })();
799 |
--------------------------------------------------------------------------------