POCA is JavaScript/ECMAScript-like, but it’s not the same. It has some differences and even a few features that ECMAScript doesn’t have.
This document shows the features of POCA. POCA is a very powerful scripting language, that uses some of the concepts coming from ECMAScript, Lua, Squirrel, Nasal, Python, and Perl. POCA supports object-oriented programming (OOP) in two flavours (prototype-based and class-based), as well as functional programming and procedural programming styles.
In POCA everything is an expression; there are no statements, so for example something like
var a = scope {
var b = 1;
for (let c = 0; c < 16; c++) {
b += c + c
};
b;
};and
var a = if (b < 1) {
1
} else if (b < 4){
2
} else {
3
};is valid POCA code.
People familiar with other programming languages, especially scripting languages such as JavaScript/ECMAScript in particular, are usually able to learn POCA fairly quickly.
POCA uses a frame stack, infinite register, bytecode instruction set architecture (ISA).
Unlike almost all other script interpreters, POCA is thread-safe and scalable when called from multiple CPU threads. No special handling is required and the threads can be scheduled concurrently. There is no global lock (GIL) on the g interpreter or the x86 just in time (JIT) compiler execution engine. The only limit to scalability is the single-threaded incremental and generational garbage collector, which must block all bytecode interpreter threads and CPU native code threads compiled by the JIT compiler before executing.
POCA’s API design concept is more or less similar to Lua’s. This means that concepts like hash tables (hashes), meta tables (hash table events), vectors (arrays), etc. exist here too.
POCA supports structured programming in the style of C. POCA supports function
and block scoping with the keywords var, let and const. POCA requires explicit
semicolons, as automatic semicolon injection can introduce hard-to-detect bugs.
The only exception is that you can omit the last semicolon in a function,
scope, or code block.
As with C-like languages, control flow can be achieved using while, for,
do / while, if / else, and switch / case statements. Functions are
weakly typed and can accept and return any type. Unspecified arguments default
to undefined. In addition to for, POCA also supports foreach, which can
iterate over ranges (arrays).
POCA, like ECMAScript/JavaScript, is weakly typed. This means that in most, but not all cases, certain types are implicitly assigned based on the operation being performed, avoiding some typical JavaScript/ECMAScript quirks.
POCA is dynamically typed. Thus, a type is associated with a concrete value and not with an expression. POCA supports several ways to test the type of objects, including duck typing.
In POCA, everything is an expression, even statements, declarations and definitions. So the following code snippet is valid:
let a = scope{let b = 0; for(let c = 0; c < 6; c++){ b++ }; b; };
let bla = if(a != 0){ 1 }else{ a ? 2 : 3 };POCA essentially maps the mathematical concept and expression of "something" into a complete scripting language.
POCA supports lexical scoping, meaning that a variable’s scope is determined by its position in the source code.
Variables declared with var are local-scoped; they are accessible only within
the scope in which they are declared and any nested scopes. This behavior is
similar to JavaScript’s function-scoped variables declared with var. Such
variables are stored in a local hash table and can be accessed from the current
frame as well as all nested frames.
In contrast, variables declared with let and const are block-scoped. Unlike
var, they are bound to the block in which they are declared, meaning their
visibility and lifetime are limited to that block and any nested blocks.
This behavior is similar to JavaScript’s block-scoped variables declared with
let and const. These block-bound variables are
not stored in the function’s normal local variable hash table (the structure
used for var variables). Instead, they reside in
specialized fast-access frame variable storages that are tied to the current
execution frame. This design provides quick access to block-scoped variables and
keeps them isolated per block. The engine manages these storages using a
display-list mechanism (a structure that maintains a list of active lexical
environments by nesting level), which allows nested blocks or inner functions to
efficiently reference variables from their enclosing blocks, even beyond the
immediate block context for closures. This is similar to how ECMAScript/JavaScript also
optimizes variable storage between registers and the stack, except that ECMAScript/JavaScript
uses environment objects for closures, while POCA uses a display-list mechanism based
on a chain of context frames, where every frame can have its own display list.
This frame-based storage is utilized whenever a block-scoped variable needs to
exist beyond the immediate block execution or be accessible from an inner scope.
For example, if an inner function (closure) is defined inside a block and captures
a let/const variable, that variable will be placed in the frame storage so the
inner function can access it later. In such cases (or generally when the function
contains any nested scopes), the compiler ensures the variable lives in the frame
storage. On the other hand, if a block’s variables are purely local (used only
within the block and not needed by any outside code or closures), POCA may optimize
by keeping them in registers instead of in the frame storage. Using registers
for strictly local variables avoids the overhead of managing an extra storage
structure and yields faster access. In summary, let and const variables are
confined to their block and kept out of the general local table, living either
in a dedicated frame storage (when necessary for scope access or lifetime) or
directly in registers for maximal performance in self-contained cases.
let and const have higher priority than var in variable resolution. When
the same identifier is used for both a let/const variable and a var variable,
the let/const binding takes precedence within its scope, effectively shadowing
the var variable. Variables in inner scopes shadow variables in outer scopes
following standard lexical scoping rules - the most local binding wins regardless
of whether it’s declared with var, let, or const. The compiler resolves let
and const bindings at compile time through static analysis of the lexical scope,
providing fast access through registers or frame storage. In contrast, var
variables are stored in a hash table and resolved at runtime, allowing for dynamic
behavior but with the overhead of hash table lookups.
Example:
let a = 1;
const b = 2;
function Func1(){
let c = 3;
scope{
let c = 4;
puts(c);
}
// c is 3 here and not 4
// because c is block-scoped
// and not function-scoped
var d = 4; // d is function-scoped
function Func2(){
let e = 5;
scope{
let e = 6;
puts(e);
}
// e is 5 here and not 6
// because e is block-scoped
// and not function-scoped
return a + b + c + d + e;
}
Func2();
}
Func1();This approach ensures that let and const variables occupy the specialized fast-access
frame variable storages only when necessary, reducing overhead, much like how
ECMAScript/JavaScript optimizes variable storage between registers and the stack.
POCA also supports closures, allowing functions to capture and remember the environment in which they were created, even if that environment is no longer in scope. This enables powerful programming techniques such as maintaining state or encapsulating private variables. Note that closures in POCA behave slightly differently from those in ECMAScript/JavaScript: in POCA, closures capture variables at the time the function is defined rather than when the block is exited. For example, consider the following code:
let a = 1;
let t = new Array();
for(let i = 0; i < 10; i++){
let b = i;
t.push(
function(){
return a + b + i;
}
);
}
for(let i = 0; i < 10; i++){
puts(t[i]());
}This code outputs:
20
20
20
20
20
20
20
20
20
20Here, the variable i is captured when the function is defined, causing each closure
to reference the same (final) value of i. In contrast, ECMAScript/JavaScript typically
captures the variable when the block is exited. To create a closure that captures the
current value of i in POCA, you can use an inline function to create a new scope:
let a = 1;
let t = new Array();
for(let i = 0; i < 10; i++){
t.push(
function(i){
let b = i;
return function(){
return a + b + i;
}
}(i)
);
}
for(let i = 0; i < 10; i++){
puts(t[i]());
}This code outputs:
1
3
5
7
9
11
13
15
17
19In this case, the inline function creates a new scope that captures the current
value of i, resulting in each closure maintaining its own copy of i. This
behavior is similar to how closures are handled in Python, where variables are
captured at function definition time.
Below is a Python script that demonstrates the "freeze effect" in closures, with print statements that refer to the POCA behavior:
# Without using default arguments:
# All closures refer to the final values of 'i' and 'b'.
a = 1
closures_without_freeze = []
for i in range(10):
b = i
closures_without_freeze.append(lambda: a + b + i)
print("Without freeze (no default arguments), like at POCA without inline function:")
for func in closures_without_freeze:
# Since 'i' and 'b' end with the value 9, each call returns: 1 + 9 + 9 = 19.
print(func())
# Using default arguments to capture (freeze) the current values at function definition time:
closures_with_freeze = []
for i in range(10):
b = i
# Here, 'i=i' and 'b=b' freeze the current values.
closures_with_freeze.append(lambda i=i, b=b: a + b + i)
print("\nWith freeze (using default arguments), like at POCA with inline function:")
for func in closures_with_freeze:
print(func())
### Explanation
#
# - **Without Freeze:**
# The closures are created without default arguments, so they capture `i`
# and `b` by reference. When the loop ends, both `i` and `b` have the final
# value of 9, and every closure returns the same result (19). This is analogous
# to POCA code that doesn’t use an inline function to create a new scope.
#
# - **With Freeze:**
# Using default arguments (`i=i, b=b`), each lambda captures the current values
# of `i` and `b` at the time it is defined. This “freezes” the values, similar
# to how an inline function in POCA creates a new scope, ensuring that each
# closure maintains its own copy of `i` and `b`.
#
# This script clearly illustrates the difference in behavior between closures that
# do not freeze variable values and those that do.Finally, POCA supports the scope and code keywords to create explicit scopes.
The scope keyword creates a new scope, while the code keyword creates a new
code block without introducing a new scope. This provides additional control over
variable visibility and lifetime.
By default, POCA captures loop variables by reference, meaning all closures created inside a loop share the same binding and see the final value of the loop variable after the loop completes. This is the classic behavior described above.
With the loopclosures pragma, POCA can instead give each loop iteration its own
copy of the captured variables. Closures created in different iterations then each
see the value from their own iteration, rather than all sharing the final value.
Without loopclosures (default behavior):
let fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i);
}
puts(fns[0]()); // 3
puts(fns[1]()); // 3
puts(fns[2]()); // 3All three closures capture the same i, which ends up as 3 after the loop.
With loopclosures on:
#pragma loopclosures on
let fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i);
}
puts(fns[0]()); // 0
puts(fns[1]()); // 1
puts(fns[2]()); // 2Each iteration gets its own copy of i, so each closure remembers the value
from its iteration.
The pragma can be toggled on and off at any point in the source:
#pragma loopclosures on // enable per-iteration capture
#pragma loopclosures off // disable (back to default shared capture)It can also be used in the function-call form:
#pragma("loopclosures on")The pragma applies to for, foreach, forindex, forkey, while, and
do/while loops. It only adds overhead in loops whose body actually contains
nested function definitions (closures); loops without closures are unaffected.
Since per-iteration copying can have a performance cost (each iteration allocates a snapshot of the captured variables), the pragma is disabled by default. Enable it only where you actually need per-iteration capture semantics.
|
Note
|
Variables declared outside the loop (e.g. a counter declared before a
while loop) are still shared across iterations, since they belong to the
enclosing scope. Only the loop’s own iteration variables and body-scoped
let/const declarations get per-iteration copies.
|
An important distinction in POCA is that let and const follow static scoping rules based
on declaration order, while var uses dynamic scoping. This difference has significant
implications for when and how variables can be accessed:
-
Static scoping (
let/const): Variables must be declared in the source code before they can be referenced. The compiler performs static analysis to resolve these bindings at compile time, ensuring that only variables defined earlier in the lexical scope are accessible. -
Dynamic scoping (
var): Variables are resolved at runtime and can be accessed from anywhere within their scope, regardless of the declaration order in the source code, unless they are shadowed by anotherlet/const/vardefinition. This provides more flexibility but comes with the overhead of hash table lookups.
This means that a function or scope { … } block can reference a var variable that is
declared later in the source code (as long as it’s in the same scope), but attempting to
reference a let or const variable declared after the function or scope block will result
in a compile-time error.
Example:
let outerLet = 1;
function example(){
puts(outerLet); // Valid: outerLet is declared before the function
puts(dynamicVar); // Valid: var uses dynamic scoping
puts(laterLet); // Error: let/const are statically scoped by order
}
let laterLet = 2;
var dynamicVar = 3;
example();This design choice allows POCA to optimize let and const variables for performance
through static analysis and register allocation, while maintaining the flexibility of
var for dynamic programming patterns that require runtime variable resolution.
Comparison with JavaScript/ECMAScript:
For developers familiar with JavaScript/ECMAScript, it’s important to note that POCA’s scoping behavior differs significantly from JavaScript’s hoisting mechanism:
In JavaScript/ECMAScript:
-
letandconst: Have block scope and are subject to the Temporal Dead Zone (TDZ). They are hoisted but cannot be accessed before their declaration, resulting in aReferenceErrorif referenced earlier in the code. -
var: Has function scope (or global scope) and is hoisted to the top of its scope. The declaration is hoisted but not the initialization, so accessing avarvariable before its declaration line returnsundefinedrather than an error.
In POCA:
-
letandconst: Follow static scoping by declaration order - they must be declared before use, with compile-time verification. This is a compile-time check, not a runtime TDZ. -
var: Uses true dynamic scoping with runtime hash table lookup, allowing access from anywhere within the scope regardless of declaration order, unless they are shadowed by anotherlet/const/vardefinition.
The key difference: JavaScript uses hoisting (moving declarations to the top) with all three
keywords using lexical scoping, while POCA fundamentally distinguishes between compile-time
static resolution (let/const) and runtime dynamic resolution (var). This makes POCA’s
var more flexible for dynamic programming patterns, while let/const enable better
compile-time optimization.
POCA has the following data types on the language level:
-
Null: Special null value -
Number: Double-precision floating point numbers (64 bit IEEE 754) -
String: Immutable sequences of characters, ASCII and UTF-8 supported together with code point and code unit operations -
Hash: Hash tables with support for meta tables (similar to Lua), equivalent to JavaScript objects -
Array: Ordered collections of values -
Ghost: Special type for external types, objects, and resources, for example, native primitives such as Locks, Threads, RegExps, etc. -
Function: First-class functions, closures, and lambdas, linked toCodeorNativeCodeobject values -
Code: Bytecode-compiled code objects, including JIT-compiled native code -
NativeCode: Native code objects, such as native functions wrapped for POCA
and some additional types on the internal engine level, such as:
-
Reference: Internal reference type for objects, so just ignore this type when programming in POCA, as it’s not exposed on the language level.
You might wonder why there’s no dedicated Boolean type. In POCA, boolean values are represented using the Number type, where 0 is considered false and any non-zero value is considered true. For convenience, POCA provides false and true as predefined constants (aliases for 0 and 1 respectively), allowing you to write idiomatic boolean logic while maintaining the simplicity of a unified numeric type.
While this may seem unusual, this approach simplifies the type system and allows for more flexible operations, similar to how some other scripting languages handle boolean logic. It also simplifies type coercion rules in expressions and the instruction set architecture.
All numbers are represented as double-precision floating point values. This design choice simplifies the language and avoids complications arising from having multiple numeric types, while still allowing for integer-like operations when needed, such as bitwise operations and integer division, although these are limited to the 53-bit integer range due to the IEEE 754 representation of the mantissa in 64-bit double-precision floats.
Instead of having both null and undefined, POCA uses the single special value null to represent both concepts. This unification simplifies the type system and reduces complexity when dealing with absent or uninitialized values.
By default, read accesses to undefined hash table keys, array indices, or out-of-bounds array accesses throw exceptions rather than returning undefined. This design choice enhances code reliability and helps catch errors early, as developers are immediately alerted to attempts to access non-existent properties or indices that might otherwise lead to silent failures.
However, POCA provides the optional chaining operator (?.) and nullish coalescing operator (??) for cases where you want to safely handle potentially missing values. These operators treat missing properties or out-of-bounds accesses as null without throwing exceptions, allowing for graceful fallback handling (see Nullish Coalescing Operator for details).
This approach minimizes confusion by providing a single representation for "no value" scenarios and aligns with POCA’s design philosophy of keeping the language straightforward and easy to use.
POCA supports class-based object-oriented programming (OOP) with the class keyword, which allows you to define classes, constructors, methods, and inheritance. These are just syntactic sugar over POCA’s
prototype-based OOP model with the Hash data type, making it easier to work with objects in a familiar way. See the other documentation sections for more details on classes and OOP in POCA.
The POCA syntax defines two types of values:
-
Fixed values
-
Variable values
Fixed values are called Literals.
Variable values are called Variables.
The two most important syntax rules for fixed values are:
Numbers are written with or without decimals:
10.50
1001Strings are text, written within double or single quotes:
"John Doe"
'John Doe'In a programming language, variables are used to store data values.
POCA uses the keywords var, let and const to declare variables.
An equal sign is used to assign values to variables.
In this example, x is defined as a variable. Then, x is assigned (given) the value 6:
let x;
x = 6;
// or
let x = 6;POCA uses arithmetic operators ( + - * / ) to compute values:
(5 + 6) * 10POCA uses an assignment operator ( = ) to assign values to variables:
let x, y;
x = 5;
y = 6;An expression is a combination of values, variables, and operators, which computes to a value.
The computation is called an evaluation.
For example, 5 * 10 evaluates to 50:
5 * 10Expressions can also contain variable values:
x * 10The values can be of various types, such as numbers and strings.
For example, "John" ~ " " ~ "Doe", evaluates to "John Doe", since ~ is using for string concatenation:
"John" ~ " " ~ "Doe"POCA provides the nullish coalescing operator ?? to provide default values when dealing with null or undefined.
The ?? operator returns its left operand if it’s not null or undefined, otherwise it returns the right operand:
let name = userName ?? "Guest"; // Use userName if defined, else "Guest"
let port = config.port ?? 8080; // Use config.port if set, else 8080
let value = input ?? fallback ?? 0; // Chain multiple defaults (left-to-right)Unlike the logical OR operator (||), which treats all falsy values (0, "", false, null, undefined) as triggers for the default, ?? only triggers on null or undefined:
let count = 0;
let x = count || 10; // x = 10 (because 0 is falsy)
let y = count ?? 10; // y = 0 (because 0 is not nullish)
let name = "";
let a = name || "Anonymous"; // a = "Anonymous" (because "" is falsy)
let b = name ?? "Anonymous"; // b = "" (because "" is not nullish)The ?? operator has very low precedence. It’s lower than || and &&, but higher than the ternary ?:, so it evaluates after most other operators. This means that in mixed expressions, || and && bind their operands first, and the resulting value then becomes the operand for ??.
When mixing ?? with && or ||, the evaluation order may be surprising because those operators bind more tightly, as ?? has lower precedence:
// ⚠️ These parse in ways that may surprise you:
result = a || b ?? c; // Parsed as: (a || b) ?? c
result = a ?? b && c; // Parsed as: a ?? (b && c)
// ✅ Always use explicit parentheses to show intent clearly:
result = a || (b ?? c); // OR first, then coalesce
result = (a || b) ?? c; // Coalesce the OR result
result = a ?? (b && c); // Coalesce with AND result
result = (a ?? b) && c; // AND after coalescingBecause the low precedence can lead to unexpected grouping, always add parentheses when mixing ?? with logical operators, even though it’s not a syntax error. This makes your intent explicit and prevents subtle logic errors.
// Providing default values
let timeout = options.timeout ?? 5000;
// Chaining fallbacks (evaluates left-to-right)
let port = args.port ?? env.PORT ?? config.port ?? 3000;
// With arithmetic - always use parentheses for clarity
let total = (price ?? 0) + (tax ?? 0);
let scaled = (value ?? 1) * multiplier;
// With conditionals
let message = isError ? errorMsg : (userMsg ?? "No message");
// Safe property access
let displayName = user?.profile?.name ?? "Anonymous";JavaScript/ECMAScript forbids mixing ?? with && or || without parentheses as a syntax error. POCA takes a different approach: it allows mixing but with clear precedence rules where ?? has lower precedence than && and ||, and encourages developers to use parentheses for clarity. Parentheses are always recommended when combining these operators to avoid ambiguity, even when some people might find it tedious and/or too verbose.
The rationale is that POCA developers should understand operator precedence, just as they do for arithmetic operators like + and *. Forbidding certain combinations adds language complexity without solving the real issue. Instead, POCA relies on documentation and linter warnings to guide developers toward using parentheses for clarity.
This keeps the language simpler while still encouraging readable code through best practices rather than hard restrictions.
-
Use
??when you specifically want to handlenull/undefinedbut preserve other falsy values like0,"", orfalse -
Use
||when you want to default on any falsy value -
Always add parentheses when combining
??with other logical operators for readability -
Prefer clear, explicit grouping in complex expressions over relying on precedence rules
POCA provides optional chaining operators to safely access nested properties, array elements, and hash values without throwing exceptions when intermediate values are null or undefined.
The ?. operator allows safe access to object properties. If the left operand is null or undefined, the entire expression returns null instead of throwing an exception:
let name = user?.profile?.name; // Safe nested access
let email = config?.settings?.email; // Returns null if any part is null
// Traditional approach (verbose and error-prone)
let oldWay = user && user.profile && user.profile.name;
// With optional chaining (clean and safe)
let newWay = user?.profile?.name;The ?[ operator provides safe access to array elements and hash table values. POCA uses ?[key] syntax (without a dot), which aligns with C#, Kotlin, Swift, Dart, and Groovy. Note that JavaScript/TypeScript uses ?.[key] (with a dot) instead:
let value = array?[index]; // Safe array access
let item = hash?[key]; // Safe hash table access
let nested = data?[prop]?[0]?[field]; // Chain safe bracket access
// Practical example: word frequency counter
let words = {};
words[word] = words?[word] + 1; // Safely handles missing keys
// Returns null if:
// - The array/hash is null or undefined
// - The key doesn't exist (for hashes)
// - The index is out of bounds (for arrays)Important: POCA uses ?[key] syntax (no dot), not ?[key?]. While JavaScript/TypeScript use ?.[key] (with a dot), most other languages use ?[key] without the dot.
Optional chaining pairs perfectly with the nullish coalescing operator for default values:
// Provide defaults for missing values
let port = config?[env]?["port"] ?? 8080;
let timeout = options?.timeout ?? 5000;
let name = user?.profile?.name ?? "Anonymous";
// Word counting with safe access and default
let count = wordFrequency?[word] ?? 0;
wordFrequency[word] = count + 1;When the left operand of ?. or ?[ is null or undefined, the entire expression short-circuits and immediately returns null without evaluating the rest:
let result = null?.expensive?.computation?.here; // Returns null immediately
let data = undefined?[key]?[index]; // No array access attempted
// This is safe and efficient:
let value = maybeNull?.method()?.property?[0];
// If maybeNull is null, method() is never called-
Use
?.for safe property access on objects that may benullorundefined -
Use
?[for safe array/hash access when keys or indices may not exist -
Combine with
??to provide default values for missing data -
Don’t overuse: if a value should always exist, use regular access (
.or[) to catch errors early -
Optional chaining is for genuinely optional values, not to hide bugs
POCA’s optional chaining syntax aligns with modern language standards. Note that JavaScript/TypeScript use ?.[ (with a dot) for bracket access, while most other languages use ?[ (without a dot):
| Language | Syntax |
|---|---|
JavaScript/TypeScript |
|
C# |
|
Kotlin |
|
Swift |
|
Dart |
|
POCA |
|
POCA keywords are used to identify actions to be performed.
The let keyword is used to create variables:
let x = 5 + 6;
let y = x * 10;The var keyword is also used to create variables:
var x = 5 + 6;
var y = x * 10;However, the const keyword is also used to create constants:
const x = 5 + 6;
const y = x * 10;Not all POCA statements are "executed".
Code after double slashes // or between
/*and
*/is treated as a comment.
Comments are ignored, and will not be executed:
let x = 5; // I will be executed
// x = 6; I will NOT be executedIdentifiers are POCA names.
Identifiers are used to name variables and keywords, and functions.
The rules for legal names are the same in most programming languages.
A POCA name must begin with:
-
A letter (A-Z or a-z)
-
A dollar sign ($)
-
Or an underscore (_)
Subsequent characters may be letters, digits, underscores, or dollar signs.
Numbers are not allowed as the first character in names.
This way POCA can easily distinguish identifiers from numbers.
All POCA identifiers are case sensitive.
The variables lastName and lastname, are two different variables:
let lastName = "Doe";
let lastname = "Peterson";POCA does not interpret LET or Let as the keyword let.
a = 3.14159; // a is then inside in the current environment hash table
var b = 0x10000; // b is then inside in the current environment hash table
let c = 0b10101; // c is then assigned to a VM-register or frame variable storage, depending on the scope
const f = "This is a constant";
var (g, h) = (0, 1);
(g, h) = (h, g);
function bla(){
return [1, 2, 3]:
}
let (x, y, z) = bla();POCA distinguishes between object/hash literals and code blocks based on their content. Object literals are defined by key-value pairs separated by colons (:) and commas (,). Code blocks consist of expressions or statements without this pattern. The scope and 'code' keywords can be used to explicitly define a code block when ambiguity might arise, as everything in POCA is treated as an expression.
// Example object literal with multiple keys
{ name: "Alice", age: 30 }
// Example object literal with a single key-value pair
{ Name: "Alice" }
// Example shorthand object literal
{ name, age } // Assuming 'name' and 'age' are defined variables, it expands to { name: name, age: age }
// Example object literal with a single shorthand key
{ name } // Assuming 'name' is defined, it expands to { name: name }
// Example code block
{ name; } // This is treated as a code block, because of the semicolon, for distinguishing it from an shorthand object literal
// Example code block
{ print("Hello"); }
// Explicit code block using 'scope' where a new scope is created
scope {
let x = 10;
print(x);
}
// Example code block using 'code' where no new scope is created
code {
let y = 20;
print(y);
}
// Example of nested code blocks with explicit 'scope' and 'code' keywords
scope {
let a = 1 + 2;
code {
let b = a + 2;
print(b);
}
}Without the scope and code keyword, POCA relies on the presence of key-value pairs (identifiers followed by a colon and value) to identify object literals. If no such pattern is found within the curly braces, it’s treated as a code block. However, empty curly braces {} are always treated as an empty object literal, since inside code blocks, these are anyway effectively no-ops and will be garbage collected later.
This distinction allows for flexibility in defining both objects and code blocks, making POCA’s syntax versatile.
let va = [1, 2, 3];
let vb = [4, 5, 6];
let vc = (va ~ vb) ~ [7, 8, 9]; // ~ is the concatenation operator for arrays, strings, etc.
let vd = vc[0 .. 4]; // range slice copy
va.push(21);
va.push(42);
va.push(1337);
for(let i = 0; i < va.size(); i++){
puts(va[i]);
}
foreach(let arrayElement in vd){
puts(arrayElement);
}
while(!va.empty()){
va.pop();
}
function Bla(){
return [1, 2, 3];
}
let (a, b, c) = Bla();
puts(a, " ", b, " ", c);let aHash = {
bla: "bla!",
bluh: "bluh?"
};
foreach(let hashElement; aHash){
puts(hashElement);
}
function oa(){
return {};
}
var x = {a: 12, y:() => puts(@a)};
let y = {prototype: x, b: 34};
let z = {prototype: y, c: 56};
const p = {b: 42, "c": 41};
puts(x.a);
puts(y.a);
puts(z.a);
puts();
y.a=13;
puts(x.a);
puts(y.a);
puts(z.a);
puts();
z.a=14;
puts(x.a);
puts(y.a);
puts(z.a);
puts();
x.y();
y.y();
z.y();
readLine();// Function parameters default to 'let'
function Test1(a, b){
return (a + b) * 2; // a and b are treated as 'let' by default
}
// Explicit 'let' (same as default)
function Test2(let a, let b){
return (a + b) * 2;
}
// Explicit 'var' when needed for hash table storage
function Test3(var a, var b){
return (a + b) * 2; // a and b stored in local hash table
}
fastfunction Test4(let a, let b){
return (a + b) * 2;
}
let u(x=(4)) -> x * x;
puts(u());
y(x) -> x * x;
puts(y(4));
let z=(x)=>x + x;
puts(z(4));
let w=function(x)(x * x) - x;
puts(w(4));
let a = function(x){
return (x * x) - x;
}
puts(a(4));
let function b(x){
return (x * x) - x;
}
puts(b(4));
f(x) -> x + 3;
function g(m, x) m(x) * m(x);
puts(g(f, 7));
function searchPrimes(let from, let to){
let (dummy, primes, n, i, j, isPrime) = (0, 0, 0, 0, 0, 0);
from = +from;
to = +to;
for(n = from; n<= to; ++n){
i = ((n % 2) === 0) ? 2 : 3;
j = n ** 0.5;
isPrime = 1;
while(i <= j){
if((n % i) === 0){
isPrime = 0;
break;
}
i += 2;
}
primes += isPrime;
}
return primes;
}Function parameters default to let scope unless explicitly declared with var.
This provides better performance by storing parameters directly in the function’s
register set or frame storage for faster access, depending on the scoping, avoiding
the hash table lookup overhead of var.
When you need dynamic variable lookup or metaprogramming features, you can
explicitly declare parameters as var to store them in the local hash table.
POCA provides a fastfunction keyword for performance-critical functions. Fast
functions are optimized by eliminating the overhead of the local variable hash
table that regular functions use for var-declared variables.
Key Differences:
-
Regular
function: Variables declared withvarare stored in a hash table (object/dictionary), which provides flexibility but has lookup overhead. -
Fast
fastfunction: Does not create the hash table forvarvariables. All variables must be declared withletorconst, which are stored directly in the function’s register set or frame storage for faster access, depending on the scoping.
When to use fastfunction:
-
Performance-critical code (hot loops, frequently called functions)
-
Functions that don’t need `var’s dynamic properties
-
Functions where all local variables can be declared with
letorconst
Restrictions:
-
Cannot use
varkeyword inside afastfunction -
All local variables must be declared with
letorconst -
Slightly less flexible than regular functions, but significantly faster
Example:
// Regular function - uses hash table for var variables
function regularSum(a, b) {
var temp = a + b; // stored in hash table
return temp * 2;
}
// Fast function - all variables in registers/frame storage
fastfunction fastSum(let a, let b) {
let temp = a + b; // stored directly in registers/frame storage
return temp * 2;
}
// Fast function with const
fastfunction computeArea(let width, let height) {
const pi = 3.14159;
let radius = width / 2;
return pi * radius * radius;
}For most code, regular function is fine. Use fastfunction when you’ve
identified performance bottlenecks through profiling and the function doesn’t
require var semantics.
var terminated = 0;
function thread1function(){
while(!terminated){
puts("Thread 1");
}
}
var thread1 = new Thread(thread1function);
var thread2 = new Thread(function(){
while(!terminated){
puts("Thread 2");
}
});
thread1.start();
readLine();
thread2.start();
readLine();
terminated = 1;
thread1.wait();
thread2.wait();function testcoroutinefunction(i){
while(1){
Coroutine.yield(i += Coroutine.get());
}
}
var testcoroutine = new Coroutine(testcoroutinefunction, 1000);
print("Go!\r\n");
print(testcoroutine.resume(100), "\r\n");
print(testcoroutine.resume(10), "\r\n");
print(testcoroutine.resume(1)," \r\n");
readLine();var a = 12, b = 4;
class Test extends BaseClass {
var a = 0;
constructor(let v){
this.a = v + 1;
}
function init(let v){
this.a = v + 1;
}
function b(){
puts(this.a);
}
}
class TestB extends Test {
var x = 0;
constructor(let v){
super(v * 2);
this.x = v + 1;
}
function b(){
super(); // calls previous Test.B
super.b(); // also calls previous Test.B
super.c(); // also previous Test.c
this.a--;
super.b(); // also calls previous Test.B
puts(this.x);
}
}
function Test.c(){
puts(if(this.a === 247) "yeah" else "ups");
}
function Test::d(){
puts((this.a === 247) ? "allright" : "fail!");
}
Test.e = function(){
puts((this.a === 247) ? ":-)" : ":-(");
}
let bla = new Test(246);
puts(bla.a, " ", a, " ", b);
bla.b();
bla.c();
bla.d();
bla.e();
puts();
puts("Keys of object bla instance of class ",bla.className,":\n", scope{
let s = "";
forkey(key;bla){
s ~= key ~ " of type " ~ typeof(bla[key]) ~ "\n";
}
s
});
let blup = new TestB(123);
puts(blup.a , " ", blup.x, " " , (blup instanceof Test) ? "true" : "false");
blup.b();
puts(Test.className);
puts(bla.className);
puts(TestB.className);
puts(blup.className);
var piep = new blup.classType(42);
puts(piep.a);
readLine();module TestModule {
class TestClass {
var a = 0;
constructor(let v){
this.a = v;
}
function b(){
puts(this.a);
}
}
function TestClass::c(){
puts(this.a * 2);
}
}
module OtherTestModule {
class TestClass {
class TestClassInsideTestClass {
module TestModuleInsideTestClassInsideTestClass {
}
}
var a = 0;
constructor(var v){
this.a = v + v;
}
function b(){
puts(this.a);
}
}
function TestClass.c(){
puts(this.a * 2);
}
}
var TestClassInstanceFromTestModule = new TestModule.TestClass(2);
TestClassInstanceFromTestModule.b();
TestClassInstanceFromTestModule.c();
puts();
var TestClassInstanceFromOtherTestModule = new OtherTestModule.TestClass(2);
TestClassInstanceFromOtherTestModule.b();
TestClassInstanceFromOtherTestModule.c();var Vector = {
create: function(let vx=0, let vy=0, let vz=0){
return setHashEvents({
prototype: this,
x: vx,
y: vy,
z: vz
}, this);
},
__add: fastfunction(let a, let b){
// Important hint: "this" can be null here (even in non-fastfunction functions), so doesn't use it here! :-)
if((a instanceof Vector) && (b instanceof Vector)){
return new Vector(a.x + b.x, a.y + b.y, a.z + b.z);
}else{
throw "No vector?";
}
},
__sub: fastfunction(let a, let b){
// Important hint: "this" can be null here (even in non-fastfunction functions), so doesn't use it here! :-)
if((a instanceof Vector) && (b instanceof Vector)){
return new Vector(a.x - b.x, a.y - b.y, a.z - b.z);
}else{
throw "No vector?";
}
},
__mul:fastfunction(let a, let b){
// Important hint: "this" can be null here (even in non-fastfunction functions), so doesn't use it here! :-)
if((a instanceof Vector) && (b instanceof Vector)){
return new Vector(a.x * b.x, a.y * b.y, a.z * b.z);
}else{
throw "No vector?";
}
},
__div: fastfunction(let a, let b){
// Important hint: "this" can be null here (even in non-fastfunction functions), so doesn't use it here! :-)
if((a instanceof Vector) && (b instanceof Vector)){
return new Vector(a.x / b.x, a.y / b.y, a.z / b.z);
}else{
throw "No vector?";
}
}
};
var va = new Vector(1, 2, 3);
var vb = new Vector(10, 20, 30);
var vc = va + vb;
puts(vc.x, " ", vc.y, " ", vc.z);
vc -= vb;
puts(vc.x, " ", vc.y, " ", vc.z);
vc *= vb;
puts(vc.x, " ", vc.y, " ", vc.z);
vc /= (va*vb);
puts(vc.x, " ", vc.y, " ", vc.z);
readLine();var expr = "", lineRegExp = /^(.*)\\s*$/, match = [], i = 0, currentScope = {};
while(1){
print((expr === "") ? "> " : "\\ ");
if(match = lineRegExp.match(line = readLine())) {
expr ~= match[0][1] ~ "\n";
continue;
}
if((expr ~= line) === ""){
break;
}
try{
print("< " ~ eval(expr, "<eval>", [], null, currentScope) ~ "\n");
}catch(err){
for(i = err.size() - 1; i >= 0; i--){
print(err[i] ~ " ");
}
print("\n");
}
expr = "";
}try{
print("Hello ");
}catch(c){
print("dear ");
}finally{
print("World!\n");
}
try{
print("Hello ");
throw 123;
}catch(c){
print("dear ");
}finally{
print("World!\n");
}let aValue = 5;
when(aValue){
case(5 .. 10, 15 .. 17){
puts("Hey! ", aValue);
aValue++;
retry;
}
case(18){
puts("Hi! ", aValue);
fallthrough;
}
case(19){
puts("Hallo!");
}
else{
puts("Ups!");
}
}let aValue = 5;
switch(aValue){
case 1:
case 5:
case 7:
case 10:
puts("Hey! ", aValue);
break;
case 18:
puts("Hi! ", aValue);
case 19:
puts("Hallo!");
break;
default:
puts("Ups!");
break;
}