Skip to content

CodeforGood1/Basis

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BASIS

BASIS is a deterministic, resource-aware systems programming language for embedded development.

BASIS is designed for firmware and embedded logic that must stay bounded, predictable, and easier to reason about as systems grow. It focuses on classes of problems that often remain hidden until integration or long running field use, such as unclear stack growth, hidden heap usage, unsafe interrupt paths, and foreign calls whose behavior is not explicit.

Today BASIS compiles to C so it can fit into existing toolchains and firmware stacks. Its goal is to provide a constrained programming model where important properties of embedded code can be checked earlier, reported clearly, and integrated into existing C based projects without requiring a completely separate runtime ecosystem.

If you are new to the project, start with LEARN.md for a guided introduction before moving to the full reference documentation.

Why BASIS

Embedded software often compiles successfully long before it becomes easy to trust. As features accumulate, projects can become harder to reason about because resource usage, call depth, interrupt safety, and foreign boundary behavior are spread across multiple files and libraries.

BASIS approaches that problem by making these concerns part of the language and compiler model. Instead of leaving them entirely to manual review and late testing, the compiler performs static checks and resource analysis so more issues can be surfaced before code reaches hardware.

What BASIS Looks Like

#[max_memory(256kb)]

import core::*;
import io::*;

fn main() -> i32 {
    let x: i32 = abs_i32(-42);
    out_i32(x);
    println("");
    return 0;
}

What It Focuses On

  • deterministic, bounded control flow
  • explicit memory budgets per module
  • whole-program call-graph stack analysis
  • bounded heap/resource accounting
  • persistent-storage budgeting
  • interrupt-safe code validation
  • task entry-point validation
  • strict deterministic module profiles
  • rollover-safe time helpers
  • MMIO-oriented volatile register access
  • bit and register helper libraries
  • fixed-size checksum and byte queue libraries
  • explicit foreign-function effect contracts
  • C code generation for host and embedded integration

Implemented Capabilities

BASIS currently provides the following language and compiler capabilities:

  • a complete front end with lexing, parsing, semantic analysis, type checking, constant evaluation, and C code generation
  • deterministic control flow rules with while removed, bounded for loops, and bounded recursion through @recursion(max=N)
  • explicit per-module memory budgets through #[max_memory(...)]
  • whole-program resource analysis covering stack, heap, task stack, persistent storage, estimated code size, and deepest call path
  • strict deterministic profiles through #[strict]
  • interrupt handlers through @interrupt
  • task entry points through @task(stack=N, priority=N)
  • persistent storage budgeting through #[max_storage(...)], #[max_storage_objects(...)], and @storage(...)
  • foreign-function contracts through annotations such as @deterministic, @nondeterministic, @blocking, @allocates, @storage, @reentrant, @uses_timer, @may_fail, and @isr_safe
  • rollover-safe time helpers in stdlib/time
  • volatile MMIO helpers in stdlib/mmio
  • register, checksum, and fixed queue helpers in stdlib/bits, stdlib/crc, and stdlib/ring
  • local examples, negative tests, and a release packaging flow for Windows

BASIS is an embedded systems language under active development, with a working compiler, a static analysis pipeline, a growing standard library, and verified source and packaged workflows.

Standard Library

The current standard library is intentionally small and targeted at deterministic embedded work:

  • core for basic numeric helpers, boolean helpers, assertions, and swaps
  • io for printing and basic host-side input
  • mem for explicit heap and memory operations
  • math for integer math and scaling helpers
  • string for C-style string functions
  • time for rollover-safe tick and deadline logic
  • mmio for volatile register access
  • bits for masks, packed fields, and alignment
  • crc for fixed-size checksum and CRC helpers
  • ring for fixed-size byte queues

Getting Started

Requirements

  • Python 3
  • gcc in PATH for host builds

Clone

git clone https://github.com/CodeforGood1/Basis.git
cd Basis

First Build

python compiler/basis.py build examples/hello.bs --run

Emit C Only

python compiler/basis.py build examples/hello.bs --emit-c

Show Resource Analysis

python compiler/basis.py build examples/callgraph_demo.bs --show-resources --run

