matrix_dsl 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+