Shunting algorithm with functions
my first post is here :)
I have seen that there are many questions about the Shunting yard algorithm, but I hope there are more forum members who are interested in helping me with another question about this algorithm.
I looked at other posts to see if my answer was answered, and I did some research on other forums and on the internet to:
http://stackoverflow.com/questions/16877546/modifying-the-shunting-yard-algorithm-c
http://www.computerhope.com/forum/index.php?topic=146535.0
http://en.wikipedia.org/wiki/Shunting-yard_algorithm
http://www.autoitscript.com/forum/topic/164627-shunting-yard-with-functions/
My code is written in vb-script because I like its simplicity and I don't know java or c-like languages.
my question is:
the algorithm currently allows misuse of "(" and ")" Example: function ((10,20,) 30) is allowed, but clearly not suitable for calling a function.
Also I'm not sure if my code is written correctly, the pseudocode from Wikipedia was my link, but it's not very clear :(
I also plan on expanding it with if-else statements and nested loops, etc., because the main goal is to write some kind of vb interpreter as a tutorial project :)
my code [edit]:
SET PRECEDENCE = CREATEOBJECT("SCRIPTING.DICTIONARY")
WITH PRECEDENCE
.ADD "^",3
.ADD "*",2
.ADD "/",2
.ADD "%",2
.ADD "+",1
.ADD "-",1
.ADD "FUNCTION",0
.ADD "(",0
.ADD ",",0
.ADD ")",0
END WITH
'#############################################################################
tokenArray = split("FUNCTION ( ( A , B ) , C )")
msgbox SHUNTINGYARD(tokenArray)
'#############################################################################
FUNCTION SHUNTINGYARD(INPUT)
TOKEN_QUEUE = ARRAY()
TOKEN_STACK = ARRAY()
FOR TOKEN_NUMBER = 0 TO UBOUND(INPUT)
SELECT CASE INPUT(TOKEN_NUMBER)
CASE "("
CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_STACK)
CASE ")"
DO WHILE NOT( PRECEDENCE( PEEK(TOKEN_STACK) ) = 0 )
CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
IF STACKISEMPTY(TOKEN_STACK) THEN CALL ERRORS("Can't find a matching ""("".", TRUE)
LOOP
IF PEEK(TOKEN_STACK) = "FUNCTION" THEN
DISCARD = POP(TOKEN_STACK)
CALL PUSH("@", TOKEN_QUEUE)
ELSE
DISCARD = POP(TOKEN_STACK)
END IF
CASE ","
DO WHILE NOT( PRECEDENCE( PEEK(TOKEN_STACK) ) = 0 )
CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
IF STACKISEMPTY(TOKEN_STACK) THEN CALL ERRORS("Can't find a matching function ""("".", TRUE)
LOOP
CASE "+","-","*","/","^","%"
TOKEN_A = INPUT(TOKEN_NUMBER)
DO WHILE ISOPERATOR(PEEK(TOKEN_STACK))
TOKEN_B = PEEK(TOKEN_STACK)
IF (ASSOCIATIVITY(TOKEN_B) = "left" AND PRECEDENCE(TOKEN_A) = PRECEDENCE(TOKEN_B)) OR (PRECEDENCE(TOKEN_A) < PRECEDENCE(TOKEN_B)) THEN
CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
ELSE
EXIT DO
END IF
LOOP
CALL PUSH(TOKEN_A, TOKEN_STACK)
CASE ELSE
CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_QUEUE)
END SELECT
NEXT
FOR ITEMCOUNT = 0 TO UBOUND(TOKEN_STACK)
IF PEEK(TOKEN_STACK) = "(" THEN CALL ERRORS("Can't find a matching "")"".", TRUE)'(
CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
NEXT
SHUNTINGYARD = JOIN(TOKEN_QUEUE,"|")
END FUNCTION
'#############################################################################
FUNCTION ASSOCIATIVITY(ASSOC)
SELECT CASE LCASE(ASSOC)
CASE "^","\"
ASSOCIATIVITY = "right"
CASE ELSE
ASSOCIATIVITY = "left"
END SELECT
END FUNCTION
FUNCTION ISOPERATOR(ITEM)
ISOPERATOR = LEN(ITEM) = 1 AND INSTR("+-*/%^",ITEM)
END FUNCTION
SUB PUSH(ITEM,BYREF STACK)
IF UBOUND(STACK) > -1 THEN
REDIM PRESERVE STACK(UBOUND(STACK) + 1)
STACK(UBOUND(STACK)) = ITEM
ELSE
STACK = ARRAY(ITEM)
END IF
END SUB
FUNCTION POP(BYREF STACK)
IF UBOUND(STACK) > -1 THEN
POP = STACK(UBOUND(STACK))
REDIM PRESERVE STACK(UBOUND(STACK) - 1)
END IF
END FUNCTION
FUNCTION STACKISEMPTY(STACK)
IF UBOUND(STACK) > -1 THEN
STACKISEMPTY = FALSE
ELSE
STACKISEMPTY = TRUE
END IF
END FUNCTION
FUNCTION PEEK(STACK)
IF UBOUND(STACK) > -1 THEN
PEEK = STACK(UBOUND(STACK))
END IF
END FUNCTION
source to share
You can handle "FUN", "(", ")", "," similar to other operators, and they can be pressed on TOKEN_STACK. (I have abbreviated FUNCTION to FUN for brevity.) "FUN", "(", ")", "," have a lower priority than "+", so the priority table looks like
^ 4
* / % 3
+ - 2
( ) , FUN 1
Consider what happens when 1 + FUN (2 * 3) is parsed
Remaining Input Stack Output
1+FUN(2*3)
+FUN(2*3) 1
FUN(2*3) + 1
(2*3) + FUN 1
2*3) + FUN ( 1
*3) + FUN ( 1 2
3) + FUN ( * 1 2
) + FUN ( * 1 2 3
+ FUN ( * ) 1 2 3 Note 1
+ FUN ( ) 1 2 3 * Note 2
+ 1 2 3 * FUN()
1 2 3 * FUN() +
The output token "FUN ()" means function evaluation.
Note 1: when we try to push ")" onto the stack, all higher priority operators are popped off the stack and pushed to output.
Note 2: When an item at the end of the stack matches "(" it is popped from the stack. There are two cases of handling, simple parenthesis matching as with 1 + (2 * 3) or if there is a function token before "(" on the stack. In this case, you get FUN from the stack and add a function token to output.
"," will be treated the same way "), which will result in higher-priority operators exiting. But this will not affect the function's output. You need some way to record how many arguments the function has.
In my code, I am using a recursion based algorithm. The parsing algorithm can be called recursively and provided with a stop marker. When a stop marker is encountered, it is returned from recursion. When a function is encountered, it calls a recursion that expects a "," or ")".
I am able to run iut using the cscript command line. I also added some debug code using Wscript.Echo.
Looking at TOKEN_STACK you never pushed the FUNCTION token onto the stack, so it wasn't there when you looked for it.
Adding a CASE "FUNCTION" CALL PUSH (INPUT (TOKEN_NUMBER), TOKEN_STACK)
seems to give the right thing. Though I'm not sure what should happen when you agree with the first closing parenthesis. It also doesn't work well with input like "E + FUNCTION ((A, B), C) + F".
SET PRECEDENCE = CREATEOBJECT("SCRIPTING.DICTIONARY")
WITH PRECEDENCE
.ADD "^",3
.ADD "*",2
.ADD "/",2
.ADD "%",2
.ADD "+",1
.ADD "-",1
.ADD "FUNCTION",0
.ADD "(",0
.ADD ",",0
.ADD ")",0
END WITH
Wscript.Echo "Start"
'#############################################################################
tokenArray = split("FUNCTION ( ( A , B ) , C )")
'#tokenArray = split("A + B * C")
Wscript.Echo "Result " + SHUNTINGYARD(tokenArray)
'#############################################################################
FUNCTION SHUNTINGYARD(INPUT)
TOKEN_QUEUE = ARRAY()
TOKEN_STACK = ARRAY()
FOR TOKEN_NUMBER = 0 TO UBOUND(INPUT)
Wscript.Echo "Token " + INPUT(TOKEN_NUMBER)
Wscript.Echo "Stack " + JOIN(TOKEN_STACK,"|")
SELECT CASE INPUT(TOKEN_NUMBER)
CASE "("
CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_STACK)
CASE ")"
DO WHILE NOT( PRECEDENCE( PEEK(TOKEN_STACK) ) = 0 )
CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
IF STACKISEMPTY(TOKEN_STACK) THEN CALL ERRORS("Can't find a matching ""("".", TRUE)
LOOP
IF PEEK(TOKEN_STACK) = "FUNCTION" THEN
DISCARD = POP(TOKEN_STACK)
CALL PUSH("@", TOKEN_QUEUE)
ELSE
DISCARD = POP(TOKEN_STACK)
END IF
CASE ","
DO WHILE NOT( PRECEDENCE( PEEK(TOKEN_STACK) ) = 0 )
CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
IF STACKISEMPTY(TOKEN_STACK) THEN CALL ERRORS("Can't find a matching function ""("".", TRUE)
LOOP
CASE "+","-","*","/","^","%"
TOKEN_A = INPUT(TOKEN_NUMBER)
DO WHILE ISOPERATOR(PEEK(TOKEN_STACK))
TOKEN_B = PEEK(TOKEN_STACK)
IF (ASSOCIATIVITY(TOKEN_B) = "left" AND PRECEDENCE(TOKEN_A) = PRECEDENCE(TOKEN_B)) OR (PRECEDENCE(TOKEN_A) < PRECEDENCE(TOKEN_B)) THEN
CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
ELSE
EXIT DO
END IF
LOOP
CALL PUSH(TOKEN_A, TOKEN_STACK)
CASE "FUNCTION"
CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_STACK)
CASE ELSE
CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_QUEUE)
END SELECT
NEXT
FOR ITEMCOUNT = 0 TO UBOUND(TOKEN_STACK)
IF PEEK(TOKEN_STACK) = "(" THEN CALL ERRORS("Can't find a matching "")"".", TRUE)'(
CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
NEXT
SHUNTINGYARD = JOIN(TOKEN_QUEUE,"|")
END FUNCTION
'#############################################################################
FUNCTION ASSOCIATIVITY(ASSOC)
SELECT CASE LCASE(ASSOC)
CASE "^","\"
ASSOCIATIVITY = "right"
CASE ELSE
ASSOCIATIVITY = "left"
END SELECT
END FUNCTION
FUNCTION ISOPERATOR(ITEM)
ISOPERATOR = LEN(ITEM) = 1 AND INSTR("+-*/%^",ITEM)
END FUNCTION
SUB PUSH(ITEM,BYREF STACK)
IF UBOUND(STACK) > -1 THEN
REDIM PRESERVE STACK(UBOUND(STACK) + 1)
STACK(UBOUND(STACK)) = ITEM
ELSE
STACK = ARRAY(ITEM)
END IF
END SUB
FUNCTION POP(BYREF STACK)
IF UBOUND(STACK) > -1 THEN
POP = STACK(UBOUND(STACK))
REDIM PRESERVE STACK(UBOUND(STACK) - 1)
END IF
END FUNCTION
FUNCTION STACKISEMPTY(STACK)
IF UBOUND(STACK) > -1 THEN
STACKISEMPTY = FALSE
ELSE
STACKISEMPTY = TRUE
END IF
END FUNCTION
FUNCTION PEEK(STACK)
IF UBOUND(STACK) > -1 THEN
PEEK = STACK(UBOUND(STACK))
END IF
END FUNCTION
source to share