version0.7.10
This commit is contained in:
parent
4d4b3b1c04
commit
36818817bd
14 changed files with 1011 additions and 0 deletions
152
doc/abstypes.txt
Normal file
152
doc/abstypes.txt
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
==============
|
||||
Abstract types
|
||||
==============
|
||||
|
||||
.. contents::
|
||||
|
||||
Abstract types in Nimrod provide a means to model different `units`:idx: of
|
||||
a `base type`:idx:.
|
||||
|
||||
|
||||
Use case 1: SQL strings
|
||||
-----------------------
|
||||
An SQL statement that is passed from Nimrod to an SQL database might be
|
||||
modelled as a string. However, using string templates and filling in the
|
||||
values is vulnerable to the famous `SQL injection attack`:idx:\:
|
||||
|
||||
.. code-block:: nimrod
|
||||
proc query(db: TDbHandle, statement: TSQL) = ...
|
||||
|
||||
var
|
||||
username: string
|
||||
|
||||
db.query("SELECT FROM users WHERE name = '$1'" % username)
|
||||
# Horrible security whole, but the compiler does not mind!
|
||||
|
||||
This can be avoided by distinguishing strings that contain SQL from strings
|
||||
that don't. Abstract types provide a means to introduce a new string type
|
||||
``TSQL`` that is incompatible with ``string``:
|
||||
|
||||
.. code-block:: nimrod
|
||||
type
|
||||
TSQL = abstract string
|
||||
|
||||
proc query(db: TDbHandle, statement: TSQL) = ...
|
||||
|
||||
var
|
||||
username: string
|
||||
|
||||
db.query("SELECT FROM users WHERE name = '$1'" % username)
|
||||
# Error at compile time: `query` expects an SQL string!
|
||||
|
||||
|
||||
It is an essential property of abstract types that they **do not** imply a
|
||||
subtype relation between the abtract type and its base type. Explict type
|
||||
conversions from ``string`` to ``TSQL`` are allowed:
|
||||
|
||||
.. code-block:: nimrod
|
||||
proc properQuote(s: string): TSQL =
|
||||
# quotes a string properly for an SQL statement
|
||||
...
|
||||
|
||||
proc `%` (frmt: TSQL, values: openarray[string]): TSQL =
|
||||
# quote each argument:
|
||||
var v = values.each(properQuote)
|
||||
# we need a temporary type for the type conversion :-(
|
||||
type TStrSeq = seq[string]
|
||||
# call strutils.`%`:
|
||||
result = TSQL(string(frmt) % TStrSeq(v))
|
||||
|
||||
db.query("SELECT FROM users WHERE name = $1".TSQL % username)
|
||||
|
||||
Now we have compile-time checking against SQL injection attacks.
|
||||
Since ``"".TSQL`` is transformed to ``TSQL("")`` no new syntax is needed
|
||||
for nice looking ``TSQL`` string literals.
|
||||
|
||||
|
||||
|
||||
Use case 2: Money
|
||||
-----------------
|
||||
Different currencies should not be mixed in monetary calculations. Abstract
|
||||
types are a perfect tool to model different currencies:
|
||||
|
||||
.. code-block:: nimrod
|
||||
type
|
||||
TDollar = abstract int
|
||||
TEuro = abstract int
|
||||
|
||||
var
|
||||
d: TDollar
|
||||
e: TEuro
|
||||
|
||||
echo d + 12
|
||||
# Error: cannot add a number with no unit with a ``TDollar``
|
||||
|
||||
Unfortunetaly, ``d + 12.TDollar`` is not allowed either,
|
||||
because ``+`` is defined for ``int`` (among others), not for ``TDollar``. So
|
||||
we define our own ``+`` for dollars:
|
||||
|
||||
.. code-block::
|
||||
proc `+` (x, y: TDollar): TDollar =
|
||||
result = TDollar(int(x) + int(y))
|
||||
|
||||
It does not make sense to multiply a dollar with a dollar, but with a
|
||||
unit-less number; and the same holds for division:
|
||||
|
||||
.. code-block::
|
||||
proc `*` (x: TDollar, y: int): TDollar =
|
||||
result = TDollar(int(x) * y)
|
||||
|
||||
proc `*` (x: int, y: TDollar): TDollar =
|
||||
result = TDollar(x * int(y))
|
||||
|
||||
proc `div` ...
|
||||
|
||||
This quickly gets tedious. The implementations are trivial and the compiler
|
||||
should not generate all this code only to optimize it away later - after all
|
||||
``+`` for dollars should produce the same binary code as ``+`` for ints.
|
||||
The pragma ``borrow`` has been designed to solve this problem; in principle
|
||||
it generates the trivial implementation for us:
|
||||
|
||||
.. code-block:: nimrod
|
||||
proc `*` (x: TDollar, y: int): TDollar {.borrow.}
|
||||
proc `*` (x: int, y: TDollar): TDollar {.borrow.}
|
||||
proc `div` (x: TDollar, y: int): TDollar {.borrow.}
|
||||
|
||||
The ``borrow`` pragma makes the compiler use the same implementation as
|
||||
the proc that deals with the abstract type's base type, so no code is
|
||||
generated.
|
||||
|
||||
But it seems we still have to repeat all this boilerplate code for
|
||||
the ``TEuro`` currency. Fortunately, Nimrod has a template mechanism:
|
||||
|
||||
.. code-block:: nimrod
|
||||
template Additive(typ: typeExpr): stmt =
|
||||
proc `+` *(x, y: typ): typ {.borrow.}
|
||||
proc `-` *(x, y: typ): typ {.borrow.}
|
||||
|
||||
# unary operators:
|
||||
proc `+` *(x: typ): typ {.borrow.}
|
||||
proc `-` *(x: typ): typ {.borrow.}
|
||||
|
||||
template Multiplicative(typ, base: typeExpr): stmt =
|
||||
proc `*` *(x: typ, y: base): typ {.borrow.}
|
||||
proc `*` *(x: base, y: typ): typ {.borrow.}
|
||||
proc `div` *(x: typ, y: base): typ {.borrow.}
|
||||
proc `mod` *(x: typ, y: base): typ {.borrow.}
|
||||
|
||||
template Comparable(typ: typeExpr): stmt =
|
||||
proc `<` * (x, y: typ): bool {.borrow.}
|
||||
proc `<=` * (x, y: typ): bool {.borrow.}
|
||||
proc `==` * (x, y: typ): bool {.borrow.}
|
||||
|
||||
template DefineCurrency(typ, base: expr): stmt =
|
||||
type
|
||||
typ* = abstract base
|
||||
Additive(typ)
|
||||
Multiplicative(typ, base)
|
||||
Comparable(typ)
|
||||
|
||||
DefineCurrency(TDollar, int)
|
||||
DefineCurrency(TEuro, int)
|
||||
|
||||
572
doc/astspec.txt
Normal file
572
doc/astspec.txt
Normal file
|
|
@ -0,0 +1,572 @@
|
|||
The AST in Nimrod
|
||||
=================
|
||||
This section describes how the AST is modelled with Nimrod's type system.
|
||||
The AST consists of nodes (``PNimrodNode``) with a variable number of
|
||||
children. Each node has a field named ``kind`` which describes what the node
|
||||
contains:
|
||||
|
||||
.. code-block:: nimrod
|
||||
|
||||
type
|
||||
TNimrodNodeKind = enum ## kind of a node; only explanatory
|
||||
nnkNone, ## invalid node kind
|
||||
nnkEmpty, ## empty node
|
||||
nnkIdent, ## node contains an identifier
|
||||
nnkIntLit, ## node contains an int literal (example: 10)
|
||||
nnkStrLit, ## node contains a string literal (example: "abc")
|
||||
nnkNilLit, ## node contains a nil literal (example: nil)
|
||||
nnkCaseStmt, ## node represents a case statement
|
||||
... ## many more
|
||||
|
||||
PNimrodNode = ref TNimrodNode
|
||||
TNimrodNode {.final.} = object
|
||||
case kind ## the node's kind
|
||||
of nnkNone, nnkEmpty, nnkNilLit:
|
||||
nil ## node contains no additional fields
|
||||
of nnkCharLit..nnkInt64Lit:
|
||||
intVal: biggestInt ## the int literal
|
||||
of nnkFloatLit..nnkFloat64Lit:
|
||||
floatVal: biggestFloat ## the float literal
|
||||
of nnkStrLit..nnkTripleStrLit:
|
||||
strVal: string ## the string literal
|
||||
of nnkIdent:
|
||||
ident: TNimrodIdent ## the identifier
|
||||
of nnkSym:
|
||||
symbol: PNimrodSymbol ## the symbol (after symbol lookup phase)
|
||||
else:
|
||||
sons: seq[PNimrodNode] ## the node's sons (or children)
|
||||
|
||||
For the ``PNimrodNode`` type, the ``[]`` operator has been overloaded:
|
||||
``n[i]`` is ``n``'s ``i``-th child.
|
||||
|
||||
To specify the AST for the different Nimrod constructs, the notation
|
||||
``nodekind(son1, son2, ...)`` or ``nodekind(value)`` or
|
||||
``nodekind(field=value)`` is used.
|
||||
|
||||
|
||||
Leaf nodes/Atoms
|
||||
================
|
||||
A leaf of the AST often corresponds to a terminal symbol in the concrete
|
||||
syntax.
|
||||
|
||||
----------------- ---------------------------------------------
|
||||
Nimrod expression corresponding AST
|
||||
----------------- ---------------------------------------------
|
||||
``42`` ``nnkIntLit(intVal = 42)``
|
||||
``42'i8`` ``nnkInt8Lit(intVal = 42)``
|
||||
``42'i16`` ``nnkInt16Lit(intVal = 42)``
|
||||
``42'i32`` ``nnkInt32Lit(intVal = 42)``
|
||||
``42'i64`` ``nnkInt64Lit(intVal = 42)``
|
||||
``42.0`` ``nnkFloatLit(floatVal = 42.0)``
|
||||
``42.0'f32`` ``nnkFloat32Lit(floatVal = 42.0)``
|
||||
``42.0'f64`` ``nnkFloat64Lit(floatVal = 42.0)``
|
||||
``"abc"`` ``nnkStrLit(strVal = "abc")``
|
||||
``r"abc"`` ``nnkRStrLit(strVal = "abc")``
|
||||
``"""abc"""`` ``nnkTripleStrLit(strVal = "abc")``
|
||||
``' '`` ``nnkCharLit(intVal = 32)``
|
||||
``nil`` ``nnkNilLit()``
|
||||
``myIdentifier`` ``nnkIdent(ident = !"myIdentifier")``
|
||||
``myIdentifier`` after lookup pass: ``nnkSym(symbol = ...)``
|
||||
----------------- ---------------------------------------------
|
||||
|
||||
Identifiers are ``nnkIdent`` nodes. After the name lookup pass these nodes
|
||||
get transferred into ``nnkSym`` nodes. However, a macro receives an AST that
|
||||
has not been checked for semantics and thus the identifiers have not been
|
||||
looked up. Thus macros deal with ``nnkIdent`` nodes.
|
||||
|
||||
|
||||
Calls/expressions
|
||||
=================
|
||||
|
||||
Command call
|
||||
------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
echo "abc", "xyz"
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkCommand(nnkIdent(!"echo"), nnkStrLit("abc"), nnkStrLit("xyz"))
|
||||
|
||||
|
||||
Call with ``()``
|
||||
----------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
echo("abc", "xyz")
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkCall(nnkIdent(!"echo"), nnkStrLit("abc"), nnkStrLit("xyz"))
|
||||
|
||||
|
||||
Infix operator call
|
||||
-------------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
"abc" & "xyz"
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkInfix(nnkIdent(!"&"), nnkStrLit("abc"), nnkStrLit("xyz"))
|
||||
|
||||
|
||||
Prefix operator call
|
||||
--------------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
? "xyz"
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkPrefix(nnkIdent(!"?"), nnkStrLit("abc"))
|
||||
|
||||
|
||||
Postfix operator call
|
||||
---------------------
|
||||
|
||||
**Note:** There are no postfix operators in Nimrod. However, the
|
||||
``nnkPostfix`` node is used for the *asterisk export marker* ``*``:
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
identifier*
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkPostfix(nnkIdent(!"*"), nnkIdent(!"identifier"))
|
||||
|
||||
|
||||
Call with named arguments
|
||||
-------------------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
writeln(file=stdout, "hallo")
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkCall(nnkIdent(!"writeln"),
|
||||
nnkExprEqExpr(nnkIdent(!"file"), nnkIdent(!"stdout")),
|
||||
nnkStrLit("hallo"))
|
||||
|
||||
|
||||
Dereference operator ``^``
|
||||
--------------------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
x^
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkDerefExpr(nnkIdent(!"x"))
|
||||
|
||||
|
||||
Addr operator
|
||||
-------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
addr(x)
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkAddr(nnkIdent(!"x"))
|
||||
|
||||
|
||||
Cast operator
|
||||
-------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
cast[T](x)
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkCast(nnkIdent(!"T"), nnkIdent(!"x"))
|
||||
|
||||
|
||||
Object access operator ``.``
|
||||
----------------------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
x.y
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkDotExpr(nnkIdent(!"x"), nnkIdent(!"y"))
|
||||
|
||||
|
||||
Array access operator ``[]``
|
||||
----------------------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
x[y]
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkBracketExpr(nnkIdent(!"x"), nnkIdent(!"y"))
|
||||
|
||||
|
||||
Parentheses
|
||||
-----------
|
||||
|
||||
Parentheses for affecting operator precedence or tuple construction
|
||||
are built with the ``nnkPar`` node.
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
(1, 2, (3))
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkPar(nnkIntLit(1), nnkIntLit(2), nnkPar(nnkIntLit(3)))
|
||||
|
||||
|
||||
Curly braces
|
||||
------------
|
||||
|
||||
Curly braces are used as the set constructor.
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
{1, 2, 3}
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkCurly(nnkIntLit(1), nnkIntLit(2), nnkIntLit(3))
|
||||
|
||||
|
||||
Brackets
|
||||
--------
|
||||
|
||||
Brackets are used as the array constructor.
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
[1, 2, 3]
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkBracket(nnkIntLit(1), nnkIntLit(2), nnkIntLit(3))
|
||||
|
||||
|
||||
Ranges
|
||||
------
|
||||
|
||||
Ranges occur in set constructors, case statement branches or array slices.
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
1..3
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkRange(nnkIntLit(1), nnkIntLit(3))
|
||||
|
||||
|
||||
If expression
|
||||
-------------
|
||||
|
||||
The representation of the if expression is subtle, but easy to traverse.
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
if cond1: expr1 elif cond2: expr2 else: expr3
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkIfExpr(
|
||||
nnkElifExpr(cond1, expr1),
|
||||
nnkElifExpr(cond2, expr2),
|
||||
nnkElseExpr(expr3)
|
||||
)
|
||||
|
||||
|
||||
Statements
|
||||
==========
|
||||
|
||||
If statement
|
||||
------------
|
||||
|
||||
The representation of the if statement is subtle, but easy to traverse. If
|
||||
there is no ``else`` branch, no ``nnkElse`` child exists.
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
if cond1:
|
||||
stmt1
|
||||
elif cond2:
|
||||
stmt2
|
||||
elif cond3:
|
||||
stmt3
|
||||
else:
|
||||
stmt4
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkIfStmt(
|
||||
nnkElifBranch(cond1, stmt1),
|
||||
nnkElifBranch(cond2, stmt2),
|
||||
nnkElifBranch(cond3, stmt3),
|
||||
nnkElse(stmt4)
|
||||
)
|
||||
|
||||
|
||||
When statement
|
||||
--------------
|
||||
|
||||
Like the ``if`` statement, but the root has the kind ``nnkWhenStmt``.
|
||||
|
||||
|
||||
Assignment
|
||||
----------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
x = 42
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkAsgn(nnkIdent(!"x"), nnkIntLit(42))
|
||||
|
||||
|
||||
Statement list
|
||||
--------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
stmt1
|
||||
stmt2
|
||||
stmt3
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkStmtList(stmt1, stmt2, stmt3)
|
||||
|
||||
|
||||
Case statement
|
||||
--------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
case expr1
|
||||
of expr2, expr3..expr4:
|
||||
stmt1
|
||||
of expr5:
|
||||
stmt2
|
||||
elif cond1:
|
||||
stmt3
|
||||
else:
|
||||
stmt4
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkCaseStmt(
|
||||
expr1,
|
||||
nnkOfBranch(expr2, nnkRange(expr3, expr4), stmt1),
|
||||
nnkOfBranch(expr5, stmt2),
|
||||
nnkElifBranch(cond1, stmt3),
|
||||
nnkElse(stmt4)
|
||||
)
|
||||
|
||||
The ``nnkElifBranch`` and ``nnkElse`` parts may be missing.
|
||||
|
||||
|
||||
While statement
|
||||
---------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
while expr1:
|
||||
stmt1
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkWhileStmt(expr1, stmt1)
|
||||
|
||||
|
||||
For statement
|
||||
-------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
for ident1, ident2 in expr1:
|
||||
stmt1
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkForStmt(ident1, ident2, expr1, stmt1)
|
||||
|
||||
|
||||
Try statement
|
||||
-------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
try:
|
||||
stmt1
|
||||
except e1, e2:
|
||||
stmt2
|
||||
except e3:
|
||||
stmt3
|
||||
except:
|
||||
stmt4
|
||||
finally:
|
||||
stmt5
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkTryStmt(
|
||||
stmt1,
|
||||
nnkExceptBranch(e1, e2, stmt2),
|
||||
nnkExceptBranch(e3, stmt3),
|
||||
nnkExceptBranch(stmt4),
|
||||
nnkFinally(stmt5)
|
||||
)
|
||||
|
||||
|
||||
Return statement
|
||||
----------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
return expr1
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkReturnStmt(expr1)
|
||||
|
||||
|
||||
Yield statement
|
||||
---------------
|
||||
|
||||
Like ``return``, but with ``nnkYieldStmt`` kind.
|
||||
|
||||
|
||||
Discard statement
|
||||
-----------------
|
||||
|
||||
Like ``return``, but with ``nnkDiscardStmt`` kind.
|
||||
|
||||
|
||||
Continue statement
|
||||
------------------
|
||||
|
||||
Concrete syntax:
|
||||
|
||||
.. code-block:: nimrod
|
||||
continue
|
||||
|
||||
AST:
|
||||
|
||||
.. code-block:: nimrod
|
||||
nnkContinueStmt()
|
||||
|
||||
Var section
|
||||
-----------
|
||||
|
||||
To be written.
|
||||
|
||||
|
||||
Const section
|
||||
-------------
|
||||
|
||||
To be written.
|
||||
|
||||
|
||||
Type section
|
||||
------------
|
||||
|
||||
To be written.
|
||||
|
||||
|
||||
Procedure declaration
|
||||
---------------------
|
||||
|
||||
To be written.
|
||||
|
||||
|
||||
Iterator declaration
|
||||
--------------------
|
||||
|
||||
To be written.
|
||||
|
||||
|
||||
Template declaration
|
||||
--------------------
|
||||
|
||||
To be written.
|
||||
|
||||
|
||||
Macro declaration
|
||||
-----------------
|
||||
|
||||
To be written.
|
||||
|
||||
|
||||
Special node kinds
|
||||
==================
|
||||
|
||||
There are several node kinds that are used for semantic checking or code
|
||||
generation. These are accessible from this module, but should not be used.
|
||||
Other node kinds are especially designed to make AST manipulations easier.
|
||||
These are explained here.
|
||||
|
||||
To be written.
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue