This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
joshmanisdabomb's profile picture

Calculate user's input.

Started by joshmanisdabomb, 07 July 2013 - 05:19 AM
joshmanisdabomb #1
Posted 07 July 2013 - 07:19 AM
Title: Calculate user's input.
I would like to know if there is a way to work out maths when the user inputs a string like:
  • "1+1" (would return 2)
  • "2+2" (4)
  • "5-4" (1)
  • "5-6" (-1)
  • "6*2.5" (15)
  • "6/2.5" (2.4)
  • "6+4+5-2" (13)
I think you guys get the idea. Is there any way to do this? Thank you.

Edit: String.
Lyqyd #2
Posted 07 July 2013 - 12:19 PM
Split into new topic.

There is a very simple way, but it's rather dangerous. You can just loadstring it and use that to calculate the string as if it were a Lua statement, but then users can execute any arbitrary code in your program. There are other ways, but they're a fair bit more work, especially if you want anything beyond basic arithmetic.
TheOddByte #3
Posted 07 July 2013 - 01:18 PM
Uhmm do you mean something like this?
Spoiler

function clear()
  term.clear()
   term.setCursorPos(1,1)
end

while true do
clear()
print("Method [+ -]")
repeat
local input = read()
until input == "+" or input == "-"

if input == "+" then
mode = "add"

else
mode = "sub"
end

clear()
write("First number: ")
repeat
fNum = read()
until tonumber(fNum)

fNum = tonumber(fNum)

write("Second number: ")
repeat
sNum = read()
until tonumber(sNum)

sNum = tonumber(sNum)

if mode = "add" then
local answ = fNum + sNum
   clear()
   print(fNum.." + "..sNum.." = "..answ)
os.pullEvent("key")

elseif mode == "sub" then

clear()
  local answ = fNum - sNum
  print(fNum.." - "..sNum.." = "..answ)
os.pullEvent("key")
	end
end

Not sure if it works since I typed this on my phone and haven't tested it..
joshmanisdabomb #4
Posted 07 July 2013 - 01:54 PM
Thank you for the relatively fast responses!

Split into new topic.

There is a very simple way, but it's rather dangerous. You can just loadstring it and use that to calculate the string as if it were a Lua statement, but then users can execute any arbitrary code in your program. There are other ways, but they're a fair bit more work, especially if you want anything beyond basic arithmetic.

Hmmmm. It seems risky…
Could I make it so it filters out any code and only does math..? Somehow?

Uhmm do you mean something like this?
Spoiler

function clear()
  term.clear()
   term.setCursorPos(1,1)
end

while true do
clear()
print("Method [+ -]")
repeat
local input = read()
until input == "+" or input == "-"

if input == "+" then
mode = "add"

else
mode = "sub"
end

clear()
write("First number: ")
repeat
fNum = read()
until tonumber(fNum)

fNum = tonumber(fNum)

write("Second number: ")
repeat
sNum = read()
until tonumber(sNum)

sNum = tonumber(sNum)

if mode = "add" then
local answ = fNum + sNum
   clear()
   print(fNum.." + "..sNum.." = "..answ)
os.pullEvent("key")

elseif mode == "sub" then

clear()
  local answ = fNum - sNum
  print(fNum.." - "..sNum.." = "..answ)
os.pullEvent("key")
	end
end

Not sure if it works since I typed this on my phone and haven't tested it..

That's not what I mean. I don't want 3 read functions, I want the user to be able to type something like "8+8" in one read function and show what it equals.
GopherAtl #5
Posted 08 July 2013 - 07:43 PM
If you only want arithmetic operations and numerical constants, you could test the input first with a lua pattern, like this…

while true do
  write(">")
  local inStr=read()
  if inStr==nil or inStr=="" then
    break
  end
  if inStr:match("^[%s%d%.%-%+%*%/%%%^]+$") then
    local f,err=loadstring("return "..inStr)
    if f then
      local  succ, res=pcall(f)
      print(res)
    else
      print(err)
    end
  else
    print("Invalid Expression!")
  end
end

Tested, but not in-game… The lua pattern will match strings containing only combinations of white space, numbers, and the basic operators (+, -, *, /, %, ^)

