gen_actor_source.py 127 KB
Newer Older
1
#!/usr/bin/python
2
# coding=utf-8
3

4 5
"""
Generates and keeps up-to-date actor skeleton sources.
6 7

Usage:
8
    gen_actor_source.py (--project|--update) DECLARATION.json
9

10 11
    --project       Run in generate project mode
    --update        Run in update files mode (used from Makefiles)
12 13

Example (create new actor skeleton):
14 15 16 17 18
    cd kumir2/src/actors
    mkdir mygreatactor
    cd mygreatactor
    { ... create here file mygreatactor.json ...}
    ../../../scripts/gen_actor_source.py --project mygreatactor.json
19 20

This will create the following files:
21 22 23 24
    MyGreatActor.pluginspec     -- actor plugin spec
    CMakeLists.txt              -- CMake project file
    mygreatactormodule.h        -- header skeleton
    mygreatactormodule.cpp      -- source skeleton
25

26
===
27

28
                        0. INTRODUCTION
29

30 31 32
The module operates in two ways:
1. Update project files. Usually called from CMakeLists.txt within build process.
2. Create new project. Creates project file (CMakeLists.txt) and module skeleton.
33

34

35
                        1. CODE LAYOUT
36

37 38
Here are two group of classes. The first one covers module description tree, while
the second one covers generated files implementation.
39 40


41
1.1. Module description class group
42

43 44 45 46
The module description input data represented as JSON-file, handled by Python as
a dictionary object. Parsing this dictionary, the following structure produces
(here '+' significates 'one or more', '*' is 'zero or none', '?' is 'zero or one',
and '|' is 'or'):
Victor Yacovlev's avatar
Victor Yacovlev committed
47

48 49 50 51 52 53
    class Module (a top level class of tree) :=
        name: class Name
        methods: (class Method)+
        types: (class BaseType)*
        gui: (class Gui)?
        settings: (class Settings)?
Victor Yacovlev's avatar
Victor Yacovlev committed
54

55
    class Name: a Kumir/C++ name representation
56

57 58 59 60 61
    class Method :=
        name: class Name
        returnType: class BaseType | None
        async: bool
        arguments: (class Argument)*
62

63 64 65 66
    class BaseType :=
        name: class Name
        standard: bool
        fields: ( <class Name, class BaseType> )*
67

68 69
    class Argument :=
        name: class Name
70
        base_type: class BaseType
71 72 73 74
        dimension: [0, 1, 2, 3]
        constatnt: bool
        reference: bool
        readable: bool
75

76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
    class Gui :=
        windows: (class Window)*
        menus: (class MenuItem)*

    class Window :=
        icon: string
        role: string

    class MenuItem :=
        title: class Name
        icon: string | None
        items: (class MenuItem)*

    class Settings :=
        entries: (class SettingsEntry)*

    class SettingsEntry :=
        key: string
        title: class Name
        type: string
        default: string
        minimum: string | None
        maximum: string | None


1.2. Module implementation class group

After the JSON file is read and corresponding module tree is built, the next
stage is to generate source files.

There are four C++ modules generated by the module description:


1.2.1. Module Qt-Plugin class (here is class PluginCppClass)

The Kumir's extension system module, implements Actor interface. Provides
actor information to Kumir. This class code is auto generated and not ought to
be modified.


1.2.2. Module implementation base class (here is ModuleBaseCppClass)

A class to be inherited by implementation. The class contains mostly pure virtual
methods according to actor algorithms headers (in C++ syntax). This class
is auto generated and not ought to be modified.

1.2.3. Module implementation skeleton (here is ModuleCppClass)

A skeleton files to be implemented by actor functionality. The class inherits
auto-generated base to match up-to-date actor description. The class generates once and
must be implemented by actor developer.

1.2.4. Async run thread class (here is AsyncThreadCppClass)

A supplementary class, which declared and implemented near plugin class in the same files.
Some actor methods should be 'Asynchronous', what means that they run in separate thread.
This class provides transparent and convient layout to developer not to implement this
feature hisself.


                        2. SCRIPT WORKFLOW

The first, script reads provided JSON file and creates an actor module description tree
(class Module instance).

Then, there are two modes of operation: generate an update set of files, or generate a
project set of files.


2.1. Update set of files

In this mode the following files generated:
    - actor plugin header (function createPluginHeaderFile);
    - actor plugin source (function createPluginSourceFile);
    - actor implementation base header (function createModuleBaseHeaderFile);
    - actor implementation base source (function createModuleBaseSourceFile);
    - actor plugin specification (function createPluginSpecFile).

2.2. Project set of files

In this mode the following files generated:
    - actor implementation header (function createModuleHeaderFile);
    - actor implementation source (function createModuleSourceFile);
    - actor project file (function createCMakeListsTxt);
    - actor documentation file (function createDocbookFile).

"""
import copy
import sys
import json
import string
import os
import inspect

170

171 172 173 174 175 176 177 178
def string_join(lines, sep):
    result = ""
    for index, line in enumerate(lines):
        if index > 0:
            result += sep
        result += line
    return result

179

180 181 182 183 184
# Hacks for Python 2/3 compatibility
if sys.version_info.major >= 3:
    unicode = str
    string.join = string_join

185 186 187 188 189
MODULE_CLASS_SUFFIX = "Module"
MODULE_BASE_CLASS_SUFFIX = "ModuleBase"
MODULE_RUN_THREAD_SUFFIX = "AsyncRunThread"
MODULE_PLUGIN_CLASS_SUFFIX = "Plugin"
MODULE_NAMESPACE_PREFIX = "Actor"
190 191 192 193 194 195 196 197 198
SETTINGS_TYPES = {
    "int": "Integer",
    "double": "Double",
    "string": "String",
    "char": "Char",
    "bool": "Bool",
    "color": "Color",
    "font": "Font"
}
199 200


201
def _render_template(template, values):
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
    """
    Renders simple template using $-substitutions

    :param template:    template to render
    :type template:     str
    :type values:       dict
    :param values:      values to substitute
    :rtype:             unicode
    :return:            a string with replaced substitutions
    """
    assert isinstance(template, str) or isinstance(template, unicode)
    assert isinstance(values, dict)
    for key, value in values.items():
        template = template.replace("$" + key, value)
    return template


219
def _add_indent(text):
220 221 222 223 224
    """
    Adds a one indent (4 spaces) to each line of text

    :type text:     unicode or str
    :param text:    text to indent
225
    :rtype:         unicode or str
226 227 228 229
    :return:        4-space indented text
    """
    assert isinstance(text, unicode) or isinstance(text, str)
    lines = text.split('\n')
230
    indented_lines = map(lambda x: "    " + x, lines)
231
    # noinspection PyUnresolvedReferences
232
    return string.join(indented_lines, '\n')
233

234

235
# The group of module description tree classes
236

237 238 239 240
class Name:
    """
    A class representing localizable name.
    """
241

242
    def __init__(self, json_node):
243 244 245
        """
        Initializes from JSON value
        """
246 247 248 249
        assert isinstance(json_node, dict) or isinstance(json_node, str) or isinstance(json_node, unicode)
        if isinstance(json_node, dict):
            assert "ascii" in json_node
            self.data = json_node
250
        else:
251
            self.data = dict()
252
            self.data["ascii"] = unicode(json_node)
253

254
    def get_kumir_value(self):
255 256 257 258 259 260 261 262 263 264 265
        """
        Returns an Unicode representation for Russian language or ASCII

        :rtype:     unicode
        :return:    name as is
        """
        if "ru_RU" in self.data:
            return unicode(self.data["ru_RU"])
        else:
            return unicode(self.data["ascii"])

266
    def get_ascii_value(self):
267 268 269 270 271 272 273 274
        """
        Returns an ASCII representation

        :rtype:     str
        :return:    ASCII name as is
        """
        return str(self.data["ascii"])

275
    def get_cpp_value(self):
276 277 278 279 280 281 282
        """
        Returns a valid C++ name based on source name

        :rtype:     str
        :return:    valid C++ identifier
        """
        result = ""
283
        next_is_capital = False
284 285
        ascii_name = self.data["ascii"]
        if ascii_name == "+":
286
            return "OperatorPLUS"
287
        elif ascii_name == "-":
288
            return "OperatorMINUS"
289
        elif ascii_name == "*":
290
            return "OperatorASTERISK"
291
        elif ascii_name == "**":
292
            return "OperatorPOWER"
293
        elif ascii_name == "/":
294
            return "OperatorSLASH"
295
        elif ascii_name == "=":
296
            return "OperatorEQUAL"
297
        elif ascii_name == "<>":
298
            return "OperatorNOTEQUAL"
299
        elif ascii_name == "<":
300
            return "OperatorLESS"
301
        elif ascii_name == ">":
302
            return "OperatorGREATER"
303
        elif ascii_name.startswith(":="):
304
            return "OperatorASSIGN"
305
        elif ascii_name == "input":
306
            return "OperatorINPUT"
307
        elif ascii_name == "output":
308
            return "OperatorOUTPUT"
309
        for c in ascii_name:
310
            if c == ' ':
311
                next_is_capital = True
312 313
            elif c in "\\%":
                break
314
            else:
315
                if next_is_capital:
316 317 318
                    result += c.upper()
                elif c.upper() in "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_":
                    result += c
319
                next_is_capital = False
320 321 322
        result = result.replace("#", "").replace("?", "").replace("!", "")
        return result

323
    def get_camel_case_cpp_value(self):
324 325 326 327 328 329
        """
        Returns a valid CamelCase C++ name based on source name

        :rtype:     str
        :return:    valid C++ identifier
        """
330
        cpp = self.get_cpp_value()
331 332 333 334 335 336 337 338 339
        return cpp[0].upper() + cpp[1:]


class BaseType:
    """
    Scalar data type
    """
    _typeTable = dict()  # a table of actor's custom types

340
    def __init__(self, module, json_node):
341 342 343
        """
        Initializes from  JSON value or uses existing type

344
        :type   module:     Module or None
345
        :param  module:     module, where type declared
346 347
        :type   json_node:   dict, str, unicode
        :param  json_node:   JSON node for custom type or name for standard type
348
        """
349
        assert isinstance(json_node, dict) or isinstance(json_node, str) or isinstance(json_node, unicode)
350
        self._module = None
351 352 353 354 355
        self._standard = None
        self._name = None
        self._fields = None
        if isinstance(json_node, dict):
            self.__init__from_dict(json_node)
356
        else:
357
            self.__init__from_string(str(json_node))
358 359
        if self._module is None:
            self._module = module
360

361
    def __init__from_dict(self, json_node):
362 363 364
        """
        Initializes a custom type from detailed specification

365 366
        :type   json_node:   dict
        :param  json_node:   JSON node for custom type details
367
        """
368 369
        assert isinstance(json_node, dict)
        assert "name" in json_node and "fields" in json_node
370
        self._standard = False
371
        self._name = Name(json_node["name"])
372
        self._fields = []
373 374 375
        node_fields = json_node["fields"]
        assert isinstance(node_fields, list)
        for field in node_fields:
376 377
            assert isinstance(field, dict)
            assert "name" in field and "baseType" in field
378 379 380
            field_name = Name(field["name"])
            field_type = BaseType(None, field["baseType"])
            pair = field_name, field_type
381
            self._fields.append(pair)
382
        BaseType._typeTable[self._name.get_ascii_value()] = self
383 384 385 386 387 388 389 390 391 392

    def __init__from_string(self, name):
        """
        Initializes a standard type by its name

        :type   name:   str
        :param  name:   a value in ["int", "double", "string", "bool", "char"]
        """
        assert isinstance(name, str) or isinstance(name, unicode)
        if name in BaseType._typeTable:
393 394 395 396 397 398
            existing_type = BaseType._typeTable[name]
            assert isinstance(existing_type, BaseType)
            self._name = copy.deepcopy(existing_type.get_name())
            self._fields = copy.deepcopy(existing_type.get_fields())
            self._standard = existing_type.is_standard_type()
            self._module = existing_type.get_module()
399 400 401
        else:
            self._standard = True
            self._fields = []
