Code archives/Algorithms/Shunting yard algorithm

This code has been declared by its author to be Public Domain code.

Download source code

Shunting yard algorithm by Warpy2009
viz. http://en.wikipedia.org/wiki/Shunting-yard_algorithm

If you ever want to convert standard infix maths notation to postfix notation, like RPN.

Also supports functions, because that was in the wikipedia description of the algorithm!
Type shuntingyard
	Field in$
	Field out$
	Field ops$[][]
	Field token$[]
	
	Method parse$()
		While in
			nexttoken()
			Select token[0]
			Case "number"
				output
			Case "function"
				push
			Case ","
				While ops[0][0]<>"("
					pop
					If token[0]<>"(" output
				Wend
			Case "("
				push
			Case ")"
				While pop()<>"("
					output
				Wend
				If ops[0][0]="function"
					pop
					output
				EndIf
			Default
				If token[0][..2]="op"
					Local otoken$[]=token
					op=Int(token[0][2..])
					While Len(ops) And ops[0][0][..2]="op" And Int(ops[0][0][2..])<op
						pop
						output
					Wend
					token=otoken
					push
				EndIf
			End Select
		Wend
		While Len(ops)
			pop
			output
		Wend
		Return out
	End Method
	
	Method nexttoken()
		Select Chr(in[0])
		Case "0","1","2","3","4","5","6","7","8","9","0"
			n=0
			While n<Len(in) And in[n]>47 And in[n]<58
				n:+1
			Wend
			token=["number",in[..n]]
			in=in[n..]
		Case "*","/"
			token=["op1",in[..1]]
			in=in[1..]
		Case "+","-"
			token=["op2",in[..1]]
			in=in[1..]
		Case "("
			token=["(","("]
			in=in[1..]
		Case ")"
			token=[")",")"]
			in=in[1..]
		Case ","
			token=[",",","]
			in=in[1..]
		Default
			n=0
			While in[n]<>Asc("(")
				n:+1
			Wend
			token=["function",in[..n]]
			in=in[n..]
		End Select
	End Method
				
	Method pop$()
		token=ops[0]
		ops=ops[1..]
		Return token[0]
	End Method
	
	Method push()
		ops=[token]+ops
	End Method	
	
	Method output()
		out:+token[1]+" "
	End Method
End Type

Function shunt$(in$)
	s:shuntingyard=New shuntingyard
	s.in=in
	Return s.parse()
End Function


While 1
	Print shunt(Input())
Wend

Comments

Grey Alien2009
Bah, I thought it was about trains when I read the title.


markcw2009
Where are the trains?


Nate the Great2009
Thanks! Ive been looking for an algorithm like this for a small project of mine but I coulnt find it.


Chroma2009
Warpy, you just solved time travel!


Warpy2010
Rewritten to separate out tokenising/shunting yard/evaluation steps:

Function tokenise$[][](in$)
	Local tokens$[][]
	i=0
	While i<Len(in)
		c=in[i]
		Select c
		Case 48,49,50,51,52,53,54,55,56,57		' 0 - 9
			s=i
			i:+1
			While i<Len(in) And in[i]>47 And in[i]<58
				i:+1
			Wend
			If i<Len(in) And in[i]=46
				i:+1
				While i<Len(in) And in[i]>47 And in[i]<58
					i:+1
				Wend
				n:Double=Float(in[s..i])
				tokens:+[["number",String(n)]]
			Else
				tokens:+[["number",in[s..i]]]
			EndIf
		Case 42,43,45,47,94			' * + - / ^
			tokens:+[["op",Chr(c)]]
			i:+1
		Case 40					' (
			tokens:+[["(","("]]
			i:+1
		Case 41					' )
			tokens:+[[")",")"]]
			i:+1
		Case 44					' ,
			tokens:+[[",",","]]
			i:+1
		Case 32					' space
			i:+1
		Default					' might be a name or an invalid character
			If (c>64 And c<91) Or (c>96 And c<123)
				s=i
				While i<Len(in) And ((in[i]>64 And in[i]<91) Or (in[i]>96 And in[i]<123) Or (in[i]>47 And in[i]<58))
					i:+1
				Wend
				name$=in[s..i]
				tokens:+[["name",name]]
			Else
				Print "whoops "+Chr(c)
			EndIf
		End Select
	Wend
'	For i=0 To Len(tokens)-1
'		Print tokens[i][0]+"~t~t"+tokens[i][1]
'	Next
	Return tokens
End Function

Function precedence(op$)
	Select op
	Case "^"
		Return 1
	Case "*","/"
		Return 2
	Case "+","-"
		Return 3
	End Select
End Function

Function shunt$[][](tokens$[][])
	Local output$[][]
	Local stack$[][]
	Local t$[]
	i=0
	While i<Len(tokens)
		t=tokens[i]
		Select t[0]
		Case "number"
			output=[t]+output
		Case "name"
			If i<Len(tokens)-1 And tokens[i+1][0]="("
				stack=[t]+stack
			Else
				output=[t]+output
			EndIf
		Case ","
			li=0
			While li<Len(stack) And stack[li][0]<>"("
				output=[stack[li]]+output
				li:+1
			Wend
			If li=Len(stack)
				Print "whoops no ( matching comma "+String(i)
			Else
				stack=stack[li..]
			EndIf
		Case "op"
			o1=precedence(t[1])
			Print o1
			While Len(stack) And stack[0][0]="op" And o1>=precedence(stack[0][1])
				output=[stack[0]]+output
				stack=stack[1..]
			Wend
			stack=[t]+stack
		Case "("
			stack=[t]+stack
		Case ")"
			li=0
			While li<Len(stack) And stack[li][0]<>"("
				output=[stack[li]]+output
				li:+1
			Wend
			If li=Len(stack)
				Print "whoops no ( matching ) "+String(i)
			Else
				stack=stack[li+1..]
				If Len(stack)>0 And stack[0][0]="name"
					output=[stack[0]]+output
					stack=stack[1..]
				EndIf
			EndIf
			
		End Select
		i:+1
	Wend
	For i=0 To Len(stack)-1
		If stack[i][0]="(" Or stack[i][0]=")"
			Print "whoops mismatched parenthesis on stack"
		Else
			output=[stack[i]]+output
		EndIf
	Next
	For i=0 To Len(output)/2-1
		t=output[i]
		output[i]=output[Len(output)-i-1]
		output[Len(output)-i-1]=t
	Next
'	For i=0 To Len(output)-1
'		Print output[i][0]+"~t~t"+output[i][1]
'	Next
	Return output
End Function

Function eval:Double(expr$[][],varnames$[],varvalues:Double[])
	If Len(varnames)<>Len(varvalues)
		Print "whoops not the same number of varnames as varvalues"
	EndIf
	Local stack:Double[]
	Local t$[]
	Local r:Double
	For i=0 To Len(expr)-1
		t=expr[i]
		Select t[0]
		Case "number"
			stack=[Double(t[1])]+stack
		Case "op"
			If Len(stack)<2
				Print "whoops need two things on stack to do op"
				Return
			EndIf
			b:Double=stack[0]
			a:Double=stack[1]
			Select t[1]
			Case "^"
				r=a^b
			Case "*"
				r=a*b
			Case "/"
				r=a/b
			Case "+"
				r=a+b
			Case "-"
				r=a-b
			End Select
			stack=[r]+stack[2..]
		Case "name"
			Select t[1]
			Case "ln"
				a:Double=stack[0]
				stack=stack[1..]
				r=Log(a)
			Case "sin"
				a:Double=stack[0]
				stack=stack[1..]
				r=Sin(a)
			Case "cos"
				a:Double=stack[0]
				stack=stack[1..]
				r=Cos(a)
			Case "e"
				r=Exp(1)
			Case "pi"
				r=Pi
			Case "f"	'function definition!
				a:Double=stack[0]
				stack=stack[1..]
				r=eval(shunt(tokenise("superduper^2+1")),varnames+["superduper"],varvalues+[a])
			Default
				For j=0 To Len(varnames)-1
					If varnames[j]=t[1]
						r=varvalues[j]
						Exit
					EndIf
				Next
			End Select
			stack=[r]+stack
		End Select
	Next
	
	If Len(stack)<>1
		Print "whoops "+String(Len(stack))+" things on stack"
	Else
		Return stack[0]
	EndIf
End Function

Local tokens$[][]
Local stack$[][]
While 1
	tokens=tokenise(Input())
	Print "------------------------------"
	stack=shunt(tokens)
	
	Local varnames$[0]
	Local varvalues:Double[0]
	For i=0 To Len(stack)-1
		If stack[i][0]="name"
			Select stack[i][1]
			Case "ln","cos","sin","e","pi"
			Default
				varnames:+[stack[i][1]]
				varvalues:+[Rnd(1,2)]
				Print stack[i][1]+"~t~t"+String(varvalues[Len(varvalues)-1])
			End Select
		EndIf
	Next
	
	Print eval(stack,varnames,varvalues)
Wend



Danny2012
This is excellent. But something like "1 * -10" or "-1 * 10" doesn't work :((
- I get the message: "whoops need two things on stack to do op"

But I have no idea how to solve this. Any ideas?

D.


Warpy2012
Here you go.

Function tokenise$[][](in$)
	Local tokens$[][]
	i=0
	While i<Len(in)
		c=in[i]
		Select c
		Case 48,49,50,51,52,53,54,55,56,57		' 0 - 9
			s=i
			i:+1
			While i<Len(in) And in[i]>47 And in[i]<58
				i:+1
			Wend
			If i<Len(in) And in[i]=46
				i:+1
				While i<Len(in) And in[i]>47 And in[i]<58
					i:+1
				Wend
				n:Double=Float(in[s..i])
				tokens:+[["number",String(n)]]
			Else
				tokens:+[["number",in[s..i]]]
			EndIf
		Case 42,43,45,47,94			' * + - / ^
			tokens:+[["op",Chr(c)]]
			i:+1
		Case 40					' (
			tokens:+[["(","("]]
			i:+1
		Case 41					' )
			tokens:+[[")",")"]]
			i:+1
		Case 44					' ,
			tokens:+[[",",","]]
			i:+1
		Case 32					' space
			i:+1
		Default					' might be a name or an invalid character
			If (c>64 And c<91) Or (c>96 And c<123)
				s=i
				While i<Len(in) And ((in[i]>64 And in[i]<91) Or (in[i]>96 And in[i]<123) Or (in[i]>47 And in[i]<58))
					i:+1
				Wend
				name$=in[s..i]
				tokens:+[["name",name]]
			Else
				Print "whoops "+Chr(c)
			EndIf
		End Select
	Wend
'	For i=0 To Len(tokens)-1
'		Print tokens[i][0]+"~t~t"+tokens[i][1]
'	Next
	Return tokens
End Function

Function precedence(op$)
	Select op
	Case "^"
		Return 1
	Case "*","/"
		Return 2
	Case "+","-"
		Return 3
	End Select
End Function

Function arity(op$)
	Select op
	Case "-u","+u"
		Return 1
	Default
		Return 2
	End Select
End Function

Function shunt$[][](tokens$[][])
	Local output$[][]
	Local stack$[][]
	Local t$[]
	i=0
	While i<Len(tokens)
		t=tokens[i]
		Select t[0]
		Case "number"
			output=[t]+output
		Case "name"
			If i<Len(tokens)-1 And tokens[i+1][0]="("
				stack=[t]+stack
			Else
				output=[t]+output
			EndIf
		Case ","
			li=0
			While li<Len(stack) And stack[li][0]<>"("
				output=[stack[li]]+output
				li:+1
			Wend
			If li=Len(stack)
				Print "whoops no ( matching comma "+String(i)
			Else
				stack=stack[li..]
			EndIf
		Case "op"
			o1=precedence(t[1])
			Print o1
			If i=0 Or tokens[i-1][0]="(" Or tokens[i-1][0]=","
				t[1]:+"u"
			EndIf
			
			While Len(stack) And stack[0][0]="op" And o1>=precedence(stack[0][1])
				output=[stack[0]]+output
				stack=stack[1..]
			Wend
			stack=[t]+stack
		Case "("
			stack=[t]+stack
		Case ")"
			li=0
			While li<Len(stack) And stack[li][0]<>"("
				output=[stack[li]]+output
				li:+1
			Wend
			If li=Len(stack)
				Print "whoops no ( matching ) "+String(i)
			Else
				stack=stack[li+1..]
				If Len(stack)>0 And stack[0][0]="name"
					output=[stack[0]]+output
					stack=stack[1..]
				EndIf
			EndIf
			
		End Select
		i:+1
	Wend
	For i=0 To Len(stack)-1
		If stack[i][0]="(" Or stack[i][0]=")"
			Print "whoops mismatched parenthesis on stack"
		Else
			output=[stack[i]]+output
		EndIf
	Next
	For i=0 To Len(output)/2-1
		t=output[i]
		output[i]=output[Len(output)-i-1]
		output[Len(output)-i-1]=t
	Next
'	For i=0 To Len(output)-1
'		Print output[i][0]+"~t~t"+output[i][1]
'	Next
	Return output
End Function

Function eval:Double(expr$[][],varnames$[],varvalues:Double[])
	If Len(varnames)<>Len(varvalues)
		Print "whoops not the same number of varnames as varvalues"
	EndIf
	Local stack:Double[]
	Local t$[]
	Local r:Double
	For i=0 To Len(expr)-1
		t=expr[i]
		Select t[0]
		Case "number"
			stack=[Double(t[1])]+stack
		Case "op"
			ar = arity(t[1])
			Print "op: "+t[1]+" ar: "+ar
			If Len(stack)<ar
				Print "whoops need "+ar+" things on stack to do op, got "+Len(stack)
				Return
			EndIf
			Select t[1]
			Case "^"
				r=stack[1]^stack[0]
			Case "*"
				r=stack[1]*stack[0]
			Case "/"
				r=stack[1]/stack[0]
			Case "+"
				r=stack[1]+stack[0]
			Case "-"
				r=stack[1]-stack[0]
			Case "+u"
				r=stack[0]
			Case "-u"
				r=-stack[0]
			End Select
			stack=[r]+stack[ar..]
		Case "name"
			Select t[1]
			Case "ln"
				a:Double=stack[0]
				stack=stack[1..]
				r=Log(a)
			Case "sin"
				a:Double=stack[0]
				stack=stack[1..]
				r=Sin(a)
			Case "cos"
				a:Double=stack[0]
				stack=stack[1..]
				r=Cos(a)
			Case "e"
				r=Exp(1)
			Case "pi"
				r=Pi
			Case "f"	'function definition!
				a:Double=stack[0]
				stack=stack[1..]
				r=eval(shunt(tokenise("superduper^2+1")),varnames+["superduper"],varvalues+[a])
			Default
				For j=0 To Len(varnames)-1
					If varnames[j]=t[1]
						r=varvalues[j]
						Exit
					EndIf
				Next
			End Select
			stack=[r]+stack
		End Select
	Next
	
	If Len(stack)<>1
		Print "whoops "+String(Len(stack))+" things on stack"
	Else
		Return stack[0]
	EndIf
End Function

Local tokens$[][]
Local stack$[][]
While 1
	tokens=tokenise(Input())
	Print "------------------------------"
	stack=shunt(tokens)
	
	Local varnames$[0]
	Local varvalues:Double[0]
	For i=0 To Len(stack)-1
		If stack[i][0]="name"
			Select stack[i][1]
			Case "ln","cos","sin","e","pi"
			Default
				varnames:+[stack[i][1]]
				varvalues:+[Rnd(1,2)]
				Print stack[i][1]+"~t~t"+String(varvalues[Len(varvalues)-1])
			End Select
		EndIf
	Next
	
	Print eval(stack,varnames,varvalues)
Wend



Code Archives Forum