aspic.ipynb 11.9 KB
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Syntax\n",
    "\n",
    "The syntax is very simple and generic:\n",
    "\n",
    "```\n",
    "    # this is a comment\n",
    "    model   ::=  stmt+\n",
    "    stmt    ::=  call | block\n",
    "    call    ::=  name args \"\\n\"\n",
    "    args    ::=  \"(\" arglist? \")\"\n",
    "    arglist ::=  arg (\",\" arg)*\n",
    "    arg     ::=  atom (\"=\" atom)?\n",
    "    block   ::=  deco? name args? \":\" \"\\n\" INDENT stmt+ DEDENT\n",
    "    deco    ::=  \"@\" name args? \"\\n\"\n",
    "    atom    ::=  name | number | code | string\n",
    "    name    ::=  /[a-z][a-z0-9_]*/i\n",
    "    number  ::=  /[+-]?[0-9]+/\n",
    "    code    ::=  /{.*}/ | /[.*]/  # simplified here (nesting is correctly handled)\n",
    "    string  ::=  /\".*\"/ | /'.*'/ | /\"\"\".*\"\"\"/a | /'''.*'''/a   # just Python strings\n",
    "```\n",
    "\n",
    "Each `block` must be one of:\n",
    " * `if` / `then` / `?else` (which means that `else` is optional)\n",
    " * `choose` / `+either` (which means that `either` may be repeated)\n",
    " * `parallel` / `+besides`\n",
    " * `race` / `+against`\n",
    " * `retry`\n",
    " * `negate`\n",
    " * `task`\n",
    "\n",
    "The correct sequencing of blocks is not enforced in the syntax but when the parse tree is built. Moreover, each block may actually have parameters nd a decorator (`@...` like in Python). It is not checked at parse time wether arguments and decorators are correctly used, instead this is let to the compiler.\n",
    "\n",
    "## Implementation\n",
    "\n",
    "Import parser and build a parse tree from souce code. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "from zinc.io.aspic import parse\n",
    "src = \"\"\"\n",
    "retry :\n",
    "    if :\n",
    "        pos()\n",
    "        comp(x=10, y=42, duration=100)\n",
    "        race :\n",
    "            traj()\n",
    "        against:\n",
    "            negate :\n",
    "                obs()\n",
    "    then :\n",
    "        pose()\n",
    "        keep()\n",
    "    else :\n",
    "        negate :\n",
    "            avoid()\n",
    "\"\"\"\n",
    "tree = parse(src)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The parse tree can be displayed in compact (`repr`) or full (`dump`) form."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "model :\n",
       " + body[0] = retry :\n",
       " |    + body[0] = if :\n",
       " |    |    + body[0] = call :\n",
       " |    |    |    + name = 'pos'\n",
       " |    |    + body[1] = call :\n",
       " |    |    |    + name = 'comp'\n",
       " |    |    |    + kargs['x'] = const :\n",
       " |    |    |    |    + value = 10\n",
       " |    |    |    |    + type = 'int'\n",
       " |    |    |    + kargs['y'] = const :\n",
       " |    |    |    |    + value = 42\n",
       " |    |    |    |    + type = 'int'\n",
       " |    |    |    + kargs['duration'] = const :\n",
       " |    |    |    |    + value = 100\n",
       " |    |    |    |    + type = 'int'\n",
       " |    |    + body[2] = race :\n",
       " |    |    |    + body[0] = call :\n",
       " |    |    |    |    + name = 'traj'\n",
       " |    |    |    + against[0] = against :\n",
       " |    |    |    |    + body[0] = negate :\n",
       " |    |    |    |    |    + body[0] = call :\n",
       " |    |    |    |    |    |    + name = 'obs'\n",
       " |    |    + else = else :\n",
       " |    |    |    + body[0] = negate :\n",
       " |    |    |    |    + body[0] = call :\n",
       " |    |    |    |    |    + name = 'avoid'\n",
       " |    |    + then = then :\n",
       " |    |    |    + body[0] = call :\n",
       " |    |    |    |    + name = 'pose'\n",
       " |    |    |    + body[1] = call :\n",
       " |    |    |    |    + name = 'keep'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tree"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "model :\n",
      " + body[0] = retry :\n",
      " |    + body[0] = if :\n",
      " |    |    + body[0] = call :\n",
      " |    |    |    + name = 'pos'\n",
      " |    |    |    + largs = []\n",
      " |    |    |    + kargs = {}\n",
      " |    |    + body[1] = call :\n",
      " |    |    |    + name = 'comp'\n",
      " |    |    |    + largs = []\n",
      " |    |    |    + kargs['x'] = const :\n",
      " |    |    |    |    + value = 10\n",
      " |    |    |    |    + type = 'int'\n",
      " |    |    |    + kargs['y'] = const :\n",
      " |    |    |    |    + value = 42\n",
      " |    |    |    |    + type = 'int'\n",
      " |    |    |    + kargs['duration'] = const :\n",
      " |    |    |    |    + value = 100\n",
      " |    |    |    |    + type = 'int'\n",
      " |    |    + body[2] = race :\n",
      " |    |    |    + body[0] = call :\n",
      " |    |    |    |    + name = 'traj'\n",
      " |    |    |    |    + largs = []\n",
      " |    |    |    |    + kargs = {}\n",
      " |    |    |    + args = None\n",
      " |    |    |    + deco = None\n",
      " |    |    |    + against[0] = against :\n",
      " |    |    |    |    + body[0] = negate :\n",
      " |    |    |    |    |    + body[0] = call :\n",
      " |    |    |    |    |    |    + name = 'obs'\n",
      " |    |    |    |    |    |    + largs = []\n",
      " |    |    |    |    |    |    + kargs = {}\n",
      " |    |    |    |    |    + args = None\n",
      " |    |    |    |    |    + deco = None\n",
      " |    |    |    |    + args = None\n",
      " |    |    |    |    + deco = None\n",
      " |    |    |    |    + against = []\n",
      " |    |    + args = None\n",
      " |    |    + deco = None\n",
      " |    |    + else = else :\n",
      " |    |    |    + body[0] = negate :\n",
      " |    |    |    |    + body[0] = call :\n",
      " |    |    |    |    |    + name = 'avoid'\n",
      " |    |    |    |    |    + largs = []\n",
      " |    |    |    |    |    + kargs = {}\n",
      " |    |    |    |    + args = None\n",
      " |    |    |    |    + deco = None\n",
      " |    |    |    + args = None\n",
      " |    |    |    + deco = None\n",
      " |    |    + then = then :\n",
      " |    |    |    + body[0] = call :\n",
      " |    |    |    |    + name = 'pose'\n",
      " |    |    |    |    + largs = []\n",
      " |    |    |    |    + kargs = {}\n",
      " |    |    |    + body[1] = call :\n",
      " |    |    |    |    + name = 'keep'\n",
      " |    |    |    |    + largs = []\n",
      " |    |    |    |    + kargs = {}\n",
      " |    |    |    + args = None\n",
      " |    |    |    + deco = None\n",
      " |    |    |    + else = None\n",
      " |    + args = None\n",
      " |    + deco = None\n"
     ]
    }
   ],
   "source": [
    "tree.dump()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `dump` above allows to see all the attributes of each node, even the empty/not used ones. For instance, each block has attributes `deco` and `args`, even if they have not been used in the parsed source.\n",
    "\n",
    "The parse tree is a nest of `node` instances, each one has the following attributes and methods:\n",
    " * `tag` the kind of the node (or the block name if this node is for a block)\n",
    " * `_fields` the names of its attributes\n",
    " * `__setitem__(self, key, val)` to add a new attribute and record it to `_fields`\n",
    " * `__getitem__(self, key)` to retrieve an attribute in a way more convenient than using `getattr`\n",
    " * `__contains__(self, key)` to check whether it has an attribute\n",
    " * `__iter__(self)` to iterate over the attributes in `_field` provided as name/value pairs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(False, True, True)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"spam\" in tree, \"body\" in tree, tree[\"body\"] is tree.body"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Compilation\n",
    "\n",
    "A very basic compiler is provided as `zinc.io.meta.Compiler`, it implements a node tranformer pattern similar to Python's standard `ast.NodeTransformer`:\n",
    " * method `visit` allows to process a node\n",
    " * it calls a method `visit_tag` if it exists, where `tag` is the node's tag\n",
    " * otherwise it calls method `generic_visit` that basically turns the node into a `dict`, recursing over its attributes\n",
    " \n",
    "To implement a compiler, one may subclass `Compiler` adding appropriate `visit_*` methods for each kind of node."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from zinc.io.meta import Compiler\n",
    "comp = Compiler()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'model': {'body': [{'retry': {'body': [{'if': {'body': [{'call': {'name': 'pos',\n",
       "           'largs': [],\n",
       "           'kargs': {}}},\n",
       "         {'call': {'name': 'comp',\n",
       "           'largs': [],\n",
       "           'kargs': {'x': {'const': {'value': 10, 'type': 'int'}},\n",
       "            'y': {'const': {'value': 42, 'type': 'int'}},\n",
       "            'duration': {'const': {'value': 100, 'type': 'int'}}}}},\n",
       "         {'race': {'body': [{'call': {'name': 'traj',\n",
       "              'largs': [],\n",
       "              'kargs': {}}}],\n",
       "           'args': None,\n",
       "           'deco': None,\n",
       "           'against': [{'against': {'body': [{'negate': {'body': [{'call': {'name': 'obs',\n",
       "                    'largs': [],\n",
       "                    'kargs': {}}}],\n",
       "                 'args': None,\n",
       "                 'deco': None}}],\n",
       "              'args': None,\n",
       "              'deco': None,\n",
       "              'against': []}}]}}],\n",
       "        'args': None,\n",
       "        'deco': None,\n",
       "        'else': {'else': {'body': [{'negate': {'body': [{'call': {'name': 'avoid',\n",
       "                'largs': [],\n",
       "                'kargs': {}}}],\n",
       "             'args': None,\n",
       "             'deco': None}}],\n",
       "          'args': None,\n",
       "          'deco': None}},\n",
       "        'then': {'then': {'body': [{'call': {'name': 'pose',\n",
       "             'largs': [],\n",
       "             'kargs': {}}},\n",
       "           {'call': {'name': 'keep', 'largs': [], 'kargs': {}}}],\n",
       "          'args': None,\n",
       "          'deco': None,\n",
       "          'else': None}}}}],\n",
       "     'args': None,\n",
       "     'deco': None}}]}}"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "comp.visit(tree)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}