matrix_dsl 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jasim A Basheer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,52 @@
1
+ Matrix Transformations
2
+ =================
3
+
4
+ Simple DSL for transforming matrices using notation similar to those given in Maths texts. Uses treetop for language parsing.
5
+
6
+ You can execute script similar to:
7
+
8
+ M1 = [ [ 1 2 3 ]
9
+ [ 4 9 6 ]
10
+ [ 9 1 5 ]
11
+ ]
12
+
13
+ Transform M1:
14
+ R2 <- 2R2 + R3
15
+ C3 <- C3 - C1
16
+
17
+ det_m1 = det M1
18
+
19
+ x = 3
20
+ M2 = ((3/4) * x) * M1
21
+
22
+ det_m2 = det M2
23
+ rank_m2 = rank M2
24
+
25
+
26
+ Features:
27
+ ---------
28
+
29
+ * No control structures. This would be a minimal DSL than a turing-complete language.
30
+
31
+ * Variables, Expressions and Arithmetic evaluation.
32
+ * Row/Column Matrix transformations
33
+ * Matrix functions - inverse, determinant, rank and transpose.
34
+ * A few arithmetic functions (from Ruby Math module)
35
+
36
+ Usage
37
+ ==========
38
+
39
+ gem install matrix_dsl
40
+ matrix_dsl_run < curl http://github.com/jasim/matrix_transformations/blob/master/example.matrix_dsl
41
+
42
+ To integrate Matrix DSL scripts into your code, please have a look at http://github.com/jasim/matrix_transformations/blob/master/tests/basic_test.rb
43
+
44
+ Questions/Feedback
45
+ ------------------
46
+ I'd appreciate any! Feel free to reach me at jasim.ab@gmail.com.
47
+
48
+
49
+ Todo
50
+ -----
51
+ * Allow comments in script
52
+ * Enable fractions in place of floats.
@@ -0,0 +1,11 @@
1
+
2
+ x=33
3
+ m1=[ [10 23]
4
+ [45 33]
5
+ ]
6
+
7
+ d_m1 = det m1
8
+ rank_m1 = rank m1
9
+
10
+ m2 = m1 * x
11
+ d_m2 = det m2
data/lib/matrix_dsl.rb ADDED
@@ -0,0 +1,178 @@
1
+ require 'treetop'
2
+ require 'matrix'
3
+
4
+ module MatrixDSL
5
+
6
+ # In the given 'matrix', sets the value of the 'row_col_identifier' (R1, C1 etc.) to the array 'value'
7
+ # ie: in M=Matrix[[1 2][2 4]], apply_row_col_transformation(M, "C1", [9 8]) would give M=Matrix[[9 2][8 4]]
8
+ def apply_row_col_transformation(matrix, row_col_identifier, value)
9
+ m=matrix.to_a
10
+ if row_col_identifier.row_or_col=='R'
11
+ # Row transformation. Apply the result to the resultant row.
12
+ m[row_col_identifier.index] = value.to_a
13
+ else
14
+ # Column transformation.
15
+ m=m.transpose
16
+ m[row_col_identifier.index] = value.to_a
17
+ m=m.transpose
18
+ end
19
+
20
+ Matrix[*m]
21
+ end
22
+
23
+ # stores all identifier variables used inside the DSL-based script.
24
+ $matrix_dsl_vars ||= {}
25
+
26
+ class NodeIdentifier < Treetop::Runtime::SyntaxNode
27
+ def name
28
+ text_value
29
+ end
30
+ def value
31
+ raise "Variable #{name} not declared." unless $matrix_dsl_vars.include? text_value
32
+ $matrix_dsl_vars[text_value]
33
+ end
34
+ end
35
+
36
+ # Used where a Matrix variable is expected as the identifier. (ie: in 'Transform M1', M1 should be a matrix variable.)
37
+ class MatrixIdentifier < Treetop::Runtime::SyntaxNode
38
+ def name
39
+ text_value
40
+ end
41
+ def value
42
+ raise "Variable #{name} not declared." unless $matrix_dsl_vars.include? text_value
43
+ raise "Variable #{name} not a Matrix." unless $matrix_dsl_vars[text_value].class == Matrix
44
+
45
+ $matrix_dsl_vars[text_value]
46
+ end
47
+ end
48
+
49
+ # Data types
50
+ class NodeNumber < Treetop::Runtime::SyntaxNode
51
+ def value
52
+ text_value.to_f
53
+ end
54
+ end
55
+
56
+ class NodeMatrix < Treetop::Runtime::SyntaxNode
57
+ def value
58
+ Matrix[*matrix_rows.elements.collect {|row| row.value}]
59
+ end
60
+ end
61
+
62
+ class NodeMatrixRow < Treetop::Runtime::SyntaxNode
63
+ def value
64
+ matrix_cols.elements.collect {|col| col.value}
65
+ end
66
+ end
67
+
68
+ class NodeMatrixElement < Treetop::Runtime::SyntaxNode
69
+ def value
70
+ node_number.value
71
+ end
72
+ end
73
+
74
+ # Common Infra
75
+ class BinaryOperator < Treetop::Runtime::SyntaxNode
76
+ def operation
77
+ text_value
78
+ end
79
+ end
80
+
81
+ class ParenExpression < Treetop::Runtime::SyntaxNode
82
+ def value
83
+ expression.value
84
+ end
85
+ end
86
+
87
+ # Arithmetic operations and expressions (outside the matrix transformation block)
88
+ class OperationAssignment < Treetop::Runtime::SyntaxNode
89
+ def execute
90
+ $matrix_dsl_vars[lvalue.name] = rvalue.value
91
+ end
92
+ end
93
+
94
+ class MathFunctions
95
+ def self.method_missing(func, parameter, *args, &block)
96
+ if parameter.class == Matrix
97
+ parameter.send func
98
+ elsif parameter.class == Fixnum || parameter.class == Float
99
+ Math.send func, parameter
100
+ end
101
+ end
102
+ end
103
+
104
+ class OperationFunction < Treetop::Runtime::SyntaxNode
105
+ def value
106
+ MathFunctions.send function.text_value, expression.value
107
+ end
108
+ end
109
+
110
+ class OperationBinary < Treetop::Runtime::SyntaxNode
111
+ def value
112
+ to_exec = "#{expr1.value} #{binary_operator.operation} #{expr2.value}"
113
+ eval(to_exec)
114
+ end
115
+ end
116
+
117
+ class OperationMultiply < Treetop::Runtime::SyntaxNode
118
+ def value
119
+ number.value * expression.value
120
+ end
121
+ end
122
+
123
+ class OperationNegate < Treetop::Runtime::SyntaxNode
124
+ def value
125
+ -1 * expression.value
126
+ end
127
+ end
128
+
129
+ # Matrix transformation
130
+ class MatrixRowColIdentifier < Treetop::Runtime::SyntaxNode
131
+ # stuff like R1, C1 etc.
132
+ def row_or_col
133
+ text_value[0].chr
134
+ end
135
+ def index
136
+ index=text_value[1..-1].to_i - 1 # mathematical notation starts at index 1. we start at zero. -1 to adjust.
137
+ end
138
+
139
+ def value(matrix_in_context)
140
+ matrix=matrix_in_context.value
141
+
142
+ return matrix.row(index) if row_or_col == 'R'
143
+ return matrix.column(index)
144
+ end
145
+ end
146
+
147
+ class TransformationBlock < Treetop::Runtime::SyntaxNode
148
+ def execute
149
+ transformation_statements.elements.each {|stmt| stmt.execute(matrix_variable) }
150
+ end
151
+ end
152
+
153
+ class MatrixTransform < Treetop::Runtime::SyntaxNode
154
+ def execute(matrix_in_context)
155
+ transform_result=transform_expression.value(matrix_in_context)
156
+ $matrix_dsl_vars[matrix_in_context.name]=apply_row_col_transformation(matrix_in_context.value, matrix_row_col_identifier, transform_result)
157
+ end
158
+ end
159
+
160
+ class TransformOperationBinary < Treetop::Runtime::SyntaxNode
161
+ def value(matrix_in_context)
162
+ # the expressions will be of form: R1 + R2, expr1 would be R1 and expr2 R2.
163
+ # Both of them have to be evaluated in the context of the matrix
164
+ # defined in the 'Transform M1' expression.
165
+ to_exec = "#{expr1.value(matrix_in_context)} #{binary_operator.operation} #{expr2.value(matrix_in_context)}"
166
+
167
+ eval(to_exec)
168
+ end
169
+ end
170
+
171
+ class TransformOperationMultiply < Treetop::Runtime::SyntaxNode
172
+ def value(matrix_in_context)
173
+ transform_expression.value(matrix_in_context) * number.value
174
+ end
175
+ end
176
+
177
+ Treetop.load(File.join(File.dirname(__FILE__),'matrix_dsl_def.treetop'))
178
+ end
@@ -0,0 +1,118 @@
1
+ grammar MatrixTransformations
2
+
3
+ rule document
4
+ (space statement space)*
5
+ end
6
+
7
+ rule statement
8
+ assignment/transformation
9
+ end
10
+
11
+ #----------------------------------------------------
12
+ # === Matrix transformation grammar ===
13
+ # ie those inside the 'Transform' block:
14
+ # Transform M1:
15
+ # R1 <- R1 + R2
16
+ # R3 <- 3R3
17
+ # R4 <- R4/2
18
+ # etc.
19
+
20
+ rule transformation
21
+ "Transform " matrix_variable ":" space transformation_statements:(matrix_transformation_statement*) <MatrixDSL:: TransformationBlock>
22
+ end
23
+
24
+ rule matrix_transformation_statement
25
+ space matrix_row_col_identifier transform_operator transform_expression space <MatrixDSL::MatrixTransform>
26
+ end
27
+
28
+ rule matrix_variable
29
+ [A-Z] [a-zA-Z0-9_]* <MatrixDSL::MatrixIdentifier>
30
+ end
31
+
32
+ rule transform_operator
33
+ space "<-" space
34
+ end
35
+
36
+ rule transform_expression
37
+ expr1:simple_transform space binary_operator space expr2:transform_expression <MatrixDSL::TransformOperationBinary>
38
+ /
39
+ simple_transform
40
+ end
41
+
42
+ rule simple_transform
43
+ (number transform_expression <MatrixDSL::TransformOperationMultiply>)/([-] transform_expression <MatrixDSL::TransformOperationNegate>)/(left_paren transform_expression right_paren)/matrix_row_col_identifier
44
+ end
45
+
46
+ rule matrix_row_col_identifier
47
+ # R1, R2, C1 etc.
48
+ ("R"/"C") number <MatrixDSL::MatrixRowColIdentifier>
49
+ end
50
+
51
+ #----------------------------------------------------
52
+ # === Normal expressions ===
53
+ # Simple arithmetical operations outside the matrix transformation.
54
+ # ie:
55
+ # a = 123
56
+ # b = (a * log a)/23.5
57
+ # etc.
58
+ #
59
+
60
+ rule assignment
61
+ lvalue:identifier space "=" space rvalue:expression <MatrixDSL::OperationAssignment>
62
+ end
63
+
64
+ rule expression
65
+ expr1:simple_expression space binary_operator space expr2:expression <MatrixDSL::OperationBinary>
66
+ /
67
+ simple_expression
68
+ end
69
+
70
+ rule simple_expression
71
+ (number expression <MatrixDSL::OperationMultiply>)/(function " " expression <MatrixDSL::OperationFunction>)/([-] expression <MatrixDSL::OperationNegate>)/(left_paren expression right_paren <MatrixDSL::ParenExpression>)/matrix/number/identifier
72
+ end
73
+
74
+ #----------------------------------------------------
75
+ # === Common to all ===
76
+
77
+ # -- For the matrix data-type:
78
+ rule matrix
79
+ "[" matrix_rows:(matrix_row*) "]" <MatrixDSL::NodeMatrix>
80
+ end
81
+
82
+ rule matrix_row
83
+ space "[" space matrix_cols:(matrix_element*) space "]" space ","? <MatrixDSL::NodeMatrixRow>
84
+ end
85
+
86
+ rule matrix_element
87
+ node_number:number space ","? space <MatrixDSL::NodeMatrixElement>
88
+ end
89
+
90
+ #-- Rest of the stuff:
91
+ rule number
92
+ [0-9]+ <MatrixDSL::NodeNumber>
93
+ end
94
+
95
+ rule function
96
+ ("rank"/"transpose"/"inv"/"inverse"/"tr"/"trace"/"det"/"log")
97
+ end
98
+
99
+ rule binary_operator
100
+ [/+/*-//] <MatrixDSL::BinaryOperator>
101
+ end
102
+
103
+ rule space
104
+ [ \t\n]*
105
+ end
106
+
107
+ rule left_paren
108
+ "(" space
109
+ end
110
+
111
+ rule right_paren
112
+ space ")"
113
+ end
114
+
115
+ rule identifier
116
+ [a-zA-Z]+ [a-zA-Z0-9_]* <MatrixDSL::NodeIdentifier>
117
+ end
118
+ end
@@ -0,0 +1,59 @@
1
+ require 'colorize'
2
+
3
+ module MatrixDSL_Helpers
4
+
5
+ def node_info(node, truncate)
6
+ return node.statement.inspect unless truncate
7
+ return node.statement.inspect.split("\n")[0..6].join("\n")+"\n<trace truncated>"
8
+ end
9
+
10
+
11
+ # executes the script and prints error details if any.
12
+ # will clear existing variables.
13
+ def execute_matrix_dsl(script, parser, truncate_error_trace=true)
14
+ error_found=false
15
+
16
+ script.rstrip!
17
+ print "\n\nExecuting script:".yellow
18
+ puts script.blue
19
+
20
+ $matrix_dsl_vars.clear # Start with a fresh state.
21
+
22
+ result=parser.parse(script)
23
+
24
+ if result.nil?
25
+ puts "error_found executing script: \n"
26
+ show_error_script(script, parser.index)
27
+ error_found = true
28
+ else
29
+
30
+ # parse the entire file and execute each statement in the syntax tree.
31
+ result.elements.each {|node|
32
+ unless node.statement.respond_to? :execute
33
+ puts "Error: execute method unavailable in statement: #{node_info(node, truncate_error_trace)}".red
34
+ error_found = true
35
+ else
36
+ node.statement.execute
37
+ end
38
+ }
39
+
40
+ end
41
+
42
+ if error_found
43
+ puts "Execution failed".red
44
+ return false
45
+ end
46
+
47
+
48
+ puts "success".green
49
+ return !error_found
50
+ end
51
+
52
+ # Displays given script, but highlights the character at supplied offset in red color.
53
+ # Uses the colorize gem.
54
+ def show_error_script(text, error_character_offset)
55
+ print text[0..error_character_offset-1] unless error_character_offset==0
56
+ print text[error_character_offset].chr.red
57
+ print text[error_character_offset+1..-1]
58
+ end
59
+ end
data/matrix_dsl_run.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'ruby-debug'
3
+ require 'colorize'
4
+
5
+ require 'lib/matrix_dsl.rb'
6
+ require 'lib/matrix_dsl_helpers.rb'
7
+ include MatrixDSL
8
+ include MatrixDSL_Helpers
9
+
10
+ script=STDIN.read
11
+ parser = MatrixTransformationsParser.new
12
+
13
+ if execute_matrix_dsl(script, parser)
14
+ puts $matrix_dsl_vars.inspect
15
+ end
@@ -0,0 +1,84 @@
1
+ require 'rubygems'
2
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
3
+
4
+ require 'matrix_dsl.rb'
5
+ require 'matrix_dsl_helpers.rb'
6
+ include MatrixDSL_Helpers
7
+
8
+ require 'colorize'
9
+ require 'test/unit'
10
+ require 'ruby-debug'
11
+ include MatrixDSL
12
+
13
+ class BasicTest < Test::Unit::TestCase
14
+ def setup
15
+ @p = MatrixTransformationsParser.new
16
+ end
17
+
18
+ def test_create_variables
19
+ script = """
20
+ a = 10
21
+ M1 = [ [1 2 3][4 5 6]]
22
+ M2 = [
23
+ [9 8, 7]
24
+ [10, 11 12]
25
+ ]
26
+ "
27
+
28
+ expected = {"a"=>10.0, "M1"=>Matrix[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], "M2"=>Matrix[[9.0, 8.0, 7.0], [10.0, 11.0, 12.0]]}
29
+
30
+ MatrixDSL_Helpers::execute_matrix_dsl(script, @p)
31
+
32
+ assert_equal expected, $matrix_dsl_vars, "are variables and matrices initialized properly"
33
+ end
34
+
35
+ def test_arithmetic_expressions
36
+ script = """
37
+ a = 10 * (2+3)
38
+ b = 12a/4
39
+ "
40
+
41
+ expected = { "a" => 50, "b" => 150}
42
+
43
+ MatrixDSL_Helpers::execute_matrix_dsl(script, @p)
44
+
45
+ assert_equal expected, $matrix_dsl_vars, "test simple arithmetic expressions"
46
+ end
47
+
48
+ def test_functions
49
+ script = """
50
+ a = log 15
51
+ M1 = [[1 2 3][4 5 6][2 1 3]]
52
+ d = det M1
53
+ r = rank M1
54
+ "
55
+
56
+ expected = { "a" => 2.70805020110221, "d" => -9, "r" => 3, "M1" => Matrix[[1.0,2.0,3.0],[4.0,5.0,6.0],[2.0,1.0,3.0]]}
57
+
58
+ MatrixDSL_Helpers::execute_matrix_dsl(script, @p)
59
+
60
+ assert_equal expected, $matrix_dsl_vars, "test math and matrix functions"
61
+ end
62
+
63
+ def test_matrix_transform
64
+ script = """
65
+ M1 = [[1 2 3][3 2 1][5 1 2]]
66
+ det1 = det M1
67
+ det2 = det M1
68
+
69
+ Transform M1:
70
+ R1 <- R1 + R2
71
+ R3 <- R3 - R2
72
+ R2 <- 2R2
73
+ "
74
+
75
+ expected = {"M1"=>Matrix[[4.0, 4.0, 4.0], [6.0, 4.0, 2.0], [2.0, -1.0, 1.0]], "det1"=>-20.0, "det2"=>-20.0}
76
+
77
+ MatrixDSL_Helpers::execute_matrix_dsl(script, @p)
78
+
79
+ assert_equal expected, $matrix_dsl_vars, "test matrix transformations"
80
+ end
81
+
82
+ end
83
+
84
+
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: matrix_dsl
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Jasim A Basheer
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-26 00:00:00 +05:30
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: treetop
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: colorize
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ description: A simple DSL for expressing and evaluating basic Matrix transformations and arithmetic operations
50
+ email: jasim.ab@gmail.com
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files:
56
+ - LICENSE
57
+ - README.markdown
58
+ files:
59
+ - README.markdown
60
+ - example.matrix_dsl
61
+ - lib/matrix_dsl.rb
62
+ - lib/matrix_dsl_def.treetop
63
+ - lib/matrix_dsl_helpers.rb
64
+ - matrix_dsl_run.rb
65
+ - tests/basic_test.rb
66
+ - LICENSE
67
+ has_rdoc: true
68
+ homepage: http://github.com/jasim/matrix_dsl
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options:
73
+ - --charset=UTF-8
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ requirements: []
95
+
96
+ rubyforge_project:
97
+ rubygems_version: 1.3.7
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: A simple DSL for expressing and evaluating basic Matrix transformations and arithmetic operations
101
+ test_files: []
102
+