Skip to content

Variables & Scopes

Variables are one of the core parts of EasyLang programs.
They allow you to store values, reuse data, and pass information between different parts of your code.

In this chapter, you will learn:

  • how variables are created
  • how assignment works
  • how EasyLang stores variables internally
  • how scopes work (global, local, function, and module scopes)
  • how nested blocks behave
  • how name resolution works

Variable Creation

Variables are created using: we let <name> = <expression>

Examples:

we let x = 10
we let name = "GreenBugX"
we let nums = [1, 2, 3]

EasyLang automatically assigns the value of the right-hand expression to the variable name.

Reassignment Example we let x = x plus 1

This reads the old value of x, evaluates the expression, then redefines x.


Rules for Variable Names

Variable names must:

  • start with a letter or _
  • contain only letters, digits, and _
  • not be a keyword (we let, if, else, repeat, etc.)

Valid:

x
user_name
count2
_value

Invalid:

2x
if
true


Scope Basics

A scope determines where a variable is visible.

EasyLang supports:

  • Global Scope
  • Local Function Scope
  • Inner Block Scope
  • Module Scope (for imports)

The interpreter internally uses nested dictionaries to store these scopes.


Global Scope

Any variable declared outside of a function belongs to the global scope.

Example:

we let x = 100
so print x   $ works

Global variables can be accessed anywhere except inside modules unless explicitly passed.


Local Function Scope

Inside functions, EasyLang creates a new inner scope.

Example:

define test(): do [
    we let x = 10
    so print x
]

test()
so print x   $ error: x does not exist here

Variables defined inside functions do not leak into the global scope.

Function Arguments also live in local scope:

define greet(name): do [
    so print "Hello, " plus name
]

name is only valid inside the function.


Block Scopes

Blocks created with [ ... ] also introduce their own scope.

Example:

if true then [
    we let a = 5
    so print a   $ valid
]

so print a      $ error

The variable a exists only inside the block.

Loop Blocks Also Create Scope

repeat from i = 1 to 3: do [
    we let x = i mul 2
    so print x
]

so print x   $ error

Each iteration inherits the loop scope, but outside, x does not exist.


Name Resolution (How EasyLang Finds Variables)

When you use a variable: so print x

EasyLang looks for x in:

  • Local scope (function or block)
  • Parent scopes (block inside block)
  • Global scope
  • Module scope (if using module properties)
  • If not found → Runtime Error with a friendly message

Example:

we let x = 100

define demo(): do [
    so print x     $ found in global!
]

demo()


Shadowing Variables

A variable in a lower (inner) scope shadows one in a higher scope.

Example:

we let x = 10

define test(): do [
    we let x = 5
    so print x   $ prints 5
]

test()
so print x       $ prints 10

Inner scope variable overrides the outer one temporarily.


Module Scope

When importing a module via: bring "math.elangh" as math

EasyLang loads the module and creates a module namespace.

Calling: math.sqrt(25)

looks in the module’s own variable environment, not the global one.

Modules cannot access global variables unless you explicitly pass them in.


Scopes in File I/O Blocks

File operations do not create new scopes by themselves:

open "out.txt" as f for write
    writeline f with "hello"
close f

so print f   $ error: f is not a normal variable

The open keyword creates a special internal tracking object, not a variable binding in user scope.


Common Errors (and Why They Happen)

❌ Using a variable before defining it

so print x   $ error
we let x = 10

❌ Accessing a variable outside its scope

if true then [
    we let a = 50
]

so print a   $ error

❌ Shadowing confusion

we let x = 10

define f(): do [
    we let x = "inner"
    ...
]

Here, x inside the function is not the same as global x.


Example: Scopes Working Together

we let g = 1   $ global

define outer(): do [
    we let o = 2   $ outer local

    define inner(): do [
        we let i = 3   $ inner local
        so print g     $ 1
        so print o     $ 2
        so print i     $ 3
    ]

    inner()
]

outer()

Output:

1
2
3

Because EasyLang resolves names from inner → outer → global.


Summary

  • Every variable has a scope.
  • Functions and blocks create new local scopes.
  • Global variables live outside all functions.
  • Modules have their own namespaces.
  • Inner scopes can see outer scopes, but not vice-versa.
  • Name resolution moves outward until a match is found.

Next Steps

Continue to Functions to learn how Functions work in detail