diff --git a/records/agent.json b/records/agent.json index d160b92..cc5a405 100644 --- a/records/agent.json +++ b/records/agent.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.12.0 (built on Crystal 1.22.0-dev [44b843563] (2026-09-11))", - "commit": "44b8435633d47d2d3ba2106abe86e0ea1188257f", + "compiler": "iyi 0.13.0 (built on Crystal 1.22.0-dev [1f79e251a] (2026-09-16))", + "commit": "1f79e251ae76b1ce03c398dc99675529f1b31825", "machine": "Linux 7.2.4-1-cachyos x86_64, AMD Ryzen AI 9 465 w/ Radeon 880M", "command": "npm run record:agent", - "when": "2026-09-12T16:06:28.321Z" + "when": "2026-09-20T11:18:01.910Z" }, "project": [ { @@ -44,7 +44,7 @@ "command": "iyi mod context --json app.iyi", "exitCode": 0, "payload": "stdout", - "stdout": "{\"file\":\"agent/app.iyi\",\"imports\":[{\"import\":\"app/greeter\",\"api\":{\"module\":\"app/greeter\",\"interface_hash\":\"90e5d376253c2ff8727008aaba9fece1\",\"functions\":[{\"name\":\"polite\",\"parameters\":[\"name : String\"],\"returns\":\"String\",\"doc\":\"The exported surface carries its doc comment, so the grounding pack an agent\\nreads says what the function is for and not only what it takes.\",\"rendered\":\"def polite(name : String) : String\"},{\"name\":\"shout\",\"parameters\":[\"name : String\"],\"returns\":\"String\",\"rendered\":\"def shout(name : String) : String\"}],\"types\":[{\"name\":\"Greet\",\"kind\":\"trait\",\"visibility\":\"pub\",\"methods\":[{\"name\":\"greet\",\"parameters\":[],\"returns\":\"String\",\"required\":true,\"rendered\":\"abstract def greet : String\"}]}],\"impls\":[]}}]}\n", + "stdout": "{\"file\":\"agent/app.iyi\",\"imports\":[{\"import\":\"app/greeter\",\"api\":{\"module\":\"app/greeter\",\"interface_hash\":\"14c937d996cd42f50dcce4594c179cca\",\"functions\":[{\"name\":\"polite\",\"parameters\":[\"name : String\"],\"returns\":\"String\",\"doc\":\"The exported surface carries its doc comment, so the grounding pack an agent\\nreads says what the function is for and not only what it takes.\",\"rendered\":\"def polite(name : String) : String\"},{\"name\":\"shout\",\"parameters\":[\"name : String\"],\"returns\":\"String\",\"rendered\":\"def shout(name : String) : String\"}],\"types\":[{\"name\":\"Greet\",\"kind\":\"trait\",\"visibility\":\"pub\",\"methods\":[{\"name\":\"greet\",\"parameters\":[],\"returns\":\"String\",\"required\":true,\"rendered\":\"abstract def greet : String\"}]}],\"impls\":[]}}]}\n", "stderr": "" }, { @@ -104,7 +104,7 @@ "command": "iyi test --json .", "exitCode": 0, "payload": "stdout", - "stdout": "{\"tests\":[{\"file\":\"./app_test.iyi\",\"status\":\"pass\",\"seconds\":0.229}],\"passed\":1,\"failed\":0,\"skipped\":0}\n", + "stdout": "{\"tests\":[{\"file\":\"./app_test.iyi\",\"status\":\"pass\",\"seconds\":0.27}],\"passed\":1,\"failed\":0,\"skipped\":0}\n", "stderr": "" }, { @@ -135,7 +135,7 @@ "bytes": 731, "sourceBytes": 707, "interfaceHashes": { - "app/greeter": "90e5d376253c2ff8727008aaba9fece1" + "app/greeter": "14c937d996cd42f50dcce4594c179cca" } }, "suggested": [ @@ -167,7 +167,7 @@ }, "serverInfo": { "name": "iyi", - "version": "0.12.0" + "version": "0.13.0" } }, "tools": [ diff --git a/records/diagnostics.json b/records/diagnostics.json index 30c80c1..da86296 100644 --- a/records/diagnostics.json +++ b/records/diagnostics.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.12.0 (built on Crystal 1.22.0-dev [44b843563] (2026-09-11))", - "commit": "44b8435633d47d2d3ba2106abe86e0ea1188257f", + "compiler": "iyi 0.13.0 (built on Crystal 1.22.0-dev [1f79e251a] (2026-09-16))", + "commit": "1f79e251ae76b1ce03c398dc99675529f1b31825", "machine": "Linux 7.2.4-1-cachyos x86_64, AMD Ryzen AI 9 465 w/ Radeon 880M", "command": "npm run record:diagnostics", - "when": "2026-09-12T16:06:26.670Z" + "when": "2026-09-20T11:18:00.000Z" }, "cases": [ { diff --git a/records/highlight.json b/records/highlight.json index b26915a..1fe07df 100644 --- a/records/highlight.json +++ b/records/highlight.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.12.0 (built on Crystal 1.22.0-dev [44b843563] (2026-09-11))", - "commit": "44b8435633d47d2d3ba2106abe86e0ea1188257f", + "compiler": "iyi 0.13.0 (built on Crystal 1.22.0-dev [1f79e251a] (2026-09-16))", + "commit": "1f79e251ae76b1ce03c398dc99675529f1b31825", "machine": "Linux 7.2.4-1-cachyos x86_64, AMD Ryzen AI 9 465 w/ Radeon 880M", "command": "npm run record:highlight", - "when": "2026-09-12T16:06:30.631Z" + "when": "2026-09-20T11:18:04.599Z" }, "files": { "samples/iyi/app/formal.iyi": "# A second module, exporting a `title` of its own. See `../modules.iyi`.\nmodule app/formal\n\npub def title : String\n "esteemed colleague"\nend\n\npub def address(name : String) : String\n "Good day, #{name}."\nend\n", @@ -15,10 +15,11 @@ "samples/iyi/calc/ast.iyi": "module calc/ast\n\n# What an evaluated program can go wrong with. Both are ordinary members of the\n# union an evaluation returns (SPEC.md III.1), so a caller either handles them\n# or propagates with `!`.\npub struct UnknownName\n getter name : String\n\n def initialize(@name : String)\n end\nend\n\nimpl Error for UnknownName\n def message : String\n "unknown name '#{name}'"\n end\nend\n\npub struct DividedByZero\n def initialize\n end\nend\n\nimpl Error for DividedByZero\n def message : String\n "divided by zero"\n end\nend\n\n# The one thing every node can do. A trait rather than a base class, because\n# there is nothing to inherit and R-3 has no opinion to break here.\npub trait Node\n abstract def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\nend\n\npub struct Number\n getter value : Int32\n\n def initialize(@value : Int32)\n end\nend\n\nimpl Node for Number\n def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\n value\n end\nend\n\npub struct Name\n getter name : String\n\n def initialize(@name : String)\n end\nend\n\nimpl Node for Name\n def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\n return UnknownName.new(name) unless scope.has_key?(name)\n scope[name]\n end\nend\n\npub class Binary\n getter op : Char\n getter left : Number | Name | Binary\n getter right : Number | Name | Binary\n\n def initialize(@op : Char, @left : Number | Name | Binary, @right : Number | Name | Binary)\n end\nend\n\nimpl Node for Binary\n def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\n a = left.evaluate(scope)!\n b = right.evaluate(scope)!\n\n if op == '+'\n a + b\n elsif op == '-'\n a - b\n elsif op == '*'\n a * b\n else\n return DividedByZero.new if b == 0\n a // b\n end\n end\nend\n", "samples/iyi/calc/lexer.iyi": "module calc/lexer\n\n# What the scanner produces. A token is its kind, the text it covers and where\n# it started, because an error that cannot say where is half an error.\npub struct Token\n getter kind : String\n getter text : String\n getter at : Int32\n\n def initialize(@kind : String, @text : String, @at : Int32)\n end\nend\n\npub struct BadCharacter\n getter at : Int32\n getter found : Char\n\n def initialize(@at : Int32, @found : Char)\n end\nend\n\nimpl Error for BadCharacter\n def message : String\n "unexpected character at #{at}"\n end\nend\n\npub def scan(source : String) : Array(Token) | BadCharacter\n tokens = [] of Token\n index = 0\n\n while index < source.size\n c = source[index]\n\n if c == ' ' || c == '\\n' || c == '\\t'\n index = index + 1\n elsif digit?(c)\n start = index\n while index < source.size && digit?(source[index])\n index = index + 1\n end\n tokens << Token.new("number", source[start, index - start], start)\n elsif letter?(c)\n start = index\n while index < source.size && (letter?(source[index]) || digit?(source[index]))\n index = index + 1\n end\n tokens << Token.new("name", source[start, index - start], start)\n elsif operator?(c)\n tokens << Token.new("op", source[index, 1], index)\n index = index + 1\n else\n return BadCharacter.new(index, c)\n end\n end\n\n tokens << Token.new("end", "", index)\n tokens\nend\n\ndef digit?(c : Char) : Bool\n c >= '0' && c <= '9'\nend\n\ndef letter?(c : Char) : Bool\n (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'\nend\n\ndef operator?(c : Char) : Bool\n c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')' || c == '='\nend\n", "samples/iyi/calc/parser.iyi": "module calc/parser\n\nimport calc/lexer\nimport calc/ast\n\nusing calc/lexer::{Token}\nusing calc/ast::{Number, Name, Binary}\n\npub struct SyntaxError\n getter at : Int32\n getter wanted : String\n\n def initialize(@at : Int32, @wanted : String)\n end\nend\n\nimpl Error for SyntaxError\n def message : String\n "expected #{wanted} at #{at}"\n end\nend\n\n# One statement: `name = expression`, or an expression on its own, which the\n# runner prints. Kept as a pair rather than a node type, because a statement is\n# not something this language evaluates twice.\npub struct Statement\n getter target : String\n getter body : Number | Name | Binary\n\n def initialize(@target : String, @body : Number | Name | Binary)\n end\nend\n\n# A cursor over the tokens. A struct with an index rather than a shifting array,\n# because backing up one token is the parser's whole trick.\npub class Reader\n getter tokens : Array(Token)\n getter at : Int32\n\n def initialize(@tokens : Array(Token))\n @at = 0\n end\n\n def peek : Token\n tokens[at]\n end\n\n def advance : Token\n token = tokens[at]\n @at = at + 1\n token\n end\nend\n\npub def parse(tokens : Array(Token)) : Array(Statement) | SyntaxError\n reader = Reader.new(tokens)\n statements = [] of Statement\n\n while reader.peek.kind != "end"\n statements << statement(reader)!\n end\n\n statements\nend\n\ndef statement(reader : Reader) : Statement | SyntaxError\n first = reader.peek\n\n if first.kind == "name" && reader.tokens[reader.at + 1].text == "="\n reader.advance\n reader.advance\n Statement.new(first.text, expression(reader)!)\n else\n Statement.new("", expression(reader)!)\n end\nend\n\ndef expression(reader : Reader) : Number | Name | Binary | SyntaxError\n left = term(reader)!\n\n while reader.peek.kind == "op" && (reader.peek.text == "+" || reader.peek.text == "-")\n op = reader.advance.text[0]\n left = Binary.new(op, left, term(reader)!)\n end\n\n left\nend\n\ndef term(reader : Reader) : Number | Name | Binary | SyntaxError\n left = factor(reader)!\n\n while reader.peek.kind == "op" && (reader.peek.text == "*" || reader.peek.text == "/")\n op = reader.advance.text[0]\n left = Binary.new(op, left, factor(reader)!)\n end\n\n left\nend\n\ndef factor(reader : Reader) : Number | Name | Binary | SyntaxError\n token = reader.advance\n\n if token.kind == "number"\n Number.new(token.text.to_i)\n elsif token.kind == "name"\n Name.new(token.text)\n elsif token.text == "("\n inside = expression(reader)!\n closing = reader.advance\n return SyntaxError.new(closing.at, "')'") unless closing.text == ")"\n inside\n else\n SyntaxError.new(token.at, "a number, a name or '('")\n end\nend\n", - "samples/iyi/calc.iyi": "# A language, in the language — the biggest program this prelude can hold.\n#\n# echo "x = 2 + 3 * 4\n# x * 10" | ./bin/iyi run samples/iyi/calc.iyi\n#\n# The other samples each document a rule. This one documents nothing and is\n# here for a different reason: everything else in `samples/iyi` is a page long,\n# and a language that has only been used for pages has not been used. This is\n# three modules and a scanner, a parser and an evaluator, the shape of every\n# real program that reads something, written against iyi's own 13,609-line\n# library and nothing else.\n#\n# What it cost to write is recorded where it landed. `String#[]`, `String#[](start,\n# count)`, `String#to_i` and `read_input` are in the prelude because this file\n# asked for them, which is the rule the prelude grows by (SPEC.md "0.1.0",\n# item 3). Nothing else was missing.\nmodule samples/calc\n\nimport calc/lexer\nimport calc/parser\nimport calc/ast\n\nusing calc/lexer::{scan, BadCharacter}\nusing calc/parser::{parse, Statement, SyntaxError}\nusing calc/ast::{Number, Name, Binary, UnknownName, DividedByZero}\n\n# What runs when nothing is piped in, so that the sample is a program rather\n# than a prompt. CI runs it this way.\nDEMO = "x = 2 + 3 * 4\ny = (x - 4) / 2\nx + y * 10\nmissing + 1\n1 / 0"\n\npub def run(source : String) : Nil\n tokens = scan(source)\n if tokens.is_a?(BadCharacter)\n puts "error: #{tokens.message}"\n return\n end\n\n program = parse(tokens)\n if program.is_a?(SyntaxError)\n puts "error: #{program.message}"\n return\n end\n\n scope = {} of String => Int32\n program.each do |statement|\n value = statement.body.evaluate(scope)\n\n if value.is_a?(Int32)\n if statement.target.empty?\n puts value\n else\n scope[statement.target] = value\n end\n else\n # `value` is an error member of the union, and every one of them answers\n # `message` because they implement `Error` (SPEC.md III.1).\n puts "error: #{value.message}"\n end\n end\nend\n\ntyped = read_input\nrun(typed.empty? ? DEMO : typed)\n", - "samples/iyi/collections.iyi": "# The `Enumerable` port, exercised — SPEC.md II.6.\n#\n# ./bin/crystal run samples/iyi/collections.iyi\n#\n# `std/enumerable.iyi` holds the trait. This file implements it twice and calls\n# every method it defines. Both matter: a default method is only typed when it\n# is called, so a trait that merely compiles has proved nothing about whether\n# its bodies are sound.\n#\n# `Nums` and `Words` answer `Elem` differently — `Int32` and `String` — and each\n# gets the same 57 default methods from one `each`. That is the claim II.6\n# makes, running.\n\nmodule samples/collections\n\nimport std/enumerable\nusing std/enumerable::{Enumerable}\n\npub struct Nums\n def initialize(@a : Array(Int32))\n end\nend\n\nimpl Enumerable for Nums\n type Elem = Int32\n\n def each(& : Int32 -> Nil) : Nil\n @a.each { |x| yield x }\n end\nend\n\npub struct Words\n def initialize(@a : Array(String))\n end\nend\n\nimpl Enumerable for Words\n type Elem = String\n\n def each(& : String -> Nil) : Nil\n @a.each { |x| yield x }\n end\nend\n\nn = Nums.new([3, 1, 4, 1, 5])\nw = Words.new(["pear", "fig", "plum", "kiwi"])\n\nputs "to_a #{n.to_a.join(",")}"\nputs "size #{n.size}"\nputs "empty? #{n.empty?} #{Nums.new([] of Int32).empty?}"\nputs "all? #{n.all? { |x| x > 0 }}"\nputs "any? #{n.any? { |x| x > 4 }}"\nputs "none? #{n.none? { |x| x > 9 }}"\nputs "one? #{n.one? { |x| x == 4 }}"\nputs "count #{n.count { |x| x == 1 }}"\nputs "find? #{n.find? { |x| x > 3 }} #{n.find? { |x| x > 9 }.inspect}"\nputs "find #{n.find { |x| x > 3 }}"\nputs "index? #{n.index? { |x| x == 4 }}"\nputs "index #{n.index { |x| x == 4 }}"\nputs "first?/first #{n.first?} #{n.first}"\nputs "map #{n.map { |x| x * 2 }.join(",")}"\nputs "flat_map #{n.flat_map { |x| [x, x] }.join(",")}"\nputs "map_with_index #{n.map_with_index { |x, i| x * i }.join(",")}"\nputs "select #{n.select { |x| x > 2 }.join(",")}"\nputs "reject #{n.reject { |x| x > 2 }.join(",")}"\nputs "partition #{n.partition { |x| x > 2 }.map(&.join("+")).join(" | ")}"\nputs "take/skip #{n.take(2).join(",")} #{n.skip(3).join(",")}"\nputs "take_while #{n.take_while { |x| x < 4 }.join(",")}"\nputs "skip_while #{n.skip_while { |x| x < 4 }.join(",")}"\nputs "reduce #{n.reduce(0) { |a, x| a + x }}"\nputs "each_with_index #{n.map_with_index { |x, i| i }.join(",")}"\nputs "each_with_object #{n.each_with_object([] of Int32) { |o, e| o << e * 10 }.join(",")}"\nputs "join #{n.join("-")}"\nputs "includes? #{n.includes?(4)} #{n.includes?(9)}"\nputs "max?/max #{n.max?} #{n.max}"\nputs "min?/min #{n.min?} #{n.min}"\nputs "sum/product #{n.sum} #{n.product}"\nputs "sum of nothing #{Nums.new([] of Int32).sum}"\nputs "tally #{n.tally[1]}"\nputs "sorted #{n.sorted.join(",")}"\nputs "max_by #{w.max_by { |s| s.size }}"\nputs "group_by #{w.group_by { |s| s.size }[4].join(",")}"\nputs "index_by #{w.index_by { |s| s.size }[3]}"\n\nputs "each_slice #{n.each_slice(2).map(&.join("+")).join(" ")}"\nputs "each_cons #{n.each_cons(2).map(&.join("+")).join(" ")}"\nputs "chunk_while #{n.chunk_while { |a, b| b >= a }.map(&.join("+")).join(" ")}"\nputs "first(n) #{n.first(3).join(",")}"\nputs "compact_map #{n.compact_map { |x| x > 2 ? x * 10 : nil }.join(",")}"\nputs "find_value #{n.find_value { |x| x > 3 ? "big" : nil }}"\nputs "accumulate #{n.accumulate(0) { |a, x| a + x }.join(",")}"\nputs "reduce? #{n.reduce? { |a, x| a + x }}"\nputs "in_groups_of #{n.in_groups_of(2, 0).map(&.join("+")).join(" ")}"\nputs "min_by #{w.min_by { |s| s.size }}"\nputs "max_of/min_of #{w.max_of { |s| s.size }} #{w.min_of { |s| s.size }}"\nputs "minmax #{n.minmax[0]},#{n.minmax[1]}"\nputs "tally_by #{w.tally_by { |s| s.size }[4]}"\nputs "sum_by #{w.sum_by { |s| s.size }}"\n\n# SPEC II.6 finding 6: one bound replaces `Indexable | Iterable | Iterator`,\n# and `O::Elem` names what the other collection yields.\nputs "zip #{n.zip(w).map { |pair| "#{pair[0]}#{pair[1]}" }.join(" ")}"\n\ncycled = [] of Int32\nNums.new([1, 2]).cycle(2) { |x| cycled << x }\nputs "cycle #{cycled.join(",")}"\n\n# A trait with an associated type used as a type: one function, any implementer.\ndef describe(e : Enumerable) : String\n "#{e.size} items"\nend\n\nputs "as a type #{describe(n)} / #{describe(w)}"\n\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * `Words.new([...]).sum` — `sum` is bounded `where Elem : Num` and\n# `String` implements `Cmp` but not `Num`. The error names the element type\n# and the bound rather than failing somewhere inside the method body.\n#\n# * A second `impl Enumerable for Nums` — a trait with an associated type is\n# implementable once per type, because a second answer for `Elem` would make\n# every call on `Nums` ambiguous.\n#\n# * `impl Num for X` with no `impl Cmp for X` before it — `Num : Cmp` requires\n# it, and the impls have to be written in dependency order.\n", + "samples/iyi/calc.iyi": "# A language, in the language — the biggest program this prelude can hold.\n#\n# echo "x = 2 + 3 * 4\n# x * 10" | ./bin/iyi run samples/iyi/calc.iyi\n#\n# The other samples each document a rule. This one documents nothing and is\n# here for a different reason: everything else in `samples/iyi` is a page long,\n# and a language that has only been used for pages has not been used. This is\n# three modules and a scanner, a parser and an evaluator, the shape of every\n# real program that reads something, written against iyi's own 14,135-line\n# library and nothing else.\n#\n# What it cost to write is recorded where it landed. `String#[]`, `String#[](start,\n# count)`, `String#to_i` and `read_input` are in the prelude because this file\n# asked for them, which is the rule the prelude grows by (SPEC.md "0.1.0",\n# item 3). Nothing else was missing.\nmodule samples/calc\n\nimport calc/lexer\nimport calc/parser\nimport calc/ast\n\nusing calc/lexer::{scan, BadCharacter}\nusing calc/parser::{parse, Statement, SyntaxError}\nusing calc/ast::{Number, Name, Binary, UnknownName, DividedByZero}\n\n# What runs when nothing is piped in, so that the sample is a program rather\n# than a prompt. CI runs it this way.\nDEMO = "x = 2 + 3 * 4\ny = (x - 4) / 2\nx + y * 10\nmissing + 1\n1 / 0"\n\npub def run(source : String) : Nil\n tokens = scan(source)\n if tokens.is_a?(BadCharacter)\n puts "error: #{tokens.message}"\n return\n end\n\n program = parse(tokens)\n if program.is_a?(SyntaxError)\n puts "error: #{program.message}"\n return\n end\n\n scope = {} of String => Int32\n program.each do |statement|\n value = statement.body.evaluate(scope)\n\n if value.is_a?(Int32)\n if statement.target.empty?\n puts value\n else\n scope[statement.target] = value\n end\n else\n # `value` is an error member of the union, and every one of them answers\n # `message` because they implement `Error` (SPEC.md III.1).\n puts "error: #{value.message}"\n end\n end\nend\n\ntyped = read_input\nrun(typed.empty? ? DEMO : typed)\n", + "samples/iyi/collections.iyi": "# The `Enumerable` port, exercised — SPEC.md II.6.\n#\n# ./bin/crystal run samples/iyi/collections.iyi\n#\n# `std/enumerable.iyi` holds the trait. This file implements it twice and calls\n# every method it defines. Both matter: a default method is only typed when it\n# is called, so a trait that merely compiles has proved nothing about whether\n# its bodies are sound.\n#\n# `Nums` and `Words` answer `Elem` differently — `Int32` and `String` — and each\n# gets the same 57 default methods from one `each`. That is the claim II.6\n# makes, running.\n\nmodule samples/collections\n\nimport std/enumerable\nusing std/enumerable::{Enumerable}\n\npub struct Nums\n def initialize(@a : Array(Int32))\n end\nend\n\nimpl Enumerable for Nums\n type Elem = Int32\n\n def each(& : Int32 -> Nil) : Nil\n @a.each { |x| yield x }\n end\nend\n\npub struct Words\n def initialize(@a : Array(String))\n end\nend\n\nimpl Enumerable for Words\n type Elem = String\n\n def each(& : String -> Nil) : Nil\n @a.each { |x| yield x }\n end\nend\n\nn = Nums.new([3, 1, 4, 1, 5])\nw = Words.new(["pear", "fig", "plum", "kiwi"])\n\nputs "to_a #{n.to_a.join(",")}"\nputs "size #{n.size}"\nputs "empty? #{n.empty?} #{Nums.new([] of Int32).empty?}"\nputs "all? #{n.all? { |x| x > 0 }}"\nputs "any? #{n.any? { |x| x > 4 }}"\nputs "none? #{n.none? { |x| x > 9 }}"\nputs "one? #{n.one? { |x| x == 4 }}"\nputs "count #{n.count { |x| x == 1 }}"\nputs "find #{n.find { |x| x > 3 }} #{n.find { |x| x > 9 }.inspect}"\nputs "index #{n.index { |x| x == 4 }} #{n.index { |x| x == 9 }.inspect}"\nputs "first?/first #{n.first?} #{n.first}"\nputs "map #{n.map { |x| x * 2 }.join(",")}"\nputs "flat_map #{n.flat_map { |x| [x, x] }.join(",")}"\nputs "map_with_index #{n.map_with_index { |x, i| x * i }.join(",")}"\nputs "select #{n.select { |x| x > 2 }.join(",")}"\nputs "reject #{n.reject { |x| x > 2 }.join(",")}"\nputs "partition #{n.partition { |x| x > 2 }.map(&.join("+")).join(" | ")}"\nputs "take/skip #{n.take(2).join(",")} #{n.skip(3).join(",")}"\nputs "take_while #{n.take_while { |x| x < 4 }.join(",")}"\nputs "skip_while #{n.skip_while { |x| x < 4 }.join(",")}"\nputs "reduce #{n.reduce(0) { |a, x| a + x }}"\nputs "each_with_index #{n.map_with_index { |x, i| i }.join(",")}"\nputs "each_with_object #{n.each_with_object([] of Int32) { |o, e| o << e * 10 }.join(",")}"\nputs "join #{n.join("-")}"\nputs "includes? #{n.includes?(4)} #{n.includes?(9)}"\nputs "max?/max #{n.max?} #{n.max}"\nputs "min?/min #{n.min?} #{n.min}"\nputs "sum/product #{n.sum} #{n.product}"\nputs "sum of nothing #{Nums.new([] of Int32).sum}"\nputs "tally #{n.tally[1]}"\nputs "sorted #{n.sorted.join(",")}"\nputs "max_by #{w.max_by { |s| s.size }}"\nputs "group_by #{w.group_by { |s| s.size }[4].join(",")}"\nputs "index_by #{w.index_by { |s| s.size }[3]}"\n\nputs "each_slice #{n.each_slice(2).map(&.join("+")).join(" ")}"\nputs "each_cons #{n.each_cons(2).map(&.join("+")).join(" ")}"\nputs "chunk_while #{n.chunk_while { |a, b| b >= a }.map(&.join("+")).join(" ")}"\nputs "first(n) #{n.first(3).join(",")}"\nputs "compact_map #{n.compact_map { |x| x > 2 ? x * 10 : nil }.join(",")}"\nputs "find_value #{n.find_value { |x| x > 3 ? "big" : nil }}"\nputs "accumulate #{n.accumulate(0) { |a, x| a + x }.join(",")}"\nputs "reduce? #{n.reduce? { |a, x| a + x }}"\nputs "in_groups_of #{n.in_groups_of(2, 0).map(&.join("+")).join(" ")}"\nputs "min_by #{w.min_by { |s| s.size }}"\nputs "max_of/min_of #{w.max_of { |s| s.size }} #{w.min_of { |s| s.size }}"\nputs "minmax #{n.minmax[0]},#{n.minmax[1]}"\nputs "tally_by #{w.tally_by { |s| s.size }[4]}"\nputs "sum_by #{w.sum_by { |s| s.size }}"\n\n# SPEC II.6 finding 6: one bound replaces `Indexable | Iterable | Iterator`,\n# and `O::Elem` names what the other collection yields.\nputs "zip #{n.zip(w).map { |pair| "#{pair[0]}#{pair[1]}" }.join(" ")}"\n\ncycled = [] of Int32\nNums.new([1, 2]).cycle(2) { |x| cycled << x }\nputs "cycle #{cycled.join(",")}"\n\n# A trait with an associated type used as a type: one function, any implementer.\ndef describe(e : Enumerable) : String\n "#{e.size} items"\nend\n\nputs "as a type #{describe(n)} / #{describe(w)}"\n\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * `Words.new([...]).sum` — `sum` is bounded `where Elem : Num` and\n# `String` implements `Cmp` but not `Num`. The error names the element type\n# and the bound rather than failing somewhere inside the method body.\n#\n# * A second `impl Enumerable for Nums` — a trait with an associated type is\n# implementable once per type, because a second answer for `Elem` would make\n# every call on `Nums` ambiguous.\n#\n# * `impl Num for X` with no `impl Cmp for X` before it — `Num : Cmp` requires\n# it, and the impls have to be written in dependency order.\n", "samples/iyi/config.iyi": "# The first program that reads text - a `key = value` file, with the line\n# number in its error.\n#\n# ./bin/iyi run samples/iyi/config.iyi\n#\n# Text comes apart into lines, a line into its two halves around a `=`, and\n# each half loses its whitespace; a line that does not parse is an error\n# value carrying where it was (SPEC.md III.1), not a printed message. Like\n# `basics` and `inventory`, this documents no rule: it is here because the\n# prelude grows only when a program in this repository needs something\n# (SPEC.md 0.1.0 item 3), and cutting text is what the third program does.\nmodule samples/config\n\nstruct ParseError\n property line : Int32\n property message : String\n\n def initialize(@line : Int32, @message : String)\n end\nend\n\nimpl Error for ParseError\n def message : String\n "line #{@line}: #{@message}"\n end\nend\n\ndef parse(text : String) : Hash(String, String) | ParseError\n config = {} of String => String\n text.lines.each_with_index do |raw, index|\n line = raw.strip\n next if line.empty? || line.starts_with?('#')\n eq = line.index('=')\n return ParseError.new(index + 1, "no '=' in #{line.inspect}") unless eq\n key = line[0, eq].strip\n return ParseError.new(index + 1, "empty key") if key.empty?\n config[key] = line[eq + 1, line.size - eq - 1].strip\n end\n config\nend\n\ngood = "# the service\\nname = iyi\\nport= 8080\\n\\nmode =fast\\n"\ncase parse(good)\nin Hash(String, String)\n puts it # => {"name" => "iyi", "port" => "8080", "mode" => "fast"}\n puts it["port"].to_i + 1 # => 8081\n puts it.keys.join(", ") # => name, port, mode\nin ParseError then puts "error: #{it.message}"\nend\n\nbad = "name = iyi\\nbroken line\\n"\ncase parse(bad)\nin Hash(String, String) then puts "parsed"\nin ParseError then puts "error: #{it.message}" # => error: line 2: no '=' in "broken line"\nend\n\n# The pieces a parser cuts, on their own.\nputs "a,,b".split(',') # => ["a", "", "b"]\nputs "one\\ntwo\\n".lines # => ["one", "two"]\nputs " padded\\t".strip.inspect # => "padded"\nputs "config.iyi".ends_with?(".iyi") # => true\n", "samples/iyi/derive.iyi": "# `derive`, and where it runs (SPEC.md R-5, II.4).\n#\n# ./bin/iyi run samples/iyi/derive.iyi\n#\n# A derive runs once, in the module that declares the type. What it generates is\n# an ordinary declaration of that module, so it travels in the module's\n# `.iyimod` and a consumer calls the derived method without running the macro\n# and without this module's source. `bench/samples_roundtrip.sh` builds this\n# sample from its artifacts with `std/derives` deleted, which is that claim\n# taken literally.\n#\n# The macro is handed the facts R-5 lets it read: the declaration's name, and\n# the fields written in its body, each with the type it was written as. It\n# cannot enumerate a type it was not given, which is what keeps a derive\n# bounded and cacheable.\n\nmodule samples/derive\n\nimport std/derives\nusing std/derives\n\npub struct Point\n @x : Int32\n @y : Int32\n\n def initialize(@x : Int32, @y : Int32)\n end\n\n derive described\nend\n\n# One derive, two declarations. The macro reads each one's own fields, so\n# neither type had to be written down inside the macro.\npub struct Span\n @from : Int32\n @to : Int32\n @label : String\n\n def initialize(@from : Int32, @to : Int32, @label : String)\n end\n\n derive described\nend\n\nputs Point.new(3, 4).described\nputs Span.new(1, 9, "range").described\n\n# The field type is a type, not a name, so a derive can ask what it implements.\n# `Name` and `Show` both belong to `std/derives`, and both arrive from that\n# module's artifact when its source is gone: `@label` is rendered through the\n# trait, and `@count` is skipped because `Int32` does not implement it.\n#\n# `getter label : Name` is a macro call, so the field it declares exists only\n# once it has run. A derive reads the declarations above it, which is why\n# `derive` goes last. Below `getter`, this would be an error rather than a\n# method generated over no fields.\n#\n# One line, two derives: each macro reads the same declaration and adds its own\n# methods.\npub struct Tag\n getter label : Name\n @count : Int32\n\n def initialize(@label : Name, @count : Int32)\n end\n\n derive described, shown\nend\n\nputs Tag.new(Name.new("hi"), 2).described\nputs Tag.new(Name.new("hi"), 2).shown\n\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * `derive missing` where nothing exports a `missing` macro. The error names\n# the macro and the declaration it would have to be written as, rather than\n# failing as an undefined method on the type's metaclass.\n#\n# * `derive` outside a class or struct body. There is no declaration to read,\n# so there is nothing for the macro to be handed.\n#\n# * A `derive` inside a reopened type from another module. That is refused\n# before the derive is reached, by R-3: iyi has no open classes, so a module\n# cannot add to a type it does not declare.\n#\n# * A `getter` (or any macro call that declares a field) written below the\n# `derive` that should read it. The derive names the call and says which way\n# to move, rather than generating a method over the fields it happened to see.\n", + "samples/iyi/enums.iyi": "# An `enum`, used the way a program uses one — SPEC.md IV.2.\n#\n# ./bin/iyi run samples/iyi/enums.iyi\n#\n# The compiler gives an enum two methods: `value`, the integer the member was\n# numbered with, and `new`, which wraps one. Everything below is the prelude's\n# (`src/iyi/enum.iyi`), and until this sample asked for it none of it was\n# there: `Level::Warn == Level::Warn` was false, `"#{Level::Warn}"` printed\n# `Samples::Enums::Level`, and a `case` over the members matched nothing —\n# because a `when` that names a member lowers to `Level::Warn === level`, and\n# `===` is `==`.\n#\n# Two enums, because the compiler writes different code for each: a plain one\n# whose `case` the exhaustiveness checker holds to every member, and a\n# `@[Flags]` one, where it synthesizes `None`, `All` and question methods that\n# ask `includes?`.\n\nmodule samples/enums\n\n# A level is an order as well as a name, which is why the threshold below\n# works: `Warn >= Info` is the whole reason a program writes one.\nenum Level : Int32\n Debug = 0\n Info = 1\n Warn = 2\n Error = 3\nend\n\n# A set of bits, one per member, which is what `@[Flags]` numbers them as.\n@[Flags]\nenum Mode\n Read\n Write\n Append\nend\n\n# 1. A member prints as its name, in interpolation and out of `to_s`.\nlevel = Level::Warn\nputs "level: #{level}"\nputs "spelled: " + Level::Error.to_s\n\n# 2. Equality, and the `case` that is built on it — a `when` naming a member\n# lowers to `===`, which is `==`.\nputs "same: #{level == Level::Warn}"\nputs "other: #{level == Level::Debug}"\n\n# `in` rather than `when`, which is the form the exhaustiveness checker holds\n# to every member: the day a member is added this sample stops compiling\n# rather than falling through, and the answer is a `String` rather than a\n# `String | Nil` because there is nothing left for an `else` to catch.\nverdict = case level\n in Level::Debug then "quiet"\n in Level::Info then "ordinary"\n in Level::Warn then "loud"\n in Level::Error then "loudest"\n end\nputs "verdict: #{verdict}"\n\n# 3. The question methods the compiler writes, which call the prelude's `==`.\nputs "warn? #{level.warn?}"\nputs "error? #{level.error?}"\n\n# 4. An order, so a level can be a threshold.\nputs "at least info: #{level >= Level::Info}"\nputs "below error: #{level < Level::Error}"\n\n# 5. The members, as a list — a menu, a validation table, a loop over all of\n# them. `names` is the same list said in text.\nLevel.values.each do |candidate|\n print candidate.to_s\n print " "\nend\nputs ""\nputs "names: #{Level.names.size}"\n\n# 6. A name read back, which is what a program does with an argument or a\n# configuration line. Unknown text answers `nil` rather than a panic, and the\n# lenient form takes the spelling a person types.\nputs "parsed: #{Level.parse?("Error").to_s}"\nputs "lenient: #{Level.parse?("warn").to_s}"\nputs "unknown: #{Level.parse?("shout").inspect}"\n\n# 7. The value, for a wire format or a C call, and the way back.\nputs "value: #{Level::Error.value}"\nputs "from 1: #{Level.from_value?(1).to_s}"\nputs "from 9: #{Level.from_value?(9).inspect}"\n\n# 8. A flags enum: the two set operators, membership, and a `to_s` that says\n# which bits are in it.\nmode = Mode::Read | Mode::Write\nputs "mode: #{mode}"\nputs "read: #{mode.includes?(Mode::Read)}"\nputs "append: #{mode.append?}"\nputs "none: #{Mode::None.to_s}"\nputs "masked: #{(mode & Mode::Write).to_s}"\n\n# 9. As a `Hash` key, which needs `hash` as well as `==`.\ncounts = {} of Level => Int32\ncounts[Level::Warn] = 2\ncounts[Level::Error] = 1\nputs "warns: #{counts[Level::Warn]}"\nputs "errors: #{counts[Level::Error]}"\n", "samples/iyi/errors.iyi": "# Errors are ordinary union members — SPEC.md III.1.\n#\n# ./bin/crystal run samples/iyi/errors.iyi\n#\n# No `Result` wrapper, no exception hierarchy, no new type machinery: unions\n# already exist and already carry a type id. What makes a member an *error*\n# member is that its type implements `Error`, a trait the compiler owns.\n\nmodule samples/errors\n\npub struct IOError\n getter path : String\n\n def initialize(@path : String)\n end\nend\n\n# `Error` is not declared here. It is the compiler's, so that `!` can ask\n# whether a member implements it without a name in scope deciding the answer.\nimpl Error for IOError\n def message : String\n "no such file: #{path}"\n end\nend\n\npub struct ParseError\n getter text : String\n\n def initialize(@text : String)\n end\nend\n\nimpl Error for ParseError\n def message : String\n "not a number: #{text}"\n end\nend\n\npub def read(path : String) : String | IOError\n return IOError.new(path) if path == "missing"\n path == "bad" ? "twelve" : "12"\nend\n\npub def to_number(text : String) : Int32 | ParseError\n return ParseError.new(text) unless text == "12"\n 12\nend\n\n# `!` propagates. If the value is a non-error member the expression evaluates to\n# it — narrowed, so `to_number` sees a `String` and not the union. If it is an\n# error member it is returned from here, which is why `load` has to admit both\n# errors in its own return type. Nothing enforces that separately: it is the\n# ordinary return-type check on the return the operator writes.\npub def load(path : String) : Int32 | IOError | ParseError\n text = read(path)!\n to_number(text)!\nend\n\n# Handling needs nothing new. An exhaustive `case` already checks totality, so\n# adding an error member to `load` turns every incomplete `case` on it into a\n# compile error. That is the main ergonomic argument for the whole approach and\n# it cost nothing to build.\n["config", "missing", "bad"].each do |path|\n case load(path)\n in Int32 then puts "read #{it}"\n in IOError then puts "io: #{it.message}"\n in ParseError then puts "parse: #{it.message}"\n end\nend\n\n# `it` is the value being matched, and it is narrowed per branch: the first sees\n# an `Int32`, the other two see one error type each, which is how `.message`\n# resolves without anything asking what the union holds. It is an assignment the\n# compiler writes into each branch, not new machinery — so `it` outlives the\n# `case` the way any variable assigned inside an `if` does, and a nested `case`\n# shadows it.\n\n# Where a `case` is overkill, `.or` takes a default and `.or_panic` takes the\n# value or dies (III.1.3). These cannot be ordinary methods: by II.1 a real\n# method call on `Int32 | IOError` would demand it from *both* members, and a\n# method every error type has to supply is what this design avoids. So the\n# compiler knows them by name — which is also why `or` and `or_panic` are not\n# names an iyi program can use for anything else.\n["config", "missing", "bad"].each do |path|\n puts "or: #{load(path).or(0)}"\nend\n\n# `.or` is an `is_a?(::Error)` and nothing more, so the result type falls out\n# instead of being computed: the default's type unioned with the non-error\n# members. Here that is `Int32`, and the default is only evaluated when there is\n# an error to recover from.\nputs "or: #{load("missing").or(-1)}"\n\n# `.or_panic` yields the non-error members alone, because what it calls never\n# returns. It reaches `message` on a value narrowed to `IOError | ParseError` —\n# two unrelated types — which resolves by II.1: both implement `Error`, so their\n# union does. Until panics exist (III.1.4), this unwinds as a Crystal exception.\nputs "panic: #{load("config").or_panic}"\n\n# ---------------------------------------------------------------------------\n# defer — III.1.4\n# ---------------------------------------------------------------------------\n#\n# Once errors are values returned early, the interesting exit from a body is an\n# ordinary `return` in the middle of it, and `begin`/`ensure` makes you wrap\n# everything after the acquisition to cover it. `defer` names the cleanup at the\n# acquisition instead. It expands to that same `ensure`, so it runs on a normal\n# exit, on a `!` propagation, and on an unwind — no new runtime machinery, only\n# a different shape.\npub def session(path : String) : Int32 | IOError | ParseError\n puts " open outer"\n defer puts " close outer"\n\n text = read(path)!\n\n puts " open inner"\n defer puts " close inner"\n\n to_number(text)!\nend\n\n# Cleanup is LIFO, and that is the nesting rather than a rule: the second\n# `defer` expands inside the first one's body, so its `ensure` is the inner one.\n# The failing run proves the part that matters — `read(path)!` returns from the\n# middle of the body and the cleanup still happens.\nputs "success:"\nputs session("config").or(-1)\nputs "propagating:"\nputs session("missing").or(-1)\n\n# The scope is the block, not the function. This is the departure from Go, and\n# it is what the expansion does rather than an extra rule: the cleanup lands\n# inside the loop body, so it runs each iteration instead of piling up until the\n# function returns — Go's best-known wart with the feature.\n2.times do |i|\n defer puts " iteration #{i} closed"\n puts " iteration #{i}"\nend\n\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * Dropping the `in ParseError` branch — `case is not exhaustive`.\n#\n# * `def go : String` with `read(p)!` in it — the error names the return type\n# and the error being propagated into it.\n#\n# * `!` on something with no error member, such as an `Int32?`. Rejected rather\n# than given a surprising meaning: `Nil` does not implement `Error` (III.1.5),\n# so without the check this would compile and silently do nothing.\n#\n# * `!` on a value whose every member is an error. It could never produce one,\n# and a function that genuinely never succeeds returns `NoReturn`.\n#\n# * `def sort!` — `!` is not part of a name (III.1.7). This is the one place\n# that still explains the naming convention, because after a `def` name there\n# is nothing to propagate.\n#\n# * `.or` and `.or_panic` on the same two operands `!` rejects. With no error\n# member there is nothing to recover; with every member an error, `.or` can\n# only return its default and `.or_panic` can only panic.\n#\n# * `.or 0` without parentheses — `.or` takes the default in parentheses, so\n# the shape cannot be confused with a call to a method of that name.\n#\n# * `it` in a `case` over a tuple, such as `case {a, b}` — there is no single\n# value to name, so `it` stays undefined rather than being bound to one of\n# the elements.\n#\n# * `defer close(f)!` — a `defer` runs while the scope is already being left,\n# possibly carrying an error of its own, so a second one leaving from there\n# is the error-during-error problem. Handle it in the `defer` body.\n#\n# * `impl Error for String` here — the orphan rule (R-3). `Error` is the\n# compiler's and `String` is the prelude's, so neither belongs to a module\n# this impl could be at home in, and every module that can name both would be\n# free to write its own.\n#\n# ---------------------------------------------------------------------------\n# Not yet implemented\n# ---------------------------------------------------------------------------\n#\n# * Panics (III.1.4). They are meant to be catchable only at task boundaries,\n# and what a task boundary is belongs to Part V.5. Until then `.or_panic`\n# raises a Crystal exception, and `defer` runs on that unwind either way.\n", "samples/iyi/files.iyi": "# Files — the missing piece between a demonstration and a tool.\n#\n# ./bin/iyi run samples/iyi/files.iyi\n#\n# Writes a file, reads it back, checks `exists?`, then deletes it and proves it\n# is gone. The path is next to the process and named so it is obvious.\n\nmodule samples/files\n\npath = "iyi-files-sample.txt"\nFile.write(path, "hello from iyi\\n")\nputs File.read(path)\nputs File.exists?(path)\nputs File.exists?("iyi-files-sample-missing.txt")\nFile.delete(path)\nputs File.exists?(path)\n", "samples/iyi/format.iyi": "# Format strings for iyi: width, alignment, padding, precision, and bases.\n#\n# ./bin/iyi run samples/iyi/format.iyi\n#\n# `sprintf` returns formatted text, `printf` writes directly to standard output,\n# and `String.format` or `format % args` provides the object surface.\n# This fills the gaps string interpolation cannot: field width, alignment,\n# zero-padding, float precision with correct rounding, integer bases, and sign.\n\nmodule samples/format\n\nimport std/format\nusing std/format::{sprintf, printf}\n\n# 1. Strings and characters\nputs sprintf("hello %s", "world")\nputs sprintf("char '%c' (ascii %d)", 'A', 65)\n\n# 2. Width and alignment\ncell = "hi"\nputs sprintf("right '%5s'", cell)\nputs sprintf("left '%-5s'", cell)\n\n# 3. Integer bases and prefixes\nn = 255\nputs sprintf("dec: %d", n)\nputs sprintf("hex: %x", n)\nputs sprintf("HEX: %#X", n)\nputs sprintf("oct: %#o", n)\nputs sprintf("bin: %#b", 5)\n\n# 4. Signs and zero padding\nputs sprintf("plus: %+d", 42)\nputs sprintf("zero: %06d", 42)\nputs sprintf("sign zero: %+06d", 42)\nputs sprintf("negative zero: %06d", -42)\n\n# 5. Precision\nputs sprintf("int precision: %.4d", 7)\nputs sprintf("float precision: %.2f", 3.14159)\nputs sprintf("float precision: %.4f", 3.14159)\nputs sprintf("string precision: %.3s", "cinnabar")\n\n# 6. Boundary cases\nputs sprintf("wider than field: %3d", 12345)\nputs sprintf("zero precision float: %.0f", 3.6)\nputs sprintf("zero precision zero int: '%5.0d'", 0)\nputs sprintf("min int64: %d", -9223372036854775808_i64)\nputs sprintf("max int64: %d", 9223372036854775807_i64)\n\n# 7. Surface styles\nputs String.format("String.format: %s = %d", "answer", 42)\nputs "percent tuple: %s = %04d" % {"score", 99}\nprintf("printf: %s completed\\n", "run")\n", @@ -26,9 +27,9 @@ "samples/iyi/generics.iyi": "# Implementing a trait for a generic type — SPEC.md II.7.\n#\n# ./bin/crystal run samples/iyi/generics.iyi\n\nmodule samples/generics\n\npub trait Show\n abstract def show : String\nend\n\npub struct Box(T)\n getter value : T\n\n def initialize(@value : T)\n end\nend\n\n# `forall T` introduces the parameter, and it is required. Without it, whether\n# `T` names a new parameter or a type already in scope would depend on what\n# happens to be imported — so a library could change what this impl means by\n# adding an export. Rust requires the same binder for the same reason.\nimpl Show for Box(T) forall T\n def show : String\n "Box(#{value})"\n end\nend\n\npub struct Pair(A, B)\n getter first : A\n getter second : B\n\n def initialize(@first : A, @second : B)\n end\nend\n\n# `Pair` declared its parameters `A, B`; this impl calls them `X, Y`. The names\n# are the impl's own and bind positionally, so an impl states arity and not\n# vocabulary — what a type chose to call its parameters is its own business.\n# Crystal requires a reopened generic to repeat the declared names; that leaks\n# the declaration's naming into every impl of it.\nimpl Show for Pair(X, Y) forall X, Y\n def show : String\n "#{label(first)}, #{label(second)}"\n end\n\n def label(v : X) : String\n "first=#{v}"\n end\n\n def label(v : Y) : String\n "second=#{v}"\n end\nend\n\nputs Box.new(42).show\nputs Box.new("hello").show\nputs Pair.new(1, "two").show\n\n# One impl covers every instantiation, so instantiations of the same generic\n# dispatch through the trait together.\nshown = [Box.new(1), Box.new("two"), Pair.new(3, "four")] of Show\nshown.each { |s| puts s.show }\n\n# ---------------------------------------------------------------------------\n# Associated types — SPEC.md II.6\n# ---------------------------------------------------------------------------\n#\n# `type Elem` declares a type the *impl* answers, not one the caller picks. A\n# collection iterates one way, so its element type is an output of the impl;\n# making it a parameter would leave a call on the collection ambiguous about\n# which impl it meant. This is the shape `Enumerable` needs: 130 methods\n# written against `Elem`, on one `abstract def each`.\n\npub trait Container\n type Elem\n\n abstract def first : Elem\n\n # A default method. `Elem` resolves to whatever the impl answered, so this\n # one body serves every implementer without being written per type.\n def pair : Array(Elem)\n [first, first]\n end\nend\n\npub struct Names\n def initialize\n end\nend\n\nimpl Container for Names\n type Elem = String\n\n def first : String\n "ada"\n end\nend\n\npub struct Counts\n def initialize\n end\nend\n\nimpl Container for Counts\n type Elem = Int32\n\n def first : Int32\n 42\n end\nend\n\nputs Names.new.pair.join(", ")\nputs Counts.new.pair.sum\n\n# ---------------------------------------------------------------------------\n# Bounds and requirements — SPEC.md II.6\n# ---------------------------------------------------------------------------\n#\n# About a quarter of `Enumerable` is valid only for some element types: `max`\n# needs `Comparable`, `sum` needs `Numeric`, `tally` needs `Hashable`. `where`\n# says so. Crystal duck-types these and fails at instantiation naming neither\n# the bound nor the element type.\n\npub trait Cmp\n abstract def cmp(other : self) : Int32\nend\n\n# `Ord : Cmp` requires an implementer to implement `Cmp` too. A requirement,\n# not an inclusion: implementing `Ord` does not hand a type `Cmp`'s methods,\n# it insists the type has its own `impl Cmp for` it. Were it an include, every\n# implementer of `Ord` would satisfy `Cmp` with no impl anywhere — the open\n# class hole R-3 exists to close.\npub trait Ord : Cmp\n type Item\n\n abstract def item : Item\n\n # `Ord` never declares `cmp`; the requirement is what guarantees it is there.\n def beats(other : self) : Bool\n cmp(other) > 0\n end\n\n # Valid only when the item type is comparable. `Ranked` answers `Item` with\n # `Score`, which implements `Cmp`, so this one is callable there.\n def best : Item where Item : Cmp\n item\n end\nend\n\npub struct Score\n getter n : Int32\n\n def initialize(@n : Int32)\n end\nend\n\nimpl Cmp for Score\n def cmp(other : self) : Int32\n n - other.n\n end\nend\n\npub struct Ranked\n getter n : Int32\n\n def initialize(@n : Int32)\n end\nend\n\nimpl Cmp for Ranked\n def cmp(other : self) : Int32\n n - other.n\n end\nend\n\nimpl Ord for Ranked\n type Item = Score\n\n def item : Score\n Score.new(n)\n end\nend\n\nputs Ranked.new(2).beats(Ranked.new(1))\nputs Ranked.new(7).best.n\n\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * `impl Ord for X` with no `impl Cmp for X` before it — the error names the\n# required trait. The impls have to be written in dependency order, because\n# the check runs where the impl is and nothing has looked ahead.\n#\n# * `Ranked.new(1).best` if `Item` were answered with a type that does not\n# implement `Cmp` — the error names the element type and the bound, which is\n# the whole reason the bound is written.\n#\n# * A second `impl Container for Names` — a trait with associated types is\n# implementable only once for a type. That restriction *is* the difference\n# from a parameter: a second answer for `Elem` would make `Names.new.first`\n# ambiguous. A trait with parameters has no such rule, because several impls\n# are the point of having parameters.\n#\n# * `impl Container for Names` with no `type Elem = ...` — the error names the\n# associated type left unanswered.\n#\n# * `type Elem = String` written in the trait — a trait declares an associated\n# type, it does not answer it.\n#\n# * `impl Show for Box(T)` without `forall T` — the error names the binder and\n# writes out the line you meant.\n#\n# * `impl Show for Box(Int32)` — iyi has no specialised impls. Overlapping\n# impls need a rule for which one wins, and that rule has to stay sound when\n# the two live in modules compiled separately; Rust has wanted this for a\n# decade and it is still unstable, and Java cannot express it at all.\n# Refusing it is what keeps `Box(T)`'s method set knowable without knowing T.\n#\n# * `impl Show for T forall T : Show` — a blanket impl, refused for the reason\n# open classes are: it lets a distant module add methods to every type.\n#\n# ---------------------------------------------------------------------------\n# Not yet implemented\n# ---------------------------------------------------------------------------\n#\n# * `forall T : Show` parses but is rejected. A bound makes the impl\n# conditional, and a conditional impl has to be checked where the type is\n# instantiated rather than where the impl is written.\n#\n# * Under R-4 an impl on a generic type should be compiled once per GC shape\n# rather than once per instantiation. Today this monomorphises, exactly the\n# way Crystal does.\n#\n# * Two impls of the same *parameterised* trait for one type collide when their\n# methods take the same arguments: `impl Into(String) for U` and\n# `impl Into(Int32) for U` both define `into`, and the second silently wins.\n# Parameters exist for exactly this case, so it needs an answer — Rust's is\n# to pick the impl from the type the call site expects.\n", "samples/iyi/grid.iyi": "# The first program with a table in it - rows of cells, built from their\n# coordinates, read across, down and along the diagonal.\n#\n# ./bin/iyi run samples/iyi/grid.iyi\n#\n# An array built by a block, a range asked for a value per position, and\n# the small set a list of numbers gets asked for: reversed, unique, paired\n# with another, one taken out. Like `basics`, `inventory` and `config`, this\n# documents no rule; it is here because the prelude grows only when a\n# program in this repository needs something (SPEC.md 0.1.0 item 3).\nmodule samples/grid\n\nn = 4\ngrid = Array(Array(Int32)).new(n) { |r| Array(Int32).new(n) { |c| r * n + c } }\ngrid.each { |row| puts row.map { |v| v.to_s.rjust(3) }.join }\n# => 0 1 2 3\n# => 4 5 6 7\n# => 8 9 10 11\n# => 12 13 14 15\n\nputs grid.map(&.sum).sum # => 120\nputs (0...n).map { |i| grid[i][i] } # => [0, 5, 10, 15]\nputs (0...n).map { |c| grid.map { |row| row[c] } }.first # => [0, 4, 8, 12]\n\n# 2. A list of numbers, asked the usual things.\nputs (1..5).to_a.reversed # => [5, 4, 3, 2, 1]\nputs (1..10).select { |i| i.even? } # => [2, 4, 6, 8, 10]\nputs (1..10).sum # => 55\nputs [3, 1, 3, 2, 1].uniq # => [3, 1, 2]\nputs [1, 2, 3].zip(["a", "b", "c"]) # => [{1, "a"}, {2, "b"}, {3, "c"}]\n\nletters = ["b", "a", "c"]\nputs letters.index("c").inspect # => 2\nputs letters.delete("a") # => true\nputs letters # => ["b", "c"]\n\n# 3. A line built piece by piece, then ended.\n5.times { |i| print i }\nputs # => 01234\n", "samples/iyi/hello.iyi": "# The first iyi program.\n#\n# ./bin/crystal run samples/iyi/hello.iyi\n#\n# Everything below compiles and runs today. What is still missing is listed at\n# the bottom — this file is meant to stay honest about how far the\n# implementation actually reaches.\n\n# R-1: the compilation-unit header. A module is the unit of compilation and of\n# recompilation; `import` edges between modules form a DAG. Reading a module's\n# `.iyimod` is what will one day replace re-analysing its source.\nmodule samples/hello\n\n# R-3: a trait is a named set of requirements. `abstract def` marks a method an\n# implementer must provide. Traits replace Crystal's open classes: a type's\n# method set is knowable without reading the whole program.\npub trait Greet\n abstract def greet : String\nend\n\npub trait Loud\n abstract def shout : String\nend\n\n# R-2: `pub` marks a declaration as exported, and exported declarations carry\n# full signatures. That signature is the module's contract, and the only thing\n# a consumer ever needs to read.\npub struct User\n getter name : String\n\n def initialize(@name : String)\n end\nend\n\npub struct Robot\n getter id : Int32\n\n def initialize(@id : Int32)\n end\nend\n\n# `impl Trait for Type` — the replacement for reopening a class. R-3's orphan\n# rule requires it to live in the module defining either the trait or the\n# type, and that is enforced: both are defined right here. `modules.iyi` shows\n# the cross-module case, where the choice actually costs something.\nimpl Greet for User\n def greet : String\n "Hello, #{name}!"\n end\nend\n\n# A type may implement several traits...\nimpl Loud for User\n def shout : String\n greet.upcase\n end\nend\n\n# ...and a trait may be implemented by several types.\nimpl Greet for Robot\n def greet : String\n "BEEP #{id}"\n end\nend\n\n# `impl` is the only way to give a type a trait. SPEC.md II.8 — each of these\n# is refused, and each for its own reason:\n#\n# struct User\n# include Greet # a type acquires a trait by having an impl, whose\n# end # location R-3 can check; `include` has no such rule\n#\n# using samples/hello/greet # a trait exports no names to bring into\n# # scope — a trait method is resolved from the\n# # receiver, never from a `using`\n#\n# impl Greet for Loud # a blanket impl in disguise\n#\n# And leaving a requirement unmet is an error where the impl is written, not\n# where the type is later used:\n#\n# impl Loud for Robot\n# end\n# # => impl Loud for Robot is missing a method required by the trait: shout\n\n# Not exported. No signature required, because nothing outside this module can\n# depend on it — inference inside a module costs only what the module costs.\ndef describe(item)\n item.greet\nend\n\n# A trait bound on a method's own type parameter — SPEC.md II.7 rule 3. The\n# method exists for every `T`; the bound is checked where `T` binds, so a call\n# with a type that does not implement `Greet` is rejected at that call:\n#\n# announce(42)\n# # => Int32 does not implement Samples::Hello::Greet, required by `T`\n# # in `announce`\n#\n# This is the shape Kemal's router needs: `&block : Context -> B forall\n# B : IntoBody` replaces a per-request runtime type check with one at compile\n# time. Note this is *not* the impl-level bound below — nothing here is\n# conditional.\npub def announce(item : T) : String forall T : Greet\n "-> #{item.greet}"\nend\n\n# Ruby ergonomics, unchanged and free to compile: blocks, string\n# interpolation, everything-is-an-expression, local inference.\nputs User.new("iyi").greet\nputs User.new("iyi").shout\nputs Robot.new(42).greet\n\n# A trait as a type: both implementers dispatch through it.\ncrowd = [User.new("crystal"), Robot.new(7)] of Greet\ncrowd.each { |member| puts describe(member) }\n\nputs announce(Robot.new(9))\n\n# ---------------------------------------------------------------------------\n# Not yet implemented\n# ---------------------------------------------------------------------------\n#\n# * Trait bounds on a generic *impl*'s parameters (`impl Show for Box(T)\n# forall T : Show`) parse but are rejected — unlike the bound on `announce`\n# above, which works. A bound on an impl makes the impl conditional:\n# `Box(Int32)` would implement `Show` only if `Int32` did, which has to be\n# checked where the type is instantiated rather than where the impl is\n# written. A different mechanism, and the harder of the two.\n#\n# * Dictionary-passing generics is unimplemented: `announce` above is\n# monomorphised the way Crystal monomorphises it.\n#\n# * `.iyimod` reaches the front end and stops there. `modules.iyi` and\n# `immutable.iyi` compile with the imported module's source deleted, but a\n# build that reads artifacts cannot yet produce a program, because the\n# artifact carries declarations and no object code (SPEC.md IV.1f).\n#\n# * The prelude under this file is iyi's own, 833 lines of it, and that is why\n# building it costs 0.32 s rather than 2.10 s. It is not a standard library\n# and does not pretend to be one: no `Float64#to_s`, no `Range`, no `Set`,\n# no formatting, and `raise` is a panic that prints and exits. What is in it\n# is what a sample called for (SPEC.md, "0.1.0", item 3).\n#\n# See `modules.iyi` for `import` and `using` across files, `generics.iyi` for\n# implementing a trait for a generic type and for associated types, bounds and\n# requirements, and `collections.iyi` for `Enumerable` ported onto all of it.\n", - "samples/iyi/immutable.iyi": "# The shareable immutable sequence, exercised — SPEC.md III.4.7.\n#\n# ./bin/crystal run samples/iyi/immutable.iyi\n#\n# `std/list.iyi` holds the type. This file is the evidence for the two claims\n# made about it, because a type that merely compiles has proved neither:\n#\n# * It is genuinely immutable — nothing a caller keeps a hold of can change\n# a list after it exists, which is what makes it shareable under III.4.4.\n# * It costs one `each` and gets the rest, which is II.6's claim tested on a\n# second type after `Enumerable` itself, and the first time on a *generic*\n# one.\n\nmodule samples/immutable\n\nimport std/list\nusing std/list::{List}\n\n# Two ways of naming the same type, both across a module boundary. The second\n# lets the argument be inferred, and it is written that way on purpose: the\n# artifact carries no machine code for a generic, so `List(String)` is a type\n# this file makes, and inference puts `new` on `List(T)` instead of on the\n# instance. That difference once linked undefined and nothing here caught it,\n# because every line in this file wrote the argument out.\nnums = List(Int32).new([3, 1, 4, 1, 5])\nwords = List.new(["pear", "fig", "plum", "kiwi"])\n\n# ---------------------------------------------------------------------------\n# Its own surface: small on purpose\n# ---------------------------------------------------------------------------\nputs "size #{nums.size}"\nputs "empty? #{nums.empty?} #{List(Int32).new([] of Int32).empty?}"\nputs "at #{nums.at(0)} #{nums.at(4)}"\n\n# ---------------------------------------------------------------------------\n# Immutability, demonstrated rather than asserted\n# ---------------------------------------------------------------------------\n#\n# `appended` returns a new list and leaves this one alone. By III.1.7 the plain\n# verb names the mutating form, so the participle is the only name this type\n# can have — `push` and `sort` describe something it cannot do. The naming\n# convention was settled before any library code existed precisely so that it\n# would fall out here rather than be applied here.\nlonger = nums.appended(9)\nputs "appended #{longer.size} from #{nums.size}, original #{nums.to_a.join(",")}"\nputs "concatenated #{nums.concatenated(List(Int32).new([7])).to_a.join(",")} / #{nums.size}"\n\n# The constructor copies, and this is the line that shows why it must. `source`\n# is still held by this file; if the list had kept it rather than copying, a\n# task holding `taken` would see it change underneath. That copy is the price\n# of having no borrow checker to say "I own this now" — one copy, at the\n# boundary, once.\nsource = [1, 2]\ntaken = List(Int32).new(source)\nsource << 3\nputs "copied list #{taken.size}, source #{source.size}"\n\n# ---------------------------------------------------------------------------\n# Everything below here is the trait's, from one `each`\n# ---------------------------------------------------------------------------\nputs "to_a #{nums.to_a.join(",")}"\nputs "all?/any? #{nums.all? { |x| x > 0 }} #{nums.any? { |x| x > 4 }}"\nputs "none?/one? #{nums.none? { |x| x > 9 }} #{nums.one? { |x| x == 4 }}"\nputs "count #{nums.count { |x| x == 1 }}"\nputs "find?/find #{nums.find? { |x| x > 3 }} #{nums.find { |x| x > 3 }}"\nputs "index?/index #{nums.index? { |x| x == 4 }} #{nums.index { |x| x == 4 }}"\nputs "first?/first #{nums.first?} #{nums.first}"\nputs "map #{nums.map { |x| x * 2 }.join(",")}"\nputs "flat_map #{nums.flat_map { |x| [x, x] }.join(",")}"\nputs "map_with_index #{nums.map_with_index { |x, i| x * i }.join(",")}"\nputs "select/reject #{nums.select { |x| x > 2 }.join(",")} #{nums.reject { |x| x > 2 }.join(",")}"\nputs "partition #{nums.partition { |x| x > 2 }.map(&.join("+")).join(" | ")}"\nputs "take/skip #{nums.take(2).join(",")} #{nums.skip(3).join(",")}"\nputs "take_while #{nums.take_while { |x| x < 4 }.join(",")}"\nputs "skip_while #{nums.skip_while { |x| x < 4 }.join(",")}"\nputs "reduce #{nums.reduce(0) { |a, x| a + x }}"\nputs "each_with_object #{nums.each_with_object([] of Int32) { |o, e| o << e * 10 }.join(",")}"\nputs "join #{nums.join("-")}"\nputs "includes? #{nums.includes?(4)} #{nums.includes?(9)}"\nputs "max/min #{nums.max} #{nums.min}"\nputs "sum/product #{nums.sum} #{nums.product}"\nputs "tally #{nums.tally[1]}"\nputs "sorted #{nums.sorted.join(",")}"\nputs "max_by #{words.max_by { |s| s.size }}"\nputs "group_by #{words.group_by { |s| s.size }[4].join(",")}"\n\n# The trait reaches a generic type, which is the part that was not built until\n# this file needed it. `type Elem = T` binds the associated type to the impl's\n# own parameter, so `List(Int32)` enumerates `Int32` and `List(String)`\n# enumerates `String` from the same impl — II.6 × II.7, which had never met.\nputs "two elem types #{nums.map { |x| x + 1 }.join(",")} / #{words.map { |s| s.size }.join(",")}"\n\n# ---------------------------------------------------------------------------\n# All of the above, with `src/std` deleted\n# ---------------------------------------------------------------------------\n#\n# ./bin/crystal build --no-codegen --emit-iyimod /tmp/mods samples/iyi/immutable.iyi\n# mv src/std /tmp/std-elsewhere\n# ./bin/crystal build --use-iyimod /tmp/mods samples/iyi/immutable.iyi # compiles\n# mv /tmp/std-elsewhere src/std\n#\n# This file is the hard case for R-1's contract, not `modules.iyi`: what has to\n# survive the round trip is a generic struct, a 575-line trait with an\n# associated type, and a generic impl that answers it. `mod dump --declarations\n# /tmp/mods/std/list.iyimod` is what the second build reads — the six headers\n# with no bodies, and `impl … for Std::List::List(T) forall T` with its\n# `type Elem = T`. Every call above is checked against those and against\n# nothing else (SPEC.md IV.1f).\n#\n# Not codegen: `--use-iyimod` implies `--no-codegen`, so this compiles the file\n# without producing a program. `./bin/crystal run` above is still an ordinary\n# build that reads the sources.\n", + "samples/iyi/immutable.iyi": "# The shareable immutable sequence, exercised — SPEC.md III.4.7.\n#\n# ./bin/crystal run samples/iyi/immutable.iyi\n#\n# `std/list.iyi` holds the type. This file is the evidence for the two claims\n# made about it, because a type that merely compiles has proved neither:\n#\n# * It is genuinely immutable — nothing a caller keeps a hold of can change\n# a list after it exists, which is what makes it shareable under III.4.4.\n# * It costs one `each` and gets the rest, which is II.6's claim tested on a\n# second type after `Enumerable` itself, and the first time on a *generic*\n# one.\n\nmodule samples/immutable\n\nimport std/list\nusing std/list::{List}\n\n# Two ways of naming the same type, both across a module boundary. The second\n# lets the argument be inferred, and it is written that way on purpose: the\n# artifact carries no machine code for a generic, so `List(String)` is a type\n# this file makes, and inference puts `new` on `List(T)` instead of on the\n# instance. That difference once linked undefined and nothing here caught it,\n# because every line in this file wrote the argument out.\nnums = List(Int32).new([3, 1, 4, 1, 5])\nwords = List.new(["pear", "fig", "plum", "kiwi"])\n\n# ---------------------------------------------------------------------------\n# Its own surface: small on purpose\n# ---------------------------------------------------------------------------\nputs "size #{nums.size}"\nputs "empty? #{nums.empty?} #{List(Int32).new([] of Int32).empty?}"\nputs "at #{nums.at(0)} #{nums.at(4)}"\n\n# ---------------------------------------------------------------------------\n# Immutability, demonstrated rather than asserted\n# ---------------------------------------------------------------------------\n#\n# `appended` returns a new list and leaves this one alone. By III.1.7 the plain\n# verb names the mutating form, so the participle is the only name this type\n# can have — `push` and `sort` describe something it cannot do. The naming\n# convention was settled before any library code existed precisely so that it\n# would fall out here rather than be applied here.\nlonger = nums.appended(9)\nputs "appended #{longer.size} from #{nums.size}, original #{nums.to_a.join(",")}"\nputs "concatenated #{nums.concatenated(List(Int32).new([7])).to_a.join(",")} / #{nums.size}"\n\n# The constructor copies, and this is the line that shows why it must. `source`\n# is still held by this file; if the list had kept it rather than copying, a\n# task holding `taken` would see it change underneath. That copy is the price\n# of having no borrow checker to say "I own this now" — one copy, at the\n# boundary, once.\nsource = [1, 2]\ntaken = List(Int32).new(source)\nsource << 3\nputs "copied list #{taken.size}, source #{source.size}"\n\n# ---------------------------------------------------------------------------\n# Everything below here is the trait's, from one `each`\n# ---------------------------------------------------------------------------\nputs "to_a #{nums.to_a.join(",")}"\nputs "all?/any? #{nums.all? { |x| x > 0 }} #{nums.any? { |x| x > 4 }}"\nputs "none?/one? #{nums.none? { |x| x > 9 }} #{nums.one? { |x| x == 4 }}"\nputs "count #{nums.count { |x| x == 1 }}"\nputs "find #{nums.find { |x| x > 3 }} #{nums.find { |x| x > 9 }.inspect}"\nputs "index #{nums.index { |x| x == 4 }} #{nums.index { |x| x == 9 }.inspect}"\nputs "first?/first #{nums.first?} #{nums.first}"\nputs "map #{nums.map { |x| x * 2 }.join(",")}"\nputs "flat_map #{nums.flat_map { |x| [x, x] }.join(",")}"\nputs "map_with_index #{nums.map_with_index { |x, i| x * i }.join(",")}"\nputs "select/reject #{nums.select { |x| x > 2 }.join(",")} #{nums.reject { |x| x > 2 }.join(",")}"\nputs "partition #{nums.partition { |x| x > 2 }.map(&.join("+")).join(" | ")}"\nputs "take/skip #{nums.take(2).join(",")} #{nums.skip(3).join(",")}"\nputs "take_while #{nums.take_while { |x| x < 4 }.join(",")}"\nputs "skip_while #{nums.skip_while { |x| x < 4 }.join(",")}"\nputs "reduce #{nums.reduce(0) { |a, x| a + x }}"\nputs "each_with_object #{nums.each_with_object([] of Int32) { |o, e| o << e * 10 }.join(",")}"\nputs "join #{nums.join("-")}"\nputs "includes? #{nums.includes?(4)} #{nums.includes?(9)}"\nputs "max/min #{nums.max} #{nums.min}"\nputs "sum/product #{nums.sum} #{nums.product}"\nputs "tally #{nums.tally[1]}"\nputs "sorted #{nums.sorted.join(",")}"\nputs "max_by #{words.max_by { |s| s.size }}"\nputs "group_by #{words.group_by { |s| s.size }[4].join(",")}"\n\n# The trait reaches a generic type, which is the part that was not built until\n# this file needed it. `type Elem = T` binds the associated type to the impl's\n# own parameter, so `List(Int32)` enumerates `Int32` and `List(String)`\n# enumerates `String` from the same impl — II.6 × II.7, which had never met.\nputs "two elem types #{nums.map { |x| x + 1 }.join(",")} / #{words.map { |s| s.size }.join(",")}"\n\n# ---------------------------------------------------------------------------\n# All of the above, with `src/std` deleted\n# ---------------------------------------------------------------------------\n#\n# ./bin/crystal build --no-codegen --emit-iyimod /tmp/mods samples/iyi/immutable.iyi\n# mv src/std /tmp/std-elsewhere\n# ./bin/crystal build --use-iyimod /tmp/mods samples/iyi/immutable.iyi # compiles\n# mv /tmp/std-elsewhere src/std\n#\n# This file is the hard case for R-1's contract, not `modules.iyi`: what has to\n# survive the round trip is a generic struct, a 575-line trait with an\n# associated type, and a generic impl that answers it. `mod dump --declarations\n# /tmp/mods/std/list.iyimod` is what the second build reads — the twelve\n# headers with no bodies, and `impl … for Std::List::List(T) forall T` with its\n# `type Elem = T`. Every call above is checked against those and against\n# nothing else (SPEC.md IV.1f).\n#\n# Not codegen: `--use-iyimod` implies `--no-codegen`, so this compiles the file\n# without producing a program. `./bin/crystal run` above is still an ordinary\n# build that reads the sources.\n", "samples/iyi/init_order.iyi": "# Module initialisation order: SPEC.md III.5.\n#\n# ./bin/crystal run samples/iyi/init_order.iyi\n#\n# Prints its five lines in the order they are numbered, and the numbering is\n# the whole point — none of it comes from where the `import` directives are\n# written. A module's top-level expressions are its initialiser (rule 3), and\n# a module initialises after every module it imports (rule 1).\n\nmodule samples/init_order\n\n# Written above the imports on purpose, and it still runs after both modules\n# below have initialised. The compiler holds a module's initialiser apart from\n# the site that imported it and puts them in the order the import DAG forces:\n# `boot/config` before `boot/registry` because the second imports the first,\n# and this file last because it imports both.\nputs "4. samples/init_order, above its own `import` lines"\n\nimport boot/registry\nimport boot/config\n\nputs "5. #{Boot::Registry.greeting}"\n\n# ---------------------------------------------------------------------------\n# What this sample does not promise\n# ---------------------------------------------------------------------------\n#\n# * The order of two modules that do not import each other. Rule 2 says that\n# order is *unobservable*, not merely unspecified — a module can only name\n# what it imports (R-1), reach what that module exports (R-2), and reopen\n# nothing (R-3), so an initialiser has nothing of an unrelated module to\n# look at. Nothing here prints from two independent modules, because that\n# would be documenting an order the spec refuses to have — and debug builds\n# shuffle it, so the documentation would be wrong most of the time. Build\n# with `--release` to get the load order back, or `IYI_INIT_SEED=1` to get\n# the same shuffled one twice.\n#\n# * Anything about the second `import` above. `boot/config` was already loaded\n# by `boot/registry`, so naming it here adds an edge and nothing else: a\n# module imported by several others is compiled once and initialised once.\n#\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * A top-level expression that propagates an error with `!`. Initialisation\n# may not fail (rule 4): if it can fail it is not initialisation, it is work,\n# and work belongs in a function the program calls when it is ready to handle\n# the failure. The error names III.5.\n#\n# * An import cycle. `import` forms a DAG (R-1), and a cycle has no order that\n# satisfies rule 1 — each module would have to initialise after the other.\n# The error names the cycle. Adding `import boot/registry` to `boot/config`\n# reports `boot/registry -> boot/config -> boot/registry`, the chain as it\n# was walked, not as it was written.\n#\n# * An `import` inside a `def` — `can't import inside def`, reported by the\n# parser. Inside any other expression the semantic pass reports `can't import\n# dynamically` instead. Rule 1 needs the import graph to be a DAG the\n# compiler can see before it runs anything.\n#\n# ---------------------------------------------------------------------------\n# Not yet implemented\n# ---------------------------------------------------------------------------\n#\n# * A module's initialiser as something an artifact could store. The compiler\n# holds the modules' *nodes* apart from their import sites, which is what\n# rules 1 and 2 needed, but `.iyimod` needs the step after: a callable\n# initialiser in the module's own object code (SPEC.md Part IV).\n", - "samples/iyi/inventory.iyi": "# The first program with a struct in it - what a person writes in the second\n# half hour, after `basics`.\n#\n# ./bin/iyi run samples/iyi/inventory.iyi\n#\n# A record type, a list of them, and the questions a list gets asked: which\n# ones, how many, the largest, each with its number, and the same list by\n# name. Like `basics`, this documents no rule. It is here because the prelude\n# grows only when a program in this repository needs something (SPEC.md 0.1.0\n# item 3), and these are the calls the second program makes that the first\n# did not.\nmodule samples/inventory\n\nstruct Item\n property name : String\n property price : Float64\n property quantity : Int32\n\n def initialize(@name : String, @price : Float64, @quantity : Int32)\n end\n\n def total : Float64\n price * quantity\n end\nend\n\nitems = [\n Item.new("apple", 0.5, 10),\n Item.new("pear", 0.75, 4),\n Item.new("melon", 3.0, 1),\n]\n\n# 1. A total, and a subset.\ngrand = 0.0\nitems.each { |item| grand += item.total }\nputs "total: #{grand}" # => total: 11.0\ncheap = items.select { |item| item.price < 1.0 }\nputs "cheap: #{cheap.map(&.name).join(", ")}" # => cheap: apple, pear\n\n# 2. Questions with one answer.\nputs items.count { |item| item.quantity > 1 } # => 2\nputs items.any? { |item| item.price > 2.0 } # => true\nputs items.all? { |item| item.quantity > 0 } # => true\nputs items.max_by { |item| item.price }.name # => melon\nputs items.min_by { |item| item.total }.name # => pear, the first of equals\nif pear = items.find? { |item| item.name == "pear" }\n puts pear.total # => 3.0\nend\nputs items.find? { |item| item.name == "plum" }.inspect # => nil\n\n# 3. A report, each line numbered.\nitems.each_with_index do |item, index|\n puts "#{index + 1}. #{item.name.ljust(6)} #{item.quantity.to_s.rjust(3)} #{item.total}"\nend\n# => 1. apple 10 5.0\n# => 2. pear 4 3.0\n# => 3. melon 1 3.0\n\n# 4. The same items by name.\nby_name = {} of String => Item\nitems.each { |item| by_name[item.name] = item }\nputs by_name.keys # => ["apple", "pear", "melon"]\nputs by_name.values.map(&.quantity).sum # => 15\nputs by_name.keys.sorted.last # => pear\n", + "samples/iyi/inventory.iyi": "# The first program with a struct in it - what a person writes in the second\n# half hour, after `basics`.\n#\n# ./bin/iyi run samples/iyi/inventory.iyi\n#\n# A record type, a list of them, and the questions a list gets asked: which\n# ones, how many, the largest, each with its number, and the same list by\n# name. Like `basics`, this documents no rule. It is here because the prelude\n# grows only when a program in this repository needs something (SPEC.md 0.1.0\n# item 3), and these are the calls the second program makes that the first\n# did not.\nmodule samples/inventory\n\nstruct Item\n property name : String\n property price : Float64\n property quantity : Int32\n\n def initialize(@name : String, @price : Float64, @quantity : Int32)\n end\n\n def total : Float64\n price * quantity\n end\nend\n\nitems = [\n Item.new("apple", 0.5, 10),\n Item.new("pear", 0.75, 4),\n Item.new("melon", 3.0, 1),\n]\n\n# 1. A total, and a subset.\ngrand = 0.0\nitems.each { |item| grand += item.total }\nputs "total: #{grand}" # => total: 11.0\ncheap = items.select { |item| item.price < 1.0 }\nputs "cheap: #{cheap.map(&.name).join(", ")}" # => cheap: apple, pear\n\n# 2. Questions with one answer.\nputs items.count { |item| item.quantity > 1 } # => 2\nputs items.any? { |item| item.price > 2.0 } # => true\nputs items.all? { |item| item.quantity > 0 } # => true\nputs items.max_by { |item| item.price }.name # => melon\nputs items.min_by { |item| item.total }.name # => pear, the first of equals\nif pear = items.find { |item| item.name == "pear" }\n puts pear.total # => 3.0\nend\nputs items.find { |item| item.name == "plum" }.inspect # => nil\n\n# 3. A report, each line numbered.\nitems.each_with_index do |item, index|\n puts "#{index + 1}. #{item.name.ljust(6)} #{item.quantity.to_s.rjust(3)} #{item.total}"\nend\n# => 1. apple 10 5.0\n# => 2. pear 4 3.0\n# => 3. melon 1 3.0\n\n# 4. The same items by name.\nby_name = {} of String => Item\nitems.each { |item| by_name[item.name] = item }\nputs by_name.keys # => ["apple", "pear", "melon"]\nputs by_name.values.map(&.quantity).sum # => 15\nputs by_name.keys.sorted.last # => pear\n", "samples/iyi/io.iyi": "# IO: line-at-a-time reading of standard input.\n#\n# printf "first line\\nsecond line\\nthird line\\n" | ./bin/iyi run samples/iyi/io.iyi\n#\n# Reads standard input line by line through stdin.read_line, demonstrating\n# buffered stream reading.\n\nmodule samples/io\n\nline_number = 0\nwhile line = stdin.read_line(chomp: true)\n line_number = line_number + 1\n puts "#{line_number}: #{line}"\nend\n\nif line_number == 0\n puts "no lines read from standard input"\nelse\n puts "read #{line_number} line(s) from standard input"\nend\n", "samples/iyi/kemal/dsl.iyi": "# Kemal's DSL, ported from `kemal/src/kemal/dsl.cr`.\n#\n# The original's first line is the whole problem: "Kemal DSL is defined here\n# and it's baked into global scope." `require "kemal"` injects `get`, `post`\n# and `before_all` into the importing program's namespace — the same action at\n# a distance as an open class, and also Kemal's entire identity. Making users\n# write `kemal.get "/" do ... end` would be safe and would kill the framework.\n#\n# `using` moves the decision to the consumer: the library exports names, and\n# the program says which ones it wants unqualified. See `webapp.iyi`.\nmodule kemal/dsl\n\nimport kemal/router\n\nusing kemal/router::{Router, Context, IntoBody}\n\nHTTP_METHODS = %w(get post put patch delete options head)\n\n# One application value, in place of `Kemal::RouteHandler::INSTANCE` and the\n# three singletons beside it. `router.cr:270` works around those being global\n# and mutable — "may have been cleared between tests" — and nothing about\n# separate compilation forced that; a clean sheet is just the moment to fix it.\nAPP = Router.new\n\n{% for method in HTTP_METHODS %}\n pub def {{ method.id }}(path : String, &block : Context -> B) : Nil forall B : IntoBody\n APP.{{ method.id }}(path, &block)\n end\n{% end %}\n\npub def before_all(path : String = "*", &block : Context -> B) : Nil forall B : IntoBody\n APP.before(path, &block)\nend\n\npub def after_all(path : String = "*", &block : Context -> B) : Nil forall B : IntoBody\n APP.after(path, &block)\nend\n\npub def mount(path : String, router : Router) : Nil\n APP.mount(path, router)\nend\n\npub def app : Router\n APP\nend\n", "samples/iyi/kemal/router.iyi": "# Kemal's modular router, ported to iyi from `kemal/src/kemal/router.cr` (337\n# lines). This is the acceptance test the design named: the hardest DSL in the\n# Crystal ecosystem for "no open classes, no global macros".\n#\n# What is deliberately not ported, and why:\n#\n# * `HTTP::Server::Context`, WebSocket and SSE — they need the stdlib, and\n# they add no construct the routes do not already exercise. `Context` here\n# is a stub.\n# * `Kemal::RouteHandler::INSTANCE` and the other global mutable singletons.\n# `router.cr:270` carries a workaround for them — "may have been cleared\n# between tests" — and a clean sheet is the moment to stop. Registration\n# returns a value instead.\nmodule kemal/router\n\npub struct Context\n getter method : String\n getter path : String\n\n def initialize(@method : String, @path : String)\n end\nend\n\n# The finding the port produced. Kemal types a route block as\n# `HTTP::Server::Context -> _` and coerces the result on every request:\n#\n# result = block.call(ctx)\n# result.is_a?(String) ? result : "" # router.cr:301\n#\n# Mandatory export signatures forbid `_`, so the block's return type has to be\n# named — and once it is named, the coercion becomes a bound. The check moves\n# from every request to compile time, and a user can now make their own type\n# returnable by implementing the trait, which Kemal cannot offer.\npub trait IntoBody\n abstract def into_body : String\nend\n\nimpl IntoBody for String\n def into_body : String\n self\n end\nend\n\nimpl IntoBody for Nil\n def into_body : String\n ""\n end\nend\n\nimpl IntoBody for Int32\n def into_body : String\n to_s\n end\nend\n\nHTTP_METHODS = %w(get post put patch delete options head)\nFILTER_METHODS = %w(get post put patch delete options)\n\npub class Router\n alias Handler = Context -> String\n\n private record RouteDefinition,\n method : String,\n path : String,\n handler : Handler\n\n private record FilterDefinition,\n kind : Symbol,\n method : String,\n path : String,\n handler : Handler\n\n private record SubRouter,\n path : String,\n router : Router\n\n getter prefix : String\n\n @routes : Array(RouteDefinition)\n @filters : Array(FilterDefinition)\n @sub_routers : Array(SubRouter)\n\n def initialize(@prefix : String = "")\n @routes = [] of RouteDefinition\n @filters = [] of FilterDefinition\n @sub_routers = [] of SubRouter\n end\n\n # The macro loop ports unchanged — it reads a module-local constant and\n # emits declarations into its own module, which is legal under R-5. The one\n # addition is `forall B : IntoBody` on each generated signature.\n {% for method in HTTP_METHODS %}\n pub def {{ method.id }}(path : String, &block : Context -> B) : Nil forall B : IntoBody\n add_route({{ method.upcase }}, path, &block)\n end\n {% end %}\n\n pub def before(path : String = "*", &block : Context -> B) : Nil forall B : IntoBody\n add_filter(:before, "ALL", path, &block)\n end\n\n pub def after(path : String = "*", &block : Context -> B) : Nil forall B : IntoBody\n add_filter(:after, "ALL", path, &block)\n end\n\n {% for method in FILTER_METHODS %}\n pub def before_{{ method.id }}(path : String = "*", &block : Context -> B) : Nil forall B : IntoBody\n add_filter(:before, {{ method.upcase }}, path, &block)\n end\n\n pub def after_{{ method.id }}(path : String = "*", &block : Context -> B) : Nil forall B : IntoBody\n add_filter(:after, {{ method.upcase }}, path, &block)\n end\n {% end %}\n\n # The sub-router arrives as a block parameter, and Kemal's original hands it\n # to the block as `self` instead — `with sub_router yield`. That is the one\n # thing about this port that reads worse than what it ports, so the reason is\n # here rather than in a commit message.\n #\n # R-2 asks an exported `def` to say what its block is (SPEC.md IV.2): the\n # body stays in this module, so a consumer reading only the signature has no\n # `yield` left to infer from. `& : Router -> Nil` says it. `with sub_router\n # yield` cannot be said at all — what makes it a DSL is that the block's\n # `self` changes, and `& : … -> …` has nowhere to write that down.\n #\n # So this is the rule costing something visible, and it is being paid rather\n # than avoided: a `namespace` that keeps `with` would have to stay unexported,\n # which is a worse trade for a DSL than one extra `|r|`. Giving `with … yield`\n # a notation is open (SPEC.md IV.2); until it has one, this is what an\n # exported block-taking method looks like.\n pub def namespace(path : String, & : Router -> Nil) : Nil\n sub_router = Router.new\n yield sub_router\n @sub_routers << SubRouter.new(path: path, router: sub_router)\n end\n\n pub def mount(path : String, router : Router) : Nil\n @sub_routers << SubRouter.new(path: path, router: router)\n end\n\n # Kemal's `register_routes` pushed into global singletons. This returns the\n # flattened table instead, which is the same walk without the globals.\n pub def routes(base_prefix : String = "") : Array(Tuple(String, String))\n full_prefix = join_paths(base_prefix, @prefix)\n collected = [] of Tuple(String, String)\n\n @routes.each do |route|\n collected << {route.method, join_paths(full_prefix, route.path)}\n end\n\n @sub_routers.each do |sub|\n collected.concat(sub.router.routes(join_paths(full_prefix, sub.path)))\n end\n\n collected\n end\n\n # Ported from `register_filters`, which is the densest code in the file:\n # `case` on a symbol, `select` destructuring a tuple, `in?` over a splat,\n # and `starts_with?`.\n pub def filters_for(method : String, path : String) : Array(Symbol)\n applicable = [] of Symbol\n\n @filters.each do |filter|\n next unless filter.method.in?("ALL", method)\n\n matches = if filter.path == "*"\n true\n else\n scoped = join_paths(@prefix, filter.path)\n path == scoped || path.starts_with?("#{scoped}/")\n end\n\n next unless matches\n\n case filter.kind\n when :before then applicable << :before\n when :after then applicable << :after\n end\n end\n\n applicable\n end\n\n pub def dispatch(ctx : Context) : String\n before = 0\n after = 0\n filters_for(ctx.method, ctx.path).each do |kind|\n case kind\n when :before then before += 1\n when :after then after += 1\n end\n end\n\n body = ""\n @routes.each do |route|\n if route.method == ctx.method && join_paths(@prefix, route.path) == ctx.path\n body = route.handler.call(ctx)\n end\n end\n\n @sub_routers.each do |sub|\n body = sub.router.dispatch(ctx) if body.empty?\n end\n\n "#{before}|#{body}|#{after}"\n end\n\n private def add_route(method : String, path : String, &block : Context -> B) : Nil forall B : IntoBody\n handler = ->(ctx : Context) { block.call(ctx).into_body }\n @routes << RouteDefinition.new(method: method, path: path, handler: handler)\n end\n\n private def add_filter(kind : Symbol, method : String, path : String, &block : Context -> B) : Nil forall B : IntoBody\n handler = ->(ctx : Context) { block.call(ctx).into_body }\n @filters << FilterDefinition.new(kind: kind, method: method, path: path, handler: handler)\n end\n\n private def join_paths(a : String, b : String) : String\n a = a.chomp('/')\n b = b.lchop('/') if b.starts_with?('/')\n return "/#{b}" if a.empty?\n return a if b.empty?\n "#{a}/#{b}"\n end\nend\n", diff --git a/records/wasm/arrays.wasm b/records/wasm/arrays.wasm index b0396db..be13c41 100755 Binary files a/records/wasm/arrays.wasm and b/records/wasm/arrays.wasm differ diff --git a/records/wasm/blocks.wasm b/records/wasm/blocks.wasm index 7b4a885..06d2b46 100755 Binary files a/records/wasm/blocks.wasm and b/records/wasm/blocks.wasm differ diff --git a/records/wasm/bounds.wasm b/records/wasm/bounds.wasm index cdefa21..830f6e3 100755 Binary files a/records/wasm/bounds.wasm and b/records/wasm/bounds.wasm differ diff --git a/records/wasm/case.wasm b/records/wasm/case.wasm index 12423c1..47da19a 100755 Binary files a/records/wasm/case.wasm and b/records/wasm/case.wasm differ diff --git a/records/wasm/classes.wasm b/records/wasm/classes.wasm index aa76a53..681a252 100755 Binary files a/records/wasm/classes.wasm and b/records/wasm/classes.wasm differ diff --git a/records/wasm/conditions.wasm b/records/wasm/conditions.wasm index dce865a..fe2e3e1 100755 Binary files a/records/wasm/conditions.wasm and b/records/wasm/conditions.wasm differ diff --git a/records/wasm/constants.wasm b/records/wasm/constants.wasm index 5ae2613..e89ee63 100755 Binary files a/records/wasm/constants.wasm and b/records/wasm/constants.wasm differ diff --git a/records/wasm/conversions.wasm b/records/wasm/conversions.wasm index b575974..694bde7 100755 Binary files a/records/wasm/conversions.wasm and b/records/wasm/conversions.wasm differ diff --git a/records/wasm/defaults.wasm b/records/wasm/defaults.wasm index 8f6dfe2..8e51447 100755 Binary files a/records/wasm/defaults.wasm and b/records/wasm/defaults.wasm differ diff --git a/records/wasm/defer.wasm b/records/wasm/defer.wasm index dbb0ec1..73bf255 100755 Binary files a/records/wasm/defer.wasm and b/records/wasm/defer.wasm differ diff --git a/records/wasm/dispatch.wasm b/records/wasm/dispatch.wasm index 8367a35..ca05f84 100755 Binary files a/records/wasm/dispatch.wasm and b/records/wasm/dispatch.wasm differ diff --git a/records/wasm/errors.wasm b/records/wasm/errors.wasm index 36b3143..3afbbed 100755 Binary files a/records/wasm/errors.wasm and b/records/wasm/errors.wasm differ diff --git a/records/wasm/exports.wasm b/records/wasm/exports.wasm index 3c5c061..d5618f4 100755 Binary files a/records/wasm/exports.wasm and b/records/wasm/exports.wasm differ diff --git a/records/wasm/functions.wasm b/records/wasm/functions.wasm index 425cdba..19f0e3a 100755 Binary files a/records/wasm/functions.wasm and b/records/wasm/functions.wasm differ diff --git a/records/wasm/generic-functions.wasm b/records/wasm/generic-functions.wasm index 0e1c9b5..1003a2a 100755 Binary files a/records/wasm/generic-functions.wasm and b/records/wasm/generic-functions.wasm differ diff --git a/records/wasm/generic-types.wasm b/records/wasm/generic-types.wasm index fb7649b..4fd5113 100755 Binary files a/records/wasm/generic-types.wasm and b/records/wasm/generic-types.wasm differ diff --git a/records/wasm/hashes.wasm b/records/wasm/hashes.wasm index 8abdfd8..04633b6 100755 Binary files a/records/wasm/hashes.wasm and b/records/wasm/hashes.wasm differ diff --git a/records/wasm/hello.wasm b/records/wasm/hello.wasm index aded9f7..41d912f 100755 Binary files a/records/wasm/hello.wasm and b/records/wasm/hello.wasm differ diff --git a/records/wasm/iyi-basics.wasm b/records/wasm/iyi-basics.wasm index eeb2681..56a06d6 100755 Binary files a/records/wasm/iyi-basics.wasm and b/records/wasm/iyi-basics.wasm differ diff --git a/records/wasm/iyi-calc.wasm b/records/wasm/iyi-calc.wasm index 16af454..a356a4d 100755 Binary files a/records/wasm/iyi-calc.wasm and b/records/wasm/iyi-calc.wasm differ diff --git a/records/wasm/iyi-collections.wasm b/records/wasm/iyi-collections.wasm index 6cabd0f..b33dc6b 100755 Binary files a/records/wasm/iyi-collections.wasm and b/records/wasm/iyi-collections.wasm differ diff --git a/records/wasm/iyi-config.wasm b/records/wasm/iyi-config.wasm index 0555970..f4b83ff 100755 Binary files a/records/wasm/iyi-config.wasm and b/records/wasm/iyi-config.wasm differ diff --git a/records/wasm/iyi-derive.wasm b/records/wasm/iyi-derive.wasm index f21f479..684f050 100755 Binary files a/records/wasm/iyi-derive.wasm and b/records/wasm/iyi-derive.wasm differ diff --git a/records/wasm/iyi-enums.wasm b/records/wasm/iyi-enums.wasm new file mode 100755 index 0000000..e2ef094 Binary files /dev/null and b/records/wasm/iyi-enums.wasm differ diff --git a/records/wasm/iyi-errors.wasm b/records/wasm/iyi-errors.wasm index fec9900..c832fbb 100755 Binary files a/records/wasm/iyi-errors.wasm and b/records/wasm/iyi-errors.wasm differ diff --git a/records/wasm/iyi-files.wasm b/records/wasm/iyi-files.wasm index 74eca3d..c6010db 100755 Binary files a/records/wasm/iyi-files.wasm and b/records/wasm/iyi-files.wasm differ diff --git a/records/wasm/iyi-format.wasm b/records/wasm/iyi-format.wasm index 8406e5a..b4a9680 100755 Binary files a/records/wasm/iyi-format.wasm and b/records/wasm/iyi-format.wasm differ diff --git a/records/wasm/iyi-formatting.wasm b/records/wasm/iyi-formatting.wasm index e959846..a165257 100755 Binary files a/records/wasm/iyi-formatting.wasm and b/records/wasm/iyi-formatting.wasm differ diff --git a/records/wasm/iyi-generics.wasm b/records/wasm/iyi-generics.wasm index ac2e33d..95ab165 100755 Binary files a/records/wasm/iyi-generics.wasm and b/records/wasm/iyi-generics.wasm differ diff --git a/records/wasm/iyi-grid.wasm b/records/wasm/iyi-grid.wasm index 5503806..b0c7b4e 100755 Binary files a/records/wasm/iyi-grid.wasm and b/records/wasm/iyi-grid.wasm differ diff --git a/records/wasm/iyi-hello.wasm b/records/wasm/iyi-hello.wasm index ca0a4f8..f730c15 100755 Binary files a/records/wasm/iyi-hello.wasm and b/records/wasm/iyi-hello.wasm differ diff --git a/records/wasm/iyi-immutable.wasm b/records/wasm/iyi-immutable.wasm index 6248f80..b575a85 100755 Binary files a/records/wasm/iyi-immutable.wasm and b/records/wasm/iyi-immutable.wasm differ diff --git a/records/wasm/iyi-init_order.wasm b/records/wasm/iyi-init_order.wasm index cf92e15..69dcf08 100755 Binary files a/records/wasm/iyi-init_order.wasm and b/records/wasm/iyi-init_order.wasm differ diff --git a/records/wasm/iyi-inventory.wasm b/records/wasm/iyi-inventory.wasm index a1477ca..b73b4c3 100755 Binary files a/records/wasm/iyi-inventory.wasm and b/records/wasm/iyi-inventory.wasm differ diff --git a/records/wasm/iyi-io.wasm b/records/wasm/iyi-io.wasm index a2f2992..77979ec 100755 Binary files a/records/wasm/iyi-io.wasm and b/records/wasm/iyi-io.wasm differ diff --git a/records/wasm/iyi-modules.wasm b/records/wasm/iyi-modules.wasm index 45ea71a..400dbef 100755 Binary files a/records/wasm/iyi-modules.wasm and b/records/wasm/iyi-modules.wasm differ diff --git a/records/wasm/iyi-sessions.wasm b/records/wasm/iyi-sessions.wasm index 0679743..9113630 100755 Binary files a/records/wasm/iyi-sessions.wasm and b/records/wasm/iyi-sessions.wasm differ diff --git a/records/wasm/iyi-shapes.wasm b/records/wasm/iyi-shapes.wasm index e9e8566..4ab17b1 100755 Binary files a/records/wasm/iyi-shapes.wasm and b/records/wasm/iyi-shapes.wasm differ diff --git a/records/wasm/iyi-socket.wasm b/records/wasm/iyi-socket.wasm index 09359f2..30a1fff 100755 Binary files a/records/wasm/iyi-socket.wasm and b/records/wasm/iyi-socket.wasm differ diff --git a/records/wasm/iyi-std_iterator.wasm b/records/wasm/iyi-std_iterator.wasm index fddfc45..68faa2f 100755 Binary files a/records/wasm/iyi-std_iterator.wasm and b/records/wasm/iyi-std_iterator.wasm differ diff --git a/records/wasm/iyi-std_text.wasm b/records/wasm/iyi-std_text.wasm index 48a95a0..3a16516 100755 Binary files a/records/wasm/iyi-std_text.wasm and b/records/wasm/iyi-std_text.wasm differ diff --git a/records/wasm/iyi-std_time.wasm b/records/wasm/iyi-std_time.wasm index 5ed9a3b..b2d6418 100755 Binary files a/records/wasm/iyi-std_time.wasm and b/records/wasm/iyi-std_time.wasm differ diff --git a/records/wasm/iyi-visited.wasm b/records/wasm/iyi-visited.wasm index 16890cf..df54170 100755 Binary files a/records/wasm/iyi-visited.wasm and b/records/wasm/iyi-visited.wasm differ diff --git a/records/wasm/iyi-webapp.wasm b/records/wasm/iyi-webapp.wasm index 33ac760..e9b1cbb 100755 Binary files a/records/wasm/iyi-webapp.wasm and b/records/wasm/iyi-webapp.wasm differ diff --git a/records/wasm/loops.wasm b/records/wasm/loops.wasm index e55a155..42c2761 100755 Binary files a/records/wasm/loops.wasm and b/records/wasm/loops.wasm differ diff --git a/records/wasm/manifest.json b/records/wasm/manifest.json index 530e2f1..dfd3d7b 100644 --- a/records/wasm/manifest.json +++ b/records/wasm/manifest.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.12.0 (built on Crystal 1.22.0-dev [44b843563] (2026-09-11))", - "commit": "44b8435633d47d2d3ba2106abe86e0ea1188257f", + "compiler": "iyi 0.13.0 (built on Crystal 1.22.0-dev [1f79e251a] (2026-09-16))", + "commit": "1f79e251ae76b1ce03c398dc99675529f1b31825", "machine": "Linux 7.2.4-1-cachyos x86_64, AMD Ryzen AI 9 465 w/ Radeon 880M", "command": "npm run record:wasm", - "when": "2026-09-12T16:05:57.307Z" + "when": "2026-09-20T11:17:09.977Z" }, "samples": [ { @@ -13,8 +13,8 @@ "path": "samples/tour/hello.iyi", "sourceSha256": "676f8372f2cd5f8941b1dcd0ccb167ee6f6f27c1de4a77cef3af0372c323134d", "wasm": "hello.wasm", - "bytes": 114038, - "sha256": "278fd6c25a83eacf6936bda4c3797fcbfad2c2b17120e47eb0467dd359911641", + "bytes": 114651, + "sha256": "2b60d4f128280f1961d6d8e051cc60ebac905c7cea6e24a303033943ff7a61af", "exitCode": 0, "nativeStdout": "Hello Türkiye!\n", "wasmStdout": "Hello Türkiye!\n", @@ -27,8 +27,8 @@ "path": "samples/tour/modules.iyi", "sourceSha256": "c908a80d8c5a2c3f42bc6c90cd9d9a822a4c0b077cbd4713cc627f2d9f4912c0", "wasm": "modules.wasm", - "bytes": 115665, - "sha256": "fcec0cfb1f81bc33bf0e94fa0490055b0f091ad7e740497a5941f216bc9e4e95", + "bytes": 116174, + "sha256": "e87f056eb9a7d18f9d0f257aae11707d3d034cba31a3823568afa645c8b25749", "exitCode": 0, "nativeStdout": "7\n", "wasmStdout": "7\n", @@ -41,8 +41,8 @@ "path": "samples/tour/exports.iyi", "sourceSha256": "dc1b07111b6eeff52bf8d5ee435a2b8bafa6fef3955fc0a9fb0e4d5ae156f1e1", "wasm": "exports.wasm", - "bytes": 115661, - "sha256": "cc46d298aaa7d8d63e348fdd4cd88874701679ffd14138ec00394464145874d0", + "bytes": 116170, + "sha256": "038aeb2c5559cfeb13310dd433bd7ed197335b11bc011c42b25de96e8f626b02", "exitCode": 0, "nativeStdout": "7\n", "wasmStdout": "7\n", @@ -55,8 +55,8 @@ "path": "samples/tour/variables.iyi", "sourceSha256": "672aec63ec81d0a8dc9cc773688d853a6b9a246618705a4cfa782191efe3fea5", "wasm": "variables.wasm", - "bytes": 133211, - "sha256": "599ebf447cadde68b203c20c19e25c1595a0e41cdee1b6065211854ea07b4679", + "bytes": 134142, + "sha256": "1c41d9741b25440a93eec088386e0103ff8bf3b0140f2206b164a25dddeab9d5", "exitCode": 0, "nativeStdout": "iyi\n2026\n0.75\ntrue\n2027\n10\n", "wasmStdout": "iyi\n2026\n0.75\ntrue\n2027\n10\n", @@ -69,8 +69,8 @@ "path": "samples/tour/types.iyi", "sourceSha256": "bbd35199bdfae3aa05673d869e512e1713213fa1aeef929e6477fec7ff6e4464", "wasm": "types.wasm", - "bytes": 134946, - "sha256": "7b7b3f44d29c3452c46073a593b224308f0b3571d3f13c35761644b74dea9d4f", + "bytes": 135877, + "sha256": "11dd59f04ec3cb1195909abfb6d4b4c767a04b60ba3621bbdc606be97ab88ba6", "exitCode": 0, "nativeStdout": "Int32\nInt64\nFloat64\nBool\nChar\nString\n3\n3.5\n1\n1024\n", "wasmStdout": "Int32\nInt64\nFloat64\nBool\nChar\nString\n3\n3.5\n1\n1024\n", @@ -83,8 +83,8 @@ "path": "samples/tour/conversions.iyi", "sourceSha256": "1b8cf5e80ec2dbe3ce9b207d12491e7b24761caf1eb2797a098f68f36e933b81", "wasm": "conversions.wasm", - "bytes": 138944, - "sha256": "bc8330ee03491208d67381bd008f4bc0fb1635914a555963129b82f518ec7c98", + "bytes": 142875, + "sha256": "8cbcf64b5138d20a40a8d3c22ea5ffe45f258ec2967dafb850216b5aeee4fe18", "exitCode": 0, "nativeStdout": "42.0\n42!\n9\n10.0\n18\n17\n\n65\nB\n", "wasmStdout": "42.0\n42!\n9\n10.0\n18\n17\n\n65\nB\n", @@ -97,8 +97,8 @@ "path": "samples/tour/constants.iyi", "sourceSha256": "e5e15868dc555999be7b1c15e2349147be0e335a5b45792b893aa9c9d3e139ba", "wasm": "constants.wasm", - "bytes": 134755, - "sha256": "a77c7de17d14038d7f630c35eae33b022e3aee43469ea584b5aed0b845e6082d", + "bytes": 135686, + "sha256": "9a63193e0c8ac33212f64ac39ebb59f16bd2cb55faf76d86cf4650fde0be4966", "exitCode": 0, "nativeStdout": "12.57\nMerhaba, dünya\ntrue\n", "wasmStdout": "12.57\nMerhaba, dünya\ntrue\n", @@ -111,8 +111,8 @@ "path": "samples/tour/functions.iyi", "sourceSha256": "1261f0c7c47c4f9469358362a6c52a84cd6450a6e4347e1d7ff9eba09a88f89a", "wasm": "functions.wasm", - "bytes": 115641, - "sha256": "d9b9acae9755eb09a23c4ce83627c0f7868e6a075abeecc137ae8da1975e5604", + "bytes": 116270, + "sha256": "5becd97eaf05707b63aa56a2456cc76330044d61a47e663470bf1359dd4c038f", "exitCode": 0, "nativeStdout": "5\nnegative\nHello, Ayşe!\nSelam, Ayşe!\n", "wasmStdout": "5\nnegative\nHello, Ayşe!\nSelam, Ayşe!\n", @@ -125,8 +125,8 @@ "path": "samples/tour/returns.iyi", "sourceSha256": "6b655fcd036d722550a028424fe4719cdfb89383fed7607e260c46fbf0fff932", "wasm": "returns.wasm", - "bytes": 117136, - "sha256": "7b8921cb85a056832df4b76971ea85b57a584627972ba5367720131ca8e00157", + "bytes": 118196, + "sha256": "05486e08b836a9b5fd1b64b24c914d1a15976d794b50589cf2456f92939ee096", "exitCode": 0, "nativeStdout": "3\n2\n2\n1\n6\n\n", "wasmStdout": "3\n2\n2\n1\n6\n\n", @@ -139,8 +139,8 @@ "path": "samples/tour/nil.iyi", "sourceSha256": "a559c045cfef442a9927954673d8ad5fc61f610a0908950bcdd79182853a4e04", "wasm": "nil.wasm", - "bytes": 115903, - "sha256": "afe48784f4675236693e66e766652241c95601634b4383e5060969200d38b74b", + "bytes": 116516, + "sha256": "24f968b41d3f9d71cb3fc5a25b3ee04ee38d436133cb350e0a14725094f4f76e", "exitCode": 0, "nativeStdout": "Ada\n\nADA\nnobody\n", "wasmStdout": "Ada\n\nADA\nnobody\n", @@ -153,8 +153,8 @@ "path": "samples/tour/loops.iyi", "sourceSha256": "18f56f89ce4e1bcf3f1c2abff8e3539b5bde85135ec978931583dddd7882992c", "wasm": "loops.wasm", - "bytes": 116323, - "sha256": "7711b352a506b4f3f261c7d41dca91d688abd9ef9755e1516080bd9e5e6ddbca", + "bytes": 117099, + "sha256": "d3adddaf856f0a47d9c8e5ed22568dbab28d2f1566011d8d8ea87015b4910d99", "exitCode": 0, "nativeStdout": "1\n2\n3\nround 0\nround 1\nround 2\n64\ncount 1\ncount 3\ncount 4\n", "wasmStdout": "1\n2\n3\nround 0\nround 1\nround 2\n64\ncount 1\ncount 3\ncount 4\n", @@ -167,8 +167,8 @@ "path": "samples/tour/ranges.iyi", "sourceSha256": "418036dc21744938451d1210eeceaca1931d0edb5329f3f8e801f626fa234fd6", "wasm": "ranges.wasm", - "bytes": 122889, - "sha256": "b887267b3c6ad83f914ba30660edb3d8c0698a403ddc15357a2771fc71d1b6e9", + "bytes": 123697, + "sha256": "b65a836649a3e17eec5f75b0e6937627ad7d5b8c721241f708321dcc75587d20", "exitCode": 0, "nativeStdout": "1\n2\n3\n4\n5\n1\n2\n3\n4\n[1, 4, 9, 16, 25]\n5050\n[3, 6, 9]\nadult\n", "wasmStdout": "1\n2\n3\n4\n5\n1\n2\n3\n4\n[1, 4, 9, 16, 25]\n5050\n[3, 6, 9]\nadult\n", @@ -181,8 +181,8 @@ "path": "samples/tour/conditions.iyi", "sourceSha256": "8ed0a951d7e760d2bb95c784b20c3e59771080e0578a2cadf0b1bdf41fd2eed2", "wasm": "conditions.wasm", - "bytes": 115468, - "sha256": "78ac645715199da5a388c3389ca09d42a6146081a98336f0d63c08f463ea31bc", + "bytes": 116219, + "sha256": "8e3376bd2852b0dafb3e434ae0383012b7cc4734c089638f52111ef4b46b6aaa", "exitCode": 0, "nativeStdout": "warm\nabove freezing\nnice day\nwarm\nodd\ntrue\nfalse\n", "wasmStdout": "warm\nabove freezing\nnice day\nwarm\nodd\ntrue\nfalse\n", @@ -195,8 +195,8 @@ "path": "samples/tour/case.iyi", "sourceSha256": "71de25c31bbd4ad83f406525df3f263bbb18c8dcab00eee4dba6baa166409ffb", "wasm": "case.wasm", - "bytes": 117531, - "sha256": "f2af236b9e9c7fd6369ead4f372744849600c176e116de2e96e3e10605d4f878", + "bytes": 118144, + "sha256": "9378effade60d5a2eca0947aa14ccc31a8eb5087a2637eb7184d2aa22b2fc4b0", "exitCode": 0, "nativeStdout": "weekend\nalmost\nweekday\nB\na number: 2\n", "wasmStdout": "weekend\nalmost\nweekday\nB\na number: 2\n", @@ -209,8 +209,8 @@ "path": "samples/tour/defer.iyi", "sourceSha256": "c39edacef8695b8aa0170cd169373a19ece2ec4fcf204439ef48e22aadcf06f2", "wasm": "defer.wasm", - "bytes": 117703, - "sha256": "65d8da04fa21f6d8a1630a6a45258e70a094242d498702d4c4d99a501f1d0c40", + "bytes": 118318, + "sha256": "5097e8fd96d3470761e822c2f7b33eefd2403bf6f508ae37e03719a582d57ae0", "exitCode": 0, "nativeStdout": "open\nworking\nclose\n42\nfirst\nsecond\nthird\nturn 0\ndone with 0\nturn 1\ndone with 1\n", "wasmStdout": "open\nworking\nclose\n42\nfirst\nsecond\nthird\nturn 0\ndone with 0\nturn 1\ndone with 1\n", @@ -223,8 +223,8 @@ "path": "samples/tour/structs.iyi", "sourceSha256": "2354525f27753b7a4bdef384dc4c0c6507c12fe845e9d2927e7aec0f62c2814c", "wasm": "structs.wasm", - "bytes": 115392, - "sha256": "4e39b329c8ebb5342752e5786952dbebd51e83949b2dbe54b39521abbd294847", + "bytes": 116012, + "sha256": "631b43c97e9a6af0e4d306b5af04948a8e070612315941429729a12b365ae61a", "exitCode": 0, "nativeStdout": "1\n(4, 6)\n(1, 2)\n", "wasmStdout": "1\n(4, 6)\n(1, 2)\n", @@ -237,8 +237,8 @@ "path": "samples/tour/classes.iyi", "sourceSha256": "cfc6709102991d8da3357f3f6230408b0c79e8bf7199e63f8b40a272c65a2f6a", "wasm": "classes.wasm", - "bytes": 115211, - "sha256": "d9f56678a7021ce9062b9fc3fbd3d4a87e4255f3a95cb7d0f0715aa3a426b95b", + "bytes": 115840, + "sha256": "bb171c2e98ea777836a5ccae6c9852904b8855eb698a973c8509c8515fe16bff", "exitCode": 0, "nativeStdout": "2\n0\n", "wasmStdout": "2\n0\n", @@ -251,8 +251,8 @@ "path": "samples/tour/arrays.iyi", "sourceSha256": "7f4841af80023b70c3e6f897ec7d5ba82001acd91a905e4772cc6f6f0dec0aa9", "wasm": "arrays.wasm", - "bytes": 125366, - "sha256": "9e03d2d17aca32e2afafcd2be2d01998f4d379e7714a169c89ade98dc992ec44", + "bytes": 126225, + "sha256": "f492c23cd591c4049c87b14390731ae63d7baab1236085efeb4031ed6e2107f3", "exitCode": 0, "nativeStdout": "5\n3\n3\n5\n[3, 1, 4, 1, 5, 9]\ntrue\n23\n[1, 1, 3, 4, 5, 9]\n[3, 4, 5, 9]\n[30, 10, 40, 10, 50, 90]\n23\nAyşe, Mehmet\n1. Ayşe\n2. Mehmet\n", "wasmStdout": "5\n3\n3\n5\n[3, 1, 4, 1, 5, 9]\ntrue\n23\n[1, 1, 3, 4, 5, 9]\n[3, 4, 5, 9]\n[30, 10, 40, 10, 50, 90]\n23\nAyşe, Mehmet\n1. Ayşe\n2. Mehmet\n", @@ -265,8 +265,8 @@ "path": "samples/tour/hashes.iyi", "sourceSha256": "1b5fb76d25d7f5979248800422d871da36d4c2d0b829ec9c311c7b200beb167b", "wasm": "hashes.wasm", - "bytes": 128064, - "sha256": "fdbe3b500a20923ab4d2405dc216b5490e00e356f6db530679764257b3365903", + "bytes": 128838, + "sha256": "d0ffb1e9eeb395eac083e26719cb363f18fef2fa6a94400c8de76394f8a2946c", "exitCode": 0, "nativeStdout": "31\n2\n[\"Ayşe\", \"Mehmet\", \"Zeynep\"]\n\nfalse\nAyşe is 31\nMehmet is 27\nZeynep is 40\n{\"a\" => 2, \"b\" => 1}\n", "wasmStdout": "31\n2\n[\"Ayşe\", \"Mehmet\", \"Zeynep\"]\n\nfalse\nAyşe is 31\nMehmet is 27\nZeynep is 40\n{\"a\" => 2, \"b\" => 1}\n", @@ -279,8 +279,8 @@ "path": "samples/tour/tuples.iyi", "sourceSha256": "75f2414a08f54a9d8ecc53d7b0621d8c4906a3f39d9a673dd1b8d2f219b4856b", "wasm": "tuples.wasm", - "bytes": 117578, - "sha256": "5b22603ff699a8824897a866fb7dd685450ae0899622b31987a2f8943dc0ecb2", + "bytes": 118337, + "sha256": "acb0fec229d7596612b9102baa0b89baba059ea077d350b3c647fa99c6ed01df", "exitCode": 0, "nativeStdout": "1\none\n1 is one\nAyşe\n31\n2..9\n", "wasmStdout": "1\none\n1 is one\nAyşe\n31\n2..9\n", @@ -293,8 +293,8 @@ "path": "samples/tour/blocks.iyi", "sourceSha256": "f2ae3fc23344a9f94f896942b8609956979599387e313e98d49cb4977ba9dd17", "wasm": "blocks.wasm", - "bytes": 117538, - "sha256": "d845103294a85fc5716a603053f7f898d5306dd678dde139e59899d8e3877850", + "bytes": 118296, + "sha256": "54cc79cc78f66a5f8f7b03e2a3ba8d5253f651b2c942ac73449ffe8598de15e6", "exitCode": 0, "nativeStdout": "step 0\nstep 1\nstep 2\n6\n49\n2\n105\n", "wasmStdout": "step 0\nstep 1\nstep 2\n6\n49\n2\n105\n", @@ -307,8 +307,8 @@ "path": "samples/tour/methods.iyi", "sourceSha256": "f4fb9b8be821d24f79148d110edb8f6bd36239bab164d7be45bc4d3da29eba7a", "wasm": "methods.wasm", - "bytes": 137859, - "sha256": "338f56d955f2a60e409cc2226e3620158a1a066cd0b2f833d8a046f6317d0c4d", + "bytes": 138721, + "sha256": "c9ebd13b5db4141ec24e066874561ef4cb66b822e8b5f6b75adc8ef674160639", "exitCode": 0, "nativeStdout": "12.0\nfalse\n48.0\nIYI\n4.0\n6\n", "wasmStdout": "12.0\nfalse\n48.0\nIYI\n4.0\n6\n", @@ -321,8 +321,8 @@ "path": "samples/tour/traits.iyi", "sourceSha256": "98aaeb457da3a65d50c3ce5402f3378e431010d50122396f9a353b4d958fc8a8", "wasm": "traits.wasm", - "bytes": 135097, - "sha256": "473698900de18a967b697f649ae47cfc146c70797184ad9ca788fc09b6a8060e", + "bytes": 136028, + "sha256": "273ce66eddefade707619d96bc79d51135315b2c7dbaad9c77178aa6de78e68b", "exitCode": 0, "nativeStdout": "3.14\n4.0\n", "wasmStdout": "3.14\n4.0\n", @@ -335,8 +335,8 @@ "path": "samples/tour/dispatch.iyi", "sourceSha256": "e9a5985790ff7c52c42846b394872bb18ac9040abfd59e9453d4ac71747fcfb9", "wasm": "dispatch.wasm", - "bytes": 120626, - "sha256": "bcc401de526b1a9551be35f6f2f16b85797482d4d8e64b750fba108aff2b8ffd", + "bytes": 121457, + "sha256": "312a8edcf91b62dcc2bcda0fc62a3ee85afc4fdefcc4c784f54dcb3cae652f05", "exitCode": 0, "nativeStdout": "hav\nmiyav\nhav miyav\n", "wasmStdout": "hav\nmiyav\nhav miyav\n", @@ -349,8 +349,8 @@ "path": "samples/tour/defaults.iyi", "sourceSha256": "7e438eff3837000a1551861b7107a2a4f7f6f78988a283fec3174429a1f20844", "wasm": "defaults.wasm", - "bytes": 117537, - "sha256": "64b43eccfc7b1494d8046b59319472735369dfbac55fc6d300e58ca618c5da47", + "bytes": 118166, + "sha256": "7cb7528f34be3d156aa81aabc980e70dc0d307d562facd85977c54c38dc07e3f", "exitCode": 0, "nativeStdout": "Merhaba, Ada!\nMERHABA, ADA!\nBEEP unit 7\nBEEP UNIT 7\n", "wasmStdout": "Merhaba, Ada!\nMERHABA, ADA!\nBEEP unit 7\nBEEP UNIT 7\n", @@ -363,8 +363,8 @@ "path": "samples/tour/unions.iyi", "sourceSha256": "61cb534c8687c14b7392657f859b165eb063dc892ae64429ccc59dd241441466", "wasm": "unions.wasm", - "bytes": 119526, - "sha256": "541edc0d88fed1f935298e79de203adfe3cf45b455025a348295bb0290c11463", + "bytes": 120772, + "sha256": "2194ecff2a96f6169a3159742fb549ee06c63de74e332378af90d96a49d35f9e", "exitCode": 0, "nativeStdout": "number 24\nproblem, not a number: x\nnumber 14\n6\n", "wasmStdout": "number 24\nproblem, not a number: x\nnumber 14\n6\n", @@ -377,8 +377,8 @@ "path": "samples/tour/errors.iyi", "sourceSha256": "af4c28f720136cd6c8b935273fa769fe234b55716da4abed70196e56d8b9faf7", "wasm": "errors.wasm", - "bytes": 125085, - "sha256": "8ae07bfd8949820d1490c8a24dac52527235a389817d7127fcbbf7653ab2dd4f", + "bytes": 126178, + "sha256": "ea6bbf7dd32aea3fe880ac82cc275312ed8dd6ba723ce161ca43ed058697ba5a", "exitCode": 0, "nativeStdout": "2027\nno such key: missing\ndefault\niyi\n", "wasmStdout": "2027\nno such key: missing\ndefault\niyi\n", @@ -391,8 +391,8 @@ "path": "samples/tour/strings.iyi", "sourceSha256": "704f192ecd578a9847d911563129b559b5b04cc276327f8de24fd1502565f542", "wasm": "strings.wasm", - "bytes": 127923, - "sha256": "b8f53f59f80f039565222c84928d6d0a3326211bd09aa5315d1a076f0615b916", + "bytes": 132610, + "sha256": "a0cd3a2f419001fc769660eaedd6fa00d7fb2fdaf7c7b43a636a2b544923becc", "exitCode": 0, "nativeStdout": "10\nHELLO, IYI\nhello, iyi\ntrue\nHello, iyi!\n[\"Hello,\", \"iyi\"]\n2 and true and [1, 2]\n12,05 TL\ntotal: 12,05 TL\nitem 12\ntotal 99\n", "wasmStdout": "10\nHELLO, IYI\nhello, iyi\ntrue\nHello, iyi!\n[\"Hello,\", \"iyi\"]\n2 and true and [1, 2]\n12,05 TL\ntotal: 12,05 TL\nitem 12\ntotal 99\n", @@ -405,8 +405,8 @@ "path": "samples/tour/generic-functions.iyi", "sourceSha256": "2b0ace59365d9bb35c904102d1131a95f5787113ba585975a819c6a354666e7f", "wasm": "generic-functions.wasm", - "bytes": 119118, - "sha256": "667e7bf0be740a8fc59b03fad29a7c0750a3b3b0dffd684a83cf1e94276c1fcd", + "bytes": 119957, + "sha256": "af4cb57532404990e8034cfe34924d9ae985fe3801eeb82e9848504965ed1d4d", "exitCode": 0, "nativeStdout": "10\na\n{\"one\", 1}\n101\n", "wasmStdout": "10\na\n{\"one\", 1}\n101\n", @@ -419,8 +419,8 @@ "path": "samples/tour/generic-types.iyi", "sourceSha256": "fe5f0b5626ef062f7af44196125d3b50d1914788fb1416d980d10e22f872aa8f", "wasm": "generic-types.wasm", - "bytes": 120694, - "sha256": "f31a8e92282c161da3b98d1217da3b71a9d75b7f9680956aa23255fae7a9d2b8", + "bytes": 121325, + "sha256": "00aab8ec6538ae6bd23e330d009f37d7b6fa0a65f2cfedb28d4e72d2261de9b3", "exitCode": 0, "nativeStdout": "2\n2\n1\n\niyi\na stack of 1\na stack of 0\n", "wasmStdout": "2\n2\n1\n\niyi\na stack of 1\na stack of 0\n", @@ -433,8 +433,8 @@ "path": "samples/tour/bounds.iyi", "sourceSha256": "cd5324bcc2756eef8d4ebeca406c61ecf12cfd5898b26a01ec8fa4fd1ef64d26", "wasm": "bounds.wasm", - "bytes": 119927, - "sha256": "e544f960e565efb688201451fe920f00042fa0853ad202e7b28e2cb33e97e9f8", + "bytes": 120768, + "sha256": "05639cababda7d80c4b7a55582709def58e5e9b63e4a63576c838a4420037105", "exitCode": 0, "nativeStdout": "200\n45\n30\n", "wasmStdout": "200\n45\n30\n", @@ -447,8 +447,8 @@ "path": "samples/iyi/basics.iyi", "sourceSha256": "2fab386bd172c15319db0a4e553008d7ae7114fd8cdef72e7a48a850c301c72c", "wasm": "iyi-basics.wasm", - "bytes": 151139, - "sha256": "4468a63b318c69e10e05126519f863beafef45909b0bde57419db9e9a3741dd2", + "bytes": 161156, + "sha256": "ca267bc713043e853ec008ba5d1fdac76f000d198e6629fa887cdb62fb7da297", "exitCode": 0, "nativeStdout": "55\n1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n3\n4,5,9,6\ntrue\n1,1,2,3,4,5,6,9\n3,1,4,1,5,9,2,6\n1,1,2,3,4,5,6,9\n31\n31\nhello, world\ntrue\n12\nKhoor, Zruog\n3\n3\n{\"a\" => 3, \"b\" => 1, \"c\" => 1}\n14\nthe 5\ncat 2\nsat 2\n[\"cat\", \"end\"]\n", "wasmStdout": "55\n1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n3\n4,5,9,6\ntrue\n1,1,2,3,4,5,6,9\n3,1,4,1,5,9,2,6\n1,1,2,3,4,5,6,9\n31\n31\nhello, world\ntrue\n12\nKhoor, Zruog\n3\n3\n{\"a\" => 3, \"b\" => 1, \"c\" => 1}\n14\nthe 5\ncat 2\nsat 2\n[\"cat\", \"end\"]\n", @@ -459,10 +459,10 @@ "id": "iyi/calc", "set": "iyi", "path": "samples/iyi/calc.iyi", - "sourceSha256": "9352562358500804e964bdabfa46dd512100942daead73ed4222f0cc0b9426cb", + "sourceSha256": "644ac5ae8b00ae0bec621a1a3962ead1695056558120c02bc228ee76ea75bbd3", "wasm": "iyi-calc.wasm", - "bytes": 157076, - "sha256": "a6f4bf143c5c108352868cf286c75c4700b394e2c290ec66fd0a97b811f12bba", + "bytes": 158322, + "sha256": "1f115342215c570068fae5a72845cf81bd9e7fad67555c7da206e4f62042615f", "exitCode": 0, "nativeStdout": "64\nerror: unknown name 'missing'\nerror: divided by zero\n", "wasmStdout": "64\nerror: unknown name 'missing'\nerror: divided by zero\n", @@ -473,13 +473,13 @@ "id": "iyi/collections", "set": "iyi", "path": "samples/iyi/collections.iyi", - "sourceSha256": "122c28029b331cd4f24aa508ada7e4b507adc55fe98f0cc144664007239aa9c1", + "sourceSha256": "3c962d15ebae945e3d9ef55c8c127392d5b63d2677ec50af7ff33bfb3b4da45a", "wasm": "iyi-collections.wasm", - "bytes": 187432, - "sha256": "f5c78d3f468e192d13943a9b0e1472bc31d3a37e0327b5152a1df98c26c24e5d", + "bytes": 189605, + "sha256": "0b90aa1f2ef0865c8b967642d38a9433ab990503989571166d4a0e97a9150337", "exitCode": 0, - "nativeStdout": "to_a 3,1,4,1,5\nsize 5\nempty? false true\nall? true\nany? true\nnone? true\none? true\ncount 2\nfind? 4 nil\nfind 4\nindex? 2\nindex 2\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect 3,4,5\nreject 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_index 0,1,2,3,4\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax?/max 5 5\nmin?/min 1 1\nsum/product 14 60\nsum of nothing 0\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\nindex_by fig\neach_slice 3+1 4+1 5\neach_cons 3+1 1+4 4+1 1+5\nchunk_while 3 1+4 1+5\nfirst(n) 3,1,4\ncompact_map 30,40,50\nfind_value big\naccumulate 0,3,4,8,9,14\nreduce? 14\nin_groups_of 3+1 4+1 5+0\nmin_by fig\nmax_of/min_of 4 3\nminmax 1,5\ntally_by 3\nsum_by 15\nzip 3pear 1fig 4plum 1kiwi\ncycle 1,2,1,2\nas a type 5 items / 4 items\n", - "wasmStdout": "to_a 3,1,4,1,5\nsize 5\nempty? false true\nall? true\nany? true\nnone? true\none? true\ncount 2\nfind? 4 nil\nfind 4\nindex? 2\nindex 2\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect 3,4,5\nreject 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_index 0,1,2,3,4\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax?/max 5 5\nmin?/min 1 1\nsum/product 14 60\nsum of nothing 0\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\nindex_by fig\neach_slice 3+1 4+1 5\neach_cons 3+1 1+4 4+1 1+5\nchunk_while 3 1+4 1+5\nfirst(n) 3,1,4\ncompact_map 30,40,50\nfind_value big\naccumulate 0,3,4,8,9,14\nreduce? 14\nin_groups_of 3+1 4+1 5+0\nmin_by fig\nmax_of/min_of 4 3\nminmax 1,5\ntally_by 3\nsum_by 15\nzip 3pear 1fig 4plum 1kiwi\ncycle 1,2,1,2\nas a type 5 items / 4 items\n", + "nativeStdout": "to_a 3,1,4,1,5\nsize 5\nempty? false true\nall? true\nany? true\nnone? true\none? true\ncount 2\nfind 4 nil\nindex 2 nil\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect 3,4,5\nreject 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_index 0,1,2,3,4\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax?/max 5 5\nmin?/min 1 1\nsum/product 14 60\nsum of nothing 0\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\nindex_by fig\neach_slice 3+1 4+1 5\neach_cons 3+1 1+4 4+1 1+5\nchunk_while 3 1+4 1+5\nfirst(n) 3,1,4\ncompact_map 30,40,50\nfind_value big\naccumulate 0,3,4,8,9,14\nreduce? 14\nin_groups_of 3+1 4+1 5+0\nmin_by fig\nmax_of/min_of 4 3\nminmax 1,5\ntally_by 3\nsum_by 15\nzip 3pear 1fig 4plum 1kiwi\ncycle 1,2,1,2\nas a type 5 items / 4 items\n", + "wasmStdout": "to_a 3,1,4,1,5\nsize 5\nempty? false true\nall? true\nany? true\nnone? true\none? true\ncount 2\nfind 4 nil\nindex 2 nil\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect 3,4,5\nreject 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_index 0,1,2,3,4\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax?/max 5 5\nmin?/min 1 1\nsum/product 14 60\nsum of nothing 0\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\nindex_by fig\neach_slice 3+1 4+1 5\neach_cons 3+1 1+4 4+1 1+5\nchunk_while 3 1+4 1+5\nfirst(n) 3,1,4\ncompact_map 30,40,50\nfind_value big\naccumulate 0,3,4,8,9,14\nreduce? 14\nin_groups_of 3+1 4+1 5+0\nmin_by fig\nmax_of/min_of 4 3\nminmax 1,5\ntally_by 3\nsum_by 15\nzip 3pear 1fig 4plum 1kiwi\ncycle 1,2,1,2\nas a type 5 items / 4 items\n", "identical": true, "note": null }, @@ -489,8 +489,8 @@ "path": "samples/iyi/config.iyi", "sourceSha256": "e1c60e92bb4150097bed4d7986dc678380776ef09fc95ff03b278746ff121c10", "wasm": "iyi-config.wasm", - "bytes": 137685, - "sha256": "7a156d571c24464f707ca0bea4bd7d40b6fa15633c8d0f896731e95eb9ce2c97", + "bytes": 142245, + "sha256": "175248dbbbf130e1794c806d83bcb0f7395cdadcf180d28cd306c0e492b6bf95", "exitCode": 0, "nativeStdout": "{\"name\" => \"iyi\", \"port\" => \"8080\", \"mode\" => \"fast\"}\n8081\nname, port, mode\nerror: line 2: no '=' in \"broken line\"\n[\"a\", \"\", \"b\"]\n[\"one\", \"two\"]\n\"padded\"\ntrue\n", "wasmStdout": "{\"name\" => \"iyi\", \"port\" => \"8080\", \"mode\" => \"fast\"}\n8081\nname, port, mode\nerror: line 2: no '=' in \"broken line\"\n[\"a\", \"\", \"b\"]\n[\"one\", \"two\"]\n\"padded\"\ntrue\n", @@ -503,22 +503,36 @@ "path": "samples/iyi/derive.iyi", "sourceSha256": "267f41c7b062041474f9b0ed5102f9215b38a4336d57b550dc8b27e565034f71", "wasm": "iyi-derive.wasm", - "bytes": 118838, - "sha256": "85d32c54e386eb65e137ef6b880e8710e7abee1b5a77dcc4aa48048e4f810119", + "bytes": 119473, + "sha256": "67658a851e03943057355513df520be189b19e1f82c6720dca7e1ddee4d65def", "exitCode": 0, "nativeStdout": "Point(@x=3 @y=4 )\nSpan(@from=1 @to=9 @label=range )\nTag(@label=Std::Derives::Name @count=2 )\n@label= \n", "wasmStdout": "Point(@x=3 @y=4 )\nSpan(@from=1 @to=9 @label=range )\nTag(@label=Std::Derives::Name @count=2 )\n@label= \n", "identical": true, "note": null }, + { + "id": "iyi/enums", + "set": "iyi", + "path": "samples/iyi/enums.iyi", + "sourceSha256": "98b275c766e680a4adc21a3fc80c18b94e954317dd4668a4b303fcbeea8c0c5d", + "wasm": "iyi-enums.wasm", + "bytes": 135471, + "sha256": "754e18307e557bcc152ed945b569c8e2f919fe7514da21cc9ccf95ff8a1014f2", + "exitCode": 0, + "nativeStdout": "level: Warn\nspelled: Error\nsame: true\nother: false\nverdict: loud\nwarn? true\nerror? false\nat least info: true\nbelow error: true\nDebug Info Warn Error \nnames: 4\nparsed: Error\nlenient: Warn\nunknown: nil\nvalue: 3\nfrom 1: Info\nfrom 9: nil\nmode: Read | Write\nread: true\nappend: false\nnone: None\nmasked: Write\nwarns: 2\nerrors: 1\n", + "wasmStdout": "level: Warn\nspelled: Error\nsame: true\nother: false\nverdict: loud\nwarn? true\nerror? false\nat least info: true\nbelow error: true\nDebug Info Warn Error \nnames: 4\nparsed: Error\nlenient: Warn\nunknown: nil\nvalue: 3\nfrom 1: Info\nfrom 9: nil\nmode: Read | Write\nread: true\nappend: false\nnone: None\nmasked: Write\nwarns: 2\nerrors: 1\n", + "identical": true, + "note": null + }, { "id": "iyi/errors", "set": "iyi", "path": "samples/iyi/errors.iyi", "sourceSha256": "ccc420d0d31fc94b376cbc7cd86a8b057bbf49fdb7540c22478516691edbb43d", "wasm": "iyi-errors.wasm", - "bytes": 126617, - "sha256": "942f0d3b753be0f78e372cd2dde0ae1b01e3b0ab980f8e608cb38e265558406a", + "bytes": 127229, + "sha256": "ffeabf2127806eef4587e908c3532a9acd3f9d7089881829945c2431e90c8ad2", "exitCode": 0, "nativeStdout": "read 12\nio: no such file: missing\nparse: not a number: twelve\nor: 12\nor: 0\nor: 0\nor: -1\npanic: 12\nsuccess:\n open outer\n open inner\n close inner\n close outer\n12\npropagating:\n open outer\n close outer\n-1\n iteration 0\n iteration 0 closed\n iteration 1\n iteration 1 closed\n", "wasmStdout": "read 12\nio: no such file: missing\nparse: not a number: twelve\nor: 12\nor: 0\nor: 0\nor: -1\npanic: 12\nsuccess:\n open outer\n open inner\n close inner\n close outer\n12\npropagating:\n open outer\n close outer\n-1\n iteration 0\n iteration 0 closed\n iteration 1\n iteration 1 closed\n", @@ -531,8 +545,8 @@ "path": "samples/iyi/files.iyi", "sourceSha256": "aaa45bbe135b26d2c882122da7a4fe69df4bcba517a6f8ed57ba0101eda8ba69", "wasm": "iyi-files.wasm", - "bytes": 116374, - "sha256": "df0b3d569ef5aaa723c2feb9e1c188bdb5a24e8609a6d8c89ac2e8ae1e6b1032", + "bytes": 116360, + "sha256": "c64edb66ee1ce8b2b79abb5403079e3e1c24eb88c4358874a773c58dd888ac81", "exitCode": 1, "nativeStdout": "hello from iyi\n\ntrue\nfalse\nfalse\n", "wasmStdout": "", @@ -545,8 +559,8 @@ "path": "samples/iyi/format.iyi", "sourceSha256": "3fe7f5dd5cd2934db125205bbc89fba9cdc2354a2d072b60dfb2fa164ee3e059", "wasm": "iyi-format.wasm", - "bytes": 205780, - "sha256": "5d5b6f7a267807f7807b7a8c4cb43ddb91ab62d01f72b9454efb18a0ecec3c96", + "bytes": 222194, + "sha256": "dbacdae22b298afc7ba98a33356fd9c7a1c284de497d5665ececdc8feb78ebec", "exitCode": 0, "nativeStdout": "hello world\nchar 'A' (ascii 65)\nright ' hi'\nleft 'hi '\ndec: 255\nhex: ff\nHEX: 0XFF\noct: 0o377\nbin: 0b101\nplus: +42\nzero: 000042\nsign zero: +00042\nnegative zero: -00042\nint precision: 0007\nfloat precision: 3.14\nfloat precision: 3.1416\nstring precision: cin\nwider than field: 12345\nzero precision float: 4\nzero precision zero int: ' '\nmin int64: -9223372036854775808\nmax int64: 9223372036854775807\nString.format: answer = 42\npercent tuple: score = 0099\nprintf: run completed\n", "wasmStdout": "hello world\nchar 'A' (ascii 65)\nright ' hi'\nleft 'hi '\ndec: 255\nhex: ff\nHEX: 0XFF\noct: 0o377\nbin: 0b101\nplus: +42\nzero: 000042\nsign zero: +00042\nnegative zero: -00042\nint precision: 0007\nfloat precision: 3.14\nfloat precision: 3.1416\nstring precision: cin\nwider than field: 12345\nzero precision float: 4\nzero precision zero int: ' '\nmin int64: -9223372036854775808\nmax int64: 9223372036854775807\nString.format: answer = 42\npercent tuple: score = 0099\nprintf: run completed\n", @@ -559,8 +573,8 @@ "path": "samples/iyi/formatting.iyi", "sourceSha256": "ad90bbbd17da587770609673949d55c990aee356dda1d437ce23394231ec75ee", "wasm": "iyi-formatting.wasm", - "bytes": 124306, - "sha256": "ebb876d02a933eb7ec6a54e9aec09e97adbe19b27b58584ba67196e6cc9a7301", + "bytes": 129027, + "sha256": "95494ebbcecf7bc091c536add7c765d29c08485b96f9e6d58a2a2168d2aa7ede", "exitCode": 0, "nativeStdout": "last 30\nfirst 10\nwritten 99\nchar o\nslice lo\nhex ff\nbin 101\nrjust ' ab'\nljust 'ab***'\nrep ababab\nnil nil\nmaybe 99\n", "wasmStdout": "last 30\nfirst 10\nwritten 99\nchar o\nslice lo\nhex ff\nbin 101\nrjust ' ab'\nljust 'ab***'\nrep ababab\nnil nil\nmaybe 99\n", @@ -573,8 +587,8 @@ "path": "samples/iyi/generics.iyi", "sourceSha256": "f02e2232f1c1f2529603c71e0480950f89de4a6a2604c7a88a019358275ad2e6", "wasm": "iyi-generics.wasm", - "bytes": 128627, - "sha256": "188aa8ea8040a6a4da8229bdb56eaf7167bb4d2d24c435724d5f1b1117cd8cad", + "bytes": 129567, + "sha256": "1688bde266863dbe9191f414968f0e269dae00fb20aa563538a1cdc3e76705ae", "exitCode": 0, "nativeStdout": "Box(42)\nBox(hello)\nfirst=1, second=two\nBox(1)\nBox(two)\nfirst=3, second=four\nada, ada\n84\ntrue\n7\n", "wasmStdout": "Box(42)\nBox(hello)\nfirst=1, second=two\nBox(1)\nBox(two)\nfirst=3, second=four\nada, ada\n84\ntrue\n7\n", @@ -587,8 +601,8 @@ "path": "samples/iyi/grid.iyi", "sourceSha256": "e0a6807746d0cea1bd0d5e420ccb39d3322f95aca02a94499e7f7570007ea24c", "wasm": "iyi-grid.wasm", - "bytes": 140231, - "sha256": "f0d8b2be4fb7455a76a1dcb6b1ed87d458ee6ecadf91f9bdd1055c35ae2eef01", + "bytes": 144588, + "sha256": "e9a8280c4da724a9e7125af45b1dd961469c8298ed6b2c4dc1f9663033e2539e", "exitCode": 0, "nativeStdout": " 0 1 2 3\n 4 5 6 7\n 8 9 10 11\n 12 13 14 15\n120\n[0, 5, 10, 15]\n[0, 4, 8, 12]\n[5, 4, 3, 2, 1]\n[2, 4, 6, 8, 10]\n55\n[3, 1, 2]\n[{1, \"a\"}, {2, \"b\"}, {3, \"c\"}]\n2\ntrue\n[\"b\", \"c\"]\n01234\n", "wasmStdout": " 0 1 2 3\n 4 5 6 7\n 8 9 10 11\n 12 13 14 15\n120\n[0, 5, 10, 15]\n[0, 4, 8, 12]\n[5, 4, 3, 2, 1]\n[2, 4, 6, 8, 10]\n55\n[3, 1, 2]\n[{1, \"a\"}, {2, \"b\"}, {3, \"c\"}]\n2\ntrue\n[\"b\", \"c\"]\n01234\n", @@ -601,8 +615,8 @@ "path": "samples/iyi/hello.iyi", "sourceSha256": "d4ff82d3bb14df4130d64be42069385ee06604b399d62cbdf37189168cbb5ffe", "wasm": "iyi-hello.wasm", - "bytes": 120602, - "sha256": "9ffb27c988695ddf3a65dabe2de552ba18399e6521868aa9a1006f0e48f0ff67", + "bytes": 121363, + "sha256": "48557e457503dab655a81117ca400ab91be0e120522605b5aef283601322628b", "exitCode": 0, "nativeStdout": "Hello, iyi!\nHELLO, IYI!\nBEEP 42\nHello, crystal!\nBEEP 7\n-> BEEP 9\n", "wasmStdout": "Hello, iyi!\nHELLO, IYI!\nBEEP 42\nHello, crystal!\nBEEP 7\n-> BEEP 9\n", @@ -613,13 +627,13 @@ "id": "iyi/immutable", "set": "iyi", "path": "samples/iyi/immutable.iyi", - "sourceSha256": "e03dcd773d28199f426c99d0e621e141827da08b3aaccf4d24842ea3a14b9383", + "sourceSha256": "805926003c9ad0bfc805c2bde8ee59988c074721bd83e91da0035c0732dab113", "wasm": "iyi-immutable.wasm", - "bytes": 161874, - "sha256": "e355316b7ae09706578aaca5e3625de5bc8b08b38123ec77b030c318e3e89a21", + "bytes": 164650, + "sha256": "9866068cdb54b7adbea841ac85dba2deba1d08697216b2cd3b5cf42258c826b3", "exitCode": 0, - "nativeStdout": "size 5\nempty? false true\nat 3 5\nappended 6 from 5, original 3,1,4,1,5\nconcatenated 3,1,4,1,5,7 / 5\ncopied list 2, source 3\nto_a 3,1,4,1,5\nall?/any? true true\nnone?/one? true true\ncount 2\nfind?/find 4 4\nindex?/index 2 2\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect/reject 3,4,5 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax/min 5 1\nsum/product 14 60\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\ntwo elem types 4,2,5,2,6 / 4,3,4,4\n", - "wasmStdout": "size 5\nempty? false true\nat 3 5\nappended 6 from 5, original 3,1,4,1,5\nconcatenated 3,1,4,1,5,7 / 5\ncopied list 2, source 3\nto_a 3,1,4,1,5\nall?/any? true true\nnone?/one? true true\ncount 2\nfind?/find 4 4\nindex?/index 2 2\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect/reject 3,4,5 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax/min 5 1\nsum/product 14 60\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\ntwo elem types 4,2,5,2,6 / 4,3,4,4\n", + "nativeStdout": "size 5\nempty? false true\nat 3 5\nappended 6 from 5, original 3,1,4,1,5\nconcatenated 3,1,4,1,5,7 / 5\ncopied list 2, source 3\nto_a 3,1,4,1,5\nall?/any? true true\nnone?/one? true true\ncount 2\nfind 4 nil\nindex 2 nil\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect/reject 3,4,5 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax/min 5 1\nsum/product 14 60\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\ntwo elem types 4,2,5,2,6 / 4,3,4,4\n", + "wasmStdout": "size 5\nempty? false true\nat 3 5\nappended 6 from 5, original 3,1,4,1,5\nconcatenated 3,1,4,1,5,7 / 5\ncopied list 2, source 3\nto_a 3,1,4,1,5\nall?/any? true true\nnone?/one? true true\ncount 2\nfind 4 nil\nindex 2 nil\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect/reject 3,4,5 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax/min 5 1\nsum/product 14 60\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\ntwo elem types 4,2,5,2,6 / 4,3,4,4\n", "identical": true, "note": null }, @@ -629,8 +643,8 @@ "path": "samples/iyi/init_order.iyi", "sourceSha256": "bb3cf75d3ccb5ad1524696ea38fb99dd04a7b9c749692672334f0a7e9fe1a847", "wasm": "iyi-init_order.wasm", - "bytes": 115466, - "sha256": "3d46a07929a27d25afa0d7e8fee27ca67cd8edaee9aa6c466855d560fa7d3b07", + "bytes": 116077, + "sha256": "7bbba3e7797c37a95d1b86b1adcf6718a51ef33053b2ab6bd7f47fa3a5afd419", "exitCode": 0, "nativeStdout": "1. boot/config\n2. boot/registry, above its own `import` line\n3. boot/registry, below it\n4. samples/init_order, above its own `import` lines\n5. Hello, iyi.\n", "wasmStdout": "1. boot/config\n2. boot/registry, above its own `import` line\n3. boot/registry, below it\n4. samples/init_order, above its own `import` lines\n5. Hello, iyi.\n", @@ -641,10 +655,10 @@ "id": "iyi/inventory", "set": "iyi", "path": "samples/iyi/inventory.iyi", - "sourceSha256": "2a10b993ebd3574463775fbcf0311ede82ae8809812687dd27909c613edb8de6", + "sourceSha256": "8d17a1ab3590cbe103b0eedb8487c9df8cc2837e0b84b26a3c8abbe353af80d6", "wasm": "iyi-inventory.wasm", - "bytes": 163496, - "sha256": "24058b7528675262af68ce5c0c5e72c9a5c3e248b38f419aa309de3992346f2d", + "bytes": 168270, + "sha256": "f814d3dd7accdd643c9f67c389e01529764d6d736674b0df13d1825c9bee3a41", "exitCode": 0, "nativeStdout": "total: 11.0\ncheap: apple, pear\n2\ntrue\ntrue\nmelon\npear\n3.0\nnil\n1. apple 10 5.0\n2. pear 4 3.0\n3. melon 1 3.0\n[\"apple\", \"pear\", \"melon\"]\n15\npear\n", "wasmStdout": "total: 11.0\ncheap: apple, pear\n2\ntrue\ntrue\nmelon\npear\n3.0\nnil\n1. apple 10 5.0\n2. pear 4 3.0\n3. melon 1 3.0\n[\"apple\", \"pear\", \"melon\"]\n15\npear\n", @@ -657,8 +671,8 @@ "path": "samples/iyi/io.iyi", "sourceSha256": "8878852be923dc681f060baf0ae863e04a30e87f123498813c37738c2c97d41f", "wasm": "iyi-io.wasm", - "bytes": 122706, - "sha256": "c776b8c58e5620ec47e3982002b3abb648a36450696c485865be42feda97ac51", + "bytes": 123384, + "sha256": "fa51082a4191ba8ce93b81ef9c656b365869a7fed1fba0b9674900ef99b31bc0", "exitCode": 0, "nativeStdout": "no lines read from standard input\n", "wasmStdout": "no lines read from standard input\n", @@ -671,8 +685,8 @@ "path": "samples/iyi/modules.iyi", "sourceSha256": "8ff138c04aee474ea65b3f5112fe24b6e29de0149d096a107ff2b20a207a6c51", "wasm": "iyi-modules.wasm", - "bytes": 117204, - "sha256": "3e944b3e68c91ac1aa9ce51205d49e258ba11fdc7b29451c10c40f9db1ef3869", + "bytes": 117831, + "sha256": "01ca13c6ac3dc5ac7aaf9d75e17fe9881e17f0edbcb27c480d390abedb62de37", "exitCode": 0, "nativeStdout": "HELLO, IYI. Good day, iyi.\nfriend\nesteemed colleague\n", "wasmStdout": "HELLO, IYI. Good day, iyi.\nfriend\nesteemed colleague\n", @@ -685,8 +699,8 @@ "path": "samples/iyi/sessions.iyi", "sourceSha256": "d33fc3528bffdd710b785425e5b0559e606c53184c2234774d56fc7d9f2f55c9", "wasm": "iyi-sessions.wasm", - "bytes": 126687, - "sha256": "dca4c9a9664e803d65c6b8bedef92e35339e0367d87f789bfe9f11b12ab5a09d", + "bytes": 127303, + "sha256": "fc0125706c032c41b8a4670d0699c3fdcba1651c5e2edbcbb033f6e1b43b4a3c", "exitCode": 0, "nativeStdout": "three arrivals:\n ada since tick 1\n linus since tick 2\n grace since tick 3\n (3 signed in)\nlinus left, had been in since tick 2\n ada since tick 1\n grace since tick 3\n (2 signed in)\nnobody named bob: true\nlinus is back:\n ada since tick 1\n grace since tick 3\n linus since tick 4\n (3 signed in)\n ada since tick 1\n grace since tick 3\n linus since tick 4\n (3 signed in)\n", "wasmStdout": "three arrivals:\n ada since tick 1\n linus since tick 2\n grace since tick 3\n (3 signed in)\nlinus left, had been in since tick 2\n ada since tick 1\n grace since tick 3\n (2 signed in)\nnobody named bob: true\nlinus is back:\n ada since tick 1\n grace since tick 3\n linus since tick 4\n (3 signed in)\n ada since tick 1\n grace since tick 3\n linus since tick 4\n (3 signed in)\n", @@ -699,8 +713,8 @@ "path": "samples/iyi/shapes.iyi", "sourceSha256": "e3e4ca261b7f764ce2a168163c6da93909efc63d663fb1828f7b6d5a79b9f805", "wasm": "iyi-shapes.wasm", - "bytes": 154470, - "sha256": "15255dc31e86f227059bea64390c10c5bf13a49fdfdb455fc40b9597b1b264a2", + "bytes": 156298, + "sha256": "77876a0ea26ec69ea13ac8af6980bfe16308942536a1be3267158a79e8da4cce", "exitCode": 0, "nativeStdout": "circle: 3.14\nrect: 6.0\n9.1\nrect\n[12, nil, 7, -3]\n16\n[12, 7, 3]\n1024\n0.25\n1099511627776\n128\n8\n", "wasmStdout": "circle: 3.14\nrect: 6.0\n9.1\nrect\n[12, nil, 7, -3]\n16\n[12, 7, 3]\n1024\n0.25\n1099511627776\n128\n8\n", @@ -713,8 +727,8 @@ "path": "samples/iyi/socket.iyi", "sourceSha256": "97a57aa5530041216007a9ff94f8b3a7a9b2868338a54988419afe58909d2097", "wasm": "iyi-socket.wasm", - "bytes": 119490, - "sha256": "1bdc6b798404f111999a00d4785a5feb1ed4d973bafdac7b3d483bb9e5e0d4f6", + "bytes": 121028, + "sha256": "f836cfca83bfc57e1ef3986a676ce5968a34539e2027313cb1617dbc3e7410a7", "exitCode": 1, "nativeStdout": "server listening on an ephemeral port\nserver received: hello from client\n\nclient received: hello from server\n\nclosed\n", "wasmStdout": "", @@ -727,8 +741,8 @@ "path": "samples/iyi/std_iterator.iyi", "sourceSha256": "96a97be201c43c8e828a8cc259de87a4f78155867850b7f2745baaba686af1ad", "wasm": "iyi-std_iterator.wasm", - "bytes": 169292, - "sha256": "23add48d853e701506df2a48376d0fa577dd54f926fd6b18c504c5bb1167d5e4", + "bytes": 171444, + "sha256": "626ee61df091c1a97b2830e807eea589c19d35bc78522e60ee5c4cec65a2b487", "exitCode": 0, "nativeStdout": "natural numbers first 5: [1, 2, 3, 4, 5]\npulls from generator: 5\neven squares first 4: [4, 16, 36, 64]\npulls for 4 matches: 8\nchained odds and evens: [1, 3, 5, 2, 4, 6]\nzipped pairs: [{\"apple\", 100}, {\"banana\", 95}, {\"cherry\", 88}]\neach_slice(3): [[1, 2, 3], [4, 5, 6], [7]]\neach_cons(3): [[10, 20, 30], [20, 30, 40], [30, 40, 50]]\nstepped by 2: [0, 20, 40, 60]\ncycled 2 times: [\"tick\", \"tock\", \"tick\", \"tock\"]\nwith_index(1): [{\"alpha\", 1}, {\"beta\", 2}, {\"gamma\", 3}]\nreduce sum: 100\nfirst > 10: 12\n", "wasmStdout": "natural numbers first 5: [1, 2, 3, 4, 5]\npulls from generator: 5\neven squares first 4: [4, 16, 36, 64]\npulls for 4 matches: 8\nchained odds and evens: [1, 3, 5, 2, 4, 6]\nzipped pairs: [{\"apple\", 100}, {\"banana\", 95}, {\"cherry\", 88}]\neach_slice(3): [[1, 2, 3], [4, 5, 6], [7]]\neach_cons(3): [[10, 20, 30], [20, 30, 40], [30, 40, 50]]\nstepped by 2: [0, 20, 40, 60]\ncycled 2 times: [\"tick\", \"tock\", \"tick\", \"tock\"]\nwith_index(1): [{\"alpha\", 1}, {\"beta\", 2}, {\"gamma\", 3}]\nreduce sum: 100\nfirst > 10: 12\n", @@ -741,8 +755,8 @@ "path": "samples/iyi/std_text.iyi", "sourceSha256": "c891f86fe35a876e7f8279c549a0d16a39f38715593fae69a4ca387cb6468276", "wasm": "iyi-std_text.wasm", - "bytes": 143914, - "sha256": "6f0f5d38b92fc7676065019eb58e53f17fea26b90baf50f90848a758d3f5ceff", + "bytes": 155590, + "sha256": "df910d71146578f954fd7bbe999d814982bd59fe2b7a3bff7b774c79617691bf", "exitCode": 0, "nativeStdout": "cleaned line: 'GET /api/v1/users HTTP/1.1'\nHTTP method: GET, path: /api/v1/users, version: HTTP/1.1\nHeader 'Authorization' = 'Bearer token123'\nslug: hello-world-this-is-iyi-std-text-\ngreeting size: 11 characters, 12 bytes\nreversed greeting: dlrow olléh\nhex 'ff' to decimal: 255\n", "wasmStdout": "cleaned line: 'GET /api/v1/users HTTP/1.1'\nHTTP method: GET, path: /api/v1/users, version: HTTP/1.1\nHeader 'Authorization' = 'Bearer token123'\nslug: hello-world-this-is-iyi-std-text-\ngreeting size: 11 characters, 12 bytes\nreversed greeting: dlrow olléh\nhex 'ff' to decimal: 255\n", @@ -755,8 +769,8 @@ "path": "samples/iyi/std_time.iyi", "sourceSha256": "d73e660c6c46cdf7954ee03bdfa31082580e817ff188d77351705fef2b675a4f", "wasm": "iyi-std_time.wasm", - "bytes": 156667, - "sha256": "a7ce2ad88396dd5411cffb0dd09c35dd7556e3dd78503e405e976d926f5d8d6f", + "bytes": 169080, + "sha256": "760de58c1a8d13c76e8e4e260fad854ce3c1d48a951df9b634747dd5517316ed", "exitCode": 0, "nativeStdout": "Reference time (UTC): 2024-02-29 12:30:45 UTC\nRFC 3339: 2024-02-29T12:30:45.123Z\nUnix timestamp: 1709209845 s (1709209845123 ms)\nYear: 2024\nMonth: 2\nDay: 29\nDay of week: Thursday\nDay of year: 60\nTomorrow: 2024-03-01T12:30:45Z\nYesterday: 2024-02-28T12:30:45Z\nLive wall clock valid: true\nMonotonic duration >= 0: true\nLeap day event: 2024-02-29 12:00:00 UTC\nIs leap year: true\n", "wasmStdout": "Reference time (UTC): 2024-02-29 12:30:45 UTC\nRFC 3339: 2024-02-29T12:30:45.123Z\nUnix timestamp: 1709209845 s (1709209845123 ms)\nYear: 2024\nMonth: 2\nDay: 29\nDay of week: Thursday\nDay of year: 60\nTomorrow: 2024-03-01T12:30:45Z\nYesterday: 2024-02-28T12:30:45Z\nLive wall clock valid: true\nMonotonic duration >= 0: true\nLeap day event: 2024-02-29 12:00:00 UTC\nIs leap year: true\n", @@ -769,8 +783,8 @@ "path": "samples/iyi/visited.iyi", "sourceSha256": "987608a702df760c9cb5f3c0c85a5f621553fda885504892acb2fbb67d57d744", "wasm": "iyi-visited.wasm", - "bytes": 137264, - "sha256": "f30ad08884c418ac29e1f06c0f59ee8e3ff1b8a16d8c54aa198248bfb82b5323", + "bytes": 141499, + "sha256": "593c1ef6065ca6043d85f6d9ba9c9dba2c189084656ac1aa254e487a690bff94", "exitCode": 0, "nativeStdout": "Set{\"home\", \"docs\", \"blog\", \"install\", \"about\"}\n5\nfalse\nSet{\"install\"}\nSet{\"orphan\"}\ntrue\nfalse\ntrue\ntrue\n[\"the\", \"cat\", \"and\", \"dog\", \"bird\"]\n", "wasmStdout": "Set{\"home\", \"docs\", \"blog\", \"install\", \"about\"}\n5\nfalse\nSet{\"install\"}\nSet{\"orphan\"}\ntrue\nfalse\ntrue\ntrue\n[\"the\", \"cat\", \"and\", \"dog\", \"bird\"]\n", @@ -783,8 +797,8 @@ "path": "samples/iyi/webapp.iyi", "sourceSha256": "d7bfd0999323ab09160188d7f912c38f888ec8aed18a69efc235e1fde2254600", "wasm": "iyi-webapp.wasm", - "bytes": 147715, - "sha256": "122e4f124e284b2a362e4434d8fbad93d3a8fc7a1353861ed9efbd94063995c4", + "bytes": 152263, + "sha256": "a1888e3ed235681d15a187dcf785e0c124e6b8bd3aafbc7e12613325f03a2dbb", "exitCode": 0, "nativeStdout": "GET /\nGET /users\nPOST /users\nGET /count\nGET /v1\nGET /v1/admin/dashboard\n1|42|0\n", "wasmStdout": "GET /\nGET /users\nPOST /users\nGET /count\nGET /v1\nGET /v1/admin/dashboard\n1|42|0\n", diff --git a/records/wasm/methods.wasm b/records/wasm/methods.wasm index 463b41f..38c54b4 100755 Binary files a/records/wasm/methods.wasm and b/records/wasm/methods.wasm differ diff --git a/records/wasm/modules.wasm b/records/wasm/modules.wasm index 8346629..7314ac7 100755 Binary files a/records/wasm/modules.wasm and b/records/wasm/modules.wasm differ diff --git a/records/wasm/nil.wasm b/records/wasm/nil.wasm index 87a08df..c5880da 100755 Binary files a/records/wasm/nil.wasm and b/records/wasm/nil.wasm differ diff --git a/records/wasm/ranges.wasm b/records/wasm/ranges.wasm index 6f837d7..7b419fa 100755 Binary files a/records/wasm/ranges.wasm and b/records/wasm/ranges.wasm differ diff --git a/records/wasm/returns.wasm b/records/wasm/returns.wasm index 9b0ec9f..e648e5f 100755 Binary files a/records/wasm/returns.wasm and b/records/wasm/returns.wasm differ diff --git a/records/wasm/strings.wasm b/records/wasm/strings.wasm index dd7c381..56dc322 100755 Binary files a/records/wasm/strings.wasm and b/records/wasm/strings.wasm differ diff --git a/records/wasm/structs.wasm b/records/wasm/structs.wasm index 01f87b6..19e067a 100755 Binary files a/records/wasm/structs.wasm and b/records/wasm/structs.wasm differ diff --git a/records/wasm/traits.wasm b/records/wasm/traits.wasm index d9c0cd8..8864658 100755 Binary files a/records/wasm/traits.wasm and b/records/wasm/traits.wasm differ diff --git a/records/wasm/tuples.wasm b/records/wasm/tuples.wasm index cc2c3ff..2d0dd80 100755 Binary files a/records/wasm/tuples.wasm and b/records/wasm/tuples.wasm differ diff --git a/records/wasm/types.wasm b/records/wasm/types.wasm index 63b8f00..a2e331b 100755 Binary files a/records/wasm/types.wasm and b/records/wasm/types.wasm differ diff --git a/records/wasm/unions.wasm b/records/wasm/unions.wasm index 64010ef..b28fd35 100755 Binary files a/records/wasm/unions.wasm and b/records/wasm/unions.wasm differ diff --git a/records/wasm/variables.wasm b/records/wasm/variables.wasm index f8fe6bf..2394d47 100755 Binary files a/records/wasm/variables.wasm and b/records/wasm/variables.wasm differ diff --git a/scripts/download.mjs b/scripts/download.mjs index e123ef2..a05245f 100755 --- a/scripts/download.mjs +++ b/scripts/download.mjs @@ -100,12 +100,16 @@ const verify = span(installer, INSTALLER, "verify", /^# The checksum line for th // The release job that writes the file the installer looks for. Anchored on // the `cat` as well, because an earlier job writes SHA256SUMS too: it is the // one that proves the refusal, below, and matching it here would have quoted -// fifty lines of the wrong job. +// fifty lines of the wrong job. The globs it sums are not spelled out: one +// SHA256SUMS covers every archive a release publishes, so 472e2ba1a added +// `*.zip` beside `*.tar.gz` and this anchor has no business caring. What it +// cares about is that the release job still writes that one file and prints +// it. const publish = span( workflow, WORKFLOW, "publish", - /sha256sum \*\.tar\.gz > SHA256SUMS && cat SHA256SUMS/, + /sha256sum [^>]*> SHA256SUMS && cat SHA256SUMS/, /gh release create/, ); @@ -176,7 +180,7 @@ if (!refused) die(`${INSTALLER}'s uname case no longer ends in a die, so a machi // is that these are one file. const wantedFile = /-o "\$tmp\/([A-Za-z0-9._]+)"/.exec(verify.text); if (!wantedFile) die(`${INSTALLER}'s verification no longer downloads a file this script can name`); -const writtenFile = /sha256sum \*\.tar\.gz > ([A-Za-z0-9._]+)/.exec(publish.text); +const writtenFile = /sha256sum [^>]*> ([A-Za-z0-9._]+)/.exec(publish.text); if (!writtenFile) die(`${WORKFLOW}'s release job no longer writes a checksum file this script can name`); if (wantedFile[1] !== writtenFile[1]) { die( diff --git a/scripts/install-targets.mjs b/scripts/install-targets.mjs index 3f0e556..4c0ca75 100755 --- a/scripts/install-targets.mjs +++ b/scripts/install-targets.mjs @@ -28,10 +28,19 @@ // shown, so the site restates the repository rather than one file of it; // * README.md's own platform words under the one-liner ("Linux x86-64 and // macOS arm64"), which is where the prose name beside each target comes -// from. It is matched back to a target by scoring the target's own `uname` -// components against the phrase's words, so the only spellings this file -// knows are the two aliases below. An unmatched or ambiguous phrase fails -// and names itself; it is never guessed. +// from. They are found by the fenced block that holds the `install.sh` +// one-liner rather than by the clause that used to follow them, because +// what follows is prose about other platforms and moves: 48bc8d0b5 put a +// PowerShell block between the sentence and the clause this once matched. +// What does not move is that the sentence sits directly under the command +// it qualifies. Each phrase is matched back to a target by scoring the +// target's own `uname` components against the phrase's words, so the only +// spellings this file knows are the two aliases below. An unmatched or +// ambiguous phrase fails and names itself; it is never guessed; +// * install.sh is the only installer read here. Windows has its own +// (install.ps1) and its own archive, a zip rather than a tarball, so it is +// not one of this record's targets and src/lib/release.ts builds no URL +// for it. // // WHAT IT DOES NOT DO. It does not check that a release actually published the // tarballs, or that a URL resolves. It checks that the site offers exactly what @@ -155,15 +164,17 @@ if (undocumented.length > 0) { ); } -/* The platform words, out of the sentence under README's one-liner. Anchored on - * the clause that follows them, because the sentence is prose and a looser - * match would drag half a paragraph in. */ -const prose = /\.? ([A-Z][^.;]*?); the tarball by hand/.exec(readme); +/* The platform words, out of the sentence under README's one-liner. Anchored + * on the one-liner's own fenced block: the sentence a reader is shown is the + * one directly under the command, and the first sentence after the fence is + * it. Anchoring on what comes after instead put this at the mercy of a + * paragraph about a platform this record does not carry. */ +const prose = /install\.sh \| sh[^`]*``` ([A-Z][^.;]*)[.;]/.exec(readme); if (!prose) { throw new Error( - `install-targets: ${CLAIM} no longer names the platforms under its ` + - `one-liner ("... ; the tarball by hand"), so there is no sentence in ` + - `the repository to take the prose name of each target from.`, + `install-targets: ${CLAIM} no longer states the platforms in a sentence ` + + `under the fenced ${SOURCE} one-liner, so there is no sentence in the ` + + `repository to take the prose name of each target from.`, ); } const phrases = prose[1].split(/\s*(?:,|and)\s*/).filter(Boolean); diff --git a/scripts/project.mjs b/scripts/project.mjs index a2c1ae6..59c458b 100755 --- a/scripts/project.mjs +++ b/scripts/project.mjs @@ -47,7 +47,7 @@ const DOCUMENTS = [ { id: "contributing", file: "CONTRIBUTING.md", blurb: "What a change to this repository is expected to come with." }, { id: "security", file: "SECURITY.md", blurb: "Where a vulnerability goes, and which of the two trackers is the right one." }, { id: "conduct", file: "CODE_OF_CONDUCT.md", blurb: "The Contributor Covenant, and the address that enforces it here." }, - { id: "notice", file: "NOTICE.md", blurb: "Crystal's copyright, the licence, and every library the compiler links." }, + { id: "notice", file: "NOTICE.md", blurb: "Crystal's copyright, the license, and every library the compiler links." }, ]; const commit = execFileSync("git", ["-C", repo, "rev-parse", "HEAD"], { diff --git a/scripts/samples.mjs b/scripts/samples.mjs index f86256c..723f83a 100755 --- a/scripts/samples.mjs +++ b/scripts/samples.mjs @@ -2,12 +2,25 @@ // Transcludes the repository's own text into JSON the site imports. Nothing a // lesson shows as code is typed into the lesson. Two kinds of text arrive here: // -// 1. The sample programs under `samples/iyi` in the iyi repository, and the -// tour under `samples/tour` in this one. A lesson names a file, a named -// top-level declaration in it, or a content-anchored span of it, and the -// build reads that text out of the file at that moment. A sample that -// changes in the repository changes on the site. A sample that is renamed -// or deleted fails the build, naming the lesson that asked for it. +// 1. The sample programs under `samples/iyi` in the iyi repository, read at +// the tag CHANGELOG.md names, and the tour under `samples/tour` in this +// one, read off disk. A lesson names a file, a named top-level +// declaration in it, or a content-anchored span of it, and the build +// reads that text out of the tree that ships it. A sample a release +// renamed or dropped fails the build, naming the lesson that asked for +// it. +// +// WHY THE TAG RATHER THAN THE CHECKOUT. Every listing is painted with +// the tokens in `records/highlight.json`, which +// `scripts/record-highlight.mjs` records from a release and +// `scripts/records.mjs` checks against that release. Indexing the text +// from master instead left the site holding two readings of one program +// the moment a commit after the tag touched a sample, and `Sample.astro` +// compares them: the build stopped, correctly, about the wrong pair of +// files. The line `scripts/records.mjs` draws is drawn here too - +// `samples/iyi/` is the iyi tree's and is read at the release, and this +// repository's own files are in front of you and move when you move +// them. // 2. The console recordings in README.md. A lesson names a block by an anchor // string, and that anchor has to appear in exactly one fenced block in the // whole README. The block travels with the line numbers it was quoted @@ -27,6 +40,7 @@ import { } from "node:fs"; import { dirname, join, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { blobAt, releaseRef, treeAt } from "./release-ref.mjs"; const here = dirname(fileURLToPath(import.meta.url)); const repo = process.env.IYI_REPO ? resolve(process.env.IYI_REPO) : resolve(here, "..", "..", "iyi"); @@ -40,7 +54,8 @@ const die = (message) => { // Samples ------------------------------------------------------------------ const site = resolve(here, ".."); -const samplesRoot = resolve(repo, "samples", "iyi"); +const release = releaseRef(repo); +const samplesRoot = `${release.tag}:samples/iyi`; const tourRoot = resolve(site, "samples", "tour"); function walk(dir) { @@ -107,22 +122,31 @@ function regionsOf(lines) { return { regions, ambiguous: [...ambiguous].sort() }; } -const samples = {}; -for (const [root, tree] of [[samplesRoot, repo], [tourRoot, site]]) { - for (const file of walk(root)) { - const path = relative(resolve(tree, "samples"), file).split("\\").join("/"); - const text = readFileSync(file, "utf8"); - const lines = text.replace(/\n$/, "").split("\n"); - const { regions, ambiguous } = regionsOf(lines); +/* Two trees, read two ways, for the reason at the top of this file. `git + * ls-tree` sorts, which is what `walk` does to a directory, so the index is in + * one order whichever side a program came from. */ +const indexed = [ + ...treeAt(repo, release.tag, "samples/iyi") + .filter((path) => path.endsWith(".iyi")) + .map((path) => [path, blobAt(repo, release.tag, path).toString("utf8")]), + ...walk(tourRoot).map((file) => [ + relative(site, file).split("\\").join("/"), + readFileSync(file, "utf8"), + ]), +]; - samples[path] = { - path: `samples/${path}`, - text, - lines: lines.length, - regions, - ambiguous, - }; - } +const samples = {}; +for (const [path, text] of indexed) { + const lines = text.replace(/\n$/, "").split("\n"); + const { regions, ambiguous } = regionsOf(lines); + + samples[path.replace(/^samples\//, "")] = { + path, + text, + lines: lines.length, + regions, + ambiguous, + }; } if (Object.keys(samples).length === 0) { @@ -443,8 +467,19 @@ if (missing.length > 0) { ); } +/* Where a citation is checked follows what the citation is. A sample is shown + * from the release, so it has to be in the release. This repository's own + * files, and the rest of the iyi checkout a lesson stands on - SPEC.md, a + * bench script, a compiler pass - are cited as they stand and are checked + * where they stand, which is the tree scripts/facts.mjs and + * scripts/proofs.mjs read as well. */ +const cited = (path) => { + if (path.startsWith("samples/iyi/")) return blobAt(repo, release.tag, path) !== null; + return existsSync(resolve(path.startsWith("records/") ? site : repo, path)); +}; + const absentSources = [...claimedSources] - .filter(([path]) => !existsSync(resolve(path.startsWith("records/") ? resolve(here, "..") : repo, path))) + .filter(([path]) => !cited(path)) .map(([path, where]) => `${path} (cited by ${[...where].join(", ")})`); if (absentSources.length > 0) { die( diff --git a/src/content/learn/declarations-not-bodies.mdx b/src/content/learn/declarations-not-bodies.mdx index d45de02..c660cd6 100644 --- a/src/content/learn/declarations-not-bodies.mdx +++ b/src/content/learn/declarations-not-bodies.mdx @@ -33,11 +33,10 @@ This is the step the language exists for. Everything else in iyi, the `pub` signatures, the traits, the orphan rule, is here to make it hold. Two files are enough to show it, so start with the one being imported. Every iyi -file opens by saying which module it is, and that line is not a namespace -declaration you can put anywhere. It is the compilation unit's name, it is -absolute, and it tells you where the file sits: a module called `app/greeter` -lives at `app/greeter.iyi`, resolved from the project root and never relative to -whoever imports it. No manifest, no include path, no search order. +file opens by naming its compilation unit, and that name is absolute. It tells +you where the file sits: a module called `app/greeter` lives at +`app/greeter.iyi`, resolved from the project root and never relative to whoever +imports it. No manifest, no include path, no search order. @@ -89,7 +88,7 @@ Two files is a demonstration. A web framework is a test. lives in `samples/iyi/kemal`. That whole directory goes away between the two builds, and the program still builds, links, runs, and prints the same output. -## It is a gate, not a slogan +## The roundtrip gate `bench/samples_roundtrip.sh` is where the claim stops being prose. For every sample that imports a module it builds with `--emit-iyimod`, runs the binary, and @@ -99,9 +98,9 @@ each sample again with `--use-iyimod`, runs that second binary, and diffs the tw outputs. Any sample that fails to build either way, or prints something different, exits the script non zero. README.md says CI runs it on every push. -Look at what the diff compares. Not that both builds succeeded, which would only -prove the front end swallowed the artifacts. That both programs ran, and printed -the same thing. The script's own header is blunt about why the deletion is the +Look at what the diff compares: that both programs ran, and printed the same +thing. Merely checking that both builds succeeded would only prove the front +end swallowed the artifacts. The script's own header is blunt about why the whole test: the only way to be sure a consumer never reads a dependency's source is to take the source away and see whether the build still produces a program that behaves. @@ -110,13 +109,12 @@ What that boundary saves in seconds belongs to a machine rather than to a language, so this page will not quote you a number. README.md publishes them with the machine attached and the command that measures them. -## The clause the deletion did not use +## Acyclic imports -Read the premise at the top of this page again. It is three sentences, and the -deletion proves the third. The second one, that `import` forms a DAG, is -enforced long before any of that, and it has to be: a module initialises after -every module it imports, so two modules that wait for each other have no order -to be initialised in and there is no build to have. +The rule that `import` forms a DAG is enforced long before any of that, and it +has to be: a module initializes after every module it imports, so two modules +that wait for each other have no order to be initialized in and there is no +build to have. Write one anyway and the compiler declines, draws the cycle it found, and names the move that fixes it. diff --git a/src/content/learn/errors-are-values.mdx b/src/content/learn/errors-are-values.mdx index cbdf228..29a0844 100644 --- a/src/content/learn/errors-are-values.mdx +++ b/src/content/learn/errors-are-values.mdx @@ -9,7 +9,7 @@ bought: >- Nothing was built for this. Exhaustiveness was already in the language and unions already carried a type id, so adding an error member to an exported signature turns every incomplete `case` on it into a compile error, and an - error a caller has not handled cannot reach production quietly. + error a caller has not handled cannot reach production. sources: - file: samples/iyi/errors.iyi note: the two producers, the propagating call, the exhaustive case, and what `.or` and `.or_panic` are @@ -83,7 +83,7 @@ incomplete `case` on it stops compiling. Sometimes you want the value or a default. `.or` takes the default, `.or_panic` takes the value or dies. -Neither can be an ordinary method, and the reason is worth understanding. A real +Neither can be an ordinary method: a real method call on `Int32 | IOError` would demand that method from both members, and a method every error type has to supply is exactly what this design avoids. So the compiler knows these two by name, which is also why `or` and `or_panic` are @@ -94,7 +94,7 @@ instead of being computed: the default's type unioned with the non-error members `.or_panic` yields the non-error members alone, because what it calls never returns. -## What this does not do yet +## Limits Panics. They are meant to be catchable only at task boundaries, and what a task boundary is has not been specified. Until it is, `.or_panic` raises a Crystal diff --git a/src/content/learn/generics-across-a-boundary.mdx b/src/content/learn/generics-across-a-boundary.mdx index 1986bad..41cdfed 100644 --- a/src/content/learn/generics-across-a-boundary.mdx +++ b/src/content/learn/generics-across-a-boundary.mdx @@ -1,13 +1,13 @@ --- title: A generic that crosses a boundary order: 6 -summary: Inside a module a generic monomorphises. Crossing a module boundary it is supposed to pass a dictionary keyed on GC shape, and that half of the rule has nothing behind it yet, so the artifact carries the body and the consumer compiles it again. +summary: Inside a module a generic monomorphizes. Crossing a module boundary it is supposed to pass a dictionary keyed on GC shape, and that half of the rule has nothing behind it yet, so the artifact carries the body and the consumer compiles it again. rule: R-4 sample: samples/iyi/immutable.iyi breaks: r4-monomorphize-unknown bought: >- What the rule has bought so far is the shape that makes a dictionary possible - at all: a bound is a trait and nothing else, there are no specialised impls + at all: a bound is a trait and nothing else, there are no specialized impls and no blanket impls, so a generic type's method set is knowable without knowing its parameter, which is what one compiled body serving many types requires. What it has not bought is the dictionary, and @@ -19,7 +19,7 @@ sources: - file: bench/generic_boundary.py note: R-4 measured, what travels in an artifact by section, and the two structural facts it asserts about a module with no generic and a module that is all generics - file: samples/iyi/generics.iyi - note: the impls over generic types, and the sample's own note that this monomorphises today + note: the impls over generic types, and the sample's own note that this monomorphizes today - file: SPEC.md note: II.5 for why a dictionary needs a precise heap layout, and III.9 for what stands in for R-4 today --- @@ -30,11 +30,11 @@ Step four gave a generic type a trait, and step five typed a generic body against its bound. Neither asked what the compiler emits. This step does, and it is the one step in this path whose rule is mostly a plan. -Read the premise above as two sentences with a boundary between them. Inside a -module, monomorphisation: a body per set of types, which is what Crystal does -and what every C++ template does. Across a module boundary, a dictionary keyed -on GC shape: one compiled body, serving every type whose values are laid out -the same way, with the operations it needs passed in. +The rule draws a boundary. Inside a module, monomorphization: a body per set +of types, which is what Crystal does and what every C++ template does. Across +a module boundary, a dictionary keyed on GC shape: one compiled body, serving +every type whose values are laid out the same way, with the operations it needs +passed in. ## The boundary, in a program @@ -54,7 +54,7 @@ turn up. That is R-1 applied to a generic, and it is also what stands in for R-4's first sentence today: the body travels in the artifact and the consumer compiles it -again, for the types it uses. Monomorphisation, across the boundary the rule +again, for the types it uses. Monomorphization, across the boundary the rule says a dictionary crosses. The round trip in the same file is the proof that nothing else is happening. @@ -75,7 +75,7 @@ away, which is step one's contract holding at its hardest point. A dictionary is one compiled body serving many types. That is only possible if a generic type's method set can be known without knowing its parameter, and three refusals in step four are what keep that true: a bound is a trait and -nothing else, a specialised impl is refused, and a blanket impl is refused. +nothing else, a specialized impl is refused, and a blanket impl is refused. Every one of them reads as a restriction on the programmer and is a prerequisite for the compiled body. @@ -91,7 +91,7 @@ leaving a reader to assume the better answer. to="way Crystal does." /> -## Why the dictionary is not a free afternoon +## What the dictionary requires A body shared by several types still has to tell the collector which words in a value are pointers, so the pointer map has to travel with the shape. That is @@ -110,10 +110,10 @@ every body and no object code, and it holds the corpus wide share of bodies under a line set with headroom. A tripwire rather than a target: what the dictionary would take out of a consumer's build, kept honest on every push. -## Ask for the clause that is not there +## Forced monomorphization R-4's third sentence names an annotation, `@[Monomorphize]`, for forcing -specialisation across a boundary. It is the part of the rule with nothing +specialization across a boundary. It is the part of the rule with nothing behind it at all, and the compiler does not pretend otherwise. Write it and -the answer is not a warning, a silent acceptance, or a slower build. It is a -name the compiler has never heard of. +the compiler rejects the name as unknown rather than issuing a warning, +accepting it silently, or falling back to a slower build. diff --git a/src/content/learn/import-and-using.mdx b/src/content/learn/import-and-using.mdx index 1e23c36..2b5ff13 100644 --- a/src/content/learn/import-and-using.mdx +++ b/src/content/learn/import-and-using.mdx @@ -29,7 +29,7 @@ import Quote from "../../components/Quote.astro"; Two steps in, the library from step one has a shape: three definitions, two of them exported, all of their types written down. Bringing that into a file of your -own takes two directives, and they are not the same one written twice. +own takes two distinct directives. -A type gets that trait by having an impl. Not by inheriting, not by including, not -by happening to have a method with the right name. +A type gets that trait by having an explicit impl rather than by inheriting, +including, or happening to have a method with the right name. @@ -68,7 +67,7 @@ same impl and discover it at link time. Coherence checked locally, with no global pass. That is the whole trade, and it is what an artifact needs. -Two doors that look open are not. `include Greet` inside a struct does not work, +Two familiar shortcuts are closed. `include Greet` inside a struct does not work, because a type acquires a trait by having an impl whose location R-3 can check, and `include` has no such rule. `using` does not reach a trait's methods either: a trait exports no names, because a trait method resolves from the receiver @@ -78,7 +77,7 @@ There is a nice side effect. Leave a requirement unmet and the error lands where you wrote the impl, not somewhere downstream where a caller trips over it. The module that declares the impl is the module that has to be right. -## forall, and why it is not noise +## Binding parameters with forall An impl over a generic type has to say which names are parameters. @@ -109,7 +108,7 @@ has never heard of your types. README.md is unkind and accurate about the comparison: Kemal accepts the same block and serves an empty body, on every request, forever. -## What this does not do yet +## Limits A bound on an impl is a different mechanism and it is not implemented. `impl Show for Box(T) forall T : Show` parses and is rejected. A bound there makes @@ -118,7 +117,7 @@ which has to be checked where the type is instantiated rather than where the imp is written. The bound on `announce` above works precisely because nothing about it is conditional. -Two more are refused on purpose rather than for want of effort. A specialised +Two more are refused on purpose rather than for want of effort. A specialized impl, `impl Show for Box(Int32)`, needs a rule for which impl wins that stays sound when the two live in separately compiled modules. A blanket impl, `impl Show for T forall T : Show`, is refused for the same reason open classes diff --git a/src/content/learn/typed-at-definition.mdx b/src/content/learn/typed-at-definition.mdx index 42e7f3a..6568c9c 100644 --- a/src/content/learn/typed-at-definition.mdx +++ b/src/content/learn/typed-at-definition.mdx @@ -1,7 +1,7 @@ --- title: Typed where it is written order: 5 -summary: A def that writes all of its types is typed at its definition, caller or no caller. A trait-restricted parameter is typed against exactly its bound, so a generic body cannot quietly use what it did not declare. +summary: A def that writes all of its types is typed at its definition, caller or no caller. A trait-restricted parameter is typed against exactly its bound, so a generic body cannot use what it did not declare. rule: R-2c sample: samples/iyi/collections.iyi breaks: r2c-outside-bound @@ -20,7 +20,7 @@ sources: - file: samples/iyi/calc/lexer.iyi note: an exported signature whose callers all live in other modules - file: SPEC.md - note: R-2c, including its own list of what it does not reach yet + note: R-2c, including its own list of limits --- import Sample from "../../components/Sample.astro"; @@ -65,15 +65,15 @@ yet, and there is no call to learn them from. Duck typed generics answer by never checking, and finding out in whichever caller compiled first. Rust answers by checking always, against the bound. -iyi answers the way Rust does, and the compiler's own account of how is worth -reading: for each trait-restricted parameter it synthesizes one witness type, -implements the trait's abstract requirements with stub bodies, and types the -body against the witness. So the body is checked against exactly what the -signature declared, and against nothing the program happens to contain. +iyi answers the way Rust does: for each trait-restricted parameter it +synthesizes one witness type, implements the trait's abstract requirements with +stub bodies, and types the body against the witness. So the body is checked +against exactly what the signature declared, and against nothing the program +happens to contain. That is why the rejection at the foot of this page names a type nobody wrote. -## What it does not reach yet +## Limits The rule states its own limits, and this step's program is one of them. diff --git a/src/content/learn/written-types.mdx b/src/content/learn/written-types.mdx index 7b904c2..f7bfa91 100644 --- a/src/content/learn/written-types.mdx +++ b/src/content/learn/written-types.mdx @@ -44,17 +44,17 @@ exported, and the file says what that costs. must="def greeting : String" /> -Read `greeting` again. It is not merely awkward to reach from another file, it is -unreachable, and the sample says why in the comment above it. A `.iyimod` +Read `greeting` again: it is unreachable from another file rather than merely +awkward to access, and the sample says why in the comment above it. A `.iyimod` artifact carries a module's exports and nothing else. So a name without `pub` has to be genuinely unreachable, or that artifact would not be enough to compile against. Qualifying it does not help. `App::Greeter.greeting` fails for the same reason a `using` cannot find it: it is not there to be found. Notice what just happened to step one. "Unreachable rather than absent from -`using`" and "not in the artifact" turned out to be the same sentence. Visibility -in iyi is not advice to the reader. It is the shape of a file the compiler will -later read instead of your source. +`using`" and "not in the artifact" turned out to be the same sentence. +Visibility in iyi is the shape of a file the compiler will later read instead +of your source. ## And what those names say @@ -82,13 +82,13 @@ is the same list of signatures with the bodies gone. ## Break it three ways An exported def with no return type looks like an oversight the compiler could -just fix, since the body is right there. It cannot, and the reason is worth more -than the rule: the body stays in this module, so the consumer never sees it. The -compiler says which symbol it would end up asking the linker for. +just fix, since the body is right there. It cannot: the body stays in this +module, so the consumer never sees it. The compiler says which symbol it would +end up asking the linker for. Then the other two ways to get this rule wrong, recorded the same way. Reach from another module for a name that carries no `pub`, which is the first half -of the rule refusing to be worked around. And write a body that does not honour +of the rule refusing to be worked around. And write a body that does not honor the return type above it, which is the second half: a written type is a promise to everyone compiling against the artifact, so the body is the thing that has to give. diff --git a/src/layouts/Page.astro b/src/layouts/Page.astro index 615e375..bf1574e 100644 --- a/src/layouts/Page.astro +++ b/src/layouts/Page.astro @@ -253,7 +253,7 @@ const current = (path: string) => here.startsWith(href(path)); d="M8 1.1V3.4M8 12.6V14.9M1.1 8H3.4M12.6 8H14.9M3.12 3.12l1.63 1.63M11.25 11.25l1.63 1.63M3.12 12.88l1.63-1.63M11.25 4.75l1.63-1.63" /> - Switch the colour scheme + Switch the color scheme