Skip to content

Builtins

Bill Hails edited this page Nov 9, 2024 · 31 revisions

There is now limited support for linking to external libraries and native C code, it's only at compile time currently, and the following functionality is provided:

Pseudo-Random Numbers

the built-in function rand takes a floating point number between 0.0 and 1.0 and returns a floating point number in the same range. It is in no way cryptographically secure and uses the most basic RNG algorithm I could find, but it works.

Assertions

For writing tests. Mostly this is implemented by F Natural in the standard preamble but there is a core built-in that either sets a flag for later (if --assertions-accumulate) or directly exits with a non-zero exit status. Support in the parser recognises the function assert(condition) and provides it with filename and line number to report before calling the built-in, and support in the main run-time will exit with a non-zero status when the program has finished if --assertions-accumulate and the aforementioned flag was set.

Character to Unicode Conversion

The built-in ord(char) returns the integer Unicode code point for the char, and conversely chr(number) returns the char at that Unicode point.

I/O

There are a number of I/O facilities implemented as built-ins, namely:

  • putc(char) puts a character to the standard output.
  • fputc(file, char) puts a character to the open file handle.
  • getc() returns the next character from stdin.
  • fgetc(file) returns the next character from the file.
  • putn(number) writes the number to stdout.
  • fputn(file, number) writes the number to the file.
  • putv(any) writes any type of argument (unformatted) to stdout.
  • fputv(file, any) writes any type of argument (unformatted) to the file.
  • puts(string) puts the string of characters to stdout.
  • fputs(file, string) puts the string of characters to the file.
  • gets() reads the next line from stdin up to EOF or '\n' and returns it, discarding any newline.
  • fgets(file) same as gets but for the argument file.
  • open(string, io_mode) attempts to open the file in the given mode. Returns a try(string, opaque file handle) where the string is the error if any, see below for io_mode and try.
  • openmem() opens an in-memory buffer (using open_memstream (3)) and returns a try(string, opaque file handle). The result should always be a success. All the normal file write operations can be performed on the file handle and will append to the buffer. Any fgets on the file handle will return any new data in the buffer since the last time it was called. fgetc is currently not implemented for buffers and will cause a run-time error. See below for try.
  • close(file) closes the file if open. It is a no-op if the file is already closed. This operation also works on the file handles returned by openmem. Returns true.
  • opendir(path) attempts to open the argument directory name for reading. returns a try of a string error message or an opaque directory handle.
  • readdir(dir) attempts to read the next entry from the open directory handle, returns a maybe(string) of the file name.
  • closedir(dir) closes the directory handle. It is a no-op if the handle is already closed. Returns true.
  • ftype(path) returns a try of a string error message and a ftype_type

Garbage collection will close any open file handles that are un-referenced, but you should still be careful to close at least write handles, which will flush the data to disk, as there is no guarantee that there will be a garbage collection before the program exits. For that reason the ioutils.fn namespace provides some monad-style utilities that can open and close file handles for you.

To support the i/o built-ins there are a few new types, one is a generic opaque type implemented in core that can be sub-typed. That is used to describe file and directory handles (as well as database and statement handles for the sqlite extension). Another is a try type which is like maybe but treats failure as more of an error. It is defined in the preamble as:

typedef try(#f, #s) { failure(#f) | success(#s) }

which allows flexibility in the type of error value (string error message or integer error code are obvious alternatives).

io_mode is a simple enum:

typedef io_mode { io_read | io_write | io_append }

ftype_type is another simple enum

typedef ftype_type {
    ftype_socket   // socket
  | ftype_symlink  // symbolic link
  | ftype_regular  // regular file
  | ftype_block    // block device
  | ftype_dir      // directory
  | ftype_char     // character device
  | ftype_fifo     // named pipe
}

SQLite

Lightweight database access.

  • sqlite3_open(string) returns a try of a string error message and an opaque database handle (see i/o above for try).
  • sqlite3_close(db_handle) closes the database handle, if it is still open, always returns true currently.
  • sqlite3_prepare(db_handle, string) prepares an sql statement for execution. returns a try of an integer error code and an opaque stmt_handle.
  • sqlite3_bind(stmt_handle, list(basic_type)) binds the arguments to the statement.
  • sqlite3_fetch(stmt_handle) executes the statement and/or returns the next row as a maybe(list(basic_type)).
  • sqlite3_finalize(stmt_handle) frees resources associated with the handle. Called automatically when the handle is garbage-collected, but it's a good idea to call it manually if you're creating a lot of statements.
  • sqlite3_close(db_handle) closes the database handle if it is still open.

The basic_type mentioned above is defined in the preamble as

typedef basic_type { basic_null | basic_number(number) | basic_string(string) | basic_char(char) }

Command-line Arguments

The argv(n) function returns the nth command-line argument passed to the executable after the file being executed. So the script only has access to arguments after that point on the command line. The first such argument is index 0. The result is a maybe(string) which is nothing if the index is out of range.

Getenv

Read-only access to the environment is provided by getenv which takes the string name of the environment variable and returns a maybe(string) of the value.

Complex Numbers

A few utilities for de-structuring complex numbers. Any argument number is acceptable, non-complex numbers are treated as complex with a zero imaginary part. Probably I should at least add a function to construct a complex number from polar co-ordinates, but it's not there yet.

  • com_real returns the real part of a complex number.
  • com_imag returns the imaginary part of a complex number. The result is still imaginary, to convert to a real number divide by 1i.
  • com_mag returns the magnitude (polar co-ordinate system) of the complex number.
  • com_theta returns the angle (polar co-ordinate system) of the complex number in radians.

Unicode_category

A function that returns the Unicode general category for any Unicode character. The set of categories is defined in the preamble as

typedef unicode_general_category_type {
    GC_Ll | GC_Lm | GC_Lo | GC_Lt | GC_Lu |
    GC_Mc | GC_Me | GC_Mn |
    GC_Nd | GC_Nl | GC_No |
    GC_Pc | GC_Pd | GC_Pe | GC_Pf | GC_Pi | GC_Po | GC_Ps |
    GC_Sc | GC_Sk | GC_Sm | GC_So |
    GC_Zl | GC_Zp | GC_Zs |
    GC_Cc | GC_Cf | GC_Co | GC_Cs | GC_Cn 
}

The builtin unicode_category has a type signature of char -> unicode_general_category_type. You can read a description of the categories on wikipedia.

Under the hood this is done with a huge lookup table of byte codes. I can only really justify having this table if it's also used by the parser, but I haven't done that yet, the builtin was just to try it out.

Next: Operators