5. Seamless interoperation with the DOM

5.1. Practical example: a simple, responsive website using no HTML or CSS at all

To many programmers, using 'static' HTML and CSS feels like being locked up in a closet. As an alternative, responsiveness can simply be programmed using Transcrypt, as can be seen on this site written in Transcrypt. Note that it features sourcemaps. The site adapts to diverse screen formats, device types and landscape vs. portrait mode.

5.2. SVG example: Turtle graphics

Turtle graphics are a way to teach computer programming to children, invented by Seymour Papert in the 1960's. Lines are drawn as 'track' of a walking turtle, that can move ahead and turn. Children use turtle graphics intuitively by imagining what they would do if they were the turtle. This leads to a recipe of motion, indeed an algorithm, which is the basis of imperative (as opposed to e.g. declarative) programming.

SVG or Scalable Vector Graphics are a way to display high quality graphs, e.g. in the browser. SVG, as opposed to e.g. the HTML Canvas, bypasses the pixel paradigm and works with floating point coordinates directly. As a consequence, SVG plots can be zoomed without becoming ragged or 'pixelated'.

When looking under the hood of SVG, there's an amazing correspondence between the primitives in an SVG path and the primitives of turtle graphics. So both from an aestethical and from a conceptual point of view, turtle graphics and SVG form a happy mariage.

Turtle graphics in Transcrypt do not require the use of any graphics libraries. Below are two turtle graphics examples and the source code of Transcrypt's turtle module, which is quite compact. As can be seen from the code integration between Transcrypt and JavaScript is trivial.

Drawing a alternatingly floodfilled star
# Free after the example from the Python 3.5 manual

from turtle import *

up ()
goto (-250, -21)
startPos = pos ()

down ()
color ('red', 'yellow')
begin_fill ()
while True:
    forward (500)
    right (170)
    if distance (startPos) < 1:
        break
end_fill ()
done ()

Click here to view the resulting zoomable star.

Drawing the contours of a snowflake
from turtle import *

josh = Turtle ()

def draw (length):
    if length > 9:
        draw (length / 3)
        josh.left (60)
        draw (length / 3)
        josh.right (120)
        draw (length / 3)
        josh.left (60)
        draw (length / 3)
    else:
        josh.forward (length)

length = 150
josh.up ()
josh.forward (length / 2)
josh.left (90)
josh.forward (length / 4)
josh.right (90)
josh.down ()

for i in range (3):
    josh.right (120)
    draw (length)
    
josh.done ()

Click here to view the resulting zoomable snowflake.

Transcrypt's turtle graphics module sits directly on top of SVG, no libraries needed, so a very compact download
__pragma__ ('skip')
document = Math = setInterval = clearInterval = 0
__pragma__ ('noskip')

_debug = False

def abs (vec2D):
    return Math.sqrt (vec2D [0] * vec2D [0] + vec2D [1] * vec2D [1])

_ns = 'http://www.w3.org/2000/svg'
_svg = document.createElementNS (_ns, 'svg')

_defaultElement = document.getElementById ('__turtlegraph__')
if not _defaultElement:
    _defaultElement = document.body
_defaultElement.appendChild (_svg)

_width = None
_height = None
_offset = None

def _rightSize (self):
    nonlocal _width
    nonlocal _height
    nonlocal _offset
    
    _width = _defaultElement.offsetWidth
    _height = _defaultElement.offsetHeight
    _offset = [_width // 2, _height // 2]
    
    _svg.setAttribute ('width', _width)
    _svg.setAttribute ('height', _height)
    
window.onresize = _rightSize

_rightSize ()

def bgcolor (color):
    nonlocal _defaultElement

    _bgcolor = color
    _defaultElement.style.backgroundColor = _bgcolor

bgcolor ('white')
    
def setDefaultElement (element):
    nonlocal _defaultElement

    _defaultElement.removeChild (_svg)
    _defaultElement = element
    element.appendChild (_svg)
    
    _rightSize ()
    bgcolor ('white')

_allTurtles = []
    
class Turtle:
    def __init__ (self):
        _allTurtles.append (self)
        self._paths = []
        self.reset ()
        
    def reset (self):
        self._heading = Math.PI / 2
        self.pensize (1)
        self.color ('black', 'black')
        self.down ()
        self._track = []    # Need to make track explicitly because:
        self.home ()        #   Makes a position but needs a track to put in in
        self.clear ()       #   Makes a track but needs a position to initialize it with
        
    def clear (self):
        for path in self._paths:
            _svg.removeChild (path)
        self._paths = []
        
        self._track = []
        self._moveto (self._position)
        
    def _flush (self):
        if _debug:
            print ('Flush:', self._track)
    
        if len (self._track) > 1:
            path = document.createElementNS (_ns, 'path')
            path.setAttribute ('d', ' '.join (self._track))
            path.setAttribute ('stroke', self._pencolor if self._pencolor != None else 'none')
            path.setAttribute ('stroke-width', self._pensize)
            path.setAttribute ('fill', self._fillcolor if self._fill and self._fillcolor != None else 'none')           
            path.setAttribute ('fill-rule', 'evenodd')
            _svg.appendChild (path)
            self._paths.append (path)
                
            self._track = []
            self._moveto (self._position)   # _track should start with a move command
        
    def done (self):
        self._flush ()
        
    def pensize (self, width):
        self._flush ()
        if width == None:
            return self._pensize
        else:
            self._pensize = width
    
    def color (self, pencolor, fillcolor = None):
        self._flush ()
        self._pencolor = pencolor
        
        if fillcolor != None:
            self._fillcolor = fillcolor
    
    def goto (self, x, y = None):
        if y == None:
            self._position = x
        else:
            self._position = [x, y]
            
        self._track.append ('{} {} {}'.format (
            'L' if self._down else 'M',
            self._position [0] + _offset [0],
            self._position [1] + _offset [1])
        )
        
    def _moveto (self, x, y = None):
        wasdown = self.isdown ()
        self.up ()
        self.goto (x, y)
        if wasdown:
            self.down ()
            
    def home (self):
        self._moveto (0, 0)
        
    def position (self):
        return self._position [:]
        
    def pos (self):
        return self.position ()
        
    def distance (self, x, y = None):
        if y == None:
            other = x
        else:
            other = [x, y]
            
        dX = other [0] - self._position [0]
        dY = other [1] - self._position [1]
        
        return Math.sqrt (dX * dX + dY * dY)
            
    def up (self):
        self._down = False
        
    def down (self):
        self._down = True
        
    def isdown (self):
        return self._down
        
    def _predict (self, length):
        delta = [Math.sin (self._heading), Math.cos (self._heading)]
        return [self._position [0] + length * delta [0], self._position [1] + length * delta [1]]
        
    def forward (self, length):
        self._position = self._predict (length)
        
        self._track.append ('{} {} {}'.format (
            'L' if self._down else 'M',
            self._position [0] + _offset [0],
            self._position [1] + _offset [1])
        )
        
    def back (self, length):
        self.forward (-length)
        
    def circle (self, radius):
        self.left (90)
        opposite = self._predict (2 * (radius + 1) + 1)
        self.right (90)
    
        self._track.append ('{} {} {} {} {} {} {} {}'.format (
            'A',
            radius,
            radius,
            0,
            1,
            0,
            opposite [0] + _offset [0],
            opposite [1] + _offset [1]
        ))
        
        self._track.append ('{} {} {} {} {} {} {} {}'.format (
            'A',
            radius,
            radius,
            0,
            1,
            0,
            self._position [0] + _offset [0],
            self._position [1] + _offset [1]
        ))
        
    def left (self, angle):
        self._heading = (self._heading + Math.PI * angle / 180) % (2 * Math.PI)
            
    def right (self, angle): 
        self.left (-angle)
        
    def begin_fill (self):
        self._flush ()
        self._fill = True
    
    def end_fill (self):
        self._flush ()
        self._fill = False
        
    def speed (speed = None):
        pass
        
_defaultTurtle = Turtle ()
_timer = None
    
def reset ():
    nonlocal _timer, _allTurtles
    if _timer:
        clearTimeout (_timer)
    bgcolor ('white')
    for turtle in _allTurtles:
        turtle.reset ()
        turtle.done ()
        
def clear ():
    nonlocal _allTurtles
    for turtle in _allTurtles:
        turtle.clear ()
        
def ontimer (fun, t = 0):
    nonlocal _timer
    _timer = setTimeout (fun, t)

def done ():                            _defaultTurtle.done ()
def pensize (width):                    _defaultTurtle.pensize (width)
def color (pencolor, fillcolor = None): _defaultTurtle.color (pencolor, fillcolor)
def home ():                            _defaultTurtle.home ()
def goto (x, y = None):                 _defaultTurtle.goto (x, y)
def position ():                        return _defaultTurtle.position ()
def pos ():                             return _defaultTurtle.pos ()
def distance (x, y = None):             return _defaultTurtle.distance (x, y)
def up ():                              _defaultTurtle.up ()
def down ():                            _defaultTurtle.down ()
def forward (length):                   _defaultTurtle.forward (length)
def back (length):                      _defaultTurtle.back (length)
def circle (radius):                    _defaultTurtle.circle (radius)
def left (angle):                       _defaultTurtle.left (angle)
def right (angle):                      _defaultTurtle.right (angle)
def begin_fill ():                      _defaultTurtle.begin_fill ()
def end_fill ():                        _defaultTurtle.end_fill ()
def speed (speed):                      _defaultTurtle.speed (speed)

Remark: In a later stage animation may be added. As a further step, for complicated fractals, transparent server side compilation of a relatively simple algorithm would allow on-line editing combined with fast client side rendering of high-resolution graphics.

6. Mixed examples

6.1. Three ways of integration with JavaScript libraries

There are three ways to integrate Transcrypt applications with existing JavaScript libraries.

  1. The simplest way is to use the library as is, without any encapsulation. In this way all symbols of that library will be in the global namespace. While many JavaScript programmers don't seem to mind that, many Python programmers do.
  2. Another way is to encapsulate the JavaScript library as a whole in a Transcrypt module. In the distibution this is done for the fabric module, that encapsulates fabric.js. In this way the global namespace stays clean.
  3. The third way is to write a complete Pythonic API for the JavaScript library. This is overkill in most cases and makes it harder to keep up with new versions of the library. Note that Transcrypt was desiged to make seamless cooperation between Transcrypt and JavaScript libraries possible without any glue code.

In the Pong example below, approach 2 is choosen to encapsulate the fabric.js graphics library. In most cases this approach strikes a good balance between effort and yield. As can be seen below, the effort involved is minimal.

The encapsulation layer for fabric.js
__pragma__ ('noanno')

fabric = __pragma__ ('js',
    '''
(function () {{
    var exports = {{}};
    {}  // Puts fabric in exports and in global window
    delete window.fabric;
    return exports;
}}) () .fabric;
    ''',
    __include__ ('com/fabricjs/__javascript__/fabric.js')
)

Note that __pragma__ ('js', <skeletoncode>, includes = [<file1>, <file2>, ..]) is used to achieve the encapsulation. It replaces the {} by the respective contents of the files. The fabric module is part of the download. Note that not all facilities were included in customizing fabric.js. You can drop-in replace the fabric.js in the __javascript__ subdirectory by another customized version without changing anything. Preferably download a development version, since that enables easy debugging. Transcryp will minify it for you on the fly.

6.2. Example: Pong

In using the fabric.js JavaScript library this example, the only thing differing from plain JavaScipt is that new <constructor> is replaced by __new__ (<constructor>).

pong.py
__pragma__ ('skip')
document = window = Math = Date = 0 # Prevent complaints by optional static checker
__pragma__ ('noskip')

__pragma__ ('noalias', 'clear')

from com.fabricjs import fabric

orthoWidth = 1000
orthoHeight = 750
fieldHeight = 650

enter, esc, space = 13, 27, 32

window.onkeydown = lambda event: event.keyCode != space # Prevent scrolldown on spacebar press

class Attribute:    # Attribute in the gaming sense of the word, rather than of an object
    def __init__ (self, game):
        self.game = game                    # Attribute knows game it's part of
        self.game.attributes.append (self)  # Game knows all its attributes
        self.install ()                     # Put in place graphical representation of attribute
        self.reset ()                       # Reset attribute to start position
                
    def reset (self):       # Restore starting positions or score, then commit to fabric
        self.commit ()      # Nothing to restore for the Attribute base class
                
    def predict (self):
        pass
                
    def interact (self):
        pass
        
    def commit (self):
        pass

class Sprite (Attribute):   # Here, a sprite is an attribute that can move
    def __init__ (self, game, width, height):
        self.width = width
        self.height = height
        Attribute.__init__ (self, game)
        
    def install (self):     # The sprite holds an image that fabric can display
        self.image = __new__ (fabric.Rect ({
            'width': self.game.scaleX (self.width), 'height': self.game.scaleY (self.height),
            'originX': 'center', 'originY': 'center', 'fill': 'white'
        }))
        
    __pragma__ ('kwargs')
    def reset (self, vX = 0, vY = 0, x = 0, y = 0):
        self.vX = vX        # Speed
        self.vY = vY
        
        self.x = x          # Predicted position, can be commit, no bouncing initially
        self.y = y
        
        Attribute.reset (self)
    __pragma__ ('nokwargs')
        
    def predict (self):     # Predict position, do not yet commit, bouncing may alter it
        self.x += self.vX * self.game.deltaT
        self.y += self.vY * self.game.deltaT

    def commit (self):      # Update fabric image for asynch draw
        self.image.left = self.game.orthoX (self.x)
        self.image.top = self.game.orthoY (self.y)
        
    def draw (self):
        self.game.canvas.add (self.image)
         
class Paddle (Sprite):
    margin = 30 # Distance of paddles from walls
    width = 10
    height = 100
    speed = 400 # / s
    
    def __init__ (self, game, index):
        self.index = index  # Paddle knows its player index, 0 == left, 1 == right
        Sprite.__init__ (self, game, self.width, self.height)
        
    def reset (self):       # Put paddle in rest position, dependent on player index
        Sprite.reset (
            self,
            x = orthoWidth // 2 - self.margin if self.index else -orthoWidth // 2 + self.margin,
            y = 0
        )
        
    def predict (self): # Let paddle react on keys
        self.vY = 0
        
        if self.index:                          # Right player
            if self.game.keyCode == ord ('K'):  # Letter K pressed
                self.vY = self.speed
            elif self.game.keyCode == ord ('M'):
                self.vY = -self.speed
        else:                                   # Left player
            if self.game.keyCode == ord ('A'):
                self.vY = self.speed
            elif self.game.keyCode == ord ('Z'):
                self.vY = -self.speed
                
        Sprite.predict (self)                   # Do not yet commit, paddle may bounce with walls

    def interact (self):    # Paddles and ball assumed infinitely thin
        # Paddle touches wall
        self.y = Math.max (self.height // 2 - fieldHeight // 2, Math.min (self.y, fieldHeight // 2 - self.height // 2))
        
        # Paddle hits ball
        if (
            (self.y - self.height // 2) < self.game.ball.y < (self.y + self.height // 2)
            and (
                (self.index == 0 and self.game.ball.x < self.x) # On or behind left paddle
                or
                (self.index == 1 and self.game.ball.x > self.x) # On or behind right paddle
            )
        ):
            self.game.ball.x = self.x               # Ball may have gone too far already
            self.game.ball.vX = -self.game.ball.vX  # Bounce on paddle
            self.game.ball.speedUp (self)
        
class Ball (Sprite):
    side = 8
    speed = 300 # / s
    
    def __init__ (self, game):
        Sprite.__init__ (self, game, self.side, self.side)
 
    def reset (self):   # Launch according to service direction with random angle offset from horizontal
        angle =  (
            self.game.serviceIndex * Math.PI    # Service direction
            +
            (1 if Math.random () > 0.5 else -1) * Math.random () * Math.atan (fieldHeight / orthoWidth)
        )
        
        Sprite.reset (
            self,
            vX = self.speed * Math.cos (angle),
            vY = self.speed * Math.sin (angle)
        )
        
    def predict (self):
        Sprite.predict (self)           # Integrate velocity to position
        
        if self.x < -orthoWidth // 2:   # If out on left side
            self.game.scored (1)        #   Right player scored
        elif self.x > orthoWidth // 2:
            self.game.scored (0)
            
        if self.y > fieldHeight // 2:   # If it hits top wall
            self.y = fieldHeight // 2   #   It may have gone too far already
            self.vY = -self.vY          #   Bounce
        elif self.y < -fieldHeight // 2:
            self.y = -fieldHeight // 2
            self.vY = -self.vY

    def speedUp (self, bat):
        factor = 1 + 0.15 * (1 - Math.abs (self.y - bat.y) / (bat.height // 2)) ** 2    # Speed will increase more if paddle hit near centre
        
        if Math.abs (self.vX) < 3 * self.speed:
            self.vX *= factor
            self.vY *= factor           

class Scoreboard (Attribute):
    nameShift = 75
    hintShift = 25
            
    def install (self): # Graphical representation of scoreboard are four labels and a separator line
        self.playerLabels = [__new__ (fabric.Text ('Player {}'.format (name), {
                'fill': 'white', 'fontFamily': 'arial', 'fontSize': '{}' .format (self.game.canvas.width / 30),
                'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (fieldHeight // 2 + self.nameShift)
        })) for name, position in (('AZ keys:', -7/16), ('KM keys:', 1/16))]
        
        self.hintLabel = __new__ (fabric.Text ('[spacebar] starts game, [enter] resets score', {
                'fill': 'white', 'fontFamily': 'arial', 'fontSize': '{}'.format (self.game.canvas.width / 70),
                'left': self.game.orthoX (-7/16 * orthoWidth), 'top': self.game.orthoY (fieldHeight // 2 + self.hintShift)
        }))
        
        self.image = __new__ (fabric.Line ([
                self.game.orthoX (-orthoWidth // 2), self.game.orthoY (fieldHeight // 2),
                self.game.orthoX (orthoWidth // 2), self.game.orthoY (fieldHeight // 2)
            ],
            {'stroke': 'white'}
        ))
                
    def increment (self, playerIndex):
        self.scores [playerIndex] += 1
        
    def reset (self):
        self.scores = [0, 0]
        Attribute.reset (self)  # Only does a commit here
        
    def commit (self):          # Committing labels is adapting their texts
        self.scoreLabels = [__new__ (fabric.Text ('{}'.format (score), {
                'fill': 'white', 'fontFamily': 'arial', 'fontSize': '{}'.format (self.game.canvas.width / 30),
                'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (fieldHeight // 2 + self.nameShift)
        })) for score, position in zip (self.scores, (-2/16, 6/16))]

    def draw (self):
        for playerLabel, scoreLabel in zip (self.playerLabels, self.scoreLabels):
            self.game.canvas.add (playerLabel)
            self.game.canvas.add (scoreLabel)
            self.game.canvas.add (self.hintLabel)
        self.game.canvas.add (self.image)
        
class Game:
    def __init__ (self):
        self.serviceIndex = 1 if Math.random () > 0.5 else 0    # Index of player that has initial service
        self.pause = True                           # Start game in paused state
        self.keyCode = None
        
        self.textFrame = document.getElementById ('text_frame')
        self.canvasFrame = document.getElementById ('canvas_frame')
        self.buttonsFrame = document.getElementById ('buttons_frame')
        
        self.canvas = __new__ (fabric.Canvas ('canvas', {'backgroundColor': 'black', 'originX': 'center', 'originY': 'center'}))
        self.canvas.onWindowDraw = self.draw        # Install draw callback, will be called asynch
        self.canvas.lineWidth = 2
        self.canvas.clear ()    

        self.attributes = []                        # All attributes will insert themselves here
        self.paddles = [Paddle (self, index) for index in range (2)]    # Pass game as parameter self
        self.ball = Ball (self)
        self.scoreboard = Scoreboard (self)     

        window.setInterval (self.update, 10)    # Install update callback, time in ms
        window.setInterval (self.draw, 20)      # Install draw callback, time in ms
        window.addEventListener ('keydown', self.keydown)
        window.addEventListener ('keyup', self.keyup)
        
        self.buttons = []
        
        for key in ('A', 'Z', 'K', 'M', 'space', 'enter'):
            button = document.getElementById (key)
            button.addEventListener ('mousedown', (lambda aKey: lambda: self.mouseOrTouch (aKey, True)) (key))  # Returns inner lambda
            button.addEventListener ('touchstart', (lambda aKey: lambda: self.mouseOrTouch (aKey, True)) (key))
            button.addEventListener ('mouseup', (lambda aKey: lambda: self.mouseOrTouch (aKey, False)) (key))
            button.addEventListener ('touchend', (lambda aKey: lambda: self.mouseOrTouch (aKey, False)) (key))
            button.style.cursor = 'pointer'
            button.style.userSelect = 'none'
            self.buttons.append (button)
            
        self.time = + __new__ (Date)
        
        window.onresize = self.resize
        self.resize ()
        
    def install (self):
        for attribute in self.attributes:
            attribute.install ()
        
    def mouseOrTouch (self, key, down):
        if down:
            if key == 'space':
                self.keyCode = space
            elif key == 'enter':
                self.keyCode = enter
            else:
                self.keyCode = ord (key)
        else:
            self.keyCode = None
 
    def update (self):                          # Note that update and draw are not synchronized
        oldTime = self.time
        self.time = + __new__ (Date)
        self.deltaT = (self.time - oldTime) / 1000.
        
        if self.pause:                          # If in paused state
            if self.keyCode == space:           #   If spacebar hit
                self.pause = False              #         Start playing
            elif self.keyCode == enter:         #   Else if enter hit
                self.scoreboard.reset ()        #         Reset score
        else:                                   # Else, so if in active state
            for attribute in self.attributes:   #   Compute predicted values
                attribute.predict ()
            
            for attribute in self.attributes:   #   Correct values for bouncing and scoring
                attribute.interact ()
            
            for attribute in self.attributes:   #   Commit them to pyglet for display
                attribute.commit ()
            
    def scored (self, playerIndex):             # Player has scored
        self.scoreboard.increment (playerIndex) # Increment player's points
        self.serviceIndex = 1 - playerIndex     # Grant service to the unlucky player
        
        for paddle in self.paddles:             # Put paddles in rest position
            paddle.reset ()
            
        self.ball.reset ()                      # Put ball in rest position
        self.pause = True                       # Wait for next round
        
    def commit (self):
        for attribute in self.attributes:
            attribute.commit ()
        
    def draw (self):
        self.canvas.clear ()
        for attribute in self.attributes:
            attribute.draw ()
             
    def resize (self):
        self.pageWidth = window.innerWidth
        self.pageHeight = window.innerHeight
        
        self.textTop = 0

        if self.pageHeight > 1.2 * self.pageWidth:
            self.canvasWidth = self.pageWidth
            self.canvasTop = self.textTop + 300
        else:
            self.canvasWidth = 0.6 * self.pageWidth
            self.canvasTop = self.textTop + 200

        self.canvasLeft = 0.5 * (self.pageWidth - self.canvasWidth)
        self.canvasHeight = 0.6 * self.canvasWidth

        self.buttonsTop = self.canvasTop + self.canvasHeight + 50
        self.buttonsWidth = 500
            
        self.textFrame.style.top = self.textTop;
        self.textFrame.style.left = self.canvasLeft + 0.05 * self.canvasWidth
        self.textFrame.style.width = 0.9 * self.canvasWidth
            
        self.canvasFrame.style.top = self.canvasTop
        self.canvasFrame.style.left = self.canvasLeft
        self.canvas.setDimensions ({'width': self.canvasWidth, 'height': self.canvasHeight})
        
        self.buttonsFrame.style.top = self.buttonsTop
        self.buttonsFrame.style.left = 0.5 * (self.pageWidth - self.buttonsWidth)
        self.buttonsFrame.style.width = self.canvasWidth
        
        self.install ()
        self.commit ()
        self.draw ()
        
    def scaleX (self, x):
        return x * (self.canvas.width / orthoWidth)
            
    def scaleY (self, y):
        return y * (self.canvas.height / orthoHeight)   
        
    def orthoX (self, x):
        return self.scaleX (x + orthoWidth // 2)
        
    def orthoY (self, y):
        return self.scaleY (orthoHeight - fieldHeight // 2 - y)
                
    def keydown (self, event):
        self.keyCode = event.keyCode
        
    def keyup (self, event):
        self.keyCode = None 
        
game = Game ()  # Create and run game
pong.mod.js
    (function () {
        var __name__ = '__main__';
        var fabric = __init__ (__world__.com.fabricjs).fabric;
        var orthoWidth = 1000;
        var orthoHeight = 750;
        var fieldHeight = 650;
        var __left0__ = tuple ([13, 27, 32]);
        var enter = __left0__ [0];
        var esc = __left0__ [1];
        var space = __left0__ [2];
        window.onkeydown = (function __lambda__ (event) {
            return event.keyCode != space;
        });
        var Attribute = __class__ ('Attribute', [object], {
            __module__: __name__,
            get __init__ () {return __get__ (this, function (self, game) {
                self.game = game;
                self.game.attributes.append (self);
                self.install ();
                self.reset ();
            });},
            get reset () {return __get__ (this, function (self) {
                self.commit ();
            });},
            get predict () {return __get__ (this, function (self) {
                // pass;
            });},
            get interact () {return __get__ (this, function (self) {
                // pass;
            });},
            get commit () {return __get__ (this, function (self) {
                // pass;
            });}
        });
        var Sprite = __class__ ('Sprite', [Attribute], {
            __module__: __name__,
            get __init__ () {return __get__ (this, function (self, game, width, height) {
                self.width = width;
                self.height = height;
                Attribute.__init__ (self, game);
            });},
            get install () {return __get__ (this, function (self) {
                self.image = new fabric.Rect (dict ({'width': self.game.scaleX (self.width), 'height': self.game.scaleY (self.height), 'originX': 'center', 'originY': 'center', 'fill': 'white'}));
            });},
            get reset () {return __get__ (this, function (self, vX, vY, x, y) {
                if (typeof vX == 'undefined' || (vX != null && vX .hasOwnProperty ("__kwargtrans__"))) {;
                    var vX = 0;
                };
                if (typeof vY == 'undefined' || (vY != null && vY .hasOwnProperty ("__kwargtrans__"))) {;
                    var vY = 0;
                };
                if (typeof x == 'undefined' || (x != null && x .hasOwnProperty ("__kwargtrans__"))) {;
                    var x = 0;
                };
                if (typeof y == 'undefined' || (y != null && y .hasOwnProperty ("__kwargtrans__"))) {;
                    var y = 0;
                };
                if (arguments.length) {
                    var __ilastarg0__ = arguments.length - 1;
                    if (arguments [__ilastarg0__] && arguments [__ilastarg0__].hasOwnProperty ("__kwargtrans__")) {
                        var __allkwargs0__ = arguments [__ilastarg0__--];
                        for (var __attrib0__ in __allkwargs0__) {
                            switch (__attrib0__) {
                                case 'self': var self = __allkwargs0__ [__attrib0__]; break;
                                case 'vX': var vX = __allkwargs0__ [__attrib0__]; break;
                                case 'vY': var vY = __allkwargs0__ [__attrib0__]; break;
                                case 'x': var x = __allkwargs0__ [__attrib0__]; break;
                                case 'y': var y = __allkwargs0__ [__attrib0__]; break;
                            }
                        }
                    }
                }
                else {
                }
                self.vX = vX;
                self.vY = vY;
                self.x = x;
                self.y = y;
                Attribute.reset (self);
            });},
            get predict () {return __get__ (this, function (self) {
                self.x += self.vX * self.game.deltaT;
                self.y += self.vY * self.game.deltaT;
            });},
            get commit () {return __get__ (this, function (self) {
                self.image.left = self.game.orthoX (self.x);
                self.image.top = self.game.orthoY (self.y);
            });},
            get draw () {return __get__ (this, function (self) {
                self.game.canvas.add (self.image);
            });}
        });
        var Paddle = __class__ ('Paddle', [Sprite], {
            __module__: __name__,
            margin: 30,
            width: 10,
            height: 100,
            speed: 400,
            get __init__ () {return __get__ (this, function (self, game, index) {
                self.index = index;
                Sprite.__init__ (self, game, self.width, self.height);
            });},
            get reset () {return __get__ (this, function (self) {
                Sprite.reset (self, __kwargtrans__ ({x: (self.index ? Math.floor (orthoWidth / 2) - self.margin : Math.floor (-(orthoWidth) / 2) + self.margin), y: 0}));
            });},
            get predict () {return __get__ (this, function (self) {
                self.vY = 0;
                if (self.index) {
                    if (self.game.keyCode == ord ('K')) {
                        self.vY = self.speed;
                    }
                    else if (self.game.keyCode == ord ('M')) {
                        self.vY = -(self.speed);
                    }
                }
                else if (self.game.keyCode == ord ('A')) {
                    self.vY = self.speed;
                }
                else if (self.game.keyCode == ord ('Z')) {
                    self.vY = -(self.speed);
                }
                Sprite.predict (self);
            });},
            get interact () {return __get__ (this, function (self) {
                self.y = Math.max (Math.floor (self.height / 2) - Math.floor (fieldHeight / 2), Math.min (self.y, Math.floor (fieldHeight / 2) - Math.floor (self.height / 2)));
                if ((self.y - Math.floor (self.height / 2) < self.game.ball.y && self.game.ball.y < self.y + Math.floor (self.height / 2)) && (self.index == 0 && self.game.ball.x < self.x || self.index == 1 && self.game.ball.x > self.x)) {
                    self.game.ball.x = self.x;
                    self.game.ball.vX = -(self.game.ball.vX);
                    self.game.ball.speedUp (self);
                }
            });}
        });
        var Ball = __class__ ('Ball', [Sprite], {
            __module__: __name__,
            side: 8,
            speed: 300,
            get __init__ () {return __get__ (this, function (self, game) {
                Sprite.__init__ (self, game, self.side, self.side);
            });},
            get reset () {return __get__ (this, function (self) {
                var angle = self.game.serviceIndex * Math.PI + ((Math.random () > 0.5 ? 1 : -(1)) * Math.random ()) * Math.atan (fieldHeight / orthoWidth);
                Sprite.reset (self, __kwargtrans__ ({vX: self.speed * Math.cos (angle), vY: self.speed * Math.sin (angle)}));
            });},
            get predict () {return __get__ (this, function (self) {
                Sprite.predict (self);
                if (self.x < Math.floor (-(orthoWidth) / 2)) {
                    self.game.scored (1);
                }
                else if (self.x > Math.floor (orthoWidth / 2)) {
                    self.game.scored (0);
                }
                if (self.y > Math.floor (fieldHeight / 2)) {
                    self.y = Math.floor (fieldHeight / 2);
                    self.vY = -(self.vY);
                }
                else if (self.y < Math.floor (-(fieldHeight) / 2)) {
                    self.y = Math.floor (-(fieldHeight) / 2);
                    self.vY = -(self.vY);
                }
            });},
            get speedUp () {return __get__ (this, function (self, bat) {
                var factor = 1 + 0.15 * Math.pow (1 - Math.abs (self.y - bat.y) / (Math.floor (bat.height / 2)), 2);
                if (Math.abs (self.vX) < 3 * self.speed) {
                    self.vX *= factor;
                    self.vY *= factor;
                }
            });}
        });
        var Scoreboard = __class__ ('Scoreboard', [Attribute], {
            __module__: __name__,
            nameShift: 75,
            hintShift: 25,
            get install () {return __get__ (this, function (self) {
                self.playerLabels = (function () {
                    var __accu0__ = [];
                    for (var [py_name, position] of tuple ([tuple (['AZ keys:', -(7) / 16]), tuple (['KM keys:', 1 / 16])])) {
                        __accu0__.append (new fabric.Text ('Player {}'.format (py_name), dict ({'fill': 'white', 'fontFamily': 'arial', 'fontSize': '{}'.format (self.game.canvas.width / 30), 'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (Math.floor (fieldHeight / 2) + self.nameShift)})));
                    }
                    return __accu0__;
                }) ();
                self.hintLabel = new fabric.Text ('[spacebar] starts game, [enter] resets score', dict ({'fill': 'white', 'fontFamily': 'arial', 'fontSize': '{}'.format (self.game.canvas.width / 70), 'left': self.game.orthoX ((-(7) / 16) * orthoWidth), 'top': self.game.orthoY (Math.floor (fieldHeight / 2) + self.hintShift)}));
                self.image = new fabric.Line (list ([self.game.orthoX (Math.floor (-(orthoWidth) / 2)), self.game.orthoY (Math.floor (fieldHeight / 2)), self.game.orthoX (Math.floor (orthoWidth / 2)), self.game.orthoY (Math.floor (fieldHeight / 2))]), dict ({'stroke': 'white'}));
            });},
            get increment () {return __get__ (this, function (self, playerIndex) {
                self.scores [playerIndex]++;
            });},
            get reset () {return __get__ (this, function (self) {
                self.scores = list ([0, 0]);
                Attribute.reset (self);
            });},
            get commit () {return __get__ (this, function (self) {
                self.scoreLabels = (function () {
                    var __accu0__ = [];
                    for (var [score, position] of zip (self.scores, tuple ([-(2) / 16, 6 / 16]))) {
                        __accu0__.append (new fabric.Text ('{}'.format (score), dict ({'fill': 'white', 'fontFamily': 'arial', 'fontSize': '{}'.format (self.game.canvas.width / 30), 'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (Math.floor (fieldHeight / 2) + self.nameShift)})));
                    }
                    return __accu0__;
                }) ();
            });},
            get draw () {return __get__ (this, function (self) {
                for (var [playerLabel, scoreLabel] of zip (self.playerLabels, self.scoreLabels)) {
                    self.game.canvas.add (playerLabel);
                    self.game.canvas.add (scoreLabel);
                    self.game.canvas.add (self.hintLabel);
                }
                self.game.canvas.add (self.image);
            });}
        });
        var Game = __class__ ('Game', [object], {
            __module__: __name__,
            get __init__ () {return __get__ (this, function (self) {
                self.serviceIndex = (Math.random () > 0.5 ? 1 : 0);
                self.pause = true;
                self.keyCode = null;
                self.textFrame = document.getElementById ('text_frame');
                self.canvasFrame = document.getElementById ('canvas_frame');
                self.buttonsFrame = document.getElementById ('buttons_frame');
                self.canvas = new fabric.Canvas ('canvas', dict ({'backgroundColor': 'black', 'originX': 'center', 'originY': 'center'}));
                self.canvas.onWindowDraw = self.draw;
                self.canvas.lineWidth = 2;
                self.canvas.clear ();
                self.attributes = list ([]);
                self.paddles = (function () {
                    var __accu0__ = [];
                    for (var index = 0; index < 2; index++) {
                        __accu0__.append (Paddle (self, index));
                    }
                    return __accu0__;
                }) ();
                self.ball = Ball (self);
                self.scoreboard = Scoreboard (self);
                window.setInterval (self.py_update, 10);
                window.setInterval (self.draw, 20);
                window.addEventListener ('keydown', self.keydown);
                window.addEventListener ('keyup', self.keyup);
                self.buttons = list ([]);
                for (var key of tuple (['A', 'Z', 'K', 'M', 'space', 'enter'])) {
                    var button = document.getElementById (key);
                    button.addEventListener ('mousedown', (function __lambda__ (aKey) {
                        return (function __lambda__ () {
                            return self.mouseOrTouch (aKey, true);
                        });
                    }) (key));
                    button.addEventListener ('touchstart', (function __lambda__ (aKey) {
                        return (function __lambda__ () {
                            return self.mouseOrTouch (aKey, true);
                        });
                    }) (key));
                    button.addEventListener ('mouseup', (function __lambda__ (aKey) {
                        return (function __lambda__ () {
                            return self.mouseOrTouch (aKey, false);
                        });
                    }) (key));
                    button.addEventListener ('touchend', (function __lambda__ (aKey) {
                        return (function __lambda__ () {
                            return self.mouseOrTouch (aKey, false);
                        });
                    }) (key));
                    button.style.cursor = 'pointer';
                    button.style.userSelect = 'none';
                    self.buttons.append (button);
                }
                self.time = +(new Date);
                window.onresize = self.resize;
                self.resize ();
            });},
            get install () {return __get__ (this, function (self) {
                for (var attribute of self.attributes) {
                    attribute.install ();
                }
            });},
            get mouseOrTouch () {return __get__ (this, function (self, key, down) {
                if (down) {
                    if (key == 'space') {
                        self.keyCode = space;
                    }
                    else if (key == 'enter') {
                        self.keyCode = enter;
                    }
                    else {
                        self.keyCode = ord (key);
                    }
                }
                else {
                    self.keyCode = null;
                }
            });},
            get py_update () {return __get__ (this, function (self) {
                var oldTime = self.time;
                self.time = +(new Date);
                self.deltaT = (self.time - oldTime) / 1000.0;
                if (self.pause) {
                    if (self.keyCode == space) {
                        self.pause = false;
                    }
                    else if (self.keyCode == enter) {
                        self.scoreboard.reset ();
                    }
                }
                else {
                    for (var attribute of self.attributes) {
                        attribute.predict ();
                    }
                    for (var attribute of self.attributes) {
                        attribute.interact ();
                    }
                    for (var attribute of self.attributes) {
                        attribute.commit ();
                    }
                }
            });},
            get scored () {return __get__ (this, function (self, playerIndex) {
                self.scoreboard.increment (playerIndex);
                self.serviceIndex = 1 - playerIndex;
                for (var paddle of self.paddles) {
                    paddle.reset ();
                }
                self.ball.reset ();
                self.pause = true;
            });},
            get commit () {return __get__ (this, function (self) {
                for (var attribute of self.attributes) {
                    attribute.commit ();
                }
            });},
            get draw () {return __get__ (this, function (self) {
                self.canvas.clear ();
                for (var attribute of self.attributes) {
                    attribute.draw ();
                }
            });},
            get resize () {return __get__ (this, function (self) {
                self.pageWidth = window.innerWidth;
                self.pageHeight = window.innerHeight;
                self.textTop = 0;
                if (self.pageHeight > 1.2 * self.pageWidth) {
                    self.canvasWidth = self.pageWidth;
                    self.canvasTop = self.textTop + 300;
                }
                else {
                    self.canvasWidth = 0.6 * self.pageWidth;
                    self.canvasTop = self.textTop + 200;
                }
                self.canvasLeft = 0.5 * (self.pageWidth - self.canvasWidth);
                self.canvasHeight = 0.6 * self.canvasWidth;
                self.buttonsTop = (self.canvasTop + self.canvasHeight) + 50;
                self.buttonsWidth = 500;
                self.textFrame.style.top = self.textTop;
                self.textFrame.style.left = self.canvasLeft + 0.05 * self.canvasWidth;
                self.textFrame.style.width = 0.9 * self.canvasWidth;
                self.canvasFrame.style.top = self.canvasTop;
                self.canvasFrame.style.left = self.canvasLeft;
                self.canvas.setDimensions (dict ({'width': self.canvasWidth, 'height': self.canvasHeight}));
                self.buttonsFrame.style.top = self.buttonsTop;
                self.buttonsFrame.style.left = 0.5 * (self.pageWidth - self.buttonsWidth);
                self.buttonsFrame.style.width = self.canvasWidth;
                self.install ();
                self.commit ();
                self.draw ();
            });},
            get scaleX () {return __get__ (this, function (self, x) {
                return x * (self.canvas.width / orthoWidth);
            });},
            get scaleY () {return __get__ (this, function (self, y) {
                return y * (self.canvas.height / orthoHeight);
            });},
            get orthoX () {return __get__ (this, function (self, x) {
                return self.scaleX (x + Math.floor (orthoWidth / 2));
            });},
            get orthoY () {return __get__ (this, function (self, y) {
                return self.scaleY ((orthoHeight - Math.floor (fieldHeight / 2)) - y);
            });},
            get keydown () {return __get__ (this, function (self, event) {
                self.keyCode = event.keyCode;
            });},
            get keyup () {return __get__ (this, function (self, event) {
                self.keyCode = null;
            });}
        });
        var game = Game ();
        __pragma__ ('<use>' +
            'com.fabricjs' +
        '</use>')
        __pragma__ ('<all>')
            __all__.Attribute = Attribute;
            __all__.Ball = Ball;
            __all__.Game = Game;
            __all__.Paddle = Paddle;
            __all__.Scoreboard = Scoreboard;
            __all__.Sprite = Sprite;
            __all__.__name__ = __name__;
            __all__.enter = enter;
            __all__.esc = esc;
            __all__.fabric = fabric;
            __all__.fieldHeight = fieldHeight;
            __all__.game = game;
            __all__.orthoHeight = orthoHeight;
            __all__.orthoWidth = orthoWidth;
            __all__.space = space;
        __pragma__ ('</all>')
    }) ();

6.3. Joined minification

Minification is currently performed by the Google closure compiler, that's also part of the distribution. Rather than separately minifying libraries, the application is minified as a whole. In principle this enables a smaller total download size. Currently closures ADVANCED_OPTIMIZATIONS switch breaks the working strict code, however, so the SIMPLE_OPTIMIZATIONS switch is used by default.

As can be seen from the listings, pong.mod.js without libraries is only slightly longer than pong.py without libraries. The difference mainly comes from the expensive keyword arguments mechanism that is activated for the reset function, using __pragma__ ('kargs') and __pragma__ ('nokwargs'). The minified version is about half this size. The Transcrypt runtime itself in minified form is about 9kB. So the bulk of the total size of the minified file, 148kB comes from fabric.js. From this example it becomes clear that Transcrypt is extremely lightweight.

6.4. Example: jQuery

In contrast to the use of the fabric.js library in the Pong example, jQuery hasn't been encapsulated at all. It's just downloaded on the fly from a content delivery network and used as-is. Instead of the $ (that is not a valid Python identifier), an S is used as alias. This might have been any character sequence.

jquery_demo.py
__pragma__ ('alias', 'S', '$')

def start ():
    def changeColors ():
        for div in S__divs:
            S (div) .css ({
                'color': 'rgb({},{},{})'.format (* [int (256 * Math.random ()) for i in range (3)]),
            })

    S__divs = S ('div')
    changeColors ()
    window.setInterval (changeColors, 500)
jquery_demo.mod.js
    (function () {
        var __name__ = '__main__';
        var start = function () {
            var changeColors = function () {
                for (var div of $divs) {
                    $ (div).css (dict ({'color': 'rgb({},{},{})'.format (...(function () {
                        var __accu0__ = [];
                        for (var i = 0; i < 3; i++) {
                            __accu0__.append (int (256 * Math.random ()));
                        }
                        return __accu0__;
                    }) ())}));
                }
            };
            var $divs = $ ('div');
            changeColors ();
            window.setInterval (changeColors, 500);
        };
        __pragma__ ('<all>')
            __all__.__name__ = __name__;
            __all__.start = start;
        __pragma__ ('</all>')
    }) ();

6.5. Example: iOS web app with native look and feel

You can write full screen iOS web apps in Transcrypt with native look and feel. As example here's an app simulating 6 dice. While this example is kept very simple, you can in fact make apps of arbitrary complexity, with fast and beautiful graphics using any JS graphics library, e.g. multiplayer games working over the Internet. If you add the app to your homescreen it will be cached, so no Internet connection is needed to use it. Web apps for iOS can obtain and use location information from the user.

A simple dice web app for iOS
The prepacked dice icon on the homescreen

You can install this app on your iPhone from http://www.transcrypt.org/live/transcrypt/demos/ios_app/ios_app.html .

ios_app.py
import random

class Dice:
    def __init__ (self):
        document.body.addEventListener ('touchstart', lambda event: event.preventDefault ())
        document.body.addEventListener ('mousedown', lambda event: event.preventDefault ())
        document.body.style.margin = 0
        document.body.style.overflow = 'hidden';
    
        self.all = document.createElement ('div')
        self.all.style.color = 'white'
        self.all.style.backgroundColor = 'black'
        self.all.style.height = '100%'
        self.all.style.width = '100%'
        self.all.style.padding = 0
        self.all.style.margin = 0
        document.body.appendChild (self.all)
        
        self.dices = []
        
        for index in range (6):
            dice = document.createElement ('div')
            dice.normalColor = '#770000' if index < 3 else '#0000ff'
            dice.style.position = 'absolute'
            dice.style.backgroundColor = dice.normalColor
            dice.addEventListener ('touchstart', (lambda aDice: lambda: self.roll (aDice)) (dice))  # Returns inner lambda
            dice.addEventListener ('mousedown', (lambda aDice: lambda: self.roll (aDice)) (dice))
            self.dices.append (dice)
            self.all.appendChild (dice)
            
            dice.inner = document.createElement ('div')
            dice.inner.setAttribute ('unselectable', 'on')
            dice.inner.style.fontWeight = 'bold'
            dice.inner.style.textAlign = 'center'
            dice.inner.style.position = 'absolute'
            dice.inner.innerHTML = '?'
            dice.appendChild (dice.inner)
            
        self.banner = document.createElement ('div')
        self.banner.style.position = 'absolute'
        self.banner.style.cursor = 'pointer'
        self.banner.addEventListener ('touchstart', self.gotoTranscryptSite)
        self.banner.addEventListener ('mousedown', self.gotoTranscryptSite)
        self.banner.style.fontFamily = 'arial'
        self.banner.innerHTML = (
            '<span id="bannerLarge"><font color="777777">www.<b><i>' +
            '<font color="ff4422">T<font color="ffb000">r<font color="228822">a<font color="3366ff">n' +
            '<font color="ff4422">s<font color="ffb000">c<font color="228822">r<font color="3366ff">y<font color="ffb000">p<font color="228822">t' +
            '</i></b><font color="777777">.org<font size={}><font color="cccccc"></span>' +
            '<span id="bannerSmall"><i> Write your apps in Python for free!</i></span>'
        )
        self.all.appendChild (self.banner)

        self.bannerLarge = document.getElementById ('bannerLarge')
        self.bannerSmall = document.getElementById ('bannerSmall')
        
        self.audio = __new__ (Audio ('ios_app.mp3'))
        
        window.onresize = self.rightSize
        self.rightSize ()
        
    def gotoTranscryptSite (self):
        document.location.href = 'http://www.transcrypt.org'
        
    def roll (self, dice):
        frameIndex = 10

        self.audio.play ()
        
        def frame ():
            nonlocal frameIndex
            frameIndex -= 1
            
            dice.inner.innerHTML = random.randint (1, 6)

            if frameIndex:
                dice.style.color = random.choice (('red', 'green', 'blue', 'yellow'))
                setTimeout (frame, 100)
            else:
                dice.style.backgroundColor = dice.normalColor
                dice.style.color = 'white'
                
        frame ()
    
    def rightSize (self):
        self.pageWidth = window.innerWidth
        self.pageHeight = window.innerHeight
        portrait = self.pageHeight > self.pageWidth
        
        for index, dice in enumerate (self.dices):
            if self.pageHeight > self.pageWidth:    # Portrait
                dice.style.height = 0.3 * self.pageHeight
                dice.style.width = 0.4 * self.pageWidth
                dice.style.top = (0.03 + (index if index < 3 else index - 3) * 0.32) * self.pageHeight
                dice.style.left = (0.06 if index < 3 else 0.54) * self.pageWidth
                
                charBoxSide = 0.3 * self.pageHeight
                dice.inner.style.top = 0.15 * self.pageHeight - 0.6 * charBoxSide
                dice.inner.style.left = 0.2 * self.pageWidth - 0.5 * charBoxSide

                self.banner.style.top = 0.975 * self.pageHeight
                self.banner.style.left = 0.06 * self.pageWidth              
                self.bannerLarge.style.fontSize = 0.017 * self.pageHeight
                self.bannerSmall.style.fontSize = 0.014 * self.pageHeight
            else:                                   # Landscape
                dice.style.height = 0.4 * self.pageHeight
                dice.style.width = 0.3 * self.pageWidth
                dice.style.top = (0.06 if index < 3 else 0.54) * self.pageHeight
                dice.style.left = (0.03 + (index if index < 3 else index - 3) * 0.32) * self.pageWidth
                
                charBoxSide = 0.4 * self.pageHeight
                dice.inner.style.top = 0.2 * self.pageHeight - 0.6 * charBoxSide
                dice.inner.style.left = 0.15 * self.pageWidth - 0.5 * charBoxSide
                
                self.banner.style.top = 0.95 * self.pageHeight
                self.banner.style.left = 0.03 * self.pageWidth
                self.bannerLarge.style.fontSize = 0.015 * self.pageWidth
                self.bannerSmall.style.fontSize = 0.012 * self.pageWidth
                
            dice.inner.style.height = charBoxSide
            dice.inner.style.width = charBoxSide
            dice.inner.style.fontSize = charBoxSide
            
dice = Dice ()
ios_app.html
<html manifest="cache.manifest">
    <!-- v45 -->
    <head>
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="apple-mobile-web-app-status-bar-style" content="black">
        <meta name="apple-mobile-web-app-title" content="Dice">
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
        <link rel="apple-touch-icon" href="ios_app.png">
    </head>
    <body>
        <script src="__javascript__/ios_app.js"></script>
    </body> 
</html>
cache.manifest
CACHE MANIFEST

# v45

CACHE:
ios_app.html
ios_app.mp3
__javascript__/ios_app.js

N.B.1 Cache manifests have to be served with mime type text/cache-manifest.

N.B.2 For native behaviour, e.g. no visible address bar, the app must indeed be added to the home screen of your iOS device.

6.6. Example: D3.js

The D3.js graphics library offers animation by data driven DOM manipulation. It combines well with class based object oriented programming as supported by Trancrypt, leading to applications that are easy to understand and maintain.

d3js_demo.py
class Spawn:
    def __init__ (self, width, height):
        self.width, self.height, self.spacing = self.fill = width, height, 100, d3.scale.category20 ()

        self.svg = d3.select ('body'
        ) .append ('svg'
        ) .attr ('width', self.width
        ) .attr ('height', self.height
        ) .on ('mousemove', self.mousemove
        ) .on ('mousedown', self.mousedown)
        
        self.svg.append ('rect'
        ) .attr ('width', self.width
        ) .attr ('height', self.height)

        self.cursor = self.svg.append ('circle'
        ) .attr ('r', self.spacing
        ) .attr ('transform', 'translate ({}, {})' .format (self.width / 2, self.height / 2)
        ) .attr ('class', 'cursor')

        self.force = d3.layout.force (
        ) .size ([self.width, self.height]
        ) .nodes ([{}]
        ) .linkDistance (self.spacing
        ) .charge (-1000
        ) .on ('tick', self.tick)       

        self.nodes, self.links, self.node, self.link = self.force.nodes (), self.force.links (), self.svg.selectAll ('.node'), self.svg.selectAll ('.link')
        
        self.restart ()
        
    def mousemove (self):
        self.cursor.attr ('transform', 'translate (' + d3.mouse (self.svg.node ()) + ')')

    def mousedown (self):
        def pushLink (target):
            x, y = target.x - node.x, target.y - node.y
            if Math.sqrt (x * x + y * y) < self.spacing:
                spawn.links.push ({'source': node, 'target': target})
                
        point = d3.mouse (self.svg.node ())
        node = {'x': point [0], 'y': point [1]}
        self.nodes.push (node)
        self.nodes.forEach (pushLink)
        self.restart ()     
            
    def tick (self):
        self.link.attr ('x1', lambda d: d.source.x
        ) .attr ('y1', lambda d: d.source.y
        ) .attr ('x2', lambda d: d.target.x
        ) .attr ('y2', lambda d: d.target.y)
            
        self.node.attr ('cx', lambda d: d.x
        ) .attr ('cy', lambda d: d.y)

    def restart (self):
        self.link = self.link.data (self.links)
        
        self.link.enter (
        ) .insert ('line', '.node'
        ) .attr('class', 'link')

        self.node = self.node.data (self.nodes)
            
        self.node.enter (
        ) .insert ('circle', '.cursor'
        ) .attr ('class', 'node'
        ) .attr ('r', 7
        ) .call (self.force.drag)

        self.force.start ()

spawn = Spawn (window.innerWidth, window.innerHeight)
d3js_demo.mod.js
    (function () {
        var __name__ = '__main__';
        var Spawn = __class__ ('Spawn', [object], {
            __module__: __name__,
            get __init__ () {return __get__ (this, function (self, width, height) {
                var __left0__ = tuple ([width, height, 100, d3.scale.category20 ()]);
                self.width = __left0__ [0];
                self.height = __left0__ [1];
                self.spacing = __left0__ [2];
                self.fill = __left0__;
                self.svg = d3.select ('body').append ('svg').attr ('width', self.width).attr ('height', self.height).on ('mousemove', self.mousemove).on ('mousedown', self.mousedown);
                self.svg.append ('rect').attr ('width', self.width).attr ('height', self.height);
                self.cursor = self.svg.append ('circle').attr ('r', self.spacing).attr ('transform', 'translate ({}, {})'.format (self.width / 2, self.height / 2)).attr ('class', 'cursor');
                self.force = d3.layout.force ().size (list ([self.width, self.height])).nodes (list ([dict ({})])).linkDistance (self.spacing).charge (-(1000)).on ('tick', self.tick);
                var __left0__ = tuple ([self.force.nodes (), self.force.links (), self.svg.selectAll ('.node'), self.svg.selectAll ('.link')]);
                self.nodes = __left0__ [0];
                self.links = __left0__ [1];
                self.node = __left0__ [2];
                self.link = __left0__ [3];
                self.restart ();
            });},
            get mousemove () {return __get__ (this, function (self) {
                self.cursor.attr ('transform', ('translate (' + d3.mouse (self.svg.node ())) + ')');
            });},
            get mousedown () {return __get__ (this, function (self) {
                var pushLink = function (target) {
                    var __left0__ = tuple ([target.x - node.x, target.y - node.y]);
                    var x = __left0__ [0];
                    var y = __left0__ [1];
                    if (Math.sqrt (x * x + y * y) < self.spacing) {
                        spawn.links.push (dict ({'source': node, 'target': target}));
                    }
                };
                var point = d3.mouse (self.svg.node ());
                var node = dict ({'x': point [0], 'y': point [1]});
                self.nodes.push (node);
                self.nodes.forEach (pushLink);
                self.restart ();
            });},
            get tick () {return __get__ (this, function (self) {
                self.link.attr ('x1', (function __lambda__ (d) {
                    return d.source.x;
                })).attr ('y1', (function __lambda__ (d) {
                    return d.source.y;
                })).attr ('x2', (function __lambda__ (d) {
                    return d.target.x;
                })).attr ('y2', (function __lambda__ (d) {
                    return d.target.y;
                }));
                self.node.attr ('cx', (function __lambda__ (d) {
                    return d.x;
                })).attr ('cy', (function __lambda__ (d) {
                    return d.y;
                }));
            });},
            get restart () {return __get__ (this, function (self) {
                self.link = self.link.data (self.links);
                self.link.enter ().insert ('line', '.node').attr ('class', 'link');
                self.node = self.node.data (self.nodes);
                self.node.enter ().insert ('circle', '.cursor').attr ('class', 'node').attr ('r', 7).call (self.force.drag);
                self.force.start ();
            });}
        });
        var spawn = Spawn (window.innerWidth, window.innerHeight);
        __pragma__ ('<all>')
            __all__.Spawn = Spawn;
            __all__.__name__ = __name__;
            __all__.spawn = spawn;
        __pragma__ ('</all>')
    }) ();

6.7. Example: React

React is a JavaScript library for easy creation of interactive UI's. Changes to the UI are made by fast manipulation of a light-weight virtual DOM. The real DOM, which is much slower to manipulate, is then compared with the altered virtual DOM and updated efficiently in a minimum number of steps. This way of working leads to good performance, at the same time keeping a straightforward structure of application UI code, since the complexities of optimizing DOM updates are left to the React library. React is unintrusive and mixes well with Transcrypt, allowing creation of extensive web applications that combine maintainability with speed. This example once again clearly illustrates the philosophy behind Transcrypt: rather than confining you to a "parallel" universe that could never keep up, Transcrypt offers you direct access to the ever expanding universe of innovative JavaScript libraries.

react_demo.py
# Helper functions


def h(elm_type, props='', *args):
    return React.createElement(elm_type, props, *args)


def render(react_element, destination_id, callback=lambda: None):
    container = document.getElementById(destination_id)
    ReactDOM.render(react_element, container, callback)


# Create a component


Hello = React.createClass({
    'displayName': 'Hello',

    'getInitialState': lambda: {'counter': 0},

    'updateCounter': lambda: (this.setState({'counter': this.state['counter']+1})),

    'componentDidMount': lambda: (setInterval(this.updateCounter, 1000)),

    'render': lambda: h('div', {'className': 'maindiv'},
                          h('h1', None, 'Hello ', this.props['name']),
                          h('p', None, 'Lorem ipsum dolor sit ame.'),
                          h('p', None, 'Counter: ', this.state['counter'])
                        )
})


# Render the component in a 'container' div

element = React.createElement(Hello, {'name': 'React!'})
render(element, 'container')
react_demo.mod.js
    (function () {
        var __name__ = '__main__';
        var h = function (elm_type, props) {
            if (typeof props == 'undefined' || (props != null && props .hasOwnProperty ("__kwargtrans__"))) {;
                var props = '';
            };
            var args = tuple ([].slice.apply (arguments).slice (2));
            return React.createElement (elm_type, props, ...args);
        };
        var render = function (react_element, destination_id, callback) {
            if (typeof callback == 'undefined' || (callback != null && callback .hasOwnProperty ("__kwargtrans__"))) {;
                var callback = (function __lambda__ () {
                    return null;
                });
            };
            var container = document.getElementById (destination_id);
            ReactDOM.render (react_element, container, callback);
        };
        var Hello = React.createClass (dict ({'displayName': 'Hello', 'getInitialState': (function __lambda__ () {
            return dict ({'counter': 0});
        }), 'updateCounter': (function __lambda__ () {
            return this.setState (dict ({'counter': this.state ['counter'] + 1}));
        }), 'componentDidMount': (function __lambda__ () {
            return setInterval (this.updateCounter, 1000);
        }), 'render': (function __lambda__ () {
            return h ('div', dict ({'className': 'maindiv'}), h ('h1', null, 'Hello ', this.props ['name']), h ('p', null, 'Lorem ipsum dolor sit ame.'), h ('p', null, 'Counter: ', this.state ['counter']));
        })}));
        var element = React.createElement (Hello, dict ({'name': 'React!'}));
        render (element, 'container');
        __pragma__ ('<all>')
            __all__.Hello = Hello;
            __all__.__name__ = __name__;
            __all__.element = element;
            __all__.h = h;
            __all__.render = render;
        __pragma__ ('</all>')
    }) ();

6.8. Example: Riot

Riot is a UI framework that combines the use of custom tags and a virtual DOM, much like the one in React. Custom tags look like ordinary HTML tags, but whereas HTML tags only define structure, Riot tags define structure, style and behaviour. Custom tags are compiled to JavaScript by a compiler that comes with Riot. With these custom tags as reusable components, web pages can be built. Riot itself is tiny, and the virtual DOM allows for fast adaptation of page content.

riot_demo.html
<!DOCTYPE html>
<html>

  <head>
    <meta charset="UTF-8" />
    <title>Hello PyRiot ;-)</title>
    <!-- for transcrypted version we do not need the compiler.
         for the classic version you can use riot+compiler -->
    <script src="https://cdn.jsdelivr.net/riot/2.5/riot.js"></script>
    <style>
        body {font-family:arial;font-size:30px;padding:50px;}
        h1 {font-size:50px;color:#0000ff;}
    </style>
  </head>

  <body>

  <!-- classic riot (compiled on server, could be done in browser)         -->
        <sample id="s1"></sample>
        <script src="tags/sample.js"></script>
        <script>riot.mount('sample')</script>


  <!-- transcrypted version (transpiled on server, using python)           -->
        <sample2 id='s2' label="nr1"></sample2>
        <mp2     id='s3' label="nr2"></mp2>
        <!-- the transpiled tag's js -->
        <script src="__javascript__/riot_demo.js"></script>
        <script>
            // register
            var cls = riot_demo.Sample2
            riot.tag2('sample2', cls.template, cls.style, '', function(opts) {
                new cls(this, opts)})
            document.t1 = riot.mount('sample2')[0].update()
            document.t2 = riot.mount('#s3', 'sample2')[0]
            document.t2.update()
        </script>
  </body>
</html>
sample.tag, a classic Riot tag
// vim: ft=html
<sample>

    <h1>Riot Native Tag</h1>

    <h5 each="{lv}">name: {name} - counter: {this.count()}</h5>

    <script>
        this.counter = 0

        // riot's synctactic sugar:
        count() { this.counter += 1; return this.counter }

        // to test stuff on console:
        window.native_tag = this

        this.on('update', function() {
            this.lv = [{name: 'n1'}, {'name': 'n2'}]
            this.update()
        })
    </script>
    <style scoped>
    h1 {color: red}
    </style>

</sample>
sample.js, compiled by Riot
// vim: ft=html
riot.tag2('sample', '<h1>Riot Native Tag</h1> <h5 each="{lv}">name: {name} - counter: {this.count()}</h5>', 
'sample h1,[riot-tag="sample"] h1,[data-is="sample"] h1{color: red}', '', function(opts) {
        this.counter = 0

        this.count = function() { this.counter += 1; return this.counter }.bind(this)

        window.native_tag = this

        this.on('update', function() {
            this.lv = [{name: 'n1'}, {'name': 'n2'}]
            this.update()
        })
});
riot_tag.py, baseclass of all Transcrypt Riot tags
# Parent Class for a Transcrypt Riot Tag
#
# This binds the namespace of a riot tag at before-mount event 100% to that of
# the a transcrypt instance, except members which begin with an underscore, those
# are private to the transcrypt object.
#
# The 4 riot lifecycle events are bound to overwritable python functions.
#
# Immutables (strings, ints, ...) are bound to property functions within the tag,
# so the templates work, based on state in the transcrypt tag.
# State can be changed in the riot tag as well but take care to not create new
# references - you won't find them in the Transcrypt tag.
#
#
# Best Practices:
#  - mutate state only in the transcrypt tag.
#  - declare all variables so they are bound into the riot tag
#  - IF you declare new variables to be used in templates, run
#    self.bind_vars(self.riot_tag)
# TODO: docstring format not accepted by the transpiler, strange.

__author__ = "Gunther Klessinger, gk@axiros.com, Germany"

# just a minihack to get some colors, mainly to test lamdas and imports:
from color import colors, cprint as col_print
c = colors
M, I, L, R, B = c['purple'], c['orange'], c['gray'], c['red'], c['black']

lifecycle_ev = ['before-mount', 'mount', 'update', 'unmount']

cur_tag_col = 0
class RiotTag:
    """
    taking care for extending the riot tag obj with
    functions and immutable(!) properties of derivations of us
    See counter.
    """
    debug = None
    # placeholders:
    template = '<h1>it worx</h1>'
    style = ''
    node_name = 'unmounted'
    opts = None
    def __init__(self, tag, opts):
        # opts into the python instance, why not:
        self.opts = opts
        self._setup_tag(tag)
        # giving ourselves a unique color:
        global cur_tag_col # working (!)
        cur_tag_col = (cur_tag_col + 1) % len(colors)
        # TODO values() on a dict
        self.my_col = colors.items()[cur_tag_col][1]

    def _setup_tag(self, tag):
        # keeping mutual refs
        tag.py_obj = self
        self.riot_tag = tag
        # making the event system call self's methods:
        handlers = {}
        for ev in lifecycle_ev:
            f = getattr(self, ev.replace('-', '_'))
            if f:
                # this.on('mount', function() {...}):
                # whats nicer?
                tag.on(ev, f)

    def pp(self, *msg):
        # color flash in the console. one color per tag instance.
        col_print(
            #B(self.riot_tag._riot_id),
            L('<', self.my_col(self.node_name, self.my_col), '/> '),
            M(' '.join([s for s in msg])))

    def _lifecycle_ev(self, mode):
        if self.debug:
            self.pp(mode + 'ing')

    # overwrite these for your specific one:
    def update (self): self._lifecycle_ev('update')
    def mount  (self): self._lifecycle_ev('mount')
    def unmount(self): self._lifecycle_ev('unmount')

    def before_mount(self):
        self._lifecycle_ev('before-mount')
        return self.bind_vars()

    def bind_vars(self):
        tag = self.riot_tag
        self.node_name = tag.root.nodeName.lower()
        self.debug and self.pp('binding vars')
        # binding self's functions into the tag instance
        # binding writable properties to everything else (e.g. ints, strs...)
        tag._immutables = im = []
        lc = lifecycle_ev
        for k in dir(self):
            # private or lifecycle function? don't bind:
            if k[0] == '_' or k in lifecycle_ev or k == 'before_mount':
                continue
            v = getattr(self, k)
            # these I can't write in python. Lets use JS then.
            # TODO there should be, maybe some mocking facility for code
            # testing w/o a js runtime:
            __pragma__('js', '{}', '''
                  typeof v === "function" || typeof v === "object" ?
                  tag[k] = self[k] : tag._immutables.push(k)''')

        __pragma__('js', '{}', '''
        var i = tag._immutables, py = self
        i.forEach(function(k, j, i) {
            Object.defineProperty(tag, k, {
                get: function()  { return self[k]},
                set: function(v) { self[k] = v }
            })
        })''')

riot_tag.mod.js, compiled by Transcrypt
    __nest__ (
        __all__,
        'riot_tag', {
            __all__: {
                __inited__: false,
                __init__: function (__all__) {
                    var __name__ = 'riot_tag';
                    var __author__ = 'Gunther Klessinger, gk@axiros.com, Germany';
                    var colors = __init__ (__world__.color).colors;
                    var col_print = __init__ (__world__.color).cprint;
                    var c = colors;
                    var __left0__ = tuple ([c ['purple'], c ['orange'], c ['gray'], c ['red'], c ['black']]);
                    var M = __left0__ [0];
                    var I = __left0__ [1];
                    var L = __left0__ [2];
                    var R = __left0__ [3];
                    var B = __left0__ [4];
                    var lifecycle_ev = list (['before-mount', 'mount', 'update', 'unmount']);
                    var cur_tag_col = 0;
                    var RiotTag = __class__ ('RiotTag', [object], {
                        __module__: __name__,
                        debug: null,
                        template: '<h1>it worx</h1>',
                        style: '',
                        node_name: 'unmounted',
                        opts: null,
                        get __init__ () {return __get__ (this, function (self, tag, opts) {
                            self.opts = opts;
                            self._setup_tag (tag);
                            cur_tag_col = __mod__ (cur_tag_col + 1, len (colors));
                            self.my_col = colors.py_items () [cur_tag_col] [1];
                        });},
                        get _setup_tag () {return __get__ (this, function (self, tag) {
                            tag.py_obj = self;
                            self.riot_tag = tag;
                            var handlers = dict ({});
                            for (var ev of lifecycle_ev) {
                                var f = getattr (self, ev.py_replace ('-', '_'));
                                if (f) {
                                    tag.on (ev, f);
                                }
                            }
                        });},
                        get pp () {return __get__ (this, function (self) {
                            var msg = tuple ([].slice.apply (arguments).slice (1));
                            col_print (L ('<', self.my_col (self.node_name, self.my_col), '/> '), M (' '.join ((function () {
                                var __accu0__ = [];
                                for (var s of msg) {
                                    __accu0__.append (s);
                                }
                                return __accu0__;
                            }) ())));
                        });},
                        get _lifecycle_ev () {return __get__ (this, function (self, mode) {
                            if (self.debug) {
                                self.pp (mode + 'ing');
                            }
                        });},
                        get py_update () {return __get__ (this, function (self) {
                            self._lifecycle_ev ('update');
                        });},
                        get mount () {return __get__ (this, function (self) {
                            self._lifecycle_ev ('mount');
                        });},
                        get unmount () {return __get__ (this, function (self) {
                            self._lifecycle_ev ('unmount');
                        });},
                        get before_mount () {return __get__ (this, function (self) {
                            self._lifecycle_ev ('before-mount');
                            return self.bind_vars ();
                        });},
                        get bind_vars () {return __get__ (this, function (self) {
                            var tag = self.riot_tag;
                            self.node_name = tag.root.nodeName.lower ();
                            self.debug && self.pp ('binding vars');
                            var __left0__ = list ([]);
                            tag._immutables = __left0__;
                            var im = __left0__;
                            var lc = lifecycle_ev;
                            for (var k of dir (self)) {
                                if (k [0] == '_' || __in__ (k, lifecycle_ev) || k == 'before_mount') {
                                    continue;
                                }
                                var v = getattr (self, k);
                                
                                                  typeof v === "function" || typeof v === "object" ?
                                                  tag[k] = self[k] : tag._immutables.push(k)
                            }
                            
                                    var i = tag._immutables, py = self
                                    i.forEach(function(k, j, i) {
                                        Object.defineProperty(tag, k, {
                                            get: function()  { return self[k]},
                                            set: function(v) { self[k] = v }
                                        })
                                    })
                        });}
                    });
                    __pragma__ ('<use>' +
                        'color' +
                    '</use>')
                    __pragma__ ('<all>')
                        __all__.B = B;
                        __all__.I = I;
                        __all__.L = L;
                        __all__.M = M;
                        __all__.R = R;
                        __all__.RiotTag = RiotTag;
                        __all__.__author__ = __author__;
                        __all__.__name__ = __name__;
                        __all__.c = c;
                        __all__.col_print = col_print;
                        __all__.colors = colors;
                        __all__.cur_tag_col = cur_tag_col;
                        __all__.lifecycle_ev = lifecycle_ev;
                    __pragma__ ('</all>')
                }
            }
        }
    );
riot_demo.py, a derived Transcrypt Riot tag class
# an example user tag, using RiotTag

from riot_tag import RiotTag

class P(RiotTag):
    debug = 1
    # never do mutables on class level. this is just to check if transpiler
    # creates the same behaviour - and it does, a second tag instance gets
    # the same lv object:
    lv = [{'name': 'n0'}]
    # immuatble on class level. does a second instance start at 1?
    # answer: yes, perfect:
    counter = 1

    template = ''' <div><h1>Riot Transcrypt Tag Instance {label}</h1>
                         <div>INNER</div></div> '''
    def count_up(self):
        self.counter = self.counter + 1
        self.pp('counter:', self.counter, 'len lv:', len(self.lv), 'adding one lv' )
        self.lv.append({'name': 'n' + self.counter})
        return self.counter

# try some inheritance...
class Sample2(P):
    # ... and change the state at every update, just for fun:
    template = P.template.replace('INNER', '''
    <div>
    <h5 each="{lv}">name: {name} - counter: {count_up()}</h5>
    </div>
    ''')

    # no scoped styles currently
    style = '''sample2 h5 {color: green}'''


    def __init__(self, tag, opts):
        self.label = opts.label.capitalize()  # this rocks so much.
        # alternative to super:
        RiotTag.__init__(self, tag, opts)
        # uncomment next line and chrome will stop:
        # debugger
        self.pp('tag init', 'adding 2 lv')
        # mutating the lv object:
        self.lv.extend([{'name': 'n1'}, {'name': 'n2'}])


    def update(self):
        self.pp('update handler in the custom tag, calling super')
        RiotTag.update(self)

riot_demo.mod.js, compiled by Transcrypt
    (function () {
        var __name__ = '__main__';
        var RiotTag = __init__ (__world__.riot_tag).RiotTag;
        var P = __class__ ('P', [RiotTag], {
            __module__: __name__,
            debug: 1,
            lv: list ([dict ({'name': 'n0'})]),
            counter: 1,
            template: ' <div><h1>Riot Transcrypt Tag Instance {label}</h1>\n                         <div>INNER</div></div> ',
            get count_up () {return __get__ (this, function (self) {
                self.counter = self.counter + 1;
                self.pp ('counter:', self.counter, 'len lv:', len (self.lv), 'adding one lv');
                self.lv.append (dict ({'name': 'n' + self.counter}));
                return self.counter;
            });}
        });
        var Sample2 = __class__ ('Sample2', [P], {
            __module__: __name__,
            template: P.template.py_replace ('INNER', '\n    <div>\n    <h5 each="{lv}">name: {name} - counter: {count_up()}</h5>\n    </div>\n    '),
            style: 'sample2 h5 {color: green}',
            get __init__ () {return __get__ (this, function (self, tag, opts) {
                self.label = opts.label.capitalize ();
                RiotTag.__init__ (self, tag, opts);
                self.pp ('tag init', 'adding 2 lv');
                self.lv.extend (list ([dict ({'name': 'n1'}), dict ({'name': 'n2'})]));
            });},
            get py_update () {return __get__ (this, function (self) {
                self.pp ('update handler in the custom tag, calling super');
                RiotTag.py_update (self);
            });}
        });
        __pragma__ ('<use>' +
            'riot_tag' +
        '</use>')
        __pragma__ ('<all>')
            __all__.P = P;
            __all__.RiotTag = RiotTag;
            __all__.Sample2 = Sample2;
            __all__.__name__ = __name__;
        __pragma__ ('</all>')
    }) ();

6.9. Example: Using input and print in a DOM __terminal__ element in your browser

Without special measures, Transcrypt's print function prints to the debugging console. However if there's an element with id __terminal__ in your DOM tree, the print function prints to this element. Moreover, the input function also prints its prompt message to the terminal element. Input is collected using a dialog box and echoed to the terminal element.

This means that you can write applications with blocking I/O, rather than event driven behaviour, e.g. for simple activities or, since they are intuitively easy to comprehend, for educational purposes.

terminal_demo.html
<html>
    <head>
        <script src="__javascript__/terminal_demo.js"></script>
    </head>
    <body>
        Refresh browser window to restart
    </body>
</html>
terminal_demo.py
while True:
    name = input ('What''s your name? (leave blank to quit)')
    
    if name == '':
        break;
    
    print ('Hi', name, 'I am your computer.')

    age = float (input ('How old are you? '))
    if age < 18:
        print ('Sorry', name, ',', age, 'is too young to drive a car in the Netherlands.')
    else:
        print ('OK', name, ',', age, 'is old enough to drive a car in the Netherlands.')
        
    print ()