Skip to content

Commit

Permalink
codemirror mode and jupyter kernel install/run
Browse files Browse the repository at this point in the history
  • Loading branch information
m-kus committed Feb 27, 2020
1 parent d57586f commit d3c9cdd
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 104 deletions.
File renamed without changes.
78 changes: 78 additions & 0 deletions assets/kernel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
define([
'codemirror/lib/codemirror',
'codemirror/addon/mode/simple'
], function(CodeMirror) {
var onload = function() {
CodeMirror.defineSimpleMode("michelson", {
start: [
// delimiters
{ regex: /[;\{\(]/, token: "variable", next: "start" },
// string
{ regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: "string" },
// bytes
{ regex: /(?<=\s|^)(?:0x[0-9a-f]+)(?=\s|;|\}|$)/i, token: "string" },
// int
{ regex: /(?<=\s|^)(?:[+-]?[0-9]+\.?[0-9]*)(?=\s|;|\}|$)/, token: "string" },
// comment
{ regex: /#.*/, token: "comment" },
{ regex: /\/\*/, token: "comment", next: "comment" },
// block
{ regex: /(?<=\s|^)(?:parameter|storage|code)(?=\s|$)/, token: "keyword" },
// data
{ regex: /(?<=\s|^)(?:Unit|True|False|Pair|Left|Right|Some|None|Elt)(?=\s|;|\)|$)/, token: "keyword" },
// instruction
{ regex: /(?<=\s|^)(?:CAST|RENAME|DROP|DUP|SWAP|PUSH|SOME|NONE|UNIT|IF_NONE|PAIR|CAR|CDR|LEFT|RIGHT|IF_LEFT|IF_RIGHT|NIL|CONS|IF_CONS|SIZE|EMPTY_SET|EMPTY_MAP|MAP|ITER|MEM|GET|UPDATE|IF|LOOP|LOOP_LEFT|LAMBDA|EXEC|DIP|FAILWITH|CONCAT|SLICE|PACK|UNPACK|ADD|SUB|MUL|EDIV|ABS|NEG|LSL|LSR|OR|AND|XOR|NOT|COMPARE|EQ|NEQ|LT|GT|LE|GE|CHECK_SIGNATURE|BLAKE2B|SHA256|SHA512|HASH_KEY|DIG|DUG|EMPTY_BIG_MAP|APPLY)(?=\s|;|\}|$)/, token: "meta"},
{ regex: /(?<=\s|^)(?:SELF|CONTRACT|TRANSFER_TOKENS|SET_DELEGATE|CREATE_CONTRACT|IMPLICIT_ACCOUNT|NOW|AMOUNT|BALANCE|STEPS_TO_QUOTA|SOURCE|SENDER|ADDRESS|CHAIN_ID)(?=\s|;|\}|$)/, token: "operator"},
// type
{ regex: /(?<=\s|^)(?:option|list|set|contract|pair|or|lambda|map|big_map)(?=\s|\)|$)/, token: "builtin" },
{ regex: /(?<=\s|^)(?:key|unit|signature|operation|address|int|nat|string|bytes|mutez|bool|key_hash|timestamp|chain_id)(?=\s|\)|\}|;|$)/, token: "builtin" },
// macros
{ regex: /(?<=\s|^)(?:IF_SOME|FAIL|ASSERT|ASSERT_NONE|ASSERT_SOME|ASSERT_LEFT|ASSERT_RIGHT|UNPAIR|(?:SET|MAP)_C[AD]+R)(?=\s|;|\}|$)/, token: "string-2" },
{ regex: /(?<=\s|^)(?:DII+P|C[AD]{2,}R|DUU+P|P[PAI]{3,}R|UNP[PAI]{3,}R)(?=\s|;|\}|$)/, token: "string-2" },
{ regex: /(?<=\s|^)(?:(?:CMP|IF|IFCMP|ASSERT_|ASSERT_CMP)(?:EQ|NEQ|LT|GT|LE|GE))(?=\s|;|\}|\{|$)/, token: "string-2" },
// annotations
{ regex: /(?<=\s|^)(?:%[A-z_0-9%@]*)/, token: "atom" },
{ regex: /(?<=\s|^)(?:@[A-z_0-9%]+)(?=\s|$)/, token: "atom" },
{ regex: /(?<=\s|^)(?::[A-z_0-9]+)(?=\s|$)/, token: "atom" },
// helpers
{ regex: /(?<=\s|;|\{|^)(?:PUT|TOP)/, token: "def" },
// fallback
{ regex: /[^\s]+/, token: "variable"}
],
comment: [
{ regex: /.*?\*\//, token: "comment", next: "start" },
{ regex: /.*/, token: "comment" }
],
meta: {
dontIndentStates: ["comment"],
lineComment: "#",
blockCommentStart: "/*",
blockCommentEnd: "*/"
}
});
CodeMirror.defineMIME("text/x-michelson", "michelson");
CodeMirror.modeInfo.push({
ext: ["tz"],
mime: "text/x-michelson",
mode: "michelson",
name: "Michelson"
});

