tala-compiler-0.1.0: test/ConsoleTests.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
{- | Console output, strings and the output format grammar.
@Spec\/StandardLibrary\/IO\/ConsoleIO.tala@ specifies the console. A program
writes with @Console.Print@, @Console.Println@, @Console.Printf@ and
@Console.Printfn@, to standard error with @Console.Error@ and its three
siblings, and builds a string with @Console.Format@. A format is a
compile-time string, checked against its arguments when the program is
compiled: a conversion that does not exist, a flag a conversion does not
take, a missing argument and an argument of the wrong type are errors of the
program and not of a run.
Every program here is run in the reference Core evaluator, on the Core the
frontend lowered and on the Core the optimizer left, and what it wrote is
compared with a string written by hand. The line terminator of the
reference is a line feed.
Output is an effect. The second half of this module holds what follows from
that under evaluation by need: an expression that writes is evaluated where
it stands and in the order it is written, whether or not its value is ever
needed, while a value that only computes is still computed by need.
-}
module ConsoleTests (consoleTests) where
import CoreInterpreter
import Data.List (isInfixOf)
import Progmasoft.Tala.Compiler
import Progmasoft.Tala.Core
import Progmasoft.Tala.Core.CorePrep.Verifier
import Progmasoft.Tala.Core.Verifier
import Progmasoft.Tala.Diagnostic
consoleTests :: [(String, Bool)]
consoleTests =
concat
[ [ ("unoptimized Core writes " ++ show expected ++ " for " ++ body, written artifactCore body == Just (expected, ""))
, ("optimized Core writes " ++ show expected ++ " for " ++ body, written artifactOptimizedCore body == Just (expected, ""))
, ("every stage verifies " ++ body, verifies body)
]
| (body, expected) <- outputs
]
++ [ ("standard error receives " ++ show expected ++ " for " ++ body, written artifactCore body == Just ("", expected))
| (body, expected) <- errors
]
++ [ ("rejected with " ++ code ++ ": " ++ body, rejectedWith code body)
| (body, code) <- rejections
]
++ [ ("both streams keep their own text", written artifactCore mixed == Just ("ac", "b\n"))
, ("the optimizer keeps both streams", written artifactOptimizedCore mixed == Just ("ac", "b\n"))
, -- A program that writes nothing has no runtime call in it.
("a program without output has no runtime call", not (mentions "CoreRuntimeCall" artifactCore "int x = 1;"))
, ("a write is a runtime call", mentions "CoreRuntimeCall" artifactCore "Console.Print(\"a\");")
-- The equality primitive on two strings would compare objects.
, ("a string pattern of is is not the equality primitive", not (mentions "CoreEqual" artifactCore "Console.Println(Name() is \"x\");"))
, ("a string pattern of match is not the equality primitive", not (mentions "CoreEqual" artifactCore "Console.Println(match (Name()) { \"x\" -> 1, _ -> 2 });"))
, ("a string is not ordered by a pattern", rejectedWith "TAT0023" "bool b = Name() is > \"A\";")
, ("a string is not matched by a number in is", rejectedWith "TAT0021" "bool b = Name() is 5;")
, -- The optimizer may not drop a write whose result nothing uses:
-- the write is the point.
("the optimizer keeps a write", mentions "CoreRuntimeCall" artifactOptimizedCore "Console.Print(\"a\");")
, ("the optimizer keeps a write in a method nothing reads the result of", written artifactOptimizedCore "Log(4);" == Just ("4\n", ""))
, -- A value that only computes is still by need beside output.
("a value that is never needed is not computed beside output", written artifactCore unusedFailure == Just ("ok\n", ""))
, ("the same holds after optimization", written artifactOptimizedCore unusedFailure == Just ("ok\n", ""))
, -- A binding whose initializer writes has no flag: it is not deferred.
("a binding that writes is not deferred", not (mentions "$known" artifactCore "int x = Log(1); Console.Println(2);"))
, ("a binding that only computes is deferred as before", mentions "$known" artifactCore "int x = Half(8); if (Zero() > 0) { Console.Println(x); }")
, -- An argument that writes is not suspended; one that only
-- computes still is.
("an argument that writes is not suspended", not (mentions "CoreMemoize" artifactCore "Console.Println(Pick(0, Log(5)));"))
, ("an argument that only computes is suspended as before", mentions "CoreMemoize" artifactCore "Console.Println(Pick(0, 8 / Zero()));")
, -- A class of the program named Console is the program's own.
("a class named Console shadows the console", shadowed)
]
where
mixed = "Console.Print(\"a\"); Console.Errorln(\"b\"); Console.Print(\"c\");"
unusedFailure = "int z = 1 / Zero(); Console.Println(\"ok\");"
{- | Programs and what they write to standard output. The body is the body of
@Main@; the helpers are the methods of 'program'.
-}
outputs :: [(String, String)]
outputs =
-- Plain output. Print writes the text; Println ends the line after it.
[ ("Console.Print(\"a\"); Console.Print(\"b\");", "ab")
, ("Console.Println(\"Hello\");", "Hello\n")
, ("Console.Println(\"\");", "\n")
, ("Console.Print(\"\");", "")
, ("Console.Println(\"one\"); Console.Println(\"two\");", "one\ntwo\n")
, ("System.Console.Println(\"qualified\");", "qualified\n")
, -- A percent sign has no meaning outside a format.
("Console.Println(\"%d\");", "%d\n")
, ("Console.Println(\"100%\");", "100%\n")
, -- A value that is not a string is written as text.
("Console.Println(42);", "42\n")
, ("Console.Println(0);", "0\n")
, ("Console.Println(0 - 7);", "-7\n")
, ("Console.Println(true);", "true\n")
, ("Console.Println(false);", "false\n")
, ("Console.Println('x');", "x\n")
, ("Console.Println(Half(10));", "5\n")
, ("int n = 9223372036854775807; Console.Println(n);", "9223372036854775807\n")
, ("uint u = 18446744073709551615; Console.Println(u);", "18446744073709551615\n")
, ("byte small = 100; Console.Println(small);", "100\n")
, ("ushort medium = 65535; Console.Println(medium);", "65535\n")
, -- A string is a value: bound, passed, returned.
("String s = \"kept\"; Console.Println(s);", "kept\n")
, ("Console.Println(Name());", "Tala\n")
, ("Console.Println(Twice(\"ab\"));", "abab\n")
, -- + joins two strings, and writes a value that is not one as text.
("Console.Println(\"Hello from \" + \"Tala\" + \"!\");", "Hello from Tala!\n")
, ("String language = \"Tala\"; Console.Println(\"Hello from \" + language + \"!\");", "Hello from Tala!\n")
, ("Console.Println(\"n=\" + 5);", "n=5\n")
, ("Console.Println(5 + \"n\");", "5n\n")
, ("Console.Println(\"b=\" + true);", "b=true\n")
, ("Console.Println(\"c=\" + 'z');", "c=z\n")
, ("Console.Println(\"\" + (1 + 2));", "3\n")
, -- + associates to the left: the string takes the 1, then the 2.
("Console.Println(\"a\" + 1 + 2);", "a12\n")
, ("Console.Println(1 + 2 + \"a\");", "3a\n")
, ("uint u = 7; Console.Println(\"u=\" + u);", "u=7\n")
, ("Console.Println(\"\" + \"\");", "\n")
, ("String t = \"x\"; t += \"y\"; t += 3; Console.Println(t);", "xy3\n")
, ("String t = \"\"; for (int i = 0; i < 4; i += 1) { t += i; } Console.Println(t);", "0123\n")
, -- Strings are compared by what they hold.
("Console.Println(\"a\" == \"a\");", "true\n")
, ("Console.Println(\"a\" == \"b\");", "false\n")
, ("Console.Println(\"a\" \\= \"b\");", "true\n")
, ("Console.Println(\"a\" \\= \"a\");", "false\n")
, ("String a = \"ab\"; String b = \"a\" + \"b\"; Console.Println(a == b);", "true\n")
, ("String a = \"ab\"; String b = \"a\" + \"c\"; Console.Println(a == b);", "false\n")
, ("Console.Println(\"\" == \"\");", "true\n")
, ("Console.Println(\"a\" == \"ab\");", "false\n")
, ("if (Name() == \"Tala\") { Console.Println(\"same\"); } else { Console.Println(\"other\"); }", "same\n")
, -- Printf writes its format; Printfn ends the line after it.
("Console.Printf(\"plain\");", "plain")
, ("Console.Printfn(\"plain\");", "plain\n")
, ("Console.Printf(\"\");", "")
, ("Console.Printfn(\"\");", "\n")
, ("Console.Printf(\"%d\", 42);", "42")
, ("Console.Printfn(\"%d\", 42);", "42\n")
, ("Console.Printf(\"Count: %d\", 3);", "Count: 3")
, ("Console.Printf(\"%d and %d\", 1, 2);", "1 and 2")
, ("Console.Printfn(\"gcd(%d, %d) = %d\", 1071, 462, 21);", "gcd(1071, 462) = 21\n")
, -- %d: a signed integer in decimal.
("Console.Printf(\"%d\", 0 - 255);", "-255")
, ("Console.Printf(\"%5d|\", 42);", " 42|")
, ("Console.Printf(\"%-5d|\", 42);", "42 |")
, ("Console.Printf(\"%05d\", 42);", "00042")
, ("Console.Printf(\"%05d\", 0 - 42);", "-0042")
, ("Console.Printf(\"%08d\", 123);", "00000123")
, ("Console.Printf(\"%+d\", 42);", "+42")
, ("Console.Printf(\"%+d\", 0 - 42);", "-42")
, ("Console.Printf(\"% d\", 42);", " 42")
, ("Console.Printf(\"%2d\", 12345);", "12345")
, -- The ' flag groups the integer digits in threes with apostrophes.
("Console.Printf(\"%'d\", 1234567);", "1'234'567")
, ("Console.Printf(\"%'d\", 123);", "123")
, ("Console.Printf(\"%'d\", 1234);", "1'234")
, ("Console.Printf(\"%'d\", 0 - 1234567);", "-1'234'567")
, ("byte small = 0 - 100; Console.Printf(\"%d\", small);", "-100")
, -- %x: hexadecimal. A negative number is a minus sign and its
-- magnitude, never the bit pattern of its representation.
("Console.Printf(\"%x\", 255);", "ff")
, ("Console.Printf(\"%x\", 0 - 255);", "-ff")
, ("Console.Printf(\"%#x\", 255);", "0xff")
, ("Console.Printf(\"%#x\", 0 - 255);", "-0xff")
, ("Console.Printf(\"%08x\", 48879);", "0000beef")
, ("Console.Printf(\"%#08x\", 255);", "0x0000ff")
, ("uint u = 4294967295; Console.Printf(\"%x\", u);", "ffffffff")
, ("Console.Printf(\"%x\", 0);", "0")
, -- %u: an unsigned integer.
("uint u = 42; Console.Printf(\"%u\", u);", "42")
, ("Console.Printf(\"%u\", 42);", "42")
, ("ubyte tiny = 255; Console.Printf(\"%u\", tiny);", "255")
, ("uint u = 1234567; Console.Printf(\"%'u\", u);", "1'234'567")
, -- %s and %c.
("Console.Printf(\"%s\", \"abc\");", "abc")
, ("Console.Printf(\"%10s|\", \"abc\");", " abc|")
, ("Console.Printf(\"%-10s|\", \"abc\");", "abc |")
, ("Console.Printf(\"%.2s\", \"abcdef\");", "ab")
, ("Console.Printf(\"%.5s\", \"abc\");", "abc")
, ("Console.Printf(\"%5.1s|\", \"abc\");", " a|")
, ("Console.Printf(\"%s: %d\", \"total\", 7);", "total: 7")
, ("Console.Printf(\"%s\", Name());", "Tala")
, ("Console.Printf(\"%c\", 'q');", "q")
, ("Console.Printf(\"%3c|\", 'q');", " q|")
, ("Console.Printf(\"%-3c|\", 'q');", "q |")
, -- %b: a Boolean.
("Console.Printf(\"%b\", true);", "true")
, ("Console.Printf(\"%b\", 1 > 2);", "false")
, ("Console.Printf(\"%6b|\", false);", " false|")
, ("Console.Printf(\"%-6b|\", true);", "true |")
, -- %% is a percent sign and %n the line terminator; neither takes an
-- argument.
("Console.Printf(\"100%%\");", "100%")
, ("Console.Printf(\"%d%%\", 50);", "50%")
, ("Console.Printf(\"A%nB\");", "A\nB")
, ("Console.Printf(\"%n\");", "\n")
, -- %f: a fixed number of digits after the point, six unless a
-- precision says otherwise; a tie rounds to the even digit.
("Console.Printf(\"%f\", 12.5);", "12.500000")
, ("Console.Printf(\"%.2f\", 12.5);", "12.50")
, ("Console.Printf(\"%.0f\", 2.5);", "2")
, ("Console.Printf(\"%.0f\", 3.5);", "4")
, ("Console.Printf(\"%.1f\", 0.25);", "0.2")
, ("Console.Printf(\"%.1f\", 0.75);", "0.8")
, ("Console.Printf(\"%.3f\", 3.14159);", "3.142")
, ("Console.Printf(\"%10.3f|\", 3.14159);", " 3.142|")
, ("Console.Printf(\"%-10.3f|\", 3.14159);", "3.142 |")
, ("Console.Printf(\"%010.3f\", 3.14159);", "000003.142")
, ("Console.Printf(\"%+.1f\", 1.5);", "+1.5")
, ("Console.Printf(\"%'.2f\", 1234567.5);", "1'234'567.50")
, ("Console.Printf(\"%.2f\", 0.0);", "0.00")
, -- The digits are those of the binary number, not of the literal.
("Console.Printf(\"%.20f\", 0.1);", "0.10000000000000000555")
, ("Console.Printf(\"%.1f\", 1000000.0);", "1000000.0")
, ("float price = 19.99; Console.Printfn(\"Price: %.2f\", price);", "Price: 19.99\n")
, -- A width or a precision written as * is the int argument before
-- the value.
("Console.Printf(\"%*d|\", 5, 42);", " 42|")
, ("Console.Printf(\"%-*d|\", 5, 42);", "42 |")
, ("Console.Printf(\"%*s|\", 6, \"ab\");", " ab|")
, ("Console.Printf(\"%.*f\", 2, 3.14159);", "3.14")
, ("Console.Printf(\"%*.*f|\", 8, 2, 3.14159);", " 3.14|")
, ("int width = 4; Console.Printf(\"%*d|\", width, 7);", " 7|")
, -- Console.Format applies a format and writes nothing.
("String t = Console.Format(\"%d-%d\", 1, 2); Console.Print(t);", "1-2")
, ("String t = Console.Format(\"Name: %s, Age: %d\", \"Ada\", 36); Console.Println(t);", "Name: Ada, Age: 36\n")
, ("String t = Console.Format(\"plain\"); Console.Print(t + \"!\");", "plain!")
, ("Console.Print(Console.Format(\"%05d\", 42) + Console.Format(\"%x\", 255));", "00042ff")
, ("String t = Console.Format(\"%d\", 1); Console.Println(\"x\");", "x\n")
, -- Output in loops, branches and methods.
("for (int i = 0; i < 3; i += 1) { Console.Print(i); }", "012")
, ("for (int i = 1; i <= 3; i += 1) { Console.Printfn(\"%d squared is %d\", i, i * i); }", "1 squared is 1\n2 squared is 4\n3 squared is 9\n")
, ("if (Half(4) == 2) { Console.Println(\"yes\"); } else { Console.Println(\"no\"); }", "yes\n")
, ("Greet(\"Ada\"); Greet(\"Alan\");", "Hello, Ada!\nHello, Alan!\n")
, ("Console.Println(Fizz(3)); Console.Println(Fizz(5)); Console.Println(Fizz(15)); Console.Println(Fizz(7));", "Fizz\nBuzz\nFizzBuzz\n7\n")
, -- Output is an effect: it happens where it is written and in the
-- order it is written, whether or not a value is needed.
("int x = Log(1); Console.Println(2);", "1\n2\n")
, ("int x = Log(1); int y = Log(2); Console.Println(y + x);", "1\n2\n3\n")
, ("int x = Log(1) + Log(2);", "1\n2\n")
, ("Console.Println(Pick(0, Log(5)));", "5\n7\n")
, ("Console.Println(Pick(1, Log(5)));", "5\n5\n")
, ("Console.Printf(\"%d %d\", Log(1), Log(2));", "1\n2\n1 2")
, -- The width of a conversion is written before its value and is
-- evaluated before it.
("Console.Printf(\"%*d\", Log(3), Log(4));", "3\n4\n 4")
, ("Console.Println(\"a\" + Log(1) + Log(2));", "1\n2\na12\n")
, ("_ = Log(8);", "8\n")
, ("Log(8);", "8\n")
, ("int v = Zero() > 0 ? Log(1) : Log(2);", "2\n")
, ("bool both = Log(0) > 0 && Log(1) > 0;", "0\n")
, ("auto say = \\(int v) -> Log(v); int x = say(1); Console.Println(9);", "1\n9\n")
, ("int total = 0; for (int i = 0; i < 3; i += 1) { total += Log(i); } Console.Println(total);", "0\n1\n2\n3\n")
, ("int x = Twofold(3); Console.Println(\"after\");", "3\n3\nafter\n")
, -- A callable may write itself, and reads what it captured.
("auto say = \\(int v) -> Console.Println(v); say(4); say(5);", "4\n5\n")
, ("auto say = \\(String s) -> Console.Printfn(\"<%s>\", s); say(\"a\");", "<a>\n")
, ("int base = 3; auto say = \\(int v) -> { Console.Println(v + base); return v; }; int x = say(1); Console.Println(\"end\");", "4\nend\n")
, -- A value that only computes is still computed by need.
("int z = 1 / Zero(); Console.Println(\"ok\");", "ok\n")
, ("int z = 8 / Zero(); Console.Println(Pick(0, z));", "7\n")
, ("String s = \"x\"; int z = 8 / Zero(); Console.Println(s);", "x\n")
, -- Strings that callables take, capture, make and return.
("auto greet = \\(String who) -> \"Hi \" + who; Console.Println(greet(\"Ada\"));", "Hi Ada\n")
, ("String prefix = Name() + \": \"; auto label = \\(int n) -> prefix + n; Console.Println(label(1)); Console.Println(label(2));", "Tala: 1\nTala: 2\n")
, ("auto twice = \\(int v) -> { Console.Printf(\"%d,\", v); return v * 2; }; Console.Println(twice(twice(1)));", "1,2,4\n")
, ("auto make = \\(int n) -> Console.Format(\"<%03d>\", n); String all = \"\"; for (int i = 0; i < 3; i += 1) { all += make(i); } Console.Println(all);", "<000><001><002>\n")
, ("String kept = Twice(\"ab\"); auto never = \\(int n) -> kept + n; Console.Println(\"x\");", "x\n")
, -- A method is a callable value: it writes where its call stands.
("auto f = Log; int x = f(1); Console.Println(9);", "1\n9\n")
, -- Appending makes a new string; another name keeps the old one.
("String s = \"a\"; String t = s; s += \"b\"; Console.Println(s + t);", "aba\n")
, ("String s = \"\"; for (int i = 0; i < 5; i += 1) { String old = s; s += i; if (old == s) { s += \"!\"; } } Console.Println(s);", "01234\n")
, -- String operations in arguments that are and are not needed.
("Console.Println(Pick(1, Half(8)) + Name());", "4Tala\n")
, ("String made = Twice(Name() + \"!\"); Console.Println(Pick(0, 8 / Zero()));", "7\n")
, ("Console.Println(Console.Format(\"[%s]\", Console.Format(\"%5s\", Console.Format(\"%d\", 42))));", "[ 42]\n")
, ("Console.Println(Twice(Twice(\"ab\")) == \"abababab\");", "true\n")
, -- A conditional selects one of two strings; the other is never made.
("String s = Zero() > 0 ? \"a\" : \"b\"; Console.Println(s);", "b\n")
, ("Console.Println(Zero() == 0 ? Name() : Twice(\"x\"));", "Tala\n")
, ("String a = \"x\"; String b = \"y\"; String c = Zero() > 0 ? a : b; Console.Println(c + a + b);", "yxy\n")
, ("Console.Println(Zero() > 0 ? \"a\" : Zero() == 0 ? \"b\" : \"c\");", "b\n")
, ("String s = Zero() > 0 ? \"\" + Log(1) : \"\" + Log(2); Console.Println(s);", "2\n2\n")
, ("String s = \"\"; for (int i = 0; i < 4; i += 1) { s += i % 2 == 0 ? \"e\" : \"o\"; } Console.Println(s);", "eoeo\n")
, ("Console.Println(\"<\" + (Zero() == 0 ? \"yes\" : \"no\") + \">\");", "<yes>\n")
, ("Greet(Zero() == 0 ? Name() : \"nobody\");", "Hello, Tala!\n")
, ("String s = \"keep\"; s = Zero() > 0 ? \"lost\" : s; Console.Println(s);", "keep\n")
, -- A match, an if used as a value and a loop yield strings the same
-- way: one is selected, and the others are never made.
("String s = match (Zero()) { 0 -> \"zero\", 1 -> \"one\", _ -> \"many\" }; Console.Println(s);", "zero\n")
, ("String s = match (Half(8)) { 0 -> \"zero\", int n if n > 3 -> \"big \" + n, _ -> \"small\" }; Console.Println(s);", "big 4\n")
, ("String s = if (Zero() > 0) { \"pos\" } else { Name() + \"!\" }; Console.Println(s);", "Tala!\n")
, ("int i = 0; String s = while (true) { i += 1; if (i > 2) { break \"done\" + i; } }; Console.Println(s);", "done3\n")
, ("Console.Println(match (Zero()) { 0 -> Name(), _ -> \"x\" } + (if (Zero() == 0) { \"a\" } else { \"b\" }));", "Talaa\n")
, ("String s = \"k\"; s = match (Zero()) { 1 -> \"lost\", _ -> s }; Console.Println(s);", "k\n")
, ("String s = match (Zero()) { 0 -> \"\" + Log(1), _ -> \"\" + Log(2) }; Console.Println(s);", "1\n1\n")
, ("String s = \"\"; for (int i = 0; i < 4; i += 1) { s += match (i % 3) { 0 -> \"a\", 1 -> \"b\", _ -> \"c\" }; } Console.Println(s);", "abca\n")
, ("Greet(match (Zero()) { 0 -> Name(), _ -> \"nobody\" });", "Hello, Tala!\n")
, ("String s = match (Zero()) { 0 -> match (Half(2)) { 1 -> \"inner\", _ -> \"other\" }, _ -> \"outer\" }; Console.Println(s);", "inner\n")
, -- A string is a subject of match and of `is`. It is matched by the
-- characters it holds: none of these subjects is the object of the
-- literal it is compared with.
("Console.Println(match (Name()) { \"x\" -> 1, \"Tala\" -> 2, _ -> 3 });", "2\n")
, ("Console.Println(match (Twice(\"ab\")) { \"ab\" -> 1, \"abab\" -> 2, _ -> 3 });", "2\n")
, ("Console.Println(match (Fizz(15)) { \"Fizz\" -> 3, \"Buzz\" -> 5, \"FizzBuzz\" -> 15, _ -> 0 });", "15\n")
, ("Console.Println(match (Name()) { \"x\" -> \"no\", String other if other == Twice(\"a\") -> \"twice\", String other -> \"is \" + other });", "is Tala\n")
, ("match (Fizz(3)), (Fizz(5)) { (\"Fizz\"), (\"Buzz\") -> { Console.Println(\"both\"); }, _, _ -> { Console.Println(\"none\"); } }", "both\n")
, ("match (Fizz(3)), (Fizz(7)) { (\"Fizz\"), (\"Buzz\") -> { Console.Println(\"both\"); }, _, _ -> { Console.Println(\"none\"); } }", "none\n")
, ("int t = 0; for (int i = 1; i < 16; i += 1) { t += match (Fizz(i)) { \"Fizz\" -> 1, \"Buzz\" -> 10, \"FizzBuzz\" -> 100, _ -> 0 }; } Console.Println(t);", "124\n")
, ("Console.Println(Name() is \"Tala\");", "true\n")
, ("Console.Println(Name() is not \"Tala\");", "false\n")
, ("Console.Println(Twice(\"ab\") is \"ab\" or \"abab\");", "true\n")
, ("Console.Println(Twice(\"ab\") is \"ab\" or \"ba\");", "false\n")
, ("Console.Println(Name() is == \"Tala\"); Console.Println(Name() is \\= \"Tala\");", "true\nfalse\n")
, ("String s = Fizz(3); if (s is \"Fizz\" && Name() is not \"x\") { Console.Println(\"ok\"); }", "ok\n")
]
-- | Programs and what they write to standard error.
errors :: [(String, String)]
errors =
[ ("Console.Error(\"e\");", "e")
, ("Console.Errorln(\"failed\");", "failed\n")
, ("Console.Error(\"a\"); Console.Errorln(\"b\");", "ab\n")
, ("Console.Errorf(\"Code: %d\", 7);", "Code: 7")
, ("Console.Errorfn(\"Code: %d\", 7);", "Code: 7\n")
, ("Console.Errorln(404);", "404\n")
, ("System.Console.Errorln(\"q\");", "q\n")
]
-- | Programs the type checker rejects, with the code it reports.
rejections :: [(String, String)]
rejections =
-- The console has the methods the specification gives it.
[ ("Console.Shout(\"x\");", "TAT0071")
, ("Console.println(\"x\");", "TAT0071")
, ("System.Console.Nothing();", "TAT0071")
, -- Print takes one value.
("Console.Print();", "TAT0072")
, ("Console.Println();", "TAT0072")
, ("Console.Print(\"a\", \"b\");", "TAT0072")
, ("Console.Printf();", "TAT0072")
, ("Console.Format();", "TAT0072")
, -- What cannot be written as text.
("auto f = \\(int v) -> v; Console.Println(f);", "TAT0073")
, ("auto f = \\(int v) -> v; String s = \"a\" + f;", "TAT0073")
, -- A format is a string literal.
("String f = \"%d\"; Console.Printf(f, 1);", "TAT0074")
, ("Console.Printf(Name());", "TAT0074")
, ("Console.Printf(\"%d\" + \"%d\", 1, 2);", "TAT0074")
, ("Console.Format(42);", "TAT0074")
, -- A conversion that does not exist, or is not finished.
("Console.Printf(\"%q\", 1);", "TAT0075")
, ("Console.Printf(\"%\");", "TAT0075")
, ("Console.Printf(\"abc%\");", "TAT0075")
, ("Console.Printf(\"%5\");", "TAT0075")
, ("Console.Printf(\"%.d\", 1);", "TAT0075")
, ("Console.Printf(\"%D\", 1);", "TAT0075")
, ("Console.Printf(\"%i\", 1);", "TAT0075")
, ("Console.Printf(\"%e\", 1.5);", "TAT0075")
, ("Console.Printf(\"%1234567890d\", 1);", "TAT0075")
, -- A flag a conversion does not take, and flags that exclude each
-- other; examples 16 to 18 of the specification file.
("Console.Printf(\"%+s\", \"a\");", "TAT0076")
, ("Console.Printf(\"%0s\", \"a\");", "TAT0076")
, ("Console.Printf(\"%#d\", 1);", "TAT0076")
, ("Console.Printf(\"%'x\", 1);", "TAT0076")
, ("Console.Printf(\"%-08d\", 1);", "TAT0076")
, ("Console.Printf(\"%+ d\", 1);", "TAT0076")
, ("Console.Printf(\"%++d\", 1);", "TAT0076")
, ("Console.Printf(\"%+u\", 1);", "TAT0076")
, ("Console.Printf(\"% u\", 1);", "TAT0076")
, ("Console.Printf(\"%#s\", \"a\");", "TAT0076")
, ("Console.Printf(\"%'s\", \"a\");", "TAT0076")
, ("Console.Printf(\"%0c\", 'a');", "TAT0076")
, ("Console.Printf(\"%+b\", true);", "TAT0076")
, ("Console.Printf(\"%#f\", 1.5);", "TAT0076")
, ("Console.Printf(\"%.2d\", 1);", "TAT0076")
, ("Console.Printf(\"%.2x\", 1);", "TAT0076")
, ("Console.Printf(\"%.1c\", 'a');", "TAT0076")
, ("Console.Printf(\"%.1b\", true);", "TAT0076")
, ("Console.Printf(\"%5%\");", "TAT0076")
, ("Console.Printf(\"%-n\");", "TAT0076")
, -- The arguments are the ones the format names, no more and no fewer.
("Console.Printf(\"%d\");", "TAT0077")
, ("Console.Printf(\"%d\", 1, 2);", "TAT0077")
, ("Console.Printf(\"plain\", 1);", "TAT0077")
, ("Console.Printf(\"%d %d\", 1);", "TAT0077")
, ("Console.Printf(\"%*d\", 1);", "TAT0077")
, ("Console.Printf(\"%*.*f\", 1, 2);", "TAT0077")
, ("Console.Printf(\"%n\", 1);", "TAT0077")
, ("Console.Printf(\"%%\", 1);", "TAT0077")
, ("Console.Format(\"%s\");", "TAT0077")
, -- An argument has the type its conversion takes. Nothing is
-- converted for formatting: examples 11 and 12.
("uint value = 42; Console.Printf(\"%d\", value);", "TAT0078")
, ("int value = 42; Console.Printf(\"%u\", value);", "TAT0078")
, ("int value = 42; Console.Printf(\"%f\", value);", "TAT0078")
, ("float value = 1.5; Console.Printf(\"%d\", value);", "TAT0078")
, ("Console.Printf(\"%s\", 42);", "TAT0078")
, ("Console.Printf(\"%d\", \"text\");", "TAT0078")
, ("Console.Printf(\"%c\", 65);", "TAT0078")
, ("Console.Printf(\"%c\", \"a\");", "TAT0078")
, ("Console.Printf(\"%b\", 1);", "TAT0078")
, ("Console.Printf(\"%d\", true);", "TAT0078")
, ("Console.Printf(\"%x\", 1.5);", "TAT0078")
, ("Console.Printf(\"%x\", \"ff\");", "TAT0078")
, ("Console.Printf(\"%*d\", true, 1);", "TAT0078")
, ("Console.Printf(\"%*d\", \"5\", 1);", "TAT0078")
, ("uint width = 5; Console.Printf(\"%*d\", width, 1);", "TAT0078")
, ("Console.Printf(\"%.*f\", 1.5, 1.5);", "TAT0078")
, -- Specified, and not implemented yet.
("Console.Printf(\"%A\", 1);", "TAT0079")
, ("Console.Printf(\"%O\", 1);", "TAT0079")
, ("Console.Println(1.5);", "TAT0079")
, ("String s = \"x\" + 1.5;", "TAT0079")
, ("longint wide = 1; Console.Println(wide);", "TAT0079")
, ("longint wide = 1; Console.Printf(\"%d\", wide);", "TAT0079")
, ("ulongint wide = 1; Console.Printf(\"%u\", wide);", "TAT0079")
, ("double wide = 1.5; Console.Printf(\"%f\", wide);", "TAT0079")
, ("Console.Stdout();", "TAT0079")
, ("Console.Stdin();", "TAT0079")
, ("Console.Stderr();", "TAT0079")
, -- Strings have + and the two equalities, and no other operator.
("String s = \"a\" - \"b\";", "TAT0012")
, ("String s = \"a\" * 2;", "TAT0012")
, ("bool b = \"a\" < \"b\";", "TAT0012")
, -- An immutable string is not appended to.
("final String s = \"a\"; s += \"b\";", "TAT0003")
]
{- | A class of the program named @Console@ is found before the console of
the language: its method is called, and nothing is written.
-}
shadowed :: Bool
shadowed = case compileSource text of
Right artifacts ->
not (any (("CoreRuntimeCall" `isInfixOf`) . show . coreFunctionBody) (coreModuleFunctions (artifactCore artifacts)))
&& (runFunctionWriting 10000 (artifactCore artifacts) "Main" [] == Just (UnitValue, Written "" ""))
Left _ -> False
where
text =
unlines
[ "namespace Demo;"
, "public class Console {"
, " public static int Println(_ int value) { return value + 1; }"
, "}"
, "public class Program {"
, " public static void Main() { int kept = Console.Println(41); }"
, "}"
]
program :: String -> String
program body =
unlines
[ "namespace Demo;"
, "public class Program {"
, " public static int Zero() { return 0; }"
, " public static int Half(_ int v) { return v / 2; }"
, " public static int Pick(_ int flag, _ int value) { if (flag > 0) { return value; } return 7; }"
, " public static int Log(_ int v) { Console.Println(v); return v; }"
, " public static int Twofold(_ int v) { return Log(v) + Log(v); }"
, " public static String Name() { return \"Tala\"; }"
, " public static String Twice(_ String s) { return s + s; }"
, " public static void Greet(_ String who) { Console.Println(\"Hello, \" + who + \"!\"); }"
, " public static String Fizz(_ int n) {"
, " if (n % 15 == 0) { return \"FizzBuzz\"; }"
, " if (n % 3 == 0) { return \"Fizz\"; }"
, " if (n % 5 == 0) { return \"Buzz\"; }"
, " return \"\" + n;"
, " }"
, " public static void Main() {"
, " " ++ body
, " }"
, "}"
]
compileSource :: String -> Either [Diagnostic] FrontendArtifacts
compileSource text = compileToCorePrep (CompilerInput "console.tala" text)
-- | What @Main@ writes to standard output and to standard error.
written :: (FrontendArtifacts -> CoreModule) -> String -> Maybe (String, String)
written select body = case compileSource (program body) of
Right artifacts -> case runFunctionWriting 200000 (select artifacts) "Main" [] of
Just (_, Written output failure) -> Just (output, failure)
Nothing -> Nothing
Left _ -> Nothing
rejectedWith :: String -> String -> Bool
rejectedWith code body = case compileSource (program body) of
Left diagnostics -> any ((== code) . diagnosticCode) diagnostics
Right _ -> False
-- | Whether the Core of @Main@ mentions the given constructor or generated name.
mentions :: String -> (FrontendArtifacts -> CoreModule) -> String -> Bool
mentions needle select body = case compileSource (program body) of
Right artifacts ->
any
((needle `isInfixOf`) . show . coreFunctionBody)
[ function
| function <- coreModuleFunctions (select artifacts)
, "Main" `isInfixOf` show (coreFunctionName function)
]
Left _ -> False
verifies :: String -> Bool
verifies body = case compileSource (program body) of
Right artifacts ->
verifyCore (artifactCore artifacts) == Right (artifactCore artifacts)
&& verifyCore (artifactOptimizedCore artifacts) == Right (artifactOptimizedCore artifacts)
&& verifyCorePrep (artifactCorePrep artifacts) == Right (artifactCorePrep artifacts)
Left _ -> False