Tools

TB 3.0.0 comes with a formatter, a checker, a test runner with coverage, a debugger, a documentation generator, an app builder, a package manager and a language server, all inside the tb command, plus an extension for VS Code.

Every command

All the tools are part of the tb program you already have; there is nothing else to install. tb --help lists them and tb COMMAND --help explains one.

CommandWhat it does
tb file.tb [args]Run a program (os.args holds the arguments)
tbType TB one line at a time (exit to leave)
tb fmtLay code out in the standard style
tb checkFind mistakes without running the program
tb test [--coverage]Run the *_test.tb files, and show which lines they reached
tb debugStep through a program with breakpoints
tb profileShow where a program spends its time and memory, with a flame graph
tb docShow a library's help, or make docs from your own comments
tb buildPack an app into a folder that runs without TB installed
tb init / add / remove / update / packagesManage a project's packages
tb search / info / login / publish / yankFind packages and publish your own to the TB Package Index
tb lspThe language server editors talk to
tb --engine tree file.tbRun with the 2.x-style interpreter instead of the virtual machine

tb fmt: tidy layout

tb fmt rewrites .tb files in one standard layout: two-space indents, one statement per line, spaces around operators. Comments and blank lines stay where you put them. Run it on a file, a folder, or with no arguments for everything under the current folder; --check only lists files that would change (handy in CI).

$ cat messy.tb
fn  greet( name,greeting="Hello" ){
return f"{greeting}, {name}!"}
let people=["Tanisha","world"]
for p in people { say(greet(p)) }

$ tb fmt messy.tb
formatted messy.tb
1 file reformatted, 0 already tidy.

$ cat messy.tb
fn greet(name, greeting = "Hello") {
  return f"{greeting}, {name}!"
}
let people = ["Tanisha", "world"]
for p in people {
  say(greet(p))
}

A project can choose its own line width and indent in the [fmt] table of its tb.toml. tb fmt, the language server's formatting and VS Code's Format Document all follow it:

[fmt]
line_width = 80   # 40 to 200, normally 100
indent = 4        # 1 to 8 spaces, normally 2

tb check: find mistakes early

tb check reads your program without running it and reports names that don't exist, the wrong number of inputs, unknown methods or library functions, changes to a const, and type mismatches where you wrote type hints (let x: number = 1, fn f(a: text) -> text). Warnings cover unused names, code that never runs and a match that misses an enum value. --strict fails on warnings too; --json is for scripts and editors.

$ tb check oops.tb
oops.tb:3:5: error: I don't know what 'nmae' is. Did you mean 'name'?
oops.tb:5:5: error: add() needs 2 inputs, but got 1.
oops.tb:7:1: error: 'MAX' is a constant (made with const), so it can't be changed.
Found 3 errors in 1 of 1 file. Fix the errors and run tb check again.

A line that breaks a rule on purpose can end with # tb-check: ignore.

tb test: tests and coverage

Name test files something_test.tb and use the test library. tb test runs every test file under a folder; --coverage adds how many lines of each file the tests ran, and which ones they missed.

# calc_test.tb
import test
import "calc.tb"

test.case("add", fn() {
  test.equal(calc.add(2, 3), 5)
})
test.case("half", fn() {
  test.equal(calc.half(9), 4.5)
})
$ tb test --coverage
calc_test.tb
  ok   add
  ok   half
  All 2 checks passed.

Coverage (lines the tests ran):
  calc.tb: 50% (3 of 6 lines) - missed 4, 5, 7
  calc_test.tb: 100% (6 of 6 lines)

OK: 2 checks passed in 1 test file.

tb debug: step through a program

tb debug file.tb stops before the first line and waits at a (tb) prompt. Set breakpoints, step into or over functions, print any expression, list the parameters and variables of the current function (locals) and the module-level names (globals).

$ tb debug main.tb
(tb) break 12          stop when line 12 is about to run   [b]
(tb) run               start the program                    [r]
(tb) next              run one line, stepping over calls    [n]
(tb) step              run one line, going into calls       [s]
(tb) print total * 2   show the value of an expression      [p]
(tb) locals            this function's parameters and vars  [l]
(tb) globals           module-level names, with values      [g]
(tb) backtrace         the functions that are running       [bt]
(tb) continue          run to the next breakpoint           [c]
(tb) quit                                                   [q]

tb debug --dap is the same debugger for editors: it speaks the Debug Adapter Protocol on standard input and output. The VS Code extension starts it when you press F5, so you can click in the margin to set breakpoints, step, watch expressions and look at variables. It works the same on the virtual machine and the tree engine. While debugging in an editor, ask() gets no typed answer (it returns "").

tb profile: find the slow part

tb profile file.tb runs the program at full speed while it looks, about a thousand times a second, at which functions are running and on which line. When the program ends you get the functions that took the most time, the busiest lines, and the largest values the program still holds. Time spent inside a library function (like logs.read) is shown under that function.

$ tb profile apps/log-triage/main.tb
...the program's own output...
Functions (self: its own code and the library calls it makes; total: with the TB
functions it calls too)
          self         total  function
   149 ms  24%   634 ms 100%  <main>
    55 ms   9%    55 ms   9%  logs.read (library)
    29 ms   4%    81 ms  13%  triage  (rules.tb:102)
    20 ms   3%    20 ms   3%  span  (main.tb:57)

Busiest lines
          time  line
    55 ms   9%  main.tb:96  records = logs.read(path)
    25 ms   4%  rules.tb:111  return alerts.sort_by(fn(a) {

Largest values held at the end
      110.1 KB  all_alerts  (list of 16 items)

tb profile --html profile.html file.tb also writes a flame graph: each bar is a function, as wide as the time spent in it, with the functions it called underneath. It is one HTML file with no scripts, so you can open it anywhere or attach it to a bug report. Put --html before the file name; anything after the file name goes to your program, as with tb file.tb args. Only the main thread is measured, not parallel tasks.

tb doc: help for libraries and your own code

tb doc lists the libraries and tb doc pcap shows one, the same text as the library reference. Pointed at your own files, it turns ## comments above functions, classes and constants into documentation: text, --md for Markdown or --html for a page per file.

## Greets someone by name and gives back the greeting.
export fn greet(name, greeting = "Hello") {
  return f"{greeting}, {name}!"
}
$ tb doc greet.tb
$ tb doc apps/ --html --out docs/

tb build: share an app

tb build app.tb makes a folder in dist/ with your program, the files it imports, its data and the TB runtime, plus a launcher. Zip it and anyone can run it, without installing TB. Use --gui for windowed apps (no console window on Windows) and --include for extra files.

$ tb build apps/notes/main.tb --gui
$ ls dist/notes
notes  notes.tb  README.txt  ...

tb init, add, remove, update, packages

A project is a folder with a tb.toml. tb add installs a package from the TB Package Index into tb_packages/ and pins the exact version and its sha256 in tb.lock; commit both files and everyone gets the same code. Add @ and a range to choose versions: ^1.2 (any 1.x from 1.2), ~1.2.3 (1.2.x), >=1.0 <2 or an exact 1.2.3.

$ tb init
Started the TB project 'report' (tb.toml written).
$ tb add tb-tables
Adding tb-tables...
  + tb-tables 1.0.0 (TB Package Index) -> tb_packages/tb-tables/
$ tb search tables      # find packages
$ tb info tb-tables     # versions, owner and checksums
$ tb packages           # what this project uses
$ tb update             # newer versions, within tb.toml's ranges
$ tb remove tb-tables
import "tb-tables" as tables
say(tables.table([{"host": "web01", "status": "up"}]))

Publishing: tb login, publish, yank

Sign in on the Publish page with GitHub and make an API token, then tb login once and tb publish in your package's folder. It checks the package with the index's rules, zips it the same way every time and uploads it. The first version of a new package is reviewed before it is listed; after that your new versions appear at once. tb yank takes a broken version out of version ranges without breaking projects that pinned it.

$ tb login              # paste the token from the website
$ tb publish --dry-run  # what would be uploaded
$ tb publish
Submitted tb-greet 1.0.0 for review.
$ tb yank tb-greet@1.0.1 --reason "breaks on Windows"

tb lsp: the language server

tb lsp speaks the Language Server Protocol over standard input and output, so any editor with LSP support can use it: errors as you type (from tb check), completion of keywords, built-ins, libraries and your own names, hover help, signature help, go to definition, document symbols, rename and formatting. The VS Code extension starts it for you.

  • Find references and rename across files. For a top-level function, class or value, both follow your imports (import "helpers.tb" as h, from "helpers.tb" import greet) through every .tb file in the workspace, including packages in tb_packages/. Rename changes the definition, the imports and every use in one step. Local names and fields stay within their file.
  • Quick fixes. When tb check asks "Did you mean 'total'?", one click makes the change. When you use a library you haven't imported, such as json.parse(...), the fix adds import json under your other imports.
  • Organise imports. Sorts the imports at the top of a file and drops repeated ones: libraries first, then your own files (VS Code: Shift+Alt+O, or Organize Imports from the command palette).

VS Code extension

The TB extension for Visual Studio Code adds highlighting for all of TB 3.0, snippets, TB: Run File (Cmd+Alt+N or Ctrl+Alt+N), TB: Check, TB: Format, TB: Run Tests, and everything tb lsp gives.

Download tb-vscode-3.2.0.vsix

  1. Install TB from the download page and check that tb --version works in a terminal.
  2. Download tb-vscode-3.2.0.vsix (above).
  3. In VS Code, open the Extensions view (Cmd+Shift+X or Ctrl+Shift+X), click the … menu at the top of the view and choose Install from VSIX…, then pick the file. Or, in a terminal: code --install-extension tb-vscode-3.2.0.vsix.
  4. Open any .tb file. If tb isn't on your PATH, set TB: Path (tb.path) in Settings to the full path of the program.

Debugging. Open a .tb file, click in the margin next to a line to set a breakpoint and press F5. The program runs until it reaches the line, then you can step over (F10), into (F11) and out of (Shift+F11) functions, read the Variables and Call Stack panels, add watch expressions and type expressions in the Debug Console. The program's output appears in the Debug Console. To keep settings, add a launch configuration (Run, Add Configuration, TB: Debug a file):

{
  "type": "tb",
  "request": "launch",
  "name": "Debug TB file",
  "program": "${file}",
  "cwd": "${fileDirname}",
  "args": [],
  "stopOnEntry": false
}

Settings: tb.path (the tb program), tb.languageServer.enabled, tb.check.onSave, tb.run.saveBeforeRun. With TB 2.x only highlighting, snippets and Run File work; the rest needs TB 3.0.