Skip to content

dlurton/Happy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

#Happy - A scripting language with syntactic sugar for code generation

A pre-release version of happy is available on the public nuget repository, to get it exeucte this command at the package manager console:

PM> Install-Package Happy -Pre 

The Short and Sweet:

  • Can be used to quickly and efficiently write a generator for any kind of text based output including HTML, XML, CSS, Javascript, C#, VB.Net, SQL, YAML, Java, Markdown, etc, etc. If it's text-based you can write a program in Happy to generate it.
  • Based on the Dynamic Language Runtime
  • Fast compilation and execution (can generate NHibernate mappings for an 800+ table database in under 6 seconds).
  • Simple Javascript-like syntax with additions for templates
  • .NET Interoperability:
    • Instantiate any .NET type
    • Invoke instance and static methods
    • Read/write properties
  • Embeddable in another program
  • Can be executed from the command-line
  • Visual Studio Debugging (this is still a WIP but it works!)
  • A hand-written lexical analyzer and parser, leading to very fast compilation and small assembly size.

##Future Work:

  • try/catch/throw
  • using blocks (a la C#)
  • Increment (++) and decrement (--) operators
  • Operation assignment operators (+=, -=, *=, /= %=, etc)
  • Ternary operator (?:)
  • modules (currently, the entire script must be contained in a single file)
  • closures (should be easy to implement thanks to the DLR)
  • events
  • constants
  • Visual Studio Debugging needs cleaning up
  • Support for changing the template output stream during script execution (so that multiple files may be generated per script run)
  • General testing and stabilizing

##The 5-Minute Intro

###Output Statements

Happy has the concept of a "current output." The current output is simply a System.IO.TextWriter where all template output is directed. At the moment, the current output must be determined by the host program executing the Happy script. This is usually the command-line application, in which case the current output is directed at the file specified in one of the command-line options. A host program in which Happy has been embedded can specify any TextReader as the current output.

There are several ways to write to the current output. The easiest way is using the output operator:

~"Text to be written to the current output.";

This shows how a single string may be written to the output. Multiple values may also be written in a single statement as long as they are properly separated by whitespace:

~"Hello, " username "!";

If the value of the username variable was "Bob", this would cause "Hello, Bob!" to be written to the current output.

Also note that any object may be written to the output. If the object is not a string already, then the ToString() method is called to first convert it to a string.

Templates

In Happy, templates are expressions that have values just like a + b is an expression with a value. Whenever script exection reaches a template, it is executed immediately and the scripts output is either written to the output or stored in a variable for later use. Templates begin with <| and end with |>. Text between <| and |> is called verbatim text. Verbatim text is written to the current output exactly as it appears, new lines and all. Within verbatim text, individual expressions may be written to the output between pairs of $ .

To write the output of a template to the current output, use the output operator described above:

~<|Hello, $nonVerbatimText$! This is some verbatim text!|>;

Assuming nonVerbatimText had the value of "Bob", the output would be:

Hello, Bob! This is some verbatim text!

Using the same example, we can assign the value of the template to a variable:

def helloText = <|Hello, $nonVerbatimText$! This is some verbatim text!|>;
~helloText;

The output is exactly the same as the previous example.

Templates can also contain inline code, similar to ASP/JSP, etc:

def helloText = <|Hello, |% 
	def userName = getUsername();
	if(String.IsNullOrEmpty(userName)
		userName = "<unknown>";
	~userName;
	%|$! this is some verbatim text!|>;
~helloText;

If the getUsername() function returned null, the output would be:

Hello, <unknown>! This is some verbatim text!

###A More Complete Example:

function generateSimpleInvoice(invoice)
{
	def itemCount = 0;
	~<|
Invoice Date  $invoice.DateTime$
Customer:  $invoice.Customer.Name$

Items Purchased:
	|% for(item in invoice.Items) { 
		%|$"\t"$ Description: $item.Description$, Price: $item.Price$ $"\n" |% 
		itemCount = itemCount + 1 
	} %|
		Number of items:  $itemCount$
	|>;
}

This would output text similar to the following:

Invoice Date:  10/12/2012 12:43 pm
Customer:  Acme, Inc
Items Purchased:
	Description:  Widget A, Price 123
	Description:  Widget B, Price 234
	Description:  Widget C, Price 345
	Number of items:  3

##Syntax Summary

####Comments:

//This is a comment
/* 
	this is also a comment 
*/

####Global variables:

def foo, bar = "Hello, world!";

(Note the use of "def" instead of "var.")

####Single Line Functions:

//since the curly braces are optional for if, while and for statements that have only one line
//why can't functions they also be optional for functions that have only one line?
function multiply(a, b) 
	return a * b;

####Multi-line functions:

function foobar(foo, bar)
{
	def intermidiateValue = someFunction(foo + bar);
	return someOtherFunction(intermediateValue);
}

####Local variables:

function example()
{
	def localFoo, localBar = "Hello, locals";
	//... something with localFoo and localBar
}

####Branching:

if(someVariable == anotherVariable)
	Console.WriteLine("Hello, world!");
else
	Console.WriteLine("Goodbye, world!");

switch(someValue)
{
case "a":
	Console.WriteLine("someValue was 'a'");
	break;
case "b":
	Console.WriteLine("someValue was 'b');
	break;
default:
	Console.WriteLine("I don't know what someValue was.");
	break;
}

####While Loop:

while(sqlReader.Read())
{
	def row = readRow(sqlReader);
	if(row.Status == WidgetStatus.Closed)
		break;

	if(row.Status == WidgetStatus.Pending)
		continue;
	
	Console.WriteLine("Widget {0} is ready!", row.Id);
}

####For Loop:

~"Widgets older than 1 year:"
for(foo in bar.Widgets where bar.Widget.Age > 1 between "\n\t")
	~foo.WidgetId ": " foo.Description;

The variable "foo" is being declared here, and like a C# foreach loop is scoped to the loop body. Following the keyword where is a boolean expression which is evaluated before each iteration--the loop body will only be executed if it evaluates to true. Following the between keyword is an expression whch will be written to the current output bewtween iterations, thus solving the "dangling comma" problem (where in logic must exist at the end of a loop body to check if this is the last iteration to prevent a trailing comma, or the trailing comma must be removed after the loop (i.e.: "1, 2, 3, 4,") and allowing repeated code to be indented at a different level than the code that came before:

Widgets older than 1 year:
	10: Foo
	12: Bar
	23: Shazam!

Any value may be specified as the between text in a for loop. As with any output expression, if it is not a string, the object.ToString() method is called.

Object Instantiation:

Object instantiation looks slightly different in Happy than in other languages:

def newInstance = new(System.DateTime, 2012, 12, 12);

The new keyword looks like a function although it is not actually a function. The first argument is the type. The type can be a direct reference to the type in its namespace as in this example or it can be an instance of the System.Type class. The remaining arguments are passed to the constructor.

##Using Types in .Net Assemblies

Assemblies may be dynamically "referenced" at runtime by loading them with the load keyword:

load "Microsoft.SqlServer.Smo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"

As in this example, if the assembly is signed with a strong name you must specify the fully qualified assembly name. If the assembly is not strong named you may simply use the assembly's filename without the extension.

##Using Classes and Namespaces

Indivual types may be placed in the global scope easily by simply assigning them to a global variable:

def Console = System.Console;
def AnotherNameForConsole = System.Console;

All types in a single namespace may be placed in the global scope easily with the use statement, which is analagous to the using statement in C#:

use System;
use Sysetm.IO;
use YourCompany.BusinessLayer;

Note that Happy does not handle using namespaces that have types of the same name. If this situation is encountered, the workaround is to assign the individual classes of one of the namespaces required by the script to global variable names as in the previous example or to use fully qualified type names, i.e. System.Console instead of simply System.

About

A sophisticated embeddable template language based on Microsoft's DLR.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages