Page 1 of 2 12>
Topic Options
#196266 - 2009-10-06 10:03 PM SteelKix 0.22
WvS Offline
Fresh Scripter
*****

Registered: 2009-05-30
Posts: 42
Loc: Netherlands
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

Top
#196267 - 2009-10-06 10:38 PM Re: SteelKix 0.22 [Re: WvS]
BoForce Offline
Fresh Scripter
**

Registered: 2005-10-22
Posts: 36
Loc: Leeuwarden, The Netherlands
Hi WvS,

Great!

I've just downloaded and tested the new release of SteelKix. Although I tested just a few items, I did found out that my macros do not work as expected.

Example:
Macro @DATE

 Code:
Macro DATE
Dim $dtn,$rc
Imports $dtn = System.DateTime.Now

Try
   $rc = $dtn.ToString("yyy/MM/dd")
EndTry
Catch
   $e
EndCatch

If $e
   $DATE = $e.ToString()
Else
   $DATE = $rc
EndIf
EndMacro


Result SteelKix 0.2
 Code:
2009/10/06


Result SteelKix 0.2.2
 Code:


No typo here. The result with 0.2.2 is nothing.

Top
#196268 - 2009-10-06 11:30 PM Re: SteelKix 0.22 [Re: BoForce]
Allen Administrator Online   shocked
KiX Supporter
*****

Registered: 2003-04-19
Posts: 4545
Loc: USA
Jedi Bo (or is it Lord Force? ;\) ), for us less acclimated, would you mind explaining the use for Try and Catch, and specifically $e. This is not anything kix has and it would be great to understand it.

Thanks.

Top
#196269 - 2009-10-07 12:59 AM Re: SteelKix 0.22 [Re: Allen]
Lonkero Administrator Offline
KiX Master Guru
*****

Registered: 2001-06-05
Posts: 22346
Loc: OK
heh.
this is one of the things kixtart is so awesome and sometimes little too idiot proof.
_________________________
!

download KiXnet

Top
#196270 - 2009-10-07 08:27 AM Re: SteelKix 0.22 [Re: Lonkero]
WvS Offline
Fresh Scripter
*****

Registered: 2009-05-30
Posts: 42
Loc: Netherlands
OK, I confirm there is a bug with macro's. I will fix this tonight, I need to get a few macro's in my test suite..
Top
#196271 - 2009-10-07 09:25 AM Re: SteelKix 0.22 [Re: Allen]
BoForce Offline
Fresh Scripter
**

Registered: 2005-10-22
Posts: 36
Loc: Leeuwarden, The Netherlands
Hi Allen,

Well it's just BoForce. Nothing special.

How to explain Try-EndTry Catch-EndCatch. I'm just starting to understand what this does. I could Try, but for you to Catch it ;\) you could read this http://msdn.microsoft.com/en-us/library/system.exception(VS.80).aspx

The info helped me to get started.

Please note that although this may look like @SERROR in Kix it isn't.
In the example below I will use the function DelKey with a none existing key as the Exception is easy to trigger.

Kix >
 Code:
DelKey('HKEY_CURRENT_USER\Test\New\some\Folder\Task')
?@SERROR


Result:
 Code:
The system cannot find the file specified.



SteelKix:
 Code:
? DelKey('HKEY_CURRENT_USER\Test\New\some\Folder\Task')


Result:
 Code:
Cannot delete a subkey tree because the subkey does not exist.


SteelKix DelKey Function:
 Code:
;SteelKix Function: DelKey()
;
;Author:		Boforce
;
;Contributors:	None
;
;Action:		Deletes the specified subkey from the registry
;
;Syntax:		DelKey("key")
;
;Version:		1.0
;
;Date:		2009-09-25
;
;Date Revised:	2009-09-25
;
;Parameters	Key
;		Required.	A string that specifies the name of the subkey you want to delete
;
;Remarks:		None
;
;Returns:		Returns nothing if the key is deleted else an error message
;
;Dependencies:
; SteelKix version: 0.2
; Tested with Dot Net 3.5.30729.01
;
;Example(s):	Delete some key:
;		$DeleteKey = DelKey('HKEY_CURRENT_USER\Test\Some\New\Key')
;		? 'Deleted key : ' + $DeleteKey
;
;Comments : This has been successfully tested on Windows XP SP3 with DOT NET 3.5.30729.01.
;
;Source:
Function DelKey($Key)
Dim $Win32,$Hive,$Action

Imports $Win32 = Microsoft.Win32

$Hive = $Key.ToString.Split("\".ToCharArray())[0]
$Key = $Key.Remove($Hive+'\',$Hive.Length+1)

Select
   Case $Hive = 'HKEY_CLASSES_ROOT' Or $Hive = 'HKCR'
	$Hive = $Win32.Registry.ClassesRoot
   Case $Hive = 'HKEY_CURRENT_USER' Or $Hive = 'HKCU'
	$Hive = $Win32.Registry.CurrentUser
   Case $Hive = 'HKEY_LOCAL_MACHINE' Or $Hive = 'HKLM'
	$Hive = $Win32.Registry.LocalMachine
   Case $Hive = 'HKEY_USERS' Or $Hive = 'HKU'
	$Hive = $Win32.Registry.Users
   Case 1
	$atHive = 'Error'
EndSelect

If $atHive = 'Error'
   $AddKey = 'Unknown hive'
Else
   Try
	 $Action = $Hive.DeleteSubKey($Key,1)
   EndTry
   Catch
	 $e
   EndCatch

   If $e
	 $DelKey = $e.Message
   Else
	 $DelKey = $Action
   EndIf
EndIf
EndFunction


Note that in this case I use $e.Message because the $e.ToString() would result in:
 Code:
System.ArgumentException: Cannot delete a subkey tree because the subkey does not exist.
   at Microsoft.Scripting.Interpreter.ThrowInstruction.Run(InterpretedFrame frame)
   at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)

Top
#196272 - 2009-10-07 09:29 AM Re: SteelKix 0.22 [Re: WvS]
BoForce Offline
Fresh Scripter
**

Registered: 2005-10-22
Posts: 36
Loc: Leeuwarden, The Netherlands
Hi WvS,

Looking forward to the next release.
You mentioned that you need a few macro's in your test suite. Do you want me to post a few?

Top
#196273 - 2009-10-07 09:31 AM Re: SteelKix 0.22 [Re: BoForce]
WvS Offline
Fresh Scripter
*****

Registered: 2009-05-30
Posts: 42
Loc: Netherlands
I'll just copy/paste them from the other posts, but if you have more, then yes please do!
Top
#196275 - 2009-10-07 09:44 AM Re: SteelKix 0.22 [Re: WvS]
BoForce Offline
Fresh Scripter
**

Registered: 2005-10-22
Posts: 36
Loc: Leeuwarden, The Netherlands
Ok. Just a few more.

 Code:
;SteelKix Macro: @TIME
;
;Author:		Boforce
;
;Contributors:	Richard von Mallesch (It took my meds)
;
;Action:		Retrieve current Time 
;
;Syntax:		@TIME
;
;Version:		1.0
;
;Date:		2009-09-28
;
;Date Revised:	2009-09-28
;
;Parameters	None
;
;Remarks:		None
;
;Returns:		Time (in the format HH:MM:SS) 
;
;Dependencies:
; SteelKix version: 0.2
; Tested with Dot Net 3.5.30729.01
;
;Example(s):	Get Current Time:
;		? @TIME
;		Result > 10:23:15
;
;Comments : This has been successfully tested on Windows XP SP3 with DOT NET 3.5.30729.01.
;
;Source:
Macro TIME
Dim $dtn,$rc
Imports $dtn = System.DateTime.Now

Try
   $rc = $dtn.ToString("HH:mm:ss")
EndTry
Catch
   $e
EndCatch

If $e
   $TIME = $e.Message
Else
   $TIME = $rc
EndIf
EndMacro


;SteelKix Macro: @DAY
;
;Author:		Boforce
;
;Contributors:	Richard von Mallesch (It took my meds)
;
;Action:		Retrieve day of the week 
;
;Syntax:		@DAY
;
;Version:		1.0
;
;Date:		2009-09-28
;
;Date Revised:	2009-09-28
;
;Parameters	None
;
;Remarks:		None
;
;Returns:		Day (Monday, Tuesday, etc.) 
;
;Dependencies:
; SteelKix version: 0.2
; Tested with Dot Net 3.5.30729.01
;
;Example(s):	Get day of the week:
;		? @DAY
;		Result > Monday
;
;Comments : This has been successfully tested on Windows XP SP3 with DOT NET 3.5.30729.01.
;
;Source:
Macro DAY
Dim $dtn,$rc
Imports $dtn = System.DateTime.Now

Try
   $rc = $dtn.DayOfWeek
EndTry
Catch
   $e
EndCatch

If $e
   $DAY = $e.Message
Else
   $DAY = $rc
EndIf
EndMacro

;SteelKix Macro: @MONTHNO
;
;Author:		Boforce
;
;Contributors:	Richard von Mallesch (It took my meds)
;
;Action:		Retrieve the number of the current month of the year 
;
;Syntax:		@MONTHNO
;
;Version:		1.0
;
;Date:		2009-09-28
;
;Date Revised:	2009-09-28
;
;Parameters	None
;
;Remarks:		None
;
;Returns:		Month number, beginning with January (1-12)
;
;Dependencies:
; SteelKix version: 0.2
; Tested with Dot Net 3.5.30729.01
;
;Example(s):	Get month number of the year:
;		? @MONTHNO
;		Result > 10 (if month is October)
;
;Comments : This has been successfully tested on Windows XP SP3 with DOT NET 3.5.30729.01.
;
;Source:
Macro MONTHNO
Dim $dtn,$rc
Imports $dtn = System.DateTime.Now

Try
   $rc = $dtn.Month
EndTry
Catch
   $e
EndCatch

If $e
   $MONTHNO = $e.Message
Else
   $MONTHNO = $rc
EndIf
EndMacro

;SteelKix Macro: @MONTH
;
;Author:		Boforce
;
;Contributors:	Richard von Mallesch (It took my meds)
;
;Action:		Retrieve the name of the current month of the year
;
;Syntax:		@MONTH
;
;Version:		1.0
;
;Date:		2009-09-28
;
;Date Revised:	2009-09-28
;
;Parameters	None
;
;Remarks:		None
;
;Returns:		Name of the Month
;
;Dependencies:
; SteelKix version: 0.2
; Tested with Dot Net 3.5.30729.01
;
;Example(s):	Get month of the year:
;		? @MONTH
;		Result > OCTOBER (if month is October)
;
;Comments : This has been successfully tested on Windows XP SP3 with DOT NET 3.5.30729.01.
;
;Source:
Macro MONTH
Dim $dtn,$glob,$rc
Imports $dtn = System.DateTime.Now
Imports $glob = System.Globalization

Try
   $rc = $dtn.Month
EndTry
Catch
   $e
EndCatch

If $e
   $MONTH = $e.Message
Else
   $MONTH = $glob.DateTimeFormatInfo.CurrentInfo.GetMonthName($rc).ToUpper()
EndIf
EndMacro

Top
#196276 - 2009-10-07 09:45 AM Re: SteelKix 0.22 [Re: BoForce]
WvS Offline
Fresh Scripter
*****

Registered: 2009-05-30
Posts: 42
Loc: Netherlands
Awesome. Thanks!
Top
#196278 - 2009-10-07 10:20 AM Re: SteelKix 0.22 [Re: WvS]
It_took_my_meds Offline
Hey THIS is FUN
*****

Registered: 2003-05-07
Posts: 273
Loc: Sydney, Australia
Excellent!
Top
#196279 - 2009-10-07 11:00 AM Re: SteelKix 0.22 [Re: It_took_my_meds]
Lonkero Administrator Offline
KiX Master Guru
*****

Registered: 2001-06-05
Posts: 22346
Loc: OK
proForce, yes, the error coming out of kixtart is wrong.
guess it's what windows api reports, versus dotnet gives to "correct" ones.

but the power of kixtart is in the fact that it handles the exceptions automatically and internally and thus is n00b friendly.
_________________________
!

download KiXnet

Top
#196280 - 2009-10-07 11:29 AM Re: SteelKix 0.22 [Re: Lonkero]
BoForce Offline
Fresh Scripter
**

Registered: 2005-10-22
Posts: 36
Loc: Leeuwarden, The Netherlands
Yes, indeed the exception message from DotNet is better. But nothing is easier than just throwing an @ERROR or @SERROR
after a line of Kix code to check the result.
So I have to agree with you on the fact that Kix is very friendly to handle and since I've been playing arround with Steelkix,
I started to appreciate Kix even more than I already did.

After working with Kix for about ten years now, Steelkix is a new challenge. Learning new stuff is always nice.

Uh
proForce, Jedi Bo, Lord Force

Just BoForce ;\)

Top
#196282 - 2009-10-07 11:50 AM Re: SteelKix 0.22 [Re: BoForce]
WvS Offline
Fresh Scripter
*****

Registered: 2009-05-30
Posts: 42
Loc: Netherlands
If error handling in kixtart is really considered to be better than the exception handling in steelkix, I could probably make some sort of setoption to catch all exceptions and set @error and @serror. COM exceptions are already handled in this way to be more backwards compatible with kixtart.

Edited by WvS (2009-10-07 11:53 AM)

Top
#196290 - 2009-10-07 05:09 PM Re: SteelKix 0.22 [Re: WvS]
Allen Administrator Online   shocked
KiX Supporter
*****

Registered: 2003-04-19
Posts: 4545
Loc: USA
I can't imagine 100% compatibility between the kixes, but would think this would be high on the list.
Top
#196291 - 2009-10-07 07:36 PM Re: SteelKix 0.22 [Re: Allen]
WvS Offline
Fresh Scripter
*****

Registered: 2009-05-30
Posts: 42
Loc: Netherlands
I've committed a fix for the macro return value bug. I'll release a new binary tomorrow.
Top
#196292 - 2009-10-07 10:56 PM Re: SteelKix 0.22 [Re: WvS]
Lonkero Administrator Offline
KiX Master Guru
*****

