In this Engineering Notebook post I'll summarize the work so far on
converting Leo's core sources to typescript. This work supports Félix's leojs
<https://github.com/boltex/leojs>project.
*Speed and accuracy*
>From the very beginning I knew that the python-to-typescript command
(script) would save lots of time. I estimated that an hour's work on the
script would be worthwhile if it saved just a minute of time massaging the
resulting .ts file.
Aha! Just now I see that the script is worth *any* amount of time because
the script eliminates potential editing errors!
*Iterative workflow*
The momentum of the project is increasing. I convert a file (leoAtFile.py)
to leoAtFile.ts, copy the file to the leojs repo, and use *vs-code to
report errors*. I may correct some errors by hand, just to make sure that I
understand what is going on, but all such corrections are ephemeral. I go
back to Leo and improve python-to-typescript and repeat the process!
*@button cvt-at-file*
The following script allows me to change the python-to-typescript command
without reloading Leo:
g.cls()
import importlib
import leo.commands.convertCommands as convertCommands
importlib.reload(convertCommands)
h = '@file leoAtFile.py'
root = g.findNodeAnywhere(c, h)
assert root, h
x = convertCommands.ConvertCommandsClass(c).PythonToTypescript(c,
alias='at')
x.convert(root)
This @button node is customized to convert leoAtFile.py, so all I need to
do is <Alt-x>cv<tab><return> Doing converts the file and puts the result as
the last top-level node of my outline.
Note: The @button script should *not* use c.convertCommands, because that a
reload wouldn't change the already-created object. Instead, the @buttons
script instantiates a *new *ConvertCommandsClass object, which in effect
reloads the PythonToTypescript class. This is an important technique for
Leo's devs to know.
*The main loop*
I consider myself fortunate to have "stumbled" upon the overall structure
of py2ts.convert_body. This method is the heart of the command. You could
say the main loop in this method was a "lucky guess". I won't show you the
code, but it's worth a look.
The general idea is to split p.b into *a mutable list of lines*, and then
apply regex's, one by one, to each line until *one *pattern matches. A
*dispatch
table *contains a list of (regex, handler) tuples. The order of tuples in
the dispatch table sometimes matters.
The main loop calls the corresponding handler when a regex matches. Each
handle *alters* the list of lines and returns the index of the next line to
be considered.
I got lucky with this organization because each handler turned out to be
incredibly elegant! Here is the handler for 'if' statements:
if_pat = re.compile(r'^([ \t]*)if[ \t]+(.*?):(.*?)\n')
def do_if(self, i, lines, m, p):
j = self.find_indented_block(i, lines, m, p)
lws, cond, tail = m.group(1), m.group(2).strip(), m.group(3).strip()
cond_s = cond if cond.startswith('(') else f"({cond})"
tail_s = f" // {tail}" if tail else ''
lines[i] = f"{lws}if {cond_s} {{{tail_s}\n"
lines.insert(j, f"{lws}}}\n")
return i + 1 # Advance.
The find_indented_block method returns the index into lines of the end of
the if block. The handler changes the "if line" in place and inserts a line
containing an '}' (properly indented!) at the end of the block. The
dispatch table has an entry:
(self.if_pat, self, do_if)
As you can see it's more convenient to define "if_pat" in the node that
defines the do_if handler.
*The simplest organization possible*
convert_body applies various *global regexes* to p.b *before *running the
main loop. These regexes use the re.MULTILINE flag to apply a pattern to
each line of p.b.
The question arises, would it be simpler to use multiline regexes
everywhere? The short answer is, "No!":
1. Although testing the dispatch dict for each line of p.b would seem to be
slow, speed doesn't matter. The command works almost instantaneously.
2. As shown above, each handler is super elegant. For example, it's trivial
to insert closing brackets into the list of lines. Using, say, re.sub would
be significantly more complicated.
*Summary*
Leo's evolving python-to-typescript command is my entry into the typescript
world.
This command is worth any amount of work. It eliminates tedious
(error-prone!) manual text munging.
I feed leoAtFile.ts (the results of the @button script) into vs-code, which
then points out all the remaining problems. I use those problems to improve
the script. I'll repeat this process until any remaining problems would be
too difficult to fix in a script.
This whole process is driving my learning of typescript. My energy levels
are off the charts!
Edward
--
You received this message because you are subscribed to the Google Groups
"leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/leo-editor/cd145a9e-ea06-4f9a-8a2e-b1b10153cbd9n%40googlegroups.com.