Useful calculator Alias

Posted by Henry Tanner on Wed 18 Aug 2010 02:12 PM — 33 posts, 122,431 views.

Finland #0
I have an alias "math *",
which sends:
result = %1
Note ("= " .. result)

to script.
I find myself using this one daily, I hope you can find an use for it :)
USA #1
Definitely a useful alias! I use something slightly different (and perhaps a little more flexible):

<aliases>
<alias
  match="^=(.*)$"
  regexp="y"
  send_to="12"
  enabled="y'
  ignore_case="y">
  <send>print(assert(loadstring("return %1"))())</send>
</alias>
</aliases>


(I might've messed up the XML a little since I wrote that out by hand.)
USA #2
Why's it more flexible? They seem to both be evaluating an expression and printing it.
Australia Forum Administrator #3
I don't think the loadstring gets you much further down the track, but this variation saves the assignment line:


<aliases>
  <alias
   match="math *"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>print ("=", %1)</send>
  </alias>
</aliases>


Australia Forum Administrator #4
The only problem with that (and the original) is that if you want to do maths-type things like sqrt you have to do math.sqrt or you see something like this:



math sqrt (9)

Run-time error
World: SmaugFUSS
Immediate execution
[string "Alias: "]:1: attempt to call global 'sqrt' (a nil value)
stack traceback:
        [string "Alias: "]:1: in main chunk


This rather whimsical alias below solves that by letting you use anything in the math table without having to put "math" in front of it. It's all done in a single "line" (one function call) although I added a couple of linebreaks for readability. I'll leave it as an exercise to work out how it works. Suffice to say that it uses a metatable, and calling a function under a different environment.


<aliases>
  <alias
   match="math *"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>
print ("=", 
       setfenv (function () return %1 end, 
       setmetatable ( {}, { __index = function (t, n) return math [n] end })) ())
</send>
  </alias>
</aliases>


Template:pasting
For advice on how to copy the above, and paste it into MUSHclient, please see Pasting XML.


Testing:


math 45847/3433           --> = 13.354791727352
math abs (-435938+23432)  --> = 412506
math log (2343)           --> = 7.7591874385078
math 45847*4532           --> = 207778604
math sqrt (128)           --> = 11.313708498985

Amended on Wed 18 Aug 2010 09:38 PM by Nick Gammon
Australia Forum Administrator #5
Another variation lets you do more stuff (like the string library, the bit library, the utils library) like this:


<aliases>
  <alias
   match="= *"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>
print ("=", 
       setfenv (function () return %1 end, 
       setmetatable ( {}, { __index = function (t, n) 
         return math [n] or bit [n] or string [n] or utils [n] end })) ())
</send>
  </alias>
</aliases>


This one shortens the alias to "=" to save typing, eg.


= hash ('nick')  --> = 2609427bf1f8b42def2bab7d01da5a4fa56db539
= find ('nick cooks', '(co+)')  --> = 6 8 coo
= log (3431234)  --> = 15.048430521205


Notice that, as in the "find" example, you can get multiple results (the columns and the matching text).

Also notice that if you want to use strings you must put them in single quotes because MUSHclient "helpfully" puts a backslash in front of double quotes.
USA #6
What I did lets you use "", since it's inserted into the loadstring argument.

I never thought about shortcut aliases for namespaces like that, Nick. Very interesting! I just use the general "=math.floor(1.5)" because it mimics the Lua REPL's shorthand.
Australia Forum Administrator #7
To use loadstring in my method (removing the need for math.xxx) is fiddlier because the loadstring environment fails. I tried it. ;)

This slightly amended version echoes the original string:


<aliases>
  <alias
   match="=*"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>
print ("%1 =", 
       setfenv (function () return %1 end, 
       setmetatable ( {}, { __index = function (t, n) 
         return math [n] or bit [n] or string [n] or utils [n] end })) ())
</send>
  </alias>
</aliases>


So you see the "question" next to the "answer" which can be helpful if you are doing a lot of maths. You might wonder which answer refers to which question.

eg.


=5*8        --> 5*8 = 40
=log10(500) --> log10(500) = 2.698970004336
= sqrt (2)  -->  sqrt (2) = 1.4142135623731


Amended on Thu 19 Aug 2010 12:09 AM by Nick Gammon
USA #8
Hrm, this worked for me:

<aliases>
  <alias
   match="=*"
   enabled="y"
   send_to="12"
   sequence="100"
  >
    <send>
