diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index 804975a0b78b..bf9fbef71fdd 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -1298,7 +1298,7 @@ class Crystal::Repl::Interpreter end end - value = interpreter.interpret(line_node, meta_vars, in_pry: true).to_s + value = interpreter.interpret(line_node, meta_vars, in_pry: true) # New local variables might have been declared during a pry session. # Remember them by asking them from the interpreter @@ -1306,16 +1306,7 @@ class Crystal::Repl::Interpreter # to their new type) local_vars = interpreter.local_vars - if @context.program.color? - begin - value = Crystal::SyntaxHighlighter::Colorize.highlight(value) - rescue - # Ignore highlight errors - end - end - - print "=> " - puts value.to_s + prompt.display(value) rescue ex : EscapingException print "Unhandled exception: " print ex diff --git a/src/compiler/crystal/interpreter/prompt.cr b/src/compiler/crystal/interpreter/prompt.cr new file mode 100644 index 000000000000..6ab6a1bea055 --- /dev/null +++ b/src/compiler/crystal/interpreter/prompt.cr @@ -0,0 +1,114 @@ +# Allows reading a prompt for the interpreter. +class Crystal::Repl::Prompt + property line_number : Int32 + + def initialize(@context : Context, @show_nest : Bool) + @buffer = "" + @nest = 0 + @incomplete = false + @line_number = 1 + end + + # Asks for a line of input, prefixed with the given prefix. + # Returns nil if the user pressed CTRL-C. + def prompt(prefix) : String? + prompt = String.build do |io| + io.print prefix + if @show_nest + io.print ':' + io.print @nest + end + io.print(@incomplete ? '*' : '>') + io.print ' ' + if @nest == 0 && @incomplete + io.print " " + else + io.print " " * @nest if @nest > 0 + end + end + + print prompt + line = gets + return unless line + + if @context.program.color? + # Go back one line to print it again colored + print "\033[F" + print prompt + + colored_line = line + begin + colored_line = Crystal::SyntaxHighlighter::Colorize.highlight(colored_line) + rescue + # Ignore highlight errors + end + + puts colored_line + end + + new_buffer = + if @buffer.empty? + line + else + "#{@buffer}\n#{line}" + end + + new_buffer + end + + # Parses the given input with the given var_scopes. + # The input must be that returned from `#prompt`. + # Returns a parsed ASTNode if the input was valid Crystal syntax. + # If the input was partial Crystal syntax, `nil` is returned + # but the partial input is remembered. Next time `#prompt` is called, + # the returned value there will be the new complete input (what there + # was before plus the new input, separated by a new line). + def parse(input : String, var_scopes : Array(Set(String))) : ASTNode? + parser = Parser.new( + input, + string_pool: @context.program.string_pool, + var_scopes: var_scopes, + ) + + begin + node = parser.parse + + @nest = 0 + @buffer = "" + @line_number += 1 + @incomplete = false + + node + rescue ex : Crystal::SyntaxException + # TODO: improve this + case ex.message + when "unexpected token: EOF", + "expecting identifier 'end', not 'EOF'" + @nest = parser.type_nest + parser.def_nest + parser.fun_nest + @buffer = input + @line_number += 1 + @incomplete = @nest == 0 + when "expecting token ']', not 'EOF'", + "unterminated array literal", + "unterminated hash literal", + "unterminated tuple literal" + @nest = parser.type_nest + parser.def_nest + parser.fun_nest + @buffer = input + @line_number += 1 + @incomplete = true + else + puts "Error: #{ex.message}" + @nest = 0 + @buffer = "" + @incomplete = false + end + nil + end + end + + # Displays a value, preceding it with "=> ". + def display(value : Value) + print "=> " + puts value + end +end diff --git a/src/compiler/crystal/interpreter/repl.cr b/src/compiler/crystal/interpreter/repl.cr index 323da547d53d..7a09f07484a3 100644 --- a/src/compiler/crystal/interpreter/repl.cr +++ b/src/compiler/crystal/interpreter/repl.cr @@ -37,18 +37,8 @@ class Crystal::Repl next unless node begin - value = interpret(node).to_s - - if @context.program.color? - begin - value = Crystal::SyntaxHighlighter::Colorize.highlight(value) - rescue - # Ignore highlight errors - end - end - - print "=> " - puts value + value = interpret(node) + prompt.display(value) rescue ex : EscapingException @nest = 0 @buffer = ""