Skip to content

Commit

Permalink
#198 Deal with Octave script-global functions
Browse files Browse the repository at this point in the history
* In Octave mode this is now supported

* In MATLAB mode this now produces a very specific error

Closes #198
  • Loading branch information
florianschanda committed Mar 16, 2021
1 parent 1743a40 commit b3f004a
Show file tree
Hide file tree
Showing 16 changed files with 114 additions and 16 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ Not quite compatible with Octave yet. See #43 [octave support](https://github.co
be unexpectedly shared between different directory trees, this is
now fixed and is working as documented.

* Support Octave script-global functions in `--octave` mode. These are
function definitions that are sprinkled in the middle of a script
file, instead of appearing at the end. Also vastly improve the error
message that appears when encountering this construct in MATLAB
mode.

### 0.9.15

* Added [documentation](https://florianschanda.github.io/miss_hit/configuration.html#pre-commit) on how to use MISS_HIT through pre-commit hooks.
Expand Down
13 changes: 7 additions & 6 deletions miss_hit_core/m_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ def __init__(self, filename, blockname=None):
os.path.dirname(os.path.abspath(filename))).startswith("@")
# Make a note if this file resides inside a @ directory

self.octave_mode = False
# If set to true, also deal with Octave's extensions to
# MATLAB.
#
# Note that this is highly incomplete right now.

@abstractmethod
def token(self):
pass
Expand Down Expand Up @@ -177,12 +183,6 @@ def __init__(self, mh, content, filename, blockname=None):
# pragmas. Hence there is an option to turn this off. It is
# also turned off in config file mode.

self.octave_mode = False
# If set to true, also deal with Octave's extensions to
# MATLAB.
#
# Note that this is highly incomplete right now.

self.block_comment = 0
# We're in a block comment and are ignore almost everything
# (except a closing block comment). This is a number because
Expand Down Expand Up @@ -1219,6 +1219,7 @@ def __init__(self, lexer, cfg):
self.mh = lexer.mh
self.lines = lexer.line_count()
self.comment_char = lexer.comment_char
self.octave_mode = lexer.octave_mode

while True:
tok = lexer.token()
Expand Down
57 changes: 54 additions & 3 deletions miss_hit_core/m_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,8 +629,18 @@ def parse_file(self):
l_pragmas + l_more_pragmas)
elif self.peek("KEYWORD", "classdef") or self.lexer.in_class_directory:
cunit = self.parse_class_file(l_pragmas)
elif self.lexer.octave_mode:
cunit = self.parse_octave_script_file(l_pragmas)
else:
cunit = self.parse_script_file(l_pragmas)
cunit = self.parse_matlab_script_file(l_pragmas)

# Special detection of Octave inline globals in MATLAB
# mode (to improve the messages produced in #198).
if not self.peek_eof() and cunit.l_functions:
self.mh.error(cunit.l_functions[0].loc(),
"script-global functions are an Octave-specific"
" feature; move your functions to the end of"
" the script file or use the --octave mode")

if self.debug_tree:
cunit.debug_parse_tree()
Expand All @@ -639,9 +649,14 @@ def parse_file(self):

return cunit

def parse_script_file(self, l_pragmas):
def parse_matlab_script_file(self, l_pragmas):
# A MATLAB script file is a bunch of statements, followed by
# zero or more private functions. This is distinct from Octave
# script files, which allow you to sprinkle functions anywhere
# in the top-level context.

# At least in MATLAB 2017b script files cannot use the
# non-ended chained functions
# non-ended chained functions.

self.functions_require_end = True
statements = []
Expand All @@ -662,6 +677,42 @@ def parse_script_file(self, l_pragmas):
l_functions,
l_pragmas + l_more_pragmas)

def parse_octave_script_file(self, l_pragmas):
# An Octave script file is very similar to a MATLAB one,
# except they allow you to put functions everywhere (as long
# as it's not the first thing, because then you have a
# function file again). The semantics are weird as well,
# because all functions encountered this way are made global,
# as if they were in their own function files.

self.functions_require_end = True
statements = []
l_functions = []
l_more_pragmas = []

if not self.peek_eof():
# Script files _have_ to start with a statment
statements.append(self.parse_statement())

# Otherwise, in Octave, script files are a sequence of
# statements intermixed with (global) function definitions
while not self.peek_eof():
if self.peek("KEYWORD", "function"):
l_functions.append(self.parse_function_def())
elif self.peek("KEYWORD", "pragma"):
l_more_pragmas.append(self.parse_annotation_pragma())
else:
statements.append(self.parse_statement())