// Force mode on refresh
// Big thanks to https://github.com/kelvich for this solution
IPython.CodeCell.options_default["cm_config"]["mode"] = "michelson";
[...document.querySelectorAll('.code_cell .CodeMirror')].forEach(c => {
c.CodeMirror.setOption('mode', 'michelson');
});
Jupyter.notebook.get_cells().forEach(function(c) {
if (c.cell_type == "code") {
c._options.cm_config['mode'] = 'michelson';
}
else if (c.cell_type == "markdown") {
c.unrender();
c.render();
}
});
}
return { onload: onload }
});
File renamed without changes.
40 changes: 39 additions & 1 deletion pytezos/cli.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import fire
from os.path import abspath
import json
import os
import shutil
from os.path import abspath, dirname, join
from glob import glob
from pprint import pprint
from ipykernel.kernelapp import IPKernelApp
from jupyter_client.kernelspec import KernelSpecManager
from tempfile import TemporaryDirectory

from pytezos import pytezos, Contract, RpcError
from pytezos.operation.result import OperationResult
from pytezos.michelson.docstring import generate_docstring
from pytezos.tools.github import create_deployment, create_deployment_status
from pytezos.repl.kernel import MichelsonKernel

kernel_js_path = join(dirname(dirname(__file__)), 'assets', 'kernel.js')
kernel_json = {
"argv": ['pytezos', 'kernel', 'run', "-file", "{connection_file}"],
"display_name": "Michelson",
"language": "michelson",
"codemirror_mode": "michelson"
}


def make_bcd_link(network, address):
Expand Down Expand Up @@ -39,6 +54,29 @@ def get_contract(path):

class PyTezosCli:

def kernel(self, action, file=None):
"""
Manage Jupyter kernel for the Michelson language
:param action: One of `install`, `remove`, `run`
:param file: connection settings (for running a kernel)
"""
if action == 'install':
kernel_spec = KernelSpecManager()
with TemporaryDirectory() as td:
os.chmod(td, 0o755)
shutil.copy(kernel_js_path, join(td, 'kernel.js'))
with open(join(td, 'kernel.json'), 'w') as f:
json.dump(kernel_json, f, sort_keys=True)
kernel_spec.install_kernel_spec(td, 'michelson', user=os.environ['USER'])
elif action == 'remove':
kernel_spec = KernelSpecManager()
kernel_spec.remove_kernel_spec('michelson')
elif action == 'run':
argv = ['-f', file] if file else None
return IPKernelApp.launch_instance(kernel_class=MichelsonKernel, argv=argv)
else:
assert False, action

def storage(self, action, path=None):
"""
:param action: One of `schema`, `default`
Expand Down
7 changes: 6 additions & 1 deletion pytezos/michelson/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,15 @@ def encode(self, data):
"""
if isinstance(data, dict) and len(data) == 1:
entrypoint = next(iter(data))
value = encode_micheline(data[entrypoint], self.schema, root=self._get_entry_root(entrypoint))
if not any(map(lambda x: x.get('fieldname') == entrypoint, self.schema.metadata.values())):
entrypoint = 'default' # prevent auto-generated entrypoint names, like `rrrllll`
else:
entrypoint = 'default'

if entrypoint == 'default':
value = encode_micheline(data, self.schema)
else:
value = encode_micheline(data[entrypoint], self.schema, root=self._get_entry_root(entrypoint))

return dict(entrypoint=entrypoint, value=value)

Expand Down
3 changes: 2 additions & 1 deletion pytezos/michelson/formatter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from datetime import datetime
from pprint import pformat

line_size = 100

Expand Down Expand Up @@ -113,4 +114,4 @@ def micheline_to_michelson(data, inline=False):
try:
return format_node(data, inline=inline, is_root=True)
except (KeyError, IndexError, TypeError):
raise MichelsonFormatterError('Failed to format Micheline expression')
raise MichelsonFormatterError(f'Failed to format expression: {pformat(data, compact=True)}')
23 changes: 16 additions & 7 deletions pytezos/michelson/grammar.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Inspired by https://github.com/jansorg/tezos-intellij/blob/master/grammar/michelson.bnf

from pprint import pformat
from ply.lex import Lexer, lex
from ply.yacc import yacc
import re
Expand All @@ -9,7 +9,13 @@


class MichelsonParserError(ValueError):
pass

