Yesterday I've released SteelKix 0.2.2!

Get it here:
http://steelkix.codeplex.com/

  • Added windows executable (does not show console)
  • Added calculator example script (with a scanner, parser and evaluator)
  • Fixed reporting of error messages related to user defined functions and the binary operation binder
  • Fixed invocation of user defined function with null values
  • Fixed issues with return value of user defined functions being null on exit
  • Added "assert" setoption for showing assertion dialogs on exceptions
  • SteelKixConsole now exits after script has finished.


Included is an example of a calculator that can parse and evaluate simple math expressions.

How to run this example:



 Code:
imports $system = system
imports $io = system.io

global $tokens = 
			{
				LeftPara=1,
				RightPara=2,
				Star=3,
				Plus=4,
				Min=5,
				Number=6,
				Div=7,
				Var=8
			}
			
;creates context for scanner
function scanner_create($expr)
	if $system.string.isnullorempty($expr) 
		throw '$expr should not be null or empty'
	endif
	
	$scanner_create = {
			PeekToken=nothing,
			Reader = $io.StringReader($expr)
		   }
endfunction

;converts object to character
function ToChar($c)
	$ToChar = $System.Convert.ToChar($c)
endfunction

;reads token from scanner state
function scanner_read($state)
	dim $c = 0
	dim $builder = $System.Text.StringBuilder()
	dim $length = 0
	
	if $state.PeekToken <> nothing
		dim $ret = $state.PeekToken
		$state.PeekToken = nothing
		$scanner_read = $ret
		exit 0
	endif
	
	$c = $state.Reader.Read

	while $c > 0
		dim $char = ToChar($c)
		
		select
			case $System.Char.IsWhitespace($char)
				$c = $state.Reader.Read
				continue
			case $System.Char.IsNumber($char)
				$length = $builder.Append($char)
				$c = $state.Reader.Peek
				
				while $c > 0
					$char = ToChar($c)
					if $System.Char.IsNumber($char) Or $char = "."
						$length = $builder.Append($char)
						$length = $state.Reader.Read
						$c = $state.Reader.Peek
					else
						break
					endif
				loop
				
				$scanner_read = {
									Token = $tokens.Number,
									Value = $builder.ToString
								}
			case $System.Char.IsLetter($char)
				$scanner_read = {
									Token = $tokens.Var,
									Value = $char
								}
			case $char = "+"
				$scanner_read = {
									Token = $tokens.Plus,
									Value = $char
								}
			case $char = "-"
				$scanner_read = {
									Token = $tokens.Min,
									Value = $char
								}
			case $char = "*"
				$scanner_read = {
									Token = $tokens.Star,
									Value = $char
								}
			case $char = "/"
				$scanner_read = {
									Token = $tokens.Div,
									Value = $char
								}
			case $char = "("
				$scanner_read = {
									Token = $tokens.LeftPara,
									Value = $char
								}			
			case $char = ")"
				$scanner_read = {
									Token = $tokens.RightPara,
									Value = $char
								}			
		endselect		
	
		if $scanner_read = nothing
			throw 'Unsupported token'
		endif
		
		break
	loop
endfunction

;peeks token from scanner context
function scanner_peek($state)
	if $state.PeekToken = nothing
		$state.PeekToken = scanner_read($state)
	endif
	
	$scanner_peek = $state.PeekToken
endfunction

;parses expression
function parser_parseExpression($scanner)
	$parser_parseExpression = parser_parseMinPlus($scanner)
endfunction

;parses final expression (atom/variable/parenthesis)
function parser_parseFinal($scanner)
	dim $token = scanner_peek($scanner)
	dim $swallow
	
	select
		case $token.token = $tokens.Number Or $token.token = $tokens.Var
			$parser_parseFinal = {
									Token = $token,
									Left=nothing,
									Right=nothing
								}
			$swallow = scanner_read($scanner)
		case $token.token = $tokens.LeftPara
			$swallow = scanner_read($scanner)
			
			$parser_parseFinal = parser_parseExpression($scanner)
			
			$swallow = scanner_read($scanner)
	endselect
endfunction

;parses multiplication and division
function parser_parseMulDiv($scanner)
	dim $left = parser_parseFinal($scanner)
	dim $swallow
	dim $token = scanner_peek($scanner)
	
	while $token <> nothing and 
				( $token.token = $tokens.Star or $token.token = $tokens.Div )
	
		$swallow = scanner_read($scanner)
		
		dim $expr = {
						Token = $token,
						Left = $left,
						Right = parser_parseFinal($scanner)
					}
					
		$left = $expr
		
		$token = scanner_peek($scanner)	
	loop
	
	$parser_parseMulDiv = $left
endfunction

;parses addition or subtraction
function parser_parseMinPlus($scanner)
	dim $left = parser_parseMulDiv($scanner)
	dim $swallow
	
	dim $token = scanner_peek($scanner)
	
	while $token <> nothing and 
				( $token.token = $tokens.Plus or $token.token = $tokens.Min )
		
		$swallow = scanner_read($scanner)
		
		dim $expr = {
						Token = $token,
						Left = $left,
						Right = parser_parseMulDiv($scanner)
					}
					
		$left = $expr
		
		$token = scanner_peek($scanner)				
	loop
	
	$parser_parseMinPlus = $left
endfunction

;prints expression tree
function parser_print($expr, $ident)
	for $i = 1 to $ident
		" "
	next 
	
	$expr.token.value ?
	
	if $expr.left <> nothing
		parser_print($expr.left, $ident+1)
	endif
	
	if $expr.right <> nothing
		parser_print($expr.right, $ident+1)
	endif	
endfunction

;evaluate expression tree
function evaluator_eval($expr, optional $variables)
	dim $left, $right
	
	select
		case $expr.token.token = $tokens.Plus
			$left = evaluator_eval($expr.left, $variables)
			$right = evaluator_eval($expr.right, $variables)
			$evaluator_eval = $left + $right
		case $expr.token.token = $tokens.Min
			$left = evaluator_eval($expr.left, $variables)
			$right = evaluator_eval($expr.right, $variables)
			$evaluator_eval = $left - $right
		case $expr.token.token = $tokens.Star
			$left = evaluator_eval($expr.left, $variables)
			$right = evaluator_eval($expr.right, $variables)
			$evaluator_eval = $left * $right
		case $expr.token.token = $tokens.Div
			$left = evaluator_eval($expr.left, $variables)
			$right = evaluator_eval($expr.right, $variables)
			$evaluator_eval = $left / $right
		case $expr.token.token = $tokens.number
			$evaluator_eval = $System.Convert.ToDouble($expr.token.value, $System.Globalization.NumberFormatInfo() With { NumberDecimalSeparator = "." })
		case $expr.token.token = $tokens.var
			if not $variables
				throw 'Variable referenced but not defined'
			endif
			$evaluator_eval = $System.Convert.ToDouble($variables[$expr.token.value])
	endselect
	
endfunction

;"1+10 * (1+2)"

"Enter an expression. Only multiplication, addition, subtraction and division is supported. Parenthesis can be used." ?
"Example: 1+10 * (1+2)" ?
"Type exit to quit" ?

dim $input

gets $input

while $input <> "exit"
	dim $scannerState = scanner_create($input)
	dim $expr = parser_parseExpression($scannerState)
	"Result: " evaluator_eval($expr, nothing) ?
	"Tree: " ? 
	parser_print($expr, 0)
	gets $input
loop