frepl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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