diff --git a/modules/septima-lang/README.md b/modules/septima-lang/README.md index 67a0907a..e373657e 100644 --- a/modules/septima-lang/README.md +++ b/modules/septima-lang/README.md @@ -324,6 +324,27 @@ Array.isArray('not array') // Returns false Array.isArray({ key: 'val' }) // Returns false ``` +#### Cryptographic 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. + +```javascript +// 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. diff --git a/modules/septima-lang/change-log.md b/modules/septima-lang/change-log.md index ba7a0ac5..c7d3935a 100644 --- a/modules/septima-lang/change-log.md +++ b/modules/septima-lang/change-log.md @@ -39,3 +39,7 @@ ### PR/174 - allow a dangling comma after last formal arg of an arrow function `(a,b,) => a + b` + +### PR/176 + +- allow computing hash values: `crypto.hash224({a: 1, b: 2})` diff --git a/modules/septima-lang/src/runtime.ts b/modules/septima-lang/src/runtime.ts index a76a5515..2131d6d5 100644 --- a/modules/septima-lang/src/runtime.ts +++ b/modules/septima-lang/src/runtime.ts @@ -1,3 +1,5 @@ +import crypto from 'crypto' + import { AstNode, show, Unit, UnitId } from './ast-node' import { extractMessage } from './extract-message' import { failMe } from './fail-me' @@ -97,6 +99,7 @@ export class Runtime { }) const parse = Value.foreign(o => JSON.parse(o.toString())) + const hash224 = Value.foreign(o => crypto.createHash('sha224').update(JSON.stringify(o.unwrap())).digest('hex')) let lib = new SymbolFrame('Object', { destination: Value.obj({ keys, entries, fromEntries }) }, empty, 'INTERNAL') lib = new SymbolFrame('String', { destination: Value.foreign(o => Value.str(o.toString())) }, lib, 'INTERNAL') @@ -105,6 +108,7 @@ export class Runtime { lib = new SymbolFrame('Array', { destination: Value.obj({ isArray }) }, lib, 'INTERNAL') lib = new SymbolFrame('console', { destination: Value.obj({ log }) }, lib, 'INTERNAL') lib = new SymbolFrame('JSON', { destination: Value.obj({ parse }) }, lib, 'INTERNAL') + lib = new SymbolFrame('crypto', { destination: Value.obj({ hash224 }) }, lib, 'INTERNAL') if (generateTheArgsObject) { lib = new SymbolFrame('args', { destination: Value.from(this.args) }, lib, 'INTERNAL') diff --git a/modules/septima-lang/tests/septima.spec.ts b/modules/septima-lang/tests/septima.spec.ts index bc26e557..ccb200f9 100644 --- a/modules/septima-lang/tests/septima.spec.ts +++ b/modules/septima-lang/tests/septima.spec.ts @@ -1,3 +1,5 @@ +import crypto from 'crypto' + import { Septima } from '../src/septima' /** @@ -1013,6 +1015,22 @@ describe('septima', () => { expect(run(`JSON.parse(5000)`)).toEqual(5000) }) }) + describe('hash224', () => { + const hashOf = (u: unknown) => crypto.createHash('sha224').update(JSON.stringify(u)).digest('hex') + test('can compute hash values of strings', () => { + expect(run(`crypto.hash224('A')`)).toEqual(hashOf('A')) + }) + test('can compute hash values of complex objects', () => { + expect(run(`crypto.hash224({a: 1, b: [{x: 'X'}, ["Y"]]})`)).toEqual(hashOf({ a: 1, b: [{ x: 'X' }, ['Y']] })) + }) + test('the hash changes when the input changes', () => { + expect(run(`crypto.hash224(110002)`)).toEqual(hashOf(110002)) + expect(run(`crypto.hash224(110003)`)).toEqual(hashOf(110003)) + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const [h1, h2] = run(`[crypto.hash224(110002), crypto.hash224(110003)]`) as string[] + expect(h1).not.toEqual(h2) + }) + }) test.todo('sort') // expect(run(`[4, 10, 9, 256, 5, 300, 2, 70].sort()`)).toEqual('--') test.todo('support file names in locations') test.todo('string interpolation via `foo` strings')