402 403
            qualified_name = dict()
            qualified_name["ascii"] = name
404
            if name == "int":
405
                qualified_name["ru_RU"] = u"цел"
406
            elif name == "double":
407
                qualified_name["ru_RU"] = u"вещ"
408
            elif name == "bool":
409
                qualified_name["ru_RU"] = u"лог"
410
            elif name == "char":
411
                qualified_name["ru_RU"] = u"сим"
412
            elif name == "string":
413 414 415 416 417 418 419 420 421
                qualified_name["ru_RU"] = u"лит"
            self._name = Name(qualified_name)
            BaseType._typeTable[self._name.get_ascii_value()] = self

    def get_name(self):
        return self._name

    def get_fields(self):
        return self._fields
422

423 424 425 426
    def get_module(self):
        return self._module

    def is_standard_type(self):
427 428 429 430 431 432 433 434
        """
        Is a standard Qt base type

        :rtype:     bool
        :return:    True, if int, double, bool, char or string
        """
        return self._standard

435
    def get_kumir_name(self):
436 437 438 439 440 441
        """
        Kumir name for the type

        :rtype:     unicode
        :return:    russian type name
        """
442 443 444 445
        return self._name.get_kumir_value()

    def get_ascii_name(self):
        return self._name.get_ascii_value()
446

447
    def get_qt_name(self):
448 449 450 451 452 453 454
        """
        Qt-style type name

        :rtype:     str
        :return:    ASCII name matched to Qt/C++ type
        """
        if not self._standard:
455 456
            return self._name.get_camel_case_cpp_value()
        elif self._name.get_ascii_value() == "string":
457
            return "QString"
458
        elif self._name.get_ascii_value() == "char":
459
            return "QChar"
460
        elif self._name.get_ascii_value() == "double":
461 462
            return "qreal"
        else:
463
            return self._name.get_ascii_value()
464

465
    def get_cpp_declaration(self):
466 467
        """
        For non-standard types creates type declaration or an empty string
468

469 470 471 472 473 474
        :rtype:     str
        :return:    an empty string for standard type or a struct declaraion otherwise
        """
        if self._standard:
            return ""
        else:
475 476
            name_decl = self.get_qt_name()
            fields_decl = ""
477
            # noinspection PyTypeChecker
478
            for name, base_type in self._fields:
479
                assert isinstance(name, Name)
480 481 482 483 484 485
                assert isinstance(base_type, BaseType)
                fields_decl += "    "
                fields_decl += base_type.get_qt_name() + " "
                fields_decl += name.get_cpp_value() + ";"
                fields_decl += "\n"
            return _render_template("""
486 487 488
struct $name {
$fields
};
489
            """, {"name": name_decl, "fields": fields_decl})
490

491
    def get_cpp_custom_type_creation(self, variable_to_append, variable_to_assign):
492 493 494
        """
        For non-standard types creates kumir type declaration, an empty string otherwise

495 496
        :type variable_to_append:    str
        :para variable_to_append:    C++ variable name (std::list or QList) to store result
497 498 499 500 501 502
        :rtype:     unicode
        :return:    an empty string for standard type or Shared::ActorInterface::CustomType implementation code
        """
        if self._standard:
            return ""
        else:
503 504 505
            assert variable_to_append is None or isinstance(variable_to_append, str)
            assert variable_to_assign is None or isinstance(variable_to_assign, str)
            assert variable_to_assign or variable_to_append
506 507 508
            result = "{\n"
            result += "    Shared::ActorInterface::CustomType custom;\n"
            result += "    Shared::ActorInterface::Record record;\n"
509
            # noinspection PyTypeChecker
510 511 512
            for fieldName, fieldType in self._fields:
                assert isinstance(fieldName, Name)
                assert isinstance(fieldType, BaseType)
513 514 515
                assert fieldType.get_qt_name() in ["int", "qreal", "bool", "QChar", "QString"]
                name = fieldName.get_kumir_value()
                typee = fieldType.get_qt_name()
516 517 518
                if typee[0].lower() == 'q':
                    typee = typee[1:]
                typee = typee[0].upper() + typee[1:]
519
                result += "    record.push_back(Field(QByteArray(\"%s\"), %s));\n" % (name, typee)
520 521 522 523 524 525 526
            type_name = self._name.get_kumir_value()
            result += \
                "    custom = Shared::ActorInterface::CustomType(QString::fromUtf8(\"%s\"), record);\n" % type_name
            if variable_to_append:
                result += "    %s.push_back(custom);\n" % variable_to_append
            elif variable_to_assign:
                result += "    %s = custom;\n" % variable_to_assign
527 528 529
            result += "}\n"
            return result

530
    def get_cpp_record_spec_creation(self, variable_to_append, variable_to_assign):
531 532 533
        """
        For non-standard types creates kumir type declaration, an empty string otherwise

534
        :type variable_to_append:    str or None
535 536 537
        :param variable_to_append:   C++ variable name (std::list or QList) to store result
        :type variable_to_assign:    str or None
        :param variable_to_assign:   C++ variable name to assign
538 539 540 541 542 543
        :rtype:     unicode
        :return:    an empty string for standard type or Shared::ActorInterface::CustomType implementation code
        """
        if self._standard:
            return ""
        else:
544 545 546
            assert variable_to_append is None or isinstance(variable_to_append, str)
            assert variable_to_assign is None or isinstance(variable_to_assign, str)
            assert variable_to_assign or variable_to_append
547 548
            result = "{\n"
            result += "    Shared::ActorInterface::RecordSpecification recordSpec;\n"
549
            # noinspection PyTypeChecker
550 551 552 553 554 555
            for field_name, field_type in self._fields:
                assert isinstance(field_name, Name)
                assert isinstance(field_type, BaseType)
                assert field_type.get_qt_name() in ["int", "qreal", "bool", "QChar", "QString"]
                name = field_name.get_kumir_value()
                typee = field_type.get_qt_name()
556 557
                if typee[0].lower() == 'q':
                    typee = typee[1:]
Victor Yacovlev's avatar
Victor Yacovlev committed
558
                typee = "Shared::ActorInterface::" + typee[0].upper() + typee[1:]
559 560
                result += "    recordSpec.record.push_back(Shared::ActorInterface::Field(QByteArray(\"%s\"), %s));\n" \
                          % (name, typee)
561
            result += "    recordSpec.asciiName = QByteArray(\"%s\");\n" % self._name.get_ascii_value()
562 563 564 565 566 567
            for key, value in self._name.data.items():
                qlocale = None
                if key == "ru_RU":
                    qlocale = "QLocale::Russian"
                if qlocale:
                    result += "     recordSpec.localizedNames[%s] = QString::fromUtf8(\"%s\");\n" % (qlocale, value)
568 569 570 571
            if variable_to_append:
                result += "    %s.push_back(recordSpec);\n" % variable_to_append
            elif variable_to_assign:
                result += "    %s = recordSpec;\n" % variable_to_assign
572 573 574
            result += "}\n"
            return result

575
    def get_cpp_custom_type_encode_decode(self):
576 577 578 579 580 581 582 583 584
        """
        Generates encode/decode to/from QVariant inline functions for custom type methods

        :rtype:     str
        :return:    functions C++ inline body for custom type or empty string for standard string
        """
        if self._standard:
            return ""
        else:
585 586
            body_encode = ""
            body_decode = ""
587
            # noinspection PyTypeChecker
588 589 590 591 592
            for index, (field_name, field_type) in enumerate(self._fields):
                assert isinstance(field_name, Name)
                assert isinstance(field_type, BaseType)
                assert field_type.get_qt_name() in ["int", "qreal", "bool", "QChar", "QString"]
                typee = field_type.get_qt_name()
593 594 595 596 597 598 599 600
                defvalue = ""
                if typee[0] == 'Q':
                    conversion = "to" + typee[1].upper() + typee[2:] + "()"
                    defvalue = typee + "()"
                elif typee == "qreal":
                    conversion = "toDouble()"
                    defvalue = "0.0"
                else:
601
                    conversion = "to" + typee[0].upper() + typee[1:] + "()"
602 603 604 605 606
                if not defvalue:
                    if typee == "int":
                        defvalue = "0"
                    elif typee == "bool":
                        defvalue = "false"
607 608 609
                field = field_name.get_cpp_value()
                body_encode += "    result << QVariant(record.%s);\n" % field
                body_decode += "    result.%s = alist.size() > %i ? alist.at(%i).%s : %s;\n" % (
610 611 612
                    field, index, index, conversion, defvalue
                )
            substitutions = {
613 614 615
                "typeName": self.get_qt_name(),
                "bodyEncode": body_encode,
                "bodyDecode": body_decode
616
            }
617
            return _render_template("""
618
QVariant encode(const $typeName & record) {
619 620 621
    QVariantList result;
$bodyEncode
    return result;
622 623
}

624
$typeName decode(const QVariant & raw) {
625 626 627 628
    $typeName result;
    const QVariantList alist = raw.toList();
$bodyDecode
    return result;
629
}
630 631
            """, substitutions)

632
    def get_typedef(self):
633 634 635
        """Creates  typedef in case of external namespace"""
        if self._module:
            return "typedef %s::%s %s;" % (
636 637
                self._module.get_module_cpp_namespace(),
                self.get_qt_name(), self.get_qt_name()
638 639 640 641
            )
        else:
            return ""

642 643 644 645 646 647

class Argument:
    """
    An actor method argument
    """

648
    def __init__(self, json_node):
649 650 651
        """
        Initializes from JSON node value

652 653
        :type   json_node:   dict
        :param  json_node:   an argument specification
654
        """
655 656 657
        assert isinstance(json_node, dict)
        self.name = Name(json_node["name"])
        self.base_type = BaseType(None, json_node["baseType"])
658
        self.dimension = 0
659 660
        if "dim" in json_node:
            self.dimension = int(json_node["dim"])
661
        assert 0 <= self.dimension <= 3
662 663 664 665
        if "access" in json_node:
            self.constant = json_node["access"] in ["in"]
            self.reference = json_node["access"] in ["out", "in/out"]
            self.readable = json_node["access"] in ["in", "in/out"]
666 667 668 669 670
        else:
            self.constant = True
            self.reference = False
            self.readable = True

671
    def _get_vector_type(self):
672 673 674 675 676 677
        """
        Creates Qt-vector type declaration

        :rtype:     str
        :return:    C++ argument declaration
        """
678
        base_type = self.base_type.get_qt_name()
679
        dimension = self.dimension
680
        assert isinstance(base_type, str)
681 682
        assert isinstance(dimension, int)
        assert 0 < dimension <= 3
683
        result = "QVector< " * dimension + base_type + " >" * dimension
684 685
        return result

686
    def get_cpp_argument_declaration(self):
687 688 689 690 691 692 693 694 695 696
        """
        Creates C++ function argument declaration

        :rtype:     str
        :return:    C++ argument declaration
        """
        result = ""
        if self.constant:
            result += "const "
        if self.dimension > 0:
697
            result += self._get_vector_type()
698
        else:
699 700
            result += self.base_type.get_qt_name()
        if self.dimension > 0 or not self.base_type.get_qt_name() in ["int", "qreal", "bool"] or self.reference:
701
            result += "&"
702
        result += " " + self.name.get_cpp_value()
703 704
        return result

705
    def get_cpp_local_variable_declaration(self):
706 707 708 709 710 711 712 713
        """
        Creates C++ local variable declaration

        :rtype:     str
        :return:    C++ plain variable declaration
        """
        result = ""
        if self.dimension > 0:
714
            result += self._get_vector_type()
715
        else:
716 717
            result += self.base_type.get_qt_name()
        result += " " + self.name.get_cpp_value()
718 719
        return result

720
    def get_kumir_argument_declaration(self):
721 722 723 724 725 726 727 728 729 730 731 732 733
        """
        Creates Kumir argument declaration

        :rtype:     unicode
        :return:    Kumir argument declaraion
        """
        result = ""
        if self.constant and not self.readable:
            result += u"арг "
        elif self.readable and self.reference:
            result += u"аргрез "
        elif self.reference:
            result += u"рез "
734
        result += self.base_type.get_kumir_name()
735 736
        if self.dimension > 0:
            result += u"таб"
737
        result += " " + self.name.get_kumir_value()
738 739 740 741 742 743 744 745 746 747
        if self.dimension > 0:
            result += "[" + "0:0," * (self.dimension - 1) + "0:0]"
        return result


class Method:
    """
    An actor static method
    """

748 749 750 751 752
    def __init__(self, json_node):
        assert isinstance(json_node, dict)
        self.name = Name(json_node["name"])
        if "returnType" in json_node:
            self.return_type = BaseType(None, json_node["returnType"])
753
        else:
754 755 756
            self.return_type = None
        if "async" in json_node:
            self.async = bool(json_node["async"])
757
        else:
758
            self.async = self.return_type is None
759
        self.arguments = []
760 761
        if "arguments" in json_node:
            for arg in json_node["arguments"]:
762 763 764 765
                assert isinstance(arg, dict)
                argument = Argument(arg)
                self.arguments.append(argument)

766
    def get_cpp_declaration(self):
767 768 769 770 771 772 773
        """
        C++ method declaraion

        rtype:      str
        return:     C++ header declaration for this method
        """
        result = ""
774
        if self.return_type is None:
775 776
            result += "void "
        else:
777
            rtype = self.return_type
778
            assert isinstance(rtype, BaseType)
779 780
            result += rtype.get_qt_name() + " "
        arg_declarations = map(lambda x: x.get_cpp_argument_declaration(), self.arguments)
781
        # noinspection PyUnresolvedReferences
782
        result += "run" + self.name.get_camel_case_cpp_value() + "(" + string.join(arg_declarations, ", ") + ")"
783 784
        return result

785
    def get_kumir_declaration(self):
786 787 788 789 790 791 792
        """
        Kumir method declaration

        rtype:      unicode
        return:     Kumir header to be parsed by Kumir analizer as text program
        """
        result = u"алг "
793
        if self.return_type is not None:
794
            rtype = self.return_type
795
            assert isinstance(rtype, BaseType)
796 797
            result += rtype.get_kumir_name() + " "
        result += self.name.get_kumir_value()
798
        if self.arguments:
799
            arg_declarations = map(lambda x: x.get_kumir_argument_declaration(), self.arguments)
800
            # noinspection PyUnresolvedReferences
801
            result += "(" + string.join(arg_declarations, ", ") + ")"
802 803
        return result

804
    def get_cpp_implementation_stub(self, class_name):
805 806 807
        """
        Creates default method implementation stub

808 809
        :type  class_name:  str
        :param class_name:  module class name used in C++ before ::
810 811 812
        :rtype:             unicode
        :return:            C++ implementation stub
        """
813
        kumir_return_type = ""
814 815
        retval = None
        result = "/* public slot */ "
816
        if self.return_type is not None:
817 818
            kumir_return_type = self.return_type.get_kumir_name() + " "
            if self.return_type.get_qt_name() == "qreal":
819
                retval = "0.0"
820
            elif self.return_type.get_qt_name() == "int":
821
                retval = "0"
822
            elif self.return_type.get_qt_name() == "bool":
823 824
                retval = "false"
            else:
825 826
                retval = self.return_type.get_qt_name() + "()"
        if self.return_type is None:
827 828
            result += "void "
        else:
829 830 831
            result += self.return_type.get_qt_name() + " "
        result += class_name + "::run" + self.name.get_camel_case_cpp_value()
        body = u"/* алг " + kumir_return_type + self.name.get_kumir_value()
832 833 834 835 836 837
        if self.arguments:
            body += "("
            for index, argument in enumerate(self.arguments):
                assert isinstance(argument, Argument)
                if index:
                    body += ", "
838
                body += argument.get_kumir_argument_declaration()
839 840 841 842 843 844 845 846 847
            body += ")"
        body += " */\n"
        body += "// TODO implement me\n"
        if not self.arguments:
            result += "()\n"
        else:
            result += "("
            for argument in self.arguments:
                assert isinstance(argument, Argument)
848
                argument_line = argument.get_cpp_argument_declaration()