def __init__(self, err_node, message=None):
message = message or f'failed to parse expression {pformat(err_node, compact=True)}'
super(MichelsonParserError, self).__init__(message)
self.message = message
self.line = err_node.lineno(0)
self.pos = err_node.lexpos(0)


class Sequence(list):
Expand Down Expand Up @@ -74,11 +80,14 @@ def p_instr_subseq(self, p):

def p_expr(self, p):
'''expr : PRIM annots args'''
expr = expand_macro(
prim=p[1],
annots=p[2] or [],
args=p[3] or []
)
try:
expr = expand_macro(
prim=p[1],
annots=p[2] or [],
args=p[3] or []
)
except AssertionError as e:
raise MichelsonParserError(p, str(e))
p[0] = Sequence(expr) if isinstance(expr, list) else expr

def p_annots(self, p):
Expand Down
2 changes: 1 addition & 1 deletion pytezos/michelson/macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def expand_macro(prim, annots, args, internal=False):
res = handler(groups[0], annots, args)
return res if internal else seq(res)

assert False, f'Unknown macro: {prim}'
assert False, f'unknown primitive {prim}'


def get_field_annots(annots):
Expand Down
24 changes: 13 additions & 11 deletions pytezos/repl/control.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import functools
from copy import deepcopy
from pprint import pformat

from pytezos.repl.stack import Stack
from pytezos.repl.types import StackItem, assert_stack_type, assert_expr_equal, Option, Lambda, Bool, List, Or, Pair, \
Expand All @@ -10,7 +11,7 @@


def assert_no_annots(prim, annots):
assert not annots, f'{prim}: unexpected annotations {annots}'
assert not annots, f'unexpected annotations {annots}'


def instruction(prim, args_len=0):
Expand All @@ -30,12 +31,12 @@ def wrapper(*args, **kwargs):

def parse_instruction(code_expr):
prim = code_expr.get('prim')
if prim not in instructions:
raise MichelsonRuntimeError.init('unknown instruction', prim) from None
if prim not in instructions:
raise MichelsonRuntimeError.init('unknown instruction', prim)
args_len, handler = instructions[prim]
args = code_expr.get('args', [])
if len(args) != args_len:
raise MichelsonRuntimeError.init(f'expected {args_len} arg(s), got {len(args)}', prim) from None
raise MichelsonRuntimeError.init(f'expected {args_len} arg(s), got {len(args)}', prim)
annots = code_expr.get('annots', [])
return prim, args, annots, handler

Expand All @@ -49,13 +50,13 @@ def do_interpret(stack: Stack, code_expr):
try:
res = handler(stack, prim, args, annots)
except AssertionError as e:
raise MichelsonRuntimeError.init(e.args, prim) from None
raise MichelsonRuntimeError.init(str(e), prim)
except MichelsonRuntimeError as e:
raise MichelsonRuntimeError.wrap(e, prim) from None
raise MichelsonRuntimeError.wrap(e, prim)
else:
return res
else:
assert False, f'unexpected code expression: {code_expr}'
assert False, f'unexpected code expression {pformat(code_expr, compact=True)}'


@instruction('PUSH', args_len=2)
Expand Down Expand Up @@ -124,7 +125,7 @@ def do_exec(stack: Stack, prim, args, annots):
do_interpret(lmbda_stack, lmbda.code)
ret = lmbda_stack.pop()
lmbda.assert_ret_type(ret)
assert len(lmbda_stack) == 0, f'lambda stack is not empty: {lmbda_stack}'
assert len(lmbda_stack) == 0, f'lambda stack is not empty {lmbda_stack}'
return stack.ins(ret, annots=annots)


Expand All @@ -139,7 +140,7 @@ def do_apply(stack: Stack, prim, args, annots):
def do_failwith(stack: Stack, prim, args, annots):
assert_no_annots(prim, annots)
top = stack.pop()
raise ValueError(top)
assert False, repr(top)


@instruction('IF', args_len=2)
Expand Down Expand Up @@ -249,15 +250,16 @@ def do_map(stack: Stack, prim, args, annots):
stack.ins(Pair.new(key, val))
do_interpret(stack, args[0])
else:
assert False, f'Unexpected type: {type(container)}'
assert False, f'unexpected type {type(container)}'


@instruction('CAST', args_len=1)
def do_cast(stack: Stack, prim, args, annots):
assert_no_annots(prim, annots)
top = stack.pop()
assert_expr_equal(args[0], top.type_expr)
top.type_expr = args[0]
return stack.ins(top, annots=annots)
return stack.ins(top)


@instruction('RENAME')
Expand Down
Loading

0 comments on commit d3c9cdd

Please sign in to comment.