epp(3)

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

πŸ’… epp(3) β€” the Erlang code preprocessor πŸ’…

bestie we are SO back, and today's episode is all about epp, the Erlang module definition you've been lowkey avoiding. sit down, grab a strawberry, this one's long but I got you πŸ“


πŸŽ€ NAME πŸŽ€

epp β€” An Erlang code preprocessor.


✨ DESCRIPTION ✨

okay so, the Erlang code preprocessor has a bunch of functions that the compile module uses to preprocess macros and include files before the parsing even starts. like, it does the whole vibe-prep before the parser walks in πŸ’…

ngl the encoding situation is real and you need to know it: the Erlang source file encoding gets picked by a comment in one of the first two lines of the source file. the first string matching the regular expression

coding\s*[:=]\s*([-a-zA-Z0-9])+

is the one that selects the encoding. no cap. and if that matching string isn't a valid encoding, it just gets ignored, no drama. the valid encodings are Latin-1 and UTF-8, and the case of the characters can be chosen freely, so go off, capitalise whatever you want πŸ‘‘

Examples, bestie (these are the whole vibe):

%% coding: utf-8

%% For this file we have chosen encoding = Latin-1

%% -*- coding: latin-1 -*-

😭 Error Information 😭

ErrorInfo is the standard ErrorInfo structure that gets returned from all I/O modules. it's giving

{ErrorLine, Module, ErrorDescriptor}

and if you want a string describing the error you get it with this call, periodt:

Module:format_error(ErrorDescriptor)

πŸ¦‹ See Also πŸ¦‹

erl_parse


🌈 DATA TYPES 🌈

  • epp_handle() :: pid(). β€” the handle to the epp server. she's just a pid, and that's iconic.
  • macros() :: [atom() | {atom(), term()} | {atom(), term(), redefine}]. β€” literally the macro vibes, exactly as written.
  • source_encoding() :: latin1 | utf8. β€” pick a lane: latin1 or utf8.
  • warning_info() :: {erl_anno:location(), module(), term()}. β€” the warning situation, in tuple form.

πŸ”₯ FUNCTIONS πŸ”₯

close(Epp) -> ok when Epp :: epp_handle().

Closes the preprocessing of a file. congrats bestie, that file is done πŸ’…

default_encoding() -> source_encoding().

Returns the default encoding of Erlang source files. that's the house default, no need to guess πŸŽ€

encoding_to_string(Encoding) -> string() when Encoding :: source_encoding().

encoding_to_string(Encoding) -> string()
                            when Encoding :: source_encoding().

Returns a string representation of an encoding. and here's the slay part: that string is recognized by read_encoding/1,2, read_encoding_from_binary/1,2, and set_encoding/1,2 as a valid encoding, so it round-trips perfectly ✨

format_error(ErrorDescriptor) -> io_lib:chars() when ErrorDescriptor :: term().

format_error(ErrorDescriptor) -> io_lib:chars()
                      when ErrorDescriptor :: term().

Takes an ErrorDescriptor and returns a string that describes the error or warning. this one is usually called implicitly when processing an ErrorInfo structure (see section Error Information above πŸ‘†).

open(Options) -> {ok, Epp} | {ok, Epp, Extra} | {error, ErrorDescriptor}

open(Options) ->
              {ok, Epp} | {ok, Epp, Extra} | {error, ErrorDescriptor}
              when
                  Options ::
                      [{default_encoding,
                        DefEncoding :: source_encoding()} |
                       {includes,
                        IncludePath :: [DirectoryName :: file:name()]} |
                       {source_name, SourceName :: file:name()} |
                       {deterministic, Enabled :: boolean()} |
                       {macros, PredefMacros :: macros()} |
                       {name, FileName :: file:name()} |
                       {location, StartLocation :: erl_anno:location()} |
                       {fd, FileDescriptor :: file:io_device()} |
                       extra |
                       {compiler_internal, [term()]}],
                  Epp :: epp_handle(),
                  Extra :: [{encoding, source_encoding() | none}],
                  ErrorDescriptor :: term().

Opens a file for preprocessing. it's giving opening night 🎬

so if you want to change the file name of the implicit -file() attributes that get inserted during preprocessing, you can do that with {source_name, SourceName}. if you leave it unset it defaults to the name of the opened file, simple as that.

setting {deterministic, Enabled} will additionally reduce the file name of those implicit -file() attributes to only the basename of the path. lowkey handy.

if extra is specified in Options, the return value is {ok, Epp, Extra} instead of {ok, Epp}. a little treat πŸ’–

the location option is forwarded to the Erlang token scanner, see erl_scan:tokens/3,4.

the {compiler_internal,term()} option is forwarded to the Erlang token scanner, see {compiler_internal,term()}.

open(FileName, IncludePath) -> {ok, Epp} | {error, ErrorDescriptor}

open(FileName, IncludePath) ->
              {ok, Epp} | {error, ErrorDescriptor}
              when
                  FileName :: file:name(),
                  IncludePath :: [DirectoryName :: file:name()],
                  Epp :: epp_handle(),
                  ErrorDescriptor :: term().

