{"slug": "embedding-eyg-in-erlang", "title": "Embedding EYG in Erlang", "summary": "A new developer guide shows how to embed the EYG scripting language in an Erlang application named my_counters, starting with a minimal module that parses EYG source via eyg@parser:all_from_string and executes it through eyg@interpreter@expression:execute, returning {ok,{integer,3}} for the script !int_add(2, 1). The guide adds pretty-printed parser and interpreter errors with source spans and hints, and explains that pure EYG programs cannot perform side effects until the Erlang host handles algebraic effects such as Log, resuming execution with a reply value.", "body_md": "This guide adds EYG scripting to an Erlang application `my_counters`.\nJust want the code look at [example](../examples/my_counters_erlang).\n\nEmbedding EYG in Erlang gives you a scripting environment with tight controls over what can be accessed. With this control you can:\n\n- Expose scripting to the users, maybe for small queries or automations\n- Build a safer shell with predefined dev tasks\n- Provide a set of business logic functions to agents\n\n## Install dependencies\n\nEYG's toolkit consists of a set of composable parts, for this guide install the following.\n\n## Run a pure program\n\nBelow is the simplest Erlang module that will run EYG code.\n\n```\n-module(my_counters_script).\n\n-export([run/1]).\n\nrun(Code) when is_binary(Code) ->\n  case eyg@parser:all_from_string(Code) of\n    {ok, Source} -> \n      Result = eyg@interpreter@expression:execute(Source, []),\n      Result;\n    {error, Reason} -> {error, Reason}\n  end.\n```\n\nStart an Erlang shell, with this module loaded, and run your first scripts.\n\n```\n(counters@MyComputer)1> my_counters_script:run(<<\"!int_add(2, 1)\">>).\n{ok,{integer,3}}\n```\n\nThis is everything needed to run any self-contained, pure EYG program. We will add two affordances that make it easier to work with.\n\nFirst we will pretty print the errors. Each EYG package has its own pretty printing functions. The code below will handle formatting parser errors as well as runtime values and errors. The return value from this function is now a binary that can be presented to the user.\n\n``` php\nrun(Code) when is_binary(Code) ->\n  case eyg@parser:all_from_string(Code) of\n    {ok, Source} -> \n      Result = eyg@interpreter@expression:execute(Source, []),\n      case Result of\n        {ok, Value} -> debug_interpreter_value(Value);\n        {error, Reason} -> debug_interpreter_error(Reason, Code)\n      end;\n    {error, Reason} -> debug_parser_error(Reason, Code)\n  end.\n\ndebug_parser_error(Reason, Code) ->\n  eyg@parser:format_error(Reason, Code).\n\ndebug_interpreter_value(Value) ->\n  eyg@interpreter@simple_debug:inspect(Value).\n\ndebug_interpreter_error({Reason, Span, _, _}, Code) ->\n  eyg@parser:render_error(\n    eyg@interpreter@simple_debug:describe(Reason),\n    eyg@interpreter@simple_debug:hint(Reason),\n    Code,\n    Span\n  ).\n```\n\nAll the examples in this guide we are running from the erlang shell so it's convenient for our run function to accept charlists as well as binaries.\n\n``` php\nrun(Code) when is_list(Code) ->\n    run(unicode:characters_to_binary(Code));\nrun(Code) when is_binary(Code) ->\n  % existing body\n```\n\nNow a runtime errors prints useful output.\n\n```\n(counters@MyComputer)1> io:put_chars(my_counters_script:run(<<\"!int_add(2, foo)\">>)).\nerror: variable undefined: foo\nhint: check the variable name or add a `let` binding before it is used\n\n 1 | !int_add(2, foo)\n                 ^^^\n```\n\n## Effects\n\nEYG programs in this first runner cannot perform any side effect. This is already good enough for a configuration script but for anything more we need to add effects. Running a simple hello world currently fails.\n\n```\nperform Log(\"Hello, world!\")\n(counters@MyComputer)1> io:put_chars(my_counters_script:run(<<\"perform Log(\\\"Hello, World!\\\")\">>)).\nerror: unhandled effect Log(\"Hello, World!\")\nhint: handle this effect or run the program in a runtime that supports it\n\n 1 | perform Log(\"Hello, World!\")\n     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n```\n\nAny error interpreting an EYG program returns the reason for the break and the full program state. An unhandled effect is one type of break.\n\n**For an explanation of what an algebraic effect is check out this [post](https://crowdhailer.me/2025-02-14/algebraic-effects-are-a-functional-approach-to-manage-side-effects/)**\n\nWhen execution stops at an unhandled effect, the Erlang host can implement it and resume the EYG program with the reply value.\nWe update the `run` function so the initial result from executing the expression is passed to a `loop` function that will keep running the program for every break that can be handled.\n\n``` php\nrun(Code) when is_binary(Code) ->\n  Output = case eyg@parser:all_from_string(Code) of\n    {ok, Source} -> \n      Result = loop(eyg@interpreter@expression:execute(Source, [])),\n      % same as before\n```\n\nThe implementation of `loop` matches on an `unhandled_effect`.\nIf the extrinsic implementation of the effect succeeds the loop recurses.\n\n```\n-include_lib(\"eyg_interpreter/include/eyg@interpreter@break_UnhandledEffect.hrl\").\n-include_lib(\"eyg_interpreter/include/eyg@interpreter@value_String.hrl\").\n\nloop({error, {#unhandled_effect{label = Label, value = Value}, Span, Env, Stack}}) ->\n  case extrinsic(Label, Value) of\n    {ok, Reply} ->\n      loop(eyg@interpreter@expression:resume(Reply, Env, Stack));\n    {error, Reason} ->\n      {error, {Reason, Span, Env, Stack}}\n  end;\nloop(Result) -> Result.\n\nextrinsic(<<\"Log\">>, #string{value = String})->\n  io:put_chars(String),\n  {ok, eyg@interpreter@value:unit()};\nextrinsic(Label, Value) -> {error, #unhandled_effect{label = Label, value = Value}}.\n```\n\nEffects can be implemented at any level of abstraction.\nThey can be core utilities like logging or more specific to the application.\nThe counter example has additional utilities `Random` and `Now` as well as three effects for managing the counter application.\n\n## Type checking\n\nBefore running an EYG we can check that it has no runtime errors. The type inferences is sound, any program that passes type checking has no runtime errors. An effect context is passed to type checking ensuring that a program has no errors including trying to call effects that are unavailable.\n\nIn this type checking loop we start with a pure context and extend it with just the the `Log` effect.\n\n``` php\ntype_check(Source) ->\n  Infer = eyg@analysis@inference@levels_j@contextual,\n  Analysis = type_check_loop(Infer:check(inference_context(), Source)),\n  Infer:all_errors(Analysis).\n\ntype_check_loop({done, Analysis}) -> Analysis;\ntype_check_loop({lookup, _Reference, Resume}) -> type_check_loop(Resume({error, nil})).\n\ninference_context() ->\n  Infer = eyg@analysis@inference@levels_j@contextual,\n  Context0 = Infer:pure(),\n  Unit = eyg@analysis@type_@isomorphic:record([]),\n  Context1 = Infer:with_effect(Context0, <<\"Log\">>, string, Unit),\n  Context1.\n\ndebug_type_error({Span, Reason}, Source) ->\n  eyg@parser:render_error(\n      eyg@analysis@type_@binding@debug:render_reason(Reason),\n      eyg@analysis@type_@binding@debug:hint(Reason),\n      Source,\n      Span\n  ).\n```\n\nReplace the successful parse branch in `run/1` with the following.\nIf type checking succeeds, run the program; otherwise, return the formatted diagnostics.\n\n``` php\n{ok, Source} ->\n  case type_check(Source) of\n    [] ->\n      Result = loop(eyg@interpreter@expression:execute(Source, [])),\n      case Result of\n        {ok, Value} -> debug_interpreter_value(Value);\n        {error, Reason} -> debug_interpreter_error(Reason, Code)\n      end;\n    Errors ->\n      unicode:characters_to_binary([[debug_type_error(Error, Code), \"\\n\"] || Error <- Errors])\n  end;\n```\n\nYou now have an EYG scripting environment that checks programs before running them and has the full power of Erlang available for implementing effects. To expose more of your application, add effect handlers and their corresponding types to the inference context.", "url": "https://wpnews.pro/news/embedding-eyg-in-erlang", "canonical_source": "https://eyg.run/guides/embedding-eyg-in-erlang-programs", "published_at": "2026-10-21 21:28:25+00:00", "updated_at": "2026-10-21 21:42:19.677515+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["EYG", "Erlang", "my_counters", "eyg@parser", "eyg@interpreter@expression"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/embedding-eyg-in-erlang", "markdown": "https://wpnews.pro/news/embedding-eyg-in-erlang.md", "text": "https://wpnews.pro/news/embedding-eyg-in-erlang.txt", "jsonld": "https://wpnews.pro/news/embedding-eyg-in-erlang.jsonld"}}