Skip to content

Commit

Permalink
commit of Poe v 1.02 from old Sourceforge release
Browse files Browse the repository at this point in the history
  • Loading branch information
rickystewart committed Feb 21, 2014
0 parents commit c8ccf84
Show file tree
Hide file tree
Showing 47 changed files with 19,176 additions and 0 deletions.
972 changes: 972 additions & 0 deletions 1000lines.txt

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
all: pdata.c puniverse.c pmem.c pdo1.c pdo2.c pexec.c pbc.c pgc.c lex.yy.c y.tab.c pstd.c pio.c pmath.c pstring.c pmain.c
gcc pdata.c puniverse.c pmem.c pdo1.c pdo2.c pexec.c pbc.c pgc.c lex.yy.c y.tab.c pstd.c pio.c pmath.c pstring.c pmain.c -lm -o pint
29 changes: 29 additions & 0 deletions QUICKSTART
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
To quickly get started with Poe, simply perform a "make", and then call the
executable pint with the name of one of the example scripts. The given
example scripts are:

sum.poe: Sums all integers passed to the script as command line options.
try: pint sum.poe 3 4 5
echo.poe: A simple implementation of the echo command line tool.
try: pint echo.poe hello world
file.poe: Consumes a filename as a command line argument and echoes the
contents of that file, capitalized.
try: pint file.poe pmain.c

The following files are less useful as command line tools, but are more
interesting to read in terms of the features of the language.

require.poe: Loads a package for "smart arrays" from smart.poe and does some
operations with it. See smart.poe for the implementation.
try: pint require.poe
scope.poe: Quickly presents some of the more interesting quirks of Poe's
approach to lexical scoping. To understand the program's output,
make sure to follow along with the source code, which is heavily
commented.
try: pint scope.poe
blocks.poe: This script is low on code but heavy on comments; it is a
discussion of Poe's blocks and code objects.
std.poe: The Poe implementation of a number of the standard library's functions
are given here. The compiled bytecode is built-in to the Poe
interpreter, so it would be redundant to execute this as a script;
nevertheless, it may be interesting to browse the source code.
90 changes: 90 additions & 0 deletions README
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
VERSION INFO:
Poe v. 1.0.2 has been released. There have been numerous bug fixes,
particularly to the garbage collector, and I have included a documentation
file 1000lines.txt which will serve, for now, as non-comprehensive documentation of the language.

---

The Poe programming language is now in the Alpha release stage. As testing
continues, more minor bugs are bound to be caught; I encourage you to
contact me if you do find any. Included in this directory are example
scripts that have demonstrated to work very reliably.

To quickly get started with Poe, see the included QUICKSTART document.

The process of building the language should be self-evident: an extremely
simple Makefile is included, which you may edit to serve your needs.
The executable that will be built is called "pint", for "Poe interpreter";
to execute a script, do

pint scriptname.poe

Explicit per-script instructions are given in each script. The scripts
serve to demonstrate a number of the language's features, including
I/O and command line interaction, metatables, and a unique approach
to lexical scoping. More comprehensive documentation of the language and
its operators/features is forthcoming.

As a side note, be aware that executing a script leaves a "remnant" bytecode
file with a .pbc extension; for example, executing scope.poe will leave a
scope.pbc file after execution. These bytecode files can be safely deleted.

SOURCE CODE
pdata.h
pdata.c -- Basic functions for keying Poe structures
puniverse.h
puniverse.c -- Basic functions for creating Poe universes
pmem.h
pmem.c -- Basic functions for memory allocation
pbc.h
pbc.c -- Bytecode compilation
pgc.h
pgc.c -- Garbage collector
pexec.h
pexec.c -- Functions for bytecode execution
pdo1.h
pdo1.c -- Instruction execution, pt. 1
pdo2.h
pdo2.c -- Instruction execution, pt. 2
y.tab.h
y.tab.c -- Bison-generated parser
lex.yy.h
lex.yy.c -- Flex-generated lexer
pio.h
pio.c -- I/O library
pstd.h
pstd.c -- standard library
pmath.h
pmath.c -- math library
pstring.h
pstring.c -- string library
pmain.c
Makefile

EXAMPLE SCRIPTS
std.poe -- A script containing definitions for standard library functions.
A pre-compiled copy of the bytecode is built into the interpreter and is
executed during every invocation of it; this does not need to be manually
executed.
sum.poe -- A simple tool to sum the numbers given on the command line (and
in a single line of code, no less).
echo.poe -- A simple implementation of the echo command line tool.
file.poe -- A tool to open a file and print its contents, with all letters
capitalized.
smart.poe -- A small library containing a definition for "smart arrays",
which are arrays that throw errors when an attempt is made to access
an element outside of the array's "size". Tests for this script are
included in the require.poe file.
require.poe -- A small script demonstrating file inclusion; it loads and
executes some of the functions in the smart.poe file.
scope.poe -- A long script demonstrating some of the unique lexical scoping
features which Poe provides.
blocks.poe -- A discussion of Poe's blocks and some of the unique situations they present.

DOCUMENTATION
1000lines.txt -- An introduction to the Poe programming language, for
the seasoned programmer, in less than 1000 lines.
standard_library.txt -- A list of the currently implemented and to-be-
implemented standard library functions.
TODO -- An ongoing list of tasks which need to be completed.
QUICKSTART -- A list of instructions to quickly execute some Poe scripts
4 changes: 4 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
1) Finish up the standard library; this information is contained in the
pstd.h and standard_library.txt files.
2) Implement runtime error messages; currently, the interpreter exits
silently if it encounters an error, which is far from acceptable.
119 changes: 119 additions & 0 deletions blocks.poe
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/* blocks.poe: a demonstration of Poe blocks */

/* usage: pint blocks.poe */

/* Poe blocks are inspired by Ruby blocks, but they are not identical. In Ruby,
blocks are just anonymous functions with a more elegant syntax. Poe offers
anonymous functions, so everything that can be done with a Ruby block
can be done quite simply with an anonymous function. However, I have not
seen a Ruby construct that can do everything Poe blocks can do -- in fact,
Poe blocks are very unique.

Essentially, a Poe block is a block of compiled code that can be invoked
anywhere with the do statement. A Poe block is like a function in a lot
of ways, except:
1) A block cannot be called or passed arguments.
2) A block does not have a parent table.
3) A new symbol table is not created when a block is invoked.
4) A block cannot return a value, and a block invocation can only be
used as an independent statement, not in the middle of an expression.

The following code will define and execute a block called b. */

b = block:
unless local a: a = 0; end;
print("hello from block b");
a = a+1;
print(a);
end;
print(b); #! Notice that the block's type is "code"
do b;

/* Evidently, invoking the block executed the block's statements; it is as
if the block's statements were pasted in place of the block invocation
"do b;". Now, a global variable a exists, and it has the value 1.
Now: */

exec_blk = func(blk):
do blk;
end;

exec_blk(b);
print(a);

/* Notice that even though the block was defined in the global scope, the
block does not operate on the global a, instead operating on a variable
local to the exec_blk function. The global a is unchanged, until we
execute the block in the global scope: */

do b;
do b;

/* So blocks have dynamic scope; exactly what actions they carry out is
dependent on where they are invoked.

Further, blocks can be read from files. Calling compile() with a file
or string containing Poe source code returns a code object, whose
lexical rules are exactly like those that we've observed here.

This is a fundamental trait of Poe bytecode objects: a string of code will
have different behavior depending on the context. Poe identifiers are never
bound to a particular scope; a symbol like "print" might lose its meaning if
we move to a context which doesn't have any conception of "print" (see
scope.poe for an example).

This approach is unconvential, but interesting: for example, say we want
to execute some bytecode from an unknown source. It may be prudent to
"sandbox" this code by executing it in a context which doesn't have
access to more sensitive functions, like I/O functions. As an artificial
example, say we had a function
delete_everything()
which automatically deletes everything on disk. Say we had some compiled
bytecode which may or may not contain a reference to delete_everything
in it. Then we could execute the bytecode with a do instruction from
within the local scope of a sandbox, whose access to certain global
variables has been restricted. For example, if we wanted to make
a sandbox function where only the print function and string library
are accessible, we could do something like this: */

sandbox = func(blk):
print = print;
string = string;
locals.^ = undef; #! Set the local table's parent to undef
do blk;
end;

/* The sandbox function grabs local copies of print and string (notice that
by default, assignment is local, so the assignment "print = print;" is
equivalent to "local print = print;"). Now, if blk attempts to access
any other global function, delete_everything() or otherwise, the result
will be undef. There is no way for the code in blk to access any
function but print and the string functions -- as far as blk is
concerned, it is executing within the global scope.

As a final note, I will discuss how I dealt with the problem of scoping
when implementing the standard library functions load and require. Load
and require compile and execute bytecode, like so:
require("example.poe");
However, the result of compiling example.poe is bytecode, and immediately,
we run into scoping problems. Keep in mind that require is a function, so
all of example.poe's assignments would be operating on require's symbol
table rather than the global symbol table; without a workaround, this
would make it very difficult to create libraries. As a concrete example,
take the function
max = func(a,b):
if a>b: return(a);
else: return(b); end;
end;
Say this were included in example.poe, which is a larger library that
we intend to include in many scripts. However, if we simply executed the
bytecode in require, max would be defined in *require's local scope*
rather than globally, which is where we want it.

The key is the standard library function export, which copies the
contents of a table into another table. The require problem can be
solved by calling export(locals), which exports the contents of the local
symbol table into the global table. So require executes example.poe's
bytecode as normal, and then exports the definitions into the global scope.
If you plan to play with Poe's scope, export will probably be a very
useful function for you. */
17 changes: 17 additions & 0 deletions echo.poe
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* echo.poe: a simple Poe implementation of echo, without command line
argument implementation */

/* usage: pint echo.poe ... */

/* join: concatenate all the string members of an array with delim interspersed
between each */
join = func(delim, arr):
acc = "";
for key, str in arr:
string.concat(acc,str,acc);
if key<@arr: string.concat(acc,delim,acc); end;
end;
return(acc);
end;

print(join(" ",argv));
20 changes: 20 additions & 0 deletions file.poe
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* file.poe: echoes the contents of a file to the terminal, capitalizing
all lowercase characters. The name of the file to be echoed is given as
a command line argument: for instance,
pint file.poe pmain.c
to capitalize and echo the contents of pmain.c. */

f = io.open(argv[0],"r");

a = io.tostr(f); #! convert the file f to an array of strings

capitalize = func(c):
if c>='a' and c<='z':
return(c-'a'+'A');
end;
return(c);
end;

print(map!(capitalize,a));

io.close(f);
Loading

0 comments on commit c8ccf84

Please sign in to comment.