-
Notifications
You must be signed in to change notification settings - Fork 0
Home
- Concepts - a short overview of compilers and how Froley fits in.
- Token Type Definition Reference - how to define token types.
- Scanner Command Reference - Scanner coding reference .
- Parser Command Reference - Parser coding reference.
A .froley file is used to define token types and write the logic for the Scanner and Parser. Name it with the name of the language you're defining - for example, Simple.froley for a language called "Simple".
A .froley file is comprised of three sections: [tokens](Token Type Definition Reference), [scanner](Scanner Command Reference), and [parser](Parser Command Reference).
There are four types of comments in Froley:
# Single-line comment
#{ Multi-line comment }#
==== Single-line "HR" comment
---- Single-line "HR" comment
Taken from the "Simple" examples in the Examples/ folder.
--------------------------------------------------------------------------------
tokens
--------------------------------------------------------------------------------
tokens begins an unnamed group of token type definitions.
Note that ---- begins a single-line comment so the "HR" bars are for visual formatting only.
EOL(end of line)
IDENTIFIER identifier [content]
NUMBER number [content]
STRING string [content]
Sets of token type names, symbols, and optional [attributes]. Symbols can't have any spaces
as part of them unless you declare the token types using the NAME(symbol) variation.
[content] is a built-in attribute type that causes created tokens to automatically have
a copy of the Scanner's scan buffer as the token's content string.
--------------------------------------------------------------------------------
tokens Symbols
--------------------------------------------------------------------------------
SYMBOL_BANG !
SYMBOL_CARET ^
SYMBOL_CLOSE_PAREN ) [structural]
SYMBOL_EQUALS =
SYMBOL_MINUS -
SYMBOL_OPEN_PAREN (
SYMBOL_PLUS +
SYMBOL_SLASH /
SYMBOL_STAR *
A named token-type group for symbols. The group name can be used to match scan buffer content with token symbols en masse.
--------------------------------------------------------------------------------
tokens Keywords
--------------------------------------------------------------------------------
KEYWORD_PRINTLN println
A named group for keywords. Keywords should be kept separate from symbols because of different scanning mechanisms used. For example, if the Scanner scans ++ it does not need to read further characters to classify ++ as an increment operator. On the other hand if else is scanned, it cannot be classified as the else operator because the next characters might be where for a variable named elsewhere. Keyword matching must first read a complete ID and then check for keywords.
--------------------------------------------------------------------------------
scanner
--------------------------------------------------------------------------------
- main
consume [ \r\t]* # whitespace
if (consume('\n')) produce EOL
# single-line comment
if consume("#" [^\n]*) restart
if (not hasAnother) halt
markPosition
match
produceAny Symbols
endMatch
scan_id_or_keyword
scan_number
scan_string
syntaxError
-
mainis the topmost routine so it becomes the start routine. -
The start routine will be repeatedly called until a
haltcommand is executed. The general approach is to produce one new Token each "pass". -
consumecan operate on characters, strings, or patterns. If the specified sequence can be read from the input, that sequence is discarded andconsumereturnstrue. Otherwise nothing is read (no partial matches) andconsumereturnsfalse. -
Patterns are a simple form of RegEx comprised of the following pattern elements:
- Curly braces
{...}can be used to unambiguously enclose an entire pattern sequence. - Single and double-quote strings are literals that must be matched exactly.
-
[...]defines a set of single characters, any single one of which can be read to match that pattern element. -
[^...]matches any single character except those listed - "NOT these". -
[A-Z]matches any single character betweenAandZ. Mix and match:[A-Za-z_]etc. - Following a pattern element with
*,+, or?allows it to occur zero or more times, one or more times, or optionally (at most once), respectively.
- Curly braces
-
restartabandons the current pass and restarts at the start routine. -
hasAnotherreturnstrueas long as there are any characters waiting to be read from the input. -
markPositionuses the current line and column of input the next time a Token is created. -
A regular
match(also calledmatch input) uses an efficient scan table technique to read the longest match among its constituent mass-produceAnyand individualcasecomparisons. In this case of any of theSymbolstoken group is matched then a corresponding Token is produced. Producing a Token automatically adds it to the output queue and restarts the Scanner at the start routine. -
scan_id_or_keyword,scan_number, andscan_stringare all routine calls. Each routine is defined to either produce a Token or return if unsuccessful. -
The final command is
syntaxError, which generates an "Unexpected input" error.- scan_id_or_keyword if (not scan([_a-zA-Z][_a-zA-Z0-9]*)) return match buffer produceAny Keywords others: produce IDENTIFIER endMatch
-
Routines cannot accept any parameters or return any values. However global variables can be used to transmit information across routine calls (not shown here).
-
scanis likeconsumeexcept instead of discarding the matching input, the characters are stored in the internal scanbuffer. So ifscan(...)returns true then a matching set of characters is stored inbuffer. -
match bufferattempts to match the characters in the scan buffer against the listed symbols and cases. The entire buffer must be matched to count as a match. If there are no matches then theothersblock is executed if defined. -
Per standard approach, keywords should be handled by first reading a complete identifier and then attempting to match it with a defined keyword.
-
The
IDENTIFIERtoken type is defined with the attribute[content], so a newIDENTIFIERtoken will automatically get a copy of the scan buffer as its.contentstring.-
scan_number if (not hasAnother) return
if (scan [0-9]) scan_integer if (scan '.') scan_integer produce NUMBER elseIf (scan '.') scan_integer produce NUMBER else return endIf
-
scan_number could be simpler if we weren't filtering out _ characters from the input. Numbers like 123_456 are equivalent to 123456.
- scan_integer
while hasAnother
if (not scan([0-9]) and not consume('_'*)) return
endWhile
return
- scan_string
if (not consume('"')) return
while (hasAnother and not nextIs('\n'))
ch = read
if (ch == '"') produce STRING
elseIf (ch == '\\' and hasAnother) ch = read
collect ch
endWhile
syntaxError "Unterminated string."
Whereas the Scanner produces tokens onto a queue, with each token being independent of every other token, the Parser produces Cmd nodes onto a stack. Each command node produced removes zero or more nodes from the stack as its properties and then is itself added to the stack. The stack should have a single node on it - the root of the AST - when the start routine returns.
-
statements consume_eols
beginList while (hasAnother) on "println" arg_list create Println(args) elseOthers expression endOn consume_eols endWhile produceList Statements
-
consume_eolsis a call to another routine (not built-in). -
beginListmarks the current top of the node stack. WhenproduceListis later called, all nodes since become part of an extendedCmdListarbitrarily calledStatements. -
hasAnotherreturns true as long as there is another unread Token available. The input tokens are automatically set up to come from the Scanner's output. -
on "println"behaves likeconsume("println")with the additional behavior of making that consumed Token the current token stored with the next created or produced Cmd node. -
Tokens can be consume'd or scan'd using either their "symbol" or their TOKEN_NAME.
-
In the Scanner,
producecreates a Token and then restarts the Scanner. In the Parser,producecreates a new command node and returns from the current routine. -
createis likeproduceexcept the current routine continues instead of being return'd from.-
consume_eols while (consume(EOL)) noAction
-
arg_list beginList while (hasAnother and not nextIs(EOL)) expression endWhile produceList Args
-
expression assign
-
assign [rightBinary] on "=" -> Assign
-
-
A
[rightBinary]routine facilitates the construction of right-associative operator subtrees. If the token content werea=b=c, the resulting subtree here would beAssign( Access(a), Assign(Access(b),Access(c)) ). -
All "operation shorthand" routines (rightBinary, binary, preUnary, postUnary) implicitly pull their higher-precedence operands from the next routine. So
-
-> shorthand
-
add_subtract [binary] on "+" -> Add on "-" -> Subtract
-
multiply_divide [binary] on "*" -> Multiply on "/" -> Divide
-
power [binary] on "^" -> Power
-
factorial [postUnary] on "!" -> Factorial(operand)
-
negate [preUnary] on "-" -> Negate(operand)
-
term on '(' expression ')': return on IDENTIFIER -> Access(name=content) on NUMBER -> Number(value=content:Real) on STRING -> LiteralString(value=content) syntaxError
- additional_nodes create ReadVar(name:String) create WriteVar(name:String,new_value:Cmd)
-