y-less.com | Alex “Y_Less” Cole’s blog

Apr/09

28

yavascript v2 proof-of-concept

With my Javascript preprocessor project due in in less than two weeks, and now (mostly) all written and working pretty well I have JUST figured out a FAR better way to do the whole project.  This is highly irritating to say the least, and now I have to find a way to justify why I didn’t do it that way in the first place without saying “I didn’t think of it”.

This new version allows full C-like text replacement and pre-processing, as well as totally custom symbols like “#define”, when “#” isn’t even a valid Javascript character:

#define VALUE (42)

alert(VALUE);

#define LOOP for (var i = 0; i < 10; i++)

LOOP
{
	alert(i);
}

Anyone who knows Javascript can tell you that what I just posted is not valid, and if you don’t know it then take my word on it. However it IS valid yavascript and can be run in any modern browser (or should be able to, I’ve only tested in FF3 and IE6) with no modification at all!

The version of yavascript in my project is function and value based, not text based, so you do things like:

$define('VALUE', 42);
alert(VALUE);

But can’t do:

$define('LOOP', 'for (var i = 0; i < 10; i++)');

LOOP
{
	alert(i);
}

Well, you COULD do that, but you would just end up with a string that did nothing followed by a useless block and an alert for an undefined number, all-in-all not much use.

The new version relies on a very simple text pre-processor and altered script tags. Firstly you don’t want your code executing, so we need to tell the browser that it doesn’t know this language (as it doesn’t):

<script type="text/yavascript">
// Code goes here
</script>

That puts your code into a script tag in the document, but will not execute it as the browser is unlikely to know that language. The next stage is to add some real Javascript to the document to search the DOM for all script tags of type “text/yavascript”, textually pre-process them in any way you like, then “eval” the result. The code for all this is below, note that, as a proof-of-concept, it’s very rough and ready. The magic happens in this function:

function doYavascript(code)
{
	// Do defines
	var
		def;
	// Should modify this to account for comments
	// but not comments in strings - have fun
	// figuring out the regex for that!
	while ((def = code.match(/^\s*#define\s+(\w+)\s+(.+)$/m)))
	{
		// Loop through all the matches
		// complete with part information
		// Replace all instances of this word
		code = code.replace(def[0], '').replace(def[1], def[2]);
	}
	// Execute the code
	eval(code);
}

The first thing that does is loops through everything matching a C define statement on a line on it’s own.

/^\s*#define\s+(\w+)\s+(.+)$/m

Searches for 0 or more spaces at the start of a line, followed by “#define”, followed by 1 or more spaces, followed by a word, which we’ll save, followed by another 1 or more spaces, followed by any text, which we’ll also save, followed by an end of line.

Once it’s got a single “#define” line, it will replace all occurrences of that word in the current chunk of code (stored in “code” and passed from either an XHR file in the case of “#include” or a “<script>” innerHTML value) with the define value. As this is entirely text based it can do anything anywhere. It also removes the current define from the document as it’s invalid Javascript and we don’t want an infinite loop. Finally, once all defines are done it executes what should now be fully valid Javascript.

Full example code:

<!--
This software is in the public domain, furnished "as is", without technical
support, and with no warranty, express or implied, as to its usefulness for
any purpose.

You can use it for anything you like, but some form of credit or note
would be nice.

new_directive.html

Proof of concept code for new javascript preprocessor directives.

Author:

Alex "Y_Less" Cole

www.y-less.com
y-less.pastebin.ca
-->
<html>
	<head>
		<title>New Directives</title>
		<script type="text/javascript">
			// Function to parse the yavascript code
			window.onload = function ()
			{
				function doYavascript(code)
				{
					// Do defines
					var
						def;
					// Should modify this to account for comments
					// but not comments in strings - have fun
					// figuring out the regex for that!
					while ((def = code.match(/^\s*#define\s+(\w+)\s+(.+)$/m)))
					{
						// Loop through all the matches
						// complete with part information
						// Replace all instances of this word
						code = code.replace(def[0], '').replace(def[1], def[2]);
					}
					// Execute the code
					eval(code);
				}

				var
					scripts = document.getElementsByTagName('script');
				for (var i in scripts)
				{
					// Find all the yavascript sections
					if (scripts[i].type == 'text/yavascript')
					{
						// Current script block
						doYavascript(scripts[i].innerHTML);
					}
				}
			}
		</script>
		<script type="text/yavascript">
			// Note the type of this section:
			// "text/[y]avascript"
			// This is so it's not run
			// when the page is loaded.

			// #include not done yet
			//#include 'other.js'

			// Can define regular constants
			#define VALUE (42)

			// Using a define
			alert('Value: ' + VALUE);

			// Can define code using this
			#define CODE for (var i = 0; i < 10; i++)

			// Using a code macro
			CODE
			{
				alert(i);
			}

			// Note that as this is a basic
			// proof of concept, doing:
			//
			// alert('VALUE: ' + VALUE);
			//
			// Will result in:
			//
			// alert('42: ' + 42);
			//
			// The regex used can't currently
			// identify strings.
			//
			// It also can't do comments
			// on preprocessor lines, so:
			//
			// #define VAL 42 // My value
			// alert(VAL);
			//
			// Will result in:
			//
			// alert(42 // My value);
			//
			// Although these should be
			// fixable with better regex.
		</script>
	</head>
	<body>
	</body>
</html>

Update…

Since writing this I have done some more work on this and now have the following code all fully working (includes need a little work for folder support, but the code below works fine). The source for the yavascript include for this is not currently released but if you ask I can provide it:

<!-- External file support -->
<script type="text/yavascript" src="include_alt.ys"></script>
<script type="text/yavascript">
	// Can define regular constants
	#define VALUE (42) // This is my value

	// Includes work
	// Could use .ys (as above)
	#include 'include.js' /* Comments after lines now work */

	#define SOME_STRING "// str" // Comments in strings and after lines work
	#define /* as do inline comments */ OTHER_THING 0

	// Also have #ifndef
	#ifdef MOOP
		#if 2
			alert('not shown');
		#endif
	#else
		#if 2
			alert('shown');
		#else
			alert('not shown');
		#endif
		alert('shown');
	#endif

	// Using a define
	alert('Value: ' + VALUE + ' ' + VALUE);

	// Remove a define
	#undef VALUE

	// This would give an already
	// defined warning if not for
	// the #undef just above
	#define VALUE (10)

	// Using a macro
	alert('Value: ' + VALUE + ' ' + VALUE);

	// Can define code using this
	#define CODE for (var i = 0; i < 10; i++)

	// Using a code macro
	CODE
	{
		alert((i + OTHER));
	}

	#if VALUE + 2 < 13
		alert('shown');
	#endif
</script>

No comments yet.

Leave a Reply

You must be logged in to post a comment.

<<

>>

Theme Design by devolux.nh2.me