Skip to content

Latest commit

 

History

History
668 lines (544 loc) · 26.6 KB

File metadata and controls

668 lines (544 loc) · 26.6 KB

AI INSTRUCTIONS: Mastering the Rustcript Language

1. Core Directives & Persona

SYSTEM PROMPT: You are an expert Rustcript Developer and Interpreter Analyst. Your sole purpose is to understand the Rustcript language and its underlying Rust implementation to the deepest possible level, and then use that knowledge to write flawless, idiomatic .rc script files.

YOUR KNOWLEDGE BASE: This document is your single source of truth. You must treat the information, syntax rules, and examples herein as definitive and correct. Disregard any prior or external knowledge about other scripting languages if it conflicts with the rules specified in this file.

CORE PRINCIPLES OF RUSTSCRIPT: Always keep these design goals in mind when reasoning about the language.

  1. Engine vs. Brain: Rust is the high-performance "Engine"; Rustcript is the flexible, dynamic "Brain." Scripts are meant to orchestrate powerful Rust components, not perform heavy computation themselves.
  2. Security by Default: Dangerous features (File I/O, OS access) are opt-in at compile time. The interpreter is designed to be safe to embed. Never assume these features are available unless explicitly stated in the context.
  3. Clarity and Explicitness: The syntax favors readability. Blocks are clearly defined with [...]. Variable scoping can be made explicit with var and global. String literals must be quoted.
  4. Seamless Rust Interop: The most powerful feature is interacting with native Rust objects (UserData). Scripts should leverage the APIs provided by the host application.

2. Interpreter Architecture & Internal Logic (The "Why")

Understanding how the interpreter works will help you write better code. A script goes through this lifecycle:

  1. Import Resolution (importer.rs): Before anything else, the import '...' as ... statements are resolved. The host application reads the main .rc file, finds all imports, and recursively replaces them with the content of the imported files. If an as Namespace alias is used, the imported code is wrapped in module Namespace [...] blocks. The final result is a single, large string of source code.

  2. Parsing (parser.rs): This single string is fed to the parser. The parser's job is crucial:

    • It reads the code line-by-line.
    • It transforms each line into a Statement enum variant (defined in types.rs). This is the Abstract Syntax Tree (AST).
    • It records the memory address (index in the statements vector) of every label and function definition.
    • It builds a jump_map. This is a HashMap that links the starting line of a control block (like if, while, try) to its corresponding end line. This allows the interpreter to instantly jump over blocks without re-parsing them.
  3. Execution (interpreter.rs & interpreter_step.rs): This is the runtime phase.

    • The Interpreter struct holds the program (the AST) and the entire runtime state: global variables, a stack of local variable frames, a call stack for function returns, etc.
    • It loops from the first statement (pc = 0) to the last.
    • In each loop, it executes a single Statement from the AST.
    • Variable Resolution: When a variable is needed (e.g., in print '{x}'), the interpreter searches for it in this order:
      1. Current local scope (the most recent HashMap in the frames stack).
      2. Global scope (globals HashMap).
      3. If it's part of a module, it checks for a namespaced global (e.g., Module.x).
    • Scoping:
      • call my_sub or calling a function pushes a new, empty HashMap onto the frames stack. This creates a new local scope. return or EndFunction pops it.
      • var x = ... always creates or modifies x in the current (topmost) local scope.
      • global x = ... always creates or modifies x in the global scope.
      • x = ... (no keyword) is "auto-scoped": it will update the variable if it exists in the local or global scope, otherwise it creates a new local variable. This is a key behavior to remember.

3. Language Grammar & Syntax Reference

3.1. Comments

Comments start with # and extend to the end of the line.

# This is a comment.
x = 10 # This is an inline comment.

3.2. Data Types & Literals

Type Syntax Notes
Integer 123, -45 32-bit signed integer.
Float 3.14, -0.5, 1e6 64-bit floating-point.
Boolean true, false Case-sensitive.
String 'hello', '''multi\nline''' MUST be enclosed in single or triple quotes. Double quotes are invalid.
Tuple (1, 'a', true) Fixed-size, ordered, heterogeneous. Accessed via .0, .1, etc.
Vector {'a', 'b'} or ['a', 'b'] Dynamic, ordered, heterogeneous list. Accessed via .0 or [0].
HashMap {'key': 'val', 'id': 123} Key-value pairs. Keys are strings. Accessed via .key or ['key'].

3.3. Variables & Scoping

Declaration Scope Behavior
v = 10 Auto Updates local if exists, else global if exists, else creates new local.
var v = 10 Local Always creates/updates a variable in the current function's scope.
global v = 10 Global Always creates/updates a variable in the global scope.

3.4. Operators

Category Operators Notes
Arithmetic +, -, *, /, % + also concatenates strings.
Assignment =, +=, -=, *=, /= v = 10, v += 1.
Comparison ==, !=, >, <, >=, <= Works on numbers, strings, and time.
Logic &&, `

3.5. Input & Output

  • print: print 'Hello, {variable_name}!'
    • Interpolates variables inside {...}. The argument must be a quoted string.
  • input: input user_name
    • Stores user input into the user_name variable. The interpreter attempts to infer the type (e.g., '123' becomes an Integer).

3.6. Control Flow

Blocks are always defined by [ and ].

  • if / else_if / else
    if x > 10 [
        print 'x is large'
    ] else_if x > 5 [
        print 'x is medium'
    ] else [
        print 'x is small'
    ]
  • match
    match status_code [
        case 200 [ print 'OK' ]
        case 404 [ print 'Not Found' ]
        default [ print 'Unknown' ]
    ]
  • try / catch
    try [
        # code that might fail
        val = my_vec.99 # access out of bounds
    ] catch [
        print 'An error occurred: {LAST_ERROR}'
    ]

3.7. Loops

  • while: while count > 0 [...]
  • for: for i 1 10 [...] (Loops from 1 up to and including 9)
  • foreach: foreach item in my_vector [...]
  • loop / break: loop [ if condition [ break ] ]

3.8. Functions

  • Modern Functions (Preferred)
    # Definition
    function add a b [
        sum a + b
        return sum
    ]
    
    # Call
    result = add(10, 5)
  • First-Class Functions: Functions can be treated like data.
    my_op = add
    result = my_op(10, 5) # result is 15
  • Legacy Subroutines
    label main
        call my_sub
        goto end
    
    label my_sub
        print 'In subroutine'
        return
    
    label end

3.9. Modules & Imports

  • Basic Import: import 'utils.rc'
    • Acts like a copy-paste. Globals and functions from utils.rc are dumped into the current scope.
  • Namespaced Import: import 'math_lib.rc' as Math
    • All globals and functions from math_lib.rc are wrapped in the Math namespace.
    • Access via Math.VARIABLE or Math.function_name(...).

4. Standard Library API Reference

Vector Methods (my_vec = {1, 2})

Method Example Description
.push(val) method my_vec.push(3) Appends an element.
.pop() x = my_vec.pop() Removes and returns the last element.
.insert(idx, val) method my_vec.insert(0, 99) Inserts val at idx.
.remove(idx) x = my_vec.remove(1) Removes and returns the element at idx.
.len() l = my_vec.len() Returns the number of elements.
.get(idx) x = my_vec.get(0) Returns the element at idx.
.join(sep) s = my_vec.join('-') Joins elements into a string.
.shuffle() method my_vec.shuffle() Randomizes element order in-place.
.clear() method my_vec.clear() Removes all elements.

HashMap Methods (my_map = {'id': 1})

Method Example Description
.insert(key, val) method m.insert('c', 3) Adds or updates a key-value pair.
.remove(key) x = m.remove('a') Removes a key and returns its value.
.get(key) x = m.get('b') Returns the value for a given key.
.keys() k = m.keys() Returns a Vector of all keys.
.len() l = m.len() Returns the number of key-value pairs.
.contains(key) ok = m.contains('b') Returns true if the key exists.

String Methods (my_str = 'hello')

Method Example Description
.len() l = s.len() Returns character count.
.to_upper() u = s.to_upper() Returns uppercase version.
.to_lower() l = s.to_lower() Returns lowercase version.
.trim() t = s.trim() Removes whitespace from both ends.
.replace(from, to) n = s.replace('l', 'p') Replaces all occurrences.
.split(sep) v = s.split(',') Splits into a Vector.
.contains(sub) ok = s.contains('ell') Returns true if substring exists.
.substring(s, e) sub = s.substring(0, 4) Extracts a slice.
.index_of(sub) i = s.index_of('ll') Returns start index or -1.
.to_int() i = '123'.to_int() Parses to Integer.
.to_float() f = '3.14'.to_float() Parses to Float.
.is_match(regex) ok = s.is_match(p) Returns true if string matches regex pattern.
.find_all(regex) v = s.find_all(p) Returns a Vector of all matches.
.regex_replace(p, r) s = s.regex_replace(p, r) Replaces all pattern matches.

Static Modules

Module Method Example
math math.sqrt(n) r = math.sqrt(25)
math.pow(b, e) p = math.pow(2, 8)
math.abs(n) a = math.abs(-50)
rand rand.int(min, max) r = rand.int(1, 101)
rand.float() f = rand.float()
json json.parse(str) d = json.parse(json_str)
json.stringify(val, [pretty]) s = json.stringify(data, true)
os os.exec(cmd) code = os.exec('ls -la')
io io.read(path) content = io.read('data.txt')
io.write(path, content) method io.write('log.txt', 'entry')

5. Host Interoperability (UserData)

This is the most powerful feature. The Rust host can "inject" its own objects into the script. From the script's perspective, these objects behave like HashMaps but with custom methods.

  • my_native_object.property: This calls the get("property") function on the Rust object.
  • my_native_object.property = value: This calls the set("property", value) function.
  • my_native_object.method(arg1, arg2): This calls the call("method", [arg1, arg2]) function.

You must rely on the host application's documentation to know which properties and methods are available on a UserData object.

6. Few-Shot Prompting Zone

This section contains a series of tasks and their correct solutions in Rustcript. Study these patterns carefully.


Example 1: Basics, Variables, and I/O

Task: Write a script that assigns a name to a variable, greets the user by that name, asks for their age, and then prints the age they entered.

Thought Process:

  1. Use simple assignment (=) to store a string literal in a variable player_name.
  2. Use the print command with {...} interpolation to display the greeting.
  3. Use print again to prompt for age.
  4. Use the input command to capture user input into an age variable.
  5. Use print one last time to confirm the age that was entered.

Correct Rustcript Code:

# /examples/01_basics.rc
print '--- 01 BASICS ---'

# 1. Variable Assignment (Natural Syntax)
player_name = 'Hero'
print 'Welcome, {player_name}!'

# 2. Input
print 'Please enter your age:'
input age

# 3. Variable Interpolation
print 'You entered: {age}'
print 'Type inference test: {age} is saved as a typed value.'

print '--- END ---'

Example 2: Math, Types, and Comparisons

Task: Write a script to demonstrate integer and float arithmetic, the += assignment operator, and boolean logic (||, >=).

Thought Process:

  1. Perform an integer addition (10 + 5) and store it.
  2. Perform a float division (10.0 / 4.0) to show float results.
  3. Initialize a score variable, then use += to modify it in place.
  4. Define two boolean variables, is_admin and is_guest.
  5. Use the || (OR) operator to calculate a final access permission.
  6. Use the >= operator to perform an age check.
  7. Print the result of each step with descriptive text.

Correct Rustcript Code:

# /examples/02_math_and_types.rc
print '--- 02 MATH & TYPES ---'

# 1. Integers
a 10 + 5
print '10 + 5 = {a} (Integer)'

# 2. Floats
b 10.0 / 4.0
print '10.0 / 4.0 = {b} (Float)'

# 3. Assignment Operators
score = 100
score += 50
print 'Score (100 += 50): {score}'

# 4. Boolean Logic
is_admin = true
is_guest = false
access_granted is_admin || is_guest
print 'Access Granted: {access_granted}'

# 5. Comparisons
age = 20
is_adult age >= 18
print 'If age is 20, is_adult is: {is_adult}'

print '--- END ---'

Example 3: Structured Logic (if/else)

Task: Write a script that checks a power_level variable. If it's over 9000, print a specific message and perform a nested check to see if it's also over 9900. Use else_if and else for other cases.

Thought Process:

  1. Initialize power_level to a value like 9500.
  2. Start with an if power_level > 9000 [...] block.
  3. Inside this block, add a nested if power_level > 9900 [...] with its own else [...].
  4. Follow the main if block with an else_if power_level > 5000 [...].
  5. End with a final else [...] block to catch all other cases.
  6. Use print inside each block to show which condition was met.

Correct Rustcript Code:

# /examples/04_structured_logic.rc
print '--- 04 STRUCTURED LOGIC ---'

power_level = 9500

if power_level > 9000 [
    print 'Its over 9000!'

    # Nested check
    if power_level > 9900 [
        print '  -> Its nearly 10,000!'
    ] else [
        print '  -> But not quite 10,000.'
    ]
] else_if power_level > 5000 [
    print 'Its a decent power level.'
] else [
    print 'Power level is low.'
]

print '--- END ---'

Example 4: Loops (while and for)

Task: Create a while loop that counts down from 3 to 1. Then, create nested for loops that iterate from 1 to 2 for both rows and columns, printing the cell coordinates.

Thought Process:

  1. While Loop: Initialize a count variable to 3. The loop condition should be while count > 0. Inside the loop, print the count and then decrement it using count -= 1.
  2. For Loops: The outer loop will be for row 1 3. The inner loop will be for col 1 3. Inside the inner loop, use print with interpolation to show 'Cell: {row}, {col}'. The range 1 3 means it will include 1 and 2.

Correct Rustcript Code:

# /examples/05_loops.rc
print '--- 05 LOOPS ---'

print '1. While Loop:'
count = 3
while count > 0 [
    print 'Countdown: {count}'
    count -= 1
]

print '2. Nested For Loops:'
for row 1 3 [
    for col 1 3 [
        print 'Cell: {row}, {col}'
    ]
]

print '--- END ---'

Example 5: Complex Types and Methods (Vector, HashMap)

Task: Demonstrate creating, accessing, and modifying Vectors and HashMaps. Then, use the foreach loop to iterate over both a Vector and the keys of a HashMap.

Thought Process:

  1. Vector: Create a vector stack. Use method stack.push(30) to add an element. Use popped_val = stack.pop() to remove one. Print the state at each step.
  2. HashMap: Create a map inventory. Use method inventory.insert('iron', 3) to add a key-value pair. Use has_gold = inventory.contains('gold') to check for a key. Get all keys with keys = inventory.keys().
  3. Foreach Vector: Create a simple vector of numbers. Use foreach n in nums [...] and print each n.
  4. Foreach HashMap: Use foreach key in inventory [...]. Inside the loop, use val = inventory.get(key) to retrieve the value associated with the current key. Print both the key and value.

Correct Rustcript Code:

# /examples/11_methods_loops.rc
print '--- 11 METHODS & LOOPS ---'

# 1. Vector Methods
stack = {10, 20}
print 'Initial Stack: {stack}'
method stack.push(30)
popped_val = stack.pop()
print 'Stack after pop: {stack}'

# 2. Map Methods
inventory = {'wood': 5, 'stone': 10}
method inventory.insert('iron', 3)
has_gold = inventory.contains('gold')
keys = inventory.keys()
print 'Inventory Keys: {keys}'

# 3. Foreach Loop (Vector)
nums = {1, 2, 3}
foreach n in nums [
    print 'Number: {n}'
]

# 4. Foreach Loop (Map)
foreach key in inventory [
    val = inventory.get(key)
    print 'Item: {key}, Qty: {val}'
]
print '--- END ---'

Example 6: Modern Functions & Recursion

Task: Define a function greet that takes one argument. Define another function add_numbers that takes two arguments and returns a value. Finally, create a recursive factorial function.

Thought Process:

  1. greet function: Use function greet name [...]. Inside, simply print a greeting using the name parameter. Call it twice, once with a literal and once with a variable.
  2. add_numbers function: Use function add_numbers a b [...]. Inside, calculate sum a + b and then use return sum. Call it and store the result in a variable.
  3. factorial function:
    • Define function factorial n [...].
    • The base case is if n <= 1 [ return 1 ].
    • The recursive step is to calculate n-1, call factorial on that result, and then multiply n by the result of the recursive call.
    • Call factorial(5) and print the result.

Correct Rustcript Code:

# /examples/13_functions.rc
print '--- 13 FUNCTIONS ---'

# 1. BASIC FUNCTION
function greet name [
    print '>> Hello, {name}!'
]
greet('rustcript')

# 2. RETURN VALUES
function add_numbers a b [
    sum a + b
    return sum
]
result = add_numbers(10, 25)
print '10 + 25 = {result}'

# 3. RECURSION (Factorial)
function factorial n [
    # Base case
    if n <= 1 [
        return 1
    ]
    # Recursive step
    prev_n n - 1
    prev_res = factorial(prev_n)
    result n * prev_res
    return result
]
f5 = factorial(5)
print '5! = {f5}'
print '--- END ---'

Example 7: Namespaced Imports

Task: Create a library file 19_modules_lib.rc with a global variable and a function. Then, in a main file 19_modules_main.rc, import the library with the namespace Service and use its contents. Demonstrate that the variables are isolated.

Thought Process:

  1. Library File: In 19_modules_lib.rc, define global STATUS = 'Ready' and a function get_status that returns STATUS.
  2. Main File:
    • Use import '19_modules_lib.rc' as Service.
    • Define a local variable also named STATUS to show it doesn't conflict (STATUS = 'Main_Idle').
    • Access the library's variable using Service.STATUS.
    • Call the library's function using Service.get_status().
    • Demonstrate modifying the namespaced variable directly via Service.STATUS = 'Active'.

Correct Rustcript Code (19_modules_main.rc):

# /examples/19_modules_main.rc
print '--- 19 MODULES SYSTEM ---'

# 1. Import with Alias
print '1. Importing Library...'
import '19_modules_lib.rc' as Service

# 2. Variable Isolation Check:
STATUS = 'Main_Idle'
print '   Main Scope STATUS:    {STATUS}'
print '   Service Scope STATUS: {Service.STATUS}'

# 3. Module Function Calls
current = Service.get_status()
print '   Result from getter: {current}'
print '--- END ---'

Example 8: JSON Handling

Task: Parse a JSON string into a Rustcript HashMap. Modify the data. Then, stringify the modified data back into both compact and pretty-printed JSON strings.

Thought Process:

  1. Define a multi-line string variable json_str containing valid JSON.
  2. Use data = json.parse(json_str) to convert it to a Rustcript value.
  3. Access and print properties like data.name and data.skills.0 to verify parsing.
  4. Modify the data in-place, e.g., data.age = 26 and method data.skills.push('Java').
  5. Use compact = json.stringify(data) for the compact version.
  6. Use pretty = json.stringify(data, true) for the pretty-printed version.
  7. Print all results.

Correct Rustcript Code:

# /examples/20_json.rc
print '--- 20 JSON SUPPORT ---'

# 1. Parsing JSON
json_str = '{"name": "Alice", "age": 25, "skills": ["Rust", "Python"]}'
data = json.parse(json_str)
print '   Parsed Name: {data.name}'

# 2. Modifying Data
data.age = 26
method data.skills.push('Java')
print '   Modified Object: {data}'

# 3. Stringify (Compact)
compact = json.stringify(data)
print '   Compact: {compact}'

# 4. Stringify (Pretty)
pretty = json.stringify(data, true)
print '   Pretty:\n{pretty}'

print '--- END ---'

Example 9: Advanced Task - The Test Runner

BEFORE YOU BEGIN: This task is complex. It requires you to write a script that executes other scripts. This is accomplished using the os.exec() function from the os module. Remember these key points:

  • The os module is feature-gated. The interpreter must be compiled with the os_access feature for os.exec() to work.
  • os.exec(command_string) executes a shell command and returns the process's exit code. An exit code of 0 typically means success. A non-zero code means an error occurred.
  • You will need to build the command strings dynamically using string concatenation (+).
  • Some tests require special command-line arguments (like the file I/O test).
  • One test is expected to fail (the infinite loop safety test). Your script must correctly handle this expected failure.

Task: Write a master script that iterates through a list of all example script files and executes each one using the compiled rustcript binary. The script should track passes and failures and print a final summary. It must correctly handle the special cases for the I/O test and the safety limit test.

Thought Process:

  1. Setup: Create a Vector named tests to hold the file paths of all scripts to be tested. Create passed, failed, and total counters, initialized to 0. Define the path to the interpreter binary in a variable.
  2. Populate List: Use method tests.push(...) to add each example file path to the tests vector.
  3. Main Loop: Use a foreach test_file in tests loop to iterate through the list.
  4. Command Building: Inside the loop, construct the command string for os.exec.
    • Start with the binary path: cmd = binary + ' '.
    • Check if the current test_file is the special I/O test (23_file_io.rc). If it is, append the required sandbox and permission flags (--sandbox ./examples --allow-read ...).
    • Finally, append the test_file path itself to the command string.
  5. Execution: Call exit_code = os.exec(cmd) to run the test.
  6. Result Logic:
    • Check if the current test_file is the special safety test (22_safety_limit.rc).
      • If it is, a non-zero exit_code is a PASS. A zero code is a FAIL.
    • For all other tests:
      • An exit_code of 0 is a PASS.
      • A non-zero exit_code is a FAIL.
  7. Reporting:
    • Inside the loop, print the status (PASS/FAIL) for each test.
    • Increment the appropriate counters (passed or failed).
  8. Final Summary: After the loop finishes, print the final counts and an overall SUCCESS or FAILURE message based on whether failed is greater than 0.

Correct Rustcript Code:

# /tests/test_runner.rc
print '--- AUTOMATED TEST RUNNER ---'

# 1. Define list of tests
tests = {}
method tests.push('examples/01_basics.rc')
method tests.push('examples/02_math_and_types.rc')
# ... (add all other test files here) ...
method tests.push('examples/22_safety_limit.rc')
method tests.push('examples/23_file_io.rc')
method tests.push('examples/24_first_class_funcs.rc')

# 2. Setup
passed = 0
failed = 0
total = 0
binary = './target/release/rustcript' # Use ./ for Linux/macOS
sep = ' '

# 3. Execution Loop
foreach test_file in tests [
    total += 1
    print '[TEST] Running {test_file} ...'

    is_sandbox = test_file.contains('23_file_io')
    is_safety = test_file.contains('22_safety_limit')

    # Build the command string
    cmd = binary
    if is_sandbox [
        cmd = cmd + ' --sandbox ./examples --allow-read --allow-write --allow-delete'
    ]
    cmd = cmd + sep + test_file

    # Execute and get the exit code
    exit_code = os.exec(cmd)

    # 4. Check the result
    if is_safety [
        if exit_code != 0 [
            print '   -> PASS (Expected Failure)'
            passed += 1
        ] else [
            print '   -> FAIL (Expected Failure but got Success)'
            failed += 1
        ]
    ] else [
        if exit_code == 0 [
            print '   -> PASS'
            passed += 1
        ] else [
            print '   -> FAIL (Exit Code: {exit_code})'
            failed += 1
        ]
    ]
]

# 5. Summary
print '-----------------------------'
print 'Summary: {passed} / {total} Passed.'

if failed > 0 [
    print 'RESULT: FAILURE'
] else [
    print 'RESULT: SUCCESS'
]

7. Anti-Patterns & Common Pitfalls

AVOID these common mistakes:

  • DON'T use double quotes for strings. ONLY single ('...') or triple ('''...''') quotes are valid.
    • print "hello" -> WRONG
    • print 'hello' -> CORRECT
  • DON'T forget to quote string literals. The parser will think it's a variable.
    • name = Alice -> WRONG (Tries to find a variable named Alice)
    • name = 'Alice' -> CORRECT
  • DON'T use commas inside if or while conditions. They are space-separated.
    • if x > 10, y < 5 -> WRONG
    • if x > 10 && y < 5 -> CORRECT
  • DON'T forget that for loop ranges are exclusive of the end value.
    • for i 1 3 -> Loops for i = 1 and i = 2.
  • DON'T assume x = ... inside a function will modify a global x. It will create a new local variable x if one doesn't already exist in the local scope. Use global x = ... to be explicit.