Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "expand" command to Crystal tools #3732

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
474 changes: 474 additions & 0 deletions spec/compiler/crystal/tools/expand_spec.cr

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion src/compiler/crystal/command.cr
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Crystal::Command

Tool:
context show context for given location
expand show macro expansion for given location
format format project, directories and/or files
hierarchy show type hierarchy
implementations show implementations for given call in location
Expand Down Expand Up @@ -141,6 +142,9 @@ class Crystal::Command
when "format".starts_with?(tool)
options.shift
format
when "expand".starts_with?(tool)
options.shift
expand
when "hierarchy".starts_with?(tool)
options.shift
hierarchy
Expand Down Expand Up @@ -198,9 +202,10 @@ class Crystal::Command
end
end

private def compile_no_codegen(command, wants_doc = false, hierarchy = false, cursor_command = false, top_level = false)
private def compile_no_codegen(command, wants_doc = false, hierarchy = false, no_cleanup = false, cursor_command = false, top_level = false)
config = create_compiler command, no_codegen: true, hierarchy: hierarchy, cursor_command: cursor_command
config.compiler.no_codegen = true
config.compiler.no_cleanup = no_cleanup
config.compiler.wants_doc = wants_doc
result = top_level ? config.top_level_semantic : config.compile
{config, result}
Expand Down
10 changes: 8 additions & 2 deletions src/compiler/crystal/command/cursor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ class Crystal::Command
end
end

private def cursor_command(command)
config, result = compile_no_codegen command, cursor_command: true
private def expand
cursor_command("tool expand", no_cleanup: true, wants_doc: true) do |location, config, result|
result = ExpandVisitor.new(location).process(result)
end
end

private def cursor_command(command, no_cleanup = false, wants_doc = false)
config, result = compile_no_codegen command, cursor_command: true, no_cleanup: no_cleanup, wants_doc: wants_doc

format = config.output_format

Expand Down
5 changes: 4 additions & 1 deletion src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ module Crystal
# If `false`, color won't be used in output messages.
property? color = true

# If `true`, skip cleanup process on semantic analysis.
property? no_cleanup = false

# If `true`, no executable will be generated after compilation
# (useful to type-check a prorgam)
property? no_codegen = false
Expand Down Expand Up @@ -131,7 +134,7 @@ module Crystal
source = [source] unless source.is_a?(Array)
program = new_program(source)
node = parse program, source
node = program.semantic node, @stats
node = program.semantic node, @stats, cleanup: !no_cleanup?
codegen program, node, source, output_filename unless @no_codegen
Result.new program, node
end
Expand Down
7 changes: 5 additions & 2 deletions src/compiler/crystal/semantic.cr
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Crystal::Program
# Runs semantic analysis on the given node, returning a node
# that's typed. In the process types and methods are defined in
# this program.
def semantic(node : ASTNode, stats = false) : ASTNode
def semantic(node : ASTNode, stats = false, cleanup = true) : ASTNode
node, processor = top_level_semantic(node, stats: stats)

Crystal.timing("Semantic (cvars initializers)", stats) do
Expand All @@ -35,15 +35,18 @@ class Crystal::Program
end

result = Crystal.timing("Semantic (main)", stats) do
visit_main(node, process_finished_hooks: true)
visit_main(node, process_finished_hooks: true, cleanup: cleanup)
end

Crystal.timing("Semantic (cleanup)", stats) do
cleanup_types
cleanup_files
end

Crystal.timing("Semantic (recursive struct check)", stats) do
RecursiveStructChecker.new(self).run
end

result
end

Expand Down
4 changes: 2 additions & 2 deletions src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ require "./semantic_visitor"

module Crystal
class Program
def visit_main(node, visitor = MainVisitor.new(self), process_finished_hooks = false)
def visit_main(node, visitor = MainVisitor.new(self), process_finished_hooks = false, cleanup = true)
node.accept visitor
program.process_finished_hooks(visitor) if process_finished_hooks

missing_types = FixMissingTypes.new(self)
node.accept missing_types
program.process_finished_hooks(missing_types) if process_finished_hooks

node = cleanup node
node = cleanup node if cleanup

if process_finished_hooks
finished_hooks.map! do |hook|
Expand Down
33 changes: 22 additions & 11 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2792,10 +2792,12 @@ module Crystal
def parse_percent_macro_expression
raise "can't nest macro expressions", @token if @in_macro_expression

location = @token.location
macro_exp = parse_macro_expression
check_macro_expression_end
end_location = token_end_location
next_token
MacroExpression.new(macro_exp)
MacroExpression.new(macro_exp).at(location).at_end(end_location)
end

def parse_macro_expression
Expand Down Expand Up @@ -2870,7 +2872,7 @@ module Crystal
next_token_skip_space
check :"%}"

return MacroFor.new(vars, exp, body)
return MacroFor.new(vars, exp, body).at_end(token_end_location)
when :if
return parse_macro_if(start_line, start_column, macro_state)
when :unless
Expand All @@ -2894,7 +2896,7 @@ module Crystal
next_token_skip_space
check :"%}"

return MacroIf.new(BoolLiteral.new(true), body)
return MacroIf.new(BoolLiteral.new(true), body).at_end(token_end_location)
when :else, :elsif, :end
return nil
end
Expand All @@ -2904,7 +2906,7 @@ module Crystal
exps = parse_expressions
@in_macro_expression = false

MacroExpression.new(exps, output: false)
MacroExpression.new(exps, output: false).at_end(token_end_location)
end

def parse_macro_if(start_line, start_column, macro_state, check_end = true)
Expand All @@ -2916,7 +2918,7 @@ module Crystal

if @token.type != :"%}" && check_end
an_if = parse_if_after_condition cond, true
return MacroExpression.new(an_if, output: false)
return MacroExpression.new(an_if, output: false).at_end(token_end_location)
end

check :"%}"
Expand Down Expand Up @@ -2956,7 +2958,7 @@ module Crystal
unexpected_token
end

return MacroIf.new(cond, a_then, a_else)
return MacroIf.new(cond, a_then, a_else).at_end(token_end_location)
end

def parse_expression_inside_macro
Expand Down Expand Up @@ -3664,7 +3666,7 @@ module Crystal
end
end
node.doc = doc
node.location = block.try(&.location) || location
node.location = location
node.end_location = block.try(&.end_location) || call_args.try(&.end_location) || end_location
node
end
Expand Down Expand Up @@ -4738,6 +4740,7 @@ module Crystal
end

def parse_lib
location = @token.location
next_token_skip_space_or_newline

name = check_const
Expand All @@ -4747,9 +4750,10 @@ module Crystal
body = push_visbility { parse_lib_body_expressions }

check_ident :end
end_location = token_end_location
next_token_skip_space

LibDef.new name, body, name_column_number
LibDef.new(name, body, name_column_number).at(location).at_end(end_location)
end

def parse_lib_body
Expand Down Expand Up @@ -4836,6 +4840,7 @@ module Crystal
IdentOrConst = [:IDENT, :CONST]

def parse_fun_def(require_body = false)
location = @token.location
doc = @token.doc

push_def if require_body
Expand Down Expand Up @@ -4914,20 +4919,22 @@ module Crystal
if require_body
if @token.keyword?(:end)
body = Nop.new
end_location = token_end_location
next_token
else
body = parse_expressions
body, end_location = parse_exception_handler body, implicit: true
end
else
body = nil
end_location = token_end_location
end

pop_def if require_body

fun_def = FunDef.new name, args, return_type, varargs, body, real_name
fun_def.doc = doc
fun_def
fun_def.at(location).at_end(end_location)
end

def parse_alias
Expand Down Expand Up @@ -5008,14 +5015,16 @@ module Crystal
end

def parse_c_struct_or_union(union : Bool)
location = @token.location
next_token_skip_space_or_newline
name = check_const
next_token_skip_statement_end
body = parse_c_struct_or_union_body_expressions
check_ident :end
end_location = token_end_location
next_token_skip_space

CStructOrUnionDef.new name, Expressions.from(body), union: union
CStructOrUnionDef.new(name, Expressions.from(body), union: union).at(location).at_end(end_location)
end

def parse_c_struct_or_union_body
Expand Down Expand Up @@ -5082,6 +5091,7 @@ module Crystal
end

def parse_enum_def
location = @token.location
doc = @token.doc

next_token_skip_space_or_newline
Expand All @@ -5103,13 +5113,14 @@ module Crystal
members = parse_enum_body_expressions

check_ident :end
end_location = token_end_location
next_token_skip_space

raise "Bug: EnumDef name can only be a Path" unless name.is_a?(Path)

enum_def = EnumDef.new name, members, base_type
enum_def.doc = doc
enum_def
enum_def.at(location).at_end(end_location)
end

def parse_enum_body
Expand Down
Loading