Namespace Scoping in Grapa
Grapa provides a sophisticated namespace scoping system that allows precise control over variable visibility and lifetime. Understanding this system is crucial for writing robust, thread-safe code.
Core Namespace Types
Grapa defines three fundamental namespace types that can be accessed using special identifiers:
$global
- Global Namespace
- Purpose: Global variables accessible from anywhere in the program
- Lifetime: Entire program execution
- Scope: Application-wide
- Use Case: Configuration, shared state, constants
$this
- Object Context
- Purpose: Current object's namespace (root of object hierarchy)
- Lifetime: Object lifetime
- Scope: Object-wide
- Use Case: Object properties, methods, internal state
$local
- Local Context
- Purpose: Variables within the current block scope (
{}
) - Lifetime: Block execution
- Scope: Function or block-level
- Use Case: Function parameters, temporary variables, thread-local storage
- Special Feature:
$local
is automatically initialized with all function parameters, providing access to the complete parameter list - Important:
$local
properly isolates variables within function scope, protecting against external variable access and ensuring thread safety in recursive functions and multi-threaded execution
$parent
- Parent Function Context
- Purpose: Access to the calling function's local namespace
- Lifetime: Function call execution
- Scope: Parent function's
$local
namespace - Use Case: Function communication, nested function data access, callback patterns
- Behavior: When called from top-level, returns the grammar system; when called from within a function, returns the calling function's
$local
namespace
$root
- Root Widget Context
- Purpose: Reference to the root widget/window in GUI applications
- Lifetime: Widget lifetime
- Scope: Widget hierarchy root
- Use Case: GUI programming, widget manipulation, window management
- Implementation: Variable lookup that resolves to the root widget
$self
- Current Widget Context
- Purpose: Reference to the current widget/object being processed
- Lifetime: Widget processing lifetime
- Scope: Current widget context
- Use Case: Widget callbacks, event handling, self-referential operations
- Implementation: Variable lookup that resolves to the current widget
$oplocal
- Function's Local Context
- Purpose: Direct access to the function's
$local
namespace, bypassing nested block scopes - Lifetime: Function execution
- Scope: Function's
$local
namespace (not affected by nested blocks) - Use Case: Accessing function parameters and variables from deeply nested scopes, debugging, function introspection
- Behavior: Always returns the function's
$local
namespace, regardless of how deeply nested the call is within the function
Syntax
All namespace variables must be accessed with the $
prefix:
// Core namespace variables:
$global.x = 5; // Global namespace
$this.property = "value"; // Object context
$local.temp = 10; // Local context
// Special context variables:
$parent.rule_name; // Grammar system access
$root.hide(); // Root widget reference
$self.get("property"); // Current widget reference
Critical Use Cases
1. Avoiding Variable Name Conflicts
When nested functions or loops use the same variable names, conflicts can occur:
// PROBLEMATIC: Variable name conflict
i = 0;
while (i < 10) {
someFunction(); // This function might use 'i' and conflict!
};
someFunction = op() {
i = 0; // This overwrites the outer loop's 'i'!
while (i < 5) {
// Unintended behavior
};
};
// SOLUTION: Use local scoping
i = 0;
while (i < 10) {
someFunction(); // Safe to call
};
someFunction = op() {
$local.i = 0; // Local variable, won't affect outer scope
while ($local.i < 5) {
// Safe to use i here
};
};
2. Thread Safety
Functions that may be called from parallel processes must use local variables:
// NOT THREAD SAFE
counter = 0;
unsafeFunction = op() {
counter += 1; // Shared global variable - race condition!
return counter;
};
// THREAD SAFE
threadSafeFunction = op() {
$local.counter = 0; // Each thread gets its own copy
$local.data = {}; // Thread-local storage
$local.counter += 1;
return $local.counter;
};
3. Function Parameter Safety
Functions should declare local variables to avoid conflicts with global state:
// Example: HTTP client function
curl = op(url_str, options) {
// Declare ALL local variables upfront
$local.method = null;
$local.cert = null;
$local.url_part = null;
$local.host = null;
$local.client = null;
$local.err = null;
$local.slash_pos = null;
$local.i = null;
$local.request = null;
$local.response = null;
$local.header_end = null;
$local.headers_raw = null;
$local.response_body = null;
$local.lines = null;
$local.status_line = null;
$local.status_parts = null;
$local.status_code = null;
$local.status_text = null;
// Now safe to use all variables without conflicts
method = "GET";
if (options.method.type() == $STR) {
method = options.method;
};
// ... rest of function implementation
};
Best Practices
1. Declare Local Variables Upfront
Always declare local variables at the beginning of functions:
myFunction = op(param1, param2) {
// Declare all local variables first
$local.result = null;
$local.temp = null;
$local.i = null;
$local.data = {};
// Then use them throughout the function
result = param1 + param2;
// ... rest of function
};
2. Use $local
for Function Variables
Any variable used within a function should be declared as local:
processData = op(data) {
$local.processed = [];
$local.i = 0;
while ($local.i < data.len()) {
$local.item = data[$local.i];
$local.processed.append($local.item.process());
$local.i += 1;
};
return $local.processed;
};
3. Multiple Ways to Add Variables to $local
You can declare multiple local variables using several different approaches:
/* Method 1: Individual declarations */
f1 = op() {
$local.x = 3;
$local.y = 2;
$local.i = 0;
$local.t = [1,2,3];
$local.v = {'a':22,'b':33};
return $local;
};
/* Method 2: Reset $local to a list (replaces all existing variables) */
f2 = op() {
$local = {x: 3, y: 2, i: 0, t: [1,2,3], v: {'a':22,'b':33}};
return $local;
};
/* Method 3: Object extension with ++= (preserves existing variables) */
f3 = op() {
$local.x = 3; /* Individual declaration first */
$local++={
y: 2,
i: 0,
t: [1,2,3],
v: {'a':22,'b':33}
};
return $local;
};
/* Method 4: Compact object extension (preserves existing variables) */
f4 = op() {
$local.x = 3; /* Individual declaration first */
$local++={y:2,i:0,t:[1,2,3],v:{'a':22,'b':33}};
return $local;
};
/* Method 5: Mixed approach */
f5 = op() {
$local.g = 1; /* Individual declaration */
$local = {a: 1, b: "hi"}; /* Reset to list (loses 'g') */
return $local;
};
Key Differences:
- Individual declarations ($local.x = 3;
): Add one variable at a time
- Reset to list ($local = {...}
): Replaces ALL existing variables with new structure
- Object extension ($local++={...}
): Adds new variables while preserving existing ones
- Compact extension ($local++={y:2,i:0}
): Same as extension but more compact syntax
Note: When you reset $local
to a list, it replaces all existing local variables with the new list structure. Use object extension (++=
) when you want to add variables while preserving existing ones.
4. Avoid Global Variables in Functions
Unless absolutely necessary, avoid using global variables within functions:
// BAD: Using global variables
globalCounter = 0;
badFunction = op() {
globalCounter += 1; // Side effects, not thread-safe
};
// GOOD: Use local variables
goodFunction = op() {
$local.counter = 0;
$local.counter += 1;
return $local.counter;
};
5. Thread-Safe Design
For functions that may run in parallel contexts:
threadSafeProcessor = op(input) {
// All variables are local - function is thread-safe
$local.result = null;
$local.temp = input.clone();
$local.processed = $local.temp.transform();
return $local.processed;
};
6. Critical: Network Programming Thread Safety
Network message handlers and concurrent functions MUST use $local
variables:
/* ✅ CRITICAL - Thread-safe network message handler */
httpMessageHandler = op(netSession, message, hasmore) {
netSession.data += message;
if (hasmore == 0) {
/* Check for connection close */
if (netSession.data.len() == 0) {
netSession.disconnect();
return(null);
};
/* ALL variables must be $local to avoid race conditions */
$local.datasplit = netSession.data.split(" ");
$local.method = datasplit[0];
$local.path = datasplit[1];
$local.response = "";
/* Process request safely */
if (path == "/") {
response = "HTTP/1.1 200 OK\r\n\r\n<h1>Server</h1>";
} else {
response = "HTTP/1.1 404 Not Found\r\n\r\n";
};
netSession.send(response);
};
};
/* ❌ DANGEROUS - Race conditions in concurrent execution */
badMessageHandler = op(netSession, message, hasmore) {
netSession.data += message;
if (hasmore == 0) {
/* Global variables cause race conditions! */
request = netSession.data; // ❌ Race condition!
method = request.left(3); // ❌ Race condition!
path = ""; // ❌ Race condition!
/* Multiple concurrent handlers will overwrite each other's variables */
};
};
Why this is critical:
- Network message handlers run concurrently
- Global variables are shared across all concurrent executions
- Race conditions can cause data corruption, crashes, or incorrect behavior
- $local
ensures each concurrent execution has its own variable instances
Namespace Hierarchy
Variables are resolved in the following order:
1. Local scope ($local
)
2. Object scope ($this
)
3. Global scope ($global
)
// Example showing hierarchy
$global.x = "global";
$this.x = "object";
myFunction = op() {
$local.x = "local";
x; // Returns "local" (local scope)
$this.x; // Returns "object" (object scope)
$global.x; // Returns "global" (global scope)
};
Special Context Variables
$parent
- Parent Function Access
$parent
provides access to the calling function's local namespace, enabling powerful function communication patterns:
/* Basic parent access */
child_function = op() {
$local.result = "processed";
$parent; // Returns calling function's $local namespace
};
parent_function = op() {
$local.data = "input";
$local.status = "processing";
child_function(); // Returns {"data":"input","status":"processing"}
};
parent_function(); // Returns {"data":"input","status":"processing"}
Key Behaviors:
- From top-level: Returns the grammar system (106+ rules)
- From within functions: Returns the calling function's $local
namespace
- Nested calls: Always refers to the immediate parent, not the root
- Block scope calls: Returns the immediate calling scope's $local
(even if it's a nested block)
- Call stack tracing: Can be used to trace back to the global namespace
/* Nested function example */
grandchild = op() {
$local.gc_data = "grandchild";
$parent; // Returns parent's $local, not grandparent's
};
child = op() {
$local.c_data = "child";
grandchild(); // Returns {"c_data":"child"}
};
parent = op() {
$local.p_data = "parent";
child(); // Returns {"c_data":"child"}
};
parent(); // Returns {"c_data":"child"}
Critical: $parent
Returns Immediate Calling Scope
$parent
always returns the immediate calling scope's $local
, even if that scope is a nested block within a function:
/* Example: $parent returns block scope, not function scope */
f1 = op() {
$parent; // Returns whatever scope called f1()
};
f2 = op() {
$local.a = 1; // Function scope: {"a":1}
if (true) {
$local.b = 2; // Block scope: {"b":2}
f1(); // Returns {"b":2} - block scope, not function scope!
};
};
f2(); // Returns {"b":2}
Key Point: $parent
does NOT return the function's $local
- it returns the immediate calling scope's $local
. In the example above, f1()
returns the if
block's $local
({"b":2}
), not the function's $local
({"a":1}
).
Call Stack Tracing with $parent
$parent
can be used to construct a call stack by leaving breadcrumbs in each function's $local
namespace:
/* Basic call stack tracing */
f1 = op() {
$local.breadcrumb = "f1";
f2();
};
f2 = op() {
$local.breadcrumb = "f2";
f3();
};
f3 = op() {
$local.breadcrumb = "f3";
$parent.breadcrumb; // Returns "f2" (immediate parent)
};
f1(); // Returns "f2"
Global Namespace Detection:
/* Detect if called from top-level */
f3 = op() {
$local.breadcrumb = "f3";
$parent == $global; // Returns true if called from top-level
};
f3(); // Returns true (called from top-level)
Advanced Call Stack Construction:
/* Recursive call stack builder */
buildCallStack = op() {
$local.stack = {};
$local.current = $local.breadcrumb;
if ($parent != $global) {
$local.stack = $parent.stack;
$local.stack += $current;
} else {
$local.stack = {$current};
};
$local.stack;
};
/* Usage with breadcrumbs */
f1 = op() {
$local.breadcrumb = "f1";
buildCallStack();
};
f2 = op() {
$local.breadcrumb = "f2";
f1();
};
f2(); // Returns {"f2", "f1"}
Debugging Applications: - Function call tracing: Track which functions called which - Error context: Provide context about where errors occurred - Performance profiling: Understand call patterns - Dynamic debugging: Inspect calling context at runtime
$oplocal
- Function's Local Context
$oplocal
provides direct access to the function's $local
namespace, bypassing nested block scopes. This is particularly useful when you need to access function parameters or variables from deeply nested scopes.
Key Difference from $parent
:
- $parent
: Returns the immediate calling scope's $local
(could be a nested block)
- $oplocal
: Always returns the function's $local
namespace, regardless of nesting depth
/* Example showing $parent vs $oplocal behavior */
f = op() {
$local.a = 1; // Function parameter/variable
if (true) {
$local.b = 2; // Nested block variable
if (true) {
$parent; // Returns {"b":2} - immediate calling scope
$oplocal; // Returns {"a":1} - function's $local
};
};
};
f(); // $parent returns {"b":2}, $oplocal returns {"a":1}
Practical Use Cases:
1. Accessing Function Parameters from Nested Scopes:
process_data = op(data, options) {
$local.result = {};
for item in data {
if (item.type == "special") {
// Need to access function parameters from nested scope
$oplocal.options.debug; // Access function parameter
$oplocal.result.set(item.id, process_special(item));
};
};
$oplocal.result; // Return the result
};
2. Debugging and Introspection:
debug_function = op(param1, param2) {
$local.debug_info = {};
if (some_condition) {
// Access all function parameters for debugging
$oplocal.debug_info.param1 = $oplocal.param1;
$oplocal.debug_info.param2 = $oplocal.param2;
$oplocal.debug_info.timestamp = $TIME().utc();
};
$oplocal.debug_info;
};
3. Avoiding Multiple $parent
Levels:
/* Without $oplocal - requires multiple $parent calls */
complex_function = op(config) {
$local.setup = {};
if (condition1) {
if (condition2) {
if (condition3) {
// Need to access function parameter - requires 3 levels of $parent
$parent.$parent.$parent.config; // Error-prone and fragile
};
};
};
};
/* With $oplocal - direct access */
complex_function = op(config) {
$local.setup = {};
if (condition1) {
if (condition2) {
if (condition3) {
// Direct access to function parameter
$oplocal.config; // Clean and reliable
};
};
};
};
When to Use $oplocal
:
- Deep nesting: When you need function parameters from deeply nested scopes
- Function introspection: When debugging or logging function state
- Parameter access: When you need reliable access to function parameters
- Avoiding $parent
chains: When multiple $parent
calls would be error-prone
When NOT to Use $oplocal
:
- Simple cases: When $local
or $parent
are sufficient
- Block-level variables: When you actually want the nested block's variables
- Global scope: $oplocal
only works within function contexts
Important Limitation - No Chaining Support:
Namespace variables ($oplocal
, $local
, $parent
, $this
) cannot be chained. The following syntax does NOT work:
// ❌ This does NOT work - chaining is not supported
$oplocal.$parent.$oplocal
$parent.$parent.$local
$this.$parent.$oplocal
Why Chaining Doesn't Work:
- Namespace variables are implemented as special identifiers, not as objects with methods
- They return the namespace object directly, not a wrapper that supports method chaining
- The current implementation doesn't provide member functions like oplocal()
, local()
, parent()
, this()
in $OBJ.grc
Workaround for Complex Navigation: If you need to access multiple levels of namespace context, store references in variables:
complex_function = op() {
$local.function_context = $oplocal; // Store function's $local
$local.parent_context = $parent; // Store parent's $local
if (some_condition) {
// Access stored contexts
$local.function_context.some_param;
$local.parent_context.some_variable;
};
};
$root
and $self
- Widget Context
In GUI applications, $root
and $self
provide widget references:
/* GUI widget example (from $editor.grc) */
button_callback = op() {
$root.hide(); // Hide the root window
$self.get("text"); // Get current widget's text
$root.child("menu"); // Access child widgets
};
/* Widget hierarchy navigation */
menu_callback = op() {
$local.h1 = $root.child("scrollitems").get("h");
$local.y1 = $root.child("scrollitems").get("y");
$root.redraw(); // Refresh the entire window
};
Widget Context Variables:
- $root
: Reference to the root widget/window
- $self
: Reference to the current widget being processed
- Use Cases: GUI programming, widget manipulation, event handling
Implementation Details
The namespace system is implemented in lib/grapa/$grapa.grc
:
| $this {@<this,{}>}
| $global {@<global,{}>}
| $local {@<local,{}>}
| $parent {@<parent,{}>}
| $oplocal {@<oplocal,{}>}
| $root {@<var,{$1}>}
| $self {@<var,{$1}>}
These rules map the namespace identifiers to their corresponding C++ implementations in GrapaLibRule.cpp
:
- GrapaLibraryRuleThisEvent::Run
- GrapaLibraryRuleGlobalEvent::Run
- GrapaLibraryRuleLocalEvent::Run
- GrapaLibraryRuleParentEvent::Run
- GrapaLibraryRuleOpLocalEvent::Run
- $root
and $self
are implemented as variable lookups in GrapaWidget.cpp
Common Pitfalls
1. Forgetting to Declare Local Variables
// BAD: Variable might conflict with global scope
function = op() {
result = process(); // Could overwrite global 'result'
};
// GOOD: Explicitly declare as local
function = op() {
$local.result = process(); // Safe local variable
};
2. Assuming Thread Safety
// BAD: Not thread-safe
sharedCounter = 0;
increment = op() {
sharedCounter += 1; // Race condition in parallel execution
};
// GOOD: Thread-safe
increment = op() {
$local.counter = 0;
$local.counter += 1;
return $local.counter;
};
3. Nested Scope Conflicts
// BAD: Variable name conflict
outerLoop = op() {
i = 0;
while (i < 10) {
innerLoop(); // innerLoop uses 'i' - conflict!
};
};
innerLoop = op() {
i = 0; // Overwrites outer loop's 'i'
while (i < 5) {
// Unintended behavior
};
};
// GOOD: Use local scoping
outerLoop = op() {
$local.i = 0;
while ($local.i < 10) {
innerLoop();
};
};
innerLoop = op() {
$local.i = 0; // Local variable, no conflict
while ($local.i < 5) {
// Safe to use i
};
};
4. Misunderstanding $parent
Behavior
// CONFUSING: $parent returns immediate calling scope, not function scope
f1 = op() {
$parent; // Returns calling scope's $local
};
f2 = op() {
$local.a = 1; // Function scope
if (true) {
$local.b = 2; // Block scope
f1(); // Returns {"b":2}, not {"a":1}!
};
};
// SOLUTION: Use $parent to access function scope from nested blocks
f3 = op() {
$local.a = 1; // Function scope
if (true) {
$local.b = 2; // Block scope
$parent; // Returns {"a":1} - function scope
};
};
Key Point: $parent
always returns the immediate calling scope's $local
, not necessarily the function's $local
. This can be confusing when functions are called from nested blocks.
Summary
Grapa's namespace scoping system provides powerful tools for managing variable visibility and lifetime. The system includes:
Core Namespace Variables:
- $global
- Global variables accessible from anywhere
- $this
- Current object's namespace
- $local
- Local variables within current block scope
Special Context Variables:
- $parent
- Access to calling function's local namespace
- $root
- Reference to root widget in GUI applications
- $self
- Reference to current widget being processed
By understanding and properly using these namespace variables, you can write robust, thread-safe code that avoids variable conflicts and maintains clear scope boundaries.
Key Takeaways:
- Always declare function variables as $local
to ensure thread safety
- Use $parent
for function communication and callback patterns
- Use $parent
for call stack tracing and debugging with breadcrumbs
- Use $root
and $self
for GUI widget manipulation
- Leverage $local
's automatic parameter list initialization for introspection
- Use $parent == $global
to detect top-level function calls