Chapter 2
Tutorial
This chapter is a hands-on introduction to Scaly. It gets you from an empty file to a running program, then takes you on a quick tour of the language. Later chapters cover everything here in depth; the goal now is to write and run real code as fast as possible.
Getting Started
To try Scaly before installing anything, a browser is enough. The tour at scaly.io/play/tour.html runs the programs of A Tour of Scaly step by step, and you can change them; the REPL at scaly.io/play is the interactive session of Trying Things Out. The compiler runs in the browser tab itself: nothing is sent anywhere.
Installing Scaly
Scaly is installed by one command. On macOS and Linux, in a terminal:
curl -fsSL https://scaly.io/install.sh | shOn macOS 26.4 and newer, Terminal stops a pasted command
that downloads something with a warning, Possible Malware,
Paste Blocked
— this one too. Choose Paste
Anyway; the script it runs can be read first at
scaly.io/install.sh.
On Windows, in PowerShell (Scaly on Windows goes through the installation there step by step, and through working with VS Code and Visual Studio):
irm https://scaly.io/install.ps1 | iexOpen a new terminal afterwards. The installer has put three
commands on your PATH: scaly, the
tool you will use most, scalyc, the compiler by
itself, and scalyls, the language server your
editor talks to. Everything else it installed lives in
~/.scaly. Installation has
the details: which systems get Scaly ready to run, what the others
need, how to ask for a particular version, and how to remove it
again.
Hello, World!
A Scaly program is just a file of statements — there is no
main function to declare and nothing to import. Put this
single line in a file called hello.scaly:
print "Hello, World!"Run it:
scaly run hello.scalyThe program prints:
Hello, World!scaly run compiles the file in memory and
runs it at once; nothing is left on disk. To get a program you can keep
and hand to someone else, build it:
scaly build hello.scaly -o hello
./helloAdd --release for the optimised build: the whole
program — your code, the standard library and every package it uses —
is optimised as one piece.
scaly new hello writes such a start for
you: a directory hello with
hello.scaly in it — a function, a test for it
(scaly test hello.scaly runs every function
whose name begins with test) and the line that
prints.
That is the whole edit-compile-run loop. Everything that follows is just more interesting things to put between the braces.
Trying Things Out
scaly without arguments starts an interactive
session. An expression shows its value and its type; a
let or var keeps a value for the entries that
follow:
$ scaly
scaly> 6 * 7
it: int = 42
scaly> let name "Scaly"
name: String = "Scaly"
scaly> :where name
name: String
the value 8 bytes, on the session page of its entry
its text 5 bytes, on a page of its own
scaly> :quit:where shows where a value lives — Scaly
manages memory in regions, and here you can watch it do so.
:help lists the other commands.
Using Scaly with Visual Studio Code on macOS and Linux
On Windows, follow Scaly on Windows instead: the installation is different there.
You can drive the whole edit-compile-run-debug loop from
Visual Studio Code. The
Scaly extension provides syntax highlighting and
connects the editor to the scalyls language server
you installed in Installing Scaly — giving live error
squiggles, hover, go-to-definition, completion, rename, and
more.
If you do not have VS Code yet, download it from
code.visualstudio.com
(installers for macOS and Linux; on Ubuntu,
sudo snap install code --classic also works).
Installing the Scaly Extension
The Scaly extension is distributed as a .vsix
package on scaly.io. Download
scaly.io/downloads/scaly-vscode.vsix,
then install it from within VS Code: open the
Extensions view, click the
··· menu in its title bar, choose
Install from VSIX…, and pick the downloaded
file.
The extension launches scalyls from your
PATH, so install the toolchain first
(Installing Scaly). If VS Code was already running when
you installed the toolchain, restart it (not just the window) so its
process picks up the updated PATH. Should the server
binary live somewhere unusual, point the
scaly.server.path setting at it.
What the Language Server Gives You
Open any folder, create a file double.scaly,
and type a small program:
function double(n: int) returns int
n * 2
let d double(21)
if d = 42
print "doubled!"As you type, the language server is at work. Things to try:
Diagnostics — misspell a call (say, change
double(21)todoubel(21)): a red squiggle appears and the Problems panel showsfunction not found: doubel. Fix it and the squiggle clears.Hover — rest the mouse on
doubleor a binding to see its signature or type.Go to Definition (F12) and Find All References (Shift+F12) — work across files and into the standard library.
Completion — names in scope and members appear as you type.
Rename (F2) — renames a symbol everywhere it is used, shadow-aware.
Outline and symbols — the Outline view and Cmd+Shift+O list the file's functions and types; Cmd+T searches symbols across the workspace. (Use Ctrl instead of Cmd on Linux.)
Signature help — parameter hints while you type a call; the hover over a construction also tells where it is allocated, on the stack or in which region; semantic highlighting and folding come for free.
If the server ever needs a kick — say, after you rebuilt it — run Scaly: Restart Language Server from the Command Palette.
Writing Scaly with GitHub Copilot
An AI assistant has seen little Scaly. Left to itself it writes
what looks like Rust or Swift, and Scaly differs exactly there:
= compares and := assigns,
not negates, a function cannot print. Two things make it
write Scaly: a file that teaches it the language, and the compiler,
whose messages it reads.
Put the language reference for assistants into your folder, in a terminal at the folder's root:
mkdir -p .github
curl -fsSL https://scaly.io/AGENTS.md -o .github/copilot-instructions.mdGitHub Copilot reads
.github/copilot-instructions.md with every chat
request in this folder. Other assistants read the same text under the
name AGENTS.md at the folder's root.
Open the Chat view, set its mode to
Agent, and say what you want — say, Write
a Scaly program that counts the words of the files named on its
command line, with a test for the counting function.
In agent
mode Copilot writes the files, runs scaly run and
scaly test in the terminal, and reads the errors
the language server reports in the Problems
panel. Scaly's messages name the line and usually the fix, and
Copilot corrects itself from them. Let it run the program and the
tests before you accept what it wrote.
Run, Build and Test
The extension offers three tasks for the file in the active
editor, with no tasks.json to write.
Terminal > Run Task…
lists them under scaly:
- scaly: run
scaly run: the program is compiled in memory and runs in the terminal panel.- scaly: build
scaly build -g: the program, with debug information, beside the file. ⇧⌘B (Ctrl+Shift+B) offers it.- scaly: test
scaly test: the file's tests.
A compiler error lands in the Problems panel,
a click away from its line. To change a task — another output, an
argument —, choose Terminal >
Configure Tasks… and pick it: VS Code
writes it into .vscode/tasks.json, where it can
be edited.
Starting the Debugger
Debugging needs nothing more. The extension drives
lldb-dap, the debugger that comes with Apple's
Command Line Tools (the installer fetches them where they are
missing), with Xcode, and with LLVM — on Ubuntu,
sudo apt install lldb-21. Open a
.scaly file and press F5: the
extension builds it with debug information and starts it under the
debugger. Should lldb-dap live somewhere unusual,
point the scaly.debugAdapter.path setting at it.
For a configuration of your own — arguments, an environment,
another program — you write a .vscode/launch.json,
and that needs a folder open (File >
Open Folder…), not just a file. Open the
Run and Debug view: the icon with the play
triangle and the bug in the Activity Bar on the left, or
⇧⌘D
(Ctrl+Shift+D).
As long as the folder has no launch.json, the
view shows a Run and Debug button — the same as
F5 — and beneath it the link
create a launch.json file. Click the link
with a .scaly file in the editor; should VS Code
ask which debugger, choose Scaly. It writes
the file, opens it, and what it writes debugs the open file:
{
"version": "0.2.0",
"configurations": [
{
"type": "scaly",
"request": "launch",
"name": "Debug the open Scaly file",
"source": "${file}"
}
]
}source is built before the launch;
program names an executable to debug as it is, and
args, env and cwd go to the
program.
Arguments and the Environment shows a configuration with arguments and an environment.
Breakpoints in Scaly Files
VS Code only lets you set breakpoints in files whose language an
installed extension has declared debuggable. The Scaly extension from
Installing the Scaly Extension does exactly that for
.scaly files, so with it installed, clicking in
the editor gutter places a breakpoint (a red dot appears) — nothing to
configure.
Setting a Breakpoint and Stepping
Put this program in area.scaly in your
workspace folder. It computes a rectangle's area from two mutable
bindings — small, but enough to stop and inspect some values:
var width 8
var height 5
var area width * height
if area = 40
print "the area is 40"
else
print "unexpected area"Click in the gutter to the left of the
var area width * height line to set a breakpoint (a red dot
appears), then press F5. The program builds and runs,
and execution stops at that line before it executes — at which point
width and height already have values but
area does not yet.
With execution paused you can:
Read
widthandheightin the Variables pane, or hover them in the editor to see their values.Step one line at a time with F10 (Step Over) — after stepping past the breakpoint line,
areaappears with the value40. Step into a called function with F11.Press F5 again to continue to the program's end.
Breakpoints, stepping, and variable inspection all rely on the
-g debug information; without it the program still runs,
but the debugger has no source lines to stop on.
Stepping into Functions and the Call Stack
Stepping becomes more interesting once a function is involved.
F10 (Step Over) runs a call as
a single step; F11 (Step Into)
descends into the called function so you can watch it run. Put this
program in rectangle.scaly in your workspace
folder — it factors the area calculation out into a function:
function area(w: int, h: int) returns int
{
var result w * h
result
}
var width 8
var height 5
var a area(width, height)
if a = 40
print "the area is 40"
else
print "unexpected area"Set a breakpoint on the var a area(width, height) line
and press F5. When execution stops there,
width and height show in the
Variables pane. Now press F11
instead of F10: the editor jumps into the body of
area and stops on var result w * h.
The Call Stack pane (bottom-left of the Run and
Debug view) now lists two frames — area on top and
main below it — showing exactly how you got here. Click a
frame to switch the Variables pane and the editor to
that frame's context: selecting main shows
width and height again, while
area shows its own local result (still
unassigned at this point, just as area was before its line
ran in the previous example).
From inside the function you can keep stepping, press Shift+F11 (Step Out) to return to the caller, or F5 to continue to the end. The function-and-procedure model is covered in Functions and Procedures and Mutation.
Arguments and the Environment
Put this program in greet.scaly in your
workspace folder. It greets everyone named on its command line, with
the greeting the environment variable GREETING holds,
and Hello
where there is none:
var greeting os.environment_variable("GREETING")
if greeting.length = 0
greeting := "Hello"
for name in os.arguments()
print "`greeting`, `name`!"os.arguments() answers the arguments the program
was started with, its own name left out;
os.environment_variable reads a variable of the
environment, and is empty where it is not set. In a terminal:
$ GREETING=Hi scaly run greet.scaly Ada Grace
Hi, Ada!
Hi, Grace!Under the debugger, the arguments and the environment come from
.vscode/launch.json (Starting the Debugger says how to create one). A
launch.json may hold several configurations; the
drop-down at the top of the Run and Debug view
chooses the one F5 starts. Here the first builds
greet.scaly and runs it with two arguments, a
variable in its environment and the workspace folder as its working
directory; the second debugs a program that is already built — with
scaly build -g, or there are no line tables for
a breakpoint to bind to:
{
"version": "0.2.0",
"configurations": [
{
"type": "scaly",
"request": "launch",
"name": "Debug greet with arguments",
"source": "${workspaceFolder}/greet.scaly",
"args": ["Ada", "Grace"],
"env": { "GREETING": "Hello" },
"cwd": "${workspaceFolder}"
},
{
"type": "scaly",
"request": "launch",
"name": "Debug the built server",
"program": "${workspaceFolder}/build/server",
"args": ["--port", "8080"]
}
]
}Choose Debug greet with arguments in the
drop-down, set a breakpoint on the print line and press
F5. The program stops there with name
"Ada" in the
Variables pane; F5 runs the next
round, with "Grace". What the program
prints appears in the Debug Console.
What the Debugger Shows
For whichever stack frame is selected:
Breakpoints land on top-level statements, bindings and function bodies, and line by line inside loop and conditional bodies — on a single statement within a
while,for,iforchoose, not just on the statement around it.The Variables pane shows a function's arguments and its
letandvarbindings: numbers and booleans; structures, unions and pointers with their fields to expand, a union's variant by its name; aStringas its text; aVector,ArrayorListwith its elements.The Watch view and Debug Console evaluate expressions as C/C++ and do not understand Scaly names. To read a value, use the Variables pane or hover over the name in the editor.
Scaly on Windows
Everything in this chapter works on Windows 10 and 11, on x64 and on Arm. Choose how you want to work: in Visual Studio Code (Using Scaly with Visual Studio Code on Windows) or in Visual Studio (Using Scaly with Visual Studio).
Using Scaly with Visual Studio Code on Windows
The following sections describe how to use Visual Studio Code to develop Scaly programs.
Installing Scaly, VS Code and the Extensions
Install Scaly. Click Start, type
PowerShelland press Enter. In the window that opens, paste this line (a right-click pastes) and press Enter:irm https://scaly.io/install.ps1 | iexAlong with Scaly, the installer sets up Microsoft's C++ build tools, which Scaly needs to build programs. This download is several gigabytes and takes a few minutes. When Windows asks whether to allow the installation, answer Yes. When the Scaly installer ends with
scaly build: ok, everything is in place. Close PowerShell.Install VS Code: download the installer from code.visualstudio.com and run it. If VS Code was installed and running already, close every one of its windows now, so that it starts again and finds Scaly.
Download the Scaly extension, scaly.io/downloads/scaly-vscode.vsix.
Start VS Code and open the Extensions view: the icon with the four squares in the Activity Bar on the left, or Ctrl+Shift+X. Click the ··· menu in the view's title bar, choose Install from VSIX…, and pick
scaly-vscode.vsixfrom yourDownloadsfolder.In the same view, type
C/C++into the search box at the top. Choose the extension named C/C++ whose publisher is Microsoft (its identifier isms-vscode.cpptools) and click Install; it brings the debugger. If you skip this step, the first F5 asks for it with a button Install C/C++.
The Scaly extension launches scalyls and
scaly from your PATH; should they
live somewhere unusual, point the scaly.server.path and
scaly.tool.path settings at them.
Checking the Language Server
Make a folder for this tutorial: choose File > Open Folder…, go to
Documents, click New folder, name itscaly-tutorial, double-click it so that the dialog shows its (still empty) inside, and click Select Folder.Trust the folder. VS Code opens a new folder in Restricted Mode, where it starts no language server and no debugger; a bar at the top of the window says so. Click Manage in that bar, then Trust. (Should VS Code ask in a dialog instead, answer Yes, I trust the authors.)
Choose File > New File…, save it right away (Ctrl+S) as
double.scalyin that folder, and type:function double(n: int) returns int n * 2 let d double(21) if d = 42 print "doubled!"The words
function,returnsandletare coloured: the extension knows the file.Change
double(21)todoubel(21). A red squiggle appears under it, and the Problems panel (Ctrl+Shift+M) showsfunction not found: doubel: the language server is running. Change it back; the squiggle clears.Rest the mouse on
doubleto see its signature, press F12 on it to go to its definition and Shift+F12 to list its uses, F2 to rename it everywhere. Ctrl+Shift+O lists the file's functions and types, Ctrl+T searches symbols across the folder.
What the Language Server Gives You lists what else the language server does. If the squiggle never appears, VS Code did not find the language server: close all of its windows and start it again.
To write Scaly with GitHub Copilot, follow Writing Scaly with GitHub Copilot; in PowerShell the reference for assistants is fetched with:
mkdir -Force .github
irm https://scaly.io/AGENTS.md -OutFile .github\copilot-instructions.mdRun, Build and Test
With double.scaly in the editor, choose
Terminal > Run Task…,
then scaly. Three tasks are offered:
- scaly: run
scaly run: the program runs in the terminal panel at the bottom and printsdoubled!.- scaly: build
scaly build -g: writesdouble.exe, the program, anddouble.pdb, its debug information, beside the file. Ctrl+Shift+B offers it too.- scaly: test
scaly test: the file's tests.
A compiler error lands in the Problems
panel, a click away from its line. Should
scaly: build say that it has nothing to
link with, the C++ build tools are missing: run the Scaly installer
again (Installing Scaly, VS Code and the Extensions) and answer
Yes when Windows asks. To
change a task, choose
Terminal > Configure
Tasks… and pick it: VS Code writes it into
.vscode\tasks.json, where it can be
edited.
Setting a Breakpoint and Stepping
Create
area.scalyin the folder, asdouble.scalyabove, with this program:var width 8 var height 5 var area width * height if area = 40 print "the area is 40" else print "unexpected area"Click in the gutter to the left of the line number of
var area width * height. A red dot appears: a breakpoint. (F9 sets one on the line the cursor is in.)Press F5. Should VS Code ask which debugger to use, choose Scaly. The extension builds
area.exeandarea.pdb, then starts the program under the Visual Studio debugger. Should a message say that the C/C++ extension is missing, click Install C/C++, wait until it is installed, and press F5 again.The program stops at the breakpoint, before the line runs; the line is highlighted in yellow. The Run and Debug view on the left shows the Variables pane:
widthis8,heightis5,areahas no value yet. Resting the mouse on a name in the editor shows its value too.Press F10 (Step Over): the line runs, and
areashows40.Press F5 again: the program runs to its end. What it prints,
the area is 40, appears in the Debug Console (Ctrl+Shift+Y).
Every F5 builds the file again, so a change to the source is always in the program you debug.
Stepping into Functions and the Call Stack
Create
rectangle.scalyin the folder, with the area calculation in a function:function area(w: int, h: int) returns int { var result w * h result } var width 8 var height 5 var a area(width, height) if a = 40 print "the area is 40" else print "unexpected area"Set a breakpoint on
var a area(width, height)and press F5. The program stops there.Press F11 (Step Into): the editor jumps into the body of
areaand stops onvar result w * h.The Call Stack pane, in the Run and Debug view below Variables, lists two frames:
areaon top,mainbelow it, each with the program's name in front (rectangle.exe!). Clickmain: the Variables pane showswidthandheight. Clickarea: it showsw,handresult, still without a value.Press Shift+F11 (Step Out) to return to the caller, then F5 to run to the end.
Arguments and the Environment
A program that is started with arguments and an environment
gets them under the debugger from a
.vscode\launch.json.
Create
greet.scalyin the folder, asdouble.scalyabove, with this program. It greets everyone named on its command line, with the greeting the environment variableGREETINGholds, andHello
where there is none:var greeting os.environment_variable("GREETING") if greeting.length = 0 greeting := "Hello" for name in os.arguments() print "`greeting`, `name`!"os.arguments()answers the arguments the program was started with, its own name left out;os.environment_variablereads a variable of the environment, and is empty where it is not set.Open the Run and Debug view: the icon with the play triangle and the bug in the Activity Bar, or Ctrl+Shift+D.
Click the link create a launch.json file below the Run and Debug button. Should VS Code ask which debugger, choose Scaly. It writes
.vscode\launch.jsonand opens it.Replace its content with this and save it (Ctrl+S):
{ "version": "0.2.0", "configurations": [ { "type": "scaly", "request": "launch", "name": "Debug greet with arguments", "source": "${workspaceFolder}/greet.scaly", "args": ["Ada", "Grace"], "env": { "GREETING": "Hello" }, "cwd": "${workspaceFolder}" }, { "type": "scaly", "request": "launch", "name": "Debug the open Scaly file", "source": "${file}" } ] }The first configuration builds
greet.scalyand runs it with two arguments, a variable in its environment and the folder as its working directory. The second is what F5 did so far: it builds and debugs the file in the editor.Choose Debug greet with arguments in the drop-down at the top of the Run and Debug view.
Open
greet.scaly, set a breakpoint on theprintline and press F5. The program stops there; the Variables pane showsnameas"Ada".Press F5: the next round stops with
"Grace". Press F5 once more to run to the end. The Debug Console showsHello, Ada!andHello, Grace!.
A configuration may also debug a program built beforehand:
"program": "${workspaceFolder}/greet.exe" in place of
source runs it as it is (built with
-g, or there is nothing for a breakpoint to bind
to).
What the Debugger Shows
Breakpoints land on top-level statements, bindings and function bodies, and line by line inside loop and conditional bodies.
The Variables pane shows a function's arguments and its
letandvarbindings: numbers and booleans; structures, unions and pointers with their fields to expand; aStringas its text; aVector,ArrayorListwith its elements. A generic type is written as in C++,Vector<int>.The Watch view and the Debug Console evaluate expressions as C++ and do not understand Scaly. To read a value, use the Variables pane or hover over the name in the editor.
Using Scaly with Visual Studio
Visual Studio debugs a Scaly program with the debugger it uses for
C++. It has no Scaly project type and does not know the language yet:
it works on a folder, builds through a task you write once, and shows a
.scaly file as plain text. For highlighting,
completion and the rest of the language server, edit in VS Code
(Using Scaly with Visual Studio Code on Windows); both can have the same folder
open at once.
Installing Visual Studio and Scaly
Visual Studio comes first: Scaly builds programs with its C++ tools, and when it finds them it installs nothing of its own.
Download Visual Studio 2026 from visualstudio.microsoft.com; the free Community edition will do. Run the installer. If Visual Studio 2026 is installed already, start the Visual Studio Installer from the Start menu instead and click Modify.
On the Workloads tab, tick Desktop development with C++. It brings the C++ debugger and the C++ tools Scaly builds with.
On a PC with an Arm processor (a Snapdragon, for instance; Settings > System > About says
ARM-based processor
), the tools for Arm must be ticked as well: in the Installation details on the right, under Desktop development with C++, tick the entry for C++ ARM64/ARM64EC build tools. On any other PC, skip this step.Click Install (or Modify) and wait until the installer is done. Should it ask you to restart your PC, do so.
Install Scaly. Click Start, type
PowerShelland press Enter. In the window that opens, paste this line (a right-click pastes) and press Enter:irm https://scaly.io/install.ps1 | iexThe installer finds the C++ tools of Visual Studio and fetches nothing more. When it ends with
scaly build: ok, everything is in place. Close PowerShell.Start Visual Studio. If it was running while Scaly was installed, close it and start it again, so that it finds Scaly.
Opening the Folder
In Visual Studio's start window, choose Open a local folder; with Visual Studio already open, choose File > Open > Folder… and go to
Documents. If there is noscaly-tutorialyet from Using Scaly with Visual Studio Code on Windows, click New folder and name itscaly-tutorial. Double-clickscaly-tutorial, so that the dialog shows its inside, and click Select Folder.The Solution Explorer lists the folder's files (View > Solution Explorer, or Ctrl+Alt+L, should it not be there).
If
area.scalyis there already, from Setting a Breakpoint and Stepping, double-click it. Otherwise right-click the folder at the top of the Solution Explorer, choose Add > New File…, name itarea.scaly, type this program, and save it with Ctrl+S:var width 8 var height 5 var area width * height if area = 40 print "the area is 40" else print "unexpected area"
Building
A task tells Visual Studio how to build a
.scaly file. It is written once per folder:
Right-click
area.scalyin the Solution Explorer and choose Configure Tasks. Visual Studio creates.vs\tasks.vs.jsonand opens it.Replace all of its content with this task, which builds the file with debug information, and save it (Ctrl+S):
{ "version": "0.2.1", "tasks": [ { "taskLabel": "scaly build", "appliesTo": "*.scaly", "type": "launch", "contextType": "build", "command": "scaly", "args": [ "build", "-g", "${file}", "-o", "${fileDirname}\\${fileBasenameNoExtension}.exe" ] } ] }Right-click
area.scalyagain: the menu now has Build. Choose it. The compiler's messages appear in the Output window (View > Output), andarea.exeandarea.pdbappear beside the source. Should the Solution Explorer not show them yet, click its Refresh button.
The task applies to every .scaly file in
the folder: each one's menu has Build,
and each builds into an .exe of its own
name.
Setting a Breakpoint and Stepping
Right-click
area.exein the Solution Explorer and choose Set as Startup Item. The start button in the toolbar (the green triangle) now says area.exe.In
area.scaly, click in the grey margin left ofvar area width * height, or put the cursor on that line and press F9. A red dot appears: a breakpoint.Press F5. A console window opens for the program, and Visual Studio stops at the breakpoint, before the line runs; a yellow arrow marks the line.
The Locals window at the bottom shows the bindings:
widthis8,heightis5. (If it is not there: Debug > Windows > Locals.) Resting the mouse on a name in the editor shows its value too.Press F10 (Step Over): the line runs, and
areashows40.Press F5 again: the program runs to its end and prints
the area is 40into its console window.
Two things to know. The console window closes when the program
ends; to read its output afterwards, untick
Tools > Options
> Debugging >
Automatically close the console when debugging
stops. And F5 runs the
.exe as it is: after changing the source,
choose Build again first. Should Visual
Studio refuse a breakpoint in the .scaly file,
start with F11 instead of F5: the
program stops at its first statement, and from there
F10 walks it line by line.
Stepping into Functions and the Call Stack
In the Solution Explorer, right-click the folder at the top, choose Add > New File…, name it
rectangle.scaly, and type the area calculation with a function in it; save it with Ctrl+S:function area(w: int, h: int) returns int { var result w * h result } var width 8 var height 5 var a area(width, height) if a = 40 print "the area is 40" else print "unexpected area"Right-click it and choose Build, then right-click
rectangle.exeand choose Set as Startup Item.Open
rectangle.scaly, set a breakpoint onvar a area(width, height)and press F5.Press F11 (Step Into): the editor jumps into the body of
areaand stops onvar result w * h.The Call Stack window (Debug > Windows > Call Stack, or Ctrl+Alt+C) lists
rectangle.exe!areaon top andrectangle.exe!mainbelow it. Double-clickmain: Locals showswidthandheight. Double-clickarea: it showsw,handresult.Press Shift+F11 (Step Out) to return to the caller, then F5 to run to the end.
Arguments and the Environment
In the Solution Explorer, right-click the folder at the top, choose Add > New File…, name it
greet.scaly, type this program and save it with Ctrl+S. It greets everyone named on its command line, with the greeting the environment variableGREETINGholds, andHello
where there is none:var greeting os.environment_variable("GREETING") if greeting.length = 0 greeting := "Hello" for name in os.arguments() print "`greeting`, `name`!"os.arguments()answers the arguments the program was started with, its own name left out;os.environment_variablereads a variable of the environment, and is empty where it is not set.Right-click
greet.scalyand choose Build.Right-click
greet.exe, choose Add Debug Configuration, and in the list that opens, Default. Visual Studio writes.vs\launch.vs.jsonand opens it.Add the arguments, the folder as working directory and a variable in the environment to its entry, and save it:
{ "version": "0.2.1", "defaults": {}, "configurations": [ { "type": "default", "project": "greet.exe", "name": "greet.exe", "args": [ "Ada", "Grace" ], "currentDir": "${workspaceRoot}", "env": { "GREETING": "Hello" } } ] }Right-click
greet.exeand choose Set as Startup Item. Set a breakpoint on theprintline ofgreet.scalyand press F5. The program stops there; Locals showsnameas"Ada".Press F5: the next round stops with
"Grace". Press F5 once more to run to the end; the console window showsHello, Ada!andHello, Grace!.
What Visual Studio Shows
The Locals and Autos windows show a function's arguments and its bindings: numbers and booleans; structures, unions and pointers with their fields to expand; a
Stringas its text; aVector,ArrayorListwith its elements. A generic type is written as in C++,Vector<int>.The Watch and Immediate windows evaluate C++, not Scaly.
The editor shows a
.scalyfile as plain text: no colours, no squiggles, no completion.
A Tour of Scaly
This section sweeps quickly across the language using small, complete
programs. If something is not fully explained here, do not worry —
every feature is covered in detail in the rest of this specification, and
each part of the tour points you to the relevant chapter. Each program
prints what it computes; print writes a line:
print "Hello from Scaly"The same tour runs in the browser, step by step: scaly.io/play/tour.html. There every program of this section stands in an editor with a Run button — change it, run it again, try what happens when you break it; Reset brings the original back. Nothing to install: the compiler runs in the browser tab.
Values and Bindings
Bind a value to a name with let. There is no equals
sign — the name is followed directly by its value. Use var
for a binding you intend to change, and := to change it.
A semicolon starts a comment, and backticks inside a string put a
value into the text.
let answer 42 ; immutable binding
var count 0 ; mutable binding
count := count + 1 ; assignment is :=
print "answer is `answer`, count is `count`"The equals sign = is reserved for comparison, so it
never gets confused with assignment. See Lexical Structure for
the full lexical rules.
Operators and Operations
Operators are ordinary identifiers, not keywords baked into the grammar. An operation is read from left to right; when operators are chained, they combine by the binding strength the standard library gives them — multiplication binds tighter than addition, comparison weaker than both:
; 3 + 4 * 5 is 3 + 20, which is 23 — not 35
let result 3 + 4 * 5
if result = 23
print "got 23"
else
print "unexpected"Parentheses group explicitly, and an operator written where a value
is expected acts as a prefix (-x, not done,
a - -2). A call takes its arguments before the operators
around it combine, and a call without parentheses takes exactly one
argument: print "hi" is print("hi"), but an
argument that is itself a call takes its parentheses,
print(describe(x)). See Expressions for
the complete model, tuples and functions as values included.
Control Flow
if chooses between branches. Single-statement branches
need no braces; the condition needs no parentheses:
let n 7
if n < 0
print "negative"
else if n = 0
print "zero"
else
print "positive"while repeats as long as its condition holds. Use braces
for a multi-statement body:
var sum 0
var i 1
while i <= 10
{
sum := sum + i
i := i + 1
}
print "the sum is `sum`"for iterates. for i in 4 runs
i from 0 up to — but not including — 4:
var total 0
for i in 4
total := total + i
print "0 + 1 + 2 + 3 is `total`"Functions
A function declares its parameter types and return type. The value
of the last expression is returned automatically — no return
keyword is needed for the final result. A single-expression body needs no
braces:
function add(a: int, b: int) returns int
a + b
print "3 + 4 is `add(3, 4)`"Functions can call themselves. Here is the classic factorial:
function factorial(n: int) returns int
if n <= 1
1
else
n * factorial(n - 1)
print "5! is `factorial(5)`"Functions are pure: they read their arguments and return a value, with no side effects. To mutate, use a procedure.
Procedures and Mutation
A procedure is like a function but may change its inputs
and perform side effects. It says which inputs it changes: a parameter
it writes is marked mutable, the receiver
this included, and a field that may change after the
object is built is declared var. A reader always knows
which calls can mutate state, and the compiler checks it. See
Mutation via Procedures for the details.
define Counter(var value: int)
{
procedure increment(mutable this)
value := value + 1
function get(this) returns int
value
}
var c Counter(0)
c.increment()
c.increment()
print "the counter is at `c.get()`"Structures and Methods
Define a structure by listing its fields. A structure with public
fields gets a constructor that takes them in order. Methods are
functions or procedures whose first parameter is this:
define Point(x: int, y: int)
{
function distance_squared(this) returns int
this.x * this.x + this.y * this.y
}
let p Point(3, 4)
print "the distance squared is `p.distance_squared()`"Constructors covers constructors, including
explicit init forms with their own signatures.
Unions, Options, and choose
A union holds one of several variants. Each variant may
carry a payload. The most common union is Option, which
represents a value that may be absent:
define Option union(Some: int, None)
let present Option.Some(42)
let absent Option.NoneMatch on a union with choose. Each when
clause names a binding, then the variant, then the result; the binding
is bound to the variant's payload. An else clause handles
the remaining variants:
define Option union(Some: int, None)
function or_zero(o: Option) returns int
choose o
when v: Some
v
else
0
print "`or_zero(Option.Some(7))` and `or_zero(Option.None)`"See Union Types for union layout, the
T? shorthand, and pattern matching in full.
Modules and Imports
Split a program across files with module: the
statement module shapes loads the sibling file
shapes.scaly. A type defined there stays hidden
until the using file imports it with use:
; shapes.scaly
define Box(width: int, height: int)
{
function area(this) returns int
width * height
}
; myprog.scaly
module shapes
use myprog.shapes.Box ; or: use myprog.shapes.*
let b Box(3, 4)
if b.area() = 12
print "area is 12"Forgetting the import is caught at compile time, and the error
states the exact use line to add. The standard library
needs no imports — it is always visible.
Code that several programs share is a package, and a package somebody else wrote is one line away:
package json 0.1.3 "github.com/someone/json"scaly fetches it from that git repository the
first time the program is run, tested or built; and
scaly install builds the programs such a
package offers and puts them on your PATH. See
Modules and Packages for packages and their sources, visibility
rules, and glob imports.
Putting It Together
Let us combine the pieces into one small, complete program: a safe
division that returns an Option instead of crashing on a zero
divisor, with the caller deciding what to print.
Start with the function. It returns None when the divisor
is zero, and Some with the quotient otherwise:
define Option union(Some: int, None)
function safe_divide(a: int, b: int) returns Option
if b = 0
Option.None
else
Option.Some(a / b)Now call it and react to the result with choose. The
Some arm receives the quotient; the else arm
handles division by zero:
let result safe_divide(20, 4)
choose result
when q: Some
if q = 5
print "20 / 4 = 5"
else
print "unexpected quotient"
else
print "cannot divide by zero"Put both pieces in divide.scaly, then build and
run it:
scaly build divide.scaly -o divide
./divideIt prints:
20 / 4 = 5Change the call to safe_divide(7, 0) and the same program
prints cannot divide by zero — the union
makes the failure a value you must handle, not a crash you can forget.
From here, the rest of this specification fills in the details: the Lexical Structure rules, the full Expressions model, and the Type System system including generics and the inferred region-based memory management.