M parser.rb => parser.rb +6 -3
@@ 4,14 4,17 @@ require 'pathname'
class Parser
class ParseError < StandardError
- attr_reader :line
+ attr_reader :filename, :line, :column, :expected
- def initialize(line)
+ def initialize(filename, line, column, expected)
+ @filename = filename
@line = line
+ @column = column
+ @expected = expected
end
def message
- "error parsing at line #{line}"
+ "expected one of: #{expected.sort.inspect}"
end
end
M program.rb => program.rb +27 -5
@@ 1,26 1,42 @@
require_relative 'parser'
require_relative 'compiler'
+require 'time'
class Program
+ EXIT_CODE_VAR_UNDEFINED = 1
+ EXIT_CODE_STACK_TOO_DEEP = 2
+ EXIT_CODE_SYNTAX_ERROR = 3
+
def initialize(code, filename: '(unknown)', args: [], stdout: $stdout)
@filename = filename
@args = args
@stdout = stdout
+ @code = code
@compiler = Compiler.new(code, filename: filename)
+ rescue Parser::ParseError => e
+ print_syntax_error(e)
+ @error_parsing = true
end
def run(code: nil, debug: 0)
+ return EXIT_CODE_SYNTAX_ERROR if @error_parsing
+ start_compile = Time.now
@instr = @compiler.compile(code)
+ total_compile = Time.now - start_compile
VM::PrettyPrinter.new(@instr, grouped: true, ip: true).print if debug >= 1
vm.debug = debug
+ start_execute = Time.now
vm.execute(@instr)
+ total_execute = Time.now - start_execute
+ puts "compile: #{total_compile}" if false # TEMP
+ puts "execute: #{total_execute}" if false
vm.return_value
rescue VM::VariableUndefined => e
print_variable_undefined_error(e)
- 1
+ EXIT_CODE_VAR_UNDEFINED
rescue VM::CallStackTooDeep => e
print_call_stack_too_deep_error(e)
- 2
+ EXIT_CODE_STACK_TOO_DEEP
end
def filename=(f)
@@ 45,10 61,11 @@ class Program
@stdout.puts(message)
end
- def error_details_to_s(e)
- return '' unless e.filename && e.filename != '' && @compiler.source[e.filename]
+ def error_details_to_s(e, code = nil)
+ return '' unless e.filename && e.filename != ''
+ return '' unless (code ||= @compiler.source[e.filename])
lines_range = (e.line - 2)..(e.line - 1)
- code = @compiler.source[e.filename].split("\n")[lines_range].map { |l| " #{l}" }.join("\n")
+ code = code.split("\n")[lines_range].map { |l| " #{l}" }.join("\n")
line = "#{e.filename}##{e.line}"
pointer = " #{' ' * e.column}^ #{e.message}"
"\n\n#{line}\n\n#{code}\n#{pointer}"
@@ 65,4 82,9 @@ class Program
@stdout.puts " #{' ' * name.column}^"
end
end
+
+ def print_syntax_error(e)
+ message = 'Syntax Error:' + error_details_to_s(e, @code)
+ @stdout.puts(message)
+ end
end
M spec/program_spec.rb => spec/program_spec.rb +25 -0
@@ 231,5 231,30 @@ describe Program do
)
end
end
+
+ context 'when a syntax error occurs' do
+ let(:code) do
+ "(foo)\n" \
+ "\n" \
+ ' (()'
+ end
+
+ it 'sets the exit code to 3' do
+ expect(subject.run).to eq(3)
+ end
+
+ it 'shows the filename, line and column of the error' do
+ subject.run
+ stdout.rewind
+ expected = ["\"", "#;", "#|", "'", "(", ")", ",", ",@", ";", "[ \t\n]", "[^() \t\n[]{}|\"]", "`", "|"]
+ expect(stdout.read).to eq(
+ "Syntax Error:\n\n" \
+ "#{__FILE__}#3\n\n" \
+ " \n" \
+ " (()\n" \
+ " ^ expected one of: #{expected.inspect}\n"
+ )
+ end
+ end
end
end
M src/lib.rs => src/lib.rs +17 -15
@@ 15,7 15,8 @@ mod lisp {
fn parse_native(rself: Value) -> Value {
let code = rbstr2str!(&rb::ivar_get(&rself, "@code"));
- let filename = rbstr2str!(&rb::ivar_get(&rself, "@filename"));
+ let filename_rbstr = rb::ivar_get(&rself, "@filename");
+ let filename = rbstr2str!(&filename_rbstr);
let newlines: Vec<usize> = code.match_indices("\n").map(|(i, _s)| i).collect();
rb::gc_disable();
match lisp::program(&code, &filename, &newlines) {
@@ 24,25 25,26 @@ fn parse_native(rself: Value) -> Value {
ast
},
Err(err) => {
- rb::gc_enable();
- //let expected = rb::vec2rbarr(
- //err.expected.iter().cloned().map(|e| rb::str_new(&e.to_string())).collect()
- //);
- println!("{}", err.line);
- println!("{}", err.column);
- println!("{:?}", err.expected);
- println!("{:?}", &code);
- println!("{:?}", &code[err.column..]);
- let c_parser = rb::const_get("Parser", &RB_NIL);
- let c_parse_error = rb::const_get("ParseError", &c_parser);
- let line = int2rbnum!(err.line);
- let error = rb::class_new_instance(&c_parse_error, vec![line]);
- rb::raise_instance(&error);
+ raise_syntax_error(err, filename_rbstr);
RB_NIL
}
}
}
+fn raise_syntax_error(err: lisp::ParseError, filename_rbstr: Value) {
+ let c_parser = rb::const_get("Parser", &RB_NIL);
+ let c_parse_error = rb::const_get("ParseError", &c_parser);
+ let line = int2rbnum!(err.line);
+ let column = int2rbnum!(err.column);
+ let mut expected = rb::ary_new();
+ for token in err.expected {
+ expected = rb::ary_push(expected, rb::str_new(token));
+ }
+ let error = rb::class_new_instance(&c_parse_error, vec![filename_rbstr, line, column, expected]);
+ rb::gc_enable();
+ rb::raise_instance(&error);
+}
+
#[no_mangle]
pub extern fn init_parser() {
let c_parser = rb::const_get("Parser", &RB_NIL);