frepl 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9b623779b0cd2543b859da5e7b63195c7e54f6e8
4
+ data.tar.gz: fde5b1a7e38e9ef4b8f1a2a1897dd875fef2de71
5
+ SHA512:
6
+ metadata.gz: c532180ba00cd864128c61d421a9fa9328eb86490c74a3554377583b11535e1542bf0e1f73bb1956b82b8d8ce4b7c820fb2fccba2f5b676957cbc876fae4d965
7
+ data.tar.gz: 8b0af0047ebd2691dae341d4dd57944800062e0a44e096f4f6d0a33e5db5190202f5b03624da4749f838244c6643b9f055f66b1c57cde9e321982d1e54108fcf
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.f90
19
+ frepl_out
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in frepl.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Luke Rodgers
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,120 @@
1
+ # Frepl
2
+
3
+ Frepl (Fortran REPL) is an experimental ruby-based REPL for Fortran,
4
+ that I wrote because I was trying to learn Fortran, and I find the feedback
5
+ loop you get with a REPL makes learning a language much easier and more
6
+ enjoyable.
7
+
8
+ You don't need to know ruby to use Frepl, but you do need to have ruby (at least version 2)
9
+ installed.
10
+
11
+ There are a lot of deficiencies with this code, namely:
12
+
13
+ * Only knows how to classify/parse a limited set of Fortran.
14
+ * IO is severely hampered; basically you can't do the I part.
15
+ I have some ideas about how to sort of accomplish this but
16
+ they are half-baked and convoluted. Also I'm not sure this is
17
+ even really that important.
18
+ * Parsing is pretty dumb. It uses complicated and somewhat opaque regexes in places
19
+ where a real lexer/parser approach might be more appropriate, though might also be
20
+ overkill.
21
+
22
+ ## Project plans
23
+
24
+ See issue tracker.
25
+
26
+ ## Known issues
27
+
28
+ * Frepl is only able to correctly classify a subset of legal Fortran. At the same time,
29
+ Frepl will also happily accept some illegal Fortran, and wait for the compiler to tell you
30
+ about the problem. The UX here is not great.
31
+ * When specifying parameter and dimension in a declaration, parameter must currently come first,
32
+ e.g. `integer, parameter, dimension(:) :: a`.
33
+ * only `do... end do` loops are supported
34
+ * no support for labels; hence, no GOTO and its ilk
35
+
36
+ ## Installation
37
+
38
+ Add this line to your application's Gemfile:
39
+
40
+ gem 'frepl'
41
+
42
+ And then execute:
43
+
44
+ $ bundle
45
+
46
+ Or install it yourself as:
47
+
48
+ $ gem install frepl
49
+
50
+ ## Usage
51
+
52
+ You should be able to run `frepl` from the command line after having installed the gem.
53
+
54
+ Alternatively, if you have the source code, you can run `rake console` from the gem folder.
55
+
56
+ You will get a prompt, and you can just start typing Fortran, type `q` to quit.
57
+
58
+ ```
59
+ > integer :: a = 1
60
+ > integer, dimension(:), allocatable :: b
61
+ > allocate(b(0:4))
62
+ > b = [1,2,3,4]
63
+ > write(*,*) a * b
64
+ 1 2 3 4
65
+ > q
66
+ ```
67
+
68
+ ### Redefine a function, change the type of a variable, etc.
69
+
70
+ ```
71
+ > integer :: a = 1
72
+ > a = 3
73
+ 3
74
+ > real a
75
+ > a = 5
76
+ 5.00000000
77
+ > integer function sum(a, b)
78
+ > integer, intent(in) :: a, b
79
+ > sum = a + b
80
+ > end function
81
+ > write(*,*) sum(1,2)
82
+ 3
83
+ > real function sum(a, b, c)
84
+ > real, intent(in) :: a, b, c
85
+ > sum = a + b + c
86
+ > end function
87
+ > write(*,*) sum(1.0,2.9,3.4)
88
+ 7.30000019
89
+ >
90
+ ```
91
+
92
+ ### Undo the last statement
93
+
94
+ Type `f:z` (z as in cmd+z for undo). Handy when you made an error, e.g.
95
+
96
+ ```
97
+ > integer :: a =3
98
+ > real :: b
99
+ > b = 'fo'
100
+ frepl_out.f90:5.4:
101
+
102
+ b = 'fo'
103
+ 1
104
+ Error: Can't convert CHARACTER(1) to REAL(4) at (1)
105
+
106
+ > f:z
107
+ > b = 3.4
108
+ 3.40000010
109
+ >
110
+ ```
111
+
112
+ You can see some repl commands by typing `f:help`. Not much going on there, currently.
113
+
114
+ ## Contributing
115
+
116
+ 1. Fork it ( http://github.com/<my-github-username>/frepl/fork )
117
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
118
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
119
+ 4. Push to the branch (`git push origin my-new-feature`)
120
+ 5. Create new Pull Request
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+ require 'frepl'
3
+
4
+ task :console do
5
+ require 'pry'
6
+ require 'frepl'
7
+
8
+ def reload!
9
+ files = $LOADED_FEATURES.select { |feat| feat =~ /\/frepl\// }
10
+ files.each { |file| load file }
11
+ end
12
+
13
+ ARGV.clear
14
+ Pry.start
15
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'frepl'
4
+
5
+ Frepl::Main.run
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'frepl/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "frepl"
8
+ spec.version = Frepl::VERSION
9
+ spec.authors = ["Luke Rodgers"]
10
+ spec.email = ["lukeasrodgers@gmail.com"]
11
+ spec.summary = %q{A hacky, experimental Fortran REPL in ruby}
12
+ spec.description = %q{(Badly) supports a small subset of Fortran to be run in a REPL-like environment, with a bunch of caveats.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'activesupport', '> 4'
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.5"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "pry"
26
+ spec.add_development_dependency "rspec", "~> 3.3"
27
+ spec.add_development_dependency "rspec-mocks", "~> 3.3"
28
+ spec.add_development_dependency "byebug"
29
+ end
@@ -0,0 +1,81 @@
1
+ require 'readline'
2
+ require 'frepl/version'
3
+ require 'frepl/classifier'
4
+ require 'frepl/statement'
5
+ require 'frepl/fortran_file'
6
+
7
+ module Frepl
8
+ extend self
9
+
10
+ attr_accessor :compiler, :debug
11
+
12
+ def log(message)
13
+ puts message if @debug
14
+ end
15
+
16
+ def output(o)
17
+ puts o
18
+ end
19
+
20
+ class Main
21
+ class << self
22
+ def run
23
+ new.run
24
+ end
25
+ end
26
+
27
+ def initialize
28
+ Frepl.compiler = 'gfortran'
29
+ Frepl.debug = false
30
+ reset!
31
+ end
32
+
33
+ def run
34
+ loop do
35
+ begin
36
+ while buf = Readline.readline(prompt, true)
37
+ @lines << buf
38
+ process_line(buf)
39
+ end
40
+ rescue Interrupt
41
+ @classifier.interrupt
42
+ puts "^C\n"
43
+ rescue SystemExit, SignalException
44
+ raise
45
+ rescue Exception => e
46
+ puts "Exception!: #{e}"
47
+ puts e.backtrace
48
+ raise
49
+ end
50
+ end
51
+ end
52
+
53
+ def run_file(file)
54
+ file.each do |line|
55
+ @lines << line
56
+ process_line(line)
57
+ end
58
+ reset!
59
+ end
60
+
61
+ private
62
+
63
+ def prompt
64
+ '> ' + (' ' * @classifier.indentation_level)
65
+ end
66
+
67
+ def process_line(line)
68
+ exit(0) if line.chomp == 'q'
69
+ Frepl.log("classifying: #{line}")
70
+ line_obj = @classifier.classify(line)
71
+ @file.add(line_obj) unless line_obj.nil? || line_obj.incomplete?
72
+ end
73
+
74
+ def reset!
75
+ @classifier = Classifier.new
76
+ @file = FortranFile.new
77
+ @lines = []
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,190 @@
1
+ module Frepl
2
+ class Classifier
3
+ VARIABLE_NAME_REGEX = /[a-zA-Z][a-zA-Z0-9_]{,30}/
4
+ # TODO this regex seems incorrect, e.g. assignable value can't start with most punctuation
5
+ ASSIGNABLE_VALUE_REGEX = /[^\s]+/
6
+ DERIVED_TYPE_IDENTIFIER_REGEX = /\(#{VARIABLE_NAME_REGEX}\)/
7
+ DERIVED_TYPE_REGEX = /type\s+(#{VARIABLE_NAME_REGEX})/i
8
+ BUILTIN_TYPE_REGEX = /real|integer|character|logical/i
9
+ DECLARABLE_TYPE_REGEX = /#{BUILTIN_TYPE_REGEX}|type\s\(#{VARIABLE_NAME_REGEX}\)\s/i
10
+ # TODO: parameter/dimension order shouldn't matter here
11
+ DECLARATION_REGEX = /\As*(#{DECLARABLE_TYPE_REGEX})\s*(\((?:kind=|len=)*[^\(\)]+\)){,1}+(\s*,?\s*parameter\s*,?\s*)?(\s*,?\s*dimension\([^\)]+\))?(\s*,?\s*target)?(\s*,?\s*pointer)?\s*(?:::)?\s*([^(?:::)]*)/
12
+ ASSIGNABLE_REGEX = /#{VARIABLE_NAME_REGEX}|#{VARIABLE_NAME_REGEX}%#{VARIABLE_NAME_REGEX}/
13
+ ASSIGNMENT_REGEX = /\As*(#{ASSIGNABLE_REGEX})\s*=\s*(#{ASSIGNABLE_VALUE_REGEX})/
14
+ OLDSKOOL_ARRAY_VALUE_REGEX = /\/[^\]]+\//
15
+ F2003_ARRAY_VALUE_REGEX = /\[[^\]]+\]/
16
+ ARRAY_VALUE_REGEX = /#{OLDSKOOL_ARRAY_VALUE_REGEX}|#{F2003_ARRAY_VALUE_REGEX}/
17
+ FUNCTION_REGEX = /(#{BUILTIN_TYPE_REGEX})\s+function\s+(#{VARIABLE_NAME_REGEX})/i
18
+ SUBROUTINE_REGEX = /subroutine\s+(#{VARIABLE_NAME_REGEX})/i
19
+ IF_STATEMENT_REGEX = /if\s+\(.+\)\sthen/i
20
+ DO_LOOP_REGEX = /do\s+[^,]+,.+/i
21
+ WHERE_REGEX = /where\s+\([^\)]+\)\s*/i
22
+
23
+ def initialize
24
+ @all_lines = []
25
+ @current_lines = []
26
+ @current_multiline_obj = nil
27
+ end
28
+
29
+ def classify(line)
30
+ if @current_multiline_obj && !@current_multiline_obj.incomplete?
31
+ @current_multiline_obj = nil
32
+ @current_lines = []
33
+ end
34
+
35
+ @all_lines << line
36
+ @current_lines << line
37
+
38
+ if multiline?(line)
39
+ Frepl.log("MULTILINE")
40
+ classify_multiline(line)
41
+ return @current_multiline_obj
42
+ else
43
+ return classify_single_line(line)
44
+ end
45
+ end
46
+
47
+ def interrupt
48
+ @current_multiline_obj = nil
49
+ @current_lines = []
50
+ end
51
+
52
+ def executable?
53
+ !get_more_lines?
54
+ end
55
+
56
+ def lines_to_execute
57
+ @current_lines
58
+ end
59
+
60
+ def multiline?(line)
61
+ line.match(/\Asubroutine|function|(?:#{IF_STATEMENT_REGEX})|(?:#{DO_LOOP_REGEX})|(?:#{DERIVED_TYPE_REGEX})|(?:#{WHERE_REGEX})\z/i) || @current_multiline_obj != nil
62
+ end
63
+
64
+ def repl_command?
65
+ current_line.start_with?('f:')
66
+ end
67
+
68
+ # TODO this is stupid, may need real parser here
69
+ def multi_declaration?
70
+ m = current_line.match(DECLARATION_REGEX)
71
+ return false unless m
72
+ if m[7].gsub(ARRAY_VALUE_REGEX, '').count(',') > 0
73
+ true
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ # TODO this is stupid, may need real parser here
80
+ def declaration?
81
+ m = current_line.match(DECLARATION_REGEX)
82
+ return false unless m
83
+ if m[7].gsub(ARRAY_VALUE_REGEX, '').count(',') == 0
84
+ true
85
+ else
86
+ false
87
+ end
88
+ end
89
+
90
+ def allocation?
91
+ current_line.match(/allocate\(/) != nil
92
+ end
93
+
94
+ def run?
95
+ current_line.match(/\s*write|print|read|call/i) != nil
96
+ end
97
+
98
+ def assignment?
99
+ current_line.match(ASSIGNMENT_REGEX)
100
+ end
101
+
102
+ def standalone_variable?
103
+ current_line.match(/\A#{VARIABLE_NAME_REGEX}\z/)
104
+ end
105
+
106
+ def indentation_level
107
+ if @current_multiline_obj && @current_multiline_obj.incomplete?
108
+ 2
109
+ else
110
+ 0
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def classify_multiline(line)
117
+ if @current_lines.size == 1
118
+ raise 'Already have multiline obj' unless @current_multiline_obj.nil?
119
+ if line.match(/function/)
120
+ @current_multiline_obj = Function.new
121
+ elsif line.match(/subroutine/)
122
+ @current_multiline_obj = Subroutine.new
123
+ elsif line.match(IF_STATEMENT_REGEX)
124
+ @current_multiline_obj = IfStatement.new
125
+ elsif line.match(DO_LOOP_REGEX)
126
+ @current_multiline_obj = DoLoop.new
127
+ elsif line.match(/\A#{DERIVED_TYPE_REGEX}\z/)
128
+ @current_multiline_obj = DerivedType.new
129
+ elsif line.match(/\A#{WHERE_REGEX}\z/)
130
+ @current_multiline_obj = Where.new
131
+ end
132
+ end
133
+ @current_multiline_obj.lines << line
134
+ end
135
+
136
+ def classify_single_line(line)
137
+ obj = if repl_command?
138
+ ReplCommand.new(line)
139
+ elsif multi_declaration?
140
+ MultiDeclaration.new(line)
141
+ elsif declaration?
142
+ Declaration.new(line)
143
+ elsif run?
144
+ Execution.new(line)
145
+ elsif assignment?
146
+ Assignment.new(line)
147
+ elsif allocation?
148
+ Allocation.new(line)
149
+ elsif standalone_variable?
150
+ StandaloneVariable.new(line)
151
+ elsif ignorable?
152
+ nil
153
+ else
154
+ puts "I don't think `#{line}` is valid Fortran. You made a mistake, or, more likely, I'm dumb."
155
+ @all_lines.pop
156
+ obj = nil
157
+ end
158
+ @current_lines = []
159
+ obj
160
+ end
161
+
162
+ def current_line
163
+ @current_lines.last
164
+ end
165
+
166
+ def get_more_lines?
167
+ @current_lines.size > 0 || @current_lines.last.match(/subroutine|function/)
168
+ end
169
+
170
+ def done_multiline?
171
+ @current_lines.size > 0 && @current_lines.last.match(/end subroutine|end function/)
172
+ end
173
+
174
+ def ignorable?
175
+ program_statements? || blank? || comment?
176
+ end
177
+
178
+ def program_statements?
179
+ current_line.match(/\Aprogram .+|implicit .+|\Aend program/) != nil
180
+ end
181
+
182
+ def blank?
183
+ current_line.match(/\A\s*\z/)
184
+ end
185
+
186
+ def comment?
187
+ current_line.match(/\A!/)
188
+ end
189
+ end
190
+ end