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

Reply via email to