Compare commits

..

8 Commits

Author SHA1 Message Date
ecfd80f36a fix: typescript transpiler misbehaving 2025-05-22 12:08:41 +03:00
a1a2293af4 fixing gradle (*again*) 2025-05-22 11:35:04 +03:00
6e9250ffd1 minor cleanup 2025-05-22 11:34:29 +03:00
45292990b1 some thesis fixes 2025-01-29 14:12:57 +02:00
2dcfff689a add page numbers to thesis 2025-01-28 14:33:34 +02:00
2619e50e9b fix typos in thesis 2025-01-28 13:25:09 +02:00
4bfda6b0a1 bump
All checks were successful
tagged-release / Tagged Release (push) Successful in 5m38s
2025-01-28 13:11:29 +02:00
58d6110e1d fix: stack overflow!! 2025-01-28 13:10:58 +02:00
21 changed files with 250 additions and 88 deletions

View File

@@ -1,5 +1,5 @@
plugins {
id("common");
id("j2s-common");
}
java {

View File

@@ -22,26 +22,28 @@ dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher");
}
if (System.getenv("REPO_URL") != null) {
publishing {
repositories {
maven {
name = "Gitea";
url = uri(System.getenv("REPO_URL") ?: "");
publishing {
repositories {
maven {
name = "Gitea";
url = uri(System.getenv("REPO_URL") ?: "");
credentials(HttpHeaderCredentials::class) {
name = "Authorization";
value = "token ${System.getenv("ACCESS_TOKEN")}";
}
credentials(HttpHeaderCredentials::class) {
name = "Authorization";
value = "token ${System.getenv("ACCESS_TOKEN")}";
}
authentication {
create<HttpHeaderAuthentication>("header");
authentication {
create<HttpHeaderAuthentication>("header");
}
}
}
}
publications {
create<MavenPublication>("maven") {
from(components["java"]);
publications {
create<MavenPublication>("maven") {
from(components["java"]);
}
}
}
}

View File

@@ -1,5 +1,5 @@
plugins {
id("common-java");
id("j2s-common-java");
}
description = "A collection of utils and structures for the rest of the project";

View File

@@ -6,55 +6,99 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
/**
* The class, used to keep track of important environment-wide values
* Supports an inheritance mechanism via parents
* In essence a simple hashmap of flag objects to values
*/
public class Environment {
/**
* The parent of this environment. Will act as a "fallback" when searching for elements.
* Operations this environment won't write into the parent
*/
public final Environment parent;
private final Map<Key<Object>, Object> map = new HashMap<>();
private final Set<Key<Object>> hidden = new HashSet<>();
/**
* Gets the element, contained in this environment, signified by the given key
* @return The element, or null if not found
*/
@SuppressWarnings("unchecked")
public <T> T get(Key<T> key) {
if (map.containsKey(key)) return (T)map.get(key);
else if (!hidden.contains(key) && parent != null) return parent.get(key);
else return null;
}
/**
* Checks if the environment has the given key
*/
public boolean has(Key<?> key) {
if (map.containsKey(key)) return true;
else if (!hidden.contains(key) && parent != null) return parent.has(key);
else return false;
}
/**
* Checks if the environment has the given key and if the value behind they key is not null
*/
public boolean hasNotNull(Key<?> key) {
return get(key) != null;
}
/**
* Gets the element, contained in this environment, signified by the given key
* @param defaultVal The value to return if the element is not found
* @return The element, or "defaultVal" if not found
*/
public <T> T get(Key<T> key, T defaultVal) {
if (has(key)) return get(key);
else return defaultVal;
}
/**
* Gets the element, contained in this environment, signified by the given key
* @param defaultVal The supplier, from which to return if the element is not found
* @return The element, or the result of "defaultVal" if not found
*/
public <T> T getWith(Key<T> key, Supplier<T> defaultVal) {
if (has(key)) return get(key);
else return defaultVal.get();
}
/**
* Inserts the given value for the given key, replacing any existing value
* If a parent has a value with the same key, it isn't replaced, but instead - shadowed
* @return The inserted element
*/
@SuppressWarnings("unchecked")
public <T> Environment add(Key<T> key, T val) {
map.put((Key<Object>)key, val);
hidden.remove(key);
return this;
}
/**
* Adds the flag key to the environment
* @return The environment instance
*/
public Environment add(Key<Void> key) {
return add(key, null);
}
/**
* Executes Environment.add for each pair of the map
* @return The environment instance
*/
@SuppressWarnings("all")
public Environment addAll(Map<Key<?>, ?> map, boolean iterableAsMulti) {
public Environment addAll(Map<Key<?>, ?> map) {
map.putAll((Map)map);
hidden.removeAll(map.keySet());
return this;
}
public Environment addAll(Map<Key<?>, ?> map) {
return addAll(map, true);
}
/**
* Removes the given key from the environment.
* If a parent has the given key, it is instead just "hidden" by this environment
* @return The environment instance
*/
@SuppressWarnings("unchecked")
public Environment remove(Key<?> key) {
map.remove(key);
@@ -62,6 +106,9 @@ public class Environment {
return this;
}
/**
* If the key exists in the environment, returns it. Otherwise, puts the given value and returns the value
*/
public <T> T init(Key<T> key, T val) {
if (!has(key)) {
this.add(key, val);
@@ -69,6 +116,9 @@ public class Environment {
}
else return get(key);
}
/**
* If the key exists in the environment, returns it. Otherwise, puts the given value from the supplier and returns it
*/
public <T> T initFrom(Key<T> key, Supplier<T> val) {
if (!has(key)) {
var res = val.get();
@@ -78,6 +128,10 @@ public class Environment {
else return get(key);
}
/**
* Creates an environment that is a child of this environment
* @return
*/
public Environment child() {
return new Environment(this);
}
@@ -85,15 +139,25 @@ public class Environment {
public Environment(Environment parent) {
this.parent = parent;
}
/**
* Creates an empty environment
*/
public Environment() {
this.parent = null;
}
/**
* If the environment is null, returns an empty environment
* Otherwise, returns the given value
*/
public static Environment wrap(Environment env) {
if (env == null) return empty();
else return env;
}
/**
* Returns a newly-created environment
*/
public static Environment empty() {
return new Environment();
}

View File

@@ -2,8 +2,17 @@ package me.topchetoeu.j2s.common;
import java.io.File;
/**
* The class that represents all filenames in J2S
*/
public class Filename {
/**
* The protocol of the filename (file://, http://, ftp://, etc...)
*/
public final String protocol;
/**
* The path to the file (/home/me/test.js, example.org/test.js, etc...)
*/
public final String path;
@Override public String toString() {
@@ -42,11 +51,18 @@ public class Filename {
this.path = path;
}
/**
* Parses the given string to a filename.
* If a :// is not found, the protocol will default to "file"
*/
public static Filename parse(String val) {
var i = val.indexOf("://");
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
else return new Filename("file", val.trim());
}
/**
* Will convert the File instance to a filename, with the protocol set to "file"
*/
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}

View File

@@ -79,9 +79,9 @@ public class Reading {
if (bytes == null) return null;
else return new String(bytes);
}
public static InputStream resourceToStream(String name) {
return Reading.class.getResourceAsStream("/" + name);
return Reading.class.getResourceAsStream("/" + name.replaceAll("//", "/"));
}
public static String resourceToString(String name) {
return streamToString(resourceToStream(name));

View File

@@ -1,5 +1,5 @@
plugins {
id("common-java");
id("j2s-common-java");
}
description = "A compiler of EcmaScript 5 code to J2S bytecode";

View File

@@ -20,6 +20,10 @@ emit {
target = "res.html",
template {
template = "template.html",
year = os.date "%Y",
prev_year = os.date "%Y" - 1,
config,
profession(config.profession),
build {
"requirements.md",
@@ -34,12 +38,5 @@ emit {
chapter_format_plain = "Глава %s: ",
},
},
profession(config.profession),
year = os.date "%Y",
prev_year = os.date "%Y" - 1,
config,
},
}

View File

@@ -3,13 +3,21 @@ title: Дипломна работа
---
# Увод {.nonum}
Един от най-важните принципи на програмирането е този на разширимостта. Този принцип повелява, че софтуерът трябва да изграждан по начин, по който може неговата функционалност да бъде разширена без вече написаният софтуер да бъде променян. Това е добре известен проблем в сферата на софтуерното инженерство, като едно от най-ефективните решения е интегрирането на втори помощен език, чрез който потребителя да може да разширява функционалността на оригиналният продукт.
Един от най-важните принципи на програмирането е този на разширимостта. Този принцип повелява, че софтуерът трябва да бъде изграждан по начин, по който може неговата функционалност да бъде разширена, без вече написаният софтуер да бъде променян. Това е добре известен проблем в сферата на софтуерното инженерство.
Но защо разширимостта е толкова важна? Това се дължи на факта, че често потребителите на софтуерните продукти искат да разширят функционалността на продукта. Традиционно, разширяването може да стане само като потребителя се свърже с разработчика на продукта и да помоли за добавянето на функционалността. Това обаче е не само непрактично, но и води до прекомерно нарастване на софтуера, което го прави сложен за поддръжка и използване.
Но защо разширимостта е толкова важна? Често потребителите на софтуерните продукти искат да разширят и персонализират функционалността на продукта. Традиционно, разширяването става само чрез модификация на кода на софтуера. Това обаче е не само непрактично, но и води до прекомерно нарастване на обема на софтуера, което го прави сложен за поддръжка. Едно добро решение на този проблем е внедряването на втори помощен език, който да служи за допълването на функционалността на продукта, без неговата промяна.
Въвеждането на скриптови езици като помощни механизми за разширяване на функционалността предлага значително по-ефективно решение. Езиците като Python, Lua и JavaScript могат да бъдат използвани за добавяне на нова функционалност, без да се променя основният код на приложението, като по този начин се осигурява гъвкавост на софтуерния продукт. JavaScript, в частност, е един от най-широко използваните езици в съвременната разработка, поради своята популярност, която се дължи на употребата на езика в уеб технологиите.
Езици като Python, Lua и JavaScript са едни от най-често вгражданите езици, като има безброй такива примери:
Целта на тази дипломна работа е да създаде лесно вградим, малък на размер интерпретатор на „EcmaScript 5.1“ езика (по-добре познат като JavaScript, както ще се и нарича оттук нататък) за продукти, написани на езикът „Java“.
- Roblox, платформа за създаване на игри, внедрява модифицирана версия на Lua
- Nginx, софтуер за интернет сървъри, внедрява JavaScript
- WirePlumber, софтуер за контролиране на аудиото на линукс машини, използва Lua като основен език за конфигурация
- Vim, конзолен текстов редактор, внедрява Lua като език за разширения
- Blender, софтуер за създаване на 3D модели, внедрява Python като скриптов език, както и език за разширения
JavaScript, в частност, е език, който първоначално е бил използван само за уеб приложения. Понеже обаче езикът е станал изключително популярен, е започнал да се ползва и извън уеб приложенията. Езикът има изключително богата екосистема от инструменти за разработка и библиотеки, заради което е добър избор за език, който да бъде внедрен в даден софтуерен продукт.
Заради това, тази дипломна работа цели да създаде лесно вградим и малък на размер интерпретатор на „EcmaScript 5.1“ езика (по-добре познат като JavaScript) за продукти, написани на езикът „Java“. Интерпретатора трябва да изпълнява бързо и правилно JavaScript код от голям размер и да бъде удобен за внедряване във вече съществуващи продукти.
# Проучвателна част
@@ -325,6 +333,18 @@ tasks {
Методи, съответстващи на събития в интерпретатора
:::
### Събитиен цикъл
Това е основен механизъм за поддръжката на привидно многонижково програмиране само чрез една нижка. В основата на събитийния цикъл стои опашка от „съобщения“, като всяко съобщение представлява команда, която събитийният цикъл трябва да изпълни. Командите се подреждат на тази опашка, като първите, които са постъпили в опашката биват изпълнени.
Събитийния цикъл гарантира, че съобщенията, който постъпват във събитийния цикъл, ще бъдат изпълнявани последователно. Това освобождава програмиста от нуждата да създава код, който се грижи за синхронизацията между две ядра.
Комуникацията между два събитийни цикъла може да бъде осъществена като първият цикъл изпраща съобщение на втория събитиен цикъл, който евентуално обработва това съобщение. Това съобщение пък може да направи дадени изчисление и да върне резултата чрез второ съобщение, което се праща на първия събитиен цикъл. На практика, приложението имплементира два събитийни цикъла - този, който изпълнява интерпретирания код и този, който приема резултатите от събитийния цикъл.
Друг модел на комуникация е да се изпрати съобщение от текущата нижка на друга нижка, която изпълнява съобщенията, като текущата нижка трябва да изчака изпълнението на съобщението. При този метод употребата на събитиен цикъл се обезмисля до известна степен, понеже се елиминира всякакво паралелно изпълнение на интерпретирания код и основното приложение.
В кода, интерфейса, който е основен за всички събитиини цикли е `EventLoop`, а неговата основна имплементация е `Engine`. `EventLoop` предоставя само един метод - `pushMsg`, на който се подава функционалния интерфейс `Runnable`, който се очаква да бъде изпълнен евентуално. `Engine` е имплементацията, която използва „блокираща“ опашка (опашка, при която присъства операцията за изчакване на добавяне на елемент).
----
## Имплементация на JavaScript стойностите
@@ -350,8 +370,6 @@ tasks {
В кода и двете стойности са имплементирани с класа `VoidValue`, който може да бъде използван за да бъдат създадени и други празни стойности.
----
### Елементарни стойности
В JavaScript елементарните стойности са всички стойности, които не са обекти. Такива стойности не могат да съдържат референция към друга JavaScript стойност. Такива стойности биват:
@@ -380,6 +398,10 @@ tasks {
В кода, целите числа са имплементирани в класа `IntValue`, а числата с плаваща запетая - класа `DoubleValue`. Тези класове обаче не могат да бъдат инстанцирани ръчно - трябва да бъде използван метода `NumberValue.of`, който създава една от двете инстанции, в зависимост от това дали подаденото число може да бъде представено с 32-битово число.
### Потребителски стойности
Това са служебен тип стойности, които съдържат само една Java стойност. Те се използват за подаване на Java стойности на JavaScript кода, за да бъдат използвани по-късно от Java кода. Потребителските стойности се имплементират от класа „UserValue“, като той съдържа само две полета: съдържаната Java стойност, както и прототипа, който стойността да използва.
### Обекти
Обектите са основополагаща концепция в JavaScript. В основата на обектите стои списък от уникални низове (т. нар. ключове) и съответстващият им член [@членове] (съществува и паралелен списък със списък от ключове-символи към членове). Обектите се използват най-вече за съхранение на няколко подстойности под формата на структурирани данни (подобно на класовете на Java), но могат да бъдат използвани и като хеш-таблици.
@@ -391,7 +413,7 @@ tasks {
- Режим без пренастройвани (запечатване) - освен ограниченията от предишният режим, полетата не могат да бъдат трити и пренастройвани
- Режим на заключване (замразяване) - обекта става на практика константа, нито едно негово свойство не може да бъде променено
JavaScript кода може да променя режима от по-свободен към по-ограничаващ, но не и обратно. Това разрешава на разработчицитие да осигурят дадено ниво на константност на данните на обектите си.
JavaScript кода може да променя режима от по-свободен към по-ограничаващ, но не и обратно. Това разрешава на разработчиците да осигурят дадено ниво на константност на данните на обектите си.
Накрая, обектите имат прототипи. Прототипа може да е `null` или обект. При търсенето на член, може обект да няма дадения ключ. Тогава търсенето продължава в прототипа на обекта. Понеже обаче прототипа е обект на практика съществува свързан списък от обекти, или т. нар. прототипна верига. Прототипите са в основата на обектно-ориентираното програмиране в JavaScript, понеже може чрез прототипите да бъде дефиниран един основен обект, който да съдържа всички функции за даден „тип“ обекти, а всички обекти от този „тип“ просто имат този основен обект като прототип. Освен това обаче прототипите могат да бъдат използвани и за осъществяване на наследство на обекти - обекта „AnimalPrototype“, който съдържа члена „speak“ може да бъде прототип на обекта „DogPrototype“, който съдържа члена „bark“. При тази конфигурация, „DogPrototype“ де факто има и двете полета - „speak“ и „bark“.
@@ -445,7 +467,7 @@ console.log(arr); // [5, 6, <empty x 2>, 8, <empty x 7> 3]
- Промяната на една от тези настройки, освен ако презаписваемо, но ненастройваемо поле се направи непрезаписваемо
- Изтриване на полето от обекта
Членовете са дефинирани чрез интерфейса „Member“, полетата чрез класа „FieldMember“, а свойствата чрез „PropertyMember“. Понеже полетата могат да бъдат виртуални и контролирани от Java код, е осигурена базова имплементация за съвсем прости полета, които са само контейнер за JavaScript стойност, в класа „SimpleFieldMember“, докато по-сложна логика може да създаде собствена имплементация на „FieldMember“ класа (на този механизъм са базирани масивите).
Членовете са дефинирани чрез интерфейса „Member“, полетата чрез класа „FieldMember“, а свойствата чрез „PropertyMember“. Понеже полетата могат да бъдат виртуални (като тези на масивите), е „FieldMember“ е абстрактен, а „SimpleFieldMember“ е имплементацията на нормалните полета.
----
@@ -688,7 +710,7 @@ public class Frame {
```txt
; Добавя 10 към стека
LOAD_NUMBER 10
; Добадя 1 към стека
; Добавя 1 към стека
LOAD_NUMBER 1
; Добавя първият елемент на
; стека с новодобавената единица
@@ -792,7 +814,7 @@ STORE_MEMBER_STR | key, keep | value, object | Еквивалент на ST
STORE_MEMBER_INT | key, keep | value, object | Еквивалент на STORE_MEMBER, но с константен числов ключ `key`
GLOB_GET | name, force | | Зарежда променливата `name` от глобалния обект. Ако `force` е `true` не се хвърля грешка ако променливата не съществува. Стойността на променливата се добавя към стека
GLOB_SET | name, keep, force | value | Съхранява променливата `name` в глобалния обект. Ако `force` е `true` не се хвърля грешка ако променливата не съществува. Стойността на променливата се добавя към стека, ако `keep` е `true`
GLOB_DEF | name | value | Създава променливата `name` със стойност `undefined` в глобавният обект, ако не съществува
GLOB_DEF | name | value | Създава променливата `name` със стойност `undefined` в глобалния обект, ако не съществува
OPERATION | type | values... | Изпълнява дадената операция (фиг. [@operations-table]) с даденият брой операнда, които операцията изисква. Добавя стойността при изчислението на операцията към стека
Набор от инструкции на междинния език (бележка: в колоната с аргументи от стека са показани аргументите в реда, в който се взимат от стека, освен ако друг ред не е уточнен)
@@ -906,7 +928,7 @@ class Frame {
## Компилиране на проекта
Можете да изтеглите кода на проекта, като и всичко нужно за компилирането му от приложената флашка (в директорията „j2s“) или от Git репозиторията <https://git.topcheto.eu/topchetoeu/j2s.git>. За компилацията на кода е нужен следния софтуер:
Можете да изтеглите кода на проекта, като и всичко нужно за компилирането му от приложената флашка (в директорията „j2s“) или от Git хранилището <https://git.topcheto.eu/topchetoeu/j2s.git>. За компилацията на кода е нужен следния софтуер:
- OpenJDK 17 SDK
- Gradle 8.10
@@ -924,7 +946,7 @@ class Frame {
## Използване на проекта като зависимост в друг проект
За проекта е налично и Maven хранилище, достъпно чрез URL адреса [https://git.topcheto.eu/api/packages/topchetoeu/maven], в което присъстват всички компонети на проекта.
За проекта е налично и Maven хранилище, достъпно чрез URL адреса [https://git.topcheto.eu/api/packages/topchetoeu/maven], в което присъстват всички компоненти на проекта.
----
@@ -1026,7 +1048,7 @@ $ for (const el of myGen(0, 30, 10)) print(el);
20i
undefined
```
Употреба на по-нов синтаксиси, с помощта на Babel
Употреба на по-нови синтаксиси, с помощта на Babel
:::
----
@@ -1055,6 +1077,8 @@ StdLib.addGlobals(env);
env.add(Compiler.KEY, Compilers.jsCompiler());
```
Важно е да се отбележе, че събитийния цикъл работи в отделна нижка по подразбиране. Не е нужно обаче да бъде стартиран събитийния цикъл, а може да бъде повиквана само неговия метод `Engine.run(true)`, като `true` в това повикване означава „изпълнение на събитийния цикъл докато опашката от съобщения приключи“.
Ако искате да регистрирате транспилатори, може да използвате следния код (имайте в предвид, че тези функции създават нова среда и нова стандартна библиотека за всеки транспилатор, за да бъдат изолирани от останалия код.):
```java
@@ -1081,7 +1105,7 @@ engine.pushMsg(false, env, new Filename("test", "test"), "console.log(10 + 5, \"
Работата с JavaScript обекти е лесна, трябва да бъдат използвани само описаните в [@имплементация-на-javascript-стойностите] методи:
```java
ArrayValue doInterestingStuff(Environment env, Value input) {
ArrayValue doStuff(Environment env, Value input) {
var obj = new ObjectValue();
obj.defineOwnField(env, "test", NumberValue.of(10));
obj.defineOwnField(env, "a", StringValue.of("fsadf"));
@@ -1095,7 +1119,7 @@ ArrayValue doInterestingStuff(Environment env, Value input) {
}
```
Често ще Ви се налага да създавате и Java функцкии (трябва да правите такива функции с повишено внимание, понеже те имат неограничен достъп до Java средата на изпълнение, което може да означава че зле написана Java функция може да компрометира сигурността). Следния код е пример как се създава Java функция:
Често ще Ви се налага да създавате и Java функции, за да давате достъп на JavaScript до Вашият софтуер. Подобни функции обаче трябва да бъдат създавани с повишено внимание, понеже могат да бъдат използвани и злонамерено. Подобни функции се създават по следния начин:
```java
var func = new NativeFunction("myFunc", args -> {
@@ -1106,6 +1130,18 @@ var func = new NativeFunction("myFunc", args -> {
});
```
`args` аргумента е от типа `Argument`. Това е служебен клас, който служи за улеснен достъп до аргументите, подадени на функцията. Той има следните основни методи и полета:
- self - „this“ аргумента
- args - масив от подадените аргументи
- env - средата, подадена при извикването
- isNew - „true“, ако е извикана в режим на конструкция (в който случай „self“ е „target“ функцията)
- setTargetProto(obj) - в контекста на конструкция, прилака прототипа на „target“ функцията на подадения обект
- n() - връща броя на аргументите
- has(i) - проверка дали този аргумент е бил подаден
- self(clazz) - ако „this“ аргумента е „UserValue“, превръща съдържаната стойност в дадения клас, иначе връща „null“
- get(i) - връща съответния на i аргумент, или „null“, ако индекса е извън обхвата на масива
# Заключение {.nonum}
В настоящата дипломна работа е демонстриран интерпретатора на JavaScript, написан на Java. За да бъде създаден, беше направено проучване на съществуващите технологии, както и методите на интерпретиране. Беше отделено и голямо внимание на добрата и проста архитектура на проекта. Архитектурата се развиваше заедно с кода, докато достигне до днешната си форма, като не стана заплетена и ненужно сложна. Интерпретатора беше тестван най-вече ръчно чрез сравнение с държанието на V8 при същия код, както и чрез трите транспилатора „Babel“, „TypeScript“ и „CoffeeScript“.
@@ -1171,7 +1207,7 @@ else return end.chainError(src.loc(i + n), "Expected end of statement");
Всеки клас за елемент от синтактичното дърво съответно има следните три основни функции:
- `compile` - Добавя съответставщите на елемента инструкции в подадения `CompileResult`
- `compile` - Добавя съответставащите на елемента инструкции в подадения `CompileResult`
- `compileFunctions` - Използва се като служебна функция, която да позволява компилирането първо на по-дълбоките функции
- `resolve` - Дефинира в подадения `CompileResult` променливите, които елемента декларира
@@ -1193,7 +1229,7 @@ else return end.chainError(src.loc(i + n), "Expected end of statement");
Създадената система за добавяне на интерпретатори от своя страна е създадена такава, че може няколко интерпретатора да бъдат „наредени“ един след друг. Това позволява изключителна гъвкавост, както и допълнителна изолация на средата - може код, който използва компилатор да дефинира собствен компилатор без да знае за компилатора, на който е базиран.
Освен кода, за изпълнението на TypeScript кода са включени и типизации на стандартните билиотеки, описващи специфичната стандартна библиотека, дефинирана тук.
Освен кода, за изпълнението на TypeScript кода са включени и типизации на стандартните библиотеки, описващи специфичната стандартна библиотека, дефинирана тук.
За да има достъп кода на стандартната библиотека до някои вътрешни функции на интерпретатора, беше създаден и класа „Primordials“, който създава набор от функции, които могат да бъдат използвани за ***незащитен*** достъп до иначе недостъпните чрез синтаксис функционалности.
@@ -1203,6 +1239,22 @@ else return end.chainError(src.loc(i + n), "Expected end of statement");
Това е втората част от „lib“ компонента. Той използва `DebugHandler` механизма, за да прихване различните събития на изпълнението на кода. Специалното на дебъгера е, че имплементира V8 протокола за дебъгване [@refs-v8-debug-protocol], което означава, че дебъгването на програма, изпълнявана в този интерпретатор, е възможно в инструментите за разработчици на Chrome, както и във Visual Studio Code.
За да бъде използван дебъгера, трябва бъде създаден „DebugServer“, който имплементира прост HTTP и WebSocket сървър, който да комуникира с V8 дебъг клиента. След това, трябва да бъде създаден регистратор на дебъгера при осъществяването на връзка със сървъра. За целта може да бъде използван вградения „SimpleDebugger“. Кода, който може да използвате за целта е следния:
```java
var handler = new SimpleDebugHandler();
env.add(DebugHandler.KEY, handler);
var server = new DebugServer();
var debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
server.targets.put("default", (socket, req) -> {
var debugger = new SimpleDebugger(socket);
debugger.attach(handler);
});
```
След това, за да бъде използван дебъгера може да се използва или Chrome (или всеки браузър, базиран на Chromium), или VSCode, като Chrome автоматично ще засече присъствието на Debug сървър и ще предложи стартирането на дебъг клиент чрез зелена икона на NodeJS, която ще се покаже в горния ляв ъгъл на опциите на разработчиците (достъпно чрез F12).
![Пример за дебъгване на програма от интерпретатора](./img/debugging-example.png "debug")
# Използвани термини и чуждици {.nonum}

View File

@@ -315,7 +315,7 @@ function converters.Image(data, ctx)
return text, title and { title } or alt_plain or url or "[picture]";
end
function converters.Figure(data, ctx)
local chapter_i = #(ctx.headers or {});
local chapter_i = count_headers(ctx.headers or {});
ctx.figures_indices = ctx.figures_indices or {};
ctx.figures_indices[chapter_i] = (ctx.figures_indices[chapter_i] or 0) + 1;
local figure_i = ctx.figures_indices[chapter_i];

View File

@@ -102,16 +102,17 @@ local function parse_impl(str, pos, end_delim)
local delim_found;
if c == "{" then
pos = pos + 1;
pos = str:find("%S", pos + 1) or pos;
local key;
local obj = {};
c = string.sub(str, pos, pos);
if c == "}" then
return obj, pos
return obj, pos;
else
while true do
pos = skip_delim(str, pos);
key, pos = parse_str_val(str, pos, true);
if key == nil then error("Expected a string key") end

View File

@@ -8,6 +8,15 @@
@page {
size: A4;
}
@page {
@bottom-right-corner {
text-align: center;
content: counter(page);
}
}
@page scan-page {
margin: 0;
}
h1 {
break-before: page;
}
@@ -34,6 +43,7 @@
}
.page {
height: 100%;
line-height: 1.25;
h1, h2, h3, h4, h5, h6 {
margin: 0;
@@ -167,6 +177,7 @@
.page {
height: 100%;
max-height: 100%;
font-size: 12pt;
}
@@ -175,9 +186,12 @@
flex-direction: row;
width: 100%;
gap: 2em;
font-size: 1.25em;
text-align: right;
margin-bottom: 1em;
}
.school-img {
height: 5em;
height: 6em;
width: unset;
}
@@ -185,6 +199,13 @@
break-after: page;
}
.scan-page {
display: flex;
flex-direction: column;
justify-content: stretch;
align-items: stretch;
page: scan-page;
}
.title-page {
display: flex;
flex-direction: column;
@@ -213,6 +234,9 @@
text-align: right; */
}
.asm-title {
display: flex;
flex-direction: column;
gap: 1em;
text-align: center;
}
.asm-content {
@@ -226,8 +250,6 @@
height: 100%;
}
.asm-page {
height: 100%;
max-height: 100%;
display: flex;
flex-direction: column;
}
@@ -471,6 +493,11 @@
// console.log(target, name, i);
}
}
for (let i = 0; i < 3; i++) {
pages[i].getElementsByClassName("pagedjs_margin-bottom-right-corner-holder")[0].innerText = "";
console.log(pages[i].getElementsByClassName("pagedjs_margin-bottom-right-corner-holder")[0]);
}
};
</script>
</head>
@@ -484,10 +511,10 @@
<div class="title-content">
<h2>ДИПЛОМНА РАБОТА</h2>
<h4>по професия код {{profession}}</h4>
<h4>специалност код {{specialty}}</h4>
<div>Тема: {{topic}}</div>
<div>по професия код {{profession}}</div>
<div>специалност код {{specialty}}</div>
</div>
<div class="title-content">Тема: {{topic}}</div>
<div class="title-authors">
<div class="title-author">
<div class="author-type">Дипломант:</div>
@@ -501,7 +528,15 @@
<div class="title-end">СОФИЯ - {{year}}</div>
</div>
<div class="page asm-page">
<div class="page scan-page">
<img src="./img-secret/scan0001.jpg"/>
</div>
<div class="page scan-page">
<img src="./img-secret/scan0003.jpg"/>
</div>
<!-- <div class="page asm-page">
<div class="school-header">
<img class="school-img" src="{{school_img}}"/>
<h4>{{school_name}}</h4>
@@ -518,11 +553,12 @@
</div>
<div class="asm-content">
<div class="asm-title">
<h2>ЗАДАНИЕ</h2>
<h2>за дипломна работа</h2>
<h2>ЗАДАНИЕ<br/>за дипломна работа</h2>
<h4>ДЪРЖАВЕН ИЗПИТ ЗА ПРИДОБИВАНЕ НА ТРЕТА СТЕПЕН НА ПРОФЕСИОНАЛНА КВАЛИФИКАЦИЯ</h4>
<h4>по професия код {{profession}}</h4>
<h4>специалност код {{specialty}}</h4>
<div>
<div>по професия код {{profession}}</div>
<div>специалност код {{specialty}}</div>
</div>
</div>
<div class="asm-requirements">
на ученика {{author}} от {{class}} клас<br/>
@@ -543,7 +579,7 @@
</div>
</div>
</div>
</div>
</div> -->
{{content}}

View File

@@ -1,4 +1,4 @@
project_group = me.topchetoeu.j2s
project_name = j2s
project_version = 0.10.10-beta
project_version = 0.10.11-beta
main_class = me.topchetoeu.j2s.repl.SimpleRepl

View File

@@ -1,7 +1,7 @@
import com.github.gradle.node.npm.task.NpmTask;
plugins {
id("common-java");
id("j2s-common-java");
id("com.github.node-gradle.node") version "5.0.0";
}

View File

@@ -21,12 +21,12 @@ public class Compilers {
try {
var res = JavaScript.compile(env, filename, raw, true);
var body = res.body();
DebugHandler.get(env).onSourceLoad(filename, raw);
for (var el : res.all()) {
DebugHandler.get(env).onFunctionLoad(el.body(), el.map(mapper));
}
return new CodeFunction(env, filename.toString(), body, new Value[0][]);
}
catch (SyntaxException e) {
@@ -59,9 +59,6 @@ public class Compilers {
public static Compiler transpilerFromSource(Compiler prev, Environment target, Filename compilerName, String compilerSrc) {
var env = StdLib.apply(null);
// var handler = new SimpleDebugHandler();
// env.add(DebugHandler.KEY, handler);
var glob = Value.global(env);
var compilerFactory = new FunctionValue[1];
@@ -86,10 +83,6 @@ public class Compilers {
var compiled = JavaScript.compile(compilerName, compilerSrc, false);
// for (var el : compiled.all()) {
// handler.onFunctionLoad(el.body(), el.map());
// }
try {
new CodeFunction(env, "intializer", compiled.body(), new Value[0][]).apply(env, Value.UNDEFINED);
return wrap(prev, env, target, compilerFactory[0]);

View File

@@ -16,10 +16,10 @@ export default function typescript(next: Compiler): Compiler {
const settings: CompilerOptions = {
target: ScriptTarget.ES5,
module: ModuleKind.Preserve,
allowImportingTsExtensions: true,
verbatimModuleSyntax: true,
verbatimModuleSyntax: false,
strict: false,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
@@ -52,7 +52,7 @@ export default function typescript(next: Compiler): Compiler {
}
},
getScriptVersion: (filename) => String(versions[filename] || 0),
readFile: () => { throw "no"; },
writeFile: () => { throw "no"; },
}, createDocumentRegistry());
@@ -61,7 +61,7 @@ export default function typescript(next: Compiler): Compiler {
service.getEmitOutput("/lib.d.ts");
});
print("Loaded typescript!");
return (filename, code, prevMap) => {
files["/src.ts"] = ScriptSnapshot.fromString(code);
versions["/src.ts"] ??= 0;

View File

@@ -1,5 +1,5 @@
plugins {
id("common-java");
id("j2s-common-java");
id("com.gradleup.shadow") version "9.0.0-beta4";
}

View File

@@ -1,5 +1,5 @@
plugins {
id("common-java");
id("j2s-common-java");
}
description = "The runtime of J2S, used to execute J2S bytecode";

View File

@@ -323,7 +323,7 @@ public final class Frame {
/**
* Induces a value on the stack (as if it were returned by the last function call)
* and executes the next instruction in the frame.
*
*
* @param value The value to induce
*/
public final Value next(Value value) {
@@ -334,7 +334,7 @@ public final class Frame {
* Note that this is different than just throwing the error outside the
* function, as the function executed could have a try-catch which
* would otherwise handle the error
*
*
* @param error The error to induce
*/
public final Value induceError(EngineException error) {
@@ -346,7 +346,7 @@ public final class Frame {
* Note that this is different than just returning the value outside the
* function, as the function executed could have a try-catch which
* would otherwise handle the error
*
*
* @param value The retunr value to induce
*/
public final Value induceReturn(Value value) {

View File

@@ -15,8 +15,9 @@ public class Arguments {
public final <T extends Value> T setTargetProto(T obj) {
if (!self.isPrimitive()) {
var proto = self.getMember(env, "prototype");
if (proto instanceof ObjectValue objProto) self.setPrototype(env, objProto);
else if (proto == Value.NULL) self.setPrototype(env, null);
if (proto instanceof ObjectValue objProto) obj.setPrototype(env, objProto);
else if (proto == Value.NULL) obj.setPrototype(env, null);
}
return obj;
}

View File

@@ -57,7 +57,7 @@ public final class StringValue implements PrimitiveValue {
}
}
return StringValue.this.getOwnMember(env, key);
return PrimitiveValue.super.getOwnMember(env, key);
}
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {