Gem Documentation
Complete guide to the Gem programming language
Getting Started
Learn how to install Gem and write your first program. Gem is a statically-typed programming language designed for safety, performance, and developer productivity.
Installation
Gem is distributed as source code that you compile locally. This ensures optimal performance for your system and gives you access to the latest features.
Prerequisites
- A C compiler (GCC, Clang, or MSVC)
- Make (on Unix-like systems)
- Git (to clone the repository)
Building from Source
# Clone the repository
git clone https://github.com/SimuCorps/Gem.git
cd Gem
# Build Gem with standard library (recommended)
make
# Or build without standard library for minimal installation
make no-stl
# Install system-wide (optional)
sudo make install
This will create a gemc
executable in the project directory. If you installed system-wide, you can run gemc
from anywhere.
💡 Build Options
make
ormake gemc
- Full build with standard librarymake no-stl
ormake gemch
- Minimal build without STLmake clean
- Remove build artifactsmake test
- Run the test suite
Your First Program
Let's write a simple "Hello, World!" program to verify your installation works correctly.
Create a File
Create a new file called hello.gem
:
# hello.gem - Your first Gem program
puts "Hello, World!"
# Variables with type safety
string name = "Gem"
int version = 1
puts "Welcome to #{name} v#{version}!"
# Functions
def greet(string person) string
return "Hello, #{person}!"
end
puts greet("Developer")
Run the Program
# Run your program
./gemc hello.gem
You should see output like:
Hello, World! Welcome to Gem v1! Hello, Developer!
Interactive REPL
Gem includes an interactive Read-Eval-Print Loop (REPL) that's perfect for experimenting with the language and testing small code snippets.
Starting the REPL
# Start the interactive shell
./gemc
You'll see a prompt where you can type Gem code:
> puts "Hello from REPL!" Hello from REPL! > int x = 42 > puts x 42 > string! message = "Mutable string" > message = "Changed!" > puts message Changed! >
🎯 REPL Tips
- Press Ctrl+C or Ctrl+D to exit
- Variables persist between statements
- Type checking works in real-time
- Great for learning and experimentation
Basic Concepts
Here are the fundamental concepts you need to understand to start programming in Gem:
Type Safety
Gem is statically typed, meaning all variables must have explicit types:
# Basic types
int age = 25
string name = "Alice"
bool isActive = true
# Type errors are caught at compile time
# age = "not a number" # ❌ Error!
Mutability Control
Variables are immutable by default. Use mut
to make them mutable (or !
as syntactic sugar):
# Immutable by default
string message = "Hello"
# Mutable with 'mut' keyword (recommended)
mut string mutableMessage = "Hello"
mutableMessage = "Changed!"
# Mutable with '!' suffix (alternative)
string! altMutableMessage = "Hello"
altMutableMessage = "Changed!"
Nullability Safety
Use ?
to allow variables to be nil
:
# Nullable variable
string? optionalName = nil
optionalName = "Alice"
# Must check before use
if (optionalName != nil)
puts "Hello, #{optionalName}!"
end
Functions
Functions have explicit parameter and return types:
# Function with parameters and return type
def add(int a, int b) int
return a + b
end
# Function with no return value
def greet(string name) void
puts "Hello, #{name}!"
end
Language Overview
Gem is a statically-typed programming language designed for safety, performance, and developer productivity. It combines modern language features with compile-time guarantees to prevent common programming errors.
Key Principles
- Safety First - Memory safety and type safety are built into the language
- Explicit is Better - Clear, explicit syntax over implicit behavior
- Immutable by Default - Variables are immutable unless explicitly marked mutable
- Null Safety - Nullable types prevent null reference errors
- Zero-Cost Abstractions - High-level features with minimal runtime overhead
Program Structure
# Comments start with #
# This is a simple Gem program
# Variable declarations
string message = "Hello, World!"
int count = 42
# Function definition
def greet(string name) string
return "Hello, #{name}!"
end
# Main execution
puts message
puts greet("Gem")
Syntax and Statement Terminators
Gem features flexible statement termination that prioritizes readability and developer convenience.
Optional Semicolons
In Gem, semicolons are optional. Statements can be terminated with either semicolons or newlines, giving you the flexibility to choose the style that works best for your code.
# Newline-terminated statements (recommended)
string name = "Alice"
int age = 30
puts "Hello, " + name
# Semicolon-terminated statements (traditional)
string name = "Alice";
int age = 30;
puts "Hello, " + name;
# Mixed style (both work)
string name = "Alice"
int age = 30;
puts "Hello, " + name
When Semicolons Are Required
Semicolons are still required when you want to place multiple statements on the same line:
# Multiple statements on one line require semicolons
int x = 10; int y = 20; puts x + y
# This would be an error:
# int x = 10 int y = 20 puts x + y # ❌ Error
# Prefer separate lines for better readability:
int x = 10
int y = 20
puts x + y
Best Practices
- Use newlines - They're cleaner and more readable
- Be consistent - Pick one style and stick with it in your project
- Semicolons for inline - Use semicolons only when placing multiple statements on one line
- Follow your team - Match the style of your existing codebase
Type System
Gem features a rich static type system with enhanced safety features.
Basic Types
# Primitive types
int number = 42; # Integer numbers
string text = "Hello"; # Text strings
bool flag = true; # Boolean values (true/false)
# Special types
void # Used for functions that don't return a value
func # Function type for first-class functions
obj # Object type for class instances
Type Modifiers
Gem uses the mut
keyword and suffix modifiers to control mutability and nullability:
# Mutability with 'mut' keyword (recommended)
mut string mutableText = "Can change"; # Mutable variable
mutableText = "Changed!"; # ✅ Valid
# Mutability with '!' suffix (syntactic sugar)
string! alsoMutable = "Can change"; # Alternative syntax
alsoMutable = "Changed!"; # ✅ Valid
# Nullability modifier (?)
string? optionalText = nil; # Can be nil
optionalText = "Some text"; # ✅ Valid
optionalText = nil; # ✅ Valid
# Combined: mutable and nullable
mut string? mutableOptional = nil; # Recommended syntax
string?! alsoMutableOptional = nil; # Alternative syntax
mutableOptional = "Text"; # ✅ Valid
mutableOptional = nil; # ✅ Valid
Type Safety Rules
- All variables must have explicit types
- Type mismatches are caught at compile time
- Immutable variables cannot be reassigned
- Non-nullable variables cannot be assigned
nil
- Nullable variables must be checked before use
Hashes
Hashes are key-value data structures that allow you to store and retrieve data using string or numeric keys.
Hash Declaration
# Hash declaration with explicit mutability
hash! userInfo = {"name": "Alice", "age": 30, "active": true};
# Immutable hash (read-only after creation)
hash readonlyData = {"version": "1.0", "stable": true};
# Empty hash
hash! emptyHash = {};
Accessing Hash Values
# Access values using bracket notation
hash! person = {"name": "Bob", "age": 25, "city": "New York"};
string name = person["name"] as string; # "Bob"
int age = person["age"] as int; # 25
string city = person["city"] as string; # "New York"
# Non-existent keys return nil
string missing = person["missing"] as string; # "nil"
Modifying Hash Values
# Only mutable hashes can be modified
hash! mutableHash = {"count": 0, "status": "pending"};
# Update existing values
mutableHash["count"] = 5;
mutableHash["status"] = "completed";
# Add new key-value pairs
mutableHash["timestamp"] = "2025-01-01";
# This would be an error with immutable hash:
# hash immutableHash = {"key": "value"};
# immutableHash["key"] = "new value"; # ❌ Error: Cannot modify immutable hash
Hash Keys
# String keys (most common)
hash! stringKeys = {"name": "Alice", "role": "admin"};
# Numeric keys (automatically converted to strings)
hash! numericKeys = {1: "first", 2: "second", 42: "answer"};
# Mixed keys
hash! mixedKeys = {"name": "Bob", 1: "first", "active": true};
Hash Best Practices
- Use descriptive string keys for better readability
- Always cast hash values to the expected type using
as
- Check for key existence when accessing potentially missing values
- Use mutable hashes (
hash!
) only when you need to modify them
Type Coercion
Type coercion allows you to explicitly convert values between different types using the as
keyword.
Basic Type Conversions
# Number to string conversion
int number = 42;
string numberStr = number as string; # "42"
# String to number conversion
string numericStr = "123";
int parsed = numericStr as int; # 123
# Decimal strings are converted to their numeric value
string decimalStr = "3.14159";
int decimal = decimalStr as int; # 3.14159
# Boolean to string conversion
bool isActive = true;
string activeStr = isActive as string; # "true"
# Nil to string conversion
string nilStr = nil as string; # "nil"
Truthiness
In Gem, only false
and nil
are falsey. All other values, including 0
and empty strings, are truthy.
# Numbers are truthy (including zero!)
int zero = 0;
int negative = -5;
int positive = 42;
bool zeroIsTruthy = zero as bool; # true
bool negativeIsTruthy = negative as bool; # true
bool positiveIsTruthy = positive as bool; # true
# Strings are truthy (including empty strings!)
string empty = "";
string nonEmpty = "hello";
bool emptyIsTruthy = empty as bool; # true
bool nonEmptyIsTruthy = nonEmpty as bool; # true
# Only false and nil are falsey
bool stillFalse = false as bool; # false
bool stillNil = nil as bool; # false (nil converts to false)
Hash Value Coercion
# Extract and convert hash values
hash! userData = {"name": "Charlie", "age": 28, "score": 0, "active": true};
# Extract with type coercion
string name = userData["name"] as string;
int age = userData["age"] as int;
bool active = userData["active"] as bool;
# Zero from hash is still truthy
bool scoreActive = userData["score"] as bool; # true (0 is truthy!)
# Missing keys return nil, which converts to "nil" string or false boolean
string missing = userData["missing"] as string; # "nil"
bool missingBool = userData["missing"] as bool; # false
Identity Casts
# Casting to the same type (no-op, but sometimes useful for clarity)
string alreadyString = "test";
string stillString = alreadyString as string; # "test"
int alreadyInt = 100;
int stillInt = alreadyInt as int; # 100
bool alreadyBool = true;
bool stillBool = alreadyBool as bool; # true
Type Coercion Rules
- Numbers to strings: Converted to their string representation
- Strings to numbers: Parsed as numeric values (must be valid numbers)
- Booleans to strings:
true
→"true"
,false
→"false"
- Nil to strings: Always converts to
"nil"
- Any to boolean: Only
false
andnil
are falsey - Identity casts: Casting to the same type returns the original value
💡 Truthiness
Unlike many languages where 0
, empty strings, or empty collections are falsey, in Gem, only false
and nil
are considered falsey. This makes boolean logic more predictable and explicit.
0 as bool
→true
✅"" as bool
→true
✅false as bool
→false
❌nil as bool
→false
❌
Variables
Variables in Gem are immutable by default and require explicit type declarations.
Declaration Syntax
# Recommended syntax with 'mut' keyword
int age = 25; # Immutable integer
mut string name = "Alice"; # Mutable string
bool? isActive = nil; # Nullable boolean
mut string? status = "pending"; # Mutable and nullable
# Alternative syntax with '!' suffix (syntactic sugar)
string! altName = "Bob"; # Mutable string (alternative)
string?! altStatus = "active"; # Mutable and nullable (alternative)
Scope Rules
# Variables are scoped to their declaration block
int x = 10;
begin
int y = 20;
puts x; # ✅ Valid: x is in scope
puts y; # ✅ Valid: y is in scope
end
puts x; # ✅ Valid: x is still in scope
# puts y; # ❌ Error: y is out of scope
Mutability Examples
# Immutable variables (default)
string greeting = "Hello";
# greeting = "Hi"; # ❌ Error: Cannot modify immutable variable
# Mutable variables (recommended syntax)
mut string mutableGreeting = "Hello";
mutableGreeting = "Hi"; # ✅ Valid: Variable is mutable
# Mutable variables (alternative syntax with '!' suffix)
string! altMutableGreeting = "Hello";
altMutableGreeting = "Hi"; # ✅ Valid: Variable is mutable
# Function parameters are immutable by default
def processText(string text) string
# text = "modified"; # ❌ Error: Parameter is immutable
return "Processed: " + text;
end
Functions
Functions in Gem are first-class values with strong type checking.
Function Declaration
# Basic function syntax
def functionName(type param1, type param2) returnType
# function body
return value;
end
# Examples
def add(int x, int y) int
return x + y;
end
def greet(string name) string
return "Hello, #{name}!";
end
def printMessage(string message) void
puts message;
# void functions don't need explicit return
end
Function Parameters
# Parameters with different modifiers
def processData(
string data, # Immutable parameter
mut string buffer, # Mutable parameter (recommended)
string? optional, # Nullable parameter
mut string? mutableOpt # Mutable and nullable (recommended)
) string
if (optional != nil)
buffer = buffer + optional;
end
return buffer + data;
end
# Alternative syntax with '!' suffix
def processDataAlt(
string data, # Immutable parameter
string! buffer, # Mutable parameter (alternative)
string? optional, # Nullable parameter
string?! mutableOpt # Mutable and nullable (alternative)
) string
if (optional != nil)
buffer = buffer + optional;
end
return buffer + data;
end
Higher-Order Functions
# Functions as parameters
def applyOperation(int x, int y, func operation) int
return operation(x, y);
end
def multiply(int a, int b) int
return a * b;
end
# Usage
int result = applyOperation(5, 3, multiply); # 15
Classes and Objects
Gem supports object-oriented programming with classes, inheritance, and encapsulation.
Class Definition
# Basic class structure
class ClassName
# Constructor
def init(parameters) void
# Initialize instance variables
end
# Methods
def methodName(parameters) returnType
# method body
end
end
# Example
class Person
def init(string name, int age) void
this.name = name;
this.age = age;
end
def getName() string
return this.name;
end
def getAge() int
return this.age;
end
def introduce() string
return "Hi, I'm " + this.name + ", age " + this.age;
end
end
Object Creation and Usage
# Creating objects
obj person = Person("Alice", 30);
# Calling methods
string name = person.getName();
string intro = person.introduce();
puts intro; # "Hi, I'm Alice, age 30"
Inheritance
# Base class
class Animal
def init(string name) void
this.name = name;
end
def speak() string
return "Some sound";
end
end
# Derived class
class Dog : Animal
def init(string name, string breed) void
super.init(name); # Call parent constructor
this.breed = breed;
end
def speak() string # Override parent method
return "Woof!";
end
def getBreed() string
return this.breed;
end
end
# Usage
obj dog = Dog("Buddy", "Labrador");
puts dog.speak(); # "Woof!"
puts dog.getBreed(); # "Labrador"
Control Flow
Gem provides standard control flow constructs with type-safe conditions.
Conditionals
# if-else statements
if (condition)
# code block
else if (otherCondition)
# code block
else
# code block
end
# Example
int age = 20;
if (age >= 18)
puts "Adult";
else
puts "Minor";
end
# Ternary operator
string status = age >= 18 ? "Adult" : "Minor";
Loops
# For loops
for (int! i = 0; i < 10; i = i + 1)
puts i;
end
# While loops
int! count = 0;
while (count < 5)
puts "Count: " + count;
count = count + 1;
end
# Loop with break and continue
for (int! i = 0; i < 20; i = i + 1)
if (i % 2 == 0)
continue; # Skip even numbers
end
if (i > 15)
break; # Exit loop
end
puts i;
end
Modules
Modules provide a way to organize and reuse code across different parts of your program.
Module Definition
# math_utils.gem
module MathUtils
def square(int x) int
return x * x;
end
def cube(int x) int
return x * x * x;
end
def factorial(int n) int
if (n <= 1)
return 1;
else
return n * factorial(n - 1);
end
end
end
Using Modules
# Import and use modules
require "math_utils";
int squared = MathUtils.square(5); # 25
int cubed = MathUtils.cube(3); # 27
int fact = MathUtils.factorial(5); # 120
Standard Library Modules
# Built-in modules
require "math";
# Math utilities
int sum = Math.add(10, 5);
int max = Math.max(10, 20);
int power = Math.power(2, 8);
Memory Safety
Gem provides automatic memory management with compile-time safety guarantees.
Scope-Based Management
# Objects are automatically cleaned up when scope ends
def processData() void
begin
obj resource = DataResource("file.txt");
resource.process();
# resource is automatically cleaned up here
end
# resource is no longer accessible
end
Borrow Checking
# Gem prevents use-after-drop at compile time
def borrowExample() void
obj resource = SomeResource("data");
begin
# resource is borrowed here
string some_variable = resource.process()
end
puts some_variable; # ❌ Compile error: use after potential drop
end
Memory Safety Rules
- Objects are automatically dropped when they go out of scope
- No manual memory management required
- Compile-time checks prevent use-after-drop
- No null pointer dereferences
- No memory leaks or double-free errors
💡 Memory Safety Benefits
- Eliminates entire classes of bugs
- No runtime overhead for safety checks
- Predictable performance characteristics
- Easier to reason about program behavior
Standard Library
Gem comes with a standard library that provides essential functionality for common programming tasks. The standard library is designed to be lightweight, efficient, and type-safe.
Available Modules
- Math Module - Mathematical operations
- HTTP Module - HTTP client functionality for web requests
- Time Module - Time operations, measurements, and duration handling
Built-in String Operations
String operations are handled through built-in operators:
# String concatenation (built-in)
string combined = "Hello" + " World"; # Returns "Hello World"
# String literals
string greeting = "Hello, Gem!";
string empty = "";
Design Principles
- Type Safety - All functions are fully typed with compile-time checking
- Immutability - Functions don't modify their inputs unless explicitly designed to
- Null Safety - Functions handle nullable types safely
- Consistency - Uniform naming and behavior across modules
📦 Module System
Use require "module_name"
to import standard library modules.
Importing Modules
Learn how to import and use standard library modules in your Gem programs.
Basic Import Syntax
# Import math module
require "math";
# Import HTTP module
require "http";
# Use imported modules
int sum = Math.add(10, 5);
hash response = Http.get("https://api.example.com");
Module Availability
📦 Standard Library Modules
"math"
- Mathematical operations"http"
- HTTP client functionality"time"
- Time operations and duration handling
Math Module
The Math module provides mathematical operations and utility functions.
Importing the Math Module
require "math";
Basic Arithmetic
# Basic operations
int sum = Math.add(10, 5); # Returns 15
int difference = Math.subtract(10, 5); # Returns 5
int product = Math.multiply(10, 5); # Returns 50
int quotient = Math.divide(10, 5); # Returns 2
int remainder = Math.modulo(10, 3); # Returns 1
Power Operations
# Exponentiation
int squared = Math.power(5, 2); # Returns 25
int cubed = Math.power(3, 3); # Returns 27
int power = Math.power(2, 8); # Returns 256
Comparison and Selection
# Min/Max operations
int minimum = Math.min(10, 5); # Returns 5
int maximum = Math.max(10, 5); # Returns 10
Absolute Value
# Absolute value
int abs1 = Math.abs(-5); # Returns 5
int abs2 = Math.abs(5); # Returns 5
int abs3 = Math.abs(0); # Returns 0
Math Module Function Reference
# Complete Math module API
Math.add(int a, int b) int # Addition
Math.subtract(int a, int b) int # Subtraction
Math.multiply(int a, int b) int # Multiplication
Math.divide(int a, int b) int # Division
Math.modulo(int a, int b) int # Modulo operation
Math.power(int base, int exp) int # Exponentiation
Math.abs(int x) int # Absolute value
Math.min(int a, int b) int # Minimum of two values
Math.max(int a, int b) int # Maximum of two values
HTTP Module
The HTTP module provides a comprehensive HTTP client for making web requests. It supports all major HTTP methods, custom headers, request data, and detailed response handling.
Importing the HTTP Module
require "http";
Basic GET Request
# Simple GET request
hash response = Http.get("https://api.example.com/data");
# Check if request was successful
bool success = response["success"] as bool;
int status = response["status"] as int;
string body = response["body"] as string;
if (success)
puts "Request successful!";
puts "Status: " + status;
puts "Response: " + body;
else
puts "Request failed with status: " + status;
end
GET Request with Custom Headers
# GET request with custom headers
hash headers = {};
headers["Authorization"] = "Bearer your-token-here";
headers["Content-Type"] = "application/json";
headers["User-Agent"] = "MyApp/1.0";
hash response = Http.get("https://api.example.com/protected", headers);
# Access response details
bool success = response["success"] as bool;
int status = response["status"] as int;
string body = response["body"] as string;
int responseTime = response["response_time"] as int;
POST Request with Data
# POST request with form data
hash data = {};
data["name"] = "John Doe";
data["email"] = "john@example.com";
data["age"] = "30";
hash response = Http.post("https://api.example.com/users", data);
if (response["success"] as bool)
puts "User created successfully!";
puts "Response: " + (response["body"] as string);
end
POST Request with JSON and Headers
# POST request with JSON data and custom headers
hash headers = {};
headers["Content-Type"] = "application/json";
headers["Authorization"] = "Bearer token123";
hash jsonData = {};
jsonData["title"] = "New Post";
jsonData["content"] = "This is the post content";
jsonData["published"] = "true";
hash response = Http.post("https://api.example.com/posts", jsonData, headers);
# Check response
bool success = response["success"] as bool;
int status = response["status"] as int;
if (success)
puts "Post created! Status: " + status;
else
puts "Failed to create post. Status: " + status;
end
PUT Request for Updates
# PUT request to update a resource
hash updateData = {};
updateData["name"] = "Jane Doe";
updateData["email"] = "jane@example.com";
hash headers = {};
headers["Content-Type"] = "application/json";
hash response = Http.put("https://api.example.com/users/123", updateData, headers);
if (response["success"] as bool)
puts "User updated successfully!";
puts "Response time: " + (response["response_time"] as int) + "ms";
end
DELETE Request
# DELETE request with authorization
hash headers = {};
headers["Authorization"] = "Bearer admin-token";
hash response = Http.delete("https://api.example.com/users/123", headers);
bool success = response["success"] as bool;
int status = response["status"] as int;
if (success)
puts "Resource deleted successfully!";
else
puts "Failed to delete resource. Status: " + status;
end
Error Handling
# Comprehensive error handling
hash response = Http.get("https://invalid-url-example.com");
bool success = response["success"] as bool;
int status = response["status"] as int;
if (!success)
if (status == 0)
puts "Network error or invalid URL";
elsif (status == 404)
puts "Resource not found";
elsif (status == 500)
puts "Server error";
else
puts "Request failed with status: " + status;
end
else
puts "Request successful!";
string body = response["body"] as string;
puts "Response: " + body;
end
HTTP Module Function Reference
# Complete HTTP module API
# GET requests
Http.get(string url) hash # Simple GET
Http.get(string url, hash headers) hash # GET with headers
# POST requests
Http.post(string url, hash data) hash # POST with data
Http.post(string url, hash data, hash headers) hash # POST with data and headers
# PUT requests
Http.put(string url, hash data) hash # PUT with data
Http.put(string url, hash data, hash headers) hash # PUT with data and headers
# DELETE requests
Http.delete(string url) hash # Simple DELETE
Http.delete(string url, hash headers) hash # DELETE with headers
# Response hash structure:
# {
# "success": bool, # Whether request succeeded
# "status": int, # HTTP status code (0 for network errors)
# "body": string, # Response body content
# "response_time": int # Response time in microseconds
# }
Common HTTP Status Codes
📡 HTTP Status Codes
200
- OK (Success)201
- Created400
- Bad Request401
- Unauthorized403
- Forbidden404
- Not Found500
- Internal Server Error0
- Network Error/Invalid URL
Time Module
The Time module provides basic time-related functionality including current time access, sleep operations, and function timing.
Importing the Time Module
require "time";
Current Time
# Get current time as floating-point timestamp
puts "Current timestamp: #{Time.now()}";
Sleep Operations
# Sleep for a specified number of milliseconds
puts "Starting operation...";
Time.sleep(2000); # Sleep for 2 seconds (2000 milliseconds)
puts "Operation completed after delay";
Function Timing
# Measure execution time of a function
def slowOperation() void
Time.sleep(1000); # Sleep for 1 second (1000 milliseconds)
puts "Slow operation completed";
end
# Measure function execution
Time.measure(slowOperation);
Timeout Operations
# Attempt to run function with timeout
def quickOperation() void
puts "Quick operation";
end
def slowOperation() void
Time.sleep(5000); # Sleep for 5 seconds (5000 milliseconds)
puts "This takes too long";
end
Time.timeout(quickOperation, 2); # 2 second timeout
Time.timeout(slowOperation, 2); # 2 second timeout
Practical Time Examples
# Simple timing and sleep operations
def timedWork() void
puts "Starting work...";
Time.sleep(1500); # 1.5 seconds of work
puts "Work completed";
end
# Measure the work
puts "Timing work operation:";
Time.measure(timedWork);
# Multiple sleep operations
puts "Sleep sequence:";
Time.sleep(500); # 0.5 seconds
puts "Step 1 complete";
Time.sleep(1000); # 1.0 seconds
puts "Step 2 complete";
Time.sleep(750); # 0.75 seconds
puts "All steps complete";