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

[RFC] Make the run macro method also accept a program as an AST node #2639

Closed
asterite opened this issue May 23, 2016 · 6 comments
Closed

[RFC] Make the run macro method also accept a program as an AST node #2639

asterite opened this issue May 23, 2016 · 6 comments

Comments

@asterite
Copy link
Member

Right now we have the run macro method that can be used like this:

foo.cr:

# This compiles and executes the "bar.cr" program, passing "world" as an argument.
# The output of this sub-program is embedded in foo.cr.
{{ run "./bar.cr", "world" }}

bar.cr:

# Here we print "puts ...", and this gets embedded in "foo.cr"
puts %(puts "hello #{ARGV[0]}")

This is nice and powerful, but requires us to create an extra file for the sub-program. Many times this sub-program is very small. Some cases I can see right now is ecr/process, slang/process (similar to the above), but we can also imagine simple programs like Time.now.to_s.

Another issue with the sub-program being in a separate file is that this file is usually included in the output of crystal doc and other tools.

So, the idea is to allow any expression as the first run argument. If it's a string literal then it must denote a program to compile and run. Otherwise, a program with that argument is compiled and run. For example:

# This executes `puts Time.now.to_s` in a sub-program and puts the output in this program
{{ run(puts Time.now.to_s) }}

Another example, removing the need to have an ecr/process.cr file:

# If the run argument is a series of expressions, those expressions are put in the program
# (you usually can't require inside a begin/end, but here the begin/end is just to wrap
# the series of expressions to use as a program)
{{ run(
     begin
       require "ecr"
       puts ECR.process_file(ARGV[0], ARGV[1])
     end,
     "file.ecr",
     "some_io"
   ) }}

With this, there's no need to merge #2638 because we can do:

{{ run(puts Time.utc_now.to_s("%Y-%m-%d")) }}

And we could even create a small program that computes the target triple using LLVM, but at compile time (of course we can do it right now with a separate file, but it feels more correct to just embed it in the only place it's going to be used).

In all the snippets above you can see all the expressions are puts, but one can imagine a sub-program that invokes some routine, passing STDOUT and appending directly to that, so I'd rather leave that puts explicit and not add it implicitly to all sub-programs.

Thoughts?

/cc @bcardiff

@bcardiff
Copy link
Member

It's nice that it solves the intermediate auxiliary file. Not sure about the run overload. Maybe just build_run would be enough for inline and intermediate auxiliary files. And leave run for execution system commands.

Although this approach will be as slow as the auxiliary file, right? The built in macro of #2638 is still the fastest way.

In which context the expression will be compiled?

ECR module would be available because is built in in crystal or because is the context project? This could go down to having development dependencies vs runtime dependencies 😕

If this is added, the next question will be if it is able to build an expression during compile time and compile/execute it ... 💫

Despite no been an optimal way to solve the Time.now macro, I prefer this approach since it would be more maintainable IMO. Yet I will second the idea of adding a now macro that will expand to {{ run(puts Time.utc_now.to_s("%Y-%m-%d")) }}.

IMO having more and more semantics of macros in the compiler feels the same way as C internals in Ruby.

@waterlink
Copy link
Contributor

I like that idea!

Von meinem iPhone gesendet

Am 23.05.2016 um 9:20 PM schrieb Ary Borenszweig [email protected]:

Right now we have the run macro method that can be used like this:

foo.cr:

This compiles and executes the "bar.cr" program, passing "world" as an argument.

The output of this sub-program is embedded in foo.cr.

{{ run "./bar.cr", "world" }}
bar.cr:

Here we print "puts ...", and this gets embedded in "foo.cr"

puts %(puts "hello #{ARGV[0]}")
This is nice and powerful, but requires us to create an extra file for the sub-program. Many times this sub-program is very small. Some cases I can see right now is ecr/process, slang/process (similar to the above), but we can also imagine simple programs like Time.now.to_s.

Another issue with the sub-program being in a separate file is that this file is usually included in the output of crystal doc and other tools.

So, the idea is to allow any expression as the first run argument. If it's a string literal then it must denote a program to compile and run. Otherwise, a program with that argument is compiled and run. For example:

This executes puts Time.now.to_s in a sub-program and puts the output in this program

{{ run(puts Time.now.to_s) }}
Another example, removing the need to have an ecr/process.cr file:

If the run argument is a series of expressions, those expressions are put in the program

(you usually can't require inside a begin/end, but here the begin/end is just to wrap

the series of expressions to use as a program)

{{ run(
begin
require "ecr"
puts ECR.process_file(ARGV[0], ARGV[1])
end,
"file.ecr",
"some_io"
) }}
With this, there's no need to merge #2638 because we can do:

{{ run(puts Time.utc_now.to_s("%Y-%m-%d")) }}
And we could even create a small program that computes the target triple using LLVM, but at compile time (of course we can do it right now with a separate file, but it feels more correct to just embed it in the only place it's going to be used).

In all the snippets above you can see all the expressions are puts, but one can imagine a sub-program that invokes some routine, passing STDOUT and appending directly to that, so I'd rather leave that puts explicit and not add it implicitly to all sub-programs.

Thoughts?

/cc @bcardiff


You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub

@ozra
Copy link
Contributor

ozra commented May 23, 2016

Similar to above, but more implicit, I would really like something along the lines of:

astmacro foo(a : ASTNode, b : NumberLiteral, c : BlockLiteral)
  x = do_stuff_here a
  y = which_goes_into_a_file b
  z = and_is_used_with_run c
  Expressions.from [x, y, z]
end

Which would be a regular function but run in external file, with arguments passed over and parsed to AST-nodes passed in. Return either AST or String as fit your bill (the AST would of course be to_s'ed before printing back over stdout). And the run call and the node-stringification etc. is of course fluffed in to a macro automatically...

@asterite
Copy link
Member Author

@bcardiff Yes, it's still slower than the now(...) call. Maybe we should have both.

@ozra Maybe passing AST nodes to a sub-program, operating on them is something that can already be done, though of course the AST nodes are first serialized with to_s and you have to parse them back. I guess your approach would simplify that, but I wouldn't want this to be abused as it does make compile times a lot slower.

@asterite
Copy link
Member Author

I just tried this, and this program:

build_date = {{ run(print Time.now.to_s).stringify }}
p build_date

takes 0.3s more to compile, compared to an empty program. Well,actually, the first compile takes more time (2s more), but then the compiler caches the bc/o files for this program (it uses md5(source)) so next compiles are faster. This is similar to what's done with run by passing a filename.

We could have another name instead of run, but I don't see why we'd want to do that (more names to remember). Passing a string as a program to execute makes no sense, so a string means a filename.

@ozra
Copy link
Contributor

ozra commented May 23, 2016

@asterite, yeah, understandably: it would add a lot of overhead indeed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants