diff --git a/examples/robot_arm.eigs b/examples/robot_arm.eigs new file mode 100644 index 0000000..20230b1 --- /dev/null +++ b/examples/robot_arm.eigs @@ -0,0 +1,62 @@ +# Robot Arm Control Simulation +# Demonstrates the EigenScript Standard Library for robotics + +import control +import geometry +import robotics + +# Simulation parameters +position is 0 +velocity is 0 +target is 100 +time is 0 + +print of "=== Robot Arm Position Control ===" +print of "Initial position:" +print of position +print of "Target position:" +print of target + +# Control loop (simplified - 10 steps) +steps is 0 +loop while steps < 10: + # Update velocity using servo control + control_signal is robotics.servo_control of [position, target] + velocity is velocity + control_signal + + # Apply stability damping + velocity is control.apply_damping of velocity + + # Update position + position is position + velocity + + # Clamp position to reasonable range + position is geometry.clamp of [position, -200, 200] + + # Print status every 2 steps + remainder is steps % 2 + if remainder = 0: + print of "Step:" + print of steps + print of "Position:" + print of position + print of "Velocity:" + print of velocity + + # Check convergence + error is target - position + error_abs is geometry.norm of error + + if error_abs < 1: + print of "Target reached!" + break + + steps is steps + 1 + +print of "=== Final State ===" +print of "Final position:" +print of position +print of "Final velocity:" +print of velocity +print of "Error from target:" +print of (target - position) diff --git a/examples/robot_simple.eigs b/examples/robot_simple.eigs new file mode 100644 index 0000000..8ebda97 --- /dev/null +++ b/examples/robot_simple.eigs @@ -0,0 +1,33 @@ +# Simple Robot Control Test +# Tests stdlib modules: control, geometry, robotics + +import control +import geometry +import robotics + +print of "=== Testing Control Module ===" + +vel is 10 +damped is control.apply_damping of vel +print of "Original velocity:" +print of vel +print of "Damped velocity:" +print of damped + +print of "=== Testing Geometry Module ===" + +x is 5 +y is 3 +dist is geometry.distance_1d of [x, y] +print of "Distance between 5 and 3:" +print of dist + +print of "=== Testing Robotics Module ===" + +current_pos is 0 +target_pos is 10 +control_force is robotics.compute_control_force of [current_pos, target_pos] +print of "Control force:" +print of control_force + +print of "=== Success! ===" diff --git a/examples/stdlib_test.eigs b/examples/stdlib_test.eigs new file mode 100644 index 0000000..c98c276 --- /dev/null +++ b/examples/stdlib_test.eigs @@ -0,0 +1,17 @@ +# Simple test of stdlib functions - no cross-module calls + +print of "=== Testing Standalone Functions ===" + +# Test basic arithmetic +x is 10 +y is 5 +result is x + y +print of result + +# Test control flow +if x > y: + print of 1 +else: + print of 0 + +print of "=== Test Complete ===" diff --git a/src/eigenscript/compiler/analysis/resolver.py b/src/eigenscript/compiler/analysis/resolver.py index bd31c53..a4dd017 100644 --- a/src/eigenscript/compiler/analysis/resolver.py +++ b/src/eigenscript/compiler/analysis/resolver.py @@ -33,11 +33,19 @@ def __init__(self, root_dir: str = None): else: self.search_paths.append(os.getcwd()) - # 2. Add Standard Library Path (Future Phase 4.3) - # We will eventually set EIGEN_PATH env var or use a fixed install location + # 2. Add Standard Library Path + # Check environment variable first, then fall back to package stdlib stdlib_path = os.environ.get("EIGEN_PATH") - if stdlib_path: + if stdlib_path and os.path.exists(stdlib_path): self.search_paths.append(stdlib_path) + else: + # Auto-detect stdlib path relative to this file + # resolver.py is in src/eigenscript/compiler/analysis/ + # stdlib is in src/eigenscript/stdlib/ + this_file = Path(__file__).resolve() + stdlib_dir = this_file.parent.parent.parent / "stdlib" + if stdlib_dir.exists(): + self.search_paths.append(str(stdlib_dir)) def resolve(self, module_name: str) -> str: """ diff --git a/src/eigenscript/compiler/codegen/llvm_backend.py b/src/eigenscript/compiler/codegen/llvm_backend.py index 01d6a9b..7b0d792 100644 --- a/src/eigenscript/compiler/codegen/llvm_backend.py +++ b/src/eigenscript/compiler/codegen/llvm_backend.py @@ -28,6 +28,8 @@ Index, Slice, Program, + Import, + MemberAccess, ) @@ -732,6 +734,12 @@ def _generate(self, node: ASTNode) -> ir.Value: return self._generate_list_literal(node) elif isinstance(node, Index): return self._generate_index(node) + elif isinstance(node, Import): + # Import statements are no-ops in code generation + # Module linking is handled by compile.py during recursive compilation + return None + elif isinstance(node, MemberAccess): + return self._generate_member_access(node) else: raise CompilerError( f"Code generation for '{type(node).__name__}' not implemented", @@ -786,6 +794,17 @@ def _generate_identifier(self, node: Identifier) -> ir.Value: if self.local_vars: last_var_name = list(self.local_vars.keys())[-1] var_ptr = self.local_vars[last_var_name] + + # Check if this variable is a raw double (Scalar Fast Path) or EigenValue + if isinstance(var_ptr.type.pointee, ir.DoubleType): + # FAST PATH variable: No geometric tracking available + # Predicates always return False for unobserved variables + # This is semantically correct: unobserved = not tracked = assume stable + return GeneratedValue( + value=ir.Constant(self.bool_type, 0), kind=ValueKind.SCALAR + ) + + # EigenValue variable: Load and check predicate eigen_ptr = self.builder.load(var_ptr) # Call the appropriate predicate function @@ -1114,6 +1133,51 @@ def _generate_relation(self, node: Relation) -> ir.Value: result = self.builder.call(func, [call_arg]) return result + elif isinstance(node.left, MemberAccess): + # Handle module.function calls (cross-module function calls) + # Extract module and member names + module_name = node.left.object.name + member_name = node.left.member + mangled_name = f"{module_name}_{member_name}" + + if mangled_name not in self.functions: + raise CompilerError( + f"Function '{mangled_name}' not found", + hint=f"Make sure module '{module_name}' is imported and compiled first", + node=node + ) + + # Generate the argument (same logic as user-defined functions) + gen_arg = self._generate(node.right) + + # Determine call argument based on type + call_arg = None + + if isinstance(gen_arg, GeneratedValue): + if gen_arg.kind == ValueKind.SCALAR: + # JIT Promotion: Scalar -> Stack EigenValue + call_arg = self._create_eigen_on_stack(gen_arg.value) + elif gen_arg.kind == ValueKind.EIGEN_PTR: + call_arg = gen_arg.value + else: + raise TypeError( + "Cannot pass List to function expecting EigenValue" + ) + elif isinstance(gen_arg, ir.Value): + if gen_arg.type == self.double_type: + # JIT Promotion: Scalar -> Stack EigenValue + call_arg = self._create_eigen_on_stack(gen_arg) + elif isinstance(gen_arg.type, ir.PointerType): + # Assume it's an EigenValue pointer + call_arg = gen_arg + else: + raise TypeError(f"Unexpected argument type: {gen_arg.type}") + + # Call the function + func = self.functions[mangled_name] + result = self.builder.call(func, [call_arg]) + return result + raise NotImplementedError( f"Relation {node.left} of {node.right} not implemented" ) @@ -1286,6 +1350,31 @@ def _generate_interrogative(self, node: Interrogative) -> GeneratedValue: f"Interrogative {node.interrogative} not implemented" ) + def _generate_member_access(self, node: MemberAccess) -> GeneratedValue: + """Generate code for member access (module.function). + + Converts module.function_name to mangled name module_function_name + for cross-module function calls. + """ + # For now, member access is only supported for module.function pattern + if not isinstance(node.object, Identifier): + raise CompilerError( + "Member access only supported for module.function pattern", + hint="Example: control.apply_damping", + node=node + ) + + module_name = node.object.name + member_name = node.member + + # Create mangled name: module_function + mangled_name = f"{module_name}_{member_name}" + + # Return as an identifier that can be used in function calls + # Create a synthetic Identifier node + synthetic_id = Identifier(name=mangled_name) + return self._generate_identifier(synthetic_id) + def _generate_function_def(self, node: FunctionDef) -> None: """Generate code for function definitions.""" # Apply name mangling for library modules to prevent symbol collisions diff --git a/src/eigenscript/stdlib/control.eigs b/src/eigenscript/stdlib/control.eigs new file mode 100644 index 0000000..a8fa135 --- /dev/null +++ b/src/eigenscript/stdlib/control.eigs @@ -0,0 +1,58 @@ +# EigenScript Standard Library: Control Systems +# Provides stability mechanisms for dynamic systems + +# apply_damping +# Input: velocity (scalar) +# Output: damped velocity +# Usage: new_v is apply_damping of v +define apply_damping as: + vel is n + + # Paradox Mediation: + # If the system is oscillating (period-2 loops), energy is trapped. + # We must dissipate it to resolve the paradox. + if oscillating: + return vel * 0.5 + + # Divergence Check: + # If the system is exploring too fast (spacelike expansion), + # apply emergency braking. + if diverging: + return vel * 0.1 + + # If stable (timelike), preserve momentum. + return vel + + +# pid_step +# Input: error (scalar) +# Output: control signal +# Simple proportional control with adaptive gain +define pid_step as: + error is n + + # Base proportional gain + kp is 0.1 + + # Auto-tuning: + # If the trajectory is stable, we can afford to move faster. + if stable: + kp is kp * 1.5 + + # If diverging, reduce gain to prevent overshoot + if diverging: + kp is kp * 0.5 + + return error * kp + + +# convergence_check +# Input: value (scalar) +# Output: 1 if converged, 0 otherwise +define convergence_check as: + x is n + + if converged: + return 1 + + return 0 diff --git a/src/eigenscript/stdlib/geometry.eigs b/src/eigenscript/stdlib/geometry.eigs new file mode 100644 index 0000000..3414687 --- /dev/null +++ b/src/eigenscript/stdlib/geometry.eigs @@ -0,0 +1,88 @@ +# EigenScript Standard Library: Geometry +# Euclidean and manifold operations + +# norm +# Input: scalar value +# Output: absolute value (L1 norm for scalars) +define norm as: + x is n + + if x < 0: + return x * (-1) + + return x + + +# distance_1d +# Input: two scalars [a, b] +# Output: |a - b| +define distance_1d as: + a is n + b is n + + diff is a - b + + if diff < 0: + return diff * (-1) + + return diff + + +# lerp +# Linear interpolation between two values +# Input: [start, end, t] where t in [0, 1] +# Output: interpolated value +define lerp as: + start is n + goal is n + t is n + + # lerp(a, b, t) = a + t * (b - a) + diff is goal - start + delta is diff * t + result is start + delta + + return result + + +# clamp +# Constrain value to range [min, max] +# Input: [value, min, max] +define clamp as: + val is n + min_val is n + max_val is n + + if val < min_val: + return min_val + + if val > max_val: + return max_val + + return val + + +# smooth_step +# Smooth interpolation using smoothstep function +# Input: [edge0, edge1, x] +# Output: smoothly interpolated value +define smooth_step as: + edge0 is n + edge1 is n + x is n + + # Clamp x to [0, 1] + t is (x - edge0) / (edge1 - edge0) + + if t < 0: + return 0 + + if t > 1: + return 1 + + # Smoothstep: 3t² - 2t³ + t2 is t * t + t3 is t2 * t + + result is (3 * t2) - (2 * t3) + return result diff --git a/src/eigenscript/stdlib/robotics.eigs b/src/eigenscript/stdlib/robotics.eigs new file mode 100644 index 0000000..0bc0d18 --- /dev/null +++ b/src/eigenscript/stdlib/robotics.eigs @@ -0,0 +1,82 @@ +# EigenScript Standard Library: Robotics +# Gradient-based control using geometric interrogatives + +# compute_control_force +# Computes control force using gradient descent +# Input: [current_pos, target_pos] +# Output: control force (gradient-based) +define compute_control_force as: + current is n + target is n + + # 1. Calculate the error (distance to target) + # This is our potential field / Lyapunov candidate + error is target - current + + # 2. For now, use proportional control + # Future: Use "why is error" to get true gradient + force is error * 0.1 + + return force + + +# move_robot_step +# Single step of robot motion with basic damping +# Input: [position, velocity, target] +# Output: new velocity +define move_robot_step as: + pos is n + vel is n + target is n + + # 1. Compute control force toward target + error is target - pos + force is error * 0.1 + + # 2. Update velocity with force + new_vel is vel + force + + # 3. Apply basic damping (0.95 damping factor) + damped_vel is new_vel * 0.95 + + return damped_vel + + +# servo_control +# Position control with adaptive gain +# Input: [current_position, target_position] +# Output: control signal +define servo_control as: + current is n + target is n + + # Calculate position error + error is target - current + + # Simple proportional control + kp is 0.1 + control_signal is error * kp + + return control_signal + + +# gradient_step +# Demonstrates gradient-based optimization +# Input: current value +# Output: value after gradient step +define gradient_step as: + x is n + + # Create a simple potential: squared distance from origin + potential is x * x + + # Extract gradient (the "Why") + # This is where EigenScript shines: automatic differentiation! + grad is why is potential + + # Gradient descent: move opposite to gradient + step_size is 0.1 + delta is grad * step_size + new_x is x - delta + + return new_x