├── 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 | --------------------------------------------------------------------------------