Skip to content

Conversation

@hishamhm
Copy link
Member

@hishamhm hishamhm commented Jun 5, 2025

This is a long-awaited development; as we get more people contributing, it's about time we split the code into multiple modules.

I must admit I do find editing a single huge file comfortable, but I obviously understand that splitting the code in modules has other advantages, including making it easier for people to grasp the overall structure more easily, and also to exercise multi-module project structure in the compiler itself.

I still want to be able to have a single-file redistributable version of the compiler, so I'm splitting it into a teal.* namespace, and I plan to make tl.tl become an auto-generated single-file version.

Steps involved:

  • Begin splitting some parts into modules, require sub-modules into main tl.tl
  • Adapt Makefile for multi-file bootstrap development workflow, so we can start running using multiple modules, build them incrementally and bootstrap-test them (it's ugly but it works)
  • Finish splitting all parts into modules, require sub-modules into main teal/init.lua
  • Adapt tl to use split modules instead
  • Adapt extras/binary.sh generator for multi-file structure
  • Adapt extras/release.sh for multi-file structure
  • Make a simple generator for the single-file amalgamation
  • Write some developer documentation on project structure and dev tools

Closes #549.

@github-actions
Copy link

github-actions bot commented Jun 5, 2025

Teal Playground URL: https://1003--teal-playground-preview.netlify.app

@hishamhm hishamhm force-pushed the module-split branch 2 times, most recently from 93f0320 to 586a137 Compare June 6, 2025 02:02
@mbartlett21
Copy link
Collaborator

For a generator for a single file, you should be able to do something like the below:

Details

local module_to_files = {
   ['teal.attributes']              = 'teal/attributes.lua',
   ['teal.checker.checker']         = 'teal/checker/checker.lua',
   ['teal.checker.file_checker']    = 'teal/checker/file_checker.lua',
   ['teal.checker.require_file']    = 'teal/checker/require_file.lua',
   ['teal.checker.string_checker']  = 'teal/checker/string_checker.lua',
   ['teal.checker.type_checker']    = 'teal/checker/type_checker.lua',
   ['teal.debug']                   = 'teal/debug.lua',
   ['teal.environment']             = 'teal/environment.lua',
   ['teal.errors']                  = 'teal/errors.lua',
   ['teal.facts']                   = 'teal/facts.lua',
   ['teal.gen.lua_generator']       = 'teal/gen/lua_generator.lua',
   ['teal.lexer']                   = 'teal/lexer.lua',
   ['teal.parser']                  = 'teal/parser.lua',
   ['teal.precompiled.default_env'] = 'teal/precompiled/default_env.lua',
   ['teal.traversal']               = 'teal/traversal.lua',
   ['teal.types']                   = 'teal/types.lua',
   ['teal.type_errors']             = 'teal/type_errors.lua',
   ['teal.type_reporter']           = 'teal/type_reporter.lua',
   ['teal.util']                    = 'teal/util.lua',
   ['teal.variables']               = 'teal/variables.lua',
}

local keys = {}
for k in pairs(module_to_files) do table.insert(keys, k) end
table.sort(keys)

-- populate the preload field so that we leave worrying about dependency order
-- to Lua
local content = {}
for _, k in ipairs(keys) do
   local ke = ('%q'):format(k)
   local fn = module_to_files[k]
   local f = assert(io.open(fn, 'rb'))
   local modcontent = assert(f:read'a')
   assert(f:close())

   table.insert(content,
string.format([[
-- module %s from %s
package.preload[%q] = function(...)
%s
end

]], k, fn, k, modcontent)
   )
end

do
   local f = assert(io.open('tl.lua', 'rb'))
   local modcontent = assert(f:read'a')
   assert(f:close())
   table.insert(content, modcontent)
end

local f = assert(io.open('tl_combined.lua', 'wb'))
assert(f:write(table.concat(content)))
assert(f:close())

@hishamhm hishamhm force-pushed the module-split branch 4 times, most recently from f4576c7 to ad8a9b6 Compare June 30, 2025 04:09
@hishamhm
Copy link
Member Author

hishamhm commented Jul 3, 2025

@mbartlett21 I pulled in your generator into extras/! I'll change it to produce a tl.lua instead of tl_combined.tl once I no longer have one in the top-level (I just need to move out the high-level API functions now, the split is almost ready).

@hishamhm hishamhm force-pushed the module-split branch 2 times, most recently from 73883a9 to 28320d0 Compare July 8, 2025 01:05
@hishamhm
Copy link
Member Author

hishamhm commented Jul 8, 2025

And here is my shot at a new API for Teal!

Lua 5.3.6  Copyright (C) 1994-2020 Lua.org, PUC-Rio
> teal = require("teal")
> compiler = teal.compiler()
> input = compiler:input([[record R end; function R.func(s:string) print(s:upper()) end; return R]])
> module, errs = input:check()
> module
nil
> require("tabular")(errs)
┌───────────────┬───────────────────────────────────────────────────────────────────────────────────┐
│syntax_errors :│┌──────────┬──────────────────────────────────────────────────────────────────┬─┬─┐│
│               ││filename  │msg                                                               │x│y││
│               ││────────  │───                                                               │─│─││
│               ││<input>.tl│record needs to be declared with 'local record' or 'global record'│1│1││
│               │└──────────┴──────────────────────────────────────────────────────────────────┴─┴─┘│
│type_errors ..:│┌┐                                                                                 │
│               │└┘                                                                                 │
│warnings .....:│┌┐                                                                                 │
│               │└┘                                                                                 │
└───────────────┴───────────────────────────────────────────────────────────────────────────────────┘
> input = compiler:input([[local record R end; function R.func(s:string) print(s:upper()) end; return R]])
> module = assert(input:check())
> module:gen()
local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string; local R = {}; function R.func(s) print(s:upper()) end; return R

Bonus stage!: I ported tl (the CLI driver) to Teal! \o/

This was very useful in test-driving the API. @euclidianAce I also kept an eye on how Cyan uses the "v2" API, but I didn't go as far as attempt to port it.

@hishamhm hishamhm force-pushed the module-split branch 4 times, most recently from dbc7474 to b7d0e8c Compare July 8, 2025 05:47
@hishamhm hishamhm force-pushed the module-split branch 8 times, most recently from 4408fae to a0bbfef Compare July 27, 2025 09:08
@hishamhm
Copy link
Member Author

hishamhm commented Oct 2, 2025

Rebased on top of the latest changes. @mbartlett21 the test updates in #1027 were a lifesaver for the rebase process!

This was the first big exercise of the new API.
Lua 5.1 and 5.2 (including LuaJIT) have some awkward defaults
for the package path: every default paths accepts the `/?/init.lua`
variant, *except* for `./?.lua`. This was since fixed in Lua 5.3
(see `lua53 -E -e 'print(package.path)'`).

Since the main Teal API is currently under `teal.init`, let's
avoid headaches by handling `?.tl` vs `?/init.tl` files consistently
in a universal way.

(Yes, theoretically this breaks the environment for someone who wanted to
explicitly disable or reorder those `init` package paths compared to their
plain siblings (and I'm not a fan of such behind-the-user's-back
behaviors, but I think this is extremely unlikely and, given what I've
already seen in Teal's own CI this changes is much more likely to make
things Just Work than to cause any problems.)
* always return LexError, like check
* minor tweak in malformed number token
@hishamhm
Copy link
Member Author

Merged manually!

@hishamhm hishamhm closed this Oct 15, 2025
@hishamhm hishamhm deleted the module-split branch October 15, 2025 16:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Split up code into multiple files

3 participants