Search code examples
regexreplacerakucapturing-group

Capture and execute multiline code and incorporate result in raku


This is a markdown document example.md I have:

## New language

Raku is a new language different from Perl.

## what does it offer
+ Object-oriented programming including generics, roles and multiple dispatch
+ Functional programming primitives, lazy and eager list evaluation, junctions, autothreading and hyperoperators (vector operators)
+ Parallelism, concurrency, and asynchrony including multi-core support
+ Definable grammars for pattern matching and generalized string processing
+ Optional and gradual typing



This code will be evaluated.


```{raku evaluate=TRUE}
4/5
```



Rakudo is a compiler for raku programming language. Install it and you're all set to run raku programs!

This code will be evaluated.

```{raku evaluate=TRUE}
say "this is promising";
say $*CWD;
```



This code will **not** be evaluated.


```{raku evaluate=FALSE}
say "Hello world";
```

which I want to convert into example.md as shown below with the code and output within it.

## New language

Raku is a new language different from Perl.

## what does it offer
+ Object-oriented programming including generics, roles and multiple dispatch
+ Functional programming primitives, lazy and eager list evaluation, junctions, autothreading and hyperoperators (vector operators)
+ Parallelism, concurrency, and asynchrony including multi-core support
+ Definable grammars for pattern matching and generalized string processing
+ Optional and gradual typing



This code will be evaluated.

Code:
```{raku evaluate=TRUE}
4/5
```

Output:
```
0.8
```

Rakudo is a compiler for raku programming language. Install it and you're all set to run raku programs!

This code will be evaluated.

Code:
```{raku evaluate=TRUE}
say "this is promising";
say $*CWD;
```

Output:
```
this is promising
"C:\Users\suman".IO
```

This code will **not** be evaluated.

Code:
```{raku evaluate=FALSE}
say "Hello world";
```

What I want to accomplish is:

  • capture the code between backticks{raku evaluate} and backticks
  • execute the code if evaluate is TRUE
  • insert the code and output back into the document

What I tried to do:

  1. Capture multiline code and evaluate expression
my $array= 'example.md'.IO.slurp;

#multiline capture code chunk and evaluate separately
if $array~~/\`\`\`\{raku (.*)\}(.*)\`\`\`/ {
    #the first capture $0 will be evaluate
    if $0~~"TRUE"{
        #execute second capture which is code chunk which is captured in $1
        }else {
       # don't execute code
        };
};
  1. create a temp.p6 file and write code chunk $1 from above into it
my $fh="temp.p6".IO.spurt: $1;
  1. execute the chunk if $0 is TRUE
my $output= q:x/raku temp.p6/ if $0==TRUE
  1. integrate all this into final example.md while we create intermediate example_new.md
my $fh-out = open "example_new.md", :w; # Create a new file

# Print out next file, line by line
for "$file.tex".IO.lines -> $line {

    # write output of code to example_new.md

}
$fh-out.close;

# copy
my $io = IO::Path.new("example_new.md");
$io.copy("example.md");

# clean up
unlink("example.md");

# move
$io.rename("example.md");

I am stuck in the first step. Any help?


Solution

  • Code that accomplishes "What I want to accomplish"

    You can run this code against your data with glot.io.

    use v6;
    
    constant $ticks = '```';
    
    my regex Search {
      $ticks '{raku evaluate=' $<evaluate>=(TRUE|FALSE) '}'
      $<code>=[<!before $ticks> .]*
      $ticks
    }
    
    sub Replace ($/) {
      "Code:\n" ~ $ticks ~ $<code> ~ $ticks ~
        ($<evaluate> eq 'TRUE'
          ?? "\n\n" ~ 'Output:' ~ "\n" ~ $ticks ~ "\n" ~ Evaluate($<code>) ~ $ticks
          !! '');
    }
    
    sub Evaluate ($code) {
      my $out; my $*OUT = $*OUT but role { method print (*@args) { $out ~= @args } }
      use MONKEY; my $eval-result = EVAL $code;
      $out // $eval-result ~ "\n"
    }
    
    spurt
      'example_new.md',
      slurp('example.md')
        .subst: &Search, &Replace, :g;
    

    Explanation

    Starting at the bottom and then working upwards:

    • The .subst method substitutes parts of its invocant string that need to be replaced and returns the revised string. .subst's first argument is a matcher; it can be a string, or, as here, a regex -- &Search1. .subst's second argument is a replacement; this can also be a string, or, as here, a Callable -- &Replace. If it's a Callable then .subst passes the match from the matcher as a match object2 as the first argument to the Callable. The :g adverb directs .subst to do the search/replace repeatedly for as many matches as there are in the invocant string.

    • slurp generates a string in one go from a file. No need for open, using handles, close, etc. Its result in this case becomes the invocant of the .subst explained above.

    • spurt does the opposite, generating a file in one go from a string, in this case the results of the slurp(...).subst... operation.

    • The Evaluate routine generates a string that's the output from evaluating the string of code passed to it. To capture the result of evaluation it temporarily modifies Raku's STDOUT variable $*OUT, redirecting prints (and thus also says etc.) to the internal variable $out before EVALing the code. If the EVAL results in anything being printd to $out then that is returned; if not, then the result of the EVAL is returned (coerced to a string by the ~). (A newline is appended in this second scenario but not the first because that is what's needed to get the correctly displayed result given how you've "specified" things by your example.)

    • The Replace routine is passed a match object from a call of the Code regex. It reconstructs the code section (without the evaluate bit) using the $<code> capture. If the $<evaluate> capture is 'TRUE' then it also appends a fresh Output: section using the Evaluate routine explained above to produce the code's output.

    • The Code regex matches a code section. It captures the TRUE or FALSE setting from the evaluate directive into a capture named $<evaluate> and the code into a capture named $<code>.

    Footnotes

    1 To pass a routine (a regex is a routine) rather than call it, it must be written with a sigil (&foo), not without (foo).

    2 It does this even if the matcher was merely a string!