Skip to main content
Porffor has a unique type system that includes both standard JavaScript types and internal types optimized for performance and memory efficiency.

JavaScript Types

Porffor supports the standard JavaScript primitive types:
// number (stored as f64)
let x = 42;
let y = 3.14;

// boolean
let flag = true;

// string (UTF-16, 2 bytes per character)
let str = "hello";

// undefined
let undef = undefined;

// bigint
let big = 123n;

// symbol
let sym = Symbol('key');

Type Representation

Internally, types are represented as numeric identifiers defined in compiler/types.js:8-16:
export const TYPES = {
  undefined: 0x00,
  number: 0x01,
  boolean: 0x02,
  string: 0x03 | TYPE_FLAGS.length,
  bigint: 0x04,
  symbol: 0x05,
  function: 0x06,
  object: 0x07
};
Each value in Porffor is represented as a pair: (value, type) where type is an i32 type identifier.

Internal Types

Porffor introduces several internal types for performance optimization:

ByteString

The most important internal type. Understanding ByteStrings is crucial for working with Porffor.
ByteString is an optimized string type that uses 1 byte per character instead of 2 bytes (UTF-16).

When to Use ByteString

ByteStrings are used when a string contains only ASCII/LATIN-1 characters (code points 0-255):
// Regular String (UTF-16, 2 bytes/char)
let emoji = "hello 👋";  // Contains emoji, must use String
let chinese = "你好";     // Contains Chinese, must use String

// ByteString (1 byte/char)
let ascii = "hello";     // ASCII only, can use ByteString
let url = "https://example.com"; // ASCII, can use ByteString

Memory Savings

let str: string = "hello"; // 5 chars × 2 bytes = 10 bytes
ByteStrings halve memory usage for ASCII strings and make operations faster due to less memory access.

Implementation Details

From CONTRIBUTING.md:36-38:
Regular strings in Porffor are UTF-16 encoded (2 bytes per character). ByteStrings use 1 byte per character for ASCII/LATIN-1, halving memory usage. Many builtins must be written twice—once for String and once for ByteString.

i32

The i32 type represents a 32-bit integer, primarily used for pointers.
From CONTRIBUTING.md:40-42: Only use i32 for pointers. It represents the WebAssembly i32 valtype, which is neither signed nor unsigned—signedness is instruction-dependent.
// Type definition from compiler/builtins/porffor.d.ts:1
export type i32 = number;

function example() {
  // Get pointer to a string
  let ptr: i32 = Porffor.wasm`local.get ${myString}`;
  
  // Use pointer for memory operations
  let charCode: i32 = Porffor.wasm.i32.load8_u(ptr, 0, 4);
}

f64

The f64 type represents a 64-bit floating-point number (IEEE 754 double):
// Type definition from compiler/builtins/porffor.d.ts:3
export type f64 = number;

// This is the default valtype in Porffor
let x: f64 = 3.14159;
let y: f64 = 42; // Integers are also f64 by default
By default, Porffor uses f64 as its valtype. You can change this with --valtype=i32 to use 32-bit integers instead, but this is experimental.

i64

The i64 type represents a 64-bit integer:
// Type definition from compiler/builtins/porffor.d.ts:2
export type i64 = number;

// Used for bigint operations and large integers
let big: i64 = 9007199254740991;

Object Types

Porffor includes many internal object types registered in compiler/types.js:43-73:
// Internal object types
registerInternalType('Array', ['iterable', 'length']);
registerInternalType('RegExp');
registerInternalType('Date');
registerInternalType('Set', ['iterable']);
registerInternalType('Map');

// TypedArrays
registerInternalType('Uint8Array', ['iterable', 'length']);
registerInternalType('Int32Array', ['iterable', 'length']);
registerInternalType('Float64Array', ['iterable', 'length']);
// ... and more

// Error types  
registerInternalType('Error');
registerInternalType('TypeError');
registerInternalType('RangeError');
// ... and more

Type Annotations

Porffor can parse and use TypeScript type annotations for optimization.

Parsing Types

Enable type annotation parsing:
porf --parse-types script.ts
If --parse-types is used without specifying a parser, Porffor automatically switches to @babel/parser which supports TypeScript syntax.

Type-Guided Optimization

Use type annotations as compiler hints:
porf --parse-types --opt-types script.ts
// Compiler knows these are numbers, generates optimized code
function add(a: number, b: number): number {
  return a + b;
}

function processString(s: bytestring): i32 {
  return s.length;
}
Porffor does not type-check your code. Type annotations are purely used as compiler hints for optimization. Invalid types won’t cause errors at compile time.

Type Flags

Types can have flags that indicate properties:
export const TYPE_FLAGS = {
  parity:    0b10000000,  // ByteString vs String
  length:    0b01000000,  // Has .length property
};
For example:
TYPES.string = 0x03 | TYPE_FLAGS.length;     // String has length
TYPES.bytestring = TYPES.string | TYPE_FLAGS.parity; // ByteString variant

Working with Types in Built-ins

Type Checking

From compiler/builtins/porffor.d.ts:103-104, use the Porffor.type() function:
function myBuiltin(arg: any) {
  const argType: i32 = Porffor.type(arg);
  
  if (argType == Porffor.TYPES.number) {
    // Handle number
  }
  if (argType == Porffor.TYPES.bytestring) {
    // Handle ByteString
  }
}

Dual String Implementation

Many builtins need both String and ByteString versions:
// From CONTRIBUTING.md:109-128
export const __ByteString_prototype_toUpperCase = (_this: bytestring) => {
  const len: i32 = _this.length;

  let out: bytestring = '';
  Porffor.wasm.i32.store(out, len, 0, 0);

  let i: i32 = Porffor.wasm`local.get ${_this}`,
      j: i32 = Porffor.wasm`local.get ${out}`;

  const endPtr: i32 = i + len;
  while (i < endPtr) {
    let chr: i32 = Porffor.wasm.i32.load8_u(i++, 0, 4); // 1 byte
    if (chr >= 97) if (chr <= 122) chr -= 32;
    Porffor.wasm.i32.store8(j++, chr, 0, 4); // 1 byte
  }

  return out;
};
The key difference: ByteString uses load8_u/store8 (1 byte), while String uses load16_u/store16 (2 bytes).

Type Annotation Requirements

When writing Porffor code (especially builtins), from CONTRIBUTING.md:200:
You must use explicit type annotations for variable declarations. This will fail:
let a = 1; // ❌ Error
Use this instead:
let a: number = 1; // ✅ Correct

Valtype Selection

Choose the default value type for your program:
porf script.js
# or explicitly:
porf --valtype=f64 script.js
Uses 64-bit floats for all numeric values. Best for general-purpose code.

Next Steps

Memory Model

Learn how types are stored in memory

Writing Built-ins

Create your own built-in functions

Wasm Operations

Use Porffor.wasm for low-level operations

Compiler Options

Type-related compiler flags

Build docs developers (and LLMs) love