local env = setmetatable({}, {
  __index = function(t, key)
    return math[key] or bit[key] or string[key] or utils[key]
  end,
})
print("%1 =", setfenv(assert(loadstring("return %1"), env))())
    </send>
  </alias>
</aliases>


Test:

=split("4,3,2", ",")
split("4,3,2", ",") = table: 010426E0
Amended on Thu 19 Aug 2010 12:26 AM by Twisol
Australia Forum Administrator #9
You have a bracket in the wrong spot. If you submit an erroneous expression you get:


=a+

Run-time error
World: SmaugFUSS
Immediate execution
[string "Alias: "]:7: bad argument #2 to 'assert' (string expected, got table)
stack traceback:
        [C]: in function 'assert'
        [string "Alias: "]:7: in main chunk



This version fixes that:


<aliases>
  <alias
   match="=*"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>
local env = setmetatable({}, {
  __index = function(t, key)
    return math[key] or bit[key] or string[key] or utils[key]
  end,
})
print("%1 =", setfenv(assert(loadstring("return %1")), env)())
    </send>
  </alias>
</aliases>


Now testing:


=a+

Run-time error
World: SmaugFUSS
Immediate execution
[string "Alias: "]:7: [string "return a+"]:1: unexpected symbol near '<eof>'
stack traceback:
        [C]: in function 'assert'
        [string "Alias: "]:7: in main chunk



And in the spirit of trying to make it as obscure as possible, here is my version without the intermediate variable:


<aliases>
  <alias
   match="=*"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>
print ("%1 =", setfenv (assert (loadstring "return %1"), setmetatable ({}, 
    {__index = function (_, n) return math [n] or bit [n] or string [n] or utils [n] or world [n] end}) ) () )
</send>
  </alias>
</aliases>



Oh, and thanks to Henry Tanner for kicking off this rather interesting thread. :)

[EDIT] Modified last one to make it shorter. And to add the "world" table.
Amended on Thu 19 Aug 2010 06:20 AM by Nick Gammon
Australia Forum Administrator #10
Speaking of obscure code:

http://www0.us.ioccc.org/main.html

There have been some very good entries in that over the years. For example, an adventure game where you got a whole lot of messages, none of which were visible in the source, at all (I think they were encoded as a variable number of spaces, or something very silly like that).


http://en.wikipedia.org/wiki/International_Obfuscated_C_Code_Contest
Amended on Thu 19 Aug 2010 03:53 AM by Nick Gammon
USA #11
Quote:
What I did lets you use "", since it's inserted into the loadstring argument.

Hmm? Well, beyond the fact that I'm not sure how that's useful, how does the original version not allow that? When you say "", do you mean just doing:

> math <enter>

meaning to print out the empty string to the world?

or do you mean

> math ""<enter>


Nick: nice trick with the metatable. :-)
Australia Forum Administrator #12
Thanks David!

On the very original, if you do this:


math string.match ("nick", "i")


You get:


Compile error
World: SmaugFUSS
Immediate execution
[string "Alias: "]:1: unexpected symbol near '\'


And similar for the variants that don't use loadstring.

This is because MUSHclient notices the quotes and tries to fix them by putting \ in front of them, so the parser sees:


math string.match (\"nick\", \"i\")


It assumes that you would want to put %1 inside quotes, and therefore quotes inside %1 need to be escaped.

The loadstring works around this, because you *have* put %1 inside quotes.

Mind you, the original one is still the simplest by far. The only problems are that one (the quotes) and the fact that things like sqrt have to be math.sqrt.
Amended on Thu 19 Aug 2010 05:27 AM by Nick Gammon
USA #13
Nick Gammon said:
You have a bracket in the wrong spot.

Whoops! I got a table for my result and didn't bother to check what it was, since I was using split() to test.
Amended on Thu 19 Aug 2010 05:33 AM by Twisol
USA #14
Nick Gammon said:

Thanks David!

On the very original, if you do this:


math string.match ("nick", "i")


You get:


Compile error
World: SmaugFUSS
Immediate execution
[string "Alias: "]:1: unexpected symbol near '\'

...


Why would you want to math a string.find? This is a quick calculator, not a replacement for /print() after all.
Australia Forum Administrator #15
WillFa said:

Why would you want to math a string.find? This is a quick calculator, not a replacement for /print() after all.


I was hoping someone would drag us back to reality at some stage. :)

Having said that, it *is* handy to sometimes use Lua to find out simple things, like the code corresponding to a letter, eg.


=byte 'a'  --> byte 'a' = 97


In this case that is shorter than:


/print (string.byte 'a')  --> 97


And if you modify it to also look up the "world" table you can do nice stuff like this:


=GetWorldID ()  --> GetWorldID () = 3bcfeeb548c77aa15ae19061


So I suppose it depends if you are using it for straight maths, or a more general purpose "evaluate an expression and print the result" tool.
Amended on Thu 19 Aug 2010 06:22 AM by Nick Gammon
Australia Forum Administrator #16
Modified version:


<aliases>
  <alias
   match="=*"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>
print ("%1 =", setfenv (assert (loadstring "return %1"), setmetatable ({}, 
    {__index = function (_, n) return math [n] or bit [n] or string [n] or utils [n] or world [n] end}) ) () )
</send>
  </alias>
</aliases>

USA #17
When you're using it as a general eval-alias, I prefer to leave out the shorthand environment because it's too un-obvious. I have to remember which tables are checked. If there are any shared keys, I have to remember which table is checked first. I can't print out a variable I just set with "/foo = 42" without adding _G to the list anyways. For that matter, I can't access global functions without adding _G to the list, either. And I have to manually add other namespaces later, like if I have a JSON API table.

For niche cases like a calculator, it makes loads of sense. But it has its limits.

Nick Gammon said:
And if you modify it to also look up the "world" table you can do nice stuff like this:

=GetWorldID ()  --> GetWorldID () = 3bcfeeb548c77aa15ae19061


You can do that with a perfectly normal, un-setfenv'd eval alias, too. :) World functions are accessible through _G.
Amended on Thu 19 Aug 2010 06:55 AM by Twisol
Australia Forum Administrator #18
As usual, the question about whether the implementation is correct depends on what you are trying to achieve.

If Henry Tanner simply wants to evaluate mathematical expressions, then we have all over-egged the pudding.

His original alias is simple, easy to understand, and works fine for that purpose.

To be honest, all this sophisticated "loadstring" and "setfenv" stuff is clever, but isn't the sort of thing you rattle off without thinking and without having to look up a reference.

The original idea works perfectly for the situation where you just need to do simple arithmetic.
Australia Forum Administrator #19
Twisol said:

(Off-topic: Happy birthday to me.... I'm now 18. ^_^)


Oh and Happy Birthday, Twisol!
USA #20
Nick Gammon said:
To be honest, all this sophisticated "loadstring" and "setfenv" stuff is clever, but isn't the sort of thing you rattle off without thinking and without having to look up a reference.


I absolutely agree... although assert(loadstring("return %1"))() is almost a necessity just because it fixes the double quotes issue.


Nick Gammon said:
Oh and Happy Birthday, Twisol!

Haha, thanks! ^_^
USA #21
Quote:
I absolutely agree... although assert(loadstring("return %1"))() is almost a necessity just because it fixes the double quotes issue.

But... why are we fixing a double quotes issue for a math calculator? I still am not sure why this is really useful. :-/

Besides, you could just use single quotes...

AFAICT the thing going for the loadstring approach is that it lets you use the metatable trick, which I think is extremely useful: having to type out math.log, math.sqrt, math.pow all the time would get pretty old. (I'm not convinced you need all the other stdlib tables, though.)
USA #22
Ya know, this reminds me of when I was younger than Twisol (Happy Bday) and in school. I got a calculator for Algebra II/Trig class, and another kid walks in with a TI-88 Graphing calculator. All the damn feature creep in that phone... by the end of the semester, he was playing Mortal Kombat on it...


It's.
A damn.
Calculator.
People.


:)


(Can your alias do Reverse Polish Notation?)
USA #23
David Haley said:

Quote:
I absolutely agree... although assert(loadstring("return %1"))() is almost a necessity just because it fixes the double quotes issue.

But... why are we fixing a double quotes issue for a math calculator? I still am not sure why this is really useful. :-/

Besides, you could just use single quotes...


Admittedly this is a little off from the original "calculator" alias, but a general evaluate-and-print alias is extremely useful. I use double quotes most by force of habit, so having to see an error every time I slip is a pain.

David Haley said:
AFAICT the thing going for the loadstring approach is that it lets you use the metatable trick, which I think is extremely useful: having to type out math.log, math.sqrt, math.pow all the time would get pretty old. (I'm not convinced you need all the other stdlib tables, though.)


You can do it without loadstring, as Nick did in his earlier examples.

WillFa: That's exactly why it's so enticing, IMHO. I've never tried, but you have to work within an interesting set of constraints, and you can do some cool stuff. Look at Tetris in Excel. ;)
USA #24
I should have said not loadstring but creating a function, it's what I was thinking in any case so I'm not sure why I said just loadstring. :P

And maybe it's just me, but I've not really ever (to my recollection) found it appealing to force myself into artificial constraints just to do something that would be easier elsewhere. So I guess I don't understand the appeal of things like Tetris in Excel. OK, yeah, it's an interesting hack on top of what Excel lets you do... but what's the point, in the end of the day? To get Tetris? Err :P
Australia Forum Administrator #25
WillFa said:

It's.
A damn.
Calculator.
People.


I've got some fancy graphing calculators and some simple ones. I usually reach for the simple one. The one that lets me add things together with minimal keystrokes, or convert from hex to decimal.

Not the one that, when I ask it:

"What is SIN(30)"

replies:

"SIN(30)"

(Yes, I know why it does it, but I don't feel that my question has been really answered).
USA #26
Quote:
Not the one that, when I ask it:

"What is SIN(30)"

replies:

"SIN(30)"

(Yes, I know why it does it, but I don't feel that my question has been really answered).

That sounds like somebody had a sense of humor when developing the calculator. :-)
Finland #27
Originally I intended to share this thing with you of the sole purpose of you maybe finding an use for it.

For now I've done well with what I have, but if and when I feel that I need to upgrade it a bit I know which post to look up ;D
Australia Forum Administrator #28
Just to make it easier to use, I have bundled the latest variation into a plugin. That way you just need to install the plugin, rather than fumbling around trying to find the alias (or one of them) mentioned in this thread.

Template:saveplugin=Calculator
To save and install the Calculator plugin do this:
  1. Copy the code below (in the code box) to the Clipboard
  2. Open a text editor (such as Notepad) and paste the plugin code into it
  3. Save to disk on your PC, preferably in your plugins directory, as Calculator.xml
    • The "plugins" directory is usually under the "worlds" directory inside where you installed MUSHclient.
  4. Go to the MUSHclient File menu -> Plugins
  5. Click "Add"
  6. Choose the file Calculator.xml (which you just saved in step 3) as a plugin
  7. Click "Close"
  8. Save your world file, so that the plugin loads next time you open it.



<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>

<muclient>
<plugin
   name="Calculator"
   author="Nick Gammon"
   id="ba1329d6355e728dd100fd41"
   language="Lua"
   purpose="Provides a simple calculator"
   date_written="2010-08-25 14:55:13"
   requires="4.50"
   version="1.0"
   >
<description trim="y">
<![CDATA[
To calculate something enter "=" followed by an expression which can be evaluated by Lua.

eg.

= 2 + 2     (prints 4)

= sqrt (64)  (prints 8)

The functions in the math, bit, string, utils and world libraries can be used without prefixing them with the library name.

eg.

= GetWorldID () 
= match ("fruit", "u")
= shl (4, 4)

]]>
</description>

</plugin>


<!--  Aliases  -->

<aliases>
  <alias
   match="=*"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>
print ("%1 =", setfenv (assert (loadstring "return %1"), setmetatable ({}, 
    {__index = function (_, n) return math [n] or bit [n] or string [n] or utils [n] or world [n] end}) ) () )
</send>
  </alias>
</aliases>

</muclient>

USA #29
At this point I think it might be better termed a mini-shell of sorts than a calculator. :P

Just wait until people want to assign results to temporary variables... ;)
Australia Forum Administrator #30
David Haley said:

Just wait until people want to assign results to temporary variables... ;)


Well, you can't for a couple of reasons.

First there is a return statement, so if you try something like this:


= a=5


You get this:


[string "Alias: "]:2: [string "return a=5"]:1: '<eof>' expected near '='


Second, because of the setfenv it is effectively sandboxed from the main script space, so you wouldn't be able to affect it anyway.

So my comment to that is, if you want to assign stuff, use Ctrl+I to do immediate evaluations of scripts.

But if you just want to evaluate an expression and print the result in a single, smooth, action, use the calculator.
Amended on Wed 25 Aug 2010 05:33 AM by Nick Gammon
USA #31
Assuming you have access to newproxy and the debug table - which the plugin-ified alias doesn't - you can get the environment of a newproxy() userdata, which is the global environment.

debug.getfenv(newproxy(false))

It's an interesting little jailbreak.
Amended on Wed 25 Aug 2010 05:46 AM by Twisol
USA #32
I know you can't assign to temporaries using the current system... that's why it was meant to be a joke based on what could be called feature creep on a simple math calculator. :-)