src/gpcr/census.cr @ trunk
compiler/crystal/syntax
scale = 0.75 fontscale = 1.500 box wid 2.796 ht 0.340 with .nw at (0.035, -0.035) fill 0xCBD5E1 color 0xCBD5E1 box wid 2.796 ht 0.340 with .nw at (0.000, 0.000) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.022) fill 0xffffff color 0xffffff line from (0.140, 0.000) to (0.140, -0.340) color white line from (2.656, 0.000) to (2.656, -0.340) color white text "require 'compiler/crystal/syntax'" color white at (1.398, -0.170)→ /pikchrshow
crystal source
require "compiler/crystal/syntax"
json block
{
"kind": "require",
"name": "compiler/crystal/syntax",
"doc": null,
"children": []
}
json
scale = 0.75 fontscale = 1.500 box wid 1.330 ht 0.340 with .nw at (0.035, -0.455) fill 0xCBD5E1 color 0xCBD5E1 box wid 1.330 ht 0.340 with .nw at (0.000, -0.420) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.443) fill 0xffffff color 0xffffff line from (0.140, -0.420) to (0.140, -0.760) color white line from (1.190, -0.420) to (1.190, -0.760) color white text "require 'json'" color white at (0.665, -0.590)→ /pikchrshow
crystal source
require "json"
json block
{
"kind": "require",
"name": "json",
"doc": null,
"children": []
}
../gpcr
scale = 0.75 fontscale = 1.500 box wid 1.498 ht 0.340 with .nw at (0.035, -0.875) fill 0xCBD5E1 color 0xCBD5E1 box wid 1.498 ht 0.340 with .nw at (0.000, -0.840) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.863) fill 0xffffff color 0xffffff line from (0.140, -0.840) to (0.140, -1.180) color white line from (1.358, -0.840) to (1.358, -1.180) color white text "require '../gpcr'" color white at (0.749, -1.010)→ /pikchrshow
crystal source
require "../gpcr"
json block
{
"kind": "require",
"name": "../gpcr",
"doc": null,
"children": []
}
enum LineKind
Native
GPCR
Primitive
end
Span
scale = 0.75 fontscale = 1.500 box wid 0.800 ht 0.340 rad 0.035 with .nw at (0.035, -1.295) fill 0xCBD5E1 color 0xCBD5E1 box wid 0.800 ht 0.340 rad 0.035 with .nw at (0.000, -1.260) fill 0xFF6680 color 0xFF6680 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -1.283) fill 0xffffff color 0xffffff text "Span" color white at (0.400, -1.430)→ /pikchrshow
crystal source
@[GPCR::Record]
record Span, kind : LineKind, line_start : Int32, line_end : Int32
json block
{
"kind": "record",
"name": "Span",
"doc": null,
"fields": [
"kind : LineKind",
"line_start : Int32",
"line_end : Int32"
]
}
private class ScanState
getter spans = [] of Span
property blocks = 0
property primitives = 0
property graphs = 0
property unannotated = 0
end
struct FileStats
getter gpcr_lines : Int32
getter primitive_lines : Int32
getter native_lines : Int32
getter total_lines : Int32
getter blocks : Int32
getter primitives : Int32
getter graphs : Int32
getter unannotated : Int32
def initialize(@gpcr_lines, @primitive_lines, @native_lines, @total_lines, @blocks, @primitives, @graphs, @unannotated)
end
def total_defs : Int32
@blocks + @primitives + @graphs + @unannotated
end
def gpcr_pct : Float64
@total_lines > 0 ? (@gpcr_lines.to_f * 100.0 / @total_lines).round(1) : 0.0
end
end
def self.count_file(source : String) : FileStats
parser = Crystal::Parser.new(source)
ast = parser.parse
ast_nodes = ast.is_a?(Crystal::Expressions) ? ast.expressions : [ast] of Crystal::ASTNode
total_lines = source.lines.size
state = ScanState.new
scan_defs(ast_nodes, state)
line_kinds = Array.new(total_lines, LineKind::Native)
state.spans.each do |span|
first = span.line_start.clamp(1, total_lines)
last = span.line_end.clamp(first, total_lines)
(first..last).each { |line_no| line_kinds[line_no - 1] = span.kind }
end
gpcr_lines = line_kinds.count(LineKind::GPCR)
primitive_lines = line_kinds.count(LineKind::Primitive)
native_lines = total_lines - gpcr_lines - primitive_lines
FileStats.new(gpcr_lines, primitive_lines, native_lines, total_lines,
state.blocks, state.primitives, state.graphs, state.unannotated)
end
private def self.scan_defs(ast_nodes : Array(Crystal::ASTNode), state : ScanState) : Nil
pending = [] of Crystal::Annotation
ast_nodes.each do |n|
if n.is_a?(Crystal::Annotation)
pending << n
else
inner = n.is_a?(Crystal::VisibilityModifier) ? n.exp : n
if inner.is_a?(Crystal::Def)
has_block = pending.any? { |a| a.path.to_s.ends_with?("Block") }
has_prim = pending.any? { |a| a.path.to_s.ends_with?("Primitive") }
has_graph = pending.any? { |a| a.path.to_s.ends_with?("Graph") }
if has_block
state.blocks += 1
state.spans << Span.new(LineKind::GPCR, span_start(pending, inner), span_end(inner))
elsif has_prim
state.primitives += 1
state.spans << Span.new(LineKind::Primitive, span_start(pending, inner), span_end(inner))
elsif has_graph
state.graphs += 1
state.spans << Span.new(LineKind::GPCR, span_start(pending, inner), span_end(inner))
else
state.unannotated += 1
end
elsif inner.is_a?(Crystal::Assign) && pending.any? { |a| a.path.to_s.ends_with?("Constant") || a.path.to_s.ends_with?("TableLiteral") }
from = pending.first?.try(&.location).try(&.line_number) || inner.location.try(&.line_number) || 1
to_loc = inner.end_location.try(&.line_number) || from
state.blocks += 1
state.spans << Span.new(LineKind::GPCR, from, to_loc)
elsif inner.is_a?(Crystal::ClassDef) && pending.any? { |a| a.path.to_s.ends_with?("TableSchema") }
from = pending.first?.try(&.location).try(&.line_number) || inner.location.try(&.line_number) || 1
to_loc = inner.end_location.try(&.line_number) || from
state.blocks += 1
state.spans << Span.new(LineKind::GPCR, from, to_loc)
elsif inner.is_a?(Crystal::Call) && inner.name == "record" && pending.any? { |a| a.path.to_s.ends_with?("Record") }
from = pending.first?.try(&.location).try(&.line_number) || inner.location.try(&.line_number) || 1
to_loc = inner.end_location.try(&.line_number) || from
state.blocks += 1
state.spans << Span.new(LineKind::GPCR, from, to_loc)
else
scan_defs(body_nodes(n), state)
end
pending = [] of Crystal::Annotation
end
end
end
private def self.body_nodes(node : Crystal::ASTNode) : Array(Crystal::ASTNode)
case node
when Crystal::Expressions
node.expressions
when Crystal::ModuleDef
body_list(node.body)
when Crystal::ClassDef
body_list(node.body)
else
[] of Crystal::ASTNode
end
end
private def self.body_list(node : Crystal::ASTNode) : Array(Crystal::ASTNode)
case node
when Crystal::Expressions then node.expressions
when Crystal::Nop then [] of Crystal::ASTNode
else [node] of Crystal::ASTNode
end
end
private def self.span_start(annotations : Array(Crystal::Annotation), node : Crystal::Def) : Int32
annotations.first?.try(&.location).try(&.line_number) ||
node.location.try(&.line_number) ||
1
end
private def self.span_end(node : Crystal::Def) : Int32
node.end_location.try(&.line_number) ||
node.location.try(&.line_number) ||
1
end
def self.census(files : Array(String)) : {Hash(String, FileStats), FileStats}
file_stats = {} of String => FileStats
total_gpcr_lines = 0
total_primitive_lines = 0
total_native_lines = 0
total_lines = 0
total_blocks = 0
total_primitives = 0
total_graphs = 0
total_unannotated = 0
files.each do |path|
source = File.read(path)
stats = count_file(source)
file_stats[File.basename(path)] = stats
total_gpcr_lines += stats.gpcr_lines
total_primitive_lines += stats.primitive_lines
total_native_lines += stats.native_lines
total_lines += stats.total_lines
total_blocks += stats.blocks
total_primitives += stats.primitives
total_graphs += stats.graphs
total_unannotated += stats.unannotated
end
totals = FileStats.new(total_gpcr_lines, total_primitive_lines, total_native_lines, total_lines, total_blocks, total_primitives, total_graphs, total_unannotated)
{file_stats, totals}
end
def self.census_sources(pairs : Array({String, String})) : {Hash(String, FileStats), FileStats}
file_stats = {} of String => FileStats
total_gpcr_lines = 0
total_primitive_lines = 0
total_native_lines = 0
total_lines = 0
total_blocks = 0
total_primitives = 0
total_graphs = 0
total_unannotated = 0
pairs.each do |(name, source)|
stats = count_file(source)
file_stats[name] = stats
total_gpcr_lines += stats.gpcr_lines
total_primitive_lines += stats.primitive_lines
total_native_lines += stats.native_lines
total_lines += stats.total_lines
total_blocks += stats.blocks
total_primitives += stats.primitives
total_graphs += stats.graphs
total_unannotated += stats.unannotated
end
totals = FileStats.new(total_gpcr_lines, total_primitive_lines, total_native_lines, total_lines, total_blocks, total_primitives, total_graphs, total_unannotated)
{file_stats, totals}
end
def self.report_json(file_stats : Hash(String, FileStats), totals : FileStats) : String
JSON.build do |j|
j.object do
j.field "files" do
j.object do
file_stats.each do |filename, stats|
j.field filename do
j.object do
j.field "gpcr_lines", stats.gpcr_lines
j.field "primitive_lines", stats.primitive_lines
j.field "native_lines", stats.native_lines
j.field "total_lines", stats.total_lines
j.field "gpcr_pct", stats.gpcr_pct
j.field "blocks", stats.blocks
j.field "primitives", stats.primitives
j.field "graphs", stats.graphs
j.field "unannotated", stats.unannotated
j.field "total_defs", stats.total_defs
end
end
end
end
end
j.field "project" do
j.object do
j.field "gpcr_lines", totals.gpcr_lines
j.field "primitive_lines", totals.primitive_lines
j.field "native_lines", totals.native_lines
j.field "total_lines", totals.total_lines
j.field "gpcr_pct", totals.gpcr_pct
j.field "blocks", totals.blocks
j.field "primitives", totals.primitives
j.field "graphs", totals.graphs
j.field "unannotated", totals.unannotated
j.field "total_defs", totals.total_defs
end
end
end
end
end