diff --git a/Makefile b/Makefile index 03a1aafd34ff..7e8070d16921 100644 --- a/Makefile +++ b/Makefile @@ -139,31 +139,9 @@ llvm_ext: $(LLVM_EXT_OBJ) format: ## Format sources ./bin/crystal tool format$(if $(check), --check) src spec samples scripts -generate_data: ## Run generator scripts for Unicode, SSL config, ... (usually with `-B`/`--always-make` flag) - -generate_data: spec/std/string/graphemes_break_spec.cr -spec/std/string/graphemes_break_spec.cr: scripts/generate_grapheme_break_specs.cr - $(CRYSTAL) run $< - -generate_data: src/string/grapheme/properties.cr -src/string/grapheme/properties.cr: scripts/generate_grapheme_properties.cr - $(CRYSTAL) run $< - -generate_data: src/openssl/ssl/defaults.cr -src/openssl/ssl/defaults.cr: scripts/generate_ssl_server_defaults.cr - $(CRYSTAL) run $< - -generate_data: src/unicode/data.cr -src/unicode/data.cr: scripts/generate_unicode_data.cr - $(CRYSTAL) run $< - -generate_data: src/crystal/system/win32/zone_names.cr -src/crystal/system/win32/zone_names.cr: scripts/generate_windows_zone_names.cr - $(CRYSTAL) run $< - -generate_data: src/html/entities.cr -src/html/entities.cr: scripts/generate_html_entities.cr scripts/html_entities.ecr - $(CRYSTAL) run $< +.PHONY: generate_data +generate_data: ## Run generator scripts for Unicode, SSL config, ... + $(MAKE) -B -f scripts/generate_data.mk .PHONY: install install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR diff --git a/Makefile.win b/Makefile.win index 595a26f5fd00..e2b18741657e 100644 --- a/Makefile.win +++ b/Makefile.win @@ -35,7 +35,7 @@ static ?= ## Enable static linking target ?= ## Cross-compilation target interpreter ?= ## Enable interpreter feature check ?= ## Enable only check when running format -order ?= ## Enable order for spec execution (values: "default" | "random" | seed number) +order ?=random ## Enable order for spec execution (values: "default" | "random" | seed number) MAKEFLAGS += --no-builtin-rules .SUFFIXES: @@ -141,31 +141,9 @@ llvm_ext: $(LLVM_EXT_OBJ) format: ## Format sources .\bin\crystal tool format$(if $(check), --check) src spec samples scripts -generate_data: ## Run generator scripts for Unicode, SSL config, ... (usually with `-B`/`--always-make` flag) - -generate_data: spec\std\string\graphemes_break_spec.cr -spec\std\string\graphemes_break_spec.cr: scripts\generate_grapheme_break_specs.cr - $(CRYSTAL) run $< - -generate_data: src\string\grapheme\properties.cr -src\string\grapheme\properties.cr: scripts\generate_grapheme_properties.cr - $(CRYSTAL) run $< - -generate_data: src\openssl\ssl\defaults.cr -src\openssl\ssl\defaults.cr: scripts\generate_ssl_server_defaults.cr - $(CRYSTAL) run $< - -generate_data: src\unicode\data.cr -src\unicode\data.cr: scripts\generate_unicode_data.cr - $(CRYSTAL) run $< - -generate_data: src\crystal\system\win32\zone_names.cr -src\crystal\system\win32\zone_names.cr: scripts\generate_windows_zone_names.cr - $(CRYSTAL) run $< - -generate_data: src\html\entities.cr -src\html\entities.cr: scripts\generate_html_entities.cr scripts\html_entities.ecr - $(CRYSTAL) run $< +.PHONY: generate_data +generate_data: ## Run generator scripts for Unicode, SSL config, ... + $(MAKE) -B -f scripts/generate_data.mk .PHONY: install install: $(O)\crystal.exe ## Install the compiler at prefix diff --git a/scripts/generate_data.mk b/scripts/generate_data.mk new file mode 100644 index 000000000000..ad04f0fd0c82 --- /dev/null +++ b/scripts/generate_data.mk @@ -0,0 +1,37 @@ + +## Run all data generators +## $ make -f scripts/generate_data.mk + +ifeq ($(OS),Windows_NT) + BIN_CRYSTAL=bin\crystal +else + BIN_CRYSTAL=bin/crystal +endif + +.PHONY: all +all: ## Run all generators + $(BIN_CRYSTAL) run scripts/generate_grapheme_break_specs.cr + $(BIN_CRYSTAL) run scripts/generate_grapheme_properties.cr + $(BIN_CRYSTAL) run scripts/generate_ssl_server_defaults.cr + $(BIN_CRYSTAL) run scripts/generate_unicode_data.cr + $(BIN_CRYSTAL) run scripts/generate_windows_zone_names.cr + $(BIN_CRYSTAL) run scripts/generate_html_entities.cr + +ifneq ($(OS),Windows_NT) +.PHONY: help +help: ## Show this help + @echo + @printf '\033[34mtargets:\033[0m\n' + @grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) |\ + sort |\ + awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' + @echo + @printf '\033[34moptional variables:\033[0m\n' + @grep -hE '^[a-zA-Z_-]+ \?=.*?## .*$$' $(MAKEFILE_LIST) |\ + sort |\ + awk 'BEGIN {FS = " \\?=.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' + @echo + @printf '\033[34mrecipes:\033[0m\n' + @grep -hE '^##.*$$' $(MAKEFILE_LIST) |\ + awk 'BEGIN {FS = "## "}; /^## [a-zA-Z_-]/ {printf " \033[36m%s\033[0m\n", $$2}; /^## / {printf " %s\n", $$2}' +endif diff --git a/spec/compiler/interpreter/sizeof_spec.cr b/spec/compiler/interpreter/sizeof_spec.cr index 4eb5216b28e5..4c52247b9687 100644 --- a/spec/compiler/interpreter/sizeof_spec.cr +++ b/spec/compiler/interpreter/sizeof_spec.cr @@ -7,4 +7,16 @@ describe Crystal::Repl::Interpreter do interpret("sizeof(typeof(1))").should eq(4) end end + + context "instance_sizeof" do + it "interprets instance_sizeof typeof" do + interpret(<<-CRYSTAL).should eq(16) + class Foo + @x = 0_i64 + end + + instance_sizeof(typeof(Foo.new)) + CRYSTAL + end + end end diff --git a/spec/compiler/semantic/c_type_spec.cr b/spec/compiler/semantic/c_type_spec.cr index dd034ae231e7..cb2e367a9593 100644 --- a/spec/compiler/semantic/c_type_spec.cr +++ b/spec/compiler/semantic/c_type_spec.cr @@ -2,24 +2,50 @@ require "../../spec_helper" describe "Semantic: type" do it "can call methods of original type" do - assert_type(" + assert_type(<<-CRYSTAL, inject_primitives: true) { uint64 } lib Lib type X = Void* fun foo : X end Lib.foo.address - ", inject_primitives: true) { uint64 } + CRYSTAL end it "can call methods of parent type" do - assert_error(" + assert_error(<<-CRYSTAL, "undefined method 'baz'") lib Lib type X = Void* fun foo : X end Lib.foo.baz - ", "undefined method 'baz'") + CRYSTAL + end + + it "can access instance variables of original type" do + assert_type(<<-CRYSTAL) { int32 } + lib Lib + struct X + x : Int32 + end + + type Y = X + fun foo : Y + end + + Lib.foo.@x + CRYSTAL + end + + it "errors if original type doesn't support instance variables" do + assert_error(<<-CRYSTAL, "can't use instance variables inside primitive types (at Int32)") + lib Lib + type X = Int32 + fun foo : X + end + + Lib.foo.@x + CRYSTAL end end diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr index 8a86939efbac..68eba643a571 100644 --- a/spec/std/big/big_int_spec.cr +++ b/spec/std/big/big_int_spec.cr @@ -283,6 +283,34 @@ describe "BigInt" do -5.to_big_i.remainder(-3).should eq(-2) end + it "#bit" do + x = 123.to_big_i + x.bit(-10.to_big_i ** 99).should eq(0) + x.bit(-(2.to_big_i ** 64)).should eq(0) + x.bit(-1).should eq(0) + x.bit(0).should eq(1) + x.bit(2).should eq(0) + x.bit(3).should eq(1) + x.bit(6).should eq(1) + x.bit(7).should eq(0) + x.bit(64).should eq(0) + x.bit(2.to_big_i ** 64).should eq(0) + x.bit(10.to_big_i ** 99).should eq(0) + + x = ~(123.to_big_i) + x.bit(-10.to_big_i ** 99).should eq(0) + x.bit(-(2.to_big_i ** 64)).should eq(0) + x.bit(-1).should eq(0) + x.bit(0).should eq(0) + x.bit(2).should eq(1) + x.bit(3).should eq(0) + x.bit(6).should eq(0) + x.bit(7).should eq(1) + x.bit(64).should eq(1) + x.bit(2.to_big_i ** 64).should eq(1) + x.bit(10.to_big_i ** 99).should eq(1) + end + it "does bitwise and" do (123.to_big_i & 321).should eq(65) (BigInt.new("96238761238973286532") & 86325735648).should eq(69124358272) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 53d28bdcee4e..3c020fee36a1 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -941,7 +941,7 @@ describe "File" do pending! "Spec cannot run as superuser" end {% end %} - expect_raises(File::AccessDeniedError) { File.read(path) } + expect_raises(File::AccessDeniedError, "Error opening file with mode 'r': '#{path.inspect_unquoted}'") { File.read(path) } end end {% end %} @@ -958,7 +958,7 @@ describe "File" do pending! "Spec cannot run as superuser" end {% end %} - expect_raises(File::AccessDeniedError) { File.write(path, "foo") } + expect_raises(File::AccessDeniedError, "Error opening file with mode 'w': '#{path.inspect_unquoted}'") { File.write(path, "foo") } end end diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 8a372a370677..178fa81a172c 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2343,6 +2343,33 @@ describe "String" do "foo".matches?(/bar/).should eq(false) end + it "#matches_full?" do + pending! if {{ Regex::Engine.resolve.name == "Regex::PCRE" }} + "foo".matches_full?(/foo/).should be_true + "fooo".matches_full?(/foo/).should be_false + "ofoo".matches_full?(/foo/).should be_false + "pattern".matches_full?(/(\A)?pattern(\z)?/).should be_true + "_pattern_".matches_full?(/(\A)?pattern(\z)?/).should be_false + end + + it "#match_full" do + pending! if {{ Regex::Engine.resolve.name == "Regex::PCRE" }} + "foo".match_full(/foo/).not_nil![0].should eq "foo" + "fooo".match_full(/foo/).should be_nil + "ofoo".match_full(/foo/).should be_nil + "pattern".match_full(/(\A)?pattern(\z)?/).not_nil![0].should eq "pattern" + "_pattern_".match_full(/(\A)?pattern(\z)?/).should be_nil + end + + it "#match_full!" do + pending! if {{ Regex::Engine.resolve.name == "Regex::PCRE" }} + "foo".match_full!(/foo/).not_nil![0].should eq "foo" + expect_raises(Regex::Error) { "fooo".match_full!(/foo/) } + expect_raises(Regex::Error) { "ofoo".match_full!(/foo/) } + "pattern".match_full!(/(\A)?pattern(\z)?/).not_nil![0].should eq "pattern" + expect_raises(Regex::Error) { "_pattern_".match_full!(/(\A)?pattern(\z)?/) } + end + it "has size (same as size)" do "ใƒ†ใ‚นใƒˆ".size.should eq(3) end diff --git a/src/big/big_int.cr b/src/big/big_int.cr index b0530c7b7592..e6b5f0214297 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -385,6 +385,12 @@ struct BigInt < Int BigInt.new { |mpz| LibGMP.com(mpz, self) } end + def bit(bit : Int) + return 0 if bit < 0 + return self < 0 ? 1 : 0 if bit > LibGMP::BitcntT::MAX + LibGMP.tstbit(self, LibGMP::BitcntT.new!(bit)) + end + def &(other : BigInt) : BigInt BigInt.new { |mpz| LibGMP.and(mpz, self, other) } end diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 9caf408f1494..3cae0de64b77 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -116,6 +116,8 @@ lib LibGMP fun xor = __gmpz_xor(rop : MPZ*, op1 : MPZ*, op2 : MPZ*) fun com = __gmpz_com(rop : MPZ*, op : MPZ*) + fun tstbit = __gmpz_tstbit(op : MPZ*, bit_index : BitcntT) : Int + fun fdiv_q_2exp = __gmpz_fdiv_q_2exp(q : MPZ*, n : MPZ*, b : BitcntT) fun mul_2exp = __gmpz_mul_2exp(rop : MPZ*, op1 : MPZ*, op2 : BitcntT) diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 1faf5db32bb6..03d7430a0fe6 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -1422,6 +1422,14 @@ class Crystal::Repl::Compiler < Crystal::Visitor false end + def visit(node : InstanceSizeOf) + return false unless @wants_value + + put_i32 inner_instance_sizeof_type(node.exp), node: node + + false + end + def visit(node : TypeNode) return false unless @wants_value @@ -3361,25 +3369,8 @@ class Crystal::Repl::Compiler < Crystal::Visitor @instructions.instructions.size end - private def aligned_sizeof_type(node : ASTNode) : Int32 - @context.aligned_sizeof_type(node) - end - - private def aligned_sizeof_type(type : Type?) : Int32 - @context.aligned_sizeof_type(type) - end - - private def inner_sizeof_type(node : ASTNode) : Int32 - @context.inner_sizeof_type(node) - end - - private def inner_sizeof_type(type : Type?) : Int32 - @context.inner_sizeof_type(type) - end - - private def aligned_instance_sizeof_type(type : Type) : Int32 - @context.aligned_instance_sizeof_type(type) - end + private delegate inner_sizeof_type, aligned_sizeof_type, + inner_instance_sizeof_type, aligned_instance_sizeof_type, to: @context private def ivar_offset(type : Type, name : String) : Int32 if type.extern_union? diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr index e91b615713f2..268d67448125 100644 --- a/src/compiler/crystal/interpreter/context.cr +++ b/src/compiler/crystal/interpreter/context.cr @@ -316,7 +316,19 @@ class Crystal::Repl::Context end def aligned_instance_sizeof_type(type : Type) : Int32 - align(@program.instance_size_of(type.sizeof_type).to_i32) + align(inner_instance_sizeof_type(type)) + end + + def inner_instance_sizeof_type(node : ASTNode) : Int32 + inner_instance_sizeof_type(node.type?) + end + + def inner_instance_sizeof_type(type : Type) : Int32 + @program.instance_size_of(type.sizeof_type).to_i32 + end + + def inner_instance_sizeof_type(type : Nil) : Int32 + 0 end def offset_of(type : Type, index : Int32) : Int32 diff --git a/src/compiler/crystal/tools/doc/html/type.html b/src/compiler/crystal/tools/doc/html/type.html index cd8115e74f5c..10c7e51fedd3 100644 --- a/src/compiler/crystal/tools/doc/html/type.html +++ b/src/compiler/crystal/tools/doc/html/type.html @@ -95,8 +95,8 @@

<%= MethodSummaryTemplate.new("Constructors", type.constructors) %> <%= MethodSummaryTemplate.new(type.program? ? "Method Summary" : "Class Method Summary", type.class_methods) %> -<%= MethodSummaryTemplate.new("Instance Method Summary", type.instance_methods) %> <%= MethodSummaryTemplate.new("Macro Summary", type.macros) %> +<%= MethodSummaryTemplate.new("Instance Method Summary", type.instance_methods) %>
<% type.ancestors.each do |ancestor| %> @@ -109,8 +109,8 @@

<%= MethodDetailTemplate.new("Constructor Detail", type.constructors) %> <%= MethodDetailTemplate.new(type.program? ? "Method Detail" : "Class Method Detail", type.class_methods) %> -<%= MethodDetailTemplate.new("Instance Method Detail", type.instance_methods) %> <%= MethodDetailTemplate.new("Macro Detail", type.macros) %> +<%= MethodDetailTemplate.new("Instance Method Detail", type.instance_methods) %>

diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 7d4bc9795a7f..ad9f3d391fa6 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -2700,7 +2700,7 @@ module Crystal end delegate remove_typedef, pointer?, defs, - macros, reference_like?, parents, to: typedef + macros, reference_like?, parents, lookup_instance_var, to: typedef def remove_indirection self diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index 665a61b22493..ca04693d0b1a 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -239,7 +239,7 @@ module Crystal::System::File sleep 0.1 end else - flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked") + flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked", target: self) end end @@ -251,7 +251,7 @@ module Crystal::System::File if errno.in?(Errno::EAGAIN, Errno::EWOULDBLOCK) false else - raise IO::Error.from_os_error("Error applying or removing file lock", errno) + raise IO::Error.from_os_error("Error applying or removing file lock", errno, target: self) end end end @@ -269,7 +269,7 @@ module Crystal::System::File end if ret != 0 - raise IO::Error.from_errno("Error syncing file") + raise IO::Error.from_errno("Error syncing file", target: self) end end end diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index d77708f314bb..d3995c205c3e 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -15,7 +15,7 @@ module Crystal::System::FileDescriptor evented_read(slice, "Error reading file") do LibC.read(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF - raise IO::Error.new "File not open for reading" + raise IO::Error.new "File not open for reading", target: self end end end @@ -25,7 +25,7 @@ module Crystal::System::FileDescriptor evented_write(slice, "Error writing file") do |slice| LibC.write(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF - raise IO::Error.new "File not open for writing" + raise IO::Error.new "File not open for writing", target: self end end end @@ -90,13 +90,13 @@ module Crystal::System::FileDescriptor seek_value = LibC.lseek(fd, offset, whence) if seek_value == -1 - raise IO::Error.from_errno "Unable to seek" + raise IO::Error.from_errno "Unable to seek", target: self end end private def system_pos pos = LibC.lseek(fd, 0, IO::Seek::Current).to_i64 - raise IO::Error.from_errno "Unable to tell" if pos == -1 + raise IO::Error.from_errno("Unable to tell", target: self) if pos == -1 pos end @@ -147,7 +147,7 @@ module Crystal::System::FileDescriptor when Errno::EINTR, Errno::EINPROGRESS # ignore else - raise IO::Error.from_errno("Error closing file") + raise IO::Error.from_errno("Error closing file", target: self) end end end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 4fceee02c6d4..43b2dd8142ec 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -493,7 +493,7 @@ module Crystal::System::File if winerror == WinError::ERROR_LOCK_VIOLATION false else - raise IO::Error.from_os_error("LockFileEx", winerror) + raise IO::Error.from_os_error("LockFileEx", winerror, target: self) end end end @@ -509,7 +509,7 @@ module Crystal::System::File private def system_fsync(flush_metadata = true) : Nil if LibC._commit(fd) != 0 - raise IO::Error.from_errno("Error syncing file") + raise IO::Error.from_errno("Error syncing file", target: self) end end end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 84cfe12c6650..6e4f42d46b17 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -15,9 +15,9 @@ module Crystal::System::FileDescriptor bytes_read = LibC._read(fd, slice, slice.size) if bytes_read == -1 if Errno.value == Errno::EBADF - raise IO::Error.new "File not open for reading" + raise IO::Error.new "File not open for reading", target: self else - raise IO::Error.from_errno("Error reading file") + raise IO::Error.from_errno("Error reading file", target: self) end end bytes_read @@ -36,9 +36,9 @@ module Crystal::System::FileDescriptor bytes_written = LibC._write(fd, slice, slice.size) if bytes_written == -1 if Errno.value == Errno::EBADF - raise IO::Error.new "File not open for writing" + raise IO::Error.new "File not open for writing", target: self else - raise IO::Error.from_errno("Error writing file") + raise IO::Error.from_errno("Error writing file", target: self) end end else @@ -106,7 +106,7 @@ module Crystal::System::FileDescriptor if file_type == LibC::FILE_TYPE_UNKNOWN error = WinError.value - raise IO::Error.from_os_error("Unable to get info", error) unless error == WinError::ERROR_SUCCESS + raise IO::Error.from_os_error("Unable to get info", error, target: self) unless error == WinError::ERROR_SUCCESS end end @@ -129,13 +129,13 @@ module Crystal::System::FileDescriptor seek_value = LibC._lseeki64(fd, offset, whence) if seek_value == -1 - raise IO::Error.from_errno "Unable to seek" + raise IO::Error.from_errno "Unable to seek", target: self end end private def system_pos pos = LibC._lseeki64(fd, 0, IO::Seek::Current) - raise IO::Error.from_errno "Unable to tell" if pos == -1 + raise IO::Error.from_errno("Unable to tell", target: self) if pos == -1 pos end @@ -165,7 +165,7 @@ module Crystal::System::FileDescriptor when Errno::EINTR # ignore else - raise IO::Error.from_errno("Error closing file") + raise IO::Error.from_errno("Error closing file", target: self) end end end @@ -204,7 +204,7 @@ module Crystal::System::FileDescriptor if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0 error = WinError.value return 0_i64 if error == WinError::ERROR_HANDLE_EOF - raise IO::Error.from_os_error "Error reading file", error + raise IO::Error.from_os_error "Error reading file", error, target: self end bytes_read.to_i64 diff --git a/src/file/error.cr b/src/file/error.cr index 79a0ec5978dc..355496488bc4 100644 --- a/src/file/error.cr +++ b/src/file/error.cr @@ -2,9 +2,12 @@ class File < IO::FileDescriptor end class File::Error < IO::Error - getter file : String getter other : String? + def file : String + target.not_nil! + end + private def self.new_from_os_error(message, os_error, **opts) case os_error when Errno::ENOENT, WinError::ERROR_FILE_NOT_FOUND, WinError::ERROR_PATH_NOT_FOUND @@ -20,6 +23,10 @@ class File::Error < IO::Error end end + def initialize(message, *, file : String | Path, @other : String? = nil) + super message, target: file + end + protected def self.build_message(message, *, file : String) : String "#{message}: '#{file.inspect_unquoted}'" end @@ -38,11 +45,6 @@ class File::Error < IO::Error end end {% end %} - - def initialize(message, *, file : String | Path, @other : String? = nil) - @file = file.to_s - super message - end end class File::NotFoundError < File::Error diff --git a/src/io/error.cr b/src/io/error.cr index b611dc523818..4c6d30952f13 100644 --- a/src/io/error.cr +++ b/src/io/error.cr @@ -1,6 +1,26 @@ class IO class Error < Exception include SystemError + + getter target : String? + + protected def self.build_message(message, *, target : File) : String + build_message(message, target: target.path) + end + + protected def self.build_message(message, *, target : Nil) : String + message + end + + protected def self.build_message(message, *, target) : String + "#{message} (#{target})" + end + + def initialize(message : String? = nil, *, target = nil) + @target = target.try(&.to_s) + + super message + end end # Raised when an `IO` operation times out. diff --git a/src/io/evented.cr b/src/io/evented.cr index c490f3b5939f..191f136466a1 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -58,7 +58,7 @@ module IO::Evented if Errno.value == Errno::EAGAIN wait_readable else - raise IO::Error.from_errno(errno_msg) + raise IO::Error.from_errno(errno_msg, target: self) end end ensure @@ -79,7 +79,7 @@ module IO::Evented if Errno.value == Errno::EAGAIN wait_writable else - raise IO::Error.from_errno(errno_msg) + raise IO::Error.from_errno(errno_msg, target: self) end end end diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index d4b9f5f0cb6f..d7ed8aa516ec 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -211,9 +211,9 @@ module IO::Overlapped when .error_io_pending? # the operation is running asynchronously; do nothing when .error_access_denied? - raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}" + raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: self else - raise IO::Error.from_os_error(method, error) + raise IO::Error.from_os_error(method, error, target: self) end else operation.synchronous = true @@ -245,7 +245,7 @@ module IO::Overlapped when .wsa_io_pending? # the operation is running asynchronously; do nothing else - raise IO::Error.from_os_error(method, error) + raise IO::Error.from_os_error(method, error, target: self) end else operation.synchronous = true diff --git a/src/string.cr b/src/string.cr index 3c378bd1d455..98b7ec3884fd 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3416,9 +3416,19 @@ class String self.match(search, offset, options: options).try &.begin end - # :ditto: + # Returns the index of the _first_ occurrence of *search* in the string. If *offset* is present, + # it defines the position to start the search. # # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`. + # + # ``` + # "Hello, World".index!('o') # => 4 + # "Hello, World".index!('Z') # raises Enumerable::NotFoundError + # "Hello, World".index!("o", 5) # => 8 + # "Hello, World".index!("H", 2) # raises Enumerable::NotFoundError + # "Hello, World".index!(/[ ]+/) # => 6 + # "Hello, World".index!(/\d+/) # raises Enumerable::NotFoundError + # ``` def index!(search, offset = 0) : Int32 index(search, offset) || raise Enumerable::NotFoundError.new end @@ -4687,6 +4697,48 @@ class String regex.matches? self, pos, options: options end + # Matches the regular expression *regex* against the entire string and returns + # the resulting `MatchData`. + # It also updates `$~` with the result. + # + # ``` + # "foo".match_full(/foo/) # => Regex::MatchData("foo") + # $~ # => Regex::MatchData("foo") + # "fooo".match_full(/foo/) # => nil + # $~ # raises Exception + # ``` + def match_full(regex : Regex) : Regex::MatchData? + match(regex, options: Regex::MatchOptions::ANCHORED | Regex::MatchOptions::ENDANCHORED) + end + + # Matches the regular expression *regex* against the entire string and returns + # the resulting `MatchData`. + # It also updates `$~` with the result. + # Raises `Regex::Error` if there are no matches. + # + # ``` + # "foo".match_full!(/foo/) # => Regex::MatchData("foo") + # $~ # => Regex::MatchData("foo") + # "fooo".match_full!(/foo/) # Regex::Error + # $~ # raises Exception + # ``` + def match_full!(regex : Regex) : Regex::MatchData? + match!(regex, options: Regex::MatchOptions::ANCHORED | Regex::MatchOptions::ENDANCHORED) + end + + # Returns `true` if the regular expression *regex* matches this string entirely. + # + # ``` + # "foo".matches_full?(/foo/) # => true + # "fooo".matches_full?(/foo/) # => false + # + # # `$~` is not set even if last match succeeds. + # $~ # raises Exception + # ``` + def matches_full?(regex : Regex) : Bool + matches?(regex, options: Regex::MatchOptions::ANCHORED | Regex::MatchOptions::ENDANCHORED) + end + # Searches the string for instances of *pattern*, # yielding a `Regex::MatchData` for each match. def scan(pattern : Regex, *, options : Regex::MatchOptions = Regex::MatchOptions::None, &) : self