return Script_File(os.path.basename(self.lexer.filename),
os.path.dirname(
os.path.abspath(self.lexer.filename)),
self.lexer.get_file_loc(),
self.lexer.line_count(),
Sequence_Of_Statements(statements),
l_functions,
l_pragmas + l_more_pragmas)

def parse_class_file(self, l_pragmas):
self.functions_require_end = True

Expand Down
3 changes: 3 additions & 0 deletions tests/lint/octave_global_functions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Octave has very confusing semantics when it comes to functions in
*script* files. These are made globally available, as if they were in
their own `m` file; but only if the script is run first.
2 changes: 2 additions & 0 deletions tests/lint/octave_global_functions/expected_out.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
=== PLAIN MODE ===
MISS_HIT Lint Summary: 7 file(s) analysed, everything seems fine
7 changes: 7 additions & 0 deletions tests/lint/octave_global_functions/foo.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
1;

function x = potato(y)
x = y + 1;
end

disp('Hello world!');
1 change: 1 addition & 0 deletions tests/lint/octave_global_functions/miss_hit.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
octave: true
6 changes: 6 additions & 0 deletions tests/lint/octave_global_functions/test_1.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
foo;
disp(potato(12));

% Not an error, because foo is a bizarre octave extension that makes
% functions in the middle go global. Specifically this happens only
% for _script_ files
3 changes: 3 additions & 0 deletions tests/lint/octave_global_functions/test_1_b.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
disp(potato(12));

% Error, because potato (from the script file foo) is not visible.
3 changes: 3 additions & 0 deletions tests/lint/octave_global_functions/test_2.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
disp(wibble(5));

% Should print 11.
3 changes: 3 additions & 0 deletions tests/lint/octave_global_functions/test_3.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
disp(wobble(5));

% Error, because wobble is not visible/private
5 changes: 5 additions & 0 deletions tests/lint/octave_global_functions/test_4.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
disp(wibble(5));
% Prints 11

disp(wobble(5));
% Error, because wobble is still not visible/private
7 changes: 7 additions & 0 deletions tests/lint/octave_global_functions/wibble.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function rv = wibble(x)
rv = wobble(x) + 1;
end

function rv = wobble(x)
rv = x * 2;
end
6 changes: 3 additions & 3 deletions tests/parser/scripts/bar.m.out
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
In bar.m, line 7
| kittens = 42; % Illegal
| ^^^^^^^ error: expected end of file, found IDENTIFIER instead
In bar.m, line 3
| function y = foo(x)
| ^^^ error: script-global functions are an Octave-specific feature; move your functions to the end of the script file or use the --octave mode
MISS_HIT Debug Summary: 1 file(s) analysed, 1 error(s)
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ <h1>Issues identified</h1>
<section>
<h2>test_1.m</h2>
<div class="message"><a href="matlab:opentoline('test_1.m', 1, 45)">test_1.m: line 1:</a> style: No copyright notice found in header</div>
<div class="message"><a href="matlab:opentoline('test_1.m', 16)">test_1.m: line 16:</a> error: expected end of file, found IDENTIFIER instead</div>
<div class="message"><a href="matlab:opentoline('test_1.m', 5, 14)">test_1.m: line 5:</a> error: script-global functions are an Octave-specific feature; move your functions to the end of the script file or use the --octave mode</div>
<h2>test_2.m</h2>
<div class="message"><a href="matlab:opentoline('test_2.m', 1, 45)">test_2.m: line 1:</a> style: No copyright notice found in header</div>
<div class="message"><a href="matlab:opentoline('test_2.m', 16, 7)">test_2.m: line 16:</a> style: end statement with a semicolon</div>
Expand Down
6 changes: 3 additions & 3 deletions tests/style/bug_199_parse_errors_and_style/expected_out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ test_1.m: error: file is not auto-fixed because it contains parse errors
In test_1.m, line 1
| % Taken from issue #199 (by alvinseville7cf)
| ^ style: No copyright notice found in header
In test_1.m, line 16
| n = 4;
| ^ error: expected end of file, found IDENTIFIER instead
In test_1.m, line 5
| function z = f(x, a)
| ^ error: script-global functions are an Octave-specific feature; move your functions to the end of the script file or use the --octave mode
In test_2.m, line 1
| % Taken from issue #199 (by alvinseville7cf)
| ^ style: No copyright notice found in header
Expand Down

0 comments on commit b3f004a

Please sign in to comment.