Project / C11

gBASIC

BASIC that grew up — money, dates and durations as real value kinds, a spreadsheet engine checked against 15,871 real Excel workbooks, and a whole business platform in one binary.

Download · Linux, x86-64 · 2.1 MBWindows, x64 · 4.3 MBNo install step on Linux — extract it and run it. Other files and checksums.

gBASIC is a BASIC-family language for people who like BASIC's readability and want real power underneath it. One self-contained interpreter in portable C11 — no runtime to install, no VM, no package manager, and nothing required to build it but a C compiler, make and bison.

The syntax is the easy part to explain and the least interesting thing about it. What makes gBASIC worth a look is what it carries: money as an exact value kind, and on top of it double-entry accounting, loan servicing, deposits and credit analytics; a spreadsheet engine that edits real Excel files and has been measured against 15,871 of them; business-calendar scheduling — "third Thursday", payroll rolled off holidays, SLA clocks, timezones — as part of the standard vocabulary; a statistics library that runs from descriptive stats to survival models and causal inference; a declarative web server; and a stack for calling a model and letting it call back. Sixty-seven libraries written in gBASIC itself, and one binary.

  • 97.38%of spreadsheet formula cells agree with Excel's own cached results, across 15,871 real workbooks — with no disagreement at all in 91.1% of them
  • 0required dependencies. Every optional library degrades to a clean runtime error, never a build failure
  • 145test suites — every one discovered by glob, so a gate cannot silently shrink

A taste

Assignment can carry a modifier — a small piece of behaviour attached at the point a value is set. It is how gBASIC gets money, dates and text transforms without ceremony:

program main(args)
    price {USD} = 19.95
    invoiced {date} = "2026-03-01 09:00:00"
    name {uppered} = "ada lovelace"

    print "price:    " + price
    print "with tax: " + (price * 1.08)
    print "due:      " + (invoiced + 45 days)
    print "customer: " + name
end program

Prints 19.95, 21.55, 2026-04-15 09:00:00, ADA LOVELACE. Money, dates and durations are real value kinds — not floats and strings you have to remember the meaning of.

Money is a value kind, and business is the library

Money in gBASIC is an exact integer of minor units with four guard digits below them — never a float — and it carries its currency. That one decision is what the rest of the business platform is built on: a ledger that balances stays balanced, because there is no rounding error to sweep anywhere.

price {USD} = 19.95
print price * 1.08                       ' 21.55

' Three people split a hundred dollars. The remainder goes out a cent at a
' time, so the parts add back up to exactly what was split.
bill {USD} = 100.00
print money.allocate(bill, 3)            ' 33.34  33.33  33.33

' 250,000 at 6% a year, monthly, over 30 years.
loan {USD} = 250000.00
print finance.pmt(0.06 / 12, 360, loan)  ' -1498.88

eur {EUR} = 10.00
print price + eur                        ' refused, by name

All 178 ISO 4217 currencies, each with its own minor unit — JPY has none, KWD has three — from a table committed to the repository rather than read from the build machine. Adding two currencies raises; comparing them for equality answers false. Splitting three ways never loses a cent nor invents one, and the payment figure is the one Excel and LibreOffice give.

On that sit five more libraries, each with a cookbook the test suite enforces: accounting (double-entry, where the accounting equation is asserted after every posting rather than enforced anywhere — it falls out of correct account typing, which is what makes it a good test), lending (servicing as an auditable fold: replay the events and the balance is explained, rather than stored and unexplainable), deposits, credit (roll rates, vintage triangles, charge-offs) and scoring (WOE binning, AUC, point scales).

These libraries are written the way they are because every defect they prevent is an ordinary-looking number. A ledger with the right amounts on the wrong side still balances. A roll rate computed over the loans still visible instead of the whole starting bucket is a perfectly plausible percentage. So the cookbooks put the wrong answer beside the right one, and the suites assert arithmetic rather than transcripts.

Spreadsheets it can actually be trusted with

Most spreadsheet libraries generate new files. gBASIC edits existing ones, which is a much harder guarantee: the reader keeps every part of a workbook, including the parts it does not understand, so saving never quietly destroys what it could not model.

It also does something unusual. An .xlsx stores the value Excel computed for every formula the last time it calculated — which makes the file its own test oracle. xlsx.check evaluates each formula and compares:

program main(args)
    wb = xlsx.open("quarterly.xlsx")
    r = xlsx.check(wb, "Ledger")
    print r.agree + " agree, " + r.disagree + " disagree, " + r.unsupported + " unsupported"
end program

You can ask, before trusting a single number, whether this engine and Excel agree on your workbook. Across a corpus of 15,871 real spreadsheets, 97.38% of judged formula cells agree and 91.1% of workbooks show no disagreement at all. What the engine cannot evaluate it refuses by name (an array formula, a reference into a workbook that isn't there), never answering with a plausible wrong number — which is why the rate is quoted over cells it actually judged rather than over all of them, since counting a refusal as a pass would flatter it.

On top of that sit three libraries that turn a messy sheet into something a database can answer questions about: grid finds the real table inside a report full of titles, subtotals and notes (and tells you how confident it is), consolidate merges differently-shaped sources onto one schema, and dbframe loads the result into SQLite with column types inferred from every value rather than the first.

Calendars are a language feature

No mainstream language treats business-calendar work — the third Thursday, payroll that rolls off holidays, "respond within 4 business hours" — as a first-class part of its standard vocabulary. gBASIC does, and it starts with arithmetic that respects the calendar:

jan31 {date} = "2026-01-31"
print jan31 + 1 month            ' 2026-02-28 — clamped, the accountant's rule

cal = dates.calendar({ holidays: [xmas], hours: { open: "9:00", close: "17:00" } })

' the board meets every third Thursday at 14:00
board = { every: "month", when: { nth: 3, weekday: "thursday" }, at: "14:00" }
meetings = dates.series(board, { from: jan1, through: dec31 }, cal)

' an SLA clock: nights, weekends and holidays excluded
due = dates.add_business_hours(ticket, 4 hours, cal)

' stored as civil time + zone, rendered anywhere — DST handled per occurrence
print to_zone(from_zone(m, "America/Chicago"), "Europe/Berlin")

A Friday-16:00 ticket with a 4-business-hour SLA is due Monday 12:00. A rule is a record — store it in a database, print it, send it to another process. Rules that no day satisfies answer unknown; questions with no honest answer (1 month > 30 days? a typo'd timezone?) are refused rather than guessed.

Calendars are data, so merging two people's calendars is a union of constraints and "a day that works for everyone" is one call. On top sit schedule.slots (appointment grids) and schedule.layout (sessions packed into working days around an immovable lunch — anything that fits nowhere is reported by name, never silently dropped). The whole surface is a 12-recipe cookbook whose every example the test suite runs.

Charts are strings

A chart in gBASIC is a string of SVG — no image library, no canvas object, and byte-for-byte deterministic, so charts are tested against goldens like everything else. Lines, scatter, area, bars, histograms, pies, correlation heatmaps and sparklines, straight off the frames the spreadsheet and statistics layers produce. Missing data draws as a gap, never a silent zero; a chart that would mislead — a stacked bar with negatives, a pie with an unknown share — is refused with an error that names the problem.

q = { quarter: [1, 2, 3, 4, 5, 6, 7, 8],
      revenue: [310, 345, unknown, 420, 462, 501, 548, 610],
      costs:   [270, 280, 285, 300, 315, 330, 355, 370] }
s = chart.spec("line", q)
s = chart.x(s, "quarter")
s = chart.y(s, ["revenue", "costs"])
s = chart.title(s, "Drawn by gBASIC - the code above, verbatim")
s = chart.size(s, 560, 260)
s = chart.options(s, { markers: true })
print(chart.render(s))
20030040050060070012345678revenuecostsDrawn by gBASIC - the code above, verbatim

The picture above is that program's output, pasted verbatim — note the gap where the third quarter's revenue is unknown. The 11-recipe cookbook is enforced by the test suite like the other two.

Ideas most BASICs don't have

Reactive watchers. Attach behaviour to values and it re-runs when they change — reactive programming with no framework and no wiring.

subtotal = 100.00
tax_rate = 0.085

watch pricing(subtotal, tax_rate)
    total = subtotal * (1 + tax_rate)
end watch

print total          ' 108.5
subtotal = 250.00
print total          ' 271.25

unwatch pricing      ' a named watcher is a first-class value with an off-switch

Naming a watcher binds a handle you can store, pass, and turn off — re-declaring the same name replaces the old watcher instead of stacking a second one, so setup code is safe to re-run, and watchers() recovers any handle you lost. Nothing can fire with its off-switch out of reach.

Policy-Based Inheritance. Compose objects by declaring what each field does on derivation — copied, shared, recomputed, or dropped — instead of building rigid class hierarchies.

account = {
    owner (copy):        "unassigned",
    bank  (link):        "First National",
    id    (reset next_id): 0,
    notes (exclude):     "internal"
}

a = new account          ' owner is its own; bank is shared;
b = new account          ' id is recomputed; notes never appears

Shared-nothing actors. Real OS processes over fork, with message passing, selective receive and death monitoring. No shared memory means no data races.

function doubler(parent)
    job = receive()
    send(parent, job * 2)
end function

program main(args)
    me = self()
    w = spawn doubler(me)
    send(w, 21)
    print "worker replied: " + receive()    ' 42
end program

Failure has a shape

A language earns trust by how it behaves when something goes wrong, and gBASIC's rule is that a failure is never silent. Errors are frame-scoped: a function decides its own error policy, and that decision cannot leak into the caller or be leaked into by it.

function parse_price(text)
    on error goto next
    n = number(text)
    if error then
        return unknown
    end if
    return n
end function

Prints 19.95, then unknown, then keeps running. on error goto next and on error goto LABEL are the whole vocabulary — there is no separate resume keyword to remember.

Beside it runs a second, quieter channel. A warning reports something suspicious without stopping anything, and — unlike an error — its volume is the caller's choice, not the callee's: on warning print, ignore, stop, or goto next. That suppression is what makes an aggressive warning affordable, which is why gBASIC can afford to tell you that a return value was discarded, or that a name you just assigned quietly shadows an outer one.

The same principle runs through the library surface. Operations that must report failure as a value have non-raising twins — try_decode for JSON you did not write, xlsx.try_open so one bad workbook cannot end a batch of thousands, process.which so asking "is this tool installed?" never kills the program that asked.

A website is a block

The server is not a library you wire together — it is a declarative block, and it adds exactly one reserved word to the language:

server demo( port: 8080 )
    get "/hello/{name}"( req )
        return { body: "hello, " + req.params.name }
    end get
end server

Route patterns are checked when the file loads, not when a request arrives: two routes that could never be told apart, a duplicate, a capture name used twice, a greedy segment that is not last — each is refused by name before the program runs. And because the block is data, the routes can be read and a request answered with no socket open at all, which is what makes a routing table testable:

print "routes declared: " + string(count(web.routes(demo)))
r = web.dispatch(demo, { id: 1, method: "GET", path: "/hello/ada", headers: {} })
print "answered with no socket open: " + r.body

Prints routes declared: 1 and answered with no socket open: hello, ada. Underneath sit TLS with SNI, streaming responses, a worker pool that reloads one worker at a time and never retires a healthy one on faith, and refusals for the request framings that carry smuggling attacks.

Calling a model, and letting it call back

tools is one declaration of what a model may call, and both the schema the model is shown and the validation of what comes back are derived from it — so they cannot disagree. The older shape had two hand-written things: a JSON-Schema record and a function, with nothing checking that the schema's id was the customer_id the function read.

A tool that raises comes back as a result the model can read rather than ending the run, and tool bodies execute in a pre-spawned worker pool rather than on the event loop, where one slow call would freeze every other client.

agent is the piece that makes an approval gate possible: a conversation in progress is a value, and the loop is a pure step that performs no I/O and returns the actions its caller must perform. That is what lets a run be stored between HTTP requests and an approval arrive ten minutes later on a different one — a naive loop cannot wait for a person on a single-threaded server. mcp publishes a toolset to any Model Context Protocol client over stdio or HTTP through one dispatcher, and consumes one the same way.

What a database estate says about itself

discovery reads the declared facts of a database estate — tables, columns, keys, and the SQL of views and procedures — across several databases at once, and traces where a column came from across hops. Two reports both show a number called total_volume, they disagree, and both are correct; what is wanted is where the derivations parted. It infers nothing, deliberately: an inferred relationship is the result of a search, and a search over a 500-table estate is some fifty million candidate pairs where coincidences are a certainty.

nlq answers a question in English over such an estate, and is built around the same finding. Hand a model twenty tables chosen badly and it writes flawless SQL about the wrong ones — and the SQL runs, because the tables it was given are real. Measured end to end, one question came back 36% short of its true answer by summing a warehouse table an ETL had already filtered: valid SQL, real table, wrong question, nothing in the output saying so. So a grounding carries its own search width, its near misses, and the words it could not match, and it refuses rather than guesses when the catalog cannot say which of two objects was meant.

Scored by an independently written answer key over nineteen questions — including deliberately ambiguous ones, and unanswerable ones where correctly declining is the right answer, with partial never folded into right — it scores 18 of 19 over a 127-object estate and 16 of 19 over a 517-object one. Grounding needs neither a model nor a database, which is exactly why that can be a gate rather than a demo.

An IDE, written in itself

gBASIC Studio is a desktop IDE for gBASIC — written in gBASIC, on GTK 4 through the GObject-Introspection bridge. It is the project's hardest dogfooding: an editor, a file browser, a run pipeline that executes a section in a child interpreter and keeps every result, a variable inspector, and state-only branches that replay the same source under different bindings.

Much of the platform on this page exists because Studio needed it and found it missing — the live child-process control, the source outliner, the atomic file replacement, the diagnostics stream. A language whose IDE is written in it cannot hide its own friction. Studio now lives in its own repository and consumes gBASIC the way any other application would, which is the more honest arrangement: it gets no privileged access to the platform it helped shape.

Batteries, optional by design

Every native module is detected at build time and compiles out cleanly when absent — a missing library turns one feature into a clear runtime error, never a broken build.

  • Data — SQLite, PostgreSQL, ODBC (one connection string for SQL Server, MySQL, Oracle, DB2 and the rest), the xlsx engine, XML with a constant-memory streaming reader, JSON with a decode that reports failure as a value instead of raising.
  • Network — an HTTP/HTTPS client, and a real HTTP server: TLS with SNI, routing with typed captures, streaming responses, a process worker pool with rolling reload, and request-smuggling defences. A whole site is a server block.
  • Time — precision-carrying datetimes, duration algebra that never blurs months into days, business calendars, date expressions, recurring schedules, SLA arithmetic, and timezone conversion over the system IANA database.
  • Statistics — pure gBASIC: distributions, matrices, OLS and the GLM suite, clustering and PCA, time series through ARIMA and GARCH, survival analysis and Cox regression, meta-analysis, factor analysis, event studies and causal inference, plus data frames.
  • Text — regular expressions as a first-class value kind, binary-safe Unicode strings, and a report-parsing spec language.
  • Charts — pure-gBASIC SVG rendering: line, scatter, area, bar, histogram, pie, heatmap, sparklines; deterministic output, honest gaps, colorblind-safe palette.
  • Desktop — a generic GObject-Introspection bridge, so any GObject library (GTK 4 first among them) is reachable without binding code, with constructors, a virtualizing table and a declarative reconciler layered over it. GUI code is testable without a display: widgets are built and interrogated, never shown.
  • Shipping — an application becomes a .deb carrying its own interpreter and standard library, built lean: a server build drops the desktop toolkit and takes the interpreter from 48 shared libraries to 10, while using the system's OpenSSL and SQLite rather than vendoring them.
  • Crypto and identity — hashing, HMAC, AES-GCM, Ed25519, PBKDF2 and scrypt key derivation, JWT and signed cookies over libcrypto; LDAP bind and search against a directory, where a directory that is down and a password that is wrong are deliberately different answers; and one-time passwords as a second factor, whose replay guard is structural rather than advisory.
  • Mail — composing a message is pure gBASIC and needs no network, so the whole of it is testable without one; the native module owns only the wire.

Evidence, not adjectives

Output is compared byte-for-byte against committed goldens, and the claims on this page are the ones the test suite actually asserts. The spreadsheet engine is scored against Excel's own cached results rather than against itself. The build is exercised in clean containers with every optional dependency present, with none present, and installed to a prefix and run from there — because a conditional-compilation guard is invisible to the compiler you build with, and that is exactly how three defects once survived unnoticed.

Fifteen cookbooks are enforced. Every code block and every output block on those pages is owned by a file the test suite runs and compares byte for byte, so a page cannot drift from the product without a test going red. Spreadsheets, charts, dates & scheduling, money, finance, accounting, lending, deposits, credit, ODBC, and database discovery among them.

That harness pays for itself in a way worth naming. Writing the money cookbook — an ordinary amortization schedule, not a stress case — surfaced a silently wrong answer in money × scalar: a double's shortest decimal can need more fractional places than a power of ten fits in an int64, and every payment in a twelve-month schedule came out zero while a 360-month one worked. The unit tests had all used short scalars. Only realistic worked examples produce a long one.

The full suite runs in CI on x86-64 Linux. riscv64 is a supported target, and the interpreter is also brought up on big-endian s390x — strings are byte-oriented and endian-independent, and a big-endian machine is how you find out whether that is actually true. Windows is supported as of 0.7.0 — a signed installer, a statically linked gbasic.exe that needs nothing beside it, and a suite of its own that runs on every platform so a Windows-only assumption shows up on Linux too. It is built and tested on Windows by hand rather than in CI, which is a weaker guarantee than the Linux figure above and is said plainly for that reason. A few things are deliberately absent there and say so when called: POSIX signals, the prompt's line editor, and the GTK bindings. macOS is not supported and no support for it is claimed.

Status

gBASIC 0.9.0 — an early release, and the version number is honest about that.

0.9.0 fixes a crash you could be hit with from outside. The JSON parser's nesting limit was a fixed 10,000 levels, chosen against the 8 MB stack a desktop Linux gives you. A nesting limit is really a stack limit wearing a count — about 240 bytes a level — so where ulimit -s is nearer 2 MB, as it often is in a container or on a hardened host, the crash came before the limit could refuse. And a request body is decoded as the request is read, whether or not the program ever looks at it, so a 20 KB POST of nested brackets killed a gBASIC server outright. The limit is derived from the actual stack now; nothing changes at the 8 MB default.

Exactness now reaches past the operators. mod(9007199254740993, 2) answered 0 — not a lost digit but the wrong answer, since the odd value became an even double before mod saw it, and a parity test is the commonest thing anyone asks it. min, max, sum, median, abs, floor, ceil, round and pow all dropped a digit above 253; sort could not order two exact integers at all; and decode(encode(x)) lost the value, so a number could be written to a file and not read back from it. All fixed. Below 253 nothing moves, so an ordinary program sees no difference.

One thing changed shape, which is why this is 0.9.0 and not 0.8.1: the gtkui widget reconciler takes a backend argument now, because GTK moved behind a seam. The reconciler no longer needs GTK at all — it loads on the plain download, which has no GUI libraries in it — and a second backend becomes a small job rather than a port.

0.8.0 was about the downloads rather than the language. 0.7.0 shipped a standard library the binary beside it could not run: of 65 libraries, 13 would not load on the -full download and 21 on the plain one. Those numbers are 6 and 14 now, and spreadsheets, XML, SQLite, compression and password hashing are in both — for about 1.4 MB, with the plain download still asking the machine for nothing but libc and libm.

Three things in it change answers a program was already getting, and all three were wrong before. has_builtin — the call a program makes to find out what the interpreter it is running on can do — affirmed sha256, password_hash and compress on downloads that cannot run any of them, so a program that checked first and then called crashed on the branch it had checked for; it answers for the build it is in now. number("…") on integer text is exact, so number("9007199254740992") + 1 no longer comes back one short. And ldap tells a TLS failure from an outage by asking the network rather than by reading a message whose wording belongs to whichever TLS library the distribution happened to build against. One diagnostic changed its text: on a build without zlib, compress said unknown function and now says compression requires zlib.

0.7.0 brought Windows, and with it one fix that changes answers a correct program was already getting: the difference of two datetimes used to consult the machine's time zone, so a day count, an age or a deadline worked out anywhere but UTC could come out a day wrong. In gBASIC's own credit library that put a sixty-days-past-due loan one bucket too healthy, silently, and differently in different offices. A datetime here is civil and carries no zone — which is why epoch(dt, zone) takes one — so the subtraction had no business asking about a zone at all. If you have code quoting an interval, re-check it; code that ran in UTC was always right, which is exactly why this went unnoticed.

0.6.0 was about working with data in bulk, and it was planned by counting what the loops in this tree actually do rather than by taste — 813 of them, classified by the shape of their body. The answer was that the missing piece was smaller than map or filter: every summing loop was adding up a field, and there was no way to name a field across a list. So sum(rows.amount) now works, and five aggregates reached business data for nothing. Beside it: sort on records by one field or several, a excluding b and a intersecting b, exact arithmetic on whole numbers past 253, and mode answering every tied value instead of always handing back the first element.

The 0.5 series before them was a language release. Dates gained a layout notation that reads the way it prints — date parts uppercase and time parts lowercase, so MM is the month and mm the minutes — used for both rendering and parsing, where before there was no output formatting at all. Modifiers chain with ;, any of them now works as a comparison lens, and every shape works inline in an expression rather than only as an assignment clause. It also refuses a date that does not exist: 2026-02-30 used to be accepted, and adding a day to it answered 2026-03-03 — so a date nobody could have meant became a different real date two days later, with nothing raised.

0.5.1 was diagnostics only: three things a reader arriving from QBasic types first now say what to write instead: <> names !=, and let and rem are refused by name — both used to parse as the start of a call, so the error asked for a parenthesis, which is the one change that cannot help. Neither word is reserved, so let is still a legal name for your own function.

The release-candidate run was less about adding features than about closing the ways a program could fail quietly: a frame-scoped error model, a warning channel whose volume is the caller's choice, and a run of fixes for failures that reported nothing at all — a top-level file that stopped halfway and exited 0, a serializer that wrote what its own reader refused, child processes that outlived the interpreter that started them, two compound values that compared equal because the comparison had no branch for them. Since then the work has been the business platform on this page, and the AI stack under it.

It is still early and the language surface may still change; each deliberate break is listed in the changelog with its migration. The known limits are written down and executable — a test suite runs one probe per documented limitation and goes red when one stops being true, naming the entry to strike. That exists because five of fourteen entries were once found to be false, each fixed by a shipped phase in the same tree, and no amount of re-reading had caught it. Live today: no closures, repeated string concatenation is quadratic, and timezone data comes from the host system. (Scientific notation was on that list — 1e20 lexed as a duration, answered 0 seconds and exited 0. It is a literal as of 0.3.0, and the duration path that swallowed it now refuses.) See the changelog, and DOGFOOD.md for the friction log kept while building real software in it.

Licence

gBASIC is Apache-2.0 — all of it. Every file declares that in its own header.

Write gBASIC programs, embed the interpreter, build a product on any of it and ship it closed-source. Nothing here restricts you, and that includes the whole spreadsheet engine, the spreadsheet-to-database pipeline and the SEC/EDGAR analysis suite.

Ten libraries were AGPL until 0.3.0, with a commercial licence offered beside them. That split is retired rather than adjusted: a gBASIC library is source, so load combined it into the caller's own program with no linking exception — which pointed the obligation at the one direction only the caller can see. Contributions are open now too, inbound=outbound, with no contributor agreement to sign.

Full map and the argument in LICENSING.md.

The gBASIC book series

Every listing was run. Every printed release stays available.

These are books built with the same discipline as the language: each code listing is a real file, each transcript records actual output, and each edition names the exact gBASIC release used to produce it.

For new programmers

gBASIC: Learning Programming

Programming from nothing, at a prompt, for somebody who has never written a program. One animal-guessing game grows from three lines in Chapter 1 to a database by Chapter 9. Fourteen chapters.

Paperback · 454 pages · ISBN 9798177530765 · targets gBASIC 0.3.0

Find on Amazon ↗ Companion examples ↓ Pinned 0.3.0 release ↓

For experienced programmers

gBASIC: The Core Language

The whole language in one place: values, records, exact money, calendar-aware time, files, schedules, watchers, and actors. Eighteen chapters of short, runnable examples for building dependable tools.

Paperback · 339 pages · ISBN 9798161798768 · targets gBASIC 0.8.0

Find on Amazon ↗ Pinned 0.8.0 release ↓ Windows 0.8.0 ↓

Why the older downloads remain: the checksums are printed on paper and cannot be corrected after publication. The 0.3.0 and 0.8.0 builds therefore remain first-class releases for readers, while new users should take the latest version offered at the top of this page.

Measured against the current set of 67 standard libraries, the 0.3.0 plain build loads 44 of the 67 and its -full build loads 52 of the 67. For 0.8.0 those figures are 52 of the 67 and 60 of the 67, respectively. Volume 1 uses the 0.3.0 full build; Volume 2 targets 0.8.0.

Errata

None reported yet. That is not a claim that there are none; it is a claim that nobody has told us. If you find something the book got wrong and it is not listed here, please say so — being told is the only way the list gets made, and a reader stuck because a page is wrong has no way of knowing it is the page.

Getting started

Download it and run it. There is no install step, no runtime to fetch, and nothing to set:

tar xzf gbasic-0.9.0-linux-x86_64.tar.gz
gbasic-0.9.0-linux-x86_64/bin/gbasic program.bas

Linux, x86-64, 2.1 MB. It is built against glibc 2.34 in a container on the oldest supported toolchain, so it runs on RHEL, Rocky and Alma 9, Ubuntu 22.04 LTS and later, Debian 12 and later, and anything newer — and the build refuses to publish an artifact whose floor has risen. It finds its own standard library wherever you unpack it.

That archive needs nothing at all, which is why it is the one to take first. It carries the language and 53 of the 67 standard libraries — dates, money, finance, accounting, lending, statistics, charts, frames and the rest, and as of 0.8.0 a real SQL database, spreadsheets, XML, compression and password hashing as well: SQLite, libxml2 and zlib are linked into the binary, and password_hash uses a vendored yescrypt. It still cannot fetch a URL.

If you want a database or the internet

Take gbasic-0.9.0-linux-x86_64-full.tar.gz instead — same language, same libraries, plus HTTP, the web client, email and the crypto builtins all working. 61 of the 67 standard libraries load in it, and the eight it adds over the first download all want the same one thing: libcurl.

It expects libcurl and OpenSSL to be on the machine — present by default on ordinary desktop and server installs, and their sonames are the same across every distribution listed above, which is measured rather than assumed. If one is missing the binary will not start, so on something minimal take the first download. It asks for two fewer libraries than 0.7.0 did, SQLite and zlib having moved inside the binary.

Spreadsheets (xlsx), XML and password_hash used to be in neither download, because libxml2's and libxcrypt's sonames are spelled differently on different distributions and a download linking them would fail to start somewhere. That argument was about dynamic linking and was never re-asked for the static case: as of 0.8.0 both downloads carry all three, libxml2 and zlib linked statically from pinned sources and password hashing from a vendored yescrypt, at no change to the libraries either binary asks the machine for. Each archive ships share/doc/gbasic/THIRD-PARTY-NOTICES.txt, since code compiled in is code redistributed.

Which libraries need which download

A library is shipped whether or not the binary beside it can run it, and load says so at once rather than failing at the first call — XML support is not available in this build. The reference has the table, derived from the libraries' own load lines so it cannot drift out of date. The short version:

  • Either download — 52 libraries. Everything pure gBASIC (dates, money, finance, accounting, lending, deposits, credit, scoring, statistics, charts, frames, notation, fake data, OTP, business rules and the rest), plus everything that needs only what is now compiled in: ownership and the four finio readers for XML, and dbframe and screener for SQLite.
  • The -full download only — 8, and every one of them for the same reason, libcurl: llm, agent, mcp, market, ari_advisor, edgar, insiders and mdna.
  • Neither; build from source — 6. Four need GObject-Introspection (gtk, gtkui, datagrid, sourceeditor), one needs unixODBC (discovery) and one libpq (retrieval). Those four are a deployment problem rather than a packaging one — GTK needs typelibs, icon themes and a display that no binary can carry — which is why a distribution package, not a tarball, is the route to them.

The dependency is transitive, which is why a couple are surprising: mdna reads XML filings and still wants libcurl, because it loads llm; insiders likewise, through edgar.

Two load anywhere and then fail, which is worth knowing because the failure comes at the call rather than at the load: crypto and otp need OpenSSL. On the first download they import cleanly and every function of either answers HMAC requires OpenSSL (libcrypto); rebuild with it installed. They work in the -full download. gpdf was a third until 0.8.0, needing zlib for a compressed stream; zlib is compiled in now, so it works in both.

On Windows

Take gbasic-0.8.0-x64.msix — a signed installer, 4.3 MB, also published as a GitHub release. Note the version: Windows is still on 0.8.0 while Linux above is on 0.9.0. The Windows installer is built and signed by hand on a Windows machine rather than in the Linux release container, so it follows a release rather than arriving with it. Everything 0.9.0 fixed is therefore still outstanding on Windows — including the nesting crash described under Status, which matters most to a server and least to a desktop. gbasic.exe is linked statically, so once installed it needs nothing beside it: no runtime, no redistributable, nothing to put on the PATH by hand.

Windows arrived in 0.7.0 and is a newer surface than the Linux build above. It is tested by its own suite — which runs on every platform, so a Windows-only assumption shows up on Linux too — but that testing is done by hand rather than in CI, so it is a weaker guarantee and worth knowing. Three things are deliberately absent and say so when you call them: POSIX signals, the prompt's line editor (arrow keys and history), and the GTK bindings. The port's status document records what was measured, including the places Windows and Linux genuinely differ.

From source

A C11 compiler, make and bison is the whole list:

git clone https://github.com/solifugus/gbasic.git
cd gbasic
./tools/check-deps.sh      # says what is optional and how to install it
make
./gbasic program.bas

Install it so gbasic is on your PATH and finds its standard library with no environment setup:

sudo make install                  # or: make install PREFIX=$HOME/.local

Optional features are auto-detected via pkg-config; whatever is missing simply turns that feature into a clean runtime error. Start with the tutorial, or the spreadsheet cookbook, whose every example is a real file the test suite runs.

Downloads & installation