Lua to Perl#
You know Lua. You build a table without thinking, you reach for setmetatable to get objects and operators, and for k, v in pairs(t) is muscle memory. This guide maps each of those habits onto Perl, so you can be productive in pperl in an afternoon instead of relearning programming from scratch.
Two shocks land in the first five minutes, so meet them now:
Lua’s one table becomes two Perl types. Lua has a single aggregate, the table, that you use as both an array (
{1, 2, 3}) and a map ({k = "v"}). Perl splits that into two distinct things: an ordered@arraykeyed by integers and a%hashkeyed by strings. Deciding “is this a list or a lookup?” up front is the central new habit, and the Arrays and Hashes sections are about nothing else.Variables wear sigils. Lua names are bare:
x,t,f. In Perl a scalar is$x, an array is@x, a hash is%x. The sigil is part of how you read the variable, not just decoration. This is the single biggest surface difference from Lua, and section one is about nothing else.
Every example here runs on pperl, a Rust reimplementation of Perl 5.42. Each Perl snippet was executed on pperl; the # ... comments show its real output. Start your scripts with:
use v5.42;
That one line turns on strict (every variable must be declared with my, the cure for Lua’s accident-prone implicit globals), warnings, the say built-in (a print with a trailing newline, like Lua’s print), subroutine signatures, and the modern feature set used throughout this guide.
How to read this#
Variables and sigils - the
$/@/%shift, andlocal xbecomingmy $x.Strings - interpolation, and
..concatenation becoming..Numbers - one surface type, float division.
Arrays and lists - half of your table, 0-based not 1-based.
Hashes - the other half of your table.
Context - the concept Lua has no word for. Read this one.
Truthiness - what counts as false, and it differs sharply from Lua.
Control flow -
if, loops, the postfix forms.Subroutines -
functionbecomessub; real closures.References - nested data and closures.
Regular expressions - real regex, not Lua patterns.
File and stream I/O -
open, line reads, slurp.Modules and packages -
requirebecomesuse.Objects - metatables become the modern
classsyntax.Error handling -
pcallbecomesevalortry/catch.Idioms - the daily one-liners.
Gotchas coming from Lua - scan before you write.
Variables and sigils#
In Lua a name just names a value, whatever its shape. In Perl the sigil tells you the shape: $ for a single value, @ for an ordered list, % for a key/value map. my declares a lexically scoped variable, the exact role of Lua’s local. Where Lua lets a bare assignment silently create a global, use v5.42 turns that into a compile error: every variable is my unless you say otherwise.
local name = "Ada"
local langs = {"Lua", "Perl"}
local ages = {Ada = 36, Linus = 56}
use v5.42;
my $name = "Ada";
my @langs = ("Lua", "Perl");
my %ages = (Ada => 36, Linus => 56);
say $name; # Ada
say $langs[0]; # Lua
say $ages{Ada}; # 36
The gotcha: when you reach into an aggregate for one element, the sigil follows the value you get back, not the container. A single element of @langs is a scalar, so it is $langs[0], not @langs[0]; one value from %ages is $ages{Ada}. The @/% form is the whole collection; the $ form is one piece of it. In Lua the table name t never changes whether you write t, t[1], or t.key; in Perl the leading mark changes with what you are accessing.
Strings#
Double quotes interpolate variables, single quotes do not. There is no string.format reflex needed for the common case, because the default string already interpolates. The big swap: concatenation is ., where Lua uses ...
local name = "Ada"
local greeting = "Hello, " .. name
local shout = string.rep("AB", 3)
use v5.42;
my $name = "Ada";
my $greeting = "Hello, $name"; # Hello, Ada
my $literal = 'Hello, $name'; # Hello, $name (no interpolation)
my $joined = "AB" . "CD"; # ABCD
my $shout = "AB" x 3; # ABABAB
The gotcha: Lua’s concatenation operator .. does not exist in Perl; the single dot . joins strings and the x operator repeats them (Lua’s string.rep). Array elements and hash values interpolate inside double quotes too, so "First: $langs[0]" works as written, which removes most of the .. chaining a Lua developer reaches for.
Numbers#
Perl has one numeric type at the surface; you do not pick between integer and float, much as Lua 5.3+ presents a single number to most code. Division always produces a float.
print(7 / 2) -- 3.5
print(math.floor(7 / 2)) -- 3
print(2 ^ 10) -- 1024.0
print(10 % 3) -- 1
use v5.42;
say 7 / 2; # 3.5
say int(7 / 2); # 3
say 2 ** 10; # 1024
say 10 % 3; # 1
The gotcha: comparison and equality operators come in two families, and you pick by the operands’ type, not the variables’. Numbers use ==, !=, <, >, <=>; strings use eq, ne, lt, gt, cmp. "10" == "10.0" is true (numeric), but "10" eq "10.0" is false (string). Lua has only == and coerces nothing across the string/number line; Perl instead asks you to choose the comparison, and using == on strings (or eq on numbers) is a frequent quiet bug.
Arrays and lists#
Here is half of the great table split. A Lua sequence ({10, 20, 30}, the table used as an array) becomes a Perl @array. The first and loudest difference: Perl arrays are 0-based, where Lua arrays conventionally start at index 1.
local t = {"x", "y", "z"}
print(t[1]) -- x (Lua: first element is index 1)
print(t[3]) -- z
print(#t) -- 3 (length)
use v5.42;
my @t = ("x", "y", "z");
say $t[0]; # x (Perl: first element is index 0)
say $t[2]; # z
say $t[-1]; # z (negative indices count from the end)
say scalar @t; # 3 (length; @t in scalar context is its size)
The mutators are renamed but familiar. table.insert/table.remove become push/pop/shift/unshift:
use v5.42;
my @nums = (10, 20, 30);
push @nums, 40; # add to the end
my $last = pop @nums; # 40, removed from the end
say scalar @nums; # 3
say "@nums[0,1]"; # 10 20 (a slice)
Lua reaches for explicit for i = 1, #t do ... end loops to transform or filter; Perl has map (transform) and grep (filter), with $_ as the current element:
use v5.42;
my @nums = (1, 2, 3, 4, 5);
my @squares = map { $_ * $_ } @nums; # 1 4 9 16 25
my @evens = grep { $_ % 2 == 0 } @nums; # 2 4
my @desc = sort { $b <=> $a } @nums; # 5 4 3 2 1
The gotcha: beyond 0-based indexing, #t has no single Perl spelling - you use scalar @a for an array’s length and keys %h for a hash’s. And sort compares as strings by default, so sort (10, 9, 100) gives 10 100 9. For numbers you must pass a comparator: sort { $a <=> $b } @nums. $a and $b are the two elements being compared, and <=> is the numeric three-way operator (cmp is its string sibling).
Hashes#
Here is the other half of the table split. A Lua table used as a map ({name = "Ada", age = 36}, or t["key"]) becomes a Perl %hash. Declared with %, accessed one value at a time with $h{key}.
local h = {apple = 3, pear = 5, plum = 2}
print(h.apple) -- 3
print(h["pear"]) -- 5
h.plum = nil -- delete a key
for k, v in pairs(h) do
print(k, v)
end
use v5.42;
my %h = (apple => 3, pear => 5, plum => 2);
say $h{apple}; # 3
say join ",", sort keys %h; # apple,pear,plum
say exists $h{pear} ? "yes" : "no"; # yes
delete $h{plum};
for my $k (sort keys %h) {
say "$k => $h{$k}"; # apple => 3 ... pear => 5
}
The gotcha: Lua merges the array and the map into one table and keeps both an array part and a hash part inside it; Perl keeps them strictly apart, so you decide up front whether a thing is an @array (integer positions, ordered) or a %hash (string keys, unordered). Membership is exists $h{key}, the counterpart of Lua’s h[k] ~= nil test, and the fat comma => is the idiomatic key/value separator that auto-quotes the bareword on its left (so apple => 3 needs no quotes). A hash has no order, so iterate sort keys %h when you want a stable one; Lua’s pairs is unordered for the same reason.
Context#
This is the chapter to read twice. Lua has no general equivalent, and almost every “Perl is weird” story traces back to it. But Lua does give you the one bridge that makes it click: multiple return values.
In Lua, local a, b = f() spreads a function’s several return values across several variables, and #t or select("#", ...) collapses a list to a count. Perl generalises that idea into a property of every expression. Every expression runs in either list context or scalar context, decided by where it appears. The same array behaves differently in each: assigned to a scalar it gives its length, assigned to a list (parenthesised variables) it copies elements.
use v5.42;
my @list = (10, 20, 30);
my $count = @list; # scalar context -> 3 (the length)
say $count; # 3
my ($first) = @list; # list context -> first element copied
say $first; # 10
my ($a, $b) = @list; # 10 and 20
say "$a $b"; # 10 20
The parentheses on the left are the whole difference. my $x = @list asks “how many?” and gets 3. my ($x) = @list asks “give me the elements” and gets 10. The list form my ($a, $b) = @list is exactly Lua’s local a, b = ... - the bridge you already know.
A subroutine returning several values is the same as a Lua function with multiple return values; the caller’s context decides what comes back:
use v5.42;
sub minmax (@nums) {
my @s = sort { $a <=> $b } @nums;
return ($s[0], $s[-1]);
}
my ($lo, $hi) = minmax(4, 1, 9, 2);
say "$lo $hi"; # 1 9
A sub can even ask which context it was called in, with wantarray, and answer differently - something Lua functions cannot do:
use v5.42;
sub items {
return wantarray ? (1, 2, 3) : "three items";
}
my @got = items(); # list context
my $msg = items(); # scalar context
say "@got"; # 1 2 3
say $msg; # three items
The gotcha: there is no single answer to “what does this array do” - it depends on the surrounding expression, where Lua resolves multiple-value spreading at the specific call site only. When something returns a number you expected to be a list (or vice versa), context is the first suspect. Reach for scalar to force a count and parentheses on the left to force element copying.
Truthiness#
This is a sharp break from Lua, so slow down. In Lua only nil and false are falsy - the number 0 and the empty string "" are both true. Perl draws the line in a completely different place. Its false values are: the number 0, the empty string "", the string "0", and undef (Perl’s nil). Everything else is true.
use v5.42;
for my $v (0, 1, "", "0", "00", "0.0", "false") {
say "'$v' is ", ($v ? "true" : "false");
}
This prints, exactly:
'0' is false
'1' is true
'' is false
'0' is false
'00' is true
'0.0' is true
'false' is true
The gotcha: a Lua developer’s instinct that 0 is true will silently flip the meaning of every if ($count). In Perl 0 and "" are false, so if ($n) is false when $n is zero - usually what you want, but the opposite of Lua. Going the other way, "00" and "0.0" are true even though they look numerically zero, because only the exact string "0" is false. For a “use this if defined, else a default” choice - Lua’s x = y or default idiom, which trips on 0 and false - use Perl’s defined-or //, which falls back only on undef (Perl’s nil):
use v5.42;
my %cfg = (host => "localhost");
my $port = $cfg{port} // 8080; # key absent -> undef -> 8080
say $port; # 8080
Control flow#
The keywords are familiar; the spelling differs. Lua’s elseif is Perl’s elsif, there is no then/do/end, and blocks are braced rather than keyword-delimited.
if n < 0 then
sign = "neg"
elseif n == 0 then
sign = "zero"
else
sign = "pos"
end
for _, x in ipairs(nums) do
print(x)
end
while running do
step()
end
use v5.42;
my $sign;
if ($n < 0) { $sign = "neg" }
elsif ($n == 0) { $sign = "zero" }
else { $sign = "pos" }
for my $x (@nums) { say $x }
while ($running) { step() }
Perl adds two things Lua does not have. Postfix modifiers let a simple statement carry its own condition or loop, which reads naturally for one-liners:
use v5.42;
say $_ for @nums; # loop, one statement
say "negative" if $n < 0; # guarded statement
$sum += $_ for @nums; # accumulate
And unless/until are the negated forms of if/while, for when the positive phrasing reads awkwardly: say "ok" unless $error.
The gotcha: Perl has no x if c else y statement; the conditional expression is the C-style ternary with ?/:, which Lua usually fakes with c and x or y. The Perl form is direct and chains: my $sign = $n < 0 ? "neg" : $n == 0 ? "zero" : "pos". Loop control is last/next where Lua has break and (in 5.4) goto continue.
Subroutines#
function becomes sub. Modern Perl has signatures that name their parameters, including defaults, rather than Lua’s positional ... and manual arg handling.
local function greet(name, greeting)
greeting = greeting or "Hello"
return greeting .. ", " .. name .. "!"
end
local function total(...)
local sum = 0
for _, n in ipairs({...}) do sum = sum + n end
return sum
end
use v5.42;
sub greet ($name, $greeting = "Hello") {
return "$greeting, $name!";
}
say greet("Ada"); # Hello, Ada!
say greet("Linus", "Hi"); # Hi, Linus!
sub total (@nums) {
my $sum = 0;
$sum += $_ for @nums;
return $sum;
}
say total(1, 2, 3, 4); # 10
Lua’s named-argument idiom (passing a single table, f{host=..., port=...}) maps to a hash parameter, since key => value pairs flatten into a list that a %opts signature gathers up:
use v5.42;
sub connect_to (%opts) {
return "host=$opts{host} port=$opts{port}";
}
say connect_to(host => "localhost", port => 8080);
# host=localhost port=8080
The gotcha: a sub returns the value of its last expression even without return (like Lua’s explicit-only return, except Perl also has the implicit last-value form), and the return is context-sensitive (see Context). If you return @list, the caller gets the list in list context but the count in scalar context - a degree of freedom Lua’s multiple-return does not have.
References#
Perl’s arrays and hashes do not nest directly; you nest them through references, a scalar that points at an aggregate. In Lua every table is already a reference (assigning a table shares it, never copies), so this concept is invisible to you there; in Perl you take a reference explicitly with \, and follow it with ->.
local data = {name = "Ada", langs = {"Perl", "Lua"}}
print(data.langs[2]) -- Lua
table.insert(data.langs, "Ruby")
use v5.42;
my $data = { name => "Ada", langs => ["Perl", "Lua"] };
say $data->{name}; # Ada
say $data->{langs}[1]; # Lua
push $data->{langs}->@*, "Ruby"; # postfix deref to push
say "@{$data->{langs}}"; # Perl Lua Ruby
{ ... } builds an anonymous hash reference and [ ... ] an anonymous array reference - the literal building blocks of nested data. The -> arrow follows a reference one step; between two subscripts it is optional, so $data->{langs}[1] and $data->{langs}->[1] are the same.
Anonymous subs are real closures, exactly like Lua’s closures over upvalues - one of the cleanest carry-overs in the whole guide:
use v5.42;
sub counter ($start) {
my $n = $start;
return sub { return $n++ };
}
my $c = counter(10);
say $c->(); # 10
say $c->(); # 11
say $c->(); # 12
The gotcha: the postfix deref forms (->@*, ->%*, ->$*) are the modern, readable way to dereference a whole aggregate, and "@{ ... }" is how you interpolate a dereferenced array into a string. The older @$ref and ${$ref}{key} forms still work and appear in existing code, but reach for the arrow and postfix forms in new code.
Regular expressions#
This is an upgrade, not a translation. Lua has patterns - string.match, string.gsub, character classes like %d - which are deliberately not regular expressions and lack alternation, quant, and backreferences. Perl has full regex as syntax: =~ binds a pattern to a string, m// matches, s/// substitutes. Capture groups land in $1, $2, and named captures in %+.
local s = "2026-06-20"
local y, m, d = string.match(s, "(%d+)-(%d+)-(%d+)")
local t = string.gsub("a,b,c", ",", ";") -- a;b;c
use v5.42;
my $s = "2026-06-20";
if ($s =~ /(\d{4})-(\d{2})-(\d{2})/) {
say "$1 $2 $3"; # 2026 06 20
}
if ($s =~ /(?<year>\d{4})/) {
say $+{year}; # 2026
}
(my $t = "a,b,c") =~ s/,/;/g; # a;b;c
my @parts = split /,/, "one,two,three"; # one two three
A /g match in list context returns all the matches, the direct analogue of looping string.gmatch:
use v5.42;
my @nums = "x1y2z3" =~ /(\d)/g; # 1 2 3
say "@nums";
The gotcha: the metacharacters differ from Lua patterns - Perl uses \d where Lua uses %d, */+/? quantifiers, | alternation, and (?<name>...) named groups, none of which Lua patterns support. And s/// modifies its target in place and returns the number of substitutions, not a new string, where Lua’s gsub returns a new string. That is why the example copies first with (my $t = "a,b,c") and then substitutes on the copy. To get a new string without touching the original, use the /r flag: my $t = $s =~ s/,/;/gr.
File and stream I/O#
The idiom is the three-argument open with a lexical filehandle, the counterpart of Lua’s io.open. The angle-bracket operator <$fh> reads a line, the role of Lua’s fh:read("l") or the fh:lines() iterator.
local f = io.open("/tmp/sample.txt", "w")
for i = 1, 3 do
f:write("line " .. i .. "\n")
end
f:close()
f = io.open("/tmp/sample.txt", "r")
for line in f:lines() do
print("got:", line)
end
f:close()
use v5.42;
my $path = "/tmp/sample.txt";
open my $wh, '>', $path or die "cannot write: $!";
print {$wh} "line $_\n" for 1 .. 3;
close $wh;
open my $rh, '<', $path or die "cannot read: $!";
while (my $line = <$rh>) {
chomp $line;
say "got: $line"; # got: line 1 ... got: line 3
}
close $rh;
To slurp every line at once, read the handle in list context:
use v5.42;
open my $fh, '<', "/tmp/sample.txt" or die $!;
my @lines = <$fh>; # all lines, including newlines
say scalar @lines; # 3
The gotcha: a line read with <$fh> keeps its trailing newline (Lua’s fh:lines() strips it, fh:read("L") keeps it); call chomp to strip it. And open returns false on failure rather than raising, much like Lua’s io.open returning nil, err, so the or die "...: $!" idiom is mandatory - $! is the system error message, Perl’s errno.
Modules and packages#
Lua’s require becomes Perl’s use. The namespace separator is ::, not ., and a module Foo::Bar lives at Foo/Bar.pm on the include path @INC (Lua’s package.path). Unlike Lua’s require, which returns a table you assign yourself, use imports selected names directly into your namespace.
local sqrt = math.sqrt
local sum = 0
for i = 1, 10 do sum = sum + i end
use v5.42;
use List::Util qw(sum0 max first);
say sum0(1 .. 10); # 55
say max(3, 7, 2); # 7
say first { $_ % 2 == 0 } (1, 3, 4, 5); # 4
Here is a pleasant surprise coming from Lua’s deliberately tiny standard library: Perl ships a large built-in set, and much of what you would hand-roll or pull from LuaRocks is in core or one use away on CPAN, the Perl package archive. List::Util, Scalar::Util, POSIX, File::Spec, and Data::Dumper are the everyday ones.
The gotcha: use runs at compile time, not at the line where it appears, so its placement relative to your code does not matter the way a require call’s does. Importing is opt-in per name: use List::Util qw(sum0 max) brings exactly sum0 and max into your namespace, nothing else.
Objects#
Lua has no built-in object system; you build one from tables and metatables - setmetatable(t, {__index = Class}) for method lookup and inheritance, __add/__eq for operators. Perl 5.42 gives you a first-class class syntax instead, so the common case needs no metatable wiring. Fields are declared with field, methods with method, and $self is available inside every method (you do not pass it as the first parameter the way Lua’s self / colon-call does).
local Point = {}
Point.__index = Point
function Point.new(x, y)
return setmetatable({x = x or 0, y = y or 0}, Point)
end
function Point:to_string()
return "(" .. self.x .. ", " .. self.y .. ")"
end
function Point:move(dx, dy)
self.x = self.x + dx
self.y = self.y + dy
return self
end
local p = Point.new(3, 4)
print(p:to_string()) -- (3, 4)
p:move(1, 1)
print(p:to_string()) -- (4, 5)
use v5.42;
use feature 'class';
class Point {
field $x :param = 0;
field $y :param = 0;
method to_string { return "($x, $y)" }
method move ($dx, $dy) {
$x += $dx;
$y += $dy;
return $self;
}
}
my $p = Point->new(x => 3, y => 4);
say $p->to_string; # (3, 4)
$p->move(1, 1);
say $p->to_string; # (4, 5)
The constructor is generated for you, replacing Lua’s hand-written Point.new: :param marks a field that the auto-built new accepts as a named argument, and = 0 is its default. Inheritance, which Lua builds from a chain of __index metatables, is the :isa attribute:
use v5.42;
use feature 'class';
class Point3D :isa(Point) {
field $z :param = 0;
method z { return $z }
}
my $q = Point3D->new(x => 1, y => 2, z => 3);
say $q->to_string, " z=", $q->z; # (1, 2) z=3
The gotcha: the class feature is still marked experimental in Perl 5.42, and on pperl the “class is experimental” notices print to stderr even when you try to silence them. They do not touch your program’s output, so a script that prints to stdout behaves exactly as shown above; just expect the notices on the error stream. Lua’s operator metamethods (__add, __eq, and friends) map to Perl’s overload pragma rather than to class; for that, the older bless-based object model, roles, and inheritance details, see the Object-oriented Programming guide.
Error handling#
Lua’s protected call pcall(f) - which returns false, err instead of unwinding - has two Perl counterparts. The classic one wraps code in eval { } and inspects $@ (the caught error) afterward:
local ok, err = pcall(function() risky(-1) end)
if not ok then
print("caught: " .. err)
end
use v5.42;
my $result = eval { risky(-1) };
if ($@) {
print "caught: $@"; # caught: negative!
}
The modern form is try/catch, which reads more like a conventional exception block than pcall’s boolean-return dance:
use v5.42;
use feature 'try';
try {
risky(-4);
}
catch ($e) {
print "caught: $e"; # caught: negative!
}
where the raising side is die, Perl’s error:
use v5.42;
sub risky ($n) {
die "negative!\n" if $n < 0;
return sqrt $n;
}
The gotcha: die with a string that ends in \n produces exactly that message; die with a string that does not end in a newline gets " at SCRIPT line N.\n" appended automatically (Lua’s error does a similar position-prefixing unless you pass level 0). End your error strings with \n when you want a clean message and omit it when you want the file-and-line trace. Note also the two error variables: $@ is the last caught exception, while $! is the last system error (a failed open, the errno string).
Idioms#
A few patterns you will see daily and want in your fingers.
Sort a list by a computed key (the Schwartzian transform - decorate, sort, undecorate in one pipeline, the Perl reflex where Lua passes a comparator function to table.sort):
use v5.42;
my @words = qw(apple fig pear);
my @by_length =
map { $_->[1] }
sort { $a->[0] <=> $b->[0] }
map { [ length $_, $_ ] } @words;
say "@by_length"; # fig pear apple
Build a hash from a list, turning a sequence into a lookup table in one step:
use v5.42;
my @words = qw(apple pear fig);
my %len = map { $_ => length $_ } @words;
say "$_ => $len{$_}" for sort keys %len;
# apple => 5
# fig => 3
# pear => 4
And pperl is a command-line tool in the awk/sed niche, a role Lua does not target. The implicit-loop one-liner is the Perl developer’s everyday reflex:
pperl -lane '$s += $F[-1]; END { print $s }' data.txt
The gotcha: read the One-Liners guide for the switches (-n, -p, -a, -l, -e) and the dozens of recipes built on them; it is one of the highest-leverage things to learn coming from a scripting background where this niche was someone else’s job.
Gotchas coming from Lua#
The traps, collected for a final scan before you start writing:
One table becomes two types. A Lua table is an
@array(integer-keyed, ordered) or a%hash(string-keyed, unordered) in Perl, never both at once. Decide which you have before you reach for it.Arrays are 0-based. Lua’s
t[1]is Perl’s$t[0]; the last element is$t[-1]. This is the most common porting off-by-one.Sigils change with usage. One element of
@ais$a[0]; one value of%his$h{k}. The container sigil and the element sigil differ - the bare Lua namethas no such shift.Concatenation is
., not... Lua’s..does not exist;.joins strings andxrepeats them.Truthiness is inverted at zero. In Lua
0and""are true; in Perl they are false. Only0,"","0", andundefare false, so"00"and"0.0"are surprisingly true.Two comparison families.
==/</<=>for numbers,eq/lt/cmpfor strings. Pick by operand type; Lua’s single==has no equivalent split.sortis string-sorted by default. Pass{ $a <=> $b }for numeric order.Context.
my $x = @ais the length;my ($x) = @ais the first element. The list form is your friendlocal a, b = f(); the scalar form is new. Re-read Context when a value comes back the wrong shape.Regex is not Lua patterns. Perl has full regex (
\d,|,*, named groups); Lua patterns (%d, no alternation) are a different, smaller language.s///edits in place and returns a count; use/rfor a new string.opendoes not raise. Useopen ... or die "...: $!", the way Lua’sio.openreturnsnil, err.chompyour line reads.<$fh>keeps the trailing newline that Lua’slines()would have stripped.Non-ASCII source needs
use utf8;. Underuse v5.42;a literal non-ASCII byte in your source is a compile error without it. Keep string data ASCII unless you specifically adduse utf8;.The
classfeature prints experimental notices to stderr onpperl. Correct output, noisy error stream. Operator metamethods map to theoverloadpragma, not toclass.