iyi

Playground

samples/iyi/calc.iyi

A language, in the language — the biggest program this prelude can hold.

samples/iyi/calc.iyi · iyi-calc.wasm · 194 KB
# A language, in the language — the biggest program this prelude can hold.
#
#   echo "x = 2 + 3 * 4
#   x * 10" | ./bin/iyi run samples/iyi/calc.iyi
#
# The other samples each document a rule. This one documents nothing and is
# here for a different reason: everything else in `samples/iyi` is a page long,
# and a language that has only been used for pages has not been used. This is
# three modules and a scanner, a parser and an evaluator, the shape of every
# real program that reads something, written against iyi's own 21,567-line
# library and nothing else.
#
# What it cost to write is recorded where it landed. `String#[]`, `String#[](start,
# count)`, `String#to_i` and `read_input` are in the prelude because this file
# asked for them, which is the rule the prelude grows by (SPEC.md "0.1.0",
# item 3). Nothing else was missing.
module samples/calc

import calc/lexer::{scan, BadCharacter}
import calc/parser::{parse, Statement, SyntaxError}
import calc/ast::{Number, Name, Binary, UnknownName, DividedByZero}

# What runs when nothing is piped in, so that the sample is a program rather
# than a prompt. CI runs it this way. Its last two lines are errors on
# purpose, and the demo exits 0 with them.
DEMO = "x = 2 + 3 * 4
y = (x - 4) / 2
x + y * 10
missing + 1
1 / 0"

# Whether every statement answered. An error goes to standard error, and
# input with one makes the program exit 1: all of them went to standard
# output at exit 0, so `echo 1 / 0 | iyi run samples/iyi/calc.iyi && next`
# ran `next`.
pub def run(source : String) : Bool
  tokens = scan(source)
  if tokens.is_a?(BadCharacter)
    stderr.puts "error: #{tokens.message}"
    return false
  end

  program = parse(tokens)
  if program.is_a?(SyntaxError)
    stderr.puts "error: #{program.message}"
    return false
  end

  answered = true
  scope = {} of String => Int32
  program.each do |statement|
    value = statement.body.evaluate(scope)

    if value.is_a?(Int32)
      if statement.target.empty?
        puts value
      else
        scope[statement.target] = value
      end
    else
      # `value` is an error member of the union, and every one of them answers
      # `message` because they implement `Error` (SPEC.md III.1).
      stderr.puts "error: #{value.message}"
      answered = false
    end
  end
  answered
end

# Input that is only line ends is nothing piped in: PowerShell's `"" |`
# sends a CRLF. And its pipe starts what it sends with a byte order mark,
# which is not part of the program: "unexpected character at 0".
typed = read_input.lchop('\uFEFF')
if typed.strip.empty?
  run(DEMO)
elsif !run(typed)
  exit 1
end
output
Press Run.

recorded with iyi 0.18.0 (built on Crystal 1.22.0-dev [2a9653052] (2026-10-18)) · Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+ · 2026-10-19

You can type in the pane: nothing here compiles, so what you type cannot run, and the page says which bytes it is running the moment the text stops being the recorded file. There is no standard input, no filesystem and no threads on this target, and one sample fails on purpose for exactly that reason.

Run executes iyi-calc.wasm, the module the compiler built from this file, in your browser. How this works

Try it on your machine

Copy the program into calc.iyi, change something, and run it. Only a compiler on your own machine can run what you wrote.

sh, after installing
$ iyi run calc.iyi