Mercurial > hg > Others > Rakudo
diff src/perl6-debug.nqp @ 0:c341f82e7ad7 default tip
Rakudo branch in cr.ie.u-ryukyu.ac.jp
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Thu, 26 Dec 2019 16:50:27 +0900 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/perl6-debug.nqp Thu Dec 26 16:50:27 2019 +0900 @@ -0,0 +1,548 @@ +use Perl6::Grammar; +use Perl6::Actions; +use Perl6::Compiler; + +class Perl6::DebugHooks { + has %!hooks; + has $!suspended; + + method set_hook($name, $callback) { + $*W.add_object($callback); + %!hooks{$name} := $callback; + } + + method has_hook($name) { + !$!suspended && nqp::existskey(%!hooks, $name) + } + + method get_hook($name) { + %!hooks{$name} + } + + method suspend() { + $!suspended := 1 + } + + method unsuspend() { + $!suspended := 0 + } +} + +sub ps_qast() { + QAST::Op.new( + :op('callmethod'), :name('new'), + QAST::WVal.new( :value($*W.find_symbol(['PseudoStash'])) ) + ) +} + +grammar Perl6::HookRegexGrammar is Perl6::RegexGrammar { + method nibbler() { + my $*RX_TOP_LEVEL_NIBBLER := 0; + unless %*RX<DEBUGGER_SEEN> { + %*RX<DEBUGGER_SEEN> := 1; + $*RX_TOP_LEVEL_NIBBLER := 1; + } + Perl6::RegexGrammar.HOW.find_method(Perl6::RegexGrammar, 'nibbler')(self) + } +} + +class Perl6::HookRegexActions is Perl6::RegexActions { + method nibbler($/) { + if $*RX_TOP_LEVEL_NIBBLER && $*DEBUG_HOOKS.has_hook('regex_region') { + my $file := nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME; + $*DEBUG_HOOKS.get_hook('regex_region')($file, $/.from, $/.to); + } + Perl6::RegexActions.nibbler($/); + } + + method quantified_atom($/) { + Perl6::RegexActions.quantified_atom($/); + my $qa := $/.ast; + if $qa && !(~$/ ~~ /^\s*$/) && $*DEBUG_HOOKS.has_hook('regex_atom') { + $/.make(QAST::Regex.new( + :rxtype('concat'), + QAST::Regex.new( + :rxtype('qastnode'), + :subtype('declarative'), + QAST::Stmts.new( + QAST::Op.new( + :op('p6store'), + QAST::Var.new( :name('$/'), :scope<lexical> ), + QAST::Op.new( + QAST::Var.new( :name('$¢'), :scope<lexical> ), + :name('MATCH'), + :op('callmethod') + ) + ), + QAST::Op.new( + :op('call'), + QAST::WVal.new( :value($*DEBUG_HOOKS.get_hook('regex_atom')) ), + $*W.add_string_constant(nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME), + ps_qast(), + $*W.add_numeric_constant($/, 'Int', $/.from), + $*W.add_numeric_constant($/, 'Int', $/.to) + ) + ) + ), + $qa + )); + } + } +} + +grammar QRegex::P5Regex::HookGrammar is Perl6::P5RegexGrammar { + method nibbler() { + my $*RX_TOP_LEVEL_NIBBLER := 0; + unless %*RX<DEBUGGER_SEEN> { + %*RX<DEBUGGER_SEEN> := 1; + $*RX_TOP_LEVEL_NIBBLER := 1; + } + QRegex::P5Regex::Grammar.HOW.find_method(QRegex::P5Regex::Grammar, 'nibbler')(self) + } +} + +class QRegex::P5Regex::HookActions is Perl6::P5RegexActions { + method nibbler($/) { + if $*RX_TOP_LEVEL_NIBBLER && $*DEBUG_HOOKS.has_hook('regex_region') { + my $file := nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME; + $*DEBUG_HOOKS.get_hook('regex_region')($file, $/.from, $/.to); + } + QRegex::P5Regex::Actions.nibbler($/); + } + + method quantified_atom($/) { + QRegex::P5Regex::Actions.quantified_atom($/); + my $qa := $/.ast; + if $qa && !(~$/ ~~ /^\s*$/) && $*DEBUG_HOOKS.has_hook('regex_atom') { + $/.make(QAST::Regex.new( + :rxtype('concat'), + QAST::Regex.new( + :rxtype('qastnode'), + :subtype('declarative'), + QAST::Op.new( + :op('call'), + QAST::WVal.new( :value($*DEBUG_HOOKS.get_hook('regex_atom')) ), + $*W.add_string_constant(nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME), + ps_qast(), + $*W.add_numeric_constant($/, 'Int', $/.from), + $*W.add_numeric_constant($/, 'Int', $/.to) + ) + ), + $qa + )); + } + } +} + +class Perl6::HookActions is Perl6::Actions { + my %uninteresting := nqp::hash( + 'package_declarator', 1, + 'routine_declarator', 1, + 'multi_declarator', 1, + 'type_declarator', 1, + 'regex_declarator', 1, + 'statement_prefix', 1 + ); + sub interesting_expr($e) { + my $accept := 1; + for $e.hash { + my $key := $_.key; + my $value := $_.value; + if %uninteresting{$key} { + $accept := 0; + last; + } + if $key eq 'scope_declarator' && $value<sym> eq 'has' { + $accept := 0; + last; + } + if $key eq 'scope_declarator' && ($value<sym> eq 'my' || $value<sym> eq 'our') { + if $value<scoped><declarator> -> $decl { + # Skip plain, boring declarations with no assignment. + if $decl<variable_declarator> && !$decl<initializer> { + $accept := 0; + last; + } + } + } + if $key eq 'circumfix' && $e<circumfix><pblock> { + $accept := 0; + last; + } + } + $accept + } + + method statement($/) { + Perl6::Actions.statement($/); + if $*ST_DEPTH <= 1 && $<EXPR> && interesting_expr($<EXPR>) { + my $stmt := $/.ast; + my $pot_hash := nqp::istype($stmt, QAST::Op) && + ($stmt.name eq '&infix:<,>' || $stmt.name eq '&infix:«=>»'); + my $nil := nqp::istype($stmt, QAST::Var) && $stmt.name eq 'Nil'; + if !$pot_hash && !$nil && $*DEBUG_HOOKS.has_hook('statement_simple') { + $/.make(QAST::Stmts.new( + QAST::Op.new( + :op('call'), + QAST::WVal.new( :value($*DEBUG_HOOKS.get_hook('statement_simple')) ), + $*W.add_string_constant(nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME), + ps_qast(), + $*W.add_numeric_constant($/, 'Int', $/.from), + $*W.add_numeric_constant($/, 'Int', $/.to) + ), + $stmt + )); + } + } + } + + method statement_control:sym<if>($/) { + if $*DEBUG_HOOKS.has_hook('statement_cond') { + my $from := $<sym>[0].from; + for $<xblock> { + my $ast := $_.ast; + $ast[0] := QAST::Stmts.new( + QAST::Op.new( + :op('call'), + QAST::WVal.new( :value($*DEBUG_HOOKS.get_hook('statement_cond')) ), + $*W.add_string_constant(nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME), + ps_qast(), + $*W.add_string_constant('if'), + $*W.add_numeric_constant($/, 'Int', $from), + $*W.add_numeric_constant($/, 'Int', $_<pblock>.from - 1) + ), + $ast[0] + ); + $from := $_<pblock>.to + 1; + } + } + Perl6::Actions.statement_control:sym<if>($/); + } + + sub simple_xblock_hook($/) { + if $*DEBUG_HOOKS.has_hook('statement_cond') { + my $stmt := $/.ast; + $stmt[0] := QAST::Stmts.new( + QAST::Op.new( + :op('call'), + QAST::WVal.new( :value($*DEBUG_HOOKS.get_hook('statement_cond')) ), + $*W.add_string_constant(nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME), + ps_qast(), + $*W.add_string_constant(~$<sym>), + $*W.add_numeric_constant($/, 'Int', $<sym>.from), + $*W.add_numeric_constant($/, 'Int', $<xblock><pblock>.from - 1) + ), + $stmt[0] + ); + } + } + + method statement_control:sym<unless>($/) { + Perl6::Actions.statement_control:sym<unless>($/); + simple_xblock_hook($/); + } + + method statement_control:sym<while>($/) { + Perl6::Actions.statement_control:sym<while>($/); + simple_xblock_hook($/); + } + + method statement_control:sym<repeat>($/) { + Perl6::Actions.statement_control:sym<repeat>($/); + if $*DEBUG_HOOKS.has_hook('statement_cond') { + my $stmt := $/.ast; + $stmt[0] := QAST::Stmts.new( + QAST::Op.new( + :op('call'), + QAST::WVal.new( :value($*DEBUG_HOOKS.get_hook('statement_cond')) ), + $*W.add_string_constant(nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME), + ps_qast(), + $*W.add_string_constant(~$<wu>), + $*W.add_numeric_constant($/, 'Int', $<wu>.from), + $*W.add_numeric_constant($/, 'Int', $<xblock> + ?? $<xblock><pblock>.from - 1 + !! $/.to) + ), + $stmt[0] + ); + } + } + + method statement_control:sym<loop>($/) { + if $*DEBUG_HOOKS.has_hook('statement_cond') { + for <e1 e2 e3> -> $expr { + if $/{$expr} -> $m { + $m[0].make(QAST::Stmts.new( + QAST::Op.new( + :op('call'), + QAST::WVal.new( :value($*DEBUG_HOOKS.get_hook('statement_cond')) ), + $*W.add_string_constant(nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME), + ps_qast(), + $*W.add_string_constant('loop'), + $*W.add_numeric_constant($/, 'Int', widen_expr_from($m[0])), + $*W.add_numeric_constant($/, 'Int', widen_expr_to($m[0])) + ), + $m[0].ast + )); + } + } + } + Perl6::Actions.statement_control:sym<loop>($/); + } + + sub widen_expr_from($e) { + my $from := $e.from; + for @($e) { + if $_.from < $from { + $from := $_.from; + } + } + $from + } + + sub widen_expr_to($e) { + my $to := $e.to; + for @($e) { + if $_.to > $to { + $to := $_.to; + } + } + $to + } + + method statement_control:sym<for>($/) { + Perl6::Actions.statement_control:sym<for>($/); + simple_xblock_hook($/); + } + + method statement_control:sym<given>($/) { + Perl6::Actions.statement_control:sym<given>($/); + simple_xblock_hook($/); + } + + method statement_control:sym<when>($/) { + Perl6::Actions.statement_control:sym<when>($/); + simple_xblock_hook($/); + } + + method statement_control:sym<require>($/) { + Perl6::Actions.statement_control:sym<require>($/); + if $*DEBUG_HOOKS.has_hook('statement_simple') { + $/.make(QAST::Stmts.new( + QAST::Op.new( + :op('call'), + QAST::WVal.new( :value($*DEBUG_HOOKS.get_hook('statement_simple')) ), + $*W.add_string_constant(nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME), + ps_qast(), + $*W.add_numeric_constant($/, 'Int', $/.from), + $*W.add_numeric_constant($/, 'Int', $/.to) + ), + $/.ast + )); + } + } + + sub routine_hook($/, $body, $type, $name) { + if $*DEBUG_HOOKS.has_hook('routine_region') { + my $file := nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME; + $*DEBUG_HOOKS.get_hook('routine_region')($file, $/.from, $/.to, $type, $name); + } + } + + method routine_declarator:sym<sub>($/) { + Perl6::Actions.routine_declarator:sym<sub>($/); + routine_hook($/, $<routine_def>, 'sub', + $<routine_def><deflongname> ?? ~$<routine_def><deflongname>[0] !! ''); + } + method routine_declarator:sym<method>($/) { + Perl6::Actions.routine_declarator:sym<method>($/); + routine_hook($/, $<method_def>, 'method', + $<method_def><longname> ?? ~$<method_def><longname> !! ''); + } + method routine_declarator:sym<submethod>($/) { + Perl6::Actions.routine_declarator:sym<submethod>($/); + routine_hook($/, $<method_def>, 'submethod', + $<method_def><longname> ?? ~$<method_def><longname> !! ''); + } + method routine_declarator:sym<macro>($/) { + #Perl6::Actions.routine_declarator:sym<macro>($/); + routine_hook($/, $<macro_def>, 'macro', + $<macro_def><deflongname> ?? ~$<macro_def><deflongname>[0] !! ''); + } +} + +class Perl6::HookGrammar is Perl6::Grammar { + my %seen_files; + + method statementlist($*statement_level = 0) { + my $file := nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME; + unless nqp::existskey(%*SEEN_FILES, $file) { + if $*DEBUG_HOOKS.has_hook('new_file') { + # First time we've seen this file; register it. + $*DEBUG_HOOKS.get_hook('new_file')($file, self.MATCH.orig); + } + %*SEEN_FILES{$file} := 1; + } + my $cur_st_depth := $*ST_DEPTH; + { + my $*ST_DEPTH := $cur_st_depth + 1; + Perl6::Grammar.HOW.find_method(Perl6::Grammar, 'statementlist')(self, $*statement_level) + } + } + + method comp_unit() { + my $*ST_DEPTH := 0; + my %*SEEN_FILES; + + # Fiddle the %*LANG for the appropriate actions. + %*LANG<Regex> := Perl6::HookRegexGrammar; + %*LANG<Regex-actions> := Perl6::HookRegexActions; + %*LANG<P5Regex> := QRegex::P5Regex::HookGrammar; + %*LANG<P5Regex-actions> := QRegex::P5Regex::HookActions; + %*LANG<MAIN> := Perl6::HookGrammar; + %*LANG<MAIN-actions> := Perl6::HookActions; + + Perl6::Grammar.HOW.find_method(Perl6::Grammar, 'comp_unit')(self) + } + + method blockoid() { + my $*ST_DEPTH := 0; + Perl6::Grammar.HOW.find_method(Perl6::Grammar, 'blockoid')(self) + } + + method semilist() { + my $cur_st_depth := $*ST_DEPTH; + { + my $*ST_DEPTH := $cur_st_depth + 1; + Perl6::Grammar.HOW.find_method(Perl6::Grammar, 'semilist')(self) + } + } + + method comment:sym<#>() { + my $c := Perl6::Grammar.HOW.find_method(Perl6::Grammar, 'comment:sym<#>')(self); + if $c { + my $comment := $c.MATCH.Str; + if $comment ~~ /'#?BREAK'/ { + if $*DEBUG_HOOKS.has_hook('new_breakpoint') { + my $file := nqp::getlexcaller('$?FILES') // $*ANON_CODE_NAME; + $*DEBUG_HOOKS.get_hook('new_breakpoint')($file, $c.MATCH().from()); + } + } + } + $c + } +} + +class Perl6::Debugger is Perl6::Compiler { + my $repl_code := 1; + method eval(*@pos, *%named) { + my $*ANON_CODE_NAME := "<REPL {$repl_code++}>"; + if $*DEBUG_HOOKS.has_hook('reset') { + $*DEBUG_HOOKS.get_hook('reset')(); + } + nqp::findmethod(Perl6::Compiler, 'eval')(self, |@pos, |%named) + } +} + +sub MAIN(*@ARGS) { + # XXX Parrot compat hack. + if nqp::islist(@ARGS[0]) { + @ARGS := @ARGS[0]; + } + + # Initialize dynops. + nqp::p6init(); + + # Create and configure compiler object. + my $comp := Perl6::Debugger.new(); + + $comp.language('perl6'); + $comp.parsegrammar(Perl6::HookGrammar); + $comp.parseactions(Perl6::HookActions); + $comp.addstage('syntaxcheck', :before<ast>); + $comp.addstage('optimize', :after<ast>); + hll-config($comp.config); + my $COMPILER_CONFIG := $comp.config; + nqp::bindhllsym('perl6', '$COMPILER_CONFIG', $comp.config); + + + # Determine Perl6 and NQP dirs. + my $config := nqp::backendconfig(); + my $sep := $config<osname> eq 'MSWin32' ?? '\\' !! '/'; +#?if jvm + my $execname := nqp::atkey(nqp::jvmgetproperties,'perl6.execname'); +#?endif +#?if !jvm + my $execname := nqp::execname(); +#?endif + my $install-dir := $execname eq '' + ?? $comp.config<prefix> + !! nqp::substr($execname, 0, nqp::rindex($execname, $sep, nqp::rindex($execname, $sep) - 1)); + + my $rakudo-home := $comp.config<static_rakudo_home> + // nqp::getenvhash()<PERL6_HOME> + // nqp::getenvhash()<RAKUDO_HOME> + // $install-dir ~ '/share/perl6'; + if nqp::substr($rakudo-home, nqp::chars($rakudo-home) - 1) eq $sep { + $rakudo-home := nqp::substr($rakudo-home, 0, nqp::chars($rakudo-home) - 1); + } + + my $nqp-home := $comp.config<static_nqp_home> + // nqp::getenvhash()<NQP_HOME> + // $install-dir ~ '/share/nqp'; + if nqp::substr($nqp-home, nqp::chars($nqp-home) - 1) eq $sep { + $nqp-home := nqp::substr($nqp-home, 0, nqp::chars($nqp-home) - 1); + } + + nqp::bindhllsym('perl6', '$RAKUDO_HOME', $rakudo-home); + nqp::bindhllsym('perl6', '$NQP_HOME', $nqp-home); + + + # Add extra command line options. + my @clo := $comp.commandline_options(); + @clo.push('setting=s'); + @clo.push('c'); + @clo.push('I=s'); + @clo.push('M=s'); + @clo.push('nqp-lib=s'); + + # Set up module loading trace + my @*MODULES := []; + + # Set up END block list, which we'll run at exit. + nqp::bindhllsym('perl6', '@END_PHASERS', []); + + # Force loading of the debugger module. + my $debugger; + my $i := 1; + while @ARGS[$i] ~~ /^\-/ { + if @ARGS[$i] ~~ /^\-D/ { + $debugger := "-M" ~ nqp::substr(@ARGS[$i], 2); + nqp::splice(@ARGS, [], $i, 1); + last; + } + $i++; + } + + if !(nqp::defined($debugger)) { + $debugger := '-MDebugger::UI::CommandLine'; + } + + my $pname := @ARGS.shift(); + @ARGS.unshift('-Ilib'); + @ARGS.unshift($debugger); + @ARGS.unshift($pname); + + # Set up debug hooks object. + my $*DEBUG_HOOKS := Perl6::DebugHooks.new(); + + # Enter the compiler. + $comp.command_line(@ARGS, :encoding('utf8'), :transcode('ascii iso-8859-1')); + + # Run any END blocks before exiting. + for nqp::gethllsym('perl6', '@END_PHASERS') { + my $result := $_(); + nqp::isfalse(nqp::isnull($result)) + && nqp::can($result, 'sink') && $result.sink; + } +}