I'm not at all sure the pcall is even necessary; I can't think of any way to make a string that matches the pattern and succeeds in loadstring, but errors on running. But, seems better safe than sorry, I may be overlooking something!
Pharap #6
Posted 09 July 2013 - 06:59 PM
Best method I can think of is handling the key events yourself.
Make it so you can only accept numeric or symbol input by only handling those keys.
Then you'd just have to use tables to cache user input and then do a bit of string parsing to deliver the result.
Your real problem would come from what order to handle the expressions in (ie what order to process statements in)
ElvishJerricco #7
Posted 09 July 2013 - 07:12 PM
If you really want to make yourself a much smarter programmer and go way over the top with this, you could learn compiler theory and develop an extremely simple, math only language, and compile users' input and return the result of running that program.
Engineer #8
Posted 09 July 2013 - 08:08 PM
This would require parsing and all that fancy stuff. Probably not the right term though.. Example:

(untested and from my phone)


local calc = ('calc string here'):gsub(' ', '')
if  not calc:find('^[0-9%-%+/%*%%%^]') then
    local digits = {}
    for i = 1, calc:len() do
       table.insert( digits, calc:sub( i, i )
    end
    -- actual calculating here
end
ElvishJerricco #9
Posted 09 July 2013 - 08:50 PM
This would require parsing and all that fancy stuff. Probably not the right term though.. Example:

(untested and from my phone)


local calc = ('calc string here'):gsub(' ', '')
if  not calc:find('^[0-9%-%+/%*%%%^]') then
	local digits = {}
	for i = 1, calc:len() do
	   table.insert( digits, calc:sub( i, i )
	end
	-- actual calculating here
end

Yea I've been working on a compiler for a new implementation of Lua for a while now (so that I can extend the language however I like). I've read several chapters out of a number of books, and have read most of the infamous Dragon Book. Trust me, that's not enough for something like that. You've got to do lexical analysis to turn all the input characters into individual tokens, a full finite state machine to turn the input string into a parse tree, then you compact that down to an abstract syntax tree, and the rest should be easy calculation. But all that is rather complex.

What you've done is made sure the input string is legal in terms of what characters are include, but not in terms of actual syntax (for example, 1 ^* 3 shouldn't be legal)(also, you did this wrong. You should be putting the ^ after the square bracket in order to mean "not in this set". What you did means "the following set is at the beginning"). Then you've turned the input string into an array of all the characters. Then you say "Do the rest here…." Rather useless.
Pharap #10
Posted 09 July 2013 - 08:55 PM
Why don't you just make a GUI calculator? It would be easier anyway.
Plus the "lua" program can already act as a calculator.
MysticT #11
Posted 09 July 2013 - 08:59 PM
Well, the easiest way is what Lyqyd said, and you can use a custom environment that doesn't have any apis or global functions (so people can't use something like "os.reboot()" or whatever).
Using an empty environment like this should work:

local input = read()
local func = loadstring("return "..input)
if func then
  setfenv(func, {})
  local ok, result = pcall(func)
  if ok then
	print(result)
  else
	printError("Error: ", result)
  end
else
  printError("Invalid input")
end

This way you could also make an environment with some math functions and constants.
ElvishJerricco #12
Posted 09 July 2013 - 09:05 PM
Well, the easiest way is what Lyqyd said, and you can use a custom environment that doesn't have any apis or global functions (so people can't use something like "os.reboot()" or whatever).
Using an empty environment like this should work:

local input = read()
local func = loadstring("return "..input)
if func then
  setfenv(func, {})
  local ok, result = pcall(func)
  if ok then
	print(result)
  else
	printError("Error: ", result)
  end
else
  printError("Invalid input")
end

This way you could also make an environment with some math functions and constants.

This is probably the best, easiest solution. And maybe the environment could __index to the math API to allow for math functions?


setfenv(func, setmetatable({}, {__index=math}))
Engineer #13
Posted 10 July 2013 - 02:01 AM
Yea I've been working on a compiler for a new implementation of Lua for a while now (so that I can extend the language however I like). I've read several chapters out of a number of books, and have read most of the infamous Dragon Book. Trust me, that's not enough for something like that. You've got to do lexical analysis to turn all the input characters into individual tokens, a full finite state machine to turn the input string into a parse tree, then you compact that down to an abstract syntax tree, and the rest should be easy calculation. But all that is rather complex.