Registered: 2001-06-05
Posts: 22346
Loc: OK
wvs, not sure which one is better.
kix way sure is lot easier for the scripter, but on the other hand having to catch the errors forces the user to learn some stuff.

maybe default catcher on by default?
what ya think, boForge?
_________________________
!

download KiXnet

Top
#196293 - 2009-10-08 12:05 AM Re: SteelKix 0.22 [Re: Lonkero]
Allen Administrator Online   shocked
KiX Supporter
*****

Registered: 2003-04-19
Posts: 4545
Loc: USA
Thanks for the link above... For others wondering here is the meat of the article.

 Quote:

This class is the base class for all exceptions. When an error occurs, either the system or the currently executing application reports it by throwing an exception containing information about the error. Once thrown, an exception is handled by the application or by the default exception handler.

The common language runtime provides an exception handling model that is based on the representation of exceptions as objects, and the separation of program code and exception handling code into try blocks and catch blocks, respectively. There can be one or more catch blocks, each designed to handle a particular type of exception, or one block designed to catch a more specific exception than another block.

If an application handles exceptions that occur during the execution of a block of application code, the code must be placed within a try statement. Application code within a try statement is a try block. Application code that handles exceptions thrown by a try block is placed within a catch statement, and is called a catch block. Zero or more catch blocks are associated with a try block, and each catch block includes a type filter that determines the types of exceptions it handles.

When an exception occurs in a try block, the system searches the associated catch blocks in the order they appear in application code, until it locates a catch block that handles the exception. A catch block handles an exception of type T if the type filter of the catch block specifies T or any type that T derives from. The system stops searching after it finds the first catch block that handles the exception. For this reason, in application code, a catch block that handles a type must be specified before a catch block that handles its base types, as demonstrated in the example that follows this section. A catch block that handles System.Exception is specified last.

If none of the catch blocks associated with the current try block handle the exception, and the current try block is nested within other try blocks in the current call, the catch blocks associated with the next enclosing try block are searched. If no catch block for the exception is found, the system searches previous nesting levels in the current call. If no catch block for the exception is found in the current call, the exception is passed up the call stack, and the previous stack frame is searched for a catch block that handles the exception. The search of the call stack continues until the exception is handled or until no more frames exist on the call stack. If the top of the call stack is reached without finding a catch block that handles the exception, the default exception handler handles it and the application terminates.


What isn't so clear is the $e. Is that something that is just predefined in this syntax? What would happen if you dimmed $e and or had a var called $e, prior to doing a try/catch?

Top
#196295 - 2009-10-08 08:35 AM Re: SteelKix 0.22 [Re: Allen]
WvS Offline
Fresh Scripter
*****

Registered: 2009-05-30
Posts: 42
Loc: Netherlands
$e is the exception that was thrown. Catch defines a new scope and $e is defined in that new scope. Any previous declarations of $e cannot be referenced.
Top
#196296 - 2009-10-08 10:09 AM Re: SteelKix 0.22 [Re: Lonkero]
BoForce Offline
Fresh Scripter
**

Registered: 2005-10-22
Posts: 36
Loc: Leeuwarden, The Netherlands
Indeed indeed.
It increases the learning curve.

Catcher on by default? You have my vote.

Top
Page 1 of 2 12>


Moderator:  Glenn Barnas, NTDOC, Arend_, Jochen, Radimus, Allen, ShaneEP, Ruud van Velsen, Mart 
Hop to:
Shout Box

Who's Online
2 registered (morganw, mole) and 414 anonymous users online.
Newest Members
gespanntleuchten, DaveatAdvanced, Paulo_Alves, UsTaaa, xxJJxx
17864 Registered Users

Generated in 0.153 seconds in which 0.103 seconds were spent on a total of 13 queries. Zlib compression enabled.

Search the board with:
superb Board Search
or try with google:
Google
Web kixtart.org