For a bit more elaborated, free-text explaination of work / thoughts process, and reasons behind core decisions. see below.
# From project root
./gradlew install # will place application distributive in `editor/build/install/editor`.
#Application should be started from it's distributive folder
cd editor/build/install/editor
./bin/editor
- (CMD/Ctrl)+O - open file.
- (CMD/Ctrl)+S - save current file.
- (CMD/Ctrl)+W - close current file w/o saving (open new file).
- (CMD/Ctrl)+Q - quit editor application.
- (CMD/Ctrl)+Enter - run JetLang program.
- (CMD/Ctrl)+T - toggle slow mode (adds 100 ms delay to every step of JetLang program). Active only on the next run.
- (CMD/Ctrl)+T - toggle interactive mode. In this mode programm is executed as soon as it's updated. Any execution running before will be cancelled. This mode is on by default.
- (CMD/Ctrl)+I - toggle display of threads in out panel. Active only on the next run.
- (CMD/Ctrl)+J - toggle between "stack" or "JVM bytecode" based compiler. Active only on the next run.
- Esc - stops currently running JetLang program.
- All files are handled in
UTF-8
. - Only one instance of JetLang program can be running at any time. (but multiple highlight tasks can be run in parallel).
map
andreduce
are elements of a language itself (and not a functions of standard library)map
andreduce
will be executed in parallel if number of their elements is bigger than 1000. It'll use ForkJoin pool with 4 threads in it.- All integer operations are performed with
java.lang.Integer
, all floating-point calculations - injava.lang.Double
. 1 / 2
will yield0
, as it's an integer division.1.0 / 2
or1 / 2.0
will give0.5
.config.json
shall be valid or non-existent.- Distributive must be started from it's root folder. (just because config file is there). If you run it from IDEA, specify a working directory
editor/src/dist/
. - JVM bytecode compiler is added as a pure experiment, and it's not pretending to be complete. Error reporting there is lacking (there are no line numbers, for example). Also there is no parallel execution (although will be easy to add).
core
project - jetLang compiler and runtime.
editor
project - editor for jetLang. Knows about core
.
Architecture layers in order of ascending visibility. i.e. core
knows nothing. ui
knows about services
and core
, but don't know about editor
.
- core (Lexer, Parser, Runtime)
- editor service level: com.xseagullx.jetlang.services (ActionManager, HighlightingService, RunService, StyleManager, TaskManager)
- editor ui level: com.xseagullx.jetlang.ui (EditPanel, OutPanel, MiscPanel, FileManagingComponent)
- editor main classes (Editor)
- Project setup. I've gradle and maven experience, but gradle is way less verbose, and have a wrapper.
- Java 8. I decided, that we don't need Java 7 support. Java 8 language features make me write code faster and more comprehensible.
- Parsing. Parsing programming language is a step-by-step process, and it consists of lexer -> parser -> AST -> instruction-selection. It can be more complex, but I'll keep it simple here. For first two steps antlr4 was an obvious choice for me. It gives me almost instant results for first to steps, transforming text to CST. I know it as I was rewriting groovy grammar to it, as my GSoC project (3 years ago). It allows me to have parser quickly, iterate over it. Also it has an IntelliJ IDEA plugin.
- AST. I've opted out from building AST, as it feels a lot like an over-engineering for this project. I'm building both runtimes (stack-based and bytecode) from CST. I also can use CST for edit-r-specific highlighting (like matching pair braces / parenthesises). AST is cool for transformations. I'm a big fan of groovy AST transformations, and wrote couple of them before, but I feel, like introducing AST here will lead to unnecessary bloated code.
- I went for stack based runtime first. It was easy to implement, I had no prior experience with
asm
and java bytecode generation, so I went for low-hungin fruit first. Make it work, you know. That runtime has an entry point, which is receiving so-called ExecutionContext. It's basically my thread model (it has frames, and local variables). It also can allow nice things, like REPL - style execution, when context of a program is preserved between calls. - I've got a decently working grammar and stack-based runtime in ~5 hours.
- Then I postponed work on
core
module and started to work on editors. I wanted to evaluate existing text editor components. I went for vanilla swing, as it was enough for my goals, as I know it (although last time I was working on desktop GUI app was 3-4 years ago). There are some gotcha's of Swing you have to take care, but otherwise it's a pretty solid choice for such a simple app. IMHO. Spent some time, to prove, that I can write an editor myself, without external libraries and without tons of auxiliary code. - Then it was a sequence of MakeItWork - MakeItRights phases for editor, when I was putting it all together, and then refactoring, to split it up. I end up getting base services extracted: TaskManager, styles manager, etc, sometimes using each other, and my UI using services, and communicating with them via callbacks.
- I haven't tried to make my UI components thread safe, I forces system accessing it to care of it.
- For parallel map / reduce, the most obvious solution:
parallelStream
was ditched out, as it is not flexible. It uses common fork-join pool, and I wanted more control over execution. I prototyped ForkJoin and java5 ExecutorService solutions formap
, but kept the former one, as it was a bit more comprehensible. - Had some fun with cancellation of task and exception propagation. For every thread I'm copying an ExecutionContext, thus code in map/reduce work same way, as if my context was sequential.
- My programs are running in separate thread (which can spawn other threads, if map/reduce operations are parallelised), but I can run only one instance of programm at given time. It's just because I didn't want to create many tabs for editor output panel. On the other hand, my highlighter can be run in parallel as many times as needed, to provide fastest possible response time to editor. Sometimes, highlighter results can be discarded: if there were changes in program text since highlighter task was started, so there should be no inconsistency in UI.
- Last
ast
-based bytecode runtime is technically an experiment, I've done for fun. I wanted to try it out. It was planned from the very beginning, that's whyProgram
is an interface, andStackBasedProgram
was extending it from very beginning, although I've done some refactoring anf unification, when my program starts to "compile" to 2 different runtimes.