What you've done is made sure the input string is legal in terms of what characters are include, but not in terms of actual syntax (for example, 1 ^* 3 shouldn't be legal)(also, you did this wrong. You should be putting the ^ after the square bracket in order to mean "not in this set". What you did means "the following set is at the beginning"). Then you've turned the input string into an array of all the characters. Then you say "Do the rest here…." Rather useless.

For a matter of fact, I know That 'do the rest here' is rather useless. But Im not going to write it out on my phone, so I decided to do get it started. Im really up for the challenge though when Im on an actual computer, where I can test and stuff.

I will definitely post a wroking piece of code right here, you just have to wait :)/> (I dont know how long its going to take to actually write it and when I can get to a computer)
ElvishJerricco #14
Posted 10 July 2013 - 03:10 AM
For a matter of fact, I know That 'do the rest here' is rather useless. But Im not going to write it out on my phone, so I decided to do get it started. Im really up for the challenge though when Im on an actual computer, where I can test and stuff.

I will definitely post a wroking piece of code right here, you just have to wait :)/> (I dont know how long its going to take to actually write it and when I can get to a computer)

For simple math expressions, it'll likely be much easier. Can't tell you quite how much easier. But chances are you can skip some of the proper compiler steps. I do however challenge you to write an actual Lua compiler that compiles down to either the Lua bytecode for LuaJ, or LASM (recommending LASM because it'd be a thousand times easier, and compiling to assembly is what real compilers do).
Engineer #15
Posted 10 July 2013 - 03:34 AM
For simple math expressions, it'll likely be much easier. Can't tell you quite how much easier. But chances are you can skip some of the proper compiler steps. I do however challenge you to write an actual Lua compiler that compiles down to either the Lua bytecode for LuaJ, or LASM (recommending LASM because it'd be a thousand times easier, and compiling to assembly is what real compilers do).

Hold on for a minute, a compiler?
I was actually more thinking about a function that calculates your string, nothing much more. Im actually very, very unfamilair with compiler theory and that stuff. So to clearify, Im going to make a function to calculate a string, nothing to due with compilers.
ElvishJerricco #16
Posted 10 July 2013 - 03:38 AM
Hold on for a minute, a compiler?
I was actually more thinking about a function that calculates your string, nothing much more. Im actually very, very unfamilair with compiler theory and that stuff. So to clearify, Im going to make a function to calculate a string, nothing to due with compilers.

Well my original point was that to do this well without using loadstring, you're going to have to know compiler theory. A knowledge of deterministic grammars will be essential, the ability to tokenize the input so that you don't have to check each character while parsing, the ability to create a parse tree, the ability to make an operation-based syntax tree. It'll all be there if you want the program to be A ) Functional, B ) Well-organized, and C ) easier to fix bugs in.
GopherAtl #17
Posted 10 July 2013 - 10:40 AM
None of that is required for a basic arithmetic expression parser. Not grammars, not tokenizing the input, not checking each character while parsing, not parse trees, not syntax trees, none of it. I wrote several, in a handful of different languages, back in high school before I'd even learned what most of those things were.
joshmanisdabomb #18
Posted 10 July 2013 - 12:50 PM
Why don't you just make a GUI calculator? It would be easier anyway.
Plus the "lua" program can already act as a calculator.

Because I don't want a GUI calculator.

Well, the easiest way is what Lyqyd said, and you can use a custom environment that doesn't have any apis or global functions (so people can't use something like "os.reboot()" or whatever).
Using an empty environment like this should work:

local input = read()
local func = loadstring("return "..input)
if func then
  setfenv(func, {})
  local ok, result = pcall(func)
  if ok then
	print(result)
  else
	printError("Error: ", result)
  end
else
  printError("Invalid input")
end

This way you could also make an environment with some math functions and constants.

This seems like the most reasonable way of doing this! :D/>/> I'll say thank you in advance, but I really can't check right now because I'm not on a computer that runs Minecraft well enough. I only just remembered I made this topic! :wacko:/>/>
Edited by
Engineer #19
Posted 10 July 2013 - 01:53 PM
For those who are interested, I did make a function for a stringed calculation input. However, sadly, it doesnt quite work yet. I know what is failing, but Im not bothered to fix it properly because this function is pointless. I'd rather use loadstring with an environment where you cant even input numbers etc.

Here is the "not-working" code:
Spoiler

local calcString = function( s )
	assert( type(s) == "string", "Bad Argument: String expected, got " .. type( s ), 2 )
	s = s:gsub( "[^0-9%%%+%-%*/%^]", "" )
	assert( s:len() >= 3 and s:sub( -1 ):find( "[^%%%+%-%*/%^]" ), "Valid calculation expected", 2 )

	local combo = {
		[ "-+" ] = "-";   [ "+-" ] = "-";
		[ "--" ] = "+";   [ "/-" ] = "/ n";
		[ "*-" ] = "* n"; [ "%-" ] = "% n";
		[ "^-" ] = "% n";
	}
	
	local numbers = {}
	local operators = {}

	local mutate = function( operator, operatorIndex )
		local calc = nil
		if operator == "^" then
			calc = numbers[ operatorIndex ] ^ numbers[ operatorIndex + 1 ]
		elseif operator == "*" then
			calc = numbers[ operatorIndex ] * numbers[ operatorIndex + 1 ]
		elseif operator == "/" then
			calc = numbers[ operatorIndex ] / numbers[ operatorIndex + 1 ]
		elseif operator == "+" then
			calc = numbers[ operatorIndex ] + numbers[ operatorIndex + 1 ]
		elseif operator == "-" then
			calc = numbers[ operatorIndex ] - numbers[ operatorIndex + 1 ]
		end
		if calc then -- To avoid possible errors
			operators[ operatorIndex ] = "AaP"
			table.remove( numbers, operatorIndex )
			table.remove( numbers, operatorIndex + 1 )
			table.insert( numbers, operatorIndex, calc )
		end
	end

	local sequence = {
		[ 1 ] = "^" ; [ 2 ] = "*/";
		[ 3 ] = "+-";
	}
	
	for number in s:gmatch( "[0-9]+" ) do
		table.insert( numbers, tonumber(number) )
	end
	
	local first = true
	for operator in s:gmatch( "[%%%+%-%*/%^]+" ) do
		if operator:len() == 2 then
			if combo[ operator ] then
				if combo[ operator ]:find("n") then
					numbers[ #operators + 2 ] = -numbers[ #operators + 2 ]
				end
				table.insert( operators, combo[ operator ]:sub( 1, 1 ))
			else
				error( "Unexpected symbol in calculation.", 2 )
			end
		else
			table.insert( operators, operator )
			if first then
				if operator == "-" and s:sub( 1, 1 ) == "-" then
					numbers[ 1 ] = -numbers[ 1 ]
					table.remove( operators, #operators )
				elseif operator == "-" and s:sub( 1, 1 ) ~= "-" then
					error( "Unexpected symbol in calculation.", 2 )
				end
				first = false
			end
		end
	end
	
	for i = 1, 3 do
		for key, operator in ipairs( operators ) do
			if operator:find( "[" ..
				(function( sPattern )
					local mChars = {
						[ "^" ] = true; [ "*" ] = true; 
						[ "+" ] = true; [ "-" ] = true;
					}	
					local safePattern = ""
					for i = 1, sPattern:len() do
						if mChars[sPattern:sub( i, i )] then
							safePattern = safePattern .. "%" .. sPattern:sub( i, i )
						else
							safePattern = safePattern .. sPattern:sub( i, i )
						end
					end
					return safePattern
				end)( sequence[ i ] ).. "]" ) then
				
				if sequence[ i ]:len() == 2 then
					if operator == sequence[ i ]:sub( 1, 1 ) then
						mutate( sequence[ i ]:sub( 1, 1 ), key )
					elseif operator == sequence[ i ]:sub( 2, 2 ) then
						mutate( sequence[ i ]:sub( 2, 2 ), key )
					end
				else
					mutate( sequence[ i ], key )
				end
			end
		end
	end
	return numbers[ 1 ]
end
I think it's not the best code, but it will do the job once that little bug is fixed. Don't use this!
The bug is, I'm trying to delete a key, but that messes up the pairs loop. Im not bothered to fix it, since this is pointless.
Probably a few of you are thinking 'ha, he gave up!' And yes, you could say that, but I have more important stuff to do right now.
joshmanisdabomb #20
Posted 10 July 2013 - 06:16 PM
Thank you for all your help guys! The loadstring method worked! Big thanks to everybody who contributed.



The magenta number is where 5+5 was typed. I hope to make it so it reads variables.