Files
n8n-openai-adapter/lib/elixir/macros.ex
T

1001 lines
28 KiB
Elixir

defmodule Elixir::Macros do
# Defines a module given by name with the given contents.
#
# ## Examples
#
# defmodule Foo do
# def bar, do: :baz
# end
#
# Foo.bar #=> :baz
#
# ## Nesting
#
# Nesting a module inside the other does not affect its name:
#
# defmodule Foo do
# defmodule Bar do
# end
# end
#
# In the example above, two modules `Foo` and `Bar`. Notice that
# the second module is **not** called `Foo::Bar`. In general,
# nesting modules is discouraged in Elixir.
#
# ## Dynamic names
#
# Elixir module names can be dynamically generated. This is very
# useful for macros. For instance, one could write:
#
# defmodule binary_to_atom("Foo#{1}", :utf8) do
# # contents ...
# end
#
# Elixir will accept any module name as long as the expression
# returns an atom.
defmacro defmodule(name, do: contents)
# Defines a function with the given name and contents.
#
# ## Examples
#
# defmodule Foo do
# def bar, do: :baz
# end
#
# Foo.bar #=> :baz
#
# A function that expects arguments can be defined as follow:
#
# defmodule Foo do
# def sum(a, b) do
# a + b
# end
# end
#
# In the example above, we defined a function `sum` that receives
# two arguments and sum them.
#
# ## Function invocation
#
# Elixir follows the same rule as Erlang when it comes to
# function invocations. Calling a function is the same thing
# as "invoking at atom". That said, one could write:
#
# defmodule Foo do
# def bar do
# :sum.(1, 2)
# end
#
# def sum(a, b), do: a + b
# end
#
# In the example above, sum is invoked by invoking an atom
# passing arguments 1 and 2. Since this syntax is a bit verbose,
# Elixir also support the more conventional:
#
# sum(1, 2)
#
# Invoking a function in another module is equally easy:
#
# Foo.sum(1, 2) #=> 3
#
# ## Dynamic function definition
#
# In macros it may be convenient to dynamically generate a function.
# This can be done by explicitly passing the function name, arguments
# and guard:
#
# name = :some_function
#
# def name, [first_arg, second_arg], [is_list(first_arg)] do
# # ...
# end
#
defmacro def(name, do: contents)
# Defines a function that is private. Private functions
# can only be accessible from the same module it is defined.
#
# Check `def/2` for more information
#
# ## Examples
#
# defmodule Foo do
# def bar do
# sum(1, 2)
# end
#
# defp sum(a, b), do: a + b
# end
#
# In the example above, `sum` is private and accessing it
# through `Foo.sum` will raise an error.
defmacro defp(name, do: contents)
# Define a record given by name and values.
#
# ## Examples
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# This macro will then define a module named FileInfo
# which will contain getters and setters for each attribute
# and initialization methods. Therefore, one can do:
#
# file_info = FileInfo.new(atime: now())
# file_info.atime #=> Returns the value of atime
# file_info.atime(now()) #=> Updates the value of atime
#
# FileInfo is simply a module with functions specific for
# the record. Notice that the name of the module is sensitive
# to the current context. For instance:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# ... will define a module named FileInfo. However, if invoked
# inside a module, the name will be nested:
#
# defmodule Foo::Bar do
# defrecord FileInfo, atime: nil, mtime: nil
# end
#
# Foo::Bar::FileInfo.new # Nested
#
# ## Default based functions
#
# Depending on the default value, Elixir will define helpers to interact
# with the record. For example, ExUnit defines a record which keeps
# track of how many tests were executed and the failures that happened
# The record definition is similar to:
#
# defrecord Config, counter: 0, failures: []
#
# Since `counter` is an integer, Elixir automatically defines a helper
# named `increment_counter` that will increase the counter value:
#
# Config.new.increment_counter.counter #=> 1
#
# `increment_counter` also accepts a number of increment as argument:
#
# Config.new.increment_counter(10).counter #=> 10
#
# Besides, if the default is a list, Elixir will define three helpers:
#
# * `merge_field` - Receives an orddict and merge it into the current value;
# * `prepend_field` - Receives another list and prepend its values
# * `append_field` - Receives another list and append its values
#
# ## Record as tuples
#
# A record is nothing more than a tuple. That said, if you create a
# record an inspect it, this is what you should see on console:
#
# IO.puts FileInfo.new
# #=> { ::FileInfo, nil, nil }
#
# The first element of the tuple is always the record name.
defmacro defrecord(name, values, opts // []) do
Record.defrecord(name, values, opts)
end
# Defines the current module as a protocol and specifies the API
# that should be implemented.
#
# ## Examples
#
# In Elixir, only `false` and `nil` are considered falsy values.
# Everything else evaluates to true in `if` clauses. Depending
# on the application, it may be important to specify a `blank?`
# protocol that returns a boolean for other data types that should
# be considered `blank?`. For instance, an empty list or an empty
# binary could be considered blanks.
#
# We could implement this protocol as follow:
#
# defmodule Presence do
# defprotocol Blank, [blank?(data)]
#
# # The opposite of blank?
# def present?(data) do
# !blank?(data)
# end
# end
#
# Now that the protocol is defined, we can implement it. We need
# to implement the protocol for each Elixir type. For example:
#
# # Numbers are never blank
# defimpl Presence::Blank, for: Number do
# def blank?(number), do: false
# end
#
# # Just empty list is blank
# defimpl Presence::Blank, for: List do
# def blank?([]), do: true
# def blank?(_), do: false
# end
#
# # Just the atoms false and nil are blank
# defimpl Presence::Blank, for: Atom do
# def blank?(false), do: true
# def blank?(nil), do: true
# def blank?(_), do: false
# end
#
# And we would have to define the implementation for all types.
# The types available are:
#
# * Tuple
# * Atom
# * List
# * BitString
# * Number
# * Function
# * PID
# * Port
# * Reference
#
# ## Selecting protocols
#
# Implement the protocol for all 9 types above can be cumbersome.
# Even more if you consider that Number, Function, PID, Port and
# Reference are never going to be blank. For this reason, Elixir
# allows you to point out that you are going to implement the protocols
# just for some types, as follows:
#
# defprotocol Blank, [blank?(data)], only: [Atom, Tuple, List, BitString]
#
# And for all other types, Elixir will now dispatch to Any. That said,
# the default behavior could be implemented as:
#
# defimpl Presence::Blank, for: Any do
# def blank?(_), do: false
# end
#
# Now, all data types that we not specified in only will be automatically
# considered non blank.
#
# ## Protocols + Records
#
# The real benefit of protocols comes when mixed with records. For instance,
# one may implement a custom dictionary as a Red-Black tree and this
# dictionary should also be considered as blank in case it has no items.
# That said, he just needs to implement the protocol for this dictionary:
#
# defimpl Presence::Blank, for: RedBlack::Dict do
# def blank?(dict), do: RedBlack.empty?(dict)
# end
#
# In the example above, we have implemented `blank?` for the custom
# dictionary that simply delegates to `RedBlack.empty?`.
#
# Finally, notice that since records are simply tuples, the default
# implementation for records can be given in the tuple implementation.
defmacro defprotocol(name, args, opts // []) do
Protocol.defprotocol(name, args, opts)
end
# Defines an implementation for the given protocol. See
# `defprotocol/2` for examples.
defmacro defimpl(name, do: block, for: for) do
Protocol.defimpl(name, do: block, for: for)
end
# `import` allows one to easily access functions from others modules
# without using the qualified name.
#
# ## Examples
#
# If you want to use the `values` function from `Orddict` several times
# in your module and you don't want to always type `Orddict.values`,
# you can simply import it:
#
# defmodule Math do
# import Orddict, only: [values: 1]
#
# def some_function do
# # call values(orddict)
# end
# end
#
# In this case, we are importing only the function `values` (with arity 1)
# from `Orddict`. Although `only` is optional, its usage is recommended.
# `except` could also be given as an option. If no option is given, all
# functions are imported.
#
# This mechanism cannot be used to import macros. Only functions.
# Check `require/2` for more information on how to import macros.
defmacro import(module, opts // [])
# `use` is a simple mechanism for extending the current module with the
# given module.
#
# == Examples
#
# defmodule AssertionTest do
# use ExUnit::Case
#
# def test_always_pass do
# true = true
# end
# end
#
# By calling `use`, a hook called `__using__` will be invoked in
# `ExUnit::Case` which will then do the proper setup. In other words,
# `use` is simply a translation to:
#
# defmodule AssertionTest do
# require ExUnit::Case
# ExUnit::Case.__using__(::AssertionTest)
#
# def test_always_pass do
# true = true
# end
# end
#
defmacro use(module, args // [])
# `refer` allows you to to setup references aliases for a given module.
#
# ## Example
#
# defmodule Math do
# refer MyOrddict, as: Orddict
# end
#
# In the example above, we have set up `MyOrdict` to be referenced
# as `Orddict`. So now, any reference to `Orddict` will be
# automatically replaced by `MyOrddict`.
#
# In case one wants to access the original `Orddict`, it can be done
# by prefixing the module name with `::`:
#
# Orddict.values #=> uses ::MyOrddict.values
# ::Orddict.values #=> uses ::Orddict.values
#
# Note that `refer` is **lexical**. This means that the mapping set
# with `refer` will be available from the moment `refer` is called.
defmacro refer(old, as: new)
# `require` enables macros usage from a given module.
#
# ## Macros example
#
# Let's suppose you created your own `if` implementation in the module
# `MyMacros`. If you want to invoke it, you need to first explicitly
# require the `MyMacros`:
#
# defmodule Math do
# require MyMacros
# MyMacros.if do_something, it_works
# end
#
# An attempt to call a macro that was not loaded will raise an error.
#
# ## Lexical scope
#
# It is important to note that `require` is **lexical**. This means you
# can require specific macros inside specific functions:
#
# defmodule Math do
# def some_function do
# # 1) Disable `if/2` from Elixir::Macros
# require Elixir::Macros, except: [if: 2]
#
# # 2) Require the new `if` macro from MyMacros
# require MyMacros, import: true
#
# # 3) Use the new macro
# if do_something, it_works
# end
# end
#
# In the example above, we required and imported macros from `MyMacros`,
# replacing the original `if/2` implementation by our own during that
# specific function. All other functions in that module will still
# be able to use the original one.
#
# `require` also accepts `only` and `except` as options to select
# which macros to import. Consecutive calls to `require` passing
# the same models override previous definitions.
#
# defmodule MyIo
# # Import bit-or and bit-and from Bitwise
# require Bitwise, only: [bor: 2, band: 2]
# def some_func(x, y, z), do: x bor y band z
#
# # Import all, except bxor, overriding previous
# require Bitwise, except: [bxor: 2]
# end
#
# ## Reference shortcut
#
# Note that, if `as:` is given as option to `require`, it will
# automatically invoke `refer` creating the desired reference.
#
defmacro require(module, opts // [])
# Returns the current module name as an atom or nil otherwise.
defmacro __MODULE__
# Returns the current file name as a char list.
defmacro __FILE__
# Returns the current line number as an integer.
defmacro __LINE__
# Allows you to get the representation of any expression.
#
# ## Examples
#
# quote { sum(1, 2, 3) }
# #=> { :sum, 0, [1, 2, 3] }
#
# ## Homoiconicity
#
# Elixir is an homoiconic language. Any Elixir program can be
# represented using its own data structures. The building block
# of Elixir homoiconicity is a tuple with three elements, for example:
#
# { :sum, 1, [1, 2, 3] }
#
# The tuple above represents a function call to sum passing 1, 2 and
# 3 as arguments. The tuple elements are:
#
# * The first element of the tuple is always an atom or
# another tuple in the same representation;
# * The second element of the tuple is always an integer
# representing the line number;
# * The third element of the tuple are the arguments for the
# function call. The third argument may also be false, meaning
# that it may be a variable.
#
# ## Macro literals
#
# Besides the tuple described above, Elixir has a few literals that
# when quoted return themselves. They are:
#
# :sum #=> Atoms
# 1 #=> Integers
# 2.0 #=> Floats
# [1,2] #=> Lists
# "binaries" #=> Binaries
# {key, value} #=> Key-value pairs (i.e. a tuple with two elements)
#
defmacro quote(do: contents)
# Unquotes the given expression from inside a macro.
#
# ## Examples
#
# Imagine the situation you have a variable `name` and
# you want to inject it inside some quote. The first attempt
# would be:
#
# value = 13
# quote { sum(1, value, 3) }
#
# Which would then return:
#
# { :sum, 0, [1, { :value, 0, false }, 3] }
#
# Which is not the expected result. For this, we use unquote:
#
# value = 13
# quote { sum(1, unquote(value), 3) }
# #=> { :sum, 0, [1, 13, 3] }
#
defmacro unquote(expr)
# Unquotes the given list expanding its arguments. Similar
# to unquote.
#
# ## Examples
#
# values = [2,3,4]
# quote { sum(1, unquote_splice(values), 5) }
# #=> { :sum, 0, [1, 2, 3, 4, 5] }
#
defmacro unquote_splice(expr)
# Returns an anonymous function based on the given arguments.
#
# ## Examples
#
# sum = fn(x, y) { x + y }
# sum.(1, 2) #=> 3
#
# Notice that a function needs to be invoked using the dot between
# th function and the arguments.
#
# A function could also be defined using `do/end` syntax, although
# this is not recommended in order to avoid ambiguity. For example,
# consider this case:
#
# Enum.map [1,2,3], fn(x){ x * 2 }
#
# The example works fine, but if we replace it by `do/end`, it will fail:
#
# Enum.map [1,2,3], fn(x) do
# x * 2
# end
#
# The reason it fails is because do/end always bind to the farthest
# function call. It is easy to see the problem if we add parentheis
# to the outer call. For example, the example using curly brackets
# would translate to:
#
# Enum.map([1,2,3], fn(x){ x * 2 })
#
# Which is the expected result, however using `do/end` blocks:
#
# Enum.map([1,2,3], fn(x)) do
# x * 2
# end
#
# Which is not what we expect.
#
# ## Function with multiple clauses
#
# One may define a function which expects different clauses as long
# as all clauses expects the same number of arguments:
#
# fun = fn do
# match: x, y when y < 0
# x - y
# match: x, y
# x + y
# end
#
# fun.(10, -10) #=> 20
# fun.(10, 10) #=> 20
#
defmacro fn(args)
# Handle annonymous recursive loops.
#
# ## Examples
#
# list = [1,2,3]
#
# loop list, [] do
# match: [h|t], acc
# recur t, [h*2|acc]
# match: [], acc
# acc
# end
# #=> [6,4,2]
#
# Notice that all match clauses expects the same ammount
# of arguments. Guards can also be given.
#
# Recursion happens by calling recur with the same number
# of arguments of each match clause. `recur` does not guarantee
# that it will be tail recursive.
defmacro loop(args)
# Inspect the given arguments according to the String::Inspect protocol.
#
# ## Examples
#
# inspect(:foo)
# #=> ":foo"
#
defmacro inspect(arg) do
quote { ::String::Inspect.inspect(unquote(arg)) }
end
# Convert the argument to a string according to the String::Inspect protocol.
# This is the function invoked when there is string interpolation.
#
# ## Examples
#
# to_binary(:foo)
# #=> "foo"
#
defmacro to_binary(arg) do
quote { ::String::Inspect.to_binary(unquote(arg)) }
end
# Convert the argument to a list according to the List::Inspect protocol.
#
# ## Examples
#
# to_list(:foo)
# #=> 'foo'
#
defmacro to_list(arg) do
quote { ::List::Inspect.to_list(unquote(arg)) }
end
# Define elem to get Tuple element according to Elixir conventions.
# We need to implement it as a macro to it can be used in guards.
#
# ## Example
#
# tuple = { :foo, :bar, 3 }
# elem(tuple, 1) #=> :foo
#
defmacro elem(tuple, index) do
quote { element(unquote(index), unquote(tuple)) }
end
# Define setelem to set Tuple element according to Elixir conventions.
# We need to implement it as a macro to it can be used in guards.
#
# ## Example
#
# tuple = { :foo, :bar, 3 }
# setelem(tuple, 1, :baz) #=> { :baz, :bar, 3 }
#
defmacro setelem(tuple, index, value) do
quote { setelement(unquote(index), unquote(tuple), unquote(value)) }
end
# Provides an integer division macro according to Erlang semantics.
# Raises an error if one of the arguments is not an integer.
# Can be used in guard tests.
#
# ## Examples
#
# 5 div 2 #=> 2
#
defmacro div(left, right), do:
quote { erlang_op :div, unquote(left), unquote(right) }
# Provides an integer remainder macro according to Erlang semantics.
# Raises an error if one of the arguments is not an integer.
# Can be used in guard tests.
#
# ## Examples
#
# 5 rem 2 #=> 1
#
defmacro rem(left, right), do:
quote { erlang_op :rem, unquote(left), unquote(right) }
# Matches the given condition against the match clauses.
#
# ## Examples
#
# case thing do
# match: { :selector, i, value } when is_integer(i)
# value
# match: value
# value
# end
#
# In the example above, we compare `thing` with each given
# match clause and execute the first one that matches. If no
# clause matches, an error is raised.
#
# Since Elixir variables can be assigned more than once, variables
# in a match clause will always be assigned instead of matching with
# its previous values. For example:
#
# i = 1
# case 10 do
# match: i
# i * 2
# end
#
# The example above will return 20, because `i` is assgined to 10
# and then multiplied by 2. If you desire to match the value of `i`
# against the given condition, you need to use the `^` operator:
#
# i = 1
# case 10 do
# match: ^i
# i * 2
# end
#
# The example above will actually fail because 10 does not match 1.
#
# Finally, `case` accepts an `else:` branch as a fallback if none
# of the clauses match:
#
# case thing do
# match: { :selector, i, value } when is_integer(i)
# value
# else:
# thing
# end
#
defmacro case(condition, blocks)
# Execute the given expressions and catch any error, exit
# or throw that may have happened.
#
# ## Examples
#
# try do
# do_something_that_may_fail(some_arg)
# catch: { :error, :badarg, _ }
# IO.puts "Invalid argument given"
# catch: { :throw, value, _ }
# IO.puts "caught #{value}"
# after:
# IO.puts "This is printed regardless if it failed or succeed"
# end
#
# Each `catch` clause must be followed by a tuple with three elements.
# The first one is the kind of exception: `:error`, `:throw` or `:exit`.
# The second one is the value given to error, throw or exit and the third
# one is the stacktrace (which one usually wants to ignore).
#
# Note that calls inside `try` are not tail recursive.
#
# ## Variable visibility
#
# Since an expression inside `try` may not have been properly evaluted,
# any variable created inside `try` cannot be accessed externaly.
# For instance:
#
# try do
# x = 1
# do_something_that_may_fail(same_arg)
# :ok
# catch: _
# :failed
# end
#
# x #=> Cannot access `x`
#
# In the example above, `x` cannot be accessed since it was defined
# inside the `try` clause.
defmacro try(args)
# The current process will hang until it receives a message
# from other processes that matches the given clauses.
#
# ## Examples
#
# receive
# match: { :selector, i, value } when is_integer(i)
# value
# match: value when is_atom(value)
# value
# else:
# IO.puts :standard_error, "Unexpected message received"
# end
#
# The match clauses above follows the same rules as `case/2`.
#
# An optional after clause can be given in case the message was not
# received after the specified period of time:
#
# receive
# match: { :selector, i, value } when is_integer(i)
# value
# match: value when is_atom(value)
# value
# else:
# IO.puts :standard_error, "Unexpected message received"
# after: 5000
# IO.puts :standard_error, "No message in 5 seconds"
# end
#
# The `after` clause can be specified even if there are no match clauses.
# There are two special cases for the timout value given to after:
#
# * `:infinity` - The process should wait indefinitely for a matching
# message, this is the same as not using a timeout.
#
# * 0 - if there is no matching message in the mailbox, the timeout
# will occur immediately.
defmacro receive(args)
# Provides an `if` macro. The macro expects the first argument to
# be a condition and the rest are key-value arguments.
#
# ## One-liner examples
#
# if(foo, do: bar)
#
# In the example above, bar will be returned if foo evalutes to
# true (i.e. it is not false nor nil). Otherwise, nil will be returned.
#
# An else option can be given to specify the opposite:
#
# if(foo, do: bar, else: bar)
#
# ## Key-value blocks examples
#
# When several expressions must be passed to if, the most appropriate
# form is thorugh key-value blocks. The first example above would then
# be translated to:
#
# if foo do
# bar
# end
#
# Notice that do/end becomes delimiters. The value given between
# do/end becomes the expression given to as `do:`. The second example
# would then translate do:
#
# if foo do
# bar
# else:
# baz
# end
#
# Notice that extra keys follows the regular `else:` form. You can also
# add extra `elsif:` clauses:
#
#
# if foo do
# bar
# elsif: some_condition
# bar + baz
# else:
# baz
# end
#
defmacro if(condition, [{:do,do_clause}|tail]) do
# Transform the condition and the expressions in the
# do_clause to a key-value block. Get the other values
# from the tail orddict.
if_clause = { :kv_block, 0, [ { [condition], do_clause } ] }
else_clause = Orddict.fetch(tail, :else, nil)
# Merge if and elsif clauses, as they will all become match clauses.
merged =
case Orddict.fetch(tail, :elsif, nil) do
match: nil
[match: if_clause]
match: elsif_clause
Erlang.elixir_kv_block.merge([match: if_clause], [match: elsif_clause])
end
# Decouple all if and elsif clauses into an array of tuples.
# Those tuples are made of three elements, the key-block key,
# the given condition and the block expressions
all = Erlang.elixir_kv_block.decouple(merged)
build_if_clauses(List.reverse(all), else_clause)
end
# Provide a unless macro that executes the expression
# unless a value evalutes to true. Check `if` for examples
# and documentation.
defmacro unless(clause, options) do
quote { if(!unquote(clause), unquote(options)) }
end
# Provide a short-circuit operator that executes the second
# expression only if the first one evalutes to true (i.e. it is
# not nil nor false). Returns the first expression otherwise.
#
# ## Examples
#
# true && true #=> true
# nil && true #=> nil
# true && 1 #=> 1
# false && error(:bad) #=> false
#
# Notice that, differently from Erlang `and` and `andalso` operators,
# this operator accepts any expression as arguments, not only booleans.
# Unfortunately cannot be used in macros.
defmacro :&&.(left, right) do
quote do
case unquote(left) do
match: false
false
match: nil
nil
match: _
unquote(right)
end
end
end
# Provide a short-circuit operator that executes the second
# expression only if the first one does not evalute to true (i.e. it
# is not nil nor false). Returns the first expression otherwise.
#
# ## Examples
#
# false || false #=> false
# nil || true #=> true
# false || 1 #=> 1
# true || error(:bad) #=> true
#
# Notice that, differently from Erlang `or` and `orelse` operators,
# this operator accepts any expression as arguments, not only booleans.
# Unfortunately cannot be used in macros.
defmacro :||.(left, right) do
quote {
case !(__EX_oror_var = unquote(left)) do
match: false
__EX_oror_var
else:
unquote(right)
end
}
end
# Optimize !! to avoid generating case twice.
# :nodoc:
defmacro :!.({:!, _, [expr]}) do
quote {
case unquote(expr) do
match: false
false
match: nil
false
else:
true
end
}
end
# Implements the unary operator ! as a macro. It receives any
# argument and returns true if it is false or nil. Returns false
# otherwise.
#
# ## Examples
#
# !1 #=> false
# ![1,2,3] #=> false
# !false #=> true
# !nil #=> true
#
defmacro :!.(expr) do
quote do
case unquote(expr) do
match: false
true
match: nil
true
else:
false
end
end
end
## Private functions
# Build if clauses by nesting them recursively.
# For instance, the following clause:
#
# if foo do
# 1
# elsif: bar
# 2
# else:
# 3
# end
#
# Becomes:
#
# case !foo do
# match: false
# 1
# match: true
# case !bar do
# match: false
# 2
# match: true
# 3
# end
# end
#
defp build_if_clauses([{ :match, [condition], clause }|t], acc) do
new_acc = quote {
case !unquote(condition) do
match: false
unquote(clause)
else:
unquote(acc)
end
}
build_if_clauses(t, new_acc)
end
defp build_if_clauses([{ :match, [], _clause }|_], _) do
error { :badarg, "No conditions given to elsif clause" }
end
defp build_if_clauses([], acc), do: acc
end