Equivalent to epp:open([{name, FileName}, {includes, IncludePath}]). that's it, that's the whole thing 🫢

open(FileName, IncludePath, PredefMacros) -> {ok, Epp} | {error, ErrorDescriptor}

open(FileName, IncludePath, PredefMacros) ->
              {ok, Epp} | {error, ErrorDescriptor}
              when
                  FileName :: file:name(),
                  IncludePath :: [DirectoryName :: file:name()],
                  PredefMacros :: macros(),
                  Epp :: epp_handle(),
                  ErrorDescriptor :: term().

Equivalent to epp:open([{name, FileName}, {includes, IncludePath}, {macros, PredefMacros}]). macrorrific πŸŽ€

parse_erl_form(Epp) -> {ok, AbsForm} | {error, ErrorInfo} | {warning, WarningInfo} | {eof, Location}

parse_erl_form(Epp) ->
                        {ok, AbsForm} |
                        {error, ErrorInfo} |
                        {warning, WarningInfo} |
                        {eof, Location}
                        when
                            Epp :: epp_handle(),
                            AbsForm :: erl_parse:abstract_form(),
                            Location :: erl_anno:location(),
                            ErrorInfo ::
                                erl_scan:error_info() |
                                erl_parse:error_info(),
                            WarningInfo :: warning_info().

Returns the next Erlang form from the opened Erlang source file. the tuple {eof, Location} gets returned at the end of the file, RIP. and heads up: the first form corresponds to an implicit attribute -file(File,1)., where File is the file name.

parse_file(FileName, Options) -> {ok, [Form]} | {ok, [Form], Extra} | {error, OpenError}

parse_file(FileName, Options) ->
                    {ok, [Form]} |
                    {ok, [Form], Extra} |
                    {error, OpenError}
                    when
                        FileName :: file:name(),
                        Options ::
                            [{includes,
                              IncludePath ::
                                  [DirectoryName :: file:name()]} |
                             {source_name, SourceName :: file:name()} |
                             {macros, PredefMacros :: macros()} |
                             {default_encoding,
                              DefEncoding :: source_encoding()} |
                             {location,
                              StartLocation :: erl_anno:location()} |
                             {reserved_word_fun,
                              Fun :: fun((atom()) -> boolean())} |
                             {features, [Feature :: atom()]} |
                             {deterministic, boolean()} |
                             extra |
                             {compiler_internal, [term()]}],
                        Form ::
                            erl_parse:abstract_form() |
                            {error, ErrorInfo} |
                            {eof, Location},
                        Location :: erl_anno:location(),
                        ErrorInfo ::
                            erl_scan:error_info() |
                            erl_parse:error_info(),
                        Extra :: [{encoding, source_encoding() | none}],
                        OpenError ::
                            file:posix() | badarg | system_limit.

Preprocesses and parses an Erlang source file, main character energy. ngl notice that the tuple {eof, Location} returned at the end of the file is included as a "form" β€” yes, she counts as a form, don't argue πŸ’€

if you want to change the file name of the implicit -file() attributes inserted during preprocessing, you do that with {source_name, SourceName}. if unset it defaults to the name of the opened file, same as before.

if extra is specified in Options, the return value is {ok, [Form], Extra} instead of {ok, [Form]} ✨

the location option is forwarded to the Erlang token scanner, see erl_scan:tokens/3,4.

the {compiler_internal,term()} option is forwarded to the Erlang token scanner, see {compiler_internal,term()}.

parse_file(FileName, IncludePath, PredefMacros) -> {ok, [Form]} | {error, OpenError}

parse_file(FileName, IncludePath, PredefMacros) ->
                    {ok, [Form]} | {error, OpenError}
                    when
                        FileName :: file:name(),
                        IncludePath :: [DirectoryName :: file:name()],
                        Form ::
                            erl_parse:abstract_form() |
                            {error, ErrorInfo} |
                            {eof, Location},
                        PredefMacros :: macros(),
                        Location :: erl_anno:location(),
                        ErrorInfo ::
                            erl_scan:error_info() |
                            erl_parse:error_info(),
                        OpenError ::
                            file:posix() | badarg | system_limit.

Equivalent to epp:parse_file(FileName, [{includes, IncludePath}, {macros, PredefMacros}]). chef's kiss πŸ‘‘

read_encoding(FileName, Options) -> source_encoding() | none

read_encoding(FileName, Options) -> source_encoding() | none
                       when
                           FileName :: file:name(),
                           Options :: [Option],
                           Option :: {in_comment_only, boolean()}.

Read the encoding from a file. Returns the read encoding, or none if no valid encoding is found πŸ™ˆ

Option in_comment_only is true by default, which is correct for Erlang source files. if you set it to false, the encoding string does not necessarily have to occur in a comment. spicy.

read_encoding_from_binary(Binary, Options) -> source_encoding() | none

read_encoding_from_binary(Binary, Options) ->
                                   source_encoding() | none
                                   when
                                       Binary :: binary(),
                                       Options :: [Option],
                                       Option ::
                                           {in_comment_only, boolean()}.

Read the encoding from a binary. Returns the read encoding, or none if no valid encoding is found. same energy, different input 🧚

Option in_comment_only is true by default, which is correct for Erlang source files. if set to false, the encoding string does not necessarily have to occur in a comment.


scan_erl_form(Epp)

scan_erl_form(Epp) ->
                       {ok, Tokens} |
                       {error, ErrorInfo} |
                       {warning, WarningInfo} |
                       {eof, Line}
                       when
                           Epp :: epp_handle(),
                           Tokens :: erl_scan:tokens(),
                           Line :: erl_anno:line(),
                           ErrorInfo ::
                               erl_scan:error_info() |
                               erl_parse:error_info(),
                           WarningInfo :: warning_info().

okay bestie, this one is so straightforward and I love that for us πŸ’– β€” you hand it an Epp handle, and it hands you back the raw tokens of the next Erlang form, straight out of the opened Erlang source file. That's the tea. πŸ¦‹

Here's what you might get back, and yes every single one of these is real:

  • {ok, Tokens} β€” slay, here are your tokens ✨
  • {error, ErrorInfo} β€” it's giving chaos 😭
  • {warning, WarningInfo} β€” not an emergency, just a heads up πŸŽ€
  • {eof, Line} β€” you hit the end of the file, that's a wrap πŸ‘‘
lowkey important: the first form you get back corresponds to an implicit attribute -file(File,1)., where File is the file name. Don't be confused when it shows up, it's supposed to be there. πŸ’…

scan_file(FileName, Options)

scan_file(FileName, Options) ->
                    {ok, [Form], Extra} | {error, OpenError}
                    when
                        FileName :: file:name(),
                        Options ::
                            [{includes,
                              IncludePath ::
                                  [DirectoryName :: file:name()]} |
                             {source_name, SourceName :: file:name()} |
                             {macros, PredefMacros :: macros()} |
                             {default_encoding,
                              DefEncoding :: source_encoding()}],
                        Form ::
                            erl_scan:tokens() |
                            {error, ErrorInfo} |
                            {eof, Loc},
                        Loc :: erl_anno:location(),
                        ErrorInfo :: erl_scan:error_info(),
                        Extra :: [{encoding, source_encoding() | none}],
                        OpenError :: file:posix() | badarg |
 system_limit.

this is the big one, the main character of the whole page honestly πŸ‘‘ β€” it preprocesses an Erlang source file and gives you back a list of the lists of raw tokens for each form. Every form, all of them, in one glorious sweep. 🌈

Two little things that trip people up and we are NOT going to be those people:

  • The tuple {eof, Line} that shows up at the end of the file is included in the list as a "form". Yes, really. It's part of the list. It's a form now, it lives there, we respect it. πŸ™ˆ
  • Any failure to scan a form is also in the list, as a tuple {error, ErrorInfo}. So the errors aren't thrown away, they're packed in with everything else, like a little mystery box of sadness. πŸ’€

And of course, you get {error, OpenError} if it can't even open the thing, where OpenError is a file:posix(), badarg, or system_limit. 🫢


set_encoding(File)

set_encoding(File) -> source_encoding() | none
                      when File :: io:device().

reads the encoding from an I/O device and then sets the encoding of that device to match 🧚 β€” very "we vibe, therefore we align" energy, and honestly? obsessed. ✨

The position of the I/O device that File points at is not affected, so nothing gets nudge-wink moved around behind your back, we love a clean operation. πŸŽ€

If no valid encoding can be read from the I/O device, the encoding of the I/O device gets set to the default encoding. Simple as that, no drama.

It returns the read encoding β€” or none, if no valid encoding was found. πŸ¦‹


set_encoding(File, Default)

set_encoding(File, Default) -> source_encoding() | none
                      when
                          Default :: source_encoding(),
                          File :: io:device().

same vibe, one tiny glow-up: you get to pass in your own Default 🌸 instead of relying on the built-in default. It reads the encoding from an I/O device and sets the encoding of the device accordingly, and again, the position of the I/O device referenced by File is not affected β€” we keep that sacred. πŸ’…

If no valid encoding can be read from the I/O device, the encoding of the I/O device is set to the encoding specified by Default. Periodt. πŸ‘‘

And just like its sibling above, it returns the read encoding, or none if no valid encoding is found. 🫢


reminder from your girl: both of these set_encoding versions return source_encoding() | none, and none is a whole valid answer. It's not a failure, it's just ✨vibes✨ of the "couldn't read a valid encoding" variety.

And that's the whole page, bestie, we ate and left no crumbs. πŸ’– This iconic little slice of stdlib is brought to you by Ericsson AB, running stdlib 8.0.4, and it lives in section epp(3) of the manual. Go forth and preprocess responsibly. πŸŽ€πŸ¦‹βœ¨

πŸ“¦ where this page comes from