Use variables inside a string in lua - lua

In javascript we can do the following:
var someString = `some ${myVar} string`
I have the following lua code, myVar is a number that needs to be in the square brackets:
splash:evaljs('document.querySelectorAll("a[title*=further]")[myVar]')

Function that fits you description is string.format:
splash:evaljs(string.format('document.querySelectorAll("a[title*=further]")[%s]', myVar))
It is not as verbose as ${}. It is more of a good old (and hated) sprintf.

I have written an emulation of Python's f'' strings, which is a set of functions you can hide inside a require file. So, if you like Python's f'' strings, this may be what you're looking for.
(If anyone finds errors, please notify.)
It's quite big compared to the other solution, but if you hide the bulk in a library, then its use is more compact and readable, IMO.
With this library you can do the following, for example:
require 'f_strings'
a = 12345
print(f'Number: {a}, formatted with two decimals: {a::%.2f}')
-- Number: 12345, formatted with two decimals: 12345.00
Note the use of Lua string.format formatting codes, and the use of double colon (instead of Python's single colon) for format specifiers because of Lua's use of colon for methods.
I have extracted only the relevant functions from a larger library. Although some optimizations may be possible for this specific use case, I leave them unchanged as they are general purpose and may also be useful for other purposes.
And here's the required library (placed somewhere in your Lua libraries folder):
-- f_strings.lua ---
unpack = table.unpack or unpack
--------------------------------------------------------------------------------
-- Escape special pattern characters in string to be treated as simple characters
--------------------------------------------------------------------------------
local
function escape_magic(s)
local MAGIC_CHARS_SET = '[()%%.[^$%]*+%-?]'
if s == nil then return end
return (s:gsub(MAGIC_CHARS_SET,'%%%1'))
end
--------------------------------------------------------------------------------
-- Returns iterator to split string on given delimiter (multi-space by default)
--------------------------------------------------------------------------------
function string:gsplit(delimiter)
if delimiter == nil then return self:gmatch '%S+' end --default delimiter is any number of spaces
if delimiter == '' then return self:gmatch '.' end
if type(delimiter) == 'number' then --break string in equal-size chunks
local index = 1
local ans
return function()
ans = self:sub(index,index+delimiter-1)
if ans ~= '' then
index = index + delimiter
return ans
end
end
end
if self:sub(-#delimiter) ~= delimiter then self = self .. delimiter end
return self:gmatch('(.-)'..escape_magic(delimiter))
end
--------------------------------------------------------------------------------
-- Split a string on the given delimiter (comma by default)
--------------------------------------------------------------------------------
function string:split(delimiter,tabled)
tabled = tabled or false --default is unpacked
local ans = {}
for item in self:gsplit(delimiter) do
ans[#ans+1] = item
end
if tabled then return ans end
return unpack(ans)
end
--------------------------------------------------------------------------------
function copy(t) --returns a simple (shallow) copy of the table
if type(t) == 'table' then
local ans = {}
for k,v in next,t do ans[ k ] = v end
return ans
end
return t
end
--------------------------------------------------------------------------------
function eval(expr,vars)
--evaluate a string expression with optional variables
if expr == nil then return end
vars = vars or {}
assert(type(expr) == 'string','String expected as 1st arg')
assert(type(vars) == 'table','Variable table expected as 2nd arg')
local env = {abs=math.abs,acos=math.acos,asin=math.asin,atan=math.atan,
atan2=math.atan2,ceil=math.ceil,cos=math.cos,cosh=math.cosh,
deg=math.deg,exp=math.exp,floor=math.floor,fmod=math.fmod,
frexp=math.frexp,huge=math.huge,ldexp=math.ldexp,log=math.log,
max=math.max,min=math.min,modf=math.modf,pi=math.pi,pow=math.pow,
rad=math.rad,random=math.random,randomseed=math.randomseed,
sin=math.sin,sinh=math.sinh,sqrt=math.sqrt,tan=math.tan,
tanh=math.tanh}
for name,value in pairs(vars) do env[name] = value end
local a,b = pcall(load('return '..expr,nil,'t',env))
if a == false then return nil,b else return b end
end
--------------------------------------------------------------------------------
-- f'' formatted strings like those introduced in Python v3.6
-- However, you must use Lua style format modifiers as with string.format()
--------------------------------------------------------------------------------
function f(s)
local env = copy(_ENV) --start with all globals
local i,k,v,fmt = 0
repeat
i = i + 1
k,v = debug.getlocal(2,i) --two levels up (1 level is this repeat block)
if k ~= nil then env[k] = v end
until k == nil
local
function go(s)
local fmt
s,fmt = s:sub(2,-2):split('::')
if s:match '%b{}' then s = (s:gsub('%b{}',go)) end
s = eval(s,env)
if fmt ~= nil then
if fmt:match '%b{}' then fmt = eval(fmt:sub(2,-2),env) end
s = fmt:format(s)
end
return s
end
return (s:gsub('%b{}',go))
end

Related

Lua - too many captures. How fix it?

Have problems with this one. If try convert cirilic words or wors have to many symbols and have error
function to_string(t)
local o = {};
for _, v in ipairs(t) do
local b = v < 0 and (0xff + v + 1) or v;
table.insert(o, string.char(b));
end
return table.concat(o);
end
function to_bytes(s)
local c = { s:match( (s:gsub(".", "(.)")) ) };
local o = {};
for _, v in pairs(c) do
table.insert(o, v:byte());
end
return o;
end
local t = to_bytes("If this have to many words или русские");
local out = "\\"
local chars = #t;
for i = 1, chars do
out = out..tostring(t[i]);
if i < chars then
out = out.."\\"
end
end
out = out..""
I think the error is self-explanatory: you have too many captures in your pattern (those groups that are wrapped into parentheses). The default value is 32. You have a couple of options: (1) recompile your Lua version to use a large number (you'll have to modify LUA_MAXCAPTURES value), but keep in mind that this limit is there for a reason and (2) change your pattern to avoid this many captures (possibly splitting into smaller fragments/patterns). You may also consider using more powerful parsers, like LPEG.
You don't need regex to convert string to array of bytes
function to_bytes(s)
return {s:byte(1, -1)}
end

How to put floating points in LUA [duplicate]

I'd like to format a number to look like 1,234 or 1,234,432 or 123,456,789, you get the idea. I tried doing this as follows:
function reformatint(i)
local length = string.len(i)
for v = 1, math.floor(length/3) do
for k = 1, 3 do
newint = string.sub(mystring, -k*v)
end
newint = ','..newint
end
return newint
end
As you can see, a failed attempt, my problem is that I can't figure out what the error is because the program I am running this in refuses to report an error back to me.
Here's a function that takes negative numbers, and fractional parts into account:
function format_int(number)
local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)')
-- reverse the int-string and append a comma to all blocks of 3 digits
int = int:reverse():gsub("(%d%d%d)", "%1,")
-- reverse the int-string back remove an optional comma and put the
-- optional minus and fractional part back
return minus .. int:reverse():gsub("^,", "") .. fraction
end
assert(format_int(1234) == '1,234')
assert(format_int(1234567) == '1,234,567')
assert(format_int(123456789) == '123,456,789')
assert(format_int(123456789.1234) == '123,456,789.1234')
assert(format_int(-123456789.) == '-123,456,789')
assert(format_int(-123456789.1234) == '-123,456,789.1234')
assert(format_int('-123456789.1234') == '-123,456,789.1234')
print('All tests passed!')
Well, let's take this from the top down. First of all, it's failing because you've got a reference error:
...
for k = 1, 3 do
newint = string.sub(mystring, -k*v) -- What is 'mystring'?
end
...
Most likely you want i to be there, not mystring.
Second, while replacing mystring with i will fix the errors, it still won't work correctly.
> =reformatint(100)
,100
> =reformatint(1)
,000
That's obviously not right. It seems like what you're trying to do is go through the string, and build up the new string with the commas added. But there are a couple of problems...
function reformatint(i)
local length = string.len(i)
for v = 1, math.floor(length/3) do
for k = 1, 3 do -- What is this inner loop for?
newint = string.sub(mystring, -k*v) -- This chops off the end of
-- your string only
end
newint = ','..newint -- This will make your result have a ',' at
-- the beginning, no matter what
end
return newint
end
With some rework, you can get a function that work.
function reformatint(integer)
for i = 1, math.floor((string.len(integer)-1) / 3) do
integer = string.sub(integer, 1, -3*i-i) ..
',' ..
string.sub(integer, -3*i-i+1)
end
return integer
end
The function above seems to work correctly. However, it's fairly convoluted... Might want to make it more readable.
As a side note, a quick google search finds a function that has already been made for this:
function comma_value(amount)
local formatted = amount
while true do
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
if (k==0) then
break
end
end
return formatted
end
You can do without loops:
function numWithCommas(n)
return tostring(math.floor(n)):reverse():gsub("(%d%d%d)","%1,")
:gsub(",(%-?)$","%1"):reverse()
end
assert(numWithCommas(100000) == "100,000")
assert(numWithCommas(100) == "100")
assert(numWithCommas(-100000) == "-100,000")
assert(numWithCommas(10000000) == "10,000,000")
assert(numWithCommas(10000000.00) == "10,000,000")
The second gsub is needed to avoid -,100 being generated.
I remember discussing about this in the LÖVE forums ... let me look for it...
Found it!
This will work with positive integers:
function reformatInt(i)
return tostring(i):reverse():gsub("%d%d%d", "%1,"):reverse():gsub("^,", "")
end
On the link above you may read details about implementation.

Lua - util_server.lua:440 attempt to index local 'self' (a nil value)

Good evening
Will you help me solve this problem?
ERROR: race/util_server.lua:440: attempt to index local 'self' (a nil value)
function string:split(separator)
if separator == '.' then
separator = '%.'
end
local result = {}
for part in self:gmatch('(.-)' .. separator) do
result[#result+1] = part
end
result[#result+1] = self:match('.*' .. separator .. '(.*)$') or self
return result
end
You're probably calling it wrong.
function string:split(separator)
Is short hand for:
function string.split(self, separator)
Given a string and separator:
s = 'This is a test'
separator = ' '
You need to call it like this:
string.split(s, separator)
Or:
s:split(separator)
If you call it like this:
s.split(separator)
You're failing to provide a value for the self argument.
Side note, you can write split more simply like this:
function string:split(separators)
local result = {}
for part in self:gmatch('[^'..separators..']+') do
result[#result + 1] = part
end
return result
end
This has the disadvantage that you can't used multi-character strings as delimiters, but the advantage that you can specify more than one delimiter. For instance, you could strip all the punctuation from a sentence and grab just the words:
s = 'This is an example: homemade, freshly-baked pies are delicious!'
for _,word in pairs(s:split(' :.,!-')) do
print(word)
end
Output:
This
is
an
example
homemade
freshly
baked
pies
are
delicious

Lua need to split at comma

I've googled and I'm just not getting it. Seems like such a simple function, but of course Lua doesn't have it.
In Python I would do
string = "cat,dog"
one, two = string.split(",")
and then I would have two variables, one = cat. two = dog
How do I do this in Lua!?
Try this
str = 'cat,dog'
for word in string.gmatch(str, '([^,]+)') do
print(word)
end
'[^,]' means "everything but the comma, the + sign means "one or more characters". The parenthesis create a capture (not really needed in this case).
If you can use libraries, the answer is (as often in Lua) to use Penlight.
If Penlight is too heavy for you and you just want to split a string with a single comma like in your example, you can do something like this:
string = "cat,dog"
one, two = string:match("([^,]+),([^,]+)")
Add this split function on the top of your page:
function string:split( inSplitPattern, outResults )
if not outResults then
outResults = { }
end
local theStart = 1
local theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart )
while theSplitStart do
table.insert( outResults, string.sub( self, theStart, theSplitStart-1 ) )
theStart = theSplitEnd + 1
theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart )
end
table.insert( outResults, string.sub( self, theStart ) )
return outResults
end
Then do as follows:
local myString = "Flintstone, Fred, 101 Rockledge, Bedrock, 98775, 555-555-1212"
local myTable = myString:split(", ")
for i = 1, #myTable do
print( myTable[i] ) -- This will give your needed output
end
For more information, visit : Tutorial: Lua String Magic
Keep Coding...............:)
-- like C strtok, splits on one more delimiter characters (finds every string not containing any of the delimiters)
function split(source, delimiters)
local elements = {}
local pattern = '([^'..delimiters..']+)'
string.gsub(source, pattern, function(value) elements[#elements + 1] = value; end);
return elements
end
-- example: var elements = split("bye# bye, miss$ american# pie", ",#$# ")
-- returns "bye" "bye" "miss" "american" "pie"
To also handle optional white space you can do:
str = "cat,dog,mouse, horse"
for word in str:gmatch('[^,%s]+') do
print(word)
end
Output will be:
cat
dog
mouse
horse
This is how I do that on mediawiki:
str = "cat,dog"
local result = mw.text.split(str,"%s*,%s*")
-- result[0] will give "cat", result[1] will give "dog"
actually, if you don't care spaces, you can use:
str = "cat,dog"
local result = mw.text.split(str,",")
-- result[0] will give "cat", result[1] will give "dog"
The API used here is implemented in Scribunto MediaWiki extension. Here is the split() method reference documentation and here is the source code for that. It relies on a lot of other capabilities in Scribunto's Lua common libraries, so it will only work for you if you are actually using MediaWiki or plan to import most of the Scribunto common library.
Functions like string.split() are largely unnecessary in Lua since you can
express string operations in LPEG.
If you still need a dedicated function a convenient approach is
to define a splitter factory (mk_splitter() in below snippet)
from which you can then derive custom splitters.
local lpeg = require "lpeg"
local lpegmatch = lpeg.match
local P, C = lpeg.P, lpeg.C
local mk_splitter = function (pat)
if not pat then
return
end
pat = P (pat)
local nopat = 1 - pat
local splitter = (pat + C (nopat^1))^0
return function (str)
return lpegmatch (splitter, str)
end
end
The advantage of using LPEG is that the function accepts
both valid Lua strings and patterns as argument.
Here is how you would use it to create a function that
splits strings at the , character:
commasplitter = mk_splitter ","
print (commasplitter [[foo, bar, baz, xyzzy,]])
print (commasplitter [[a,b,c,d,e,f,g,h]])

lua misinterpreying check condition

I have a program which checks for conditions some variable field, like
if(tostring(field) == '0') then {do something}
if(tostring(field) == '1') then {do something}
if(tostring(field) == '2') then {do something}
But, i think lua is interpreting '0' and '1' as TRUE/FALSE values and not checking the corresponding if conditions properly. The condition executes properly for field == '2' condition.
How can i overcome this case? How can i make it work for check conditions '0' and '1'?
Thank You in advance!
In case you are wondering why i tagged wireshark, the if check condition is checking for a field in pcap file.
My lua code for reference is as follows:
#!/usr/bin/lua
do
local pkts = 0
local stat = {}
local file = io.open("luawrite","w")
local function init_listener()
local tap = Listener.new("wlan")
local src_addr = Field.new("wlan.sa")
local type = Field.new("wlan.fc.type")
local sub_type = Field.new("wlan.fc.subtype")
local frame_length = Field.new("frame.len")
local data_rate = Field.new("wlan.data_rate")
function tap.reset()
pkts = 0;
end
function tap.packet(pinfo, tvb)
local client = src_addr()
local stype = sub_type()
local ty = type()
local ts = tostring(pinfo.rel_ts)
local fl = frame_length()
rate = data_rate()
if(tostring(ty) == '0') then
file:write(tostring(ts), "\t", tostring(fl), "\t", tostring(rate), "\n")
end
end
end
init_listener()
end
The condition i am referring to 7th line from last line. If i give the condition tostring(ty) == '2', it works properly.
From the manual:
The condition expression of a control structure can return any value.
Both false and nil are considered false. All values different from nil
and false are considered true (in particular, the number 0 and the
empty string are also true).
Both the number 0 and the empty string evaluate to true, so it's definitely not mistaking the string "0" for false. I might avoid redefining type. Also, I think frame_type returns a number so you can get rid of the tostring() in the condition.
do
local pkts = 0
local stat = {}
local file = io.open("luawrite","w")
local function init_listener()
local tap = Listener.new("wlan")
local src_addr = Field.new("wlan.sa")
-- Changed function from type to frame_type
local frame_type = Field.new("wlan.fc.type")
local sub_type = Field.new("wlan.fc.subtype")
local frame_length = Field.new("frame.len")
local data_rate = Field.new("wlan.data_rate")
function tap.reset()
pkts = 0;
end
function tap.packet(pinfo, tvb)
local client = src_addr()
local stype = sub_type()
local ty = frame_type()
local ts = tostring(pinfo.rel_ts)
local fl = frame_length()
rate = data_rate()
-- skip the tostring
if ty == 0 then
file:write(tostring(ts), "\t", tostring(fl), "\t", tostring(rate), "\n")
end
end
end
init_listener()
end
If all else fails, try writing a line regardless of the frame type and write the frame type with it:
function tap.packet(pinfo, tvb)
local client = src_addr()
local stype = sub_type()
local ty = frame_type()
local ts = tostring(pinfo.rel_ts)
local fl = frame_length()
rate = data_rate()
file:write(tostring(ty), "\t", tostring(ts), "\t", tostring(rate), "\n")
end
Then you can see which frame types you're receiving.
As Corbin said, it's definitely not interpreting "0" and "1" as TRUE/FALSE. Wireshark runs the stock Lua interpreter, and Lua only considers nil and the boolean value of false as false values. Of course you're not really checking if they're false values, you're doing a string comparison of the string-ified value of the "wlan.fc.type" field value to the string "0" or the string "1" or whatever. The actual value you get for the "wlan.fc.type" field is a number, so as Corbin said there's no need to convert it to a string and string-compare it to a string of "0" or whatever... just compare them as numbers.
Regardless, stringifying both of them should have worked too (just been less efficient), so the odds are there simply isn't an 802.11 packet with a wlan.fc.type of 0 in your capture. A wlan.fc.type of 0 is a management frame, a 1 is a control frame, and a 2 is a data frame. So the odds are you're only capturing 802.11 data packets, which is why your comparison of the string-ified wlan.fc.type to the string "2" succeeds.
One way to find out is to open your capture file in wireshark, and put in a display filter for "wlan.fc.type == 0" and see if any packets are shown. If not, then you don't have any management packets.

Resources