Explore Embedded-Oriented Features

python compiler/basis.py build examples/time_demo.bs --run
python compiler/basis.py build examples/task_demo.bs --show-resources --emit-c
python compiler/basis.py build examples/storage_demo.bs --show-resources --emit-c
python compiler/basis.py build examples/mmio_demo.bs --emit-c --target esp32
python compiler/basis.py build examples/bits_demo.bs --run
python compiler/basis.py build examples/crc_demo.bs --run
python compiler/basis.py build examples/ring_demo.bs --run

Verify the Install

powershell -NoProfile -ExecutionPolicy Bypass -File .\tests\run_local_checks.ps1

To also verify the packaged Windows release flow:

powershell -NoProfile -ExecutionPolicy Bypass -File .\tests\run_local_checks.ps1 -VerifyPackage

Build a Windows Release Package

powershell -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -Clean -Package

This creates a versioned distribution directory and ZIP archive containing the compiler, examples, standard library, and documentation.

Optional: Make basis Easy to Run

If you want a shell command instead of calling Python directly, add the repo's compiler folder to your PATH and use the provided launcher script on Windows.

Build as a C-Linked Library

python compiler/basis.py build --lib examples/isr_demo.bs --emit-c --target esp32

This generates C output that can be linked into an existing C or embedded firmware project.

Deployment / Use Modes

There are two main ways to use BASIS today:

1. Host Executable

Use BASIS as a normal compiled language on your machine:

python compiler/basis.py build my_program.bs --run

2. Embedded / C Integration

Use BASIS as a constrained logic layer that compiles to C:

python compiler/basis.py build --lib control.bs --emit-c --target esp32

Then compile the generated C with your existing firmware stack, HAL, or SDK.

For embedded workflows, the generated C is intended to be built by your existing platform toolchain. In practice, BASIS can be used to compile analyzable application logic into C and then linked into an ESP IDF, STM32 HAL, RP2040 SDK, or other C based firmware project once the surrounding platform code is provided.

Key Features

Explicit Memory Budget

Every BASIS file declares a maximum memory budget:

#[max_memory(256kb)]
#[max_memory(32kb)]
#[max_memory(512b)]

Compile-Time Resource Analysis

The compiler reports stack, heap, task-stack, storage usage, code-size estimate, and deepest call path:

======================================================================
RESOURCE ANALYSIS
======================================================================

Program Size Summary:
  Stack (max):         20 bytes
  Heap (total):       512 bytes
  Task stack:        1024 bytes
  Storage use:        256 bytes / 2 objects
  Code (~):           700 bytes
  -------------------------------
  TOTAL:             2256 bytes (2.20 KB)
  Deepest path:  main -> filter -> accumulate
======================================================================

Static Safety Checks

  • array bounds checks
  • bounded recursion checks
  • whole-program stack checks
  • heap size checks
  • persistent-storage budget checks
  • interrupt validation
  • task entry-point validation
  • strict module validation
  • explicit foreign-function contracts

Documentation Index

Document Description
compiler/syntax.md Complete language syntax reference
compiler/safeguards.md Safety guarantees and compile-time checks
compiler/limitations.md Known limitations and workarounds
LEARN.md Guided introduction for new users

Safety Errors

Error Meaning
E_INDEX_OUT_OF_BOUNDS Array access exceeds declared size
E_UNBOUNDED_LOOP Loop without determinable bound
E_WHILE_REMOVED while loops are not part of BASIS
E_MISSING_RECURSION_ANNOTATION Recursive function needs @recursion(max=N)
E_UNBOUNDED_HEAP Allocation size not compile-time constant
E_EXTERN_STACK_REQUIRED extern fn is missing @stack(N)
E_EXTERN_EFFECT_REQUIRED extern fn must declare @deterministic or @nondeterministic
E_EXTERN_ALLOCATES_BUDGET_REQUIRED generic extern fn with @allocates needs @allocates(max=N)
E_STORAGE_CONTRACT_REQUIRED @storage must declare bytes and/or object budgets
E_EFFECT_CONFLICT incompatible effect annotations were combined
E_INTERRUPT_SIGNATURE @interrupt handler has invalid signature
E_INTERRUPT_BLOCKING @interrupt handler calls blocking code
E_INTERRUPT_STORAGE @interrupt handler uses persistent storage
E_TASK_SIGNATURE @task entry point has invalid signature
E_TASK_STACK_REQUIRED @task requires an explicit stack budget
E_TASK_INTERRUPT_CONFLICT function cannot be both @task and @interrupt
E_STRICT_BLOCKING #[strict] module calls blocking code
E_MISSING_RETURN Function must return value on all paths
E_INVALID_RETURN_TYPE Cannot return arrays by value
missing #[max_memory] File lacks memory directive
Program exceeds declared memory budget Total usage > declared max

Examples

Bounded Recursion

@recursion(max=10)
fn factorial(n: i32) -> i32 {
    if n <= 1 { return 1; }
    return n * factorial(n - 1);
}

Heap Allocation

import mem::*;

fn main() -> i32 {
    let buffer: *u8 = alloc_bytes(256 as u32);
    free_bytes(buffer);
    return 0;
}

Array Processing

fn sum(arr: [i32; 5]) -> i32 {
    let total: i32 = 0;
    for i in 0..5 {
        total = total + arr[i];
    }
    return total;
}

Project Structure

compiler/
├── basis.py          # Main compiler driver
├── lexer.py          # Tokenization
├── parser.py         # Recursive descent parser
├── ast_defs.py       # AST node definitions
├── sema.py           # Semantic analysis
├── typecheck.py      # Type checking
├── consteval.py      # Constant evaluation
├── loop_analysis.py  # Loop bound analysis
├── resource_analysis.py  # Resource tracking
├── codegen.py        # Single-file C generation
├── module_codegen.py # Multi-module C generation
└── diagnostics.py    # Error reporting

stdlib/
├── core/core.bs
├── io/io.bs
├── mem/mem.bs
├── math/math.bs
├── string/string.bs
├── time/time.bs
├── mmio/mmio.bs
├── bits/bits.bs
├── crc/crc.bs
└── ring/ring.bs

examples/
├── hello.bs
├── bits_demo.bs
├── crc_demo.bs
├── ring_demo.bs
└── test_io.bs

Appendix: Extern and C ABI Specification

Extern Declaration Syntax

@deterministic @isr_safe @stack(N) extern fn IDENTIFIER(param_list?) -> type;
@deterministic @blocking @stack(N) extern fn IDENTIFIER(param_list?) -> type;
@deterministic @allocates(max=N) @stack(N) extern fn IDENTIFIER(param_list?) -> type;
@nondeterministic @blocking @stack(N) extern fn IDENTIFIER(param_list?) -> type = "symbol_name";
param_list ::= param ("," param)*
param ::= IDENTIFIER ":" type
  • Allowed types: i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool, void return; raw pointers to any allowed type; pointers to structs; structs by value only if trivially C-compatible.
  • Forbidden in extern signatures: arrays, slices, function pointers, opaque handles, managed/high-level types.
  • Variadic functions: forbidden.
  • @stack(N) is required on every extern so the whole-program call graph stays bounded.
  • Every extern must declare exactly one determinism contract: @deterministic or @nondeterministic.
  • @blocking, @allocates(max=N), and @isr_safe are optional refinements.
  • @isr_safe cannot be combined with @blocking, @allocates, or @nondeterministic.
  • Bare @allocates is only valid for compiler-known allocator models such as malloc; generic foreign allocators must use @allocates(max=N).

Name Binding Rules

  • Default symbol name = BASIS identifier, case-sensitive, no mangling.
  • Aliasing: optional = "symbol_name" binds to the exact C symbol literal.
  • No implicit decoration or namespaces.

Calling Convention

  • Always platform C ABI.
  • Parameters passed per platform C ABI; no hidden args.
  • Returns: scalars/pointers per C ABI; structs by value only if ABI supports it (else rejected). Hidden sret may be used by ABI; if unsupported, reject.
  • No exceptions/unwinding across the boundary.

Memory & Ownership Rules

  • No implicit allocation or freeing by the compiler.
  • No ownership transfer by default; caller retains ownership of pointers passed/received unless explicitly documented externally.
  • Foreign heap behavior is explicit: @allocates(max=N) contributes to heap budgets, and compiler-known allocators are modeled directly at call sites.
  • Blocking behavior is explicit: @blocking propagates through the call graph and disqualifies ISR use.
  • Determinism across extern boundaries is explicit: @deterministic / @nondeterministic propagate through the call graph.

Header Emission Rules

  • Headers generated only for non-entry modules; entry (main) emits no header.
  • Public externs appear in headers as non-static prototypes; private externs only in .c as static prototypes.
  • Headers contain declarations only, never bodies. Own guard first, then standard includes, then imported module headers in lexicographic order.

Symbol Visibility

  • public fn -> prototype in header, definition in .c (non-static).
  • private fn -> no header prototype; definition in .c as static.
  • extern fn -> prototype only; no definition. Public externs non-static in header; private externs static in .c. Entry main is never extern/static and no header is emitted for it.

Diagnostics

  • E_EXTERN_TYPE: invalid extern type in signature.
  • E_EXTERN_ALIAS: invalid extern alias; expected string literal.
  • E_EXTERN_VARIADIC: variadic externs are not supported.
  • E_EXTERN_STRUCTRET: struct return not supported by target ABI.
  • E_EXTERN_BODY: extern functions must not have bodies.
  • E_EXTERN_EFFECT_REQUIRED: extern functions must declare @deterministic or @nondeterministic.
  • E_EXTERN_ALLOCATES_BUDGET_REQUIRED: generic foreign allocators must use @allocates(max=N).
  • E_EFFECT_CONFLICT: incompatible effect annotations were combined.

PART B — Compiler Responsibilities

  • Store extern declarations (with optional alias) in symbol table.
  • Type check externs like declared functions without bodies; validate parameter/return types.
  • Exclude extern bodies from resource/loop analyses; analyze call sites using explicit effect contracts.
  • Externs do not create module import dependencies.
  • Emit C prototypes respecting visibility and aliasing; no bodies generated.

PART C — Code Generation Rules

  • Lowering: extern fn foo(a: i32) -> i32; -> int32_t foo(int32_t a);
  • Aliasing: extern fn local(a: i32) -> i32 = "c_symbol"; -> int32_t local(int32_t a) __asm__("c_symbol"); (or equivalent alias mechanism; if unavailable, emit prototype named c_symbol).
  • Struct pointer extern: extern fn process(s: *MyStruct) -> i32; -> int32_t process(struct MyStruct* s);
  • No stubs/wrappers/thunks; direct calls only; zero overhead.

PART D — Non-Goals (Forbidden)

  • C++ ABI or name mangling.
  • Automatic/reflective bindings.
  • Implicit headers beyond stated rules.
  • Runtime shims/trampolines/thunks.
  • Foreign exception propagation or unwinding across the boundary.

PART E — Implementation Checklist

  • Extern symbols recorded in AST and symbol table with alias info.
  • Externs participate in type checking; bodies forbidden.
  • Externs excluded from resource/loop analyses beyond call-site effects.
  • Extern effects participate in determinism, blocking, heap, and ISR-safety propagation.
  • Public externs -> prototypes in headers; private externs -> static prototypes in .c.
  • No code emitted for extern bodies; zero-overhead direct calls.
  • ABI-compatible prototypes generated with correct C types and calling convention.
  • Diagnostics enforced: E_EXTERN_TYPE, E_EXTERN_ALIAS, E_EXTERN_VARIADIC, E_EXTERN_STRUCTRET, E_EXTERN_BODY, E_EXTERN_EFFECT_REQUIRED, E_EXTERN_ALLOCATES_BUDGET_REQUIRED, E_EFFECT_CONFLICT.

About

Basis is a deterministic systems programming language tailored for embedded environments. Its primary objective is guaranteeing predictability and resource safety before code reaches deployment. The language eliminates unexpected runtime crashes by rigidly managing memory, providing a foundation for highly reliable and consistently stable firmware.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors