I've been lurking on this list for a while and playing with Parrot. I have created a little stack language not entirely unlike Forth I've come to calling Parakeet. Consider the most trivial example:
0> 2 2 + println 4 0> Two integers are pushed on the stack, the word "+" adds them leaving the result on the stack which the word "println" prints on its own line. New words can be defined with 'def ... end': 0> def square dup * end 0> 16 square println 256 0> This defines the word "square" whose body is composed of "dup *". New words are compiled directly to PIR. You can see the PIR code of defined words with "see": 0> see square .include "languages/parakeet/macros.imc" .sub _square .POP .PUSH .PUSH .POP2 .TOS = .NOS * .TOS .PUSH ret .end 0> (Obviously this is not the most optimal code, but could easily be optimized to cache the top two stack elements in registers at all times). There is some primitive support for pmc objects with "findtype" and "new": 0> "PerlInt" findtype new println 0 0> The string "PerlInt" is pushed on the stack, the word "findtype" finds the type code and pushes it on the stack, which is instantiated with "new". These words map to their Parrot opcode equivalents. Simple variables can be created with the word "var" 0> 4 var x 0> x println 4 0> Parakeet is a stack language like Forth but is very machine specific to Parrot. There are no "cells" or access to "memory", the stack is heterogeneous and can hold any PMC. There is no return stack, since Parrot handles that with 'jsr ... ret'. Flow control currently consists of 'if/then' and 'do/loop': 0> def decide if 5 then end 0> 1 decide println 5 0> 0 decide 0> The word "decide" pushes the integer 5 onto the stack if the top of the stack is true, it does nothing otherwise. Here is its PIR: 0> see decide .include "languages/parakeet/macros.imc" .sub _decide .POP unless .TOS, endif2 .TOS = new .PerlInt .TOS = 1 .PUSH endif2: ret .end Parakeet is incomplete, but I'd like to keep working on it if the Parrot team is interested in another language. I have run up against some problems, however, that I have been unable to solve or find solutions for in the docs. For example: 0> def shoot do "bang" println loop end 0> see shoot .include "languages/parakeet/macros.imc" .sub _shoot .POP2 push .CSTACK, .NOS push .CSTACK, .TOS do2: .TOS = new .PerlString .TOS = "bang" .PUSH .POP print .TOS print "\n" pop .TOS, .CSTACK pop .NOS, .CSTACK inc .TOS push .CSTACK, .NOS push .CSTACK, .TOS ne .TOS, .NOS, do2 pop .TOS, .CSTACK pop .NOS, .CSTACK ret .end 0> The word "shoot" should print the string "bang" inside a loop whose limit and start are on the data stack (in the above code, the CSTACK is the control stack to hold the current loop values, not the data stack): 0> 4 0 shoot pop .NOS, .CSTACK findtypepop .NOS, .CSTACK findtypepop .NOS, .CSTACK findtypepop .NOS, .CSTACK findtype0> It does print four things, but not the four things I was expecting! I have having some other problems with resolving variables names: 0> def bob var x end 0> see bob .include "languages/parakeet/macros.imc" .sub _bob .POP .VARS["x"] = .TOS ret .end Now when I try to run it: 0> 4 bob Key not a string! [EMAIL PROTECTED] I suspect maybe these two problems are related. Anyone spot it? Cuz I'm stumped. If you are interested in playing with it and seeing what it is all about I have attached the two .imc files that you need to run it. Just put the two files in languages/parakeet and point Parrot to parakeet.imc and you will get the interpreter prompt. In my analysis I've found the Parrot VM to be very cool and quite fast. I've had a lot of experience with the Python VM and it's "C loop" interpreter design. I have no idea how the two currently compare (but soon we'll find out which way the pie flies ;) but it looks to me that Parrot's JIT compiling VM with lots of on-processor register space is going to present some clear competition to Python's stack-based VM implemented in a C loop. No matter who wins at OSCON the Python VM will have to embrace these optimizations to remain viable. We'll see. -Michel
.macro IPROMPT "> " .endm .macro CPROMPT "... " .endm .const int Space = 32 .const int Tab = 9 # Registers used by machine. .macro PIRC P29 .endm .macro PASMC P28 .endm .macro TOS P27 .endm # Top Of Stack .macro NOS P26 .endm # Next On Stack .macro USERS P25 .endm # Hash of User words .macro VARS P24 .endm # Hash of variables .macro CSTACK P23 .endm # control stack .macro OPTIONS P22 .endm # misc. machine options .macro WORDS P21 .endm # hash of core words .macro LEVEL I31 .endm # current namespace level .macro BODY S31 .endm # Body of to-be-compiled word .macro CURR S30 .endm # current word .macro SRC S29 .endm # remaining source line to be interpreted .macro COMPILING .LEVEL > 0 .endm .macro INTERPRETING .LEVEL == 0 .endm .macro emit(s) concat .BODY, .s .endm .macro addWord(name, lbl) set_addr $I0, .lbl .WORDS[.name] = $I0 .endm # errors .macro checkNotEmpty depth $I0 if $I0 > 0 goto .$NoError new $P0, .Exception $P0["_message"] = "Stack is Empty!" throw $P0 .local $NoError: .endm # stackies .macro POP .checkNotEmpty restore .TOS .endm .macro POP2 .checkNotEmpty restore .TOS .checkNotEmpty restore .NOS .endm .macro PUSH save .TOS .endm
# Parakeet is a stack machine language for the Parrot VM a bit like # the Forth programming language. Parakeet is extremely simple and # interactive. Just run the file through Parrot and you will get an # interpreter prompt. From there, you can type in Parakeet code. .include "languages/parakeet/macros.imc" .sub _main @MAIN .param PerlArray argv $I0 = argv # name of the program .local string program_name program_name = shift argv .local pmc stdin getstdin stdin .LEVEL = 0 newsub $P0, .Exception_Handler, _handler set_eh $P0 # currently I keep track of my own vars in a hash, I'd like to use # the lexical stuff but I can't get it to work right. # new_pad .LEVEL # store_lex -1, "argv", argv compreg .PASMC, "PASM" compreg .PIRC, "PIR" .WORDS = new .PerlHash .USERS = new .PerlHash .VARS = new .PerlHash .CSTACK = new .PerlArray .addWord("def", _DEF) .addWord("end", _END) .addWord("var", _VAR) .addWord("findtype", _FINDTYPE) .addWord("new", _NEW) .addWord("bye", _BYE) .addWord("see", _SEE) .addWord("throw", _THROW) .addWord("print", _PRINT) .addWord("println", _PRINTLN) .addWord("drop", _DROP) .addWord("dup", _DUP) .addWord("if", _IF) .addWord("then", _THEN) .addWord("else", _ELSE) .addWord("do", _DO) .addWord("loop", _LOOP) .addWord("for", _FOR) .addWord("next", _NEXT) .addWord("+", _ADD) .addWord("-", _SUB) .addWord("*", _MUL) .addWord("/", _DIV) BeginInteraction: if .COMPILING goto CompilePrompt depth $I0 print $I0 print .IPROMPT goto Prompted CompilePrompt: print .CPROMPT Prompted: readline .SRC, stdin length $I0, .SRC dec $I0 substr .SRC, .SRC, 0, $I0 BeginCollecting: .CURR = "" bsr EatLeadingWhitespace bsr CollectWord if .CURR == "" goto BeginInteraction Comment: substr $S0, .CURR, 0, 1 unless $S0 == "#" goto Zero goto BeginInteraction Zero: unless .CURR == "0" goto Integer if .COMPILING goto CompileZero .TOS = new .PerlInt .TOS = 0 .PUSH goto BeginCollecting CompileZero: .emit(".TOS = new .PerlInt\n") .emit(".TOS = 0\n") .emit(".PUSH\n") goto BeginCollecting Integer: $I0 = .CURR unless $I0 > 0 goto String if .COMPILING goto CompileInteger .TOS = new .PerlInt .TOS = $I0 .PUSH goto BeginCollecting CompileInteger: .emit(".TOS = new .PerlInt\n") .emit(".TOS = ") .emit(.CURR) .emit("\n.PUSH\n") goto BeginCollecting String: # This is lame-o. figure out Rx4 to parse out string literals substr $S0, .CURR, 0, 1 unless $S0 == "\"" goto Word length $I0, .CURR dec $I0 dec $I0 substr .CURR, .CURR, 1, $I0 .TOS = new .PerlString .TOS = .CURR if .COMPILING goto CompileString .PUSH goto BeginCollecting CompileString: .emit(".TOS = new .PerlString\n.TOS = \"") .emit(.CURR) .emit("\"\n.PUSH\n") goto BeginCollecting Word: .TOS = .WORDS[.CURR] $I0 = typeof .TOS if $I0 == .PerlUndef goto Variable $I0 = .TOS jsr $I0 goto BeginCollecting Variable: .TOS = .VARS[.CURR] $I0 = typeof .TOS if $I0 == .PerlUndef goto User if .COMPILING goto CompileVariable .PUSH # if .COMPILING goto CompileVariable # newsub $P1, .Exception_Handler, _var_not_found # set_eh $P1 # find_lex .TOS, .CURR # .PUSH # goto BeginCollecting # _var_not_found: # clear_eh # goto User CompileVariable: .emit(".TOS = .VARS[\"") .emit(.CURR) .emit("\"]\n.PUSH\n") # .emit("$S0 = ") # .emit(.CURR) # .emit("\"\nfind_lex .TOS, $S0\"") # .emit(".PUSH\n") goto BeginCollecting User: .TOS = .USERS[.CURR] $I0 = typeof .TOS if $I0 == .PerlUndef goto UnknownWord $I0 = .TOS[1] if .COMPILING goto CompileUser jsr $I0 goto BeginCollecting CompileUser: .emit("set $I0, ") $S0 = $I0 .emit($S0) .emit("\njsr $I0\n") goto BeginCollecting UnknownWord: print "Unknown Word: " print .CURR print "\n" .LEVEL = 0 goto BeginInteraction _handler: print "An Exception was thrown: " set S0, P5["_message"] # P5 is the exception object print S0 print "\n" .LEVEL = 0 goto BeginInteraction End: goto BeginInteraction # Helper routines taken from forth.pasm. Should use RX4. EatLeadingWhitespace: length $I1, .SRC eq $I1, 0, DoneLeadingWhitespace set $I3, 0 ord $I2, .SRC, $I3 eq $I2, Space, StartLeadingWhitespace eq $I2, Tab, StartLeadingWhitespace branch DoneLeadingWhitespace StartLeadingWhitespace: inc $I3 eq $I1, $I1, FinishLeadingWhitespace ord $I2, .SRC, $I3 eq $I2, Space, StartLeadingWhitespace eq $I2, Tab, StartLeadingWhitespace FinishLeadingWhitespace: substr .SRC, 0, $I3, "" DoneLeadingWhitespace: ret CollectWord: length $I1, .SRC eq $I1, 0, DoneCollectWord set $I3, 0 NextCharCollectWord: eq $I3, $I1, EndDarkspace ord $I2, .SRC, $I3 eq $I2, Space, EndDarkspace eq $I2, Tab, EndDarkspace inc $I3 branch NextCharCollectWord EndDarkspace: substr .CURR, .SRC, 0, $I3, "" DoneCollectWord: ret # Word definitions # Define a new word in the current lexical scope. _DEF: # save the parent word compilation will resume at save .BODY save .CURR bsr EatLeadingWhitespace bsr CollectWord inc .LEVEL save .CURR .BODY = ".include \"languages/parakeet/macros.imc\"\n" .emit(".sub ") .emit("_") .emit(.CURR) .emit("\n") ret # # End the word being currently compiled. _END: unless .LEVEL == 0 goto _DOEND print "Nothing to end!\n" ret _DOEND: .emit("ret\n") .emit(".end\n") print .BODY compile $P0, .PIRC, .BODY $P1 = new .PerlString $P1 = .BODY setprop $P0, "__asm__", $P1 restore .CURR .USERS[.CURR] = $P0 dec .LEVEL restore .CURR restore .BODY ret # # See a word's asm source. Interpret only. _SEE: unless .INTERPRETING goto _BLIND bsr EatLeadingWhitespace bsr CollectWord .TOS = .USERS[.CURR] $I0 = typeof .TOS if $I0 == .PerlUndef goto _NOSEEUM getprop $P0, "__asm__", .TOS print $P0 ret _NOSEEUM: print "Can't see word: " print .CURR print "\n" _BLIND: ret # # See ya. _BYE: if .COMPILING goto _CBYE end _CBYE: .emit("end\n") ret # # Create a new varible in the current lexical scope. _VAR: bsr EatLeadingWhitespace bsr CollectWord unless .INTERPRETING goto _CVAR .POP .VARS[.CURR] = .TOS ret # .POP # newsub $P1, .Exception_Handler, _var_not_found2 # set_eh $P1 # find_lex $P0, .CURR # _var_not_found2: # clear_eh # store_lex -1, .CURR, .TOS # ret # assign $P0, .TOS # ret _CVAR: $P0 = new .PerlInt # place-holder $P0 = 0 .VARS[.CURR] = $P0 .emit(".POP\n") .emit(".VARS[\"") .emit(.CURR) .emit("\"] = .TOS\n") # .TOS = new .PerlInt # store_lex -1, .CURR, .TOS # .emit(".POP\n$S0 = \"") # .emit(.CURR) # .emit("\"\nfind_lex $P0, $S0\n") # .emit("assign $P0, .TOS\n") ret # # Exceptions. _THROW: if .COMPILING goto _CTHROW .POP throw .TOS ret _CTHROW: .emit(".POP\n") .emit("throw .TOS\n") ret # # I/O _PRINT: if .COMPILING goto _CPRINT .POP print .TOS ret _CPRINT: .emit(".POP\n") .emit("print .TOS\n") ret _PRINTLN: if .COMPILING goto _CPRINTLN .POP print .TOS print "\n" ret _CPRINTLN: .emit(".POP\n") .emit("print .TOS\n") .emit("print \"\\n\"\n") ret # # Control Flow # if/else/then _IF: unless .COMPILING goto _ENDIF inc .LEVEL .emit(".POP\n") .emit("unless .TOS, endif") $S0 = .LEVEL .emit($S0) .emit("\n") _ENDIF: ret _ELSE: unless .COMPILING goto _ENDELSE _ENDELSE: ret _THEN: unless .COMPILING goto _ENDTHEN .emit("endif") $S0 = .LEVEL .emit($S0) .emit(":\n") dec .LEVEL _ENDTHEN: ret # do/loop _DO: unless .COMPILING goto _ENDDO # bsr EatLeadingWhitespace # bsr CollectWord inc .LEVEL .emit(".POP2\n") .emit("push .CSTACK, .NOS\n") .emit("push .CSTACK, .TOS\n") # .emit(".VARS[\"") # .emit(.CURR) # .emit("\"] = .TOS\n") .emit("do") $S0 = .LEVEL .emit($S0) .emit(":\n") _ENDDO: ret _LOOP: unless .COMPILING goto _ENDLOOP .emit("pop .TOS, .CSTACK\n") .emit("pop .NOS, .CSTACK\n") .emit("inc .TOS\n") # .emit(".VARS[\"") # .emit(.CURR) # .emit("\"] = .TOS\n") .emit("push .CSTACK, .NOS\n") .emit("push .CSTACK, .TOS\n") .emit("ne .TOS, .NOS, do") $S0 = .LEVEL .emit($S0) .emit("\n") .emit("pop .TOS, .CSTACK\n") .emit("pop .NOS, .CSTACK\n") dec .LEVEL _ENDLOOP: ret # for/next _FOR: unless .COMPILING goto _ENDFOR _ENDFOR: ret _NEXT: unless .COMPILING goto _ENDNEXT _ENDNEXT: ret # # Instantiate a new object whose type is on the stack _NEW: if .COMPILING goto _CNEW .POP $I0 = .TOS .TOS = new $I0 .PUSH ret _CNEW: .emit(".POP\n") .emit("$I0 = .TOS\n") .emit(".TOS = new $I0\n") .emit(".PUSH\n") ret # # Find a type whose name is a string on the stack. _FINDTYPE: if .COMPILING goto _CFINDTYPE .POP $S0 = .TOS find_type $I0, $S0 .TOS = $I0 .PUSH ret _CFINDTYPE: .emit(".POP\n") .emit("$S0 = .TOS\n") .emit("find_type $I0, $S0\n") .emit(".TOS = $I0\n") .emit(".PUSH\n") ret # Stack _DROP: if .COMPILING goto _CDROP .POP ret _CDROP: .emit(".POP\n") ret _DUP: if .COMPILING goto _CDUP .POP .PUSH .PUSH ret _CDUP: .emit(".POP\n") .emit(".PUSH\n") .emit(".PUSH\n") ret # Math _ADD: if .COMPILING goto _CADD .POP2 .TOS = .NOS + .TOS .PUSH ret _CADD: .emit(".POP2\n") .emit(".TOS = .NOS + .TOS\n") .emit(".PUSH\n") ret _SUB: if .COMPILING goto _CSUB .POP2 .TOS = .NOS - .TOS .PUSH ret _CSUB: .emit(".POP2\n") .emit(".TOS = .NOS - .TOS\n") .emit(".PUSH\n") ret _MUL: if .COMPILING goto _CMUL .POP2 .TOS = .NOS * .TOS .PUSH ret _CMUL: .emit(".POP2\n") .emit(".TOS = .NOS * .TOS\n") .emit(".PUSH\n") ret _DIV: if .COMPILING goto _CDIV .POP2 .TOS = .NOS / .TOS .PUSH ret _CDIV: .emit(".POP2\n") .emit(".TOS = .NOS / .TOS\n") .emit(".PUSH\n") ret .end