Skip to content

Builtin functions #232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 70 additions & 8 deletions doc/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ An `import` statement references another module and lets the current
module use its exported module variables and functions. Its syntax
is:

local <localname> = import "<modname>"
[local <localname> =] import "<modname>"

The module name `<modname>` is a name like `foo`, `foo.bar`, or `foo.bar.baz`.
The Titan compiler translates a module name into a file name by converting
Expand All @@ -290,6 +290,9 @@ all dots to path separators, and then appending `.so`, for binary modules, or
modules will correspond to `foo.{so|titan}`, `foo/bar.{so|titan}`, and `foo/bar/baz.{so|titan}`.
The Titan compiler will recompile the module if its source is newer than its binary.

If the `<localname>` is not given, Titan will use the last name of
the module (`foo` for all of `foo`, `bar.foo`, or `baz.bar.foo`, for example).

Binary modules are looked up in the *runtime search path*, a semicolon-separated list
of paths that defaults to `.;/usr/local/lib/titan/0.5`, but can be overriden with a
`TITAN_PATH_0_5` or `TITAN_PATH` environment variable. Source modules are looked in
Expand Down Expand Up @@ -323,12 +326,15 @@ as call the exported function `bar`.
A `foreign import` statement loads a C header file, and imports all its
function and type definitions. Its syntax is:

local <localname> = foreign import "<headername>"
[local <localname> =] foreign import "<headername>"

The string containing the `<headername>` argument may be a relative or
absolute pathname to a C header file. When a relative pathname is given,
the Titan compiler will search in standard C header paths.

If the `<localname>` is not given, Titan will use the name of the header
file without the path part (`foo` for `path/foo.h`, for example).

Due to the flat namespace of C functions and types, all types and functions
imported via foreign imports share a single namespace in Titan as well.

Expand All @@ -340,8 +346,8 @@ many headers will import the same system headers.

For example, if one loads two header files like this

local foo = foreign import "foo.h"
local bar = foreign import "bar.h"
foreign import "foo.h"
foreign import "bar.h"

and both `foo.h` and `bar.h` happen to include (directly or indirectly) the
system header `stdio.h`, this means that both `foo.printf` and `bar.printf`
Expand Down Expand Up @@ -400,23 +406,79 @@ Casts from `float` to `integer` fail if it is not an integral value, and if
this value is outside the allowable range for integers. Casts from `value`
fail if the value does not have the target type, or cannot be converted to it.

## The standard library

### Basic functions

You can call these functions directly from anywhere in a Titan module,
without needing to import anything.

#### assert(v: value, msg: string): string

Returns `v` if it is truthy, otherwise throws an error with message `msg`.

#### dofile(filename: string, ...: value): { value }

Executes the Lua chunk inside the file named `filename`, passing any extra
arguments (you can access these arguments inside the Lua code with `...`).
Returns an array containing all the values that the Lua chunk returned.

The Lua code can load (using `require`) any Titan module that is part of
a Titan application, if you are running in the context of an application.

#### dostring(chunk: string, ...: value): { value }

Executes the Lua chunk inside `chunk`, passing any extra
arguments (you can access these arguments inside the Lua code with `...`).
Returns an array containing all the values that the Lua chunk returned.

The Lua code can load (using `require`) any Titan module that is part of
a Titan application, if you are running in the context of an application.

#### error(msg: string)

Throws an error with error message `msg`.

#### print(...: value)

Prints its arguments to the standard output, separated by tabs and ending in a newline
(`print()` prints just a newline).

#### tostring(v: value): string

Returns the string representation for the Titan value `v`.

#### tofloat(s: string): float

If `s` has a numeric literal returns the corresponding number as a floating
point value, otherwise returns `0.0`.

#### tointeger(s: string): integer

If `s` has an integer literal or an integral floating point literal
returns the corresponding integer value, otherwise returns `0`.

## The Complete Syntax of Titan

Here is the complete syntax of Titan in extended BNF. As usual in extended BNF, {A} means 0 or more As, and \[A\] means an optional A.

program ::= {tlfunc | tlvar | tlrecord | tlimport | tlforeignimport}
program ::= {tlfunc | tlvar | tlrecord | tlimport | tlforeignimport | tlforeignfunc}

tlfunc ::= [local] function Name '(' [parlist] ')' ':' type block end

tlvar ::= [local] Name [':' type] '=' Numeral

tlrecord ::= record Name recordfields end

tlimport ::= local Name '=' import LiteralString
tlimport ::= [local Name '='] import LiteralString

tlforeignimport ::= [local Name '='] foreign import LiteralString

tlforeignfunc ::= foreign function Name '(' [parlist] ')' ':' type

tlforeignimport ::= local Name '=' foreign import LiteralString
param ::= Name ':' type | '...' ':' type

parlist ::= Name ':' type {',' Name ':' type}
parlist ::= param {',' param}

type ::= value | integer | float | boolean | string | array | map

Expand Down
71 changes: 59 additions & 12 deletions spec/checker_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1629,25 +1629,29 @@ describe("Titan type checker", function()
end)

it("catches use of function as first-class value", function ()
local code = [[
assert_type_error("access a function", [[
function foo(): integer
return foo
end
]]
local ok, err = run_checker(code)
assert.falsy(ok)
assert.match("access a function", err)
]])
assert_type_error("access a function", [[
function foo(): integer
return print
end
]])
end)

it("catches assignment to function", function ()
local code = [[
assert_type_error("assign to a function",[[
function foo(): integer
foo = 2
end
]]
local ok, err = run_checker(code)
assert.falsy(ok)
assert.match("assign to a function", err)
]])
assert_type_error("assign to a function",[[
function foo(): integer
print = 2
end
]])
end)

it("catches use of external function as first-class value", function ()
Expand Down Expand Up @@ -1907,13 +1911,56 @@ describe("Titan type checker", function()
function h()
f(20, (g()))
end
]], args = 2, params = 3 }}
]], args = 2, params = 3 },{ code = [[
function g(): integer
return 20
end
function h()
assert(g())
end
]], args = 1, params = 2 }, { code = [[
function g(): (integer, integer)
return 20, 30
end
function h()
assert(20, g())
end
]], args = 3, params = 2 }, { code = [[
function g(): (integer, integer)
return 20, 30
end
function h()
assert((g()))
end
]], args = 1, params = 2 }}
for _, c in ipairs(cases) do
local ok, err = run_checker(c.code)
assert.falsy(ok)
assert.match("function 'f' called with " .. c.args .. " arguments but expects " .. c.params, err)
assert.match("called with " .. c.args .. " arguments but expects " .. c.params, err)
end
end)

it("vararg foreigns and functions", function ()
assert_type_check([[
function g()
print('foo', 1, 2.5)
end
]])
assert_type_error("only the last parameter", [[
function f(...: float, a: string)
end
]])
assert_type_error("only the last parameter", [[
record R end
function R:f(...: float, a: string)
end
]])
assert_type_error("expected string but found float", [[
function g()
dostring(2.5, 1, 'bar')
end
]])
end)
end)

describe("Titan typecheck of records", function()
Expand Down
Loading