----------------------------------------------------------------------------- Polemy 0.1.0 by k.inaba (www.kmonos.net) Nov 20, 2010 ----------------------------------------------------------------------------- <> - Install DMD http://www.digitalmars.com/d/2.0/changelog.html Version 2.050 is recommended. Older or newer version may not work. - Build (for Windows) Run build.bat (for Unix) Run build.sh or use your favorite build tools upon main.d and polemy/*.d. Then you will get the executable "polemy" in the "bin" directory. <> d2stacktrace/* is written by Benjamin Thaut and licensed under 2-clause BSD License. See http://3d.benjamin-thaut.de/?p=15 for the detail. (this package is used only for enabling stack-traces during printing exceptions; it is not used for release builds.) polemy/* main.d All the other parts are written by Kazuhiro Inaba and licensed under NYSL 0.9982 ( http://www.kmonos.net/nysl/ ). <> > polemy starts REPL > polemy foo.pmy executes foo.pmy > polemy -l foo.pmy after executing foo.pmy, starts REPL > polemy -l foo.pmy -l bar.pmy buz.pmy executes foo.pmy, bar.bmy, and then buz.pmy <> Comment is "# ... \n" E ::= // declaration | ("var"|"let"|"def"|LAYER) ID "=" E (";"|"in") E | ("var"|"let"|"def"|LAYER) ID "(" PARAMS ")" "{" E "}" (";"|"in") E | ("var"|"let"|"def"|LAYER) ID "=" E | ("var"|"let"|"def"|LAYER) ID "(" PARAMS ")" "{" E "}" // literal | INTEGER | STRING | "{" ENTRYS "}" // table | "fun" "(" PARAMS ")" "{" E "}" // anonymous function // function call | E "(" ARGS")" where ARGS ::= E "," ... "," E PARAMS ::= ID LAYER* "," ... "," ID LAYER* ENTRYS ::= ID ":" E "," ... "," ID ":" E ID ::= 'a-zA-Z0-9_...'+ LAYER ::= "@" ID // operators | "(" E ")" | E "." ID // table field access | E ".?" ID // table field existence check | E "{" ENTRYS "}" // table extend (pure functionally) | E BINOP E | "if" "(" E ")" "{" E "}" | "if" "(" E ")" "{" E "}" "else "{" E "}" // layered exec | LAYER "(" E ")" The following are actually rewritten to function calls: - if (E) then{E} else{E} ==> if( E, fun(){E}, fun(){E} ) - E BINOP E ==> BINOP(E, E) - E.ID ==> . (E, ID) - E.?ID ==> .?(E, ID) - {} ==> {}() - { ENTRIES } ==> {}{ ENTRIES } - E {ID:E, ...} ==> (.=(E, ID, E)) { ... } Several styles of variable declaration can be used: - fun(x){ fun(y){x} } # K-combinator - fun(x){ let f = fun(y){x} in f } # let-in style - fun(x){ var f = fun(y){x}; f } # var-; style - fun(x){ def f = fun(y){x} in f } # you can use any combination of (let|var|def)-(;|in) - fun(x){ def f(y){x} in f } # syntax sugar for function declaration - fun(x){ let f(y){x}; f } # this is also ok - fun(x){ var f(y){x} } # omitting (;|in) returns the last declared object directly - fun(x,y){x} #< this is not equal to the above ones. functions are no curried. NOTE: Theres no "let rec" syntax, but still recursive definition works def f(x) { if(x==0){1}else{x*f(x-1)} } in f(10) #=> 3628800 yet still the code below also works def x=21 in def x=x+x in x #=> 42. The internal scoping mechanism is a little tricky (this is for coping with the "layer" feature explained below), but I hope that it works as everyone expects in most cases, as long as you don't use the same-name-variables heavily :). (Experimental) pattern matching is also available. Here is an example. def adjSum(lst) { case( lst ) when( {car:x, cdr:{car: y, cdr:z}} ) { {car: x+y, cdr: adjSum(z)} } when( {car:x, cdr:{}} ) { {car: x, cdr: {}} } when( {} ) { {} } }; It is expanded to a sequence of if-then-elses prefering the first-match. Note that {a: _} pattern matches all the tables that have the .a field. It also matches to {a: 123, b: 456} having extra .b field. So, changing the order of "when"s in the above code changes the behavior. <> Polemy is an untyped functional programming language that has - integers: 0, 123, 456666666666666666666666666666666666666789, ... - strings: "hello, world!\n", ... - tables: {car: 1, cdr: {car: 2, cdr: {}}} - functions: fun(x){x+1} as primitive datatypes. Functions capture lexical closures. It is almost 'pure' (except the primitve function "print" and some trick inside scoping mechanisms). <> Polemy's runtime environment has many "layer"s. Usual execution run in the @value layer. >> 1 + 2 3 >> @value( 1 + 2 ) 3 Here you can see that @LayerName( Expression ) executes the inner Expression in the @LayerName layer. Other than @value, one other predefined layer exists: @macro. >> @macro( 1+2 ) {pos@value:{lineno@value:3, column@value:9, filename@value:}, is@value:app, args@value:{car@value:{pos@value:{lineno@value:3, column@value:9, filename@value:}, is@value:int, data@value:1}, cdr@value:{ car@value:{pos@value:{lineno@value:3, column@value:11, filename@value:}, is@value:int, data@value:2}, cdr@value:{}}}, fun@value:{pos@value:{lineno@value:3, column@value:10, filename@value:}, is@value:var, name@value:+}} (Sorry, this pretty printing is not available on the actual interpreter...) This evaluates the expression 1+2 in the @macro layer. In this layer, the meaning of the program is its abstract syntax tree. You can interleave layers. The root node of the abstract syntax tree is function "app"lication. >> @value(@macro( 1+2 ).is) app <> To define a new layer, you should first tell how to "lift" existing values two the new layer. Let us define the "@type" layer, where the meaning of programs is their static type. >> @@type = fun(x) { >> if( _isint(x) ) { "int" } else { >> if( _isfun(x) ) { x } else { "unknown" } } >> } (Note: polemy REPL may warn some exception here but please ignore) For simplicity, I here deal only with integers. _isint is a primitive function of Polemy that checks the dynamic type of a value. For function, leaving it untouched works well for almost all layers. >> @type( 1 ) int >> @type( 2 ) int >> @type( "foo" ) unknown Fine! Let's try to type 1+2. >> @type( 1 + 2 ) ...\value.d(119): [:6:8] only @value layer can call native function Note that the behavior of this program is - run 1+2 in the @type layer and NOT - run 1+2 in @value and obtain 3 and run 3 in the @type. The problem is, the variable "+" is defined only in the @value layer. To carry out computation in the @type layer. We need to define it also in the @type layer. To define some variable in a specific layer, use @LayerName in place of (let|var|def)s. >> let x = 2 >> @value x = 2 >> @type x = "int" >> @hoge x = "fuga" For "+", do it like this. >> @type "+" = fun(x,y) {@value( >> if( @type(x)=="int" && @type(y)=="int" ) { "int" } else { "typeerror" } >> )} polemy.value.native!(IntValue,IntValue,IntValue).native.__anonclass24 It is just computing the return type from the input type. Not here that the intended "meaning" of if-then-else is the runtime-branching, and the meaning of "==" is the value-comparison. These are the @value layer behavior. So we have defined the function body inside @value layer. But when we refer the variables x and y, we need its @type layer meaning. Hence we use @type() there. Now we get it. >> @type( 1 + 2 ) int Well, but do we have to define the @type layer meaning for every variables??? No. After you defined @type "+", you'll automatically get the following: >> def double(x) { x + x } (function:17e4740:1789720) >> @type( double(123) ) int Every user-defined functions are automatically "lift"ed to the appropriate layer. Only primitive functions like "+" requires @yourNewLayer annotation. <> let|var|def is to define a variable in the "current" layer. Not necessary to the @value layer. >> @value( let x = 1 in @value(x) ) 1 >> @macro( let x = 1 in @value(x) ) polemy.failure.RuntimeException: [:14:29] variable x not found >> @macro( let x = 1 in @macro(x) ) {pos@value:{lineno@value:15, ... <> >> def foo(x @macro @value) { {fst: x, snd: @macro(x)} } (function:1730360:1789720) If you annotate function parameters by @LayerNames, when you invoke the function... >> foo(1+2) {snd@value: {pos@value:{lineno@value:17, column@value:5, filename@value:}, is@value:app, arg@value:{... /fst@value:3 /} its corresponding arguments are evaluated in the layer and passed to it. If you specify multiple layers, the argument expression is run multiple times. If you do not specify any layer for a parameter, it works in the neutral layer. <<@macro layer>> When function is invoked, it first run in the @macro layer, and after that, it run in the neutral layer. Here is an example. >> @macro twice(x) { x; x } >> def f() { twice(print("Hello")); 999 } (function:173b6a0:1789720) >> f() Hello Hello 999 When the interpreter evaluates f(), it first executes "twice(print("Hello")); 999" in the @macro layer. Basically what it does is to just construct its syntax tree. But, since we have defined the "twice" function in the @macro layer, it is execute as a function. Resulting syntax tree is "print("Hello"); print("Hello"); 999" and this is executed on the neutral (in this example, @value) layer. This is the reason why you see two "Hello"s. [[limitations]] This @macro layer is a very primitive one, and not a perfect macro language. Two major limitations are seen in the following "it" example. >> @macro LetItBe(x, y) { let it = x in y }; The variable name is not hygenic, and so without any effort, the syntax tree "y" can access the outer variable "it". >> def foo() { LetItBe( 1+2+3, it*it ) } >> foo() 36 Of course, this is not just a limitation; it can sometimes allow us to write many interesting macros. The other problem is that the macro expansion is only done at function startup. So >> LetItBe( 1+2+3, it*it ) ...\value.d(173): [:24:1] variable LetItBe is not set in layer @value you cannot directly use the macro in the same scope as the definition. You need to wrap it up in a function (like the foo() in the above example). [[quote and unquote]] Here is more involved example of code genration. From "x", it generates "x*x*x*x*x*x*x*x*x*x". @macro pow10(x) { @value( def pow(x, n) { if( n == 1 ) { x } else { @macro( @value(x) * @value(pow(x,n-1)) ) } } in pow(@macro(x),10) ) }; Here, x is a syntax tree but n is an actual integer. If you read carefully, you should get what is going on. Basically, @macro can be considered like quasiquoting and @value to be an escape from it. <> {} 0-ary create-empty-table . 2-ary table-get .? 2-ary table-has? .= 3-ary table-set if 3-ary if-then-else + - * / % || && 2-ary integer-operations (no short-circuit!) < > <= >= == != 2-ary generic comparison print 1-ary print-to-stdout _isint _isstr _isfun _isundefined _istable 1-ary dynamic-type-test