-
Notifications
You must be signed in to change notification settings - Fork 426
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 erlang-module support #2094
Conversation
lib/livebook/runtime/evaluator.ex
Outdated
Enum.map( | ||
statements, | ||
fn str -> | ||
{:ok, result, _} = :erl_scan.string(String.to_charlist(str)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to handle errors accordingly. It will be similar to the eval_statements
branch, there is probably a lot we can share between the two. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback - will adapt based on the original-code.
@@ -675,6 +675,47 @@ defmodule Livebook.Runtime.Evaluator do | |||
end | |||
|
|||
defp eval(:erlang, code, binding, env) do | |||
is_module = String.starts_with?(code, "-module(") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of detecting based on a string, we can do the first pass with :erl_scan.string(String.to_charlist(str))
and then traverse all entries and see if any defines a module attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your opinion: Analyze the forms to only allow one module? Throw an error to instruct the user?
Seems more inline with how Erlang defines modules.
If you defined multiple modules we would have to have a lot more checks, and we would have to seperate the statements per module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, for now, I think the best is to:
- Check if it has attributes
- If it has attributes, one of them (no more, no less) has to be a module attribute
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For visibility @josevalim @fnchooft Did a PR for this here!
fnchooft#1
This is very exciting @fnchooft, I have added some comments. We need error handling and tests. :) |
3af8aa1
to
9296c48
Compare
Hi Jose, still W.I.P... Changes:
|
lib/livebook/runtime/evaluator.ex
Outdated
Enum.map( | ||
form_statements, | ||
fn {form_statement} -> | ||
{:ok, form} = :erl_parse.parse_form(form_statement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of try/catch below, we should pattern match and handle those forms similar to the other branch. You can look into Elixir's with
(which is similar to Erlang's maybe
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been cleaned up in the manner you proposed.
lib/livebook/runtime/evaluator.ex
Outdated
catch | ||
kind, error -> | ||
stacktrace = prune_stacktrace(:erl_eval, __STACKTRACE__) | ||
{{:error, kind, error, stacktrace}, []} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this outer try-catch is not necessary. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Morning - this has been cleaned up in latest update.
Also note that we need to recognise which modules are defined and used by each evaluation and return this information, similarly to livebook/lib/livebook/runtime/evaluator.ex Lines 766 to 774 in 9296c48
For Elixir we use tracer, but as a reference that's how the entries look like: livebook/lib/livebook/runtime/evaluator.ex Lines 938 to 949 in 9296c48
|
@jonatanklosko - Thanks for the insight! Question: In the case we would limit the Code-erlang-block to the definition of only one Erlang-module, not allowing other expressions for instance, would it be enough to only add the module_name to the used_identifiers? |
If we enforce just a single module we can compute md5 of the source as the module version or phash of the forms, because we would know all the code is about the module. |
9296c48
to
1447859
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Can you please add some tests that exercise the error branches too? Some that make the tokenizer fail after a module is defined. And another that makes the parser file :) thanks!
The code is deemed a module definition if the first line start with "-module(". In this case the entire code-block is interpreted as erlang-module and if there are no errors the module is compiled and loaded. Basic error handling has been added Basic tests have been added
1447859
to
13e17f6
Compare
Afternoon - had some time to add:
@jonatanklosko - I tried adding only the modulename + md5 to the logic - as you suggested. For instance - it is not detecting correctly if a call to a function in the module is stale or not. |
Yeah, and the same is going to be the case if you define a module with For elixir we use compiler tracer to get the referenced modules and we then we use this information here: livebook/lib/livebook/runtime/evaluator.ex Lines 861 to 864 in efeebe9
@josevalim I'm thinking that ideally we would emit the tracer events programmatically (defining a module, referencing functions) and then the rest is handled using the same code path. Does that make sense, or is there any reason not to do that? |
@jonatanklosko emulating the tracer or the |
Ah true, actually it should be simpler to use Other notes: we need to persist the module bytecode with |
@jonatanklosko - Apologies for letting this simmer for some months. I have not been following changes in livebook - but is there still value in this approach? If I have some time I will rebase and re-read the comments. Kind regards, |
@fnchooft no worries, nothing changed in this matter, so sounds good :) |
Uffizzi Preview |
Hello! @fnchooft @josevalim @jonatanklosko Is there an update on this feature? |
Hi, yesterday night I rebased on master, hence the trigger.
I want to try and reserve some time to complete this; it seems we are
almost there.
Kind regards,
Fabian
…On Wed, Sep 25, 2024 at 4:09 PM Guillermo Saez Santana < ***@***.***> wrote:
Hello! @fnchooft <https://github.com/fnchooft> @josevalim
<https://github.com/josevalim> @jonatanklosko
<https://github.com/jonatanklosko> Is there an update on this feature?
—
Reply to this email directly, view it on GitHub
<#2094 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABD6LCJBNPOKGPIQSGCT3IDZYMC65AVCNFSM6AAAAABOZCPPSGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGNZUHE3TCNZUHE>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
[] | ||
) | ||
|
||
assert_receive {:runtime_evaluation_response, :code_4, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fnchooft Again, I'm not very faimiliar with the test logic in Elixir, but I noticed that for most tests it actually returns a map, not a tuple. i changed this lines to
assert_receive {:runtime_evaluation_response, :code_4,
terminal_text("\"erlang module successfully compiled\""), metadata()}
[] | ||
) | ||
|
||
assert_receive {:runtime_evaluation_output, :code_4, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert_receive {:runtime_evaluation_output, :code_4,
terminal_text(":1:52: function go/0 already defined\n", true)}
assert_receive { | ||
:runtime_evaluation_response, | ||
:code_4, | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert_receive {
:runtime_evaluation_response,
:code_4,
error(_),
%{code_markers: _List}
}
Hi yesterday i spent some time cleaning those up. I will try to share today.
Kind regards,
Fabian
…On Thu, Sep 26, 2024, 23:22 Guillermo Saez Santana ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In test/livebook/runtime/evaluator_test.exs
<#2094 (comment)>
:
> @@ -1300,6 +1300,53 @@ defmodule Livebook.Runtime.EvaluatorTest do
assert_receive {:runtime_evaluation_response, :code_1, terminal_text("6"), metadata()}
end
+ test "evaluate erlang-module code", %{evaluator: evaluator} do
+ Evaluator.evaluate_code(
+ evaluator,
+ :erlang,
+ "-module(tryme). -export([go/0]). go() ->{ok,went}.",
+ :code_4,
+ []
+ )
+
+ assert_receive {:runtime_evaluation_response, :code_4,
@fnchooft <https://github.com/fnchooft> Again, I'm not very faimiliar
with the test logic in Elixir, but I noticed that for most tests it
actually returns a map, not a tuple. i changed this lines to
assert_receive {:runtime_evaluation_response, :code_4,
%{:text => "\"erlang module successfully compiled\""}, metadata()}
—
Reply to this email directly, view it on GitHub
<#2094 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABD6LCJC37DQWU7NNCPBGLTZYS6NHAVCNFSM6AAAAABOZCPPSGVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZDGMZSGYZTAMJQGU>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Hi all, I started a new pull-request based on latest main. Thanks all for the the discussions/contributions here - lets pick up the remaining issues in #2806? |
The code is deemed a module definition if the first line start with "-module(".
In this case the entire code-block is interpreted as erlang-module and if there are no errors the module is compiled and loaded.
An example for a working livebook (livemd-file):
Untitled notebook
Erlang - on the fly ( inline_module ) via Livebook