From ea6c50714b7d4644ce3e4ddf783e783f9b0d684f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 6 Jun 2021 22:17:40 +0800 Subject: [PATCH 1/7] Make HierarchyPrinter abstract --- src/compiler/crystal/tools/print_hierarchy.cr | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/compiler/crystal/tools/print_hierarchy.cr b/src/compiler/crystal/tools/print_hierarchy.cr index 931cd2112733..d6c65b8293f0 100644 --- a/src/compiler/crystal/tools/print_hierarchy.cr +++ b/src/compiler/crystal/tools/print_hierarchy.cr @@ -6,7 +6,7 @@ module Crystal def self.print_hierarchy(program, exp, format) case format when "text" - HierarchyPrinter.new(program, exp).execute + TextHierarchyPrinter.new(program, exp).execute when "json" JSONHierarchyPrinter.new(program, exp).execute else @@ -14,12 +14,13 @@ module Crystal end end - class HierarchyPrinter + abstract class HierarchyPrinter + abstract def print_all + @llvm_typer : LLVMTyper def initialize(@program : Program, exp : String?) @exp = exp ? Regex.new(exp) : nil - @indents = [] of Bool @targets = Set(Type).new @llvm_typer = @program.llvm_typer end @@ -29,9 +30,7 @@ module Crystal compute_targets(@program.types, exp, false) end - with_color.light_gray.bold.surround(STDOUT) do - print_type @program.object - end + print_all end def compute_targets(types : Array, exp, must_include = false) @@ -95,6 +94,28 @@ module Crystal false end + def must_print?(type : NonGenericClassType) + !(@exp && !@targets.includes?(type)) + end + + def must_print?(type : GenericClassType) + !(@exp && !@targets.includes?(type)) + end + + def must_print?(type) + false + end + end + + class TextHierarchyPrinter < HierarchyPrinter + @indents = [] of Bool + + def print_all + with_color.light_gray.bold.surround(STDOUT) do + print_type @program.object + end + end + def print_subtypes(types) types = types.sort_by &.to_s types.each_with_index do |type, i| @@ -232,18 +253,6 @@ module Crystal end end - def must_print?(type : NonGenericClassType) - !(@exp && !@targets.includes?(type)) - end - - def must_print?(type : GenericClassType) - !(@exp && !@targets.includes?(type)) - end - - def must_print?(type) - false - end - def print_indent unless @indents.empty? print " " @@ -270,11 +279,7 @@ module Crystal end class JSONHierarchyPrinter < HierarchyPrinter - def execute - if exp = @exp - compute_targets(@program.types, exp, false) - end - + def print_all JSON.build(STDOUT) do |json| json.object do print_type(@program.object, json) From ec474a2594f4cb87593133f2a172ac420ccfa687 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sun, 6 Jun 2021 23:13:27 +0800 Subject: [PATCH 2/7] Keep json builder as ivar --- src/compiler/crystal/tools/print_hierarchy.cr | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/compiler/crystal/tools/print_hierarchy.cr b/src/compiler/crystal/tools/print_hierarchy.cr index d6c65b8293f0..0218e9f564d2 100644 --- a/src/compiler/crystal/tools/print_hierarchy.cr +++ b/src/compiler/crystal/tools/print_hierarchy.cr @@ -279,15 +279,17 @@ module Crystal end class JSONHierarchyPrinter < HierarchyPrinter + private getter json = JSON::Builder.new(STDOUT) + def print_all - JSON.build(STDOUT) do |json| + json.document do json.object do - print_type(@program.object, json) + print_type(@program.object) end end end - def print_subtypes(types, json) + def print_subtypes(types) types = types.sort_by &.to_s json.field "sub_types" do @@ -295,7 +297,7 @@ module Crystal types.each_with_index do |type, index| if must_print? type json.object do - print_type(type, json) + print_type(type) end end end @@ -303,7 +305,7 @@ module Crystal end end - def print_type_name(type, json) + def print_type_name(type) json.field "name", type.to_s json.field "kind", type.struct? ? "struct" : "class" @@ -313,19 +315,19 @@ module Crystal end end - def print_type(type : GenericClassType | NonGenericClassType | GenericClassInstanceType, json) - print_type_name(type, json) + def print_type(type : GenericClassType | NonGenericClassType | GenericClassInstanceType) + print_type_name(type) subtypes = type.subclasses.select { |sub| must_print?(sub) } - print_instance_vars(type, !subtypes.empty?, json) - print_subtypes(subtypes, json) + print_instance_vars(type, !subtypes.empty?) + print_subtypes(subtypes) end - def print_type(type, json) + def print_type(type) # Nothing to do end - def print_instance_vars(type : GenericClassType, has_subtypes, json) + def print_instance_vars(type : GenericClassType, has_subtypes) instance_vars = type.instance_vars return if instance_vars.empty? @@ -341,7 +343,7 @@ module Crystal end end - def print_instance_vars(type, has_subtypes, json) + def print_instance_vars(type, has_subtypes) instance_vars = type.instance_vars return if instance_vars.empty? From 79b93d9a64f4cc81d6fb7771413dc4e8fa08b53c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 7 Jun 2021 01:24:02 +0800 Subject: [PATCH 3/7] Misc --- src/compiler/crystal/tools/print_hierarchy.cr | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/src/compiler/crystal/tools/print_hierarchy.cr b/src/compiler/crystal/tools/print_hierarchy.cr index 0218e9f564d2..d60ebc157cec 100644 --- a/src/compiler/crystal/tools/print_hierarchy.cr +++ b/src/compiler/crystal/tools/print_hierarchy.cr @@ -105,6 +105,14 @@ module Crystal def must_print?(type) false end + + def type_size(type) + @llvm_typer.size_of(@llvm_typer.llvm_struct_type(type)) + end + + def ivar_size(ivar) + @llvm_typer.size_of(@llvm_typer.llvm_embedded_type(ivar.type)) + end end class TextHierarchyPrinter < HierarchyPrinter @@ -148,28 +156,16 @@ module Crystal if (type.is_a?(NonGenericClassType) || type.is_a?(GenericClassInstanceType)) && !type.is_a?(PointerInstanceType) && !type.is_a?(ProcInstanceType) - size = @llvm_typer.size_of(@llvm_typer.llvm_struct_type(type)) with_color.light_gray.surround(STDOUT) do print " (" - print size.to_s + print type_size(type).to_s print " bytes)" end end puts end - def print_type(type : NonGenericClassType | GenericClassInstanceType) - print_type_name type - - subtypes = type.subclasses.select { |sub| must_print?(sub) } - print_instance_vars type, !subtypes.empty? - - with_indent do - print_subtypes subtypes - end - end - - def print_type(type : GenericClassType) + def print_type(type : GenericClassType | NonGenericClassType | GenericClassInstanceType) print_type_name type subtypes = type.subclasses.select { |sub| must_print?(sub) } @@ -217,13 +213,8 @@ module Crystal max_name_size = instance_vars.max_of &.name.size - if typed_instance_vars.empty? - max_type_size = 0 - max_bytes_size = 0 - else - max_type_size = typed_instance_vars.max_of &.type.to_s.size - max_bytes_size = typed_instance_vars.max_of { |var| @llvm_typer.size_of(@llvm_typer.llvm_embedded_type(var.type)).to_s.size } - end + max_type_size = typed_instance_vars.max_of?(&.type.to_s.size) || 0 + max_bytes_size = typed_instance_vars.max_of? { |var| ivar_size(var).to_s.size } || 0 instance_vars.each do |ivar| print_indent @@ -239,10 +230,9 @@ module Crystal print " : " if ivar_type = ivar.type? print ivar_type.to_s.ljust(max_type_size) - size = @llvm_typer.size_of(@llvm_typer.llvm_embedded_type(ivar_type)) with_color.light_gray.surround(STDOUT) do print " (" - print size.to_s.rjust(max_bytes_size) + print ivar_size(ivar).to_s.rjust(max_bytes_size) print " bytes)" end else @@ -311,7 +301,7 @@ module Crystal if (type.is_a?(NonGenericClassType) || type.is_a?(GenericClassInstanceType)) && !type.is_a?(PointerInstanceType) && !type.is_a?(ProcInstanceType) - json.field "size_in_bytes", @llvm_typer.size_of(@llvm_typer.llvm_struct_type(type)) + json.field "size_in_bytes", type_size(type) end end @@ -355,7 +345,7 @@ module Crystal json.object do json.field "name", instance_var.name.to_s json.field "type", ivar_type.to_s - json.field "size_in_bytes", @llvm_typer.size_of(@llvm_typer.llvm_embedded_type(ivar_type)) + json.field "size_in_bytes", ivar_size(instance_var) end end end From a3e5fb5c3325f6e96e2fa914e760794825ff705f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 7 Jun 2021 01:40:58 +0800 Subject: [PATCH 4/7] Allow any IO in hierarchy printers --- src/compiler/crystal/command.cr | 2 +- src/compiler/crystal/tools/print_hierarchy.cr | 89 +++++++++---------- 2 files changed, 42 insertions(+), 49 deletions(-) diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 7f73093bccad..70aab5f366e3 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -190,7 +190,7 @@ class Crystal::Command private def hierarchy config, result = compile_no_codegen "tool hierarchy", hierarchy: true, top_level: true @progress_tracker.stage("Tool (hierarchy)") do - Crystal.print_hierarchy result.program, config.hierarchy_exp, config.output_format + Crystal.print_hierarchy result.program, STDOUT, config.hierarchy_exp, config.output_format end end diff --git a/src/compiler/crystal/tools/print_hierarchy.cr b/src/compiler/crystal/tools/print_hierarchy.cr index d60ebc157cec..532e5f070350 100644 --- a/src/compiler/crystal/tools/print_hierarchy.cr +++ b/src/compiler/crystal/tools/print_hierarchy.cr @@ -3,12 +3,12 @@ require "colorize" require "../syntax/ast" module Crystal - def self.print_hierarchy(program, exp, format) + def self.print_hierarchy(program, io, exp, format) case format when "text" - TextHierarchyPrinter.new(program, exp).execute + TextHierarchyPrinter.new(program, io, exp).execute when "json" - JSONHierarchyPrinter.new(program, exp).execute + JSONHierarchyPrinter.new(program, io, exp).execute else raise "Unknown hierarchy format: #{format}" end @@ -116,10 +116,15 @@ module Crystal end class TextHierarchyPrinter < HierarchyPrinter - @indents = [] of Bool + private getter io + + def initialize(program : Program, @io : IO, exp : String?) + super(program, exp) + @indents = [] of Bool + end def print_all - with_color.light_gray.bold.surround(STDOUT) do + with_color.light_gray.bold.surround(io) do print_type @program.object end end @@ -139,8 +144,7 @@ module Crystal unless @indents.empty? print_indent - print "|" - puts + io << "|\n" end print_type type @@ -148,21 +152,16 @@ module Crystal def print_type_name(type) print_indent - print "+" unless @indents.empty? - print "- " - print type.struct? ? "struct" : "class" - print " " - print type + io << "+" unless @indents.empty? + io << "- " << (type.struct? ? "struct" : "class") << " " << type if (type.is_a?(NonGenericClassType) || type.is_a?(GenericClassInstanceType)) && !type.is_a?(PointerInstanceType) && !type.is_a?(ProcInstanceType) - with_color.light_gray.surround(STDOUT) do - print " (" - print type_size(type).to_s - print " bytes)" + with_color.light_gray.surround(io) do + io << " (" << type_size(type) << " bytes)" end end - puts + io << '\n' end def print_type(type : GenericClassType | NonGenericClassType | GenericClassInstanceType) @@ -188,19 +187,13 @@ module Crystal instance_vars.each do |name, var| print_indent - print (@indents.last ? "|" : " ") - if has_subtypes - print " . " - else - print " " - end + io << (@indents.last ? "|" : " ") << (has_subtypes ? " . " : " ") - with_color.light_gray.surround(STDOUT) do - print name.ljust(max_name_size) - print " : " - print var + with_color.light_gray.surround(io) do + name.ljust(io, max_name_size) + io << " : " << var end - puts + io << '\n' end end @@ -218,40 +211,35 @@ module Crystal instance_vars.each do |ivar| print_indent - print (@indents.last ? "|" : " ") - if has_subtypes - print " . " - else - print " " - end + io << (@indents.last ? "|" : " ") << (has_subtypes ? " . " : " ") - with_color.light_gray.surround(STDOUT) do - print ivar.name.ljust(max_name_size) - print " : " + with_color.light_gray.surround(io) do + ivar.name.ljust(io, max_name_size) + io << " : " if ivar_type = ivar.type? - print ivar_type.to_s.ljust(max_type_size) - with_color.light_gray.surround(STDOUT) do - print " (" - print ivar_size(ivar).to_s.rjust(max_bytes_size) - print " bytes)" + ivar_type.to_s.ljust(io, max_type_size) + with_color.light_gray.surround(io) do + io << " (" + ivar_size(ivar).to_s.rjust(io, max_bytes_size) + io << " bytes)" end else - print "MISSING".colorize.red.bright + io << "MISSING".colorize.red.bright end end - puts + io << '\n' end end def print_indent unless @indents.empty? - print " " + io << " " 0.upto(@indents.size - 2) do |i| indent = @indents[i] if indent - print "| " + io << "| " else - print " " + io << " " end end end @@ -269,7 +257,12 @@ module Crystal end class JSONHierarchyPrinter < HierarchyPrinter - private getter json = JSON::Builder.new(STDOUT) + private getter json + + def initialize(program : Program, io : IO, exp : String?) + super(program, exp) + @json = JSON::Builder.new(io) + end def print_all json.document do From aa09c49a198064d654f371df6c02445fec93e209 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 7 Jun 2021 02:09:46 +0800 Subject: [PATCH 5/7] Add minimal specs for hierarchy printers --- spec/compiler/crystal/tools/hierarchy_spec.cr | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 spec/compiler/crystal/tools/hierarchy_spec.cr diff --git a/spec/compiler/crystal/tools/hierarchy_spec.cr b/spec/compiler/crystal/tools/hierarchy_spec.cr new file mode 100644 index 000000000000..2513807114db --- /dev/null +++ b/spec/compiler/crystal/tools/hierarchy_spec.cr @@ -0,0 +1,67 @@ +require "../../../spec_helper" + +describe Crystal::TextHierarchyPrinter do + it "works" do + program = semantic(%( + class Foo + end + + class Bar < Foo + end + ), inject_primitives: false).program + + output = String.build { |io| Crystal.print_hierarchy(program, io, "ar$", "text") } + output.should eq(<<-EOS) + - class Object (4 bytes) + | + +- class Reference (4 bytes) + | + +- class Foo (4 bytes) + | + +- class Bar (4 bytes)\n + EOS + end +end + +describe Crystal::JSONHierarchyPrinter do + it "works" do + program = semantic(%( + class Foo + end + + class Bar < Foo + end + ), inject_primitives: false).program + + output = String.build { |io| Crystal.print_hierarchy(program, io, "ar$", "json") } + JSON.parse(output).should eq(JSON.parse(<<-EOS)) + { + "name": "Object", + "kind": "class", + "size_in_bytes": 4, + "sub_types": [ + { + "name": "Reference", + "kind": "class", + "size_in_bytes": 4, + "sub_types": [ + { + "name": "Foo", + "kind": "class", + "size_in_bytes": 4, + "sub_types": [ + { + "name": "Bar", + "kind": "class", + "size_in_bytes": 4, + "sub_types": [] + } + ] + } + ] + } + ] + } + EOS + end +end From d8d3ebf3d5fafcd210db10a5d0afe8f3c320822b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 7 Jun 2021 19:49:18 +0800 Subject: [PATCH 6/7] fixup --- src/compiler/crystal/tools/print_hierarchy.cr | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/compiler/crystal/tools/print_hierarchy.cr b/src/compiler/crystal/tools/print_hierarchy.cr index 532e5f070350..7a0490665593 100644 --- a/src/compiler/crystal/tools/print_hierarchy.cr +++ b/src/compiler/crystal/tools/print_hierarchy.cr @@ -94,12 +94,8 @@ module Crystal false end - def must_print?(type : NonGenericClassType) - !(@exp && !@targets.includes?(type)) - end - - def must_print?(type : GenericClassType) - !(@exp && !@targets.includes?(type)) + def must_print?(type : NonGenericClassType | GenericClassType) + !@exp || @targets.includes?(type) end def must_print?(type) From bd8b013a705d0c84fa492fedcde27c94402594d8 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 23 Jul 2021 13:06:42 +0800 Subject: [PATCH 7/7] don't use private getters --- src/compiler/crystal/tools/print_hierarchy.cr | 94 +++++++++---------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/src/compiler/crystal/tools/print_hierarchy.cr b/src/compiler/crystal/tools/print_hierarchy.cr index 7a0490665593..64aa9df5e0a6 100644 --- a/src/compiler/crystal/tools/print_hierarchy.cr +++ b/src/compiler/crystal/tools/print_hierarchy.cr @@ -112,15 +112,13 @@ module Crystal end class TextHierarchyPrinter < HierarchyPrinter - private getter io - def initialize(program : Program, @io : IO, exp : String?) super(program, exp) @indents = [] of Bool end def print_all - with_color.light_gray.bold.surround(io) do + with_color.light_gray.bold.surround(@io) do print_type @program.object end end @@ -140,7 +138,7 @@ module Crystal unless @indents.empty? print_indent - io << "|\n" + @io << "|\n" end print_type type @@ -148,16 +146,16 @@ module Crystal def print_type_name(type) print_indent - io << "+" unless @indents.empty? - io << "- " << (type.struct? ? "struct" : "class") << " " << type + @io << "+" unless @indents.empty? + @io << "- " << (type.struct? ? "struct" : "class") << " " << type if (type.is_a?(NonGenericClassType) || type.is_a?(GenericClassInstanceType)) && !type.is_a?(PointerInstanceType) && !type.is_a?(ProcInstanceType) - with_color.light_gray.surround(io) do - io << " (" << type_size(type) << " bytes)" + with_color.light_gray.surround(@io) do + @io << " (" << type_size(type) << " bytes)" end end - io << '\n' + @io << '\n' end def print_type(type : GenericClassType | NonGenericClassType | GenericClassInstanceType) @@ -183,13 +181,13 @@ module Crystal instance_vars.each do |name, var| print_indent - io << (@indents.last ? "|" : " ") << (has_subtypes ? " . " : " ") + @io << (@indents.last ? "|" : " ") << (has_subtypes ? " . " : " ") - with_color.light_gray.surround(io) do - name.ljust(io, max_name_size) - io << " : " << var + with_color.light_gray.surround(@io) do + name.ljust(@io, max_name_size) + @io << " : " << var end - io << '\n' + @io << '\n' end end @@ -207,35 +205,35 @@ module Crystal instance_vars.each do |ivar| print_indent - io << (@indents.last ? "|" : " ") << (has_subtypes ? " . " : " ") + @io << (@indents.last ? "|" : " ") << (has_subtypes ? " . " : " ") - with_color.light_gray.surround(io) do - ivar.name.ljust(io, max_name_size) - io << " : " + with_color.light_gray.surround(@io) do + ivar.name.ljust(@io, max_name_size) + @io << " : " if ivar_type = ivar.type? - ivar_type.to_s.ljust(io, max_type_size) - with_color.light_gray.surround(io) do - io << " (" - ivar_size(ivar).to_s.rjust(io, max_bytes_size) - io << " bytes)" + ivar_type.to_s.ljust(@io, max_type_size) + with_color.light_gray.surround(@io) do + @io << " (" + ivar_size(ivar).to_s.rjust(@io, max_bytes_size) + @io << " bytes)" end else - io << "MISSING".colorize.red.bright + @io << "MISSING".colorize.red.bright end end - io << '\n' + @io << '\n' end end def print_indent unless @indents.empty? - io << " " + @io << " " 0.upto(@indents.size - 2) do |i| indent = @indents[i] if indent - io << "| " + @io << "| " else - io << " " + @io << " " end end end @@ -253,16 +251,14 @@ module Crystal end class JSONHierarchyPrinter < HierarchyPrinter - private getter json - def initialize(program : Program, io : IO, exp : String?) super(program, exp) @json = JSON::Builder.new(io) end def print_all - json.document do - json.object do + @json.document do + @json.object do print_type(@program.object) end end @@ -271,11 +267,11 @@ module Crystal def print_subtypes(types) types = types.sort_by &.to_s - json.field "sub_types" do - json.array do + @json.field "sub_types" do + @json.array do types.each_with_index do |type, index| if must_print? type - json.object do + @json.object do print_type(type) end end @@ -285,12 +281,12 @@ module Crystal end def print_type_name(type) - json.field "name", type.to_s - json.field "kind", type.struct? ? "struct" : "class" + @json.field "name", type.to_s + @json.field "kind", type.struct? ? "struct" : "class" if (type.is_a?(NonGenericClassType) || type.is_a?(GenericClassInstanceType)) && !type.is_a?(PointerInstanceType) && !type.is_a?(ProcInstanceType) - json.field "size_in_bytes", type_size(type) + @json.field "size_in_bytes", type_size(type) end end @@ -310,12 +306,12 @@ module Crystal instance_vars = type.instance_vars return if instance_vars.empty? - json.field "instance_vars" do - json.array do + @json.field "instance_vars" do + @json.array do instance_vars.each do |name, var| - json.object do - json.field "name", name.to_s - json.field "type", var.to_s + @json.object do + @json.field "name", name.to_s + @json.field "type", var.to_s end end end @@ -327,14 +323,14 @@ module Crystal return if instance_vars.empty? instance_vars = instance_vars.values - json.field "instance_vars" do - json.array do + @json.field "instance_vars" do + @json.array do instance_vars.each do |instance_var| if ivar_type = instance_var.type? - json.object do - json.field "name", instance_var.name.to_s - json.field "type", ivar_type.to_s - json.field "size_in_bytes", ivar_size(instance_var) + @json.object do + @json.field "name", instance_var.name.to_s + @json.field "type", ivar_type.to_s + @json.field "size_in_bytes", ivar_size(instance_var) end end end