849
                if argument != self.arguments[-1]:
850
                    argument_line += ", "
851
                else:
852 853 854
                    argument_line += ")"
                result += argument_line
                body += "Q_UNUSED(" + argument.name.get_cpp_value() + ")  // Remove this line on implementation;\n"
855 856 857
            result += "\n"
        if retval:
            body += "return " + retval + ";\n"
858
        result += "{\n" + _add_indent(body) + "\n}\n\n"
859 860 861 862 863 864 865 866
        return result


class Window:
    """
    A windows of GUI part.
    """

867
    def __init__(self, json_node):
868 869 870
        """
        Initializes from JSON value

871 872
        :type   json_node:   dict
        :param  json_node:   window specification
873
        """
874 875 876
        assert isinstance(json_node, dict)
        self.role = str(json_node["role"])
        self.icon = str(json_node["icon"])
877 878 879 880 881 882 883


class MenuItem:
    """
    An item of GUI menu.
    """

884
    def __init__(self, json_node):
885 886 887
        """
        Initializes from JSON value

888 889
        :type   json_node:   dict
        :param  json_node:   menu item specification
890
        """
891 892 893
        assert isinstance(json_node, dict)
        assert "title" in json_node
        self.title = Name(json_node["title"])
894
        self.items = []
895 896
        if "items" in json_node:
            for item in json_node["items"]:
897
                self.items.append(MenuItem(item))
898 899
        if "icon" in json_node:
            self.icon = str(json_node["icon"])
900 901
        else:
            self.icon = None
902

903 904 905
    def __repr__(self):
        return self.title.get_ascii_value()

906

907 908 909 910 911
class Gui:
    """
    GUI part of module.
    """

912
    def __init__(self, json_node):
913 914 915
        """
        Initializes from JSON value

916 917
        :type   json_node:   dict
        :param  json_node:   GUI specification
918
        """
919
        assert isinstance(json_node, dict)
920 921
        self.windows = []
        self.menus = []
922 923
        if "windows" in json_node:
            for window in json_node["windows"]:
924
                self.windows.append(Window(window))
925 926
        if "menus" in json_node:
            for menu in json_node["menus"]:
927
                self.menus.append(MenuItem(menu))
928 929 930 931
        if "optional" in json_node:
            self.optional = json_node["optional"]
        else:
            self.optional = False
932

933
    def get_icon_name(self, role):
934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953
        """
        Icon name for specified window role

        :type role:     str
        :param role:    role of window (currently "main" and "pult")
        :rtype:         str
        :return:        icon base name of an empty string if not specified
        """
        for window in self.windows:
            assert isinstance(window, Window)
            if window.role == role and window.icon:
                return window.icon
        return ""


class Module:
    """
    A Kumir module.
    """

Victor Yacovlev's avatar
Victor Yacovlev committed
954
    def __init__(self, module_dir, json_file_name, json_node):
955 956 957
        """
        Initializes from JSON value

958 959 960 961
        :type   module_dir:  str or unicode
        :param  module_dir:  module root directory
        :type   json_node:   dict
        :param  json_node:   module specification (JSON root)
962
        """
963 964 965
        assert isinstance(json_node, dict)
        assert "name" in json_node
        assert "methods" in json_node
Victor Yacovlev's avatar
Victor Yacovlev committed
966
        assert isinstance(json_file_name, str) or isinstance(json_file_name, unicode)
967
        self.name = Name(json_node["name"])
968
        self.types = []
969
        self.uses_list = []
Victor Yacovlev's avatar
Victor Yacovlev committed
970
        self.default_template_parameters = []
Victor Yacovlev's avatar
Victor Yacovlev committed
971
        self.json_file_name = json_file_name
Victor Yacovlev's avatar
Victor Yacovlev committed
972 973
        if "templateDefaults" in json_node:
            self.default_template_parameters = json_node["templateDefaults"]
974 975
        if "types" in json_node:
            for typee in json_node["types"]:
976
                self.types.append(BaseType(self, typee))
977 978 979
        if "uses" in json_node:
            self.uses_list = json_node["uses"]
            self._read_dependencies(module_dir, self.uses_list)
</