The "language" proposed here is a type of real time state machine or token interpreter which is targeted at pattern matching and translation using a variation of the BNF grammer. Other examples are CUMP Byte Code, and Minimal Controller Idea.
| "when you see this" ? "output this" .
| "or change this" ? "into this" .
| @Timer "12:00:00" ? "Good Morning!" @Day = Day + 1 .
|| &h1B ? @PCLState "1"||| "&f" ? @PCLMacro|
|| "*p" num | 'X' ...|
| @Comment "This is a comment, it will be copied to the bit bucket" .
to get the current exchange rate with Italy:
@http://www.x-rates.com/htmlgraphs/ITL30.htm 'current rate: ' number ? 'Our exchange rate with Italy is ' number .
to translate written text to numbers. "one-two" becomes "1-2" The ":" word causes the OutBuffer to become a local buffer so that the digits that are matched are placed in a local holding area. When digit is referenced after the "?" word, the InBuffer is redirected to that local buffer. Each call to digit adds or removed one entry from the local buffer so that more than one digit can be matched in a single pattern.
| digit '-' digit ? digit '-' digit
The idea of very small, punctuation type words rather than english keywords is not commonly seen in programming languages. Years ago, I wondered why the punctuation marks in standard english text were of no use. The Bitscope Command Set is a brilliant example of the application of punctuation to small programming languages. Compair this to the Oricom ICTC command set, NPCI or the PICos commands. CUMP Byte Code, and Minimal Controller Idea are other attempts. Meta-L tries to use the same meaning of punctuation symboles from human text in program code. "(" and ")" denote a comment rather than a memory reference.
|"Fail to" This word does the following:
|"Match" The quote form is for standard ASCII text. The & form is
followed by a code indicating radix and is used for binary data e.g. &h1B
or &b00011011 or &o033 are the same value. r can also be a 2 digit
decimal number so &h and &16, &o and &08, &b and &02
are the same. (note that Meta-L is all about conditionals, so the logical
"and" is implied, and "or" is "|" aka Fail to). Match works differently depending
on whether the "?" aka succeed word has appeared. Prior to ?, match rejects
or continues testing the value of an input buffer. After ?, match (and its
"=" form, see below) is copying the result of a match to the output
buffer. The match word does the following :
|"Equals" This is a combination of "Match" and "Evaluate" when succeed
("?") has appeared.
|"Success" or "Then" This word indicates that a match has been made and sets up the system for output by switching the meaning of the "" word to the output version vice the input version. At run-time it resets the "success/fail-flag" so that the "|" (fail-to) word will return rather than continuing to the next possible match.|
|"Evaluate" Begins the evaluation of the words in the section using simple math and variable names defined by the @ word. Constants are expressed in decimal by default. Other radix are indicated by different "&" numbers. For example "&o" is octal and "&b" is binary. See also: "="|
|"At" changes InBuf (before the ?) or OutBuf (after the ?) to a different
run-time address according to the text following the word and calls
the method code for that word with a "change-time" message. Think of this
as an assembler ORG directive. Actually, @ can just change the outbuffer
to point to the InBuf (before the ?) or OutBuf (after the ?) and the word
following can just OutBuf its address.
Each defined word has a local buffer to hold the result of execution of that word. These local buffers are accessed by their own words (and tokens) since the word itself is responsible for setting the pointer to the local buffer. Words that always generate fresh content when accessed will point to a zero length buffer (with a method token before the length) so that their method is always called when InBuf is accessed.
Some pre-defined buffer names may include:
When calling MetaL from the command line in an OS, the InBuffer will start as the command line and the OutBuffer will be internal memory. The command line can change the InBuffer and or OutBuffer to StdIO or what ever as needed.
|"Return" or "Stop" this word returns to the previous level after setting up the system for input by switching the meaning of the " word to the input version vice the output version.|
|"Define" causes a new word to be defined in the token dictionary. That
word will store and keep track of what ever its code produces and allow that
product to be recalled and used in other code.
The following items are placed in the OutBuffer:
The next execution of a given instance of define (when the code before the ":" succeeds) will cause the token created by that define to be placed in the outbuffer followed by a count of the number of references to this word in the current code. This count is incremented after each reference and cleared when "?" (Then) is compiled.
The reference count is also stored on the stack, and a new thread is launched to finish compiling the code following the reference to this word. After that thread completes (the "." is executed), the count is restored to its value at the point where the defined word was referenced.
If the match for the name of the defined word occured after the "?" (Then), the text following the name of the word is checked for a "#" and a number. This number, or the current reference count, is placed after the token in the Outbuffer.
The defined code itself is not executed. The thread started by execution of the define builds token is stopped (like the "." (stop) word).
When the code that uses that definition is executed a new thread will start with the Define "Does" code (basically a buffer handler method) which behaves differently if executed before or after "?" (Then).
Before "?" (Then) it will save the OutBufPtr, and make the local Buffer the OutBuffer,
Each time the word is called, prior to the "?" (Then) word, the reference count for this instance of the word is checked and the local buffer from this entry on will be cleared. Data resulting from the execution of the thread after the original ":" will be appended to the buffer pre-pended with a length byte.
In any pre "?" (Then) case, DefineDoes will execute the code following the local Buffer (the code that was compiled when the word was defined). When that code has completed, the OutBufPtr will be restored and any data OutBuffed by the code following the define will be available to the calling code by reference to the defined tokens local buffer.
After the "?" (Then), executing the defined word will simply tranfer the entry specified by the reference count from the local buffer to the OutBuf, and return without executing the code for the defined word.
|"Comment" Text enclosed in parens are ignored or used to describe the word when used after a ":"|
Each word address is expected to point to a machine language command (i.e. the "interpreter" just jumps to the code at that address) and that command can be a call (or more likely a software interrupt or "restart") that invokes the interpreter with the address of the word on the stack which will point to the addresses that make up the word.
Rather than using word addresses, we can use a single byte "token" or index to a memory address in a table. To increase the number of available subroutines past 255, some of the routines can implement their own jump table and fetch the value of a second byte following their own token in the code to use as an index in their own table. This process can be repeated as often as needed.
To improve performance, the current thread IP can be held in a register (vice on the stack) and can be pushed to the stack when a new thread is started by the interpreter.
Instead of a conditional word like MATCH popping its thread to fail to the next entry, Just set the CY flag and return to FailTo which will then Jump to the next entry (if CY=1) or return (popping the FailToPtr) to the Parent (if CY=0) when a simple TRet is encountered indicating success at the end of a series of trials.
The following example assumes that the inbuffer contains "good"
|addr0||Begin (call interpreter)||IPInt <addr0>|
|Failto <addr1>||IPInt <addr0> FailtoRun <addr1>|
|Match "bad"||IPInt <addr0> FailtoRun <addr1>||;carry flag set and return|
|IPInt <addr1>||;FailtoRun Jumps to <addr1>|
|Failto <addr2>||IPInt <addr1> FailtoRun <addr2>|
|Match "good"||IPInt <addr1> FailtoRun <addr2)||;Carry flag clear|
|IPInt <addr1>||;FailtoRun continues at new IP|
TRet ;FailtoRun drops <addr> and returns
|[(match)<len)(buffername)](bufdef)(token)(startthread)(method||static)(buffer len:byte=[1...255])(buffer data:15 bytes)|
|dynamic)(byte=0)(chunk pointer)[(chunk pointer)](byte=0)|
|virtual)(byte=0)(cache len)(cache:14bytes max) or (byte=0)(byte=0)(cache chunk pointer)(byte=0)|
|file)(byte=0)(handle len)(handle/zero if not open)(url len)(url)(offset len)(file offset)(cache chunk pointer)|
After a static buffer is filled with 15 bytes of data, its method can be called to "make room" at which time, the static method will be replaced with a dynamic method and a chunk allocated, the original data moved to it, a chunk pointer inserted in the buffer, and the length changed to zero. If the dynamic buffer is not often accessed and free chunks are no longer available, the chunk allocator may replace the dynamic method with a virtual method and move the data to a disk file.
It should be kept in mind that all the program code, data, dictionary etc.. that make up the system must also be contained in buffers. Match, fail, Outbuf must all take into account the number of bytes left in the current In and Out buffers and break up output / repeat input operations accordingly. All programs will basically be paragraph aligned to simplify memory management.
|which can be
a pointer of:
|4GB||4G bytes||1 byte||32 bits|
|4GB||16M chunks||256 bytes|
|4GB||64k segments||65536 bytes||16 bits|
|1MB||1M bytes||1 byte||20 bits|
|1MB||4096 chunks||256 bytes|
|1MB||16 segments||65536 bytes||4 bits|
|640KB||2560 chunks||256 bytes|
|640KB||10 segments||65536 bytes||4 bits|
|64KB||64k bytes||1 byte||16 bits|
|64KB||256 chunks||256 bytes||8 bits|
|64KB||2048 buffer entries||about 32 bytes|
One solution may be to convert the memory buffer to a file buffer and
then keep track of the chunks that are in cache. We can specify that some
number of chunks be used to hold access count information in each byte rather
than a chunk pointer. One chunk per segment suffices
if each chunks access count is limited to 255. Each method that accesses
a chunk must select the related byte in the index and increase its value
if it is not already 255. It should be noted that we wish to remove first
groups of chunks pointed to by index chunks (because the overhead required
to track the position of the removed chunk is probably greater than the space
that would be released by the removal of one chunk) that A) do not contain
data often needed and B) are not index chunks themselves that are
used to get to data that is often needed. Also, while the relationship between
index chunks that point to infrequently used data is related to the structure
of buffers, it is entirely possible that one part of a buffer will be constantly
in use while another part of the same buffer is never used after its initial
For simplicity, it would seem to be best to combine this system with a system for tracking unused chunks. See Chunk Allocation elsewhere.
When the virtual memory manager is called to free space:
In the case that only one chunk referenced in a chunk index is repeatedly
called when all the other chunks are never used; if we only increment the
chunk usage count for the index chunk when we shift from one chunk to the
next - since the count will remain low for the index there is a better chance
that it will be selected to page out (good) and if the high usage count for
the high usage chunk is noted when the indexed chunks are being processed,
perhaps the index chunks can be reorganized so that the high usage chunk
is moved to a different index. For example a buffer with only one commonly
used chunk could end up with chunk pointers to index chunks that are all
paged out followed by one chunk pointer directly to the we want to keep handy
followed by more chunk pointer to paged out index chunks. Actually all the
paged out chunks should be combined into one paged out index chunk before
the commonly used chunk and one other after it. This results in only 3 chunks
and the buffer area as the worst case minimum memory usage for any size buffer
where only one chunk is really needed.
|dynamic var||medium||indirect||pointer to pointer|
|virtual||medium slow||disk/cache||VM manager|
|file: /Techref/language/meta-l/index.htm, 37KB, , updated: 2016/1/5 15:41, local time: 2016/2/12 06:19,
|©2016 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?|
<A HREF="http://www.piclist.com/techref/language/meta-l/index.htm"> Meta-Language</A>
|Did you find what you needed?|
PICList 2016 contributors:
o List host: MIT, Site host massmind.org, Top posters @20160212 RussellMc, Bob Blick, Peter, alan.b.pearce, embedded systems, Neil, Dwayne Reid, Isaac Marino Bavaresco, IVP, Jean-Paul Louis,
* Page Editors: James Newton, David Cary, and YOU!
* Roman Black of Black Robotics donates from sales of Linistep stepper controller kits.
* Ashley Roll of Digital Nemesis donates from sales of RCL-1 RS232 to TTL converters.
* Monthly Subscribers: Gregg Rew. on-going support is MOST appreciated!
* Contributors: Richard Seriani, Sr.
Welcome to www.piclist.com!