septima-lang
v0.4.2
Published
Septima is a programming language that closely follows JavaScript, not just in syntax but also in behavior. If you're familiar with JavaScript, you'll feel right at home with Septima's objects, arrays, functions, and built-in methods. However, Septima mak
Downloads
42
Readme
Septima: An Overview
Septima is a programming language that closely follows JavaScript, not just in syntax but also in behavior. If you're familiar with JavaScript, you'll feel right at home with Septima's objects, arrays, functions, and built-in methods. However, Septima makes some deliberate departures from JavaScript to promote cleaner, more predictable code:
- It is immutable - variables cannot be reassigned after definition
- Side effect free - a computation is only affected by its inputs. The only "trace" that a computation leaves is the value that it computed.
- All expressions, including
if...else, return values - There are no
nullvalues - onlyundefined - There's no automatic type coercion
- No global scope or
varkeyword - only lexical block scoping withlet - No classes or prototypes - You can create objects using JavaScript's standard object notation (
{a: 'foo'}).
Why Septima?
Septima excels in scenarios where you need to safely execute user-provided code within your application, particularly in backend systems. Common use cases include:
- Configuration as Code: Allow users to write complex configuration logic using a familiar JavaScript-like syntax instead of being limited to static JSON or YAML files
- Business Rules Engines: Enable domain experts to define and maintain business rules in a familiar syntax without risking system stability
- ETL Transformations: Let users write data transformation logic that can safely process data without accessing external systems
- Plugin Systems: Implement extensible architectures where third-party code can be safely executed in a controlled environment
- Customizable Workflows: Allow users to define custom workflow logic while ensuring system security
Key benefits that make Septima ideal for these scenarios:
Secured:
- No file system or network access
- No
eval()or dynamic code execution - No access to system resources
Reliable:
- Functional programming paradigm (no loops) makes code more robust
- Immutability and lack of side effects make code easier to reason about
- No type coercion reduces unexpected behavior
- Modules allow you to organize and manage large Septima codebases
Friendly:
- JavaScript-like syntax reduces learning curve
- Highly compatible with JavaScript - code ports easily in both directions
Unlike alternatives such as JavaScript's vm module or eval(), Septima provides a secure environment for running user-provided code without sacrificing expressiveness or ease of use. It strikes a balance between power and safety that makes it particularly well-suited for enterprise applications where reliability and security are paramount.
Table of Contents
Language Fundamentals
Like JavaScript, Septima works with familiar data types like numbers, strings, and booleans. However, its treatment of these types is more strict and predictable than JavaScript's loose type handling.
Numbers and Arithmetic
Numbers in Septima work similarly to JavaScript, but without the quirks. There's no automatic type coercion in arithmetic operations.
// Similar to JavaScript
5 // Integer literal
3.14 // Floating point literal
8 * 2 // Multiplication
3 + 1 // Addition
// Different from JavaScript - no type coercion
'5' + 3 // Error: Cannot add string and number
5 + '3' // Error: Cannot add number and stringBooleans and Logic
Boolean operations in Septima are similar to JavaScript but stricter. They only work with actual boolean values.
// Similar to JavaScript
true || false // Logical OR
true && false // Logical AND
// Different from JavaScript - no truthy/falsy values
1 && 2 // Error: Expected boolean values
'' || 'default' // Error: Expected boolean valuesControl Flow and Expressions
Unlike JavaScript, all control structures in Septima are expressions that return values.
// Different from JavaScript - if expressions return values
let result = if (4 > 3) 200 else -100 // Valid in Septima
// Different from JavaScript - no if statements without else
if (x > 0) doSomething() // Error in Septima - must have else
// Ternary operator works the same as JavaScript
let result = (4 > 3) ? 200 : -100Variables and Immutability
One of the biggest differences from JavaScript is Septima's immutability. Variables cannot be reassigned after definition.
// Similar to JavaScript - initial definition
let x = 5
// Different from JavaScript - no reassignment
x = 6 // Error: Cannot reassign variables
// Different from JavaScript - no var or const
var y = 10 // Error: var is not supported
const z = 15 // Error: const is not supportedArrays and Objects
Arrays and objects in Septima are immutable by default, unlike their mutable JavaScript counterparts.
// Similar to JavaScript - creation and access
let arr = [1, 2, 3]
arr[0] // Returns 1
// Different from JavaScript - no mutation methods
arr.push(4) // Error: Arrays are immutable
arr[0] = 2 // Error: Arrays are immutable
// Object behavior
let obj = { a: 1, b: 2 }
obj.a // Returns 1
// Different from JavaScript - no mutation
obj.c = 3 // Error: Objects are immutable
obj.a = 2 // Error: Objects are immutableConversions
Unlike JavaScript's automatic type coercion, Septima requires explicit conversion between different types. It provides three conversion functions that closely mirror their JavaScript counterparts in behavior.
The String function converts any value to its string representation:
String(42) // "42"
String(3.14) // "3.14"
String(true) // "true"
String(undefined) // "undefined"For objects and arrays, String produces a JSON representation of its argument:
String({ a: 1 }) // "{"a":1}"
String([1, 2]) // "[1,2]"The Boolean function implements standard truthiness rules, converting values to true or false:
Boolean(42) // true
Boolean(0) // false
Boolean('hello') // true
Boolean('') // false
Boolean(undefined) // false
Boolean({}) // true
Boolean([]) // trueThe Number function converts values to numbers where possible, returning NaN when conversion fails:
Number('42') // 42
Number('3.14') // 3.14
Number('abc') // NaN
Number(true) // 1
Number(false) // 0
Number(undefined) // NaNUnlike JavaScript, Septima requires these explicit conversions and does not perform automatic type coercion:
"42" + 7 // Error: Cannot add string and number
7 + "42" // Error: Cannot add number and string
let x = if ("hello") 1 else -1 // Error: Condition must be booleanCoding in Septima
Functions
Functions in Septima are similar to JavaScript arrow functions, but with some key differences in scope and purity.
// Similar to JavaScript - arrow functions
const double = x => x * 2
// Different from JavaScript - no function declarations
function double(x) {
return x * 2
} // Error: Use arrow syntax
// Different from JavaScript - pure functions
let counter = 0
let increment = () => counter++ // Error: Cannot modify external state
// Different from JavaScript - no this binding
let obj = {
name: 'test',
getName: function () {
return this.name
}, // Error: No this keyword
}Extended Operators
Spread Operator (...)
The spread operator creates shallow copies of arrays and objects:
// Objects
let user = { name: 'Sam', id: 123 }
let userWithRole = { ...user, role: 'admin' } // { name: 'Sam', id: 123, role: 'admin' }
// Arrays
let numbers = [1, 2, 3]
let moreNumbers = [...numbers, 4, 5] // [1, 2, 3, 4, 5]Nullish Coalescing (??)
The nullish coalescing operator provides a way to handle undefined values:
let config = {
port: undefined,
host: 'localhost',
}
let port = config.port ?? 8080 // Returns 8080 (fallback when undefined)
let host = config.host ?? 'default' // Returns 'localhost' (keeps defined value)Note: Unlike JavaScript, Septima doesn't have null, so the nullish coalescing operator only works with undefined.
Built-in Methods
Septima provides common methods for working with arrays, strings, and objects. These methods follow JavaScript conventions while maintaining Septima's no-side-effects guarantee. As such, methods such as Array.push() are not supported.
Modifying Arrays
Instead of mutating methods like push() use the spread operator to create new arrays:
let nums = [1, 2, 3]
nums.push(4) // Error: Array.push is not supported
[...nums, 4] // Returns [1, 2, 3, 4]Array and String Methods
Transform strings and arrays using familiar methods (non-exhaustive list):
// String methods
'hello'.toUpperCase() // Returns "HELLO"
'hello_world'.split('_') // Returns ['hello', 'world']
' piano '
.trim() // Returns "piano"
[
// Array methods
(1, 2, 3)
].map(x => x * 2) // Returns [2, 4, 6]
[(19, 6, 8, 3, 10)].filter(x => x > 5) // Returns [19, 6, 8, 10]
[(1, 2, 3, 4)].reduce((a, b) => a + b, 0) // Returns 10Object Methods
Access and transform object properties and structure:
let user = {
name: 'Sam',
role: 'admin',
active: true,
}
// Get object keys as array
Object.keys(user) // Returns ['name', 'role', 'active']
// Get key-value pairs as array
Object.entries(user) // Returns [['name', 'Sam'], ['role', 'admin'], ['active', true]]
// Create object from key-value pairs
let pairs = [
['name', 'Pat'],
['role', 'user'],
]
Object.fromEntries(pairs) // Returns { name: 'Pat', role: 'user' }Array.isArray()
Checks whether the given input is an array:
Array.isArray([1, 2, 3]) // Returns true
Array.isArray('not array') // Returns false
Array.isArray({ key: 'val' }) // Returns falseCryptographic Hashing
Septima provides a secure hashing function through the crypto.hash224() method, which computes SHA-224 hashes of any value. The method takes a single argument of any type and returns a hexadecimal string representing the hash.
// Hash a simple string
crypto.hash224('hello') // Returns a 56-character hex string
// Hash numbers
crypto.hash224(42) // Hashes the number 42
// Hash complex objects
crypto.hash224({ name: 'Alice', roles: ['admin', 'user'], settings: { theme: 'dark' } }) // Hashes the entire object structure
// Hashes are deterministic but unique per input
crypto.hash224('A') === crypto.hash224('A') // true (same input = same hash)
crypto.hash224('A') !== crypto.hash224('B') // true (different input = different hash)Note: Septima's crypto object is not intended to be compatible with Node.js's crypto module. It provides its own simplified cryptographic utilities specifically designed for Septima's use cases.
Console Output for Debugging
Like JavaScript, Septima provides console.log() for debugging and monitoring your code. However, unlike JavaScript's console.log() which can take multiple arguments, Septima's version accepts only a single argument. In keeping with Septima's functional nature, console.log() returns its argument, making it useful within expressions.
// Basic logging - single argument only
console.log('Hello World') // Prints: Hello World
// Different from JavaScript - can't log multiple values
console.log('x', 42) // Error: Too many arguments
// Logging within expressions works because console.log returns its argument
let result = 5 * console.log(3) // Prints: 3, returns: 15
// Logging with arrays and objects
let arr = [1, 2, 3]
console.log(arr) // Prints: [1, 2, 3]
let obj = { a: 1, b: 2 }
console.log(obj) // Prints: {"a":1,"b":2}
// To log multiple values, you need to combine them first
console.log([x, y, z]) // Use an array
console.log({ x: x, y: y, z: z }) // Or an object
console.log('x=' + x + ', y=' + y) // Or string concatenationModules
Septima provides a module system for organizing code across files, supporting exports and namespace imports.
Exports
Use the export keyword to expose values from a module:
// utils.septima.js
export let capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
// config.septima.js
export let timeoutInSeconds = 30
export let retries = 3Key points:
- Only top-level definitions can be exported
- Multiple exports per file are allowed
- No default exports
Imports
Import using the namespace pattern:
import * as utils from './utils.septima.js'
import * as config from './config.septima.js'
// Use imported values
let result = utils.capitalize('hello')
let timeoutInMillis = config.timeoutInSeconds * 1000Key points:
- Files must end with
.septima.js - Only namespace imports (
* as) supported - Imports must be at file top
- Paths are relative to current file
- No circular dependencies
Error Handling
Unlike JavaScript's sometimes forgiving nature, Septima is strict about type checking and provides clear error messages. It doesn't do automatic type coercion or silent failures.
// JavaScript allows this
'5' + 3 // "53"
// Septima throws an error
'5' + 3 // Error: Cannot add string and number
// JavaScript returns undefined
let obj = {}
obj.nonexistent.prop // TypeError: Cannot read property 'prop' of undefined
// Septima provides better error message
obj.nonexistent.prop // Error: Attempting to access property of undefined