erl_syntax_lib(3)

πŸ“¦
version 29.0.6 Β· 3 versions
, in ubuntu stonking

πŸ’– erl_syntax_lib β€” bestie, it's giving support-library energy

okay so this page is the manual for erl_syntax_lib, straight out of the Erlang Module Definition section. it's the support library that hangs out with the abstract Erlang syntax trees, and honestly? it's a lot. grab a snack. πŸ“


🌸 NAME

erl_syntax_lib β€” support library for abstract Erlang syntax trees. periodt. ✨

πŸ¦‹ DESCRIPTION

This module is literally just a big ol' pile of utility functions for working with the abstract data type defined over in the erl_syntax module. that's it, that's the vibe. πŸ’…

✨ DATA TYPES

okay so here's the cast of characters, no cap:

  • appFunName() β€” the name of an applied function:
appFunName() :: {atom(), arity()} | {atom(), {atom(), arity()}}.
  • field() β€” one lil' record field:
field() :: {atom(), {field_default(), field_type()}}.
  • field_default() β€” a field's default, or the atom none if there isn't one:
field_default() :: none | syntaxTree().
  • field_type() β€” a field's type, or none if there isn't one:
field_type() :: none | syntaxTree().
  • fields() β€” a whole list of those fields, slay:
fields() :: [field()].
  • functionN():
functionN() :: atom() | {atom(), arity()}.
  • functionName() β€” nesting goes brrr:
functionName() :: functionN() | {atom(), functionN()}.
  • info():
info() ::
          {atom(), [{atom(), syntaxTree()}]} | {atom(), atom()} |
atom().
  • info_pair() β€” it's a key and a term, a matched set, iconic:
info_pair() :: {key(), term()}.
  • key() β€” the only keys allowed, bestie:
key() ::
          attributes | errors | exports | functions | imports | module |
          records | warnings.
  • name():
name() :: shortname() | {atom(), shortname()}.
  • ordset(T):
ordset(T) :: ordsets:ordset(T).
  • set(T):
set(T) :: sets:set(T).
  • shortname():
shortname() :: atom() | {atom(), arity()}.
  • syntaxTree():
syntaxTree() :: erl_syntax:syntaxTree().

This one is an abstract syntax tree. if you want the details, go read the erl_syntax module, bestie. πŸ¦‹

  • typeName() β€” the way this one nests three different ways:
typeName() ::
          atom() | {module(), {atom(), arity()}} | {atom(), arity()}.

πŸ”₯ FUNCTIONS

main character section incoming. every single one, we're not skipping anybody. πŸ‘‘

analyze_application(syntaxTree()) -> appFunName() | arity().

Returns the name of a called function.

So here's the deal: remember F and A from that one math class you ignored? no? okay. the result is a representation of the name of the applied function F/A when Node represents a function application "F(X_1, ..., X_A)". but if the function isn't explicitly named β€” aka F is just some expression β€” you only get the arity A back. sorry babe. 🌸

lowkey important: the evaluation throws syntax_error if Node does not represent a well-formed application expression. don't be messy.

See also: analyze_function_name/1.

analyze_attribute(syntaxTree()) -> preprocessor | {atom(), term()}.

Analyzes an attribute node.

If Node represents a preprocessor directive, you get the atom preprocessor back, that's it. otherwise, if Node represents a module attribute like "-Name...", you get a tuple {Name, Info}, where Info depends on Name. here's the whole breakdown:

  • {module, Info} β€” where Info = analyze_module_attribute(Node).
  • {export, Info} β€” where Info = analyze_export_attribute(Node).
  • {import, Info} β€” where Info = analyze_import_attribute(Node).
  • {file, Info} β€” where Info = analyze_file_attribute(Node).
  • {record, Info} β€” where Info = analyze_record_attribute(Node).
  • {Name, Info} β€” where {Name, Info} = analyze_wild_attribute(Node).
the evaluation throws syntax_error if Node does not represent a well-formed module attribute. noted, bestie.

See also: analyze_export_attribute/1, analyze_file_attribute/1, analyze_import_attribute/1, analyze_module_attribute/1, analyze_record_attribute/1, analyze_wild_attribute/1.

analyze_export_attribute(syntaxTree()) -> [functionName()].

Returns the list of function names declared by an export attribute.

okay real talk: we do not guarantee that each name occurs at most once in that list. and the order of listing? not defined. it's giving chaos, and we're at peace with it. πŸ’…

the evaluation throws syntax_error if Node does not represent a well-formed export attribute.

See also: analyze_attribute/1.

analyze_file_attribute(syntaxTree()) -> {string(), integer()}.

Returns the file name and line number of a file attribute.

The result is the pair {File, Line} if Node represents "-file(File, Line).". cute, right? πŸŽ€

the evaluation throws syntax_error if Node does not represent a well-formed file attribute.

See also: analyze_attribute/1.

analyze_form(syntaxTree()) -> {atom(), term()} | atom().

Analyzes a "source code form" node.

If Node is a "form" type (peep erl_syntax:is_form/1 for the receipts), the returned value is a tuple {Type, Info} where Type is the node type and Info depends on Type, like so:

  • {attribute, Info} β€” where Info = analyze_attribute(Node).
  • {error_marker, Info} β€” where Info = erl_syntax:error_marker_info(Node).
  • {function, Info} β€” where Info = analyze_function(Node).
  • {warning_marker, Info} β€” where Info = erl_syntax:warning_marker_info(Node).

For other types of forms, only the node type is returned. that's it, just the type, no extra lore.

the evaluation throws syntax_error if Node is not well-formed.

See also: analyze_attribute/1, analyze_function/1, erl_syntax:error_marker_info/1, erl_syntax:is_form/1, erl_syntax:warning_marker_info/1.

analyze_forms(erl_syntax:forms()) -> [info_pair()].

Analyzes a sequence of "program forms". this one is the whole friend group, buckle up. πŸ’€

The given Forms may be a single syntax tree of type form_list, or a list of "program form" syntax trees. Either way works, we're flexible. The returned value is a list of pairs {Key, Info}, where each value of Key occurs at most once in the list. and if a particular key is just... absent? that means there's no well-defined value for that key. ghosted. πŸ‘»

Each entry in the resulting list contains the following corresponding info about the program forms:

  • {attributes, Attributes}
    • Attributes = [{atom(), term()}]
    • Attributes is a list of pairs representing the names and corresponding values of all the so-called "wild" attributes (things like "-compile(...)") that show up in Forms β€” go look at analyze_wild_attribute/1. we do not guarantee that each name occurs at most once in the list. the order of listing is not defined. again, chaos, we cope. ✨
  • {errors, Errors}
    • Errors = [term()]
    • Errors is the list of error descriptors of all error_marker nodes that occur in Forms. the order of listing is not defined.
  • {exports, Exports}
    • Exports = [FunctionName]
    • FunctionName = atom() | {atom(), integer()} | {ModuleName, FunctionName}
    • ModuleName = atom()
    • Exports is a list of representations of those function names that are listed by export declaration attributes in Forms (see analyze_export_attribute/1). we do not guarantee that each name occurs at most once in the list. the order of listing is not defined.
  • {functions, Functions}
    • Functions = [{atom(), integer()}]
    • Functions is a list of the names of the functions that are defined in Forms (see analyze_function/1). we do not guarantee that each name occurs at most once in the list. the order of listing is not defined.
  • {imports, Imports}
    • Imports = [{Module, Names}]
    • Module = atom()
    • Names = [FunctionName]
    • FunctionName = atom() | {atom(), integer()} | {ModuleName, FunctionName}
    • ModuleName = atom()
    • Imports is a list of pairs representing those module names and corresponding function names that are listed by import declaration attributes in Forms (see analyze_import_attribute/1), where each Module occurs at most once in Imports. we do not guarantee that each name occurs at most once in the lists of function names. the order of listing is not defined.
  • {module, ModuleName}
    • ModuleName = atom()
    • ModuleName is the name declared by a module attribute in Forms. if no module name is defined in Forms, the result will contain no entry for the module key at all. and if multiple module name declarations somehow happen? all but the first get ignored. first one wins, periodt. πŸ’…
  • {records, Records}
    • Records = [{atom(), Fields}]
    • Fields = [{atom(), {Default, Type}}]
    • Default = none | syntaxTree()
    • Type = none | syntaxTree()
    • Records is a list of pairs representing the names and corresponding field declarations of all record declaration attributes occurring in Forms. for a field declared with no default value, the corresponding value for Default is the atom none. same energy for Type: if a field is declared with no type, the corresponding value for Type is the atom none (see analyze_record_attribute/1). we do not guarantee that each record name occurs at most once in the list. the order of listing is not defined.
  • {warnings, Warnings}
    • Warnings = [term()]
    • Warnings is the list of error descriptors of all warning_marker nodes that occur in Forms. the order of listing is not defined.
the evaluation throws syntax_error if an ill-formed Erlang construct is encountered. be careful out there, bestie. 😭

See also: analyze_export_attribute/1, analyze_function/1, analyze_import_attribute/1, analyze_record_attribute/1, analyze_wild_attribute/1, erl_syntax:error_marker_info/1, erl_syntax:warning_marker_info/1.

analyze_function(syntaxTree()) -> {atom(), arity()}.

Returns the name and arity of a function definition.

The result is a pair {Name, A} if Node represents a function definition "Name(P_1, ..., P_A) -> ...". look at it go. ✨

the evaluation throws syntax_error if Node does not represent a well-formed function definition.

analyze_function_name(syntaxTree()) -> functionName().

Returns the function name represented by a syntax tree.

If Node represents a function name β€” like "foo/1" or "bloggs:fred/2" β€” you get a uniform representation of that name back. and the best part? different nestings of arity and module name qualifiers in the syntax tree do not affect the result. normalize, bestie. πŸ’…

the evaluation throws syntax_error if Node does not represent a well-formed function name.

analyze_implicit_fun(syntaxTree()) -> functionName().

Returns the name of an implicit fun expression "fun F".

The result is a representation of the function name F β€” see analyze_function_name/1 for how that works, iykyk. πŸ¦‹

the evaluation throws syntax_error if Node does not represent a well-formed implicit fun.

See also: analyze_function_name/1.

analyze_import_attribute(syntaxTree()) -> {atom(), [functionName()]} | atom().

Returns the module name and (if present) the list of function names declared by an import attribute.

So the value it hands back is either an atom Module, or a pair {Module, Names} when there's extra tea. And Names? That's the list of function names declared as imported from the module named by Module. πŸ’–

Two things we are not promising, bestie: that each name occurs at most once in Names, and literally anything at all about the order of listing. The order of listing is not defined. That's just how it is. πŸ’…

The evaluation throws syntax_error if Node does not represent a well-formed import attribute.

See also: analyze_attribute/1.


analyze_module_attribute(syntaxTree()) -> atom() | {atom(), [atom()]}.

Gives you the module name, plus any parameters it declares, from a module attribute. ✨

  • Plain module declaration, like -module(name)? You just get the module name back, simple as.
  • Parameterized module declaration? Now you get a tuple with the module name and a list of the parameter variable names. πŸŽ€

The evaluation throws syntax_error if Node does not represent a well-formed module attribute.

See also: analyze_attribute/1.


analyze_record_attribute(syntaxTree()) -> {atom(), fields()}.

Hands over the name and the list of fields of a record declaration attribute. 🌸

If Node represents -record(Name, {...})., the result is a pair {Name, Fields}, where Fields is a list of pairs {Label, {Default, Type}} β€” one for each field "Label", "Label = Default", "Label :: Type", or "Label = Default :: Type" in the declaration, listed in left-to-right order. 🫢

  • Field with no default-value declaration? Default is just the atom none.
  • Field with no type declaration? Type is just the atom none.

And no, we do not guarantee that each label occurs at most once in that list. πŸ™ˆ

The evaluation throws syntax_error if Node does not represent a well-formed record declaration attribute.

See also: analyze_attribute/1, analyze_record_field/1.


analyze_record_expr(syntaxTree()) -> {atom(), info()} | atom().

Returns the record name and the field name/names of a record expression. πŸŽ€

If Node has type record_expr, record_index_expr or record_access, you get a pair {Type, Info}. Otherwise you just get an atom Type. Type is the node type of Node, and Info depends on Type, like this:

  • record_expr β€” {atom(), [{atom(), Value}]}
  • record_access β€” {atom(), atom()}
  • record_index_expr β€” {atom(), atom()}

For a record_expr node, Info is the record name plus the list of descriptors for the involved fields, listed in the order they appear. A field descriptor is a pair {Label, Value} if Node represents "Label = Value". For a record_access node, Info is the record name and the field name. For a record_index_expr node, Info is the record name and the name field name. πŸ‘‘

The evaluation throws syntax_error if Node represents a record expression that is not well-formed.

See also: analyze_record_attribute/1, analyze_record_field/1.


analyze_record_field(syntaxTree()) -> field().

Returns the label, value-expression, and type of a record field specifier. πŸ’œ

The result is a pair {Label, {Default, Type}} if Node represents "Label", "Label = Default", "Label :: Type", or "Label = Default :: Type".

  • No value-expression on the field? Default is the atom none.
  • No type on the field? Type is the atom none. 🫢

The evaluation throws syntax_error if Node does not represent a well-formed record field specifier.

See also: analyze_record_attribute/1, analyze_record_expr/1.


analyze_type_application(syntaxTree()) -> typeName().

Returns the name of a used type. πŸ¦‹

If Node represents a local (user) type application "N(T_1, ..., T_A)", the result is a representation of the name of the used pre-defined or local type N/A. If Node represents a remote user type application "M:N(T_1, ..., T_A)", the result is a representation of the name of the used remote type M:N/A. ⭐

The evaluation throws syntax_error if Node does not represent a well-formed (user) type application expression.

See also: analyze_type_name/1.


analyze_type_name(syntaxTree()) -> typeName().

Returns the type name that a syntax tree represents. πŸ’«

If Node represents a type name β€” say "foo/1" or "bloggs:fred/2" β€” you get a uniform representation of that name back. So clean. πŸŽ€

The evaluation throws syntax_error if Node does not represent a well-formed type name.


analyze_wild_attribute(syntaxTree()) -> {atom(), term()}.

Returns the name and value of a "wild" attribute. πŸ”₯

The result is the pair {Name, Value} if Node represents -Name(Value).

Real talk: no checking happens on whether Name is a reserved attribute name like module or export. It's just assumed that the attribute is "wild". 😌

The evaluation throws syntax_error if Node does not represent a well-formed wild attribute.

See also: analyze_attribute/1.


annotate_bindings(syntaxTree()) -> syntaxTree().

Adds or updates annotations on the nodes in a syntax tree. ✨

Equivalent to annotate_bindings(Tree, Bindings), where the top-level environment Bindings is taken from the annotation {env, Bindings} on the root node of Tree. And if no such annotation should exist? An exception gets thrown, bestie. 😭

See also: annotate_bindings/2.


annotate_bindings(syntaxTree(), ordset(atom())) -> syntaxTree().

Also adds or updates annotations on nodes in a syntax tree β€” but this time you bring the Bindings.

Bindings is the set of bound variables in the environment of the top level node. The following annotations are affected: 🫢

  • {env, Vars} β€” the input environment of the subtree.
  • {bound, Vars} β€” the variables that are bound in the subtree.
  • {free, Vars} β€” the free variables in the subtree.

Bindings and Vars are ordered-set lists (peep module ordsets) of atoms representing variable names. πŸ“š

See also: //stdlib/ordsets, annotate_bindings/1.


fold(fun((syntaxTree(), term()) -> term()), term(), syntaxTree()) -> term().

Folds a function over all the nodes of a syntax tree. πŸ”₯

The result is

Function(X1, Function(X2, ... Function(Xn, Start) ... ))

where [X1, X2, ..., Xn] are the nodes of Tree in a post-order traversal. πŸŒ€

See also: fold_subtrees/3, foldl_listlist/3.


fold_subtrees(fun((syntaxTree(), term()) -> term()), term(), syntaxTree()) -> term().

Folds a function over the immediate subtrees of a syntax tree. 🌿

Basically fold/3, except it only touches the immediate subtrees of Tree, in left-to-right order β€” and nope, it does not include the root node of Tree. 🚫

See also: fold/3.


foldl_listlist(fun((term(), term()) -> term()), term(), [[term()]]) -> term().

Exactly like lists:foldl/3, but over a list of lists. That's it, that's the function. πŸ’…

See also: //stdlib/lists:foldl/3, fold/3.


function_name_expansions([name()]) -> [{shortname(), name()}].

Builds a mapping from corresponding short names to full function names. πŸ—ΊοΈ

Names are represented by nested tuples of atoms and integers (peep analyze_function_name/1). For each element Name in the given list you get a pair {ShortName, Name}, where the corresponding ShortName is the rightmost-innermost part of Name. So the list is basically a finite mapping from unqualified names to the corresponding qualified names. ✨

Careful though β€” the resulting list can contain more than one tuple {ShortName, Name} for the same ShortName, possibly with different values for Name, all depending on the given list. πŸ™ˆ

See also: analyze_function_name/1.


is_fail_expr(syntaxTree()) -> boolean().

Returns true if Tree represents an expression that never terminates normally. πŸ’€

Big caveat: the reverse does not apply. And right now, the detected cases are calls to exit/1, throw/1, erlang:error/1 and erlang:error/2. That's the whole list, periodt.

See also: //erts/erlang:error/1, //erts/erlang:error/2, //erts/erlang:exit/1, //erts/erlang:throw/1.


limit(syntaxTree(), integer()) -> syntaxTree().

Equivalent to limit(Tree, Depth, Text), using the text "..." as the default replacement. πŸŽ€

See also: limit/3, erl_syntax:text/1.


limit(syntaxTree(), integer(), syntaxTree()) -> syntaxTree().

Limits a syntax tree to a depth you pick. πŸͺ„

All non-leaf subtrees in Tree at the given Depth get replaced by Node. And if Depth is negative, the result is always Node β€” even if Tree has no subtrees at all. πŸ’€

When a group of subtrees is chilling at the specified depth (think the argument list of an application node, for example) and there are two or more subtrees in that group, they get collectively replaced by Node even if they're leaf nodes. Groups of subtrees above the specified depth get limited in size instead, as if each subsequent tree in the group were one level deeper than the previous. So if Tree represents a list of integers [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], then limit(Tree, 5) represents [1, 2, 3, 4, ...]. Obsessed honestly. 😭

The resulting syntax tree is typically only useful for pretty-printing or similar visual formatting β€” don't go reading it like it's real code, bestie.

See also: limit/2.


map(fun((syntaxTree()) -> syntaxTree()), syntaxTree()) -> syntaxTree().

Applies a function to each node of a syntax tree. πŸ”„

The result of each application replaces the corresponding original node, and the order of traversal is bottom-up. 🌱

See also: map_subtrees/2.


map_subtrees(fun((syntaxTree()) -> syntaxTree()), syntaxTree()) -> syntaxTree().

Applies a function to each immediate subtree of a syntax tree. πŸƒ

The result of each application replaces the corresponding original node. ✨

See also: map/2.


mapfold(fun((syntaxTree(), term()) -> {syntaxTree(), term()}), term(), syntaxTree()) -> {syntaxTree(), term()}.

Deets incoming β€” keep scrolling, bestie. 🫢

Combines map and fold in one single operation. πŸ’…

This is giving map/2 energy, except it also passes an extra value from each application of the Function along to the next one, while doing a post-order traversal of the tree exactly like fold/3 does. The value Start goes to the very first function application, and the final result is whatever the last application handed back. ✨

See also: fold/3, map/2.


mapfold_subtrees/3

mapfold_subtrees(fun((syntaxTree(), term()) ->
                          {syntaxTree(), term()}),
                 term(),
                 syntaxTree()) ->
                    {syntaxTree(), term()}.

Does a mapfold operation over the immediate subtrees of a syntax tree. πŸ¦‹

It's basically mapfold/3, but ONLY on the immediate subtrees of Tree, in left-to-right order β€” and it does not include the root node of Tree. no cap.

See also: mapfold/3.


mapfoldl_listlist/3

mapfoldl_listlist(fun((term(), term()) -> {term(), term()}),
                  term(),
                  [[term()]]) ->
                     {[[term()]], term()}.

Like lists:mapfoldl/3, but over a list of lists. 🫢

The list of lists in the result has the same structure as the given list of lists β€” literally untouched shape-wise.


new_variable_name/1

new_variable_name(set(atom())) -> atom().

Returns an atom which is not already in the set Used. ✨

This is equivalent to new_variable_name(Function, Used), where Function maps a given integer N to the atom whose name consists of "V" followed by the numeral for N.

See also: new_variable_name/2.


new_variable_name/2

new_variable_name(fun((integer()) -> atom()), set(atom())) ->
                       atom().

Returns a user-named atom which is not already in the set Used. πŸŽ€

The atom is generated by applying the given Function to a generated integer. Integers are generated using an algorithm which tries to keep the names randomly distributed within a reasonably small range relative to the number of elements in the set.

This function uses the module rand to generate new keys. The seed it uses can be initialized by calling rand:seed/1 or rand:seed/2 before this function is first called. πŸ‘‘

See also: //stdlib/rand, //stdlib/sets, new_variable_name/1.


new_variable_names/2

new_variable_names(integer(), set(atom())) -> [atom()].

Like new_variable_name/1, but generates a list of N new names. 🌈

See also: new_variable_name/1.


new_variable_names/3

new_variable_names(integer(),
                   fun((integer()) -> atom()),
                   set(atom())) ->
                      [atom()].

Like new_variable_name/2, but generates a list of N new names. πŸ’«

See also: new_variable_name/2.


strip_comments/1

strip_comments(syntaxTree()) -> syntaxTree().

Removes all comments from all nodes of a syntax tree. πŸ’€

All other attributes (such as position information) remain unchanged. Standalone comments in form lists are removed; any other standalone comments are changed into null-comments (no text, no indentation). 🧚


to_comment/2

to_comment(syntaxTree(), string()) -> syntaxTree().

Equivalent to to_comment(Tree, Prefix, F) for a default formatting function F.

The default F simply calls erl_prettypr:format/1. ✨

See also: to_comment/3, erl_prettypr:format/1.


to_comment/3

to_comment(syntaxTree(),
           string(),
           fun((syntaxTree()) -> string())) ->
              syntaxTree().

Transforms a syntax tree into an abstract comment. πŸŽ€

The lines of the comment contain the text for Node, as produced by the given Printer function. Each line of the comment is prefixed by the string Prefix (this does not include the initial "%" character of the comment line).

For example, the result of to_comment(erl_syntax:abstract([a,b,c])) represents

%% [a,b,c]

(see to_comment/1).

NOTE: The text returned by the formatting function will be split automatically into separate comment lines at each line break. No extra work is needed. πŸ’…

See also: to_comment/1, to_comment/2.


variables/1

variables(syntaxTree()) -> set(atom()).

Returns the names of variables occurring in a syntax tree. πŸ‘‘

The result is a set of variable names represented by atoms. Macro names are not included. πŸ™ˆ

See also: //stdlib/sets.


and that's a wrap bestie 🫢 this erl_syntax_lib(3) page came to you from Ericsson AB, shipping as part of syntax_tools version 4.1 β€” go forth and slay those syntax trees, ily πŸ’–

πŸ“¦ where this page comes from