mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
[ruby/prism] Port diagnostics format to Prism from CRuby
https://github.com/ruby/prism/commit/46a5f19f77
This commit is contained in:
1 parent
08fb9346de
commit
2526cb6154
14 files changed
+1237
-123
Notes:
git
2026-07-24 21:09:08 +00:00
No files matched your search
+17
-17
@@ -103,23 +103,23 @@ module Prism
|
||||
# def gets: (?Integer integer) -> (String | nil)
|
||||
# end
|
||||
#
|
||||
# def self.parse: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseResult
|
||||
# def self.profile: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> void
|
||||
# def self.lex: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> LexResult
|
||||
# def self.parse_lex: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseLexResult
|
||||
# def self.dump: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> String
|
||||
# def self.parse_comments: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> Array[Comment]
|
||||
# def self.parse_success?: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> bool
|
||||
# def self.parse_failure?: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> bool
|
||||
# def self.parse_stream: (_Stream stream, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseResult
|
||||
# def self.parse_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseResult
|
||||
# def self.profile_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> void
|
||||
# def self.lex_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> LexResult
|
||||
# def self.parse_lex_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseLexResult
|
||||
# def self.dump_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> String
|
||||
# def self.parse_file_comments: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> Array[Comment]
|
||||
# def self.parse_file_success?: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> bool
|
||||
# def self.parse_file_failure?: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> bool
|
||||
# def self.parse: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseResult
|
||||
# def self.profile: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> void
|
||||
# def self.lex: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> LexResult
|
||||
# def self.parse_lex: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseLexResult
|
||||
# def self.dump: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> String
|
||||
# def self.parse_comments: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> Array[Comment]
|
||||
# def self.parse_success?: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> bool
|
||||
# def self.parse_failure?: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> bool
|
||||
# def self.parse_stream: (_Stream stream, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseResult
|
||||
# def self.parse_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseResult
|
||||
# def self.profile_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> void
|
||||
# def self.lex_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> LexResult
|
||||
# def self.parse_lex_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseLexResult
|
||||
# def self.dump_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> String
|
||||
# def self.parse_file_comments: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> Array[Comment]
|
||||
# def self.parse_file_success?: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> bool
|
||||
# def self.parse_file_failure?: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?raise_error: Symbol | true, ?scopes: Array[Array[Symbol]], ?version: String) -> bool
|
||||
end
|
||||
|
||||
require_relative "prism/polyfill/byteindex"
|
||||
|
||||
+121
-6
@@ -93,6 +93,7 @@ module Prism # :nodoc:
|
||||
pm_source_init_result_values = %i[PM_SOURCE_INIT_SUCCESS PM_SOURCE_INIT_ERROR_GENERIC PM_SOURCE_INIT_ERROR_DIRECTORY PM_SOURCE_INIT_ERROR_NON_REGULAR]
|
||||
enum :pm_source_init_result_t, pm_source_init_result_values
|
||||
enum :pm_string_query_t, [:PM_STRING_QUERY_ERROR, -1, :PM_STRING_QUERY_FALSE, :PM_STRING_QUERY_TRUE]
|
||||
enum :pm_errors_format_type_t, [:PM_ERRORS_FORMAT_PLAIN, 1, :PM_ERRORS_FORMAT_STYLE, :PM_ERRORS_FORMAT_COLOR]
|
||||
|
||||
# Ractor-safe lookup table for pm_source_init_result_t, since FFI's
|
||||
# enum_type accesses module instance variables that are not shareable.
|
||||
@@ -112,6 +113,7 @@ module Prism # :nodoc:
|
||||
"pm_serialize_lex",
|
||||
"pm_serialize_parse_lex",
|
||||
"pm_serialize_parse_success_p",
|
||||
"pm_serialize_parse_errors_format",
|
||||
[]
|
||||
)
|
||||
|
||||
@@ -293,6 +295,8 @@ module Prism # :nodoc:
|
||||
|
||||
# Mirror the Prism.parse_stream API by using the serialization API.
|
||||
def parse_stream(stream, **options)
|
||||
format_type = raise_error_format_type(options)
|
||||
|
||||
LibRubyParser::PrismBuffer.with do |buffer|
|
||||
# The largest number of bytes a single character can occupy in any
|
||||
# encoding Ruby supports. IO#gets(limit) may return up to
|
||||
@@ -321,7 +325,15 @@ module Prism # :nodoc:
|
||||
pm_source = LibRubyParser.pm_source_stream_new(nil, callback, eof_callback)
|
||||
begin
|
||||
LibRubyParser.pm_serialize_parse_stream(buffer.pointer, pm_source, dump_options(options))
|
||||
Prism.load(source, buffer.read, options.fetch(:freeze, false))
|
||||
result = Prism.load(source, buffer.read, options.fetch(:freeze, false))
|
||||
|
||||
if format_type && result.failure?
|
||||
LibRubyParser::PrismSource.with_string(source) do |string|
|
||||
raise_error(string, options, format_type)
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
ensure
|
||||
LibRubyParser.pm_source_free(pm_source) if pm_source && !pm_source.null?
|
||||
end
|
||||
@@ -376,6 +388,10 @@ module Prism # :nodoc:
|
||||
# Mirror the Prism.profile API by using the serialization API.
|
||||
def profile(source, **options)
|
||||
LibRubyParser::PrismSource.with_string(source) do |string|
|
||||
if (format_type = raise_error_format_type(options))
|
||||
raise_error(string, options, format_type)
|
||||
end
|
||||
|
||||
LibRubyParser::PrismBuffer.with do |buffer|
|
||||
LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
|
||||
nil
|
||||
@@ -386,8 +402,13 @@ module Prism # :nodoc:
|
||||
# Mirror the Prism.profile_file API by using the serialization API.
|
||||
def profile_file(filepath, **options)
|
||||
LibRubyParser::PrismSource.with_file(filepath) do |string|
|
||||
options[:filepath] = filepath
|
||||
|
||||
if (format_type = raise_error_format_type(options))
|
||||
raise_error(string, options, format_type)
|
||||
end
|
||||
|
||||
LibRubyParser::PrismBuffer.with do |buffer|
|
||||
options[:filepath] = filepath
|
||||
LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
|
||||
nil
|
||||
end
|
||||
@@ -397,6 +418,10 @@ module Prism # :nodoc:
|
||||
private
|
||||
|
||||
def dump_common(string, options) # :nodoc:
|
||||
if (format_type = raise_error_format_type(options))
|
||||
raise_error(string, options, format_type)
|
||||
end
|
||||
|
||||
LibRubyParser::PrismBuffer.with do |buffer|
|
||||
LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
|
||||
|
||||
@@ -408,18 +433,31 @@ module Prism # :nodoc:
|
||||
end
|
||||
|
||||
def lex_common(string, code, options) # :nodoc:
|
||||
format_type = raise_error_format_type(options)
|
||||
|
||||
LibRubyParser::PrismBuffer.with do |buffer|
|
||||
LibRubyParser.pm_serialize_lex(buffer.pointer, string.pointer, string.length, dump_options(options))
|
||||
Serialize.load_lex(code, buffer.read, options.fetch(:freeze, false))
|
||||
result = Serialize.load_lex(code, buffer.read, options.fetch(:freeze, false))
|
||||
|
||||
raise_error(string, options, format_type) if format_type && result.failure?
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def parse_common(string, code, options) # :nodoc:
|
||||
format_type = raise_error_format_type(options)
|
||||
serialized = dump_common(string, options)
|
||||
Serialize.load_parse(code, serialized, options.fetch(:freeze, false))
|
||||
result = Serialize.load_parse(code, serialized, options.fetch(:freeze, false))
|
||||
|
||||
raise_error(string, options, format_type) if format_type && result.failure?
|
||||
result
|
||||
end
|
||||
|
||||
def parse_comments_common(string, code, options) # :nodoc:
|
||||
if (format_type = raise_error_format_type(options))
|
||||
raise_error(string, options, format_type)
|
||||
end
|
||||
|
||||
LibRubyParser::PrismBuffer.with do |buffer|
|
||||
LibRubyParser.pm_serialize_parse_comments(buffer.pointer, string.pointer, string.length, dump_options(options))
|
||||
Serialize.load_parse_comments(code, buffer.read, options.fetch(:freeze, false))
|
||||
@@ -427,14 +465,82 @@ module Prism # :nodoc:
|
||||
end
|
||||
|
||||
def parse_lex_common(string, code, options) # :nodoc:
|
||||
format_type = raise_error_format_type(options)
|
||||
|
||||
LibRubyParser::PrismBuffer.with do |buffer|
|
||||
LibRubyParser.pm_serialize_parse_lex(buffer.pointer, string.pointer, string.length, dump_options(options))
|
||||
Serialize.load_parse_lex(code, buffer.read, options.fetch(:freeze, false))
|
||||
result = Serialize.load_parse_lex(code, buffer.read, options.fetch(:freeze, false))
|
||||
|
||||
raise_error(string, options, format_type) if format_type && result.failure?
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def parse_file_success_common(string, options) # :nodoc:
|
||||
LibRubyParser.pm_serialize_parse_success_p(string.pointer, string.length, dump_options(options))
|
||||
format_type = raise_error_format_type(options)
|
||||
success = LibRubyParser.pm_serialize_parse_success_p(string.pointer, string.length, dump_options(options))
|
||||
|
||||
raise_error(string, options, format_type) if format_type && !success
|
||||
success
|
||||
end
|
||||
|
||||
# Extract the raise_error option from the given options hash and convert
|
||||
# it into the format type that should be used when formatting errors, or
|
||||
# nil if raising is disabled.
|
||||
def raise_error_format_type(options) # :nodoc:
|
||||
case (value = options.delete(:raise_error))
|
||||
when nil, false
|
||||
nil
|
||||
when true
|
||||
# When given true, mirror the behavior of CRuby itself: color when
|
||||
# $stderr is a terminal (unless NO_COLOR is set), bold styling when
|
||||
# NO_COLOR is set, plain otherwise. This policy is duplicated in
|
||||
# ext/prism/extension.c (build_options_i) and the two must stay in
|
||||
# sync.
|
||||
if $stderr.respond_to?(:tty?) && $stderr.tty?
|
||||
no_color = ENV["NO_COLOR"]
|
||||
(no_color.nil? || no_color.empty?) ? :PM_ERRORS_FORMAT_COLOR : :PM_ERRORS_FORMAT_STYLE
|
||||
else
|
||||
:PM_ERRORS_FORMAT_PLAIN
|
||||
end
|
||||
when :plain
|
||||
:PM_ERRORS_FORMAT_PLAIN
|
||||
when :style
|
||||
:PM_ERRORS_FORMAT_STYLE
|
||||
when :color
|
||||
:PM_ERRORS_FORMAT_COLOR
|
||||
when Symbol
|
||||
raise ArgumentError, "invalid raise_error value: #{value}"
|
||||
else
|
||||
raise TypeError, "wrong argument type #{value.class} (expected Symbol)"
|
||||
end
|
||||
end
|
||||
|
||||
# Parse the given source and format any errors that are encountered into
|
||||
# an appropriate exception, then raise it. If the source parses without
|
||||
# any errors, then return nil.
|
||||
def raise_error(string, options, format_type) # :nodoc:
|
||||
LibRubyParser::PrismBuffer.with do |buffer|
|
||||
level = LibRubyParser.pm_serialize_parse_errors_format(buffer.pointer, string.pointer, string.length, dump_options(options), format_type)
|
||||
return if level == -1
|
||||
|
||||
encoding_name, _, message = buffer.read.partition("\0")
|
||||
|
||||
case level
|
||||
when 0 # syntax
|
||||
error = SyntaxError.new(message.force_encoding(encoding_name))
|
||||
error.instance_variable_set(:@path, options.fetch(:filepath, ""))
|
||||
raise error
|
||||
when 1 # argument
|
||||
raise ArgumentError, message.force_encoding(encoding_name)
|
||||
when 2 # load
|
||||
error = LoadError.new(message.force_encoding(Encoding.find("locale")))
|
||||
error.instance_variable_set(:@path, nil)
|
||||
raise error
|
||||
else
|
||||
raise "Unknown error level: #{level}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return the value that should be dumped for the command_line option.
|
||||
@@ -490,8 +596,17 @@ module Prism # :nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
# The set of options that are understood by the parsing APIs. Note that
|
||||
# raise_error is not listed here because it is deleted from the options
|
||||
# hash by raise_error_format_type before the options are dumped.
|
||||
DUMP_OPTIONS_KEYS = [:command_line, :encoding, :filepath, :freeze, :frozen_string_literal, :line, :main_script, :partial_script, :scopes, :version].freeze
|
||||
private_constant :DUMP_OPTIONS_KEYS
|
||||
|
||||
# Convert the given options into a serialized options string.
|
||||
def dump_options(options)
|
||||
unknown_keys = options.keys - DUMP_OPTIONS_KEYS
|
||||
raise ArgumentError, "unknown keyword: #{unknown_keys.first}" unless unknown_keys.empty?
|
||||
|
||||
template = +""
|
||||
values = []
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ Gem::Specification.new do |spec|
|
||||
"include/prism/comments.h",
|
||||
"include/prism/constant_pool.h",
|
||||
"include/prism/diagnostic.h",
|
||||
"include/prism/errors_format.h",
|
||||
"include/prism/excludes.h",
|
||||
"include/prism/integer.h",
|
||||
"include/prism/json.h",
|
||||
@@ -205,6 +206,7 @@ Gem::Specification.new do |spec|
|
||||
"src/constant_pool.c",
|
||||
"src/diagnostic.c",
|
||||
"src/encoding.c",
|
||||
"src/errors_format.c",
|
||||
"src/integer.c",
|
||||
"src/json.c",
|
||||
"src/line_offset_list.c",
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
#include "prism/errors_format.h"
|
||||
|
||||
#include "prism/internal/allocator.h"
|
||||
#include "prism/internal/buffer.h"
|
||||
#include "prism/internal/diagnostic.h"
|
||||
#include "prism/internal/encoding.h"
|
||||
#include "prism/internal/line_offset_list.h"
|
||||
#include "prism/internal/parser.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Check if the source touched by the given location is valid UTF-8. The
|
||||
* location represents the location of the error, but the slice of the source
|
||||
* that will be displayed includes the content of all of the lines that the
|
||||
* error touches, so we need to check those parts as well.
|
||||
*/
|
||||
static bool
|
||||
location_is_utf8(const pm_parser_t *parser, const pm_location_t *location) {
|
||||
const pm_line_offset_list_t *line_offsets = &parser->line_offsets;
|
||||
const size_t start_line = (size_t) pm_line_offset_list_line_column(line_offsets, location->start, 1).line;
|
||||
const size_t end_line = (size_t) pm_line_offset_list_line_column(line_offsets, location->start + location->length, 1).line;
|
||||
|
||||
const uint8_t *cursor = parser->start + line_offsets->offsets[start_line - 1];
|
||||
const uint8_t *end = (end_line == line_offsets->size) ? parser->end : (parser->start + line_offsets->offsets[end_line]);
|
||||
|
||||
while (cursor < end) {
|
||||
size_t width = pm_encoding_utf_8_char_width(cursor, end - cursor);
|
||||
if (width == 0) return false;
|
||||
|
||||
cursor += width;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
pm_diagnostic_t *diagnostic;
|
||||
int32_t line;
|
||||
uint32_t column_start;
|
||||
uint32_t column_end;
|
||||
size_t idx;
|
||||
} pm_rich_error_t;
|
||||
|
||||
static int
|
||||
pm_rich_error_compare(const void *a, const void *b) {
|
||||
const pm_rich_error_t *left = (const pm_rich_error_t *) a;
|
||||
const pm_rich_error_t *right = (const pm_rich_error_t *) b;
|
||||
|
||||
if (left->line != right->line) {
|
||||
return (left->line < right->line) ? -1 : 1;
|
||||
}
|
||||
|
||||
if (left->column_start != right->column_start) {
|
||||
return (left->column_start < right->column_start) ? -1 : 1;
|
||||
}
|
||||
|
||||
if (left->idx != right->idx) {
|
||||
return (left->idx < right->idx) ? 1 : -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define COLOR_BOLD "\033[1m"
|
||||
#define COLOR_GRAY "\033[2m"
|
||||
#define COLOR_RED "\033[1;31m"
|
||||
#define COLOR_RESET "\033[m"
|
||||
#define TRUNCATE 30
|
||||
|
||||
typedef struct {
|
||||
const char *number_prefix;
|
||||
const char *blank_prefix;
|
||||
const char *divider;
|
||||
size_t blank_prefix_length;
|
||||
size_t divider_length;
|
||||
} pm_error_line_format_t;
|
||||
|
||||
/**
|
||||
* Initialize the given line format struct based on whether or not to highlight
|
||||
* the line and the first and last line numbers.
|
||||
*/
|
||||
static void
|
||||
pm_error_line_format_init(pm_error_line_format_t *format, pm_errors_format_type_t format_type, int32_t first_line, int32_t last_line) {
|
||||
/* If we have a maximum line number that is negative, then we are going to
|
||||
* use the absolute value for comparison but multiply by 10 to additionally
|
||||
* have a column for the negative sign. */
|
||||
if (first_line < 0) first_line = (-first_line) * 10;
|
||||
if (last_line < 0) last_line = (-last_line) * 10;
|
||||
int32_t max_line = first_line > last_line ? first_line : last_line;
|
||||
|
||||
if (max_line < 10) {
|
||||
if (format_type > PM_ERRORS_FORMAT_PLAIN) {
|
||||
*format = (pm_error_line_format_t) {
|
||||
.number_prefix = COLOR_GRAY "%1" PRIi32 " | " COLOR_RESET,
|
||||
.blank_prefix = COLOR_GRAY " | " COLOR_RESET,
|
||||
.divider = COLOR_GRAY " ~~~~~" COLOR_RESET "\n"
|
||||
};
|
||||
} else {
|
||||
*format = (pm_error_line_format_t) {
|
||||
.number_prefix = "%1" PRIi32 " | ",
|
||||
.blank_prefix = " | ",
|
||||
.divider = " ~~~~~\n"
|
||||
};
|
||||
}
|
||||
} else if (max_line < 100) {
|
||||
if (format_type > PM_ERRORS_FORMAT_PLAIN) {
|
||||
*format = (pm_error_line_format_t) {
|
||||
.number_prefix = COLOR_GRAY "%2" PRIi32 " | " COLOR_RESET,
|
||||
.blank_prefix = COLOR_GRAY " | " COLOR_RESET,
|
||||
.divider = COLOR_GRAY " ~~~~~~" COLOR_RESET "\n"
|
||||
};
|
||||
} else {
|
||||
*format = (pm_error_line_format_t) {
|
||||
.number_prefix = "%2" PRIi32 " | ",
|
||||
.blank_prefix = " | ",
|
||||
.divider = " ~~~~~~\n"
|
||||
};
|
||||
}
|
||||
} else if (max_line < 1000) {
|
||||
if (format_type > PM_ERRORS_FORMAT_PLAIN) {
|
||||
*format = (pm_error_line_format_t) {
|
||||
.number_prefix = COLOR_GRAY "%3" PRIi32 " | " COLOR_RESET,
|
||||
.blank_prefix = COLOR_GRAY " | " COLOR_RESET,
|
||||
.divider = COLOR_GRAY " ~~~~~~~" COLOR_RESET "\n"
|
||||
};
|
||||
} else {
|
||||
*format = (pm_error_line_format_t) {
|
||||
.number_prefix = "%3" PRIi32 " | ",
|
||||
.blank_prefix = " | ",
|
||||
.divider = " ~~~~~~~\n"
|
||||
};
|
||||
}
|
||||
} else if (max_line < 10000) {
|
||||
if (format_type > PM_ERRORS_FORMAT_PLAIN) {
|
||||
*format = (pm_error_line_format_t) {
|
||||
.number_prefix = COLOR_GRAY "%4" PRIi32 " | " COLOR_RESET,
|
||||
.blank_prefix = COLOR_GRAY " | " COLOR_RESET,
|
||||
.divider = COLOR_GRAY " ~~~~~~~~" COLOR_RESET "\n"
|
||||
};
|
||||
} else {
|
||||
*format = (pm_error_line_format_t) {
|
||||
.number_prefix = "%4" PRIi32 " | ",
|
||||
.blank_prefix = " | ",
|
||||
.divider = " ~~~~~~~~\n"
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (format_type > PM_ERRORS_FORMAT_PLAIN) {
|
||||
*format = (pm_error_line_format_t) {
|
||||
.number_prefix = COLOR_GRAY "%5" PRIi32 " | " COLOR_RESET,
|
||||
.blank_prefix = COLOR_GRAY " | " COLOR_RESET,
|
||||
.divider = COLOR_GRAY " ~~~~~~~~" COLOR_RESET "\n"
|
||||
};
|
||||
} else {
|
||||
*format = (pm_error_line_format_t) {
|
||||
.number_prefix = "%5" PRIi32 " | ",
|
||||
.blank_prefix = " | ",
|
||||
.divider = " ~~~~~~~~\n"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
format->blank_prefix_length = strlen(format->blank_prefix);
|
||||
format->divider_length = strlen(format->divider);
|
||||
}
|
||||
|
||||
static void
|
||||
pm_error_format_line(const pm_parser_t *parser, pm_buffer_t *buffer, const char *number_prefix, int32_t line, uint32_t column_start, uint32_t column_end) {
|
||||
int32_t line_delta = line - parser->start_line;
|
||||
assert(line_delta >= 0);
|
||||
|
||||
size_t index = (size_t) line_delta;
|
||||
assert(index < parser->line_offsets.size);
|
||||
|
||||
const uint8_t *start = parser->start + parser->line_offsets.offsets[index];
|
||||
const uint8_t *end;
|
||||
|
||||
if (index >= parser->line_offsets.size - 1) {
|
||||
end = parser->end;
|
||||
} else {
|
||||
end = parser->start + parser->line_offsets.offsets[index + 1];
|
||||
}
|
||||
|
||||
pm_buffer_append_format(buffer, number_prefix, line);
|
||||
|
||||
/* Here we determine if we should truncate the end of the line. Note that
|
||||
* this is written to avoid computing start + column_end, which could be
|
||||
* more than one past the end of the source when the error is at the end
|
||||
* of the input. */
|
||||
bool truncate_end = false;
|
||||
if ((column_end != 0) && ((end - start) - ((ptrdiff_t) column_end) >= TRUNCATE)) {
|
||||
const uint8_t *end_candidate = start + column_end + TRUNCATE;
|
||||
|
||||
for (const uint8_t *cursor = start; cursor < end_candidate;) {
|
||||
size_t char_width = pm_parser_encoding_char_width(parser, cursor, parser->end - cursor);
|
||||
|
||||
/* If we failed to decode a character, then just bail out and
|
||||
* truncate at the fixed width. */
|
||||
if (char_width == 0) break;
|
||||
|
||||
/* If this next character would go past the end candidate,
|
||||
* then we need to truncate before it. */
|
||||
if (cursor + char_width > end_candidate) {
|
||||
end_candidate = cursor;
|
||||
break;
|
||||
}
|
||||
|
||||
cursor += char_width;
|
||||
}
|
||||
|
||||
end = end_candidate;
|
||||
truncate_end = true;
|
||||
}
|
||||
|
||||
/* Here we determine if we should truncate the start of the line. */
|
||||
if (column_start >= TRUNCATE) {
|
||||
pm_buffer_append_string(buffer, "... ", 4);
|
||||
start += column_start;
|
||||
}
|
||||
|
||||
pm_buffer_append_string(buffer, (const char *) start, (size_t) (end - start));
|
||||
|
||||
if (truncate_end) {
|
||||
pm_buffer_append_string(buffer, " ...\n", 5);
|
||||
} else if (end == parser->end && end > parser->start && end[-1] != '\n') {
|
||||
pm_buffer_append_byte(buffer, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pm_rich_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, pm_errors_format_type_t format_type, size_t nerrors, pm_rich_error_t *rich_errors, bool inline_messages) {
|
||||
pm_error_line_format_t format;
|
||||
pm_error_line_format_init(&format, format_type, rich_errors[0].line, rich_errors[nerrors - 1].line);
|
||||
|
||||
/* We are going to iterate through every error in our error list and display
|
||||
* it. While we are iterating, we will display some padding lines of the
|
||||
* source before the error to give some context. We will be careful not to
|
||||
* display the same line twice in case the errors are close enough in the
|
||||
* source. */
|
||||
int32_t last_line = parser->start_line - 1;
|
||||
uint32_t last_column_start = 0;
|
||||
|
||||
for (size_t idx = 0; idx < nerrors; idx++) {
|
||||
pm_rich_error_t *rich_error = rich_errors + idx;
|
||||
|
||||
/* Here we determine how many lines of padding of the source to display,
|
||||
* based on the difference from the last line that was displayed. */
|
||||
if (rich_error->line - last_line > 1) {
|
||||
if (rich_error->line - last_line > 2) {
|
||||
if ((idx != 0) && (rich_error->line - last_line > 3)) {
|
||||
pm_buffer_append_string(buffer, format.divider, format.divider_length);
|
||||
}
|
||||
|
||||
pm_buffer_append_string(buffer, " ", 2);
|
||||
pm_error_format_line(parser, buffer, format.number_prefix, rich_error->line - 2, 0, 0);
|
||||
}
|
||||
|
||||
pm_buffer_append_string(buffer, " ", 2);
|
||||
pm_error_format_line(parser, buffer, format.number_prefix, rich_error->line - 1, 0, 0);
|
||||
}
|
||||
|
||||
/* If this is the first error or we are on a new line, then we will
|
||||
* display the line that has the error in it. */
|
||||
if ((idx == 0) || (rich_error->line != last_line)) {
|
||||
switch (format_type) {
|
||||
case PM_ERRORS_FORMAT_PLAIN:
|
||||
pm_buffer_append_string(buffer, "> ", 2);
|
||||
break;
|
||||
case PM_ERRORS_FORMAT_STYLE:
|
||||
pm_buffer_append_format(buffer, "%s", COLOR_BOLD "> " COLOR_RESET);
|
||||
break;
|
||||
case PM_ERRORS_FORMAT_COLOR:
|
||||
pm_buffer_append_format(buffer, "%s", COLOR_RED "> " COLOR_RESET);
|
||||
break;
|
||||
}
|
||||
|
||||
last_column_start = rich_error->column_start;
|
||||
|
||||
/* Find the maximum column end of all the errors on this line. */
|
||||
uint32_t column_end = rich_error->column_end;
|
||||
for (size_t next_idx = idx + 1; next_idx < nerrors; next_idx++) {
|
||||
if (rich_errors[next_idx].line != rich_error->line) break;
|
||||
if (rich_errors[next_idx].column_end > column_end) column_end = rich_errors[next_idx].column_end;
|
||||
}
|
||||
|
||||
pm_error_format_line(parser, buffer, format.number_prefix, rich_error->line, rich_error->column_start, column_end);
|
||||
}
|
||||
|
||||
const uint8_t *start = parser->start + parser->line_offsets.offsets[rich_error->line - parser->start_line];
|
||||
if (start == parser->end) pm_buffer_append_byte(buffer, '\n');
|
||||
|
||||
/* Now we will display the actual error message. We will do this by
|
||||
* first putting the prefix to the line, then a bunch of blank spaces
|
||||
* depending on the column, then as many tildes as we need to display
|
||||
* the width of the error, then the error message itself.
|
||||
*
|
||||
* Note that this doesn't take into account the width of the actual
|
||||
* character when displayed in the terminal. For some east-asian
|
||||
* languages or emoji, this means it can be thrown off pretty badly. We
|
||||
* will need to solve this eventually. */
|
||||
pm_buffer_append_string(buffer, " ", 2);
|
||||
pm_buffer_append_string(buffer, format.blank_prefix, format.blank_prefix_length);
|
||||
|
||||
size_t column = 0;
|
||||
if (last_column_start >= TRUNCATE) {
|
||||
pm_buffer_append_string(buffer, " ", 4);
|
||||
column = last_column_start;
|
||||
}
|
||||
|
||||
while (column < rich_error->column_start) {
|
||||
pm_buffer_append_byte(buffer, ' ');
|
||||
|
||||
size_t char_width = pm_parser_encoding_char_width(parser, start + column, parser->end - (start + column));
|
||||
column += (char_width == 0 ? 1 : char_width);
|
||||
}
|
||||
|
||||
switch (format_type) {
|
||||
case PM_ERRORS_FORMAT_PLAIN:
|
||||
pm_buffer_append_byte(buffer, '^');
|
||||
break;
|
||||
case PM_ERRORS_FORMAT_STYLE:
|
||||
pm_buffer_append_format(buffer, "%s^", COLOR_BOLD);
|
||||
break;
|
||||
case PM_ERRORS_FORMAT_COLOR:
|
||||
pm_buffer_append_format(buffer, "%s^", COLOR_RED);
|
||||
break;
|
||||
}
|
||||
|
||||
size_t char_width = pm_parser_encoding_char_width(parser, start + column, parser->end - (start + column));
|
||||
column += (char_width == 0 ? 1 : char_width);
|
||||
|
||||
while (column < rich_error->column_end) {
|
||||
pm_buffer_append_byte(buffer, '~');
|
||||
|
||||
size_t char_width = pm_parser_encoding_char_width(parser, start + column, parser->end - (start + column));
|
||||
column += (char_width == 0 ? 1 : char_width);
|
||||
}
|
||||
|
||||
if (format_type != PM_ERRORS_FORMAT_PLAIN) {
|
||||
pm_buffer_append_format(buffer, "%s", COLOR_RESET);
|
||||
}
|
||||
|
||||
if (inline_messages) {
|
||||
const char *message = pm_diagnostic_message(rich_error->diagnostic);
|
||||
|
||||
pm_buffer_append_byte(buffer, ' ');
|
||||
pm_buffer_append_string(buffer, message, strlen(message));
|
||||
}
|
||||
|
||||
pm_buffer_append_byte(buffer, '\n');
|
||||
|
||||
/* Here we determine how many lines of padding to display after the
|
||||
* diagnostic, depending on where the next diagnostic is in source. */
|
||||
last_line = rich_error->line;
|
||||
int32_t next_line;
|
||||
|
||||
if (idx == nerrors - 1) {
|
||||
next_line = ((int32_t) parser->line_offsets.size) + parser->start_line;
|
||||
|
||||
/* If the file ends with a newline, subtract one from our
|
||||
* "next_line" so that we do not output an extra line at the end of
|
||||
* the file. */
|
||||
if ((parser->start + parser->line_offsets.offsets[parser->line_offsets.size - 1]) == parser->end) {
|
||||
next_line--;
|
||||
}
|
||||
} else {
|
||||
next_line = rich_errors[idx + 1].line;
|
||||
}
|
||||
|
||||
if (next_line - last_line > 1) {
|
||||
pm_buffer_append_string(buffer, " ", 2);
|
||||
pm_error_format_line(parser, buffer, format.number_prefix, ++last_line, 0, 0);
|
||||
}
|
||||
|
||||
if (next_line - last_line > 1) {
|
||||
pm_buffer_append_string(buffer, " ", 2);
|
||||
pm_error_format_line(parser, buffer, format.number_prefix, ++last_line, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pm_rich_error_init(const pm_parser_t *parser, pm_rich_error_t *rich_error, pm_diagnostic_t *diagnostic, pm_location_t *location) {
|
||||
const pm_line_offset_list_t *line_offsets = &parser->line_offsets;
|
||||
pm_line_column_t start_line_column = pm_line_offset_list_line_column(line_offsets, location->start, parser->start_line);
|
||||
pm_line_column_t end_line_column = pm_line_offset_list_line_column(line_offsets, location->start + location->length, parser->start_line);
|
||||
|
||||
uint32_t column_end;
|
||||
if (start_line_column.line == end_line_column.line) {
|
||||
column_end = end_line_column.column;
|
||||
} else {
|
||||
column_end = (uint32_t) (line_offsets->offsets[start_line_column.line - parser->start_line + 1] - line_offsets->offsets[start_line_column.line - parser->start_line] - 1);
|
||||
}
|
||||
|
||||
/* Ensure we have at least one column of error. */
|
||||
if (start_line_column.column == column_end) column_end++;
|
||||
|
||||
*rich_error = (pm_rich_error_t) {
|
||||
.diagnostic = diagnostic,
|
||||
.line = start_line_column.line,
|
||||
.column_start = start_line_column.column,
|
||||
.column_end = column_end
|
||||
};
|
||||
}
|
||||
|
||||
pm_error_level_t
|
||||
pm_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, pm_errors_format_type_t format_type) {
|
||||
size_t nerrors = parser->error_list.size;
|
||||
assert(nerrors > 0);
|
||||
|
||||
int filepath_length = (int) pm_string_length(&parser->filepath);
|
||||
const char *filepath = (const char *) pm_string_source(&parser->filepath);
|
||||
|
||||
size_t rich_errors_size = nerrors * sizeof(pm_rich_error_t);
|
||||
if (rich_errors_size / sizeof(pm_rich_error_t) != nerrors) abort();
|
||||
|
||||
pm_rich_error_t *rich_errors = xmalloc(rich_errors_size);
|
||||
if (rich_errors == NULL) abort();
|
||||
|
||||
bool is_utf8 = true;
|
||||
size_t rich_errors_idx = 0;
|
||||
|
||||
for (pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) parser->error_list.head; diagnostic != NULL; diagnostic = (pm_diagnostic_t *) diagnostic->node.next) {
|
||||
pm_location_t location = pm_diagnostic_location(diagnostic);
|
||||
|
||||
switch (pm_diagnostic_error_level(diagnostic)) {
|
||||
case PM_ERROR_LEVEL_SYNTAX: {
|
||||
if (is_utf8 && !location_is_utf8(parser, &location)) {
|
||||
is_utf8 = false;
|
||||
}
|
||||
|
||||
pm_rich_error_init(parser, &rich_errors[rich_errors_idx], diagnostic, &location);
|
||||
rich_errors[rich_errors_idx].idx = rich_errors_idx;
|
||||
rich_errors_idx++;
|
||||
break;
|
||||
}
|
||||
case PM_ERROR_LEVEL_ARGUMENT: {
|
||||
/* If we get an argument error, we want to format it and return it
|
||||
* immediately. They should include a snippet of the source if
|
||||
* possible. */
|
||||
pm_buffer_append_format(
|
||||
buffer,
|
||||
"%.*s:%" PRIi32 ": %s",
|
||||
filepath_length,
|
||||
filepath,
|
||||
pm_line_offset_list_line(&parser->line_offsets, location.start, parser->start_line),
|
||||
pm_diagnostic_message(diagnostic)
|
||||
);
|
||||
|
||||
if (location_is_utf8(parser, &location)) {
|
||||
pm_buffer_append_byte(buffer, '\n');
|
||||
pm_rich_error_init(parser, rich_errors, diagnostic, &location);
|
||||
pm_rich_errors_format(parser, buffer, format_type, 1, rich_errors, false);
|
||||
}
|
||||
|
||||
xfree_sized(rich_errors, rich_errors_size);
|
||||
return PM_ERROR_LEVEL_ARGUMENT;
|
||||
}
|
||||
case PM_ERROR_LEVEL_LOAD: {
|
||||
/* If we get a load error, we want to format it and return it
|
||||
* immediately. It should only include the diagnostic message. */
|
||||
const char *message = pm_diagnostic_message(diagnostic);
|
||||
pm_buffer_append_string(buffer, message, strlen(message));
|
||||
|
||||
xfree_sized(rich_errors, rich_errors_size);
|
||||
return PM_ERROR_LEVEL_LOAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(rich_errors_idx == nerrors);
|
||||
|
||||
/* The header displays the line of the first error in parse order, which
|
||||
* is the first entry in the array because it has not been sorted yet. */
|
||||
pm_buffer_append_format(
|
||||
buffer,
|
||||
"%.*s:%" PRIi32 ": syntax error%s found\n",
|
||||
filepath_length,
|
||||
filepath,
|
||||
rich_errors[0].line,
|
||||
(nerrors > 1) ? "s" : ""
|
||||
);
|
||||
|
||||
if (is_utf8) {
|
||||
/* In here, we have all of the rich diagnostic information and we know
|
||||
* the snippets are valid UTF-8. We will sort the errors so that they
|
||||
* are displayed in the order that they appear in the source, and then
|
||||
* format them all together with the source code snippets. */
|
||||
qsort(rich_errors, nerrors, sizeof(pm_rich_error_t), pm_rich_error_compare);
|
||||
pm_rich_errors_format(parser, buffer, format_type, nerrors, rich_errors, true);
|
||||
} else {
|
||||
/* In here, we have content in the source that is not valid UTF-8. In
|
||||
* that case, we do not want to attempt to format the source code
|
||||
* snippets because we do not know how the terminal will handle it. The
|
||||
* diagnostics are displayed in the order that they were generated,
|
||||
* which is the order of the array before it has been sorted. */
|
||||
for (size_t idx = 0; idx < nerrors; idx++) {
|
||||
pm_rich_error_t *rich_error = &rich_errors[idx];
|
||||
|
||||
if (idx > 0) pm_buffer_append_byte(buffer, '\n');
|
||||
pm_buffer_append_format(
|
||||
buffer,
|
||||
"%.*s:%" PRIi32 ": %s",
|
||||
filepath_length,
|
||||
filepath,
|
||||
rich_error->line,
|
||||
pm_diagnostic_message(rich_error->diagnostic)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
xfree_sized(rich_errors, rich_errors_size);
|
||||
return PM_ERROR_LEVEL_SYNTAX;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @file errors_format.h
|
||||
*
|
||||
* A function for formatting the errors in a parser into a buffer.
|
||||
*/
|
||||
#ifndef PRISM_ERRORS_FORMAT_H
|
||||
#define PRISM_ERRORS_FORMAT_H
|
||||
|
||||
#include "prism/compiler/exported.h"
|
||||
#include "prism/compiler/nodiscard.h"
|
||||
#include "prism/compiler/nonnull.h"
|
||||
|
||||
#include "prism/buffer.h"
|
||||
#include "prism/diagnostic.h"
|
||||
#include "prism/parser.h"
|
||||
|
||||
/** The type of formatting to use when formatting errors. */
|
||||
typedef enum {
|
||||
/** Format errors in a plain format with no colors or styles. Typically
|
||||
* used when outputting to a file or when the output is not a terminal.
|
||||
*/
|
||||
PM_ERRORS_FORMAT_PLAIN = 1,
|
||||
|
||||
/** Format errors in a style format with bold only. Typically used when
|
||||
* outputting to a terminal where you do not want colors.
|
||||
*/
|
||||
PM_ERRORS_FORMAT_STYLE = 2,
|
||||
|
||||
/** Format errors in a color format with bold and colors. The default case
|
||||
* when outputting on the terminal.
|
||||
*/
|
||||
PM_ERRORS_FORMAT_COLOR = 3
|
||||
} pm_errors_format_type_t;
|
||||
|
||||
/**
|
||||
* Format the errors in a parser into a buffer. After return, the buffer will
|
||||
* contain the formatted errors and any relevant source snippets if available
|
||||
* and possible.
|
||||
*
|
||||
* @param parser The parser containing the errors to format. Note that the
|
||||
* parser does not need to be fully initialized, but it will need to have
|
||||
* start, end, start_line, encoding, line_offsets, filepath, and error_list
|
||||
* initialized.
|
||||
* @param buffer The buffer to format the errors into.
|
||||
* @param format_type The type of formatting to use when formatting the errors.
|
||||
* @returns The error level of the error with the highest precedence that was
|
||||
* formatted. In practice this means it will be the argument or load error
|
||||
* level if any were found, otherwise it will be the syntax error level.
|
||||
* Callers should raise appropriate error types based on the returned level.
|
||||
*/
|
||||
PRISM_EXPORTED_FUNCTION pm_error_level_t pm_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, pm_errors_format_type_t format_type) PRISM_NONNULL(1, 2);
|
||||
|
||||
#endif
|
||||
+270
-100
@@ -39,14 +39,52 @@ ID rb_id_option_frozen_string_literal;
|
||||
ID rb_id_option_line;
|
||||
ID rb_id_option_main_script;
|
||||
ID rb_id_option_partial_script;
|
||||
ID rb_id_option_raise_error;
|
||||
ID rb_id_option_scopes;
|
||||
ID rb_id_option_version;
|
||||
|
||||
ID rb_id_source_for;
|
||||
|
||||
ID rb_id_forwarding_positionals;
|
||||
ID rb_id_forwarding_keywords;
|
||||
ID rb_id_forwarding_block;
|
||||
ID rb_id_forwarding_all;
|
||||
|
||||
ID rb_id_raise_error_plain;
|
||||
ID rb_id_raise_error_style;
|
||||
ID rb_id_raise_error_color;
|
||||
|
||||
/******************************************************************************/
|
||||
/* Result struct for working with functions that may need to raise errors. */
|
||||
/******************************************************************************/
|
||||
|
||||
typedef struct {
|
||||
enum {
|
||||
RESULT_OK,
|
||||
RESULT_ERR
|
||||
} type;
|
||||
VALUE value;
|
||||
} result_t;
|
||||
|
||||
static result_t
|
||||
result_ok(VALUE value) {
|
||||
return (result_t) { .type = RESULT_OK, .value = value };
|
||||
}
|
||||
|
||||
static result_t
|
||||
result_err(VALUE value) {
|
||||
return (result_t) { .type = RESULT_ERR, .value = value };
|
||||
}
|
||||
|
||||
static VALUE
|
||||
result_get(result_t result) {
|
||||
if (result.type == RESULT_OK) {
|
||||
return result.value;
|
||||
} else {
|
||||
rb_exc_raise(result.value);
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* IO of Ruby code */
|
||||
/******************************************************************************/
|
||||
@@ -66,7 +104,6 @@ check_string(VALUE value) {
|
||||
return RSTRING_PTR(value);
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************/
|
||||
/* Building C options from Ruby options */
|
||||
/******************************************************************************/
|
||||
@@ -233,6 +270,44 @@ build_options_i(VALUE key, VALUE value, VALUE argument) {
|
||||
if (!NIL_P(value)) pm_options_partial_script_set(options, RTEST(value));
|
||||
} else if (key_id == rb_id_option_freeze) {
|
||||
if (!NIL_P(value)) pm_options_freeze_set(options, RTEST(value));
|
||||
} else if (key_id == rb_id_option_raise_error) {
|
||||
if (!NIL_P(value)) {
|
||||
if (value == Qtrue) {
|
||||
/* When given true, color when $stderr is a terminal (unless
|
||||
* NO_COLOR is set), bold styling when NO_COLOR is set, plain
|
||||
* otherwise.
|
||||
*
|
||||
* rb_stderr_tty_p is not available to extensions, so instead
|
||||
* we call the tty? method on $stderr, guarding against it
|
||||
* having been replaced by an object that does not respond to
|
||||
* tty?. */
|
||||
if (rb_respond_to(rb_stderr, rb_intern_const("tty?")) && RTEST(rb_funcall(rb_stderr, rb_intern_const("tty?"), 0))) {
|
||||
const char *no_color = getenv("NO_COLOR");
|
||||
if (no_color == NULL || no_color[0] == '\0') {
|
||||
pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_COLOR);
|
||||
} else {
|
||||
pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_STYLE);
|
||||
}
|
||||
} else {
|
||||
pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_PLAIN);
|
||||
}
|
||||
} else {
|
||||
if (!SYMBOL_P(value)) {
|
||||
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Symbol)", rb_obj_class(value));
|
||||
}
|
||||
|
||||
ID value_id = SYM2ID(value);
|
||||
if (value_id == rb_id_raise_error_plain) {
|
||||
pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_PLAIN);
|
||||
} else if (value_id == rb_id_raise_error_style) {
|
||||
pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_STYLE);
|
||||
} else if (value_id == rb_id_raise_error_color) {
|
||||
pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_COLOR);
|
||||
} else {
|
||||
rb_raise(rb_eArgError, "invalid raise_error value: %" PRIsVALUE, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, key);
|
||||
}
|
||||
@@ -358,6 +433,56 @@ file_options(int argc, VALUE *argv, pm_options_t *options, VALUE *encoded_filepa
|
||||
return pm_src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given parser has any errors and the raise option was set. If
|
||||
* both are true, format them and return the formatted error wrapped in a
|
||||
* result.
|
||||
*/
|
||||
static result_t
|
||||
check_raise_error_option(pm_parser_t *parser, const pm_options_t *options, rb_encoding *path_encoding) {
|
||||
if (pm_parser_errors_size(parser) == 0) return result_ok(Qnil);
|
||||
|
||||
uint8_t raise_error = pm_options_raise_error(options);
|
||||
if (raise_error == 0) return result_ok(Qnil);
|
||||
|
||||
pm_buffer_t *buffer = pm_buffer_new();
|
||||
if (buffer == NULL) {
|
||||
return result_err(rb_exc_new_cstr(rb_eNoMemError, "failed to allocate memory"));
|
||||
}
|
||||
|
||||
pm_error_level_t error_level = pm_errors_format(parser, buffer, (pm_errors_format_type_t) raise_error);
|
||||
|
||||
/* Copy the message out of the buffer and free it before building any
|
||||
* other Ruby objects so that the buffer cannot be leaked if building the
|
||||
* objects raises. */
|
||||
rb_encoding *message_encoding = (error_level == PM_ERROR_LEVEL_LOAD) ? rb_locale_encoding() : rb_enc_find(pm_parser_encoding_name(parser));
|
||||
VALUE message = rb_enc_str_new(pm_buffer_value(buffer), (long) pm_buffer_length(buffer), message_encoding);
|
||||
pm_buffer_free(buffer);
|
||||
|
||||
VALUE error = Qnil;
|
||||
switch (error_level) {
|
||||
case PM_ERROR_LEVEL_SYNTAX: {
|
||||
error = rb_exc_new_str(rb_eSyntaxError, message);
|
||||
|
||||
rb_encoding *path_encoding_normal = path_encoding == NULL ? rb_utf8_encoding() : path_encoding;
|
||||
VALUE path = rb_enc_str_new((const char *) pm_string_source(pm_options_filepath(options)), pm_string_length(pm_options_filepath(options)), path_encoding_normal);
|
||||
rb_ivar_set(error, rb_intern_const("@path"), path);
|
||||
break;
|
||||
}
|
||||
case PM_ERROR_LEVEL_ARGUMENT: {
|
||||
error = rb_exc_new_str(rb_eArgError, message);
|
||||
break;
|
||||
}
|
||||
case PM_ERROR_LEVEL_LOAD: {
|
||||
error = rb_exc_new_str(rb_eLoadError, message);
|
||||
rb_ivar_set(error, rb_intern_const("@path"), Qnil);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result_err(error);
|
||||
}
|
||||
|
||||
#ifndef PRISM_EXCLUDE_SERIALIZATION
|
||||
|
||||
/******************************************************************************/
|
||||
@@ -367,21 +492,26 @@ file_options(int argc, VALUE *argv, pm_options_t *options, VALUE *encoded_filepa
|
||||
/**
|
||||
* Dump the AST corresponding to the given input to a string.
|
||||
*/
|
||||
static VALUE
|
||||
dump_input(const uint8_t *input, size_t input_length, const pm_options_t *options) {
|
||||
pm_buffer_t *buffer = pm_buffer_new();
|
||||
if (!buffer) {
|
||||
rb_raise(rb_eNoMemError, "failed to allocate memory");
|
||||
}
|
||||
|
||||
static result_t
|
||||
dump_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
|
||||
pm_arena_t *arena = pm_arena_new();
|
||||
pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
|
||||
|
||||
pm_node_t *node = pm_parse(parser);
|
||||
pm_serialize(parser, node, buffer);
|
||||
|
||||
VALUE result = rb_str_new(pm_buffer_value(buffer), pm_buffer_length(buffer));
|
||||
pm_buffer_free(buffer);
|
||||
result_t result = check_raise_error_option(parser, options, path_encoding);
|
||||
if (result.type == RESULT_OK) {
|
||||
pm_buffer_t *buffer = pm_buffer_new();
|
||||
if (buffer) {
|
||||
pm_serialize(parser, node, buffer);
|
||||
result = result_ok(rb_str_new(pm_buffer_value(buffer), pm_buffer_length(buffer)));
|
||||
pm_buffer_free(buffer);
|
||||
|
||||
if (pm_options_freeze(options)) rb_obj_freeze(result.value);
|
||||
} else {
|
||||
result = result_err(rb_exc_new_cstr(rb_eNoMemError, "failed to allocate memory"));
|
||||
}
|
||||
}
|
||||
|
||||
pm_parser_free(parser);
|
||||
pm_arena_free(arena);
|
||||
|
||||
@@ -410,8 +540,7 @@ dump(int argc, VALUE *argv, VALUE self) {
|
||||
source = (const uint8_t *) dup;
|
||||
#endif
|
||||
|
||||
VALUE value = dump_input(source, length, options);
|
||||
if (pm_options_freeze(options)) rb_obj_freeze(value);
|
||||
result_t result = dump_input(source, length, options, NULL);
|
||||
|
||||
#ifdef PRISM_BUILD_DEBUG
|
||||
#ifdef xfree_sized
|
||||
@@ -422,8 +551,7 @@ dump(int argc, VALUE *argv, VALUE self) {
|
||||
#endif
|
||||
|
||||
pm_options_free(options);
|
||||
|
||||
return value;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -441,11 +569,11 @@ dump_file(int argc, VALUE *argv, VALUE self) {
|
||||
VALUE encoded_filepath;
|
||||
pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
|
||||
|
||||
VALUE value = dump_input(pm_source_source(src), pm_source_length(src), options);
|
||||
result_t result = dump_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
|
||||
pm_source_free(src);
|
||||
pm_options_free(options);
|
||||
|
||||
return value;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -780,8 +908,8 @@ parse_lex_encoding_changed_callback(pm_parser_t *parser) {
|
||||
* Parse the given input and return a ParseResult containing just the tokens or
|
||||
* the nodes and tokens.
|
||||
*/
|
||||
static VALUE
|
||||
parse_lex_input(const uint8_t *input, size_t input_length, const pm_options_t *options, bool return_nodes) {
|
||||
static result_t
|
||||
parse_lex_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding, bool return_nodes) {
|
||||
pm_arena_t *arena = pm_arena_new();
|
||||
pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
|
||||
pm_parser_encoding_changed_callback_set(parser, parse_lex_encoding_changed_callback);
|
||||
@@ -802,32 +930,34 @@ parse_lex_input(const uint8_t *input, size_t input_length, const pm_options_t *o
|
||||
|
||||
pm_node_t *node = pm_parse(parser);
|
||||
|
||||
/* Update the Source object with the correct encoding and line offsets,
|
||||
* which are only available after pm_parse() completes. */
|
||||
rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
|
||||
rb_enc_associate(source_string, encoding);
|
||||
result_t result = check_raise_error_option(parser, options, path_encoding);
|
||||
if (result.type == RESULT_OK) {
|
||||
/* Update the Source object with the correct encoding and line offsets,
|
||||
* which are only available after pm_parse() completes. */
|
||||
rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
|
||||
rb_enc_associate(source_string, encoding);
|
||||
|
||||
const pm_line_offset_list_t *line_offsets = pm_parser_line_offsets(parser);
|
||||
for (size_t index = 0; index < line_offsets->size; index++) {
|
||||
rb_ary_store(offsets, (long) index, ULONG2NUM(line_offsets->offsets[index]));
|
||||
}
|
||||
const pm_line_offset_list_t *line_offsets = pm_parser_line_offsets(parser);
|
||||
for (size_t index = 0; index < line_offsets->size; index++) {
|
||||
rb_ary_store(offsets, (long) index, ULONG2NUM(line_offsets->offsets[index]));
|
||||
}
|
||||
|
||||
if (pm_options_freeze(options)) {
|
||||
rb_obj_freeze(source_string);
|
||||
rb_obj_freeze(offsets);
|
||||
rb_obj_freeze(source);
|
||||
rb_obj_freeze(parse_lex_data.tokens);
|
||||
}
|
||||
if (pm_options_freeze(options)) {
|
||||
rb_obj_freeze(source_string);
|
||||
rb_obj_freeze(offsets);
|
||||
rb_obj_freeze(source);
|
||||
rb_obj_freeze(parse_lex_data.tokens);
|
||||
}
|
||||
|
||||
VALUE result;
|
||||
if (return_nodes) {
|
||||
VALUE value = rb_ary_new_capa(2);
|
||||
rb_ary_push(value, pm_ast_new(parser, node, parse_lex_data.encoding, source, pm_options_freeze(options)));
|
||||
rb_ary_push(value, parse_lex_data.tokens);
|
||||
if (pm_options_freeze(options)) rb_obj_freeze(value);
|
||||
result = parse_result_create(rb_cPrismParseLexResult, parser, value, parse_lex_data.encoding, source, pm_options_freeze(options));
|
||||
} else {
|
||||
result = parse_result_create(rb_cPrismLexResult, parser, parse_lex_data.tokens, parse_lex_data.encoding, source, pm_options_freeze(options));
|
||||
if (return_nodes) {
|
||||
VALUE value = rb_ary_new_capa(2);
|
||||
rb_ary_push(value, pm_ast_new(parser, node, parse_lex_data.encoding, source, pm_options_freeze(options)));
|
||||
rb_ary_push(value, parse_lex_data.tokens);
|
||||
if (pm_options_freeze(options)) rb_obj_freeze(value);
|
||||
result = result_ok(parse_result_create(rb_cPrismParseLexResult, parser, value, parse_lex_data.encoding, source, pm_options_freeze(options)));
|
||||
} else {
|
||||
result = result_ok(parse_result_create(rb_cPrismLexResult, parser, parse_lex_data.tokens, parse_lex_data.encoding, source, pm_options_freeze(options)));
|
||||
}
|
||||
}
|
||||
|
||||
pm_parser_free(parser);
|
||||
@@ -849,10 +979,10 @@ lex(int argc, VALUE *argv, VALUE self) {
|
||||
pm_options_t *options = pm_options_new();
|
||||
VALUE string = string_options(argc, argv, options);
|
||||
|
||||
VALUE result = parse_lex_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, false);
|
||||
result_t result = parse_lex_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL, false);
|
||||
pm_options_free(options);
|
||||
|
||||
return result;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -870,11 +1000,11 @@ lex_file(int argc, VALUE *argv, VALUE self) {
|
||||
VALUE encoded_filepath;
|
||||
pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
|
||||
|
||||
VALUE value = parse_lex_input(pm_source_source(src), pm_source_length(src), options, false);
|
||||
result_t result = parse_lex_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath), false);
|
||||
pm_source_free(src);
|
||||
pm_options_free(options);
|
||||
|
||||
return value;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
@@ -884,21 +1014,25 @@ lex_file(int argc, VALUE *argv, VALUE self) {
|
||||
/**
|
||||
* Parse the given input and return a ParseResult instance.
|
||||
*/
|
||||
static VALUE
|
||||
parse_input(const uint8_t *input, size_t input_length, const pm_options_t *options) {
|
||||
static result_t
|
||||
parse_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
|
||||
pm_arena_t *arena = pm_arena_new();
|
||||
pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
|
||||
|
||||
pm_node_t *node = pm_parse(parser);
|
||||
rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
|
||||
|
||||
bool freeze = pm_options_freeze(options);
|
||||
VALUE source = pm_source_new(parser, encoding, freeze);
|
||||
VALUE value = pm_ast_new(parser, node, encoding, source, freeze);
|
||||
VALUE result = parse_result_create(rb_cPrismParseResult, parser, value, encoding, source, freeze);
|
||||
result_t result = check_raise_error_option(parser, options, path_encoding);
|
||||
if (result.type == RESULT_OK) {
|
||||
rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
|
||||
|
||||
if (freeze) {
|
||||
rb_obj_freeze(source);
|
||||
bool freeze = pm_options_freeze(options);
|
||||
VALUE source = pm_source_new(parser, encoding, freeze);
|
||||
VALUE value = pm_ast_new(parser, node, encoding, source, freeze);
|
||||
result = result_ok(parse_result_create(rb_cPrismParseResult, parser, value, encoding, source, freeze));
|
||||
|
||||
if (freeze) {
|
||||
rb_obj_freeze(source);
|
||||
}
|
||||
}
|
||||
|
||||
pm_parser_free(parser);
|
||||
@@ -939,6 +1073,20 @@ parse_input(const uint8_t *input, size_t input_length, const pm_options_t *optio
|
||||
* script that you know will be embedded inside another script later, but
|
||||
* you do not have that context yet. For example, when parsing an ERB
|
||||
* template that will be evaluated inside another script.
|
||||
* * `raise_error` - either nil, true, or a symbol indicating that an error
|
||||
* should be raised if the source contains any errors. The message of
|
||||
* the error will include the source of the lines that contain errors
|
||||
* when possible, and the value of this option controls how those lines
|
||||
* are formatted. Valid values are `:plain` (no formatting), `:style`
|
||||
* (bold formatting), and `:color` (bold and color formatting). When
|
||||
* `true` is given, the formatting is determined by whether or not
|
||||
* `$stderr` is a terminal and whether or not the `NO_COLOR` environment
|
||||
* variable is set, mirroring the behavior of CRuby itself. Syntax-level
|
||||
* errors raise SyntaxError, argument-level errors raise ArgumentError,
|
||||
* and load-level errors raise LoadError. Note that this option is
|
||||
* honored by every API that accepts these options, including predicates
|
||||
* like Prism.parse_success?, which will raise instead of returning a
|
||||
* boolean when the source contains errors.
|
||||
* * `scopes` - the locals that are in scope surrounding the code that is being
|
||||
* parsed. This should be an array of arrays of symbols or nil. Scopes are
|
||||
* ordered from the outermost scope to the innermost one.
|
||||
@@ -966,7 +1114,7 @@ parse(int argc, VALUE *argv, VALUE self) {
|
||||
source = (const uint8_t *) dup;
|
||||
#endif
|
||||
|
||||
VALUE value = parse_input(source, length, options);
|
||||
result_t result = parse_input(source, length, options, NULL);
|
||||
|
||||
#ifdef PRISM_BUILD_DEBUG
|
||||
#ifdef xfree_sized
|
||||
@@ -977,7 +1125,7 @@ parse(int argc, VALUE *argv, VALUE self) {
|
||||
#endif
|
||||
|
||||
pm_options_free(options);
|
||||
return value;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -995,24 +1143,28 @@ parse_file(int argc, VALUE *argv, VALUE self) {
|
||||
VALUE encoded_filepath;
|
||||
pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
|
||||
|
||||
VALUE value = parse_input(pm_source_source(src), pm_source_length(src), options);
|
||||
result_t result = parse_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
|
||||
pm_source_free(src);
|
||||
pm_options_free(options);
|
||||
|
||||
return value;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given input and return nothing.
|
||||
*/
|
||||
static void
|
||||
profile_input(const uint8_t *input, size_t input_length, const pm_options_t *options) {
|
||||
static result_t
|
||||
profile_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
|
||||
pm_arena_t *arena = pm_arena_new();
|
||||
pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
|
||||
|
||||
pm_parse(parser);
|
||||
|
||||
result_t result = check_raise_error_option(parser, options, path_encoding);
|
||||
pm_parser_free(parser);
|
||||
pm_arena_free(arena);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1029,8 +1181,9 @@ profile(int argc, VALUE *argv, VALUE self) {
|
||||
pm_options_t *options = pm_options_new();
|
||||
VALUE string = string_options(argc, argv, options);
|
||||
|
||||
profile_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options);
|
||||
result_t result = profile_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL);
|
||||
pm_options_free(options);
|
||||
result_get(result);
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
@@ -1051,9 +1204,10 @@ profile_file(int argc, VALUE *argv, VALUE self) {
|
||||
VALUE encoded_filepath;
|
||||
pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
|
||||
|
||||
profile_input(pm_source_source(src), pm_source_length(src), options);
|
||||
result_t result = profile_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
|
||||
pm_source_free(src);
|
||||
pm_options_free(options);
|
||||
result_get(result);
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
@@ -1147,38 +1301,46 @@ parse_stream(int argc, VALUE *argv, VALUE self) {
|
||||
pm_parser_t *parser;
|
||||
|
||||
pm_node_t *node = pm_parse_stream(&parser, arena, src, options);
|
||||
rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
|
||||
|
||||
VALUE source = pm_source_new(parser, encoding, pm_options_freeze(options));
|
||||
VALUE value = pm_ast_new(parser, node, encoding, source, pm_options_freeze(options));
|
||||
VALUE result = parse_result_create(rb_cPrismParseResult, parser, value, encoding, source, pm_options_freeze(options));
|
||||
result_t result = check_raise_error_option(parser, options, NULL);
|
||||
if (result.type == RESULT_OK) {
|
||||
rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
|
||||
|
||||
VALUE source = pm_source_new(parser, encoding, pm_options_freeze(options));
|
||||
VALUE value = pm_ast_new(parser, node, encoding, source, pm_options_freeze(options));
|
||||
result = result_ok(parse_result_create(rb_cPrismParseResult, parser, value, encoding, source, pm_options_freeze(options)));
|
||||
}
|
||||
|
||||
pm_source_free(src);
|
||||
pm_parser_free(parser);
|
||||
pm_arena_free(arena);
|
||||
pm_options_free(options);
|
||||
|
||||
return result;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given input and return an array of Comment objects.
|
||||
*/
|
||||
static VALUE
|
||||
parse_input_comments(const uint8_t *input, size_t input_length, const pm_options_t *options) {
|
||||
static result_t
|
||||
parse_input_comments(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
|
||||
pm_arena_t *arena = pm_arena_new();
|
||||
pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
|
||||
|
||||
pm_parse(parser);
|
||||
rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
|
||||
|
||||
VALUE source = pm_source_new(parser, encoding, pm_options_freeze(options));
|
||||
VALUE comments = parser_comments(parser, source, pm_options_freeze(options));
|
||||
result_t result = check_raise_error_option(parser, options, path_encoding);
|
||||
if (result.type == RESULT_OK) {
|
||||
rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
|
||||
|
||||
VALUE source = pm_source_new(parser, encoding, pm_options_freeze(options));
|
||||
result = result_ok(parser_comments(parser, source, pm_options_freeze(options)));
|
||||
}
|
||||
|
||||
pm_parser_free(parser);
|
||||
pm_arena_free(arena);
|
||||
|
||||
return comments;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1194,10 +1356,10 @@ parse_comments(int argc, VALUE *argv, VALUE self) {
|
||||
pm_options_t *options = pm_options_new();
|
||||
VALUE string = string_options(argc, argv, options);
|
||||
|
||||
VALUE result = parse_input_comments((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options);
|
||||
result_t result = parse_input_comments((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL);
|
||||
pm_options_free(options);
|
||||
|
||||
return result;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1215,11 +1377,11 @@ parse_file_comments(int argc, VALUE *argv, VALUE self) {
|
||||
VALUE encoded_filepath;
|
||||
pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
|
||||
|
||||
VALUE value = parse_input_comments(pm_source_source(src), pm_source_length(src), options);
|
||||
result_t result = parse_input_comments(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
|
||||
pm_source_free(src);
|
||||
pm_options_free(options);
|
||||
|
||||
return value;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1242,10 +1404,10 @@ parse_lex(int argc, VALUE *argv, VALUE self) {
|
||||
pm_options_t *options = pm_options_new();
|
||||
VALUE string = string_options(argc, argv, options);
|
||||
|
||||
VALUE value = parse_lex_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, true);
|
||||
result_t result = parse_lex_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL, true);
|
||||
pm_options_free(options);
|
||||
|
||||
return value;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1270,24 +1432,27 @@ parse_lex_file(int argc, VALUE *argv, VALUE self) {
|
||||
VALUE encoded_filepath;
|
||||
pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
|
||||
|
||||
VALUE value = parse_lex_input(pm_source_source(src), pm_source_length(src), options, true);
|
||||
result_t result = parse_lex_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath), true);
|
||||
pm_source_free(src);
|
||||
pm_options_free(options);
|
||||
|
||||
return value;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given input and return true if it parses without errors.
|
||||
*/
|
||||
static VALUE
|
||||
parse_input_success_p(const uint8_t *input, size_t input_length, const pm_options_t *options) {
|
||||
static result_t
|
||||
parse_input_success_p(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
|
||||
pm_arena_t *arena = pm_arena_new();
|
||||
pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
|
||||
|
||||
pm_parse(parser);
|
||||
|
||||
VALUE result = pm_parser_errors_size(parser) == 0 ? Qtrue : Qfalse;
|
||||
result_t result = check_raise_error_option(parser, options, path_encoding);
|
||||
if (result.type == RESULT_OK) {
|
||||
result = result_ok(pm_parser_errors_size(parser) == 0 ? Qtrue : Qfalse);
|
||||
}
|
||||
|
||||
pm_parser_free(parser);
|
||||
pm_arena_free(arena);
|
||||
|
||||
@@ -1307,10 +1472,10 @@ parse_success_p(int argc, VALUE *argv, VALUE self) {
|
||||
pm_options_t *options = pm_options_new();
|
||||
VALUE string = string_options(argc, argv, options);
|
||||
|
||||
VALUE result = parse_input_success_p((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options);
|
||||
result_t result = parse_input_success_p((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL);
|
||||
pm_options_free(options);
|
||||
|
||||
return result;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1341,11 +1506,11 @@ parse_file_success_p(int argc, VALUE *argv, VALUE self) {
|
||||
VALUE encoded_filepath;
|
||||
pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
|
||||
|
||||
VALUE result = parse_input_success_p(pm_source_source(src), pm_source_length(src), options);
|
||||
result_t result = parse_input_success_p(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
|
||||
pm_source_free(src);
|
||||
pm_options_free(options);
|
||||
|
||||
return result;
|
||||
return result_get(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1435,8 +1600,8 @@ string_query_method_name_p(VALUE self, VALUE string) {
|
||||
*/
|
||||
RUBY_FUNC_EXPORTED void
|
||||
Init_prism(void) {
|
||||
// Make sure that the prism library version matches the expected version.
|
||||
// Otherwise something was compiled incorrectly.
|
||||
/* Make sure that the prism library version matches the expected version.
|
||||
* Otherwise something was compiled incorrectly. */
|
||||
if (strcmp(pm_version(), EXPECTED_PRISM_VERSION) != 0) {
|
||||
rb_raise(
|
||||
rb_eRuntimeError,
|
||||
@@ -1447,12 +1612,12 @@ Init_prism(void) {
|
||||
}
|
||||
|
||||
#ifdef HAVE_RB_EXT_RACTOR_SAFE
|
||||
// Mark this extension as Ractor-safe.
|
||||
/* Mark this extension as Ractor-safe. */
|
||||
rb_ext_ractor_safe(true);
|
||||
#endif
|
||||
|
||||
// Grab up references to all of the constants that we're going to need to
|
||||
// reference throughout this extension.
|
||||
/* Grab up references to all of the constants that we are going to need to
|
||||
* reference throughout this extension. */
|
||||
rb_cPrism = rb_define_module("Prism");
|
||||
rb_cPrismNode = rb_define_class_under(rb_cPrism, "Node", rb_cObject);
|
||||
rb_cPrismSource = rb_define_class_under(rb_cPrism, "Source", rb_cObject);
|
||||
@@ -1473,8 +1638,8 @@ Init_prism(void) {
|
||||
|
||||
rb_cPrismCurrentVersionError = rb_const_get(rb_cPrism, rb_intern("CurrentVersionError"));
|
||||
|
||||
// Intern all of the IDs eagerly that we support so that we don't have to do
|
||||
// it every time we parse.
|
||||
/* Intern all of the IDs eagerly that we support so that we do not have to
|
||||
* do it every time we parse. */
|
||||
rb_id_option_command_line = rb_intern_const("command_line");
|
||||
rb_id_option_encoding = rb_intern_const("encoding");
|
||||
rb_id_option_filepath = rb_intern_const("filepath");
|
||||
@@ -1483,20 +1648,26 @@ Init_prism(void) {
|
||||
rb_id_option_line = rb_intern_const("line");
|
||||
rb_id_option_main_script = rb_intern_const("main_script");
|
||||
rb_id_option_partial_script = rb_intern_const("partial_script");
|
||||
rb_id_option_raise_error = rb_intern_const("raise_error");
|
||||
rb_id_option_scopes = rb_intern_const("scopes");
|
||||
rb_id_option_version = rb_intern_const("version");
|
||||
|
||||
rb_id_source_for = rb_intern("for");
|
||||
|
||||
rb_id_forwarding_positionals = rb_intern("*");
|
||||
rb_id_forwarding_keywords = rb_intern("**");
|
||||
rb_id_forwarding_block = rb_intern("&");
|
||||
rb_id_forwarding_all = rb_intern("...");
|
||||
|
||||
rb_id_raise_error_plain = rb_intern_const("plain");
|
||||
rb_id_raise_error_style = rb_intern_const("style");
|
||||
rb_id_raise_error_color = rb_intern_const("color");
|
||||
|
||||
/**
|
||||
* The version of the prism library.
|
||||
*/
|
||||
rb_define_const(rb_cPrism, "VERSION", rb_str_freeze(rb_str_new_cstr(EXPECTED_PRISM_VERSION)));
|
||||
|
||||
// First, the functions that have to do with lexing and parsing.
|
||||
rb_define_singleton_method(rb_cPrism, "lex", lex, -1);
|
||||
rb_define_singleton_method(rb_cPrism, "lex_file", lex_file, -1);
|
||||
rb_define_singleton_method(rb_cPrism, "parse", parse, -1);
|
||||
@@ -1522,6 +1693,5 @@ Init_prism(void) {
|
||||
rb_define_singleton_method(rb_cPrismStringQuery, "constant?", string_query_constant_p, 1);
|
||||
rb_define_singleton_method(rb_cPrismStringQuery, "method_name?", string_query_method_name_p, 1);
|
||||
|
||||
// Next, initialize the other APIs.
|
||||
Init_prism_api_node();
|
||||
}
|
||||
@@ -105,6 +105,18 @@ struct pm_options_t {
|
||||
*/
|
||||
int8_t frozen_string_literal;
|
||||
|
||||
/*
|
||||
* Whether or not the parser should raise an error when it encounters an
|
||||
* error. By default this is off, but can be turned on in the case that you
|
||||
* want nicely formatted syntax errors. The values that this can be set to
|
||||
* are:
|
||||
* - 0 - do not raise an error, just return the errors in the result
|
||||
* - PM_ERRORS_FORMAT_PLAIN - raise a plain error with no formatting
|
||||
* - PM_ERRORS_FORMAT_STYLE - raise an error with bold formatting
|
||||
* - PM_ERRORS_FORMAT_COLOR - raise an error with color formatting
|
||||
*/
|
||||
uint8_t raise_error;
|
||||
|
||||
/*
|
||||
* Whether or not the encoding magic comments should be respected. This is a
|
||||
* niche use-case where you want to parse a file with a specific encoding
|
||||
|
||||
@@ -243,6 +243,20 @@ pm_options_freeze_set(pm_options_t *options, bool freeze) {
|
||||
options->freeze = freeze;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raise_error option on the given options struct.
|
||||
*/
|
||||
uint8_t pm_options_raise_error(const pm_options_t *options) {
|
||||
return options->raise_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the raise_error option on the given options struct.
|
||||
*/
|
||||
void pm_options_raise_error_set(pm_options_t *options, uint8_t raise_error) {
|
||||
options->raise_error = raise_error;
|
||||
}
|
||||
|
||||
// For some reason, GCC analyzer thinks we're leaking allocated scopes and
|
||||
// locals here, even though we definitely aren't. This is a false positive.
|
||||
// Ideally we wouldn't need to suppress this.
|
||||
|
||||
@@ -249,6 +249,22 @@ PRISM_EXPORTED_FUNCTION bool pm_options_freeze(const pm_options_t *options) PRIS
|
||||
*/
|
||||
PRISM_EXPORTED_FUNCTION void pm_options_freeze_set(pm_options_t *options, bool freeze) PRISM_NONNULL(1);
|
||||
|
||||
/**
|
||||
* Get the raise_error option on the given options struct.
|
||||
*
|
||||
* @param options The options struct to get the raise_error value from.
|
||||
* @returns The raise_error value.
|
||||
*/
|
||||
PRISM_EXPORTED_FUNCTION uint8_t pm_options_raise_error(const pm_options_t *options) PRISM_NONNULL(1);
|
||||
|
||||
/**
|
||||
* Set the raise_error option on the given options struct.
|
||||
*
|
||||
* @param options The options struct to set the raise_error value on.
|
||||
* @param raise_error The raise_error value to set.
|
||||
*/
|
||||
PRISM_EXPORTED_FUNCTION void pm_options_raise_error_set(pm_options_t *options, uint8_t raise_error) PRISM_NONNULL(1);
|
||||
|
||||
/**
|
||||
* Allocate and zero out the scopes array on the given options struct.
|
||||
*
|
||||
|
||||
@@ -23375,6 +23375,41 @@ pm_serialize_parse_stream(pm_buffer_t *buffer, pm_source_t *source, const char *
|
||||
pm_options_cleanup(&options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given source and format any errors that are encountered into the
|
||||
* given buffer using the given format type. If the source parses without any
|
||||
* errors, then -1 is returned and the buffer is left empty. Otherwise, the
|
||||
* name of the encoding of the source is written to the buffer, followed by a
|
||||
* null byte, followed by the formatted errors, and the error level of the
|
||||
* error with the highest precedence is returned.
|
||||
*/
|
||||
int8_t
|
||||
pm_serialize_parse_errors_format(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data, pm_errors_format_type_t format_type) {
|
||||
pm_options_t options = { 0 };
|
||||
pm_options_read(&options, data);
|
||||
|
||||
pm_arena_t arena = { 0 };
|
||||
pm_parser_t parser;
|
||||
pm_parser_init(&arena, &parser, source, size, &options);
|
||||
|
||||
pm_parse(&parser);
|
||||
|
||||
int8_t result = -1;
|
||||
if (parser.error_list.size > 0) {
|
||||
const char *encoding_name = parser.encoding->name;
|
||||
pm_buffer_append_string(buffer, encoding_name, strlen(encoding_name));
|
||||
pm_buffer_append_byte(buffer, '\0');
|
||||
|
||||
result = (int8_t) pm_errors_format(&parser, buffer, format_type);
|
||||
}
|
||||
|
||||
pm_parser_cleanup(&parser);
|
||||
pm_arena_cleanup(&arena);
|
||||
pm_options_cleanup(&options);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and serialize the comments in the given source to the given buffer.
|
||||
*/
|
||||
|
||||
@@ -14,6 +14,7 @@ extern "C" {
|
||||
#include "prism/ast.h"
|
||||
#include "prism/buffer.h"
|
||||
#include "prism/diagnostic.h"
|
||||
#include "prism/errors_format.h"
|
||||
#include "prism/json.h"
|
||||
#include "prism/node.h"
|
||||
#include "prism/options.h"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "prism/compiler/nonnull.h"
|
||||
|
||||
#include "prism/buffer.h"
|
||||
#include "prism/errors_format.h"
|
||||
#include "prism/parser.h"
|
||||
#include "prism/source.h"
|
||||
#include "prism/stream.h"
|
||||
@@ -91,6 +92,24 @@ PRISM_EXPORTED_FUNCTION void pm_serialize_parse_lex(pm_buffer_t *buffer, const u
|
||||
*/
|
||||
PRISM_EXPORTED_FUNCTION bool pm_serialize_parse_success_p(const uint8_t *source, size_t size, const char *data) PRISM_NONNULL(1);
|
||||
|
||||
/**
|
||||
* Parse the given source and format any errors that are encountered into the
|
||||
* given buffer using the given format type. If the source parses without any
|
||||
* errors, then -1 is returned and the buffer is left empty. Otherwise, the
|
||||
* name of the encoding of the source is written to the buffer, followed by a
|
||||
* null byte, followed by the formatted errors, and the error level of the
|
||||
* error with the highest precedence is returned.
|
||||
*
|
||||
* @param buffer The buffer to write the encoding name and formatted errors to.
|
||||
* @param source The source to parse.
|
||||
* @param size The size of the source.
|
||||
* @param data The optional data to pass to the parser.
|
||||
* @param format_type The type of formatting to use when formatting the errors.
|
||||
* @returns The error level of the error with the highest precedence, or -1 if
|
||||
* the source parsed without errors.
|
||||
*/
|
||||
PRISM_EXPORTED_FUNCTION int8_t pm_serialize_parse_errors_format(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data, pm_errors_format_type_t format_type) PRISM_NONNULL(1, 2);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,157 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../test_helper"
|
||||
|
||||
module Prism
|
||||
class RaiseErrorTest < TestCase
|
||||
def test_option_not_set
|
||||
assert_nothing_raised { Prism.parse("foo = 1 +") }
|
||||
assert_nothing_raised { Prism.parse("foo = 1 +", raise_error: nil) }
|
||||
end
|
||||
|
||||
def test_option_invalid
|
||||
error = assert_raise(ArgumentError) { Prism.parse("foo = 1", raise_error: :bogus) }
|
||||
assert_includes error.message, "invalid raise_error value: bogus"
|
||||
|
||||
error = assert_raise(TypeError) { Prism.parse("foo = 1", raise_error: "plain") }
|
||||
assert_equal "wrong argument type String (expected Symbol)", error.message
|
||||
end
|
||||
|
||||
def test_option_misspelled
|
||||
error = assert_raise(ArgumentError) { Prism.parse("foo = 1 +", raise_errors: :plain) }
|
||||
assert_includes error.message, "unknown keyword"
|
||||
end
|
||||
|
||||
def test_syntax_error_single
|
||||
error = assert_raise(SyntaxError) { Prism.parse("foo(\n", raise_error: :plain, filepath: "single.rb") }
|
||||
assert_includes error.message, "syntax error found"
|
||||
assert_syntax_error_path "single.rb", error
|
||||
end
|
||||
|
||||
def test_syntax_error_multiple
|
||||
error = assert_raise(SyntaxError) { Prism.parse("foo = 1 +\n", raise_error: :plain, filepath: "test.rb") }
|
||||
assert_includes error.message, "syntax errors found"
|
||||
end
|
||||
|
||||
def test_syntax_error_true
|
||||
# The formatting is determined by whether or not $stderr is a terminal
|
||||
# and the NO_COLOR environment variable, so only assert that it raises.
|
||||
assert_raise(SyntaxError) { Prism.parse("foo = 1 +\n", raise_error: true) }
|
||||
end
|
||||
|
||||
def test_syntax_error_style
|
||||
error = assert_raise(SyntaxError) { Prism.parse("foo = 1 +\n", raise_error: :style) }
|
||||
assert_include error.message, "\e[m"
|
||||
assert_not_include error.message, "\e[1;31m"
|
||||
end
|
||||
|
||||
def test_syntax_error_color
|
||||
error = assert_raise(SyntaxError) { Prism.parse("foo = 1 +\n", raise_error: :color) }
|
||||
assert_include error.message, "\e[1;31m"
|
||||
end
|
||||
|
||||
def test_syntax_error_not_utf8
|
||||
error = assert_raise(SyntaxError) { Prism.parse("foo = 1 + \xFF\xFE\n".b, raise_error: :plain, filepath: "bin.rb") }
|
||||
assert_include error.message, "invalid multibyte character"
|
||||
assert_not_include error.message, "> 1 |"
|
||||
end
|
||||
|
||||
def test_argument_error
|
||||
assert_raise(ArgumentError) { Prism.parse("# encoding: bogus\n1 + 1\n", raise_error: :plain, filepath: "enc.rb") }
|
||||
end
|
||||
|
||||
def test_load_error
|
||||
error = assert_raise(LoadError) { Prism.parse("foo = 1\n", raise_error: :plain, command_line: "x") }
|
||||
assert_nil error.path
|
||||
end
|
||||
|
||||
def test_string_apis
|
||||
source = "foo = 1 +\n"
|
||||
apis = [
|
||||
-> { Prism.parse(source, raise_error: :plain) },
|
||||
-> { Prism.lex(source, raise_error: :plain) },
|
||||
-> { Prism.parse_lex(source, raise_error: :plain) },
|
||||
-> { Prism.parse_comments(source, raise_error: :plain) },
|
||||
-> { Prism.profile(source, raise_error: :plain) },
|
||||
-> { Prism.parse_success?(source, raise_error: :plain) },
|
||||
-> { Prism.parse_failure?(source, raise_error: :plain) },
|
||||
-> { Prism.parse_stream(StringIO.new(source), raise_error: :plain) }
|
||||
]
|
||||
|
||||
if !ENV["PRISM_BUILD_MINIMAL"]
|
||||
apis << -> { Prism.dump(source, raise_error: :plain) }
|
||||
end
|
||||
|
||||
apis.each do |api|
|
||||
error = assert_raise(SyntaxError, &api)
|
||||
assert_syntax_error_path "", error
|
||||
end
|
||||
end
|
||||
|
||||
def test_file_apis
|
||||
Tempfile.create(["raise_error", ".rb"]) do |file|
|
||||
file.write("foo = 1 +\n")
|
||||
file.close
|
||||
|
||||
filepath = file.path
|
||||
apis = [
|
||||
-> { Prism.parse_file(filepath, raise_error: :plain) },
|
||||
-> { Prism.lex_file(filepath, raise_error: :plain) },
|
||||
-> { Prism.parse_lex_file(filepath, raise_error: :plain) },
|
||||
-> { Prism.parse_file_comments(filepath, raise_error: :plain) },
|
||||
-> { Prism.profile_file(filepath, raise_error: :plain) },
|
||||
-> { Prism.parse_file_success?(filepath, raise_error: :plain) },
|
||||
-> { Prism.parse_file_failure?(filepath, raise_error: :plain) }
|
||||
]
|
||||
|
||||
if !ENV["PRISM_BUILD_MINIMAL"]
|
||||
apis << -> { Prism.dump_file(filepath, raise_error: :plain) }
|
||||
end
|
||||
|
||||
apis.each do |api|
|
||||
error = assert_raise(SyntaxError, &api)
|
||||
assert_syntax_error_path filepath, error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_valid_source
|
||||
source = "foo = 1\n"
|
||||
|
||||
Tempfile.create(["raise_error", ".rb"]) do |file|
|
||||
file.write(source)
|
||||
file.close
|
||||
|
||||
assert_nothing_raised do
|
||||
Prism.parse(source, raise_error: :plain)
|
||||
Prism.lex(source, raise_error: :plain)
|
||||
Prism.parse_lex(source, raise_error: :plain)
|
||||
Prism.parse_comments(source, raise_error: :plain)
|
||||
Prism.profile(source, raise_error: :plain)
|
||||
Prism.parse_success?(source, raise_error: :plain)
|
||||
Prism.parse_stream(StringIO.new(source), raise_error: :plain)
|
||||
Prism.parse_file(file.path, raise_error: :plain)
|
||||
|
||||
if !ENV["PRISM_BUILD_MINIMAL"]
|
||||
Prism.dump(source, raise_error: :plain)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
if RUBY_ENGINE == "truffleruby"
|
||||
def assert_syntax_error_path(expected, error)
|
||||
end
|
||||
elsif SyntaxError.method_defined?(:path)
|
||||
def assert_syntax_error_path(expected, error)
|
||||
assert_equal expected, error.path
|
||||
end
|
||||
else
|
||||
def assert_syntax_error_path(expected, error)
|
||||
assert_equal expected, error.instance_variable_get(:@path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -13,6 +13,7 @@ module Prism
|
||||
test_helper.rb
|
||||
unescape_test.rb
|
||||
api/parse_stream_test.rb
|
||||
api/raise_error_test.rb
|
||||
encoding/regular_expression_encoding_test.rb
|
||||
encoding/string_encoding_test.rb
|
||||
result/breadth_first_search_test.rb
|
||||
|
||||
Reference in new issue
Block a user