Skip to content

Commit

Permalink
Implement writer
Browse files Browse the repository at this point in the history
  • Loading branch information
soutaro committed Sep 9, 2019
1 parent ac29238 commit 834b369
Show file tree
Hide file tree
Showing 5 changed files with 376 additions and 5 deletions.
1 change: 1 addition & 0 deletions lib/ruby/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@
require "ruby/signature/constant"
require "ruby/signature/constant_table"
require "ruby/signature/ast/comment"
require "ruby/signature/writer"

require "ruby/signature/parser"
15 changes: 10 additions & 5 deletions lib/ruby/signature/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class Ruby::Signature::Parser
token tUIDENT tLIDENT tNAMESPACE tINTERFACEIDENT tLKEYWORD tUKEYWORD tGLOBALIDENT
tIVAR tCLASSVAR
tANNOTATION
tSTRING tSYMBOL tINTEGER
tSTRING tSYMBOL tINTEGER tWRITE_ATTR
kLPAREN kRPAREN kLBRACKET kRBRACKET kLBRACE kRBRACE
kVOID kNIL kANY kTOP kBOT kSELF kSELFQ kINSTANCE kCLASS kBOOL kSINGLETON kTYPE kDEF kMODULE kSUPER
kPRIVATE kPUBLIC kALIAS
Expand Down Expand Up @@ -496,7 +496,7 @@ rule
location: val[0].location + val[1].location)
}
| tQUOTEDMETHOD

| tWRITE_ATTR

method_name0: tUIDENT | tLIDENT | identifier_keywords

Expand Down Expand Up @@ -1137,7 +1137,10 @@ PUNCTS = {
"!~" => :tOPERATOR,
"!=" => :tOPERATOR,
">=" => :tOPERATOR,
"<<" => :tOPERATOR,
"<=>" => :tOPERATOR,
"<=" => :tOPERATOR,
">>" => :tOPERATOR,
">" => :tOPERATOR,
"~" => :tOPERATOR,
"+@" => :tOPERATOR,
Expand Down Expand Up @@ -1219,6 +1222,8 @@ def next_token
new_token(:tANNOTATION, s)
when input.scan(/self\?/)
new_token(:kSELFQ, "self?")
when input.scan(/(([a-zA-Z]\w*)|(_\w+))=/)
new_token(:tWRITE_ATTR)
when input.scan(KEYWORDS_RE)
new_token(KEYWORDS[input.matched], input.matched.to_sym)
when input.scan(/:\w+\b/)
Expand All @@ -1238,12 +1243,12 @@ def next_token
new_token(:tIVAR, input.matched.to_sym)
when input.scan(/@@[a-zA-Z_]\w*/)
new_token(:tCLASSVAR, input.matched.to_sym)
when input.scan(/_[a-zA-Z]\w*\b/)
new_token(:tINTERFACEIDENT)
when input.scan(/[A-Z]\w*\b/)
new_token(:tUIDENT)
when input.scan(/[a-z]\w*\b/)
when input.scan(/[a-z_]\w*\b/)
new_token(:tLIDENT)
when input.scan(/_[a-zA-Z]\w*\b/)
new_token(:tINTERFACEIDENT)
when input.scan(/"(\\"|[^"])*"/)
s = input.matched.yield_self {|s| s[1, s.length - 2] }.gsub(/\\"/, '"')
new_token(:tSTRING, s)
Expand Down
232 changes: 232 additions & 0 deletions lib/ruby/signature/writer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
module Ruby
module Signature
class Writer
attr_reader :out

def initialize(out:)
@out = out
end

def write_annotation(annotations, level:)
prefix = " " * level

annotations.each do |annotation|
string = annotation.string
case
when string !~ /\}/
out.puts "#{prefix}%a{#{string}}"
when string !~ /\)/
out.puts "#{prefix}%a(#{string})"
when string !~ /\]/
out.puts "#{prefix}%a[#{string}]"
when string !~ /\>/
out.puts "#{prefix}%a<#{string}>"
when string !~ /\|/
out.puts "#{prefix}%a|#{string}|"
end
end
end

def write_comment(comment, level:)
if comment
prefix = " " * level
comment.string.lines.each do |line|
out.puts "#{prefix}# #{line}"
end
end
end

def write(decls)
decls.each.with_index do |decl, index|
if index > 0
out.puts
end

write_decl decl
end
end

def write_decl(decl)
case decl
when AST::Declarations::Class
super_class = if decl.super_class
" < #{name_and_args(decl.super_class.name, decl.super_class.args)}"
end
write_comment decl.comment, level: 0
write_annotation decl.annotations, level: 0
out.puts "class #{name_and_args(decl.name, decl.type_params)}#{super_class}"

decl.members.each.with_index do |member, index|
if index > 0
out.puts
end
write_member member
end

out.puts "end"

when AST::Declarations::Module
self_type = if decl.self_type
" : #{decl.self_type}"
end

write_comment decl.comment, level: 0
write_annotation decl.annotations, level: 0
out.puts "module #{name_and_args(decl.name, decl.type_params)}#{self_type}"
decl.members.each.with_index do |member, index|
if index > 0
out.puts
end
write_member member
end
out.puts "end"
when AST::Declarations::Constant
write_comment decl.comment, level: 0
out.puts "#{decl.name}: #{decl.type}"

when AST::Declarations::Global
write_comment decl.comment, level: 0
out.puts "#{decl.name}: #{decl.type}"

when AST::Declarations::Alias
write_comment decl.comment, level: 0
write_annotation decl.annotations, level: 0
out.puts "type #{decl.name} = #{decl.type}"

when AST::Declarations::Interface
write_comment decl.comment, level: 0
write_annotation decl.annotations, level: 0
out.puts "interface #{name_and_args(decl.name, decl.type_params)}"
decl.members.each.with_index do |member, index|
if index > 0
out.puts
end
write_member member
end
out.puts "end"

when AST::Declarations::Extension
write_comment decl.comment, level: 0
write_annotation decl.annotations, level: 0
out.puts "extension #{name_and_args(decl.name, decl.type_params)} (#{decl.extension_name})"
decl.members.each.with_index do |member, index|
if index > 0
out.puts
end
write_member member
end
out.puts "end"
end
end

def name_and_args(name, args)
if name && args
if args.empty?
"#{name}"
else
"#{name}[#{args.join(", ")}]"
end
end
end

def write_member(member)
case member
when AST::Members::Include
write_comment member.comment, level: 2
write_annotation member.annotations, level: 2
out.puts " include #{name_and_args(member.name, member.args)}"
when AST::Members::Extend
write_comment member.comment, level: 2
write_annotation member.annotations, level: 2
out.puts " extend #{name_and_args(member.name, member.args)}"
when AST::Members::Prepend
write_comment member.comment, level: 2
write_annotation member.annotations, level: 2
out.puts " prepend #{name_and_args(member.name, member.args)}"
when AST::Members::AttrAccessor
write_comment member.comment, level: 2
write_annotation member.annotations, level: 2
out.puts " #{attribute(:accessor, member)}"
when AST::Members::AttrReader
write_comment member.comment, level: 2
write_annotation member.annotations, level: 2
out.puts " #{attribute(:reader, member)}"
when AST::Members::AttrWriter
write_comment member.comment, level: 2
write_annotation member.annotations, level: 2
out.puts " #{attribute(:writer, member)}"
when AST::Members::Public
out.puts " public"
when AST::Members::Private
out.puts " private"
when AST::Members::Alias
write_comment member.comment, level: 2
write_annotation member.annotations, level: 2
new_name = member.singleton? ? "self.#{member.new_name}" : member.new_name
old_name = member.singleton? ? "self.#{member.old_name}" : member.old_name
out.puts " alias #{new_name} #{old_name}"
when AST::Members::InstanceVariable
write_comment member.comment, level: 2
out.puts " #{member.name}: #{member.type}"
when AST::Members::ClassInstanceVariable
write_comment member.comment, level: 2
out.puts " self.#{member.name}: #{member.type}"
when AST::Members::ClassVariable
write_comment member.comment, level: 2
out.puts " #{member.name}: #{member.type}"
when AST::Members::MethodDefinition
write_comment member.comment, level: 2
write_annotation member.annotations, level: 2
write_def member
end
end

def method_name(name)
s = name.to_s

if s =~ Parser::KEYWORDS_RE
"`#{s}`"
else
s
end
end

def write_def(member)
name = case member.kind
when :instance
"#{method_name(member.name)}"
when :singleton_instance
"self?.#{method_name(member.name)}"
when :singleton
"self.#{method_name(member.name)}"
end

attrs = member.attributes.empty? ? "" : member.attributes.join(" ") + " "
prefix = " #{attrs}def #{name}:"
padding = " " * (prefix.size-1)

out.print prefix

member.types.each.with_index do |type, index|
if index > 0
out.print padding
out.print "|"
end
out.puts " #{type}"
end
end

def attribute(kind, attr)
var = case attr.ivar_name
when nil
""
when false
"()"
else
"(#{attr.ivar_name})"
end
"attr_#{kind} #{attr.name}#{var}: #{attr.type}"
end
end
end
end
Loading

0 comments on commit 834b369

Please sign in to comment.