diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3b58b380..11555aec 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,21 +1,8 @@ -### Description - - +**Is your pull request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +**Describe the solution you've done** +Key points of your tech and arch decisions. -### Related Issues - - - -### References - - - - -### Checklist: - - -- [ ] I have followed the [contribution guidelines](../CONTRIBUTING.md) and code style for this project. -- [ ] I have added tests covering my contributions. -- [ ] I have updated the documentation accordingly. -- [ ] I have added corresponding labels with respect to the part of the interpreter i've worked on. +**Related Issues** +If it fixes an open issue, please link to the issue here. diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 5c470007..be08eaf0 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -53,7 +53,10 @@ jobs: run: dotnet test -c Debug --no-build -v n --filter-trait Category=Unit - name: Integration Tests run: | - dotnet test --project tests/HydraScript.IntegrationTests -c Debug --no-build -v n --coverage --coverage-output-format cobertura --coverage-output coverage.cobertura.xml + dotnet test --project tests/HydraScript.IntegrationTests ` + -c Debug --no-build -v n ` + --coverage --coverage-output-format cobertura --coverage-output coverage.cobertura.xml ` + --coverage-settings tests/coverage-exclude.xml mkdir coverage-report - name: Code Coverage Summary Report For Merge Request if: github.event_name == 'pull_request' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cdd4d14d..93f4734f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,7 @@ on: permissions: contents: write issues: write + actions: write jobs: create-release: @@ -85,6 +86,8 @@ jobs: name: Publish release runs-on: ubuntu-latest needs: upload-release-assets + outputs: + determined_version: ${{ needs.upload-release-assets.outputs.determined_version }} steps: - name: Setup .NET uses: actions/setup-dotnet@v5 @@ -103,4 +106,24 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} repository: 'Stepami/hydrascript' - milestone: ${{ needs.upload-release-assets.outputs.determined_version }} \ No newline at end of file + milestone: ${{ needs.upload-release-assets.outputs.determined_version }} + + publish-nuget: + name: Publish as dotnet tool to NuGet + runs-on: windows-latest + needs: publish-release + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x + - name: Build + run: dotnet build ./src/HydraScript/HydraScript.csproj -c Release -v n ` + /p:Version=${{ needs.publish-release.outputs.determined_version }} ` + /p:PublishAot=false /p:PublishSingleFile=false + - name: Publish + run: dotnet nuget push ./src/HydraScript/bin/Release/*.nupkg ` + --api-key ${{ secrets.NUGET_API_KEY }} ` + --source https://api.nuget.org/v3/index.json \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index c5a1f364..7676fe4e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,11 +7,16 @@ true latest false + false - + none false + Size + true + false + true diff --git a/Directory.Packages.props b/Directory.Packages.props index c1782766..0e3eb7ec 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,17 +1,18 @@ - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + diff --git a/ExtendedJavaScriptSubset.slnx b/ExtendedJavaScriptSubset.slnx index f5a7e5c2..4932071c 100644 --- a/ExtendedJavaScriptSubset.slnx +++ b/ExtendedJavaScriptSubset.slnx @@ -1,6 +1,6 @@ - + @@ -28,6 +28,7 @@ + @@ -48,6 +49,7 @@ + diff --git a/Readme.md b/Readme.md index 4a2220b5..14b84f5a 100644 --- a/Readme.md +++ b/Readme.md @@ -2,63 +2,74 @@ ![logo](hydrascript-logo.jpg) -## "Расширенное подмножество ЯП JavaScript" +## Installation -### Скачать - -Файл интерпретатора собирается в рамках релиза на три платформы: +Interpreter executable is built during release for 3 following platforms: - Windows (x64) - MacOS (arm64 Apple Silicon) - Linux (x64) -Скачать нужную версию можно со страницы соответствующего релиза +Download hydrascript executable on the corresponding release page. + +[The latest relase is available here.](https://github.com/Stepami/hydrascript/releases/latest) + +Alternatively you can consume HydraScript as dotnet tool: +``` +dotnet tool update --global hydrascript +``` -[Последний релиз доступен по этой ссылке](https://github.com/Stepami/hydrascript/releases/latest) +## Project History -### Вводная информация +It started as a bachelor thesis "Extended JavaScript Subset". Now it's named "HydraScript", because as I fix one bug another occurs. -За основу был взят стандарт [ECMA-262](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/) +I took [ECMA-262 standard](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/) as a basis and made: +- [Lexical structure](src/Domain/HydraScript.Domain.Constants/TokenTypes.cs) +- [Grammar](src/Domain/HydraScript.Domain.FrontEnd/Parser/grammar.txt) -[Лексическая структура](src/Domain/HydraScript.Domain.Constants/TokenTypes.cs) +[Working samples can be found here.](tests/HydraScript.IntegrationTests/Samples) I use them for integration tests. -[Грамматика](src/Domain/HydraScript.Domain.FrontEnd/Parser/grammar.txt) +## Project Goals +1. Partially implement JavaScript with objects and strong static structural typing, getting rid of such things as: `constructor`, `class`, `interface` +2. Publicly reverse engineer modern static analysis (type inference, forward refs, compile-time detection of the runtime errors, etc.) +3. Simplifying compilers construction domain with HydraScript source code +4. Gather clear and concise standard problem solutions and algorithm implementations (Lexer, Parser, CFG, SSA, DCE, etc.) -[Рабочие примеры](tests/HydraScript.IntegrationTests/Samples) +## Language docs -### Цели проекта -1. Частично реализовать JavaScript с объектами и статической структурной типизацией, избавившись от таких понятий, как `constructor`, `class`, `interface` -2. Публично реверс-инжинирить современный статический анализ (вывод типов, форвард рефы, ошибки выполнения на стадии компиляции) -3. Упростить понимание области конструирования компиляторов за счёт исходного кода проекта - собрать понятные реализации алгоритмов и типовых задач в репозитории (Lexer, Parser, CFG, SSA, DCE, etc.) +### Comments -### Конструкции языка +All comments are single-line: +``` +// double slash comment +# shebang comment +``` -#### Типизация -В языке структурная статическая сильная типизация. +### Typing +The HydraScript has strong static structural typing. -Есть 5 примитивных типов: +There are 5 primitive types: 1. number 2. boolean 3. string 4. null 5. void -Остальные типы делятся на группы: -- NullableType (тип, который допускает значение ```null```) -- ObjectType (тип объекта, является NullableType) -- ArrayType (списковый тип) +The other types can be grouped as such: +- NullableType (type that allows ```null```) +- ObjectType (the type of object, obviously, and also is NullableType) +- ArrayType (the type of array) -##### Значения по умолчанию +**The types have the following default values:** -| Тип | Значение | -|--------------|----------| -| number | 0 | -| boolean | false | -| string | "" | -| NullableType | null | -| ArrayType | [] | -##### type alias -Можно создать свой type alias по типу того, как это сделано в С++ +| Type | Value | +|--------------|-------| +| number | 0 | +| boolean | false | +| string | "" | +| NullableType | null | +| ArrayType | [] | +**You can also create your own type alias as in C++ or TypeScript:** ``` type int = number type maybeInt = int? @@ -72,42 +83,108 @@ type composite = { arr: ints; } ``` -#### Объявление переменных + +#### Strings + +Strings support following operations: +- index access, returns `string`: +``` +let str = "str" +>>> str[1] // t ``` -let i = 1 // интерпретатор выведет тип из выражения -let j: number // запишет значение по умолчанию в переменную -let k: number = 1 // полностью явное объявление +- length getter: ``` +let str = "str" +>>> ~str // 3 +``` + +### Variables -#### Объекты +For declaring mutable variables use `let`: +``` +let i = 1 // HydraScript infers type from expression +let j: number // Here it writes to j the default value +let k: number = 1 // Fully explicit declaration +``` +For declaring readonly variables use `const`: +``` +const flag = true +``` + +To work with Environment Variables place `$` before identifier: +``` +$MY_ENV_VAR = "my_env_var_value" +const flagValue = $FLAG_VALUE +``` + +### Input and Output + +The HydraScript is able to print any values in console and read strings from it: +``` +# output +>>> [1, 2, 3] +>>> { x: true; } +>>> "Hello, world!" + +// input +let name: string +<<< name +``` + +### Objects + +Object literal declaration is similar to JSON: ``` let v2d = { x: 3; y: 4; } ``` -#### Списки + +You can clone objects using `with` expressions like in C#: +``` +let v = { + x: 2; + y: 1; +} + +let vToX = v with { y: 0; } +``` + +### Arrays + +HydraScript arrays are basically lists. Legacy naming, unfortunately. + +Array literal declaration is similar to JSON: ``` let array = [1, 2, 3] -let size = ~array // длина списка -array::1 // удаление элемента по индексу -array = array ++ [5, 7] // конкатенация списков -``` -#### Операторы -| Оператор | Вид | Типы операндов | Тип операции | -|------------------|----------|------------------------|----------------| -| + | бинарный | оба number, оба string | number, string | -| *, -, /, % | бинарный | number | number | -| ||, && | бинарный | boolean | boolean | -| !=, == | бинарный | равный с двух сторон | boolean | -| <=, >=, >, < | бинарный | number | boolean | -| ! | унарный | boolean | boolean | -| - | унарный | number | number | -| ++ | бинарный | [] | [] | -| :: | бинарный | [] и number | void | -| ~ | унарный | [] | number | - -#### Ветвление +``` + +You have special operators to work with arrays: +``` +let size = ~array // length +array::1 // remove at index +array = array ++ [5, 7] // concatenation +``` + +### Operators + +| Operator | Binary or Unary | Operand Types | Result Type | +|------------------|-----------------|----------------------------|------------------| +| + | binary | both number or both string | number or string | +| *, -, /, % | binary | number | number | +| ||, && | binary | boolean | boolean | +| !=, == | binary | same on both sides | boolean | +| <=, >=, >, < | binary | number | boolean | +| ! | unary | boolean | boolean | +| - | unary | number | number | +| ++ | binary | [] | [] | +| :: | binary | [] and number | void | +| ~ | unary | [] or string | number | + +### Conditionals + +The language supports classic `if-else`: ``` if (1 == 1) { // ... @@ -117,15 +194,18 @@ if (1 == 1) { else { // ... } -// в общем как в Си подобных языках -// главное, чтобы выражение условия -// возвращало boolean +// basically C-like syntax and semantic +// test expression must have boolean type ``` -Также есть тернарный оператор + +There is also ternary operator: ``` let x = 1 > 0 ? 0 <= 1 ? 1 : 0 : -2 < 0 ? -1 : 0 ``` -#### Цикл + +### Loops + +Now it has only `while`: ``` while (cond) { // ... @@ -134,62 +214,114 @@ while (cond) { break } ``` -#### Функции + +### Functions + +Functions are similar to TypeScript: ``` -// объявление +// declaration function add(a: number, b: number): number { return a + b } -// вызов + +// call let c = add(1, 2) ``` -#### Методы + +The return type can be specified explicitly or inferred: ``` -// сделаны подобно Go - привязка по имени типа +// this is also valid +function add(a: number, b: number) { + return a + b +} +let c = add(1, 2) // c is number +``` + +### Methods -// шаг 1. Объявить type alias +A function can be method if it's bound to an object using the "Golang-like approach": +``` +# The binding is done with object type alias + +// step 1. Declare type alias type Point2 = { x: number; y: number; } -// шаг 2. Объявить переменную этого типа +// step 2. Declare variable of type defined earlier +// It's obligatory to explicitly define type after colon to make it work let v2d: Point2 = { x: 3; y: 4; } -// шаг 3. Указать первым параметром функции - объект типа +// step 3. While declaring function place first the paremeter with aliased object type +// it's basically explicit "this" function lengthSquared(obj: Point2) { let x = obj.x let y = obj.y return x * x + y * y } + +// step 4. make a call +let s = v2d.lengthSquared() ``` -#### Операции доступа + +### Overloads and default parameters + +Both, methods and functions, do support overloading and default parameters: +``` +function func(x = 0){ + >>> x +} + +function func(x:number, y:number) { + return x + y +} + +func() +func(1) +func(func(2, 3)) +``` + +### Access operators + +Access means being able to get element of an array or property of an object: ``` -// объекты +// objects let x = v2d.x -let s = v2d.lengthSquared() -// массивы + +// arrays let l = array[2] ``` -#### Приведение типов + +### Type casting + +HydraScript supports explicit casting through `as` keyword: ``` let s = v2d as string +let one = "1" as number +let falseValue = 0 as boolean + +let nullableNumber = null as number? ``` -#### Печать на экран -``` -let obj = {} ->>>obj ->>>"Hello, World!" -``` -### Требования +One can cast using following rules: +- `any` -> `string` +- `string` -> `number` +- `string` -> `boolean` +- `boolean` -> `number` +- `number` -> `boolean` +- `type?` -> `type` +- `type` -> `type?` +- `null` -> `type?` + +## Requirements -До версии **2.3.0** для запуска интерпретатора требовалась [установка .NET Runtime](https://dotnet.microsoft.com/ru-ru/download/dotnet) +Before **2.3.0** version executable launch needed [.NET Runtime installed.](https://dotnet.microsoft.com/ru-ru/download/dotnet) -Таблица соответствий hydrascript и dotnet: +There is match table of hydrascript and dotnet: | hydrascript | dotnet | |-------------------------------------------------------------------|--------| @@ -199,31 +331,31 @@ let obj = {} | 2.0.0 | .NET 8 | |
  • 2.1.0
  • 2.1.1
  • 2.2.0
| .NET 9 | -### Сборка -Необходим .NET SDK 9.0.202 (поддержка SLNX) +If you use dotnet tool then requirements is specified on NuGet page. -После клонирования репозитория идём в папку проекта `HydraScript`. +## Build from source +Install **latest** .NET SDK. The project uses **SLNX** as solution format. -Там выполняем команду: +Do this inside `HydraScript` root after cloning repository: ```dotnet publish ./src/HydraScript/HydraScript.csproj -r -o ``` -Список идентификаторов рантайма лежит [тут](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog#windows-rids) +[Runtime identifier list can be found here](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog#windows-rids) -### Запуск +## How to run -Простой: +Default: ``` HydraScript file.js ``` -С выводом дебаг инфы (токены, ast, инструкции): +Dumping debug info as files (tokens, ast, ir code): ``` HydraScript file.js --dump ``` -### Источники: +## Sources: -1. Курсы "Конструирование Компиляторов" и "Генерация Оптимального Кода" кафедры ИУ-9 МГТУ им. Н.Э. Баумана [@bmstu-iu9](https://www.github.com/bmstu-iu9) +1. "Compilers Construction" and "Optimized Code Generation" courses of [@bmstu-iu9](https://www.github.com/bmstu-iu9) 2. [ECMA-262](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/) 3. [DragonBook](https://suif.stanford.edu/dragonbook/) 4. [Stanford CS143 Lectures](https://web.stanford.edu/class/archive/cs/cs143/cs143.1128/) diff --git a/icon.png b/icon.png new file mode 100644 index 00000000..8de02b7f Binary files /dev/null and b/icon.png differ diff --git a/src/Application/HydraScript.Application.CodeGeneration/IValueDtoConverter.cs b/src/Application/HydraScript.Application.CodeGeneration/IValueDtoConverter.cs deleted file mode 100644 index 36d70216..00000000 --- a/src/Application/HydraScript.Application.CodeGeneration/IValueDtoConverter.cs +++ /dev/null @@ -1,9 +0,0 @@ -using HydraScript.Domain.BackEnd; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; - -namespace HydraScript.Application.CodeGeneration; - -public interface IValueDtoConverter -{ - public IValue Convert(ValueDto dto); -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/IValueFactory.cs b/src/Application/HydraScript.Application.CodeGeneration/IValueFactory.cs new file mode 100644 index 00000000..68505783 --- /dev/null +++ b/src/Application/HydraScript.Application.CodeGeneration/IValueFactory.cs @@ -0,0 +1,14 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl.Values; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Application.CodeGeneration; + +public interface IValueFactory +{ + public IValue Create(ValueDto dto); + + public Name Create(IdentifierReference id); + + public Name Create(string id); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/Impl/ValueDtoConverter.cs b/src/Application/HydraScript.Application.CodeGeneration/Impl/ValueDtoConverter.cs deleted file mode 100644 index ee3f919b..00000000 --- a/src/Application/HydraScript.Application.CodeGeneration/Impl/ValueDtoConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -using HydraScript.Domain.BackEnd; -using HydraScript.Domain.BackEnd.Impl.Values; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; - -namespace HydraScript.Application.CodeGeneration.Impl; - -internal class ValueDtoConverter : IValueDtoConverter -{ - public IValue Convert(ValueDto dto) => - dto switch - { - { Type: ValueDtoType.Constant, Label: not null } => - new Constant(dto.Value, dto.Label), - { Type: ValueDtoType.Name, Name: not null } => - new Name(dto.Name), - _ => throw new ArgumentOutOfRangeException(nameof(dto)) - }; -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/Impl/ValueFactory.cs b/src/Application/HydraScript.Application.CodeGeneration/Impl/ValueFactory.cs new file mode 100644 index 00000000..2aa447fd --- /dev/null +++ b/src/Application/HydraScript.Application.CodeGeneration/Impl/ValueFactory.cs @@ -0,0 +1,42 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl.Frames; +using HydraScript.Domain.BackEnd.Impl.Values; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Application.CodeGeneration.Impl; + +internal class ValueFactory( + IFrameContext frameContext, + IEnvironmentVariableProvider provider) : IValueFactory +{ + public IValue Create(ValueDto dto) => + dto switch + { + { Type: ValueDtoType.Constant, Label: not null } => + new Constant(dto.Value, dto.Label), + { Type: ValueDtoType.Name, Name: not null } => + new Name(dto.Name, CurrentFrame), + { Type: ValueDtoType.Env, Name: not null } => + new EnvName(dto.Name, EnvFrame), + _ => throw new ArgumentOutOfRangeException(nameof(dto)) + }; + + public Name Create(IdentifierReference id) + { + var dto = id.ToValueDto(); + return dto switch + { + { Type: ValueDtoType.Name, Name: not null } => + new Name(dto.Name, CurrentFrame), + { Type: ValueDtoType.Env, Name: not null } => + new EnvName(dto.Name, EnvFrame), + _ => throw new ArgumentOutOfRangeException(nameof(dto)) + }; + } + + public Name Create(string id) => new(id, CurrentFrame); + + private CurrentFrame CurrentFrame { get; } = new(frameContext); + + private EnvFrame EnvFrame { get; } = new(provider); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/ServiceCollectionExtensions.cs b/src/Application/HydraScript.Application.CodeGeneration/ServiceCollectionExtensions.cs index b79c4be5..bd3254d8 100644 --- a/src/Application/HydraScript.Application.CodeGeneration/ServiceCollectionExtensions.cs +++ b/src/Application/HydraScript.Application.CodeGeneration/ServiceCollectionExtensions.cs @@ -10,7 +10,7 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddCodeGeneration(this IServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); services.AddKeyedSingleton< IVisitor, InstructionProvider>(CodeGeneratorType.General); diff --git a/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs b/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs index 82802b41..a34b5ea0 100644 --- a/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs +++ b/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs @@ -5,6 +5,7 @@ using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Create; using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Read; using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Write; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ExplicitCast; using HydraScript.Domain.BackEnd.Impl.Instructions.WithJump; using HydraScript.Domain.BackEnd.Impl.Values; using HydraScript.Domain.FrontEnd.Parser; @@ -32,21 +33,21 @@ internal class ExpressionInstructionProvider : VisitorBase, IVisitor { - private readonly IValueDtoConverter _valueDtoConverter; + private readonly IValueFactory _valueFactory; - public ExpressionInstructionProvider(IValueDtoConverter valueDtoConverter) => - _valueDtoConverter = valueDtoConverter; + public ExpressionInstructionProvider(IValueFactory valueFactory) => + _valueFactory = valueFactory; public override AddressedInstructions Visit(IAbstractSyntaxTreeNode visitable) => []; public AddressedInstructions Visit(PrimaryExpression visitable) => - [new Simple(_valueDtoConverter.Convert(visitable.ToValueDto()))]; + [new Simple(_valueFactory.Create(visitable.ToValueDto()))]; public AddressedInstructions Visit(ArrayLiteral visitable) { var arraySize = visitable.Expressions.Count; - var arrayName = visitable.Id; + var arrayName = _valueFactory.Create(visitable.Id); var createArray = new CreateArray(arrayName, arraySize); var result = new AddressedInstructions { createArray }; @@ -60,11 +61,11 @@ public AddressedInstructions Visit(ArrayLiteral visitable) result.Add(new IndexAssignment( arrayName, index, - _valueDtoConverter.Convert(primary.ToValueDto()))); + _valueFactory.Create(primary.ToValueDto()))); else { result.AddRange(expression.Accept(This)); - var last = new Name(result.OfType().Last().Left!); + var last = result.OfType().Last().Left!; result.Add(new IndexAssignment(arrayName, index, last)); } } @@ -74,7 +75,7 @@ public AddressedInstructions Visit(ArrayLiteral visitable) public AddressedInstructions Visit(ObjectLiteral visitable) { - var objectId = visitable.Id; + var objectId = _valueFactory.Create(visitable.Id); var createObject = new CreateObject(objectId); var result = new AddressedInstructions { createObject }; @@ -89,7 +90,7 @@ public AddressedInstructions Visit(ObjectLiteral visitable) public AddressedInstructions Visit(Property visitable) { - var objectId = visitable.Object.Id; + var objectId = _valueFactory.Create(visitable.Object.Id); var (id, expression) = visitable; var propertyId = new Constant(id); @@ -98,10 +99,10 @@ public AddressedInstructions Visit(Property visitable) return [new DotAssignment( objectId, propertyId, - _valueDtoConverter.Convert(primary.ToValueDto()))]; + _valueFactory.Create(primary.ToValueDto()))]; var instructions = expression.Accept(This); - var last = new Name(instructions.OfType().Last().Left!); + var last = instructions.OfType().Last().Left!; instructions.Add(new DotAssignment(objectId, propertyId, last)); return instructions; @@ -110,10 +111,10 @@ public AddressedInstructions Visit(Property visitable) public AddressedInstructions Visit(UnaryExpression visitable) { if (visitable.Expression is PrimaryExpression primary) - return [new Simple(visitable.Operator, _valueDtoConverter.Convert(primary.ToValueDto()))]; + return [new Simple(visitable.Operator, _valueFactory.Create(primary.ToValueDto()))]; var result = visitable.Expression.Accept(This); - var last = new Name(result.OfType().Last().Left!); + var last = result.OfType().Last().Left!; result.Add(new Simple(visitable.Operator, last)); return result; @@ -122,25 +123,25 @@ public AddressedInstructions Visit(UnaryExpression visitable) public AddressedInstructions Visit(BinaryExpression visitable) { if (visitable is { Left: IdentifierReference arr, Right: PrimaryExpression primary, Operator: "::" }) - return [new RemoveFromArray(arr.Name, index: _valueDtoConverter.Convert(primary.ToValueDto()))]; + return [new RemoveFromArray(_valueFactory.Create(arr), index: _valueFactory.Create(primary.ToValueDto()))]; var result = new AddressedInstructions(); IValue left, right; if (visitable.Left is PrimaryExpression primaryLeft) - left = _valueDtoConverter.Convert(primaryLeft.ToValueDto()); + left = _valueFactory.Create(primaryLeft.ToValueDto()); else { result.AddRange(visitable.Left.Accept(This)); - left = new Name(result.OfType().Last().Left!); + left = result.OfType().Last().Left!; } if (visitable.Right is PrimaryExpression primaryRight) - right = _valueDtoConverter.Convert(primaryRight.ToValueDto()); + right = _valueFactory.Create(primaryRight.ToValueDto()); else { result.AddRange(visitable.Right.Accept(This)); - right = new Name(result.OfType().Last().Left!); + right = result.OfType().Last().Left!; } result.Add(new Simple(left, visitable.Operator, right)); @@ -150,19 +151,28 @@ public AddressedInstructions Visit(BinaryExpression visitable) public AddressedInstructions Visit(CastAsExpression visitable) { + Func asFactory = visitable.ToType switch + { + CastAsExpression.DestinationType.Undefined => throw new NotSupportedException(), + CastAsExpression.DestinationType.String => value => new AsString(value), + CastAsExpression.DestinationType.Number => value => new AsNumber(value), + CastAsExpression.DestinationType.Boolean => value => new AsBool(value), + _ => throw new ArgumentOutOfRangeException(nameof(visitable.ToType)) + }; + if (visitable.Expression is PrimaryExpression primary) - return [new AsString(_valueDtoConverter.Convert(primary.ToValueDto()))]; - + return [asFactory(_valueFactory.Create(primary.ToValueDto()))]; + var result = visitable.Expression.Accept(This); - var last = new Name(result.OfType().Last().Left!); - result.Add(new AsString(last)); - + var last = result.OfType().Last().Left!; + result.Add(asFactory(last)); + return result; } public AddressedInstructions Visit(WithExpression visitable) { - var objectId = visitable.ObjectLiteral.Id; + var objectId = _valueFactory.Create(visitable.ObjectLiteral.Id); var createObject = new CreateObject(objectId); var result = new AddressedInstructions { createObject }; @@ -187,15 +197,15 @@ public AddressedInstructions Visit(WithExpression visitable) result.AddRange(visitable.Expression is PrimaryExpression ? [] : visitable.Expression.Accept(This)); var copyFrom = visitable.Expression is IdentifierReference objectIdent - ? new Name(objectIdent) - : new Name(result.OfType().Last().Left!); + ? _valueFactory.Create(objectIdent) + : result.OfType().Last().Left!; for (var i = 0; i < visitable.ComputedCopiedProperties.Count; i++) { var property = new Constant(visitable.ComputedCopiedProperties[i]); result.Add(new DotRead(copyFrom, property)); var read = result[result.End].Address.Name; - result.Add(new DotAssignment(objectId, property, new Name(read))); + result.Add(new DotAssignment(objectId, property, _valueFactory.Create(read))); } return result; @@ -210,11 +220,11 @@ public AddressedInstructions Visit(ConditionalExpression visitable) var result = new AddressedInstructions(); if (visitable.Test is PrimaryExpression primary) - result.Add(new IfNotGoto(test: _valueDtoConverter.Convert(primary.ToValueDto()), startBlockLabel)); + result.Add(new IfNotGoto(test: _valueFactory.Create(primary.ToValueDto()), startBlockLabel)); else { result.AddRange(visitable.Test.Accept(This)); - var last = new Name(result.OfType().Last().Left!); + var last = result.OfType().Last().Left!; result.Add(new IfNotGoto(last, startBlockLabel)); } @@ -227,7 +237,7 @@ public AddressedInstructions Visit(ConditionalExpression visitable) result.OfType().Last().Left = temp; result.Add(new EndBlock(BlockType.Condition, blockId), endBlockLabel.Name); - result.Add(new Simple(new Name(temp))); + result.Add(new Simple(temp)); return result; } @@ -241,14 +251,14 @@ public AddressedInstructions Visit(AssignmentExpression visitable) if (last is IWriteToComplexData assignment) result.Add(assignment.ToSimple()); else - result.Add(new Simple(new Name(last.Left!))); + result.Add(new Simple(last.Left!)); } if (visitable.Destination.Empty()) - result.OfType().Last().Left = visitable.Destination.Id; + result.OfType().Last().Left = _valueFactory.Create(visitable.Destination.Id); else { - var last = new Name(result.OfType().Last().Left!); + var last = result.OfType().Last().Left!; result.AddRange(visitable.Destination.Accept(This)); var lastRead = result.OfType().Last(); result.Replace(lastRead.ToInstruction(), lastRead.ToAssignment(last)); @@ -267,10 +277,10 @@ public AddressedInstructions Visit(DotAccess visitable) var right = new Constant(visitable.Property.Name); if (!visitable.HasPrev() && visitable.Parent is LeftHandSideExpression lhs) - return [new DotRead(new Name(lhs.Id), right)]; + return [new DotRead(_valueFactory.Create(lhs.Id), right)]; var result = visitable.Prev?.Accept(This) ?? []; - var left = new Name(result.OfType().Last().Left!); + var left = result.OfType().Last().Left!; result.Add(new DotRead(left, right)); return result; @@ -283,19 +293,19 @@ public AddressedInstructions Visit(IndexAccess visitable) IValue right; if (visitable.Index is PrimaryExpression primary) - right = _valueDtoConverter.Convert(primary.ToValueDto()); + right = _valueFactory.Create(primary.ToValueDto()); else { result.AddRange(visitable.Index.Accept(This)); - right = new Name(result.OfType().Last().Left!); + right = result.OfType().Last().Left!; } if (!visitable.HasPrev() && visitable.Parent is LeftHandSideExpression lhs) - result.Add(new IndexRead(new Name(lhs.Id), right)); + result.Add(new IndexRead(_valueFactory.Create(lhs.Id), right)); else { result.AddRange(visitable.Prev?.Accept(This) ?? []); - var left = new Name(result.OfType().Last().Left!); + var left = result.OfType().Last().Left!; result.Add(new IndexRead(left, right)); } @@ -320,20 +330,22 @@ public AddressedInstructions Visit(CallExpression visitable) if (methodCall) { - var caller = result.Any() ? result.OfType().Last().Left! : visitable.Id; - result.Add(new PushParameter(new Name(caller))); + var caller = result.Count > 0 + ? result.OfType().Last().Left! + : _valueFactory.Create(visitable.Id); + result.Add(new PushParameter(caller)); } for (var i = 0; i < visitable.Parameters.Count; i++) { var expr = visitable.Parameters[i]; if (expr is PrimaryExpression primary) - result.Add(new PushParameter(_valueDtoConverter.Convert(primary.ToValueDto()))); + result.Add(new PushParameter(_valueFactory.Create(primary.ToValueDto()))); else { result.AddRange(expr.Accept(This)); var id = result.OfType().Last().Left!; - result.Add(new PushParameter(new Name(id))); + result.Add(new PushParameter(id)); } } diff --git a/src/Application/HydraScript.Application.CodeGeneration/Visitors/InstructionProvider.cs b/src/Application/HydraScript.Application.CodeGeneration/Visitors/InstructionProvider.cs index 4fb64d7f..4eab8f98 100644 --- a/src/Application/HydraScript.Application.CodeGeneration/Visitors/InstructionProvider.cs +++ b/src/Application/HydraScript.Application.CodeGeneration/Visitors/InstructionProvider.cs @@ -2,8 +2,8 @@ using HydraScript.Domain.BackEnd.Impl.Addresses; using HydraScript.Domain.BackEnd.Impl.Instructions; using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ExplicitCast; using HydraScript.Domain.BackEnd.Impl.Instructions.WithJump; -using HydraScript.Domain.BackEnd.Impl.Values; using HydraScript.Domain.Constants; using HydraScript.Domain.FrontEnd.Parser; using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; @@ -24,17 +24,18 @@ internal class InstructionProvider : VisitorBase, IVisitor, IVisitor, - IVisitor + IVisitor, + IVisitor { - private readonly IValueDtoConverter _valueDtoConverter; + private readonly IValueFactory _valueFactory; private readonly IVisitor _expressionVisitor; public InstructionProvider( - IValueDtoConverter valueDtoConverter, + IValueFactory valueFactory, [FromKeyedServices(CodeGeneratorType.Expression)] IVisitor expressionVisitor) { - _valueDtoConverter = valueDtoConverter; + _valueFactory = valueFactory; _expressionVisitor = expressionVisitor; } @@ -103,11 +104,11 @@ public AddressedInstructions Visit(ReturnStatement visitable) case null: return [new Return()]; case PrimaryExpression primary: - return [new Return(_valueDtoConverter.Convert(primary.ToValueDto()))]; + return [new Return(_valueFactory.Create(primary.ToValueDto()))]; } var result = visitable.Expression.Accept(_expressionVisitor); - var last = new Name(result.OfType().Last().Left!); + var last = result.OfType().Last().Left!; result.Add(new Return(last)); return result; @@ -115,7 +116,7 @@ public AddressedInstructions Visit(ReturnStatement visitable) public AddressedInstructions Visit(FunctionDeclaration visitable) { - if (!visitable.Statements.Any()) + if (visitable.IsEmpty) return []; var functionInfo = new FunctionInfo(visitable.ComputedFunctionAddress); @@ -132,10 +133,7 @@ public AddressedInstructions Visit(FunctionDeclaration visitable) for (var i = 0; i < visitable.Arguments.Count; i++) { var arg = visitable.Arguments[i]; - if (arg is DefaultValueArgument @default) - result.Add(new PopParameter(arg.Name, @default.Info.Value)); - else - result.Add(new PopParameter(arg.Name)); + result.Add(new PopParameter(_valueFactory.Create(arg.Name), arg.Info.Value)); } result.AddRange(visitable.Statements.Accept(This)); @@ -159,11 +157,11 @@ public AddressedInstructions Visit(WhileStatement visitable) }; if (visitable.Condition is PrimaryExpression primary) - result.Add(new IfNotGoto(test: _valueDtoConverter.Convert(primary.ToValueDto()), endBlockLabel)); + result.Add(new IfNotGoto(test: _valueFactory.Create(primary.ToValueDto()), endBlockLabel)); else { result.AddRange(visitable.Condition.Accept(_expressionVisitor)); - var last = new Name(result.OfType().Last().Left!); + var last = result.OfType().Last().Left!; result.Add(new IfNotGoto(last, endBlockLabel)); } @@ -193,7 +191,7 @@ public AddressedInstructions Visit(WhileStatement visitable) public AddressedInstructions Visit(IfStatement visitable) { - if (visitable.Empty()) + if (visitable.Empty) return []; var blockId = $"if_else_{visitable.GetHashCode()}"; @@ -203,11 +201,11 @@ public AddressedInstructions Visit(IfStatement visitable) var result = new AddressedInstructions(); if (visitable.Test is PrimaryExpression primary) - result.Add(new IfNotGoto(test: _valueDtoConverter.Convert(primary.ToValueDto()), startBlockLabel)); + result.Add(new IfNotGoto(test: _valueFactory.Create(primary.ToValueDto()), startBlockLabel)); else { result.AddRange(visitable.Test.Accept(_expressionVisitor)); - var last = new Name(result.OfType().Last().Left!); + var last = result.OfType().Last().Left!; result.Add(new IfNotGoto(last, visitable.HasElseBlock() ? startBlockLabel @@ -233,21 +231,32 @@ public AddressedInstructions Visit(IfStatement visitable) return result; } - public AddressedInstructions Visit(PrintStatement visitable) + public AddressedInstructions Visit(OutputStatement visitable) { - AddressedInstructions result = []; - if (visitable.Expression is PrimaryExpression prim) - result.Add(new AsString(_valueDtoConverter.Convert(prim.ToValueDto()))); - else { - result.AddRange(visitable.Expression.Accept(_expressionVisitor)); - var name = new Name(result.OfType().Last().Left!); - result.Add(new AsString(name)); + var valueDto = prim.ToValueDto(); + var printedValue = _valueFactory.Create(valueDto); + IExecutableInstruction instruction = valueDto is { Type: ValueDtoType.Env } or { Type: ValueDtoType.Constant, Value: string } + ? new Output(printedValue) + : new AsString(printedValue); + AddressedInstructions shortResult = [instruction]; + if (instruction is AsString asString) + shortResult.Add(new Output(asString.Left!)); + return shortResult; } - result.Add(new Print(new Name((result[result.End] as AsString)!.Left!))); + AddressedInstructions result = []; + + result.AddRange(visitable.Expression.Accept(_expressionVisitor)); + var name = result.OfType().Last().Left!; + var nameAsString = new AsString(name); + result.Add(nameAsString); + result.Add(new Output(nameAsString.Left!)); return result; } + + public AddressedInstructions Visit(InputStatement visitable) => + [new Input(_valueFactory.Create(visitable.Destination))]; } \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ExplicitCastNotSupported.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ExplicitCastNotSupported.cs new file mode 100644 index 00000000..09ac2523 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ExplicitCastNotSupported.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class ExplicitCastNotSupported(CastAsExpression cast, Type from, Type to) : + SemanticException(cast.Segment, $"Cast from {from} to {to} is not supported"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IDefaultValueForTypeCalculator.cs b/src/Application/HydraScript.Application.StaticAnalysis/IDefaultValueForTypeCalculator.cs deleted file mode 100644 index ba34c898..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/IDefaultValueForTypeCalculator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace HydraScript.Application.StaticAnalysis; - -public interface IDefaultValueForTypeCalculator -{ - public object? GetDefaultValueForType(Type type); -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs b/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs new file mode 100644 index 00000000..cabbd2d3 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs @@ -0,0 +1,22 @@ +namespace HydraScript.Application.StaticAnalysis; + +public interface IHydraScriptTypesService +{ + public Type Number { get; } + + public Type Boolean { get; } + + public Type String { get; } + + public Type Undefined { get; } + + public Type Void { get; } + + public IEnumerable GetDefaultTypes(); + + public bool Contains(Type type); + + public object? GetDefaultValueForType(Type type); + + public bool IsExplicitCastAllowed(Type from, Type to); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs deleted file mode 100644 index 42e79dac..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace HydraScript.Application.StaticAnalysis; - -public interface IJavaScriptTypesProvider -{ - public IEnumerable GetDefaultTypes(); - - public bool Contains(Type type); -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs b/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs index d7ac8ff2..99553c85 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs @@ -7,4 +7,6 @@ public interface ITypeDeclarationsResolver public void Store(TypeDeclaration declaration); public void Resolve(); + + IHydraScriptTypesService TypesService { get; } } \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs deleted file mode 100644 index d42b560a..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using HydraScript.Domain.IR.Types; - -namespace HydraScript.Application.StaticAnalysis.Impl; - -internal class DefaultValueForTypeCalculator : IDefaultValueForTypeCalculator -{ - private readonly Type _boolean = "boolean"; - private readonly Type _number = "number"; - private readonly Type _string = "string"; - private readonly Type _void = "void"; - private readonly Type _null = new NullType(); - - public object? GetDefaultValueForType(Type type) - { - if (type is NullableType) - return null; - if (type.Equals(_boolean)) - return false; - if (type.Equals(_number)) - return 0; - if (type.Equals(_string)) - return string.Empty; - if (type.Equals(_void)) - return new object(); - if (type.Equals(_null)) - return null; - if (type is ArrayType) - return new List(); - - return new object(); - } -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs new file mode 100644 index 00000000..013ace91 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs @@ -0,0 +1,83 @@ +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Application.StaticAnalysis.Impl; + +internal class HydraScriptTypesService : IHydraScriptTypesService +{ + private readonly HashSet _types; + private readonly Dictionary> _allowedConversions; + + public HydraScriptTypesService() + { + _types = + [ + Number, + Boolean, + String, + Null, + Undefined, + Void + ]; + _allowedConversions = new() + { + [String] = [new Any()], + [Number] = [String, Boolean], + [Boolean] = [String, Number], + }; + } + + public Type Number => NumberType.Instance; + + public Type Boolean => BooleanType.Instance; + + public Type String => StringType.Instance; + + private Type Null => NullType.Instance; + + public Type Undefined => "undefined"; + + public Type Void => "void"; + + public IEnumerable GetDefaultTypes() => _types; + + public bool Contains(Type type) => _types.Contains(type); + + public object? GetDefaultValueForType(Type type) + { + if (type is NullableType) + return null; + if (type.Equals(Boolean)) + return false; + if (type.Equals(Number)) + return 0; + if (type.Equals(String)) + return string.Empty; + if (type.Equals(Void)) + return new object(); + if (type.Equals(Null)) + return null; + if (type is ArrayType) + return new List(); + + return new object(); + } + + public bool IsExplicitCastAllowed(Type from, Type to) + { + var typeEqualityComparer = default(CommutativeTypeEqualityComparer); + + if (typeEqualityComparer.Equals(from, to)) + return true; + + if (!_allowedConversions.TryGetValue(to, out var allowedFrom)) + return false; + + for (var i = 0; i < allowedFrom.Count; i++) + { + if (typeEqualityComparer.Equals(allowedFrom[i], from)) + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs deleted file mode 100644 index 425a08a5..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs +++ /dev/null @@ -1,20 +0,0 @@ -using HydraScript.Domain.IR.Types; - -namespace HydraScript.Application.StaticAnalysis.Impl; - -internal class JavaScriptTypesProvider : IJavaScriptTypesProvider -{ - private readonly HashSet _types = - [ - "number", - "boolean", - "string", - new NullType(), - "undefined", - "void" - ]; - - public IEnumerable GetDefaultTypes() => _types; - - public bool Contains(Type type) => _types.Contains(type); -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs index 19506663..20f174fd 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs @@ -4,18 +4,13 @@ namespace HydraScript.Application.StaticAnalysis.Impl; -internal class StandardLibraryProvider : IStandardLibraryProvider +internal class StandardLibraryProvider(IHydraScriptTypesService typesService) : IStandardLibraryProvider { - private readonly IJavaScriptTypesProvider _provider; - - public StandardLibraryProvider(IJavaScriptTypesProvider provider) => - _provider = provider; - public ISymbolTable GetStandardLibrary() { var library = new SymbolTable(); - foreach (var type in _provider.GetDefaultTypes()) + foreach (var type in typesService.GetDefaultTypes()) library.AddSymbol(new TypeSymbol(type)); var symbolTable = new SymbolTable(); diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs index f75c8b42..6c2e1bd0 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs @@ -5,7 +5,7 @@ namespace HydraScript.Application.StaticAnalysis.Impl; internal class TypeDeclarationsResolver( - IJavaScriptTypesProvider provider, + IHydraScriptTypesService typesService, ISymbolTableStorage symbolTables, IVisitor typeBuilder) : ITypeDeclarationsResolver { @@ -16,7 +16,7 @@ public void Store(TypeDeclaration declaration) => public void Resolve() { - var defaults = provider.GetDefaultTypes() + var defaults = TypesService.GetDefaultTypes() .AsValueEnumerable() .Select(x => new TypeSymbol(x)) .ToList(); @@ -43,4 +43,6 @@ public void Resolve() } } } + + public IHydraScriptTypesService TypesService { get; } = typesService; } \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs b/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs index d7f9cf40..ae8dd7b3 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs @@ -19,8 +19,7 @@ public static IServiceCollection AddStaticAnalysis(this IServiceCollection servi services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs index be503f12..7bb0f789 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs @@ -7,7 +7,6 @@ using HydraScript.Domain.IR.Impl.Symbols; using HydraScript.Domain.IR.Impl.Symbols.Ids; using HydraScript.Domain.IR.Types; -using ZLinq; namespace HydraScript.Application.StaticAnalysis.Visitors; @@ -15,6 +14,7 @@ internal class DeclarationVisitor : VisitorNoReturnBase IVisitor, IVisitor { + private readonly IHydraScriptTypesService _typesService; private readonly IFunctionWithUndefinedReturnStorage _functionStorage; private readonly IMethodStorage _methodStorage; private readonly ISymbolTableStorage _symbolTables; @@ -23,6 +23,7 @@ internal class DeclarationVisitor : VisitorNoReturnBase private readonly IVisitor _returnAnalyzer; public DeclarationVisitor( + IHydraScriptTypesService typesService, IFunctionWithUndefinedReturnStorage functionStorage, IMethodStorage methodStorage, ISymbolTableStorage symbolTables, @@ -30,6 +31,7 @@ public DeclarationVisitor( IVisitor typeBuilder, IVisitor returnAnalyzer) { + _typesService = typesService; _functionStorage = functionStorage; _methodStorage = methodStorage; _symbolTables = symbolTables; @@ -55,9 +57,9 @@ public VisitUnit Visit(LexicalDeclaration visitable) throw new DeclarationAlreadyExists(assignment.Destination.Id); var destinationType = assignment.DestinationType?.Accept( - _typeBuilder) ?? "undefined"; + _typeBuilder) ?? _typesService.Undefined; - if (destinationType == "undefined" && + if (destinationType == _typesService.Undefined && assignment.Source is ImplicitLiteral or ArrayLiteral { Expressions.Count: 0 }) throw visitable.ReadOnly ? new ConstWithoutInitializer(assignment.Destination.Id) @@ -79,16 +81,16 @@ public VisitUnit Visit(FunctionDeclaration visitable) visitable.AllCodePathsEndedWithReturn = returnAnalyzerResult.CodePathEndedWithReturn; var parentTable = _symbolTables[visitable.Parent.Scope]; - var indexOfFirstDefaultArgument = visitable.Arguments.AsValueEnumerable() - .Select((x, i) => new { Argument = x, Index = i }) - .FirstOrDefault(pair => pair.Argument is DefaultValueArgument)?.Index ?? -1; - var parameters = visitable.Arguments.AsValueEnumerable() - .Select(x => - new VariableSymbol( - x.Name, - x.TypeValue.Accept(_typeBuilder))).ToList(); - var functionSymbolId = new FunctionSymbolId(visitable.Name, parameters.Select(x => x.Type)); + var parameters = new List(); + for (var i = 0; i < visitable.Arguments.Count; i++) + { + parameters.Add(visitable.Arguments[i].TypeValue.Accept(_typeBuilder)); + var arg = new VariableSymbol(visitable.Arguments[i].Name, parameters[i]); + arg.Initialize(); + _symbolTables[visitable.Scope].AddSymbol(arg); + } + var functionSymbolId = new FunctionSymbolId(visitable.Name, parameters); _ambiguousInvocations.Clear(functionSymbolId); visitable.ComputedFunctionAddress = functionSymbolId.ToString(); var functionSymbol = new FunctionSymbol( @@ -99,46 +101,34 @@ public VisitUnit Visit(FunctionDeclaration visitable) if (functionSymbolId.Equals(parentTable.FindSymbol(functionSymbolId)?.Id)) throw new OverloadAlreadyExists(visitable.Name, functionSymbolId); - for (var i = 0; i < parameters.Count; i++) - { - var arg = parameters[i]; - arg.Initialize(); - _symbolTables[visitable.Scope].AddSymbol(arg); - } - - var isMethod = - parameters is [{ Type: ObjectType }, ..] && - visitable.Arguments is [{ TypeValue: TypeIdentValue }, ..]; - if (isMethod) - _methodStorage.BindMethod((parameters[0].Type as ObjectType)!, functionSymbol, functionSymbolId); + if (parameters is [ObjectType methodOwner, ..] && visitable.Arguments is [{ TypeValue: TypeIdentValue }, ..]) + _methodStorage.BindMethod(methodOwner, functionSymbol, functionSymbolId); - Type undefined = "undefined"; - if (functionSymbol.Type.Equals(undefined)) + if (functionSymbol.Type.Equals(_typesService.Undefined)) { if (visitable.HasReturnStatement) _functionStorage.Save(functionSymbol, visitable); else - functionSymbol.DefineReturnType("void"); + functionSymbol.DefineReturnType(_typesService.Void); } parentTable.AddSymbol(functionSymbol); - for (var i = indexOfFirstDefaultArgument; i < visitable.Arguments.Count; i++) + for (var i = visitable.IndexOfFirstDefaultArgument; i < visitable.Arguments.Count; i++) { - if (i is -1) break; - if (visitable.Arguments[i] is not DefaultValueArgument) + if (visitable.Arguments[i].Info.Type is ValueDtoType.Name) throw new NamedArgumentAfterDefaultValueArgument( visitable.Segment, function: visitable.Name, visitable.Arguments[i]); - var overload = new FunctionSymbolId(visitable.Name, parameters[..i].Select(x => x.Type)); + var overload = new FunctionSymbolId(visitable.Name, parameters[..i]); var existing = parentTable.FindSymbol(overload); var functionToAdd = existing is not null && existing < functionSymbol ? existing : functionSymbol; parentTable.AddSymbol(functionToAdd, overload); - if (isMethod) - _methodStorage.BindMethod((parameters[0].Type as ObjectType)!, functionToAdd, overload); + if (parameters is [ObjectType overloadOwner, ..] && visitable.Arguments is [{ TypeValue: TypeIdentValue }, ..]) + _methodStorage.BindMethod(overloadOwner, functionToAdd, overload); if (existing is not null && !existing.Id.Equals(overload)) { diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/ReturnAnalyzer.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/ReturnAnalyzer.cs index 30b4f91c..94f8fa7b 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/ReturnAnalyzer.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/ReturnAnalyzer.cs @@ -4,42 +4,53 @@ namespace HydraScript.Application.StaticAnalysis.Visitors; -internal class ReturnAnalyzer : VisitorBase, +internal class ReturnAnalyzer : VisitorBase, IVisitor, - IVisitor, - IVisitor + IVisitor, + IVisitor { + private readonly List _returnStatements = []; + public ReturnAnalyzerResult Visit(FunctionDeclaration visitable) { IAbstractSyntaxTreeNode astNode = visitable; - return Visit(astNode); + var codePathEndedWithReturn= Visit(astNode); + var returnStatements = new List(_returnStatements); + ReturnAnalyzerResult result = new(codePathEndedWithReturn, returnStatements); + _returnStatements.Clear(); + return result; } - public override ReturnAnalyzerResult Visit(IAbstractSyntaxTreeNode visitable) + public override bool Visit(IAbstractSyntaxTreeNode visitable) { - var result = ReturnAnalyzerResult.AdditiveIdentity; for (var i = 0; i < visitable.Count; i++) { var visitableResult = visitable[i].Accept(This); - if (visitableResult.CodePathEndedWithReturn) - return visitableResult * result; - result += visitableResult; + if (visitableResult) + return true; } - return result; + return false; } - public ReturnAnalyzerResult Visit(IfStatement visitable) + public bool Visit(IfStatement visitable) { var thenReturns = visitable.Then.Accept(This); if (visitable.Else is null) - return thenReturns + ReturnAnalyzerResult.AdditiveIdentity; + return false; var elseReturns = visitable.Else.Accept(This); - return thenReturns + elseReturns; + return thenReturns && elseReturns; + } + + public bool Visit(ReturnStatement visitable) + { + _returnStatements.Add(visitable); + return true; } +} - public ReturnAnalyzerResult Visit(ReturnStatement visitable) => - new(CodePathEndedWithReturn: true, ReturnStatements: [visitable]); -} \ No newline at end of file +public sealed record ReturnAnalyzerResult( + bool CodePathEndedWithReturn, + IReadOnlyList ReturnStatements); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/ReturnAnalyzerResult.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/ReturnAnalyzerResult.cs deleted file mode 100644 index f9c2fb43..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/ReturnAnalyzerResult.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Numerics; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; - -namespace HydraScript.Application.StaticAnalysis.Visitors; - -public sealed record ReturnAnalyzerResult(bool CodePathEndedWithReturn, IReadOnlyList ReturnStatements) : - IAdditiveIdentity, - IAdditionOperators, - IMultiplyOperators -{ - public static ReturnAnalyzerResult operator +(ReturnAnalyzerResult left, ReturnAnalyzerResult right) => - new( - left.CodePathEndedWithReturn && right.CodePathEndedWithReturn, - ReturnStatements: [..left.ReturnStatements, ..right.ReturnStatements]); - - public static ReturnAnalyzerResult AdditiveIdentity { get; } = new(CodePathEndedWithReturn: false, ReturnStatements: []); - - public static ReturnAnalyzerResult operator *(ReturnAnalyzerResult left, ReturnAnalyzerResult right) => - new( - left.CodePathEndedWithReturn || right.CodePathEndedWithReturn, - ReturnStatements: [..left.ReturnStatements, ..right.ReturnStatements]); -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs index a0990dcf..fa217df2 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs @@ -24,6 +24,7 @@ internal class SemanticChecker : VisitorBase, IVisitor, IVisitor, IVisitor, + IVisitor, IVisitor, IVisitor, IVisitor, @@ -41,9 +42,10 @@ internal class SemanticChecker : VisitorBase, IVisitor, IVisitor, IVisitor, - IVisitor + IVisitor, + IVisitor { - private readonly IDefaultValueForTypeCalculator _calculator; + private readonly IHydraScriptTypesService _typesService; private readonly IFunctionWithUndefinedReturnStorage _functionStorage; private readonly IMethodStorage _methodStorage; private readonly ISymbolTableStorage _symbolTables; @@ -52,7 +54,7 @@ internal class SemanticChecker : VisitorBase, private readonly IVisitor _typeBuilder; public SemanticChecker( - IDefaultValueForTypeCalculator calculator, + IHydraScriptTypesService typesService, IFunctionWithUndefinedReturnStorage functionStorage, IMethodStorage methodStorage, ISymbolTableStorage symbolTables, @@ -60,7 +62,7 @@ public SemanticChecker( IAmbiguousInvocationStorage ambiguousInvocations, IVisitor typeBuilder) { - _calculator = calculator; + _typesService = typesService; _functionStorage = functionStorage; _methodStorage = methodStorage; _symbolTables = symbolTables; @@ -69,7 +71,7 @@ public SemanticChecker( _typeBuilder = typeBuilder; } - public override Type Visit(IAbstractSyntaxTreeNode visitable) => "undefined"; + public override Type Visit(IAbstractSyntaxTreeNode visitable) => _typesService.Undefined; public Type Visit(ScriptBody visitable) { @@ -83,33 +85,31 @@ public Type Visit(ScriptBody visitable) _symbolTables.Clear(); _computedTypes.Clear(); _ambiguousInvocations.Clear(); - - return "undefined"; + + return _typesService.Undefined; } public Type Visit(WhileStatement visitable) { var condType = visitable.Condition.Accept(This); - Type boolean = "boolean"; - if (!condType.Equals(boolean)) + if (!condType.Equals(_typesService.Boolean)) throw new NotBooleanTestExpression(visitable.Segment, condType); visitable.Statement.Accept(This); - return "undefined"; + return _typesService.Undefined; } public Type Visit(IfStatement visitable) { var testType = visitable.Test.Accept(This); - Type boolean = "boolean"; - if (!testType.Equals(boolean)) + if (!testType.Equals(_typesService.Boolean)) throw new NotBooleanTestExpression(visitable.Segment, testType); visitable.Then.Accept(This); visitable.Else?.Accept(This); - return "undefined"; + return _typesService.Undefined; } public Type Visit(InsideStatementJump visitable) @@ -132,7 +132,7 @@ public Type Visit(InsideStatementJump visitable) break; } - return "undefined"; + return _typesService.Undefined; } public Type Visit(ReturnStatement visitable) @@ -140,7 +140,7 @@ public Type Visit(ReturnStatement visitable) if (!visitable.ChildOf()) throw new ReturnOutsideFunction(visitable.Segment); - return visitable.Expression?.Accept(This) ?? "void"; + return visitable.Expression?.Accept(This) ?? _typesService.Void; } public Type Visit(ExpressionStatement visitable) => @@ -154,13 +154,20 @@ public Type Visit(IdentifierReference visitable) return symbol?.Type ?? throw new UnknownIdentifierReference(visitable); } + public Type Visit(EnvVarReference visitable) => _typesService.String; + public Type Visit(Literal visitable) => visitable.Type.Accept(_typeBuilder); public Type Visit(ImplicitLiteral visitable) { var type = visitable.Type.Accept(_typeBuilder); - visitable.ComputedDefaultValue = _calculator.GetDefaultValueForType(type); + if (!visitable.IsDefined) + { + var definedValue = _typesService.GetDefaultValueForType(type); + visitable.SetValue(definedValue); + } + return type; } @@ -207,8 +214,7 @@ public ObjectType Visit(ObjectLiteral visitable) public Type Visit(ConditionalExpression visitable) { var tType = visitable.Test.Accept(This); - Type boolean = "boolean"; - if (!tType.Equals(boolean)) + if (!tType.Equals(_typesService.Boolean)) throw new NotBooleanTestExpression(visitable.Test.Segment, tType); var cType = visitable.Consequent.Accept(This); @@ -229,82 +235,61 @@ public Type Visit(BinaryExpression visitable) var lType = visitable.Left.Accept(This); var rType = visitable.Right.Accept(This); - if (visitable.Operator != "::" && !lType.Equals(rType)) + if (!lType.TryGetOperator(visitable.Operator, out var @operator)) + throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator); + + var operation = new OperationDescriptor(visitable.Operator, OperandTypes: [lType, rType]); + if (!@operator.TryGetResultType(operation, out var resultType)) throw new IncompatibleTypesOfOperands( visitable.Segment, left: lType, right: rType); - Type number = "number"; - Type @string = "string"; - Type boolean = "boolean"; + if (resultType.Equals(_typesService.Undefined)) + throw new CannotDefineType(visitable.Segment); - return visitable.Operator switch - { - "+" when lType.Equals(number) => number, - "+" when lType.Equals(@string) => @string, - "+" => throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "-" or "*" or "/" or "%" => lType.Equals(number) - ? number - : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "||" or "&&" => lType.Equals(boolean) - ? boolean - : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "==" or "!=" => boolean, - ">" or ">=" or "<" or "<=" => lType.Equals(number) - ? boolean - : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "++" when lType is ArrayType { Type: Any } && rType is ArrayType { Type: Any } => - throw new CannotDefineType(visitable.Segment), - "++" => lType is ArrayType lArrType && rType is ArrayType rArrType - ? lArrType.Type is not Any ? lArrType : rArrType.Type is not Any ? rArrType : throw new CannotDefineType(visitable.Segment) - : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "::" when lType is not ArrayType => - throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "::" => rType.Equals(number) ? "void" : throw new ArrayAccessException(visitable.Segment, rType), - _ => "undefined" - }; + return resultType; } public Type Visit(UnaryExpression visitable) { var eType = visitable.Expression.Accept(This); - Type number = "number"; - Type boolean = "boolean"; + if (!eType.TryGetOperator(visitable.Operator, out var @operator)) + throw new UnsupportedOperation(visitable.Segment, eType, visitable.Operator); - return visitable.Operator switch - { - "-" when eType.Equals(number) => number, - "!" when eType.Equals(boolean) => boolean, - "~" when eType is ArrayType => number, - _ => throw new UnsupportedOperation(visitable.Segment, eType, visitable.Operator) - }; + var operation = new OperationDescriptor(visitable.Operator, OperandTypes: [eType]); + if (!@operator.TryGetResultType(operation, out var resultType)) + throw new UnsupportedOperation(visitable.Segment, eType, visitable.Operator); + + if (resultType.Equals(_typesService.Undefined)) + throw new CannotDefineType(visitable.Segment); + + return resultType; } public Type Visit(LexicalDeclaration visitable) { - Type undefined = "undefined", @void = "void"; - for (var i = 0; i < visitable.Assignments.Count; i++) { var assignment = visitable.Assignments[i]; var registeredSymbol = _symbolTables[visitable.Scope].FindSymbol(new VariableSymbolId(assignment.Destination.Id))!; var sourceType = assignment.Source.Accept(This); - if (sourceType.Equals(undefined)) + if (sourceType.Equals(_typesService.Undefined)) throw new CannotDefineType(assignment.Source.Segment); - if (sourceType.Equals(@void)) + if (sourceType.Equals(_typesService.Void)) throw new CannotAssignVoid(assignment.Source.Segment); - if (!registeredSymbol.Type.Equals(undefined) && !registeredSymbol.Type.Equals(sourceType)) + if (!registeredSymbol.Type.Equals(_typesService.Undefined) && + !default(CommutativeTypeEqualityComparer).Equals(registeredSymbol.Type, sourceType)) throw new IncompatibleTypesOfOperands( assignment.Segment, left: registeredSymbol.Type, right: sourceType); - if (sourceType is NullType && registeredSymbol.Type.Equals(undefined)) + if (sourceType is NullType && registeredSymbol.Type.Equals(_typesService.Undefined)) throw new CannotAssignNullWhenUndefined(assignment.Segment); - var actualType = registeredSymbol.Type.Equals(undefined) + var actualType = registeredSymbol.Type.Equals(_typesService.Undefined) ? sourceType : registeredSymbol.Type; var actualSymbol = actualType switch @@ -316,11 +301,13 @@ public Type Visit(LexicalDeclaration visitable) _symbolTables[visitable.Scope].AddSymbol(actualSymbol); } - return undefined; + return _typesService.Undefined; } public Type Visit(AssignmentExpression visitable) { + var typeComparer = default(CommutativeTypeEqualityComparer); + if (visitable.Destination is CallExpression) throw new WrongAssignmentTarget(visitable.Destination); @@ -328,7 +315,7 @@ public Type Visit(AssignmentExpression visitable) if (!visitable.Destination.Empty()) { var destinationType = visitable.Destination.Accept(This); - if (!destinationType.Equals(sourceType)) + if (!typeComparer.Equals(destinationType, sourceType)) throw new IncompatibleTypesOfOperands( visitable.Segment, left: destinationType, @@ -336,14 +323,16 @@ public Type Visit(AssignmentExpression visitable) return destinationType; } - var symbol = - _symbolTables[visitable.Scope].FindSymbol(new VariableSymbolId(visitable.Destination.Id)) ?? - throw new UnknownIdentifierReference(visitable.Destination.Id); + // здесь может быть переменная программы, а может быть переменная среды + var symbol = visitable.Destination.Id.ToValueDto().Type is ValueDtoType.Name + ? _symbolTables[visitable.Scope].FindSymbol(new VariableSymbolId(visitable.Destination.Id)) ?? + throw new UnknownIdentifierReference(visitable.Destination.Id) + : new VariableSymbol(visitable.Destination.Id, _typesService.String); if (symbol.ReadOnly) throw new AssignmentToConst(visitable.Destination.Id); - if (!sourceType.Equals(symbol.Type)) + if (!typeComparer.Equals(sourceType, symbol.Type)) throw new IncompatibleTypesOfOperands( visitable.Segment, left: symbol.Type, @@ -354,9 +343,10 @@ public Type Visit(AssignmentExpression visitable) public Type Visit(MemberExpression visitable) { - var idType = visitable.Id.Accept(This); + IAbstractSyntaxTreeNode id = visitable.Id; + var idType = id.Accept(This); visitable.ComputedIdTypeGuid = _computedTypes.Save(idType); - return visitable.Empty() ? idType : visitable.AccessChain?.Accept(This) ?? "undefined"; + return visitable.Empty() ? idType : visitable.AccessChain?.Accept(This) ?? _typesService.Undefined; } public Type Visit(IndexAccess visitable) @@ -366,17 +356,16 @@ public Type Visit(IndexAccess visitable) ?? (visitable.Parent as MemberExpression)!.ComputedIdTypeGuid; var prevType = _computedTypes.Get(prevTypeGuid); - if (prevType is not ArrayType arrayType) + if (!prevType.TryGetOperator("[]", out var indexOperator)) throw new NonAccessibleType(prevType); - Type number = "number"; var indexType = visitable.Index.Accept(This); - if (!indexType.Equals(number)) + var indexAccessDescriptor = new OperationDescriptor("[]", [prevType, indexType]); + if (!indexOperator.TryGetResultType(indexAccessDescriptor, out var elemType)) throw new ArrayAccessException(visitable.Segment, indexType); - var elemType = arrayType.Type; visitable.ComputedTypeGuid = _computedTypes.Save(elemType); - return visitable.HasNext() ? visitable.Next?.Accept(This) ?? "undefined" : elemType; + return visitable.HasNext() ? visitable.Next?.Accept(This) ?? _typesService.Undefined : elemType; } public Type Visit(DotAccess visitable) @@ -396,7 +385,7 @@ public Type Visit(DotAccess visitable) ? objectType : throw new ObjectAccessException(visitable.Segment, objectType, visitable.Property); visitable.ComputedTypeGuid = _computedTypes.Save(fieldType); - return visitable.HasNext() ? visitable.Next?.Accept(This) ?? "undefined" : fieldType; + return visitable.HasNext() ? visitable.Next?.Accept(This) ?? _typesService.Undefined : fieldType; } public ObjectType Visit(WithExpression visitable) @@ -422,15 +411,23 @@ public ObjectType Visit(WithExpression visitable) public Type Visit(CastAsExpression visitable) { - Type undefined = "undefined"; - var exprType = visitable.Expression.Accept(This); + var from = visitable.Expression.Accept(This); - if (exprType.Equals(undefined)) + if (from.Equals(_typesService.Undefined)) throw new CannotDefineType(visitable.Expression.Segment); - return visitable.Cast.Accept(_typeBuilder) == "string" - ? "string" - : throw new NotSupportedException("Other types but 'string' have not been supported for casting yet"); + var to = visitable.Cast.Accept(_typeBuilder); + visitable.ToType = to switch + { + _ when to.Equals(_typesService.String) => CastAsExpression.DestinationType.String, + _ when to.Equals(_typesService.Number) => CastAsExpression.DestinationType.Number, + _ when to.Equals(_typesService.Boolean) => CastAsExpression.DestinationType.Boolean, + _ => CastAsExpression.DestinationType.Undefined + }; + + return _typesService.IsExplicitCastAllowed(from, to) + ? to + : throw new ExplicitCastNotSupported(visitable, from, to); } public Type Visit(CallExpression visitable) @@ -466,51 +463,47 @@ public Type Visit(CallExpression visitable) .Zip(parameters).Zip(functionSymbol.Parameters.AsValueEnumerable().Skip(methodCall ? 1 : 0)) .ToList().ForEach(pair => { - var ((expr, actualType), expected) = pair; - if (!actualType.Equals(expected.Type)) - throw new WrongTypeOfArgument(expr.Segment, expected.Type, actualType); + var ((expr, actualType), expectedType) = pair; + if (!actualType.Equals(expectedType)) + throw new WrongTypeOfArgument(expr.Segment, expectedType, actualType); }); - Type undefined = "undefined"; - if (functionSymbol.Type.Equals(undefined)) + if (functionSymbol.Type.Equals(_typesService.Undefined)) { var declaration = _functionStorage.Get(functionSymbol); functionReturnType = declaration.Accept(This); } - Type @void = "void"; - if (!functionReturnType.Equals(@void)) + if (!functionReturnType.Equals(_typesService.Void)) visitable.HasReturnValue = true; return functionReturnType; } public Type Visit(FunctionDeclaration visitable) { - var parameters = visitable.Arguments.Select(x => x.TypeValue.Accept(_typeBuilder)); + var parameters = visitable.Arguments.Select(x => x.TypeValue.Accept(_typeBuilder)).ToList(); var symbol = _symbolTables[visitable.Scope].FindSymbol(new FunctionSymbolId(visitable.Name, parameters))!; _functionStorage.RemoveIfPresent(symbol); visitable.Statements.Accept(This); - Type undefined = "undefined"; HashSet returnTypes = []; for (var i = 0; i < visitable.ReturnStatements.Count; i++) { var returnStatementType = visitable.ReturnStatements[i].Accept(This); returnTypes.Add(returnStatementType); - if (returnTypes.Count > 1 && symbol.Type.Equals(undefined)) + if (returnTypes.Count > 1 && symbol.Type.Equals(_typesService.Undefined)) throw new CannotDefineType(visitable.Segment); - if (!symbol.Type.Equals(undefined) && !symbol.Type.Equals(returnStatementType)) + if (!symbol.Type.Equals(_typesService.Undefined) && !symbol.Type.Equals(returnStatementType)) throw new WrongReturnType( visitable.ReturnStatements[i].Segment, expected: symbol.Type, actual: returnStatementType); } - if (symbol.Type.Equals(undefined)) + if (symbol.Type.Equals(_typesService.Undefined)) symbol.DefineReturnType(returnTypes.Single()); - Type @void = "void"; - if (!symbol.Type.Equals(@void) && !visitable.AllCodePathsEndedWithReturn) + if (!symbol.Type.Equals(_typesService.Void) && !visitable.AllCodePathsEndedWithReturn) throw new FunctionWithoutReturnStatement(visitable.Segment); if (symbol.Type is NullType) @@ -523,12 +516,21 @@ public Type Visit(BlockStatement visitable) { for (var i = 0; i < visitable.Count; i++) visitable[i].Accept(This); - return "undefined"; + return _typesService.Undefined; } - public Type Visit(PrintStatement visitable) + public Type Visit(OutputStatement visitable) { visitable.Expression.Accept(This); - return "undefined"; + return _typesService.Undefined; + } + + public Type Visit(InputStatement visitable) + { + IAbstractSyntaxTreeNode id = visitable.Destination; + var idType = id.Accept(This); + if (!idType.Equals(_typesService.String)) + throw new UnsupportedOperation(visitable.Segment, idType, "<<<"); + return _typesService.Undefined; } } \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs index 7f1e10ce..b6c77fe9 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs @@ -12,16 +12,13 @@ internal class TypeSystemLoader : VisitorNoReturnBase, IVisitor { private readonly ITypeDeclarationsResolver _resolver; - private readonly IJavaScriptTypesProvider _provider; private readonly ISymbolTableStorage _symbolTables; public TypeSystemLoader( ITypeDeclarationsResolver resolver, - IJavaScriptTypesProvider provider, ISymbolTableStorage symbolTables) { _resolver = resolver; - _provider = provider; _symbolTables = symbolTables; } @@ -45,7 +42,7 @@ public VisitUnit Visit(TypeDeclaration visitable) { var symbolTable = _symbolTables[visitable.Scope]; if (symbolTable.ContainsSymbol(new TypeSymbolId(visitable.TypeId)) || - _provider.Contains(visitable.TypeId.Name)) + _resolver.TypesService.Contains(visitable.TypeId.Name)) throw new DeclarationAlreadyExists(visitable.TypeId); symbolTable.AddSymbol( diff --git a/src/Domain/HydraScript.Domain.BackEnd/AddressedInstructions.cs b/src/Domain/HydraScript.Domain.BackEnd/AddressedInstructions.cs index 95620e6a..2a974ddd 100644 --- a/src/Domain/HydraScript.Domain.BackEnd/AddressedInstructions.cs +++ b/src/Domain/HydraScript.Domain.BackEnd/AddressedInstructions.cs @@ -4,7 +4,7 @@ namespace HydraScript.Domain.BackEnd; -public class AddressedInstructions : IEnumerable +public class AddressedInstructions : IReadOnlyCollection { private readonly LinkedList _addresses = new(); private readonly Dictionary> _addressToNode = new(); @@ -85,6 +85,8 @@ public IEnumerator GetEnumerator() => IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public int Count => _addresses.Count; + public override string ToString() => ZString.Join('\n', this); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Call.cs b/src/Domain/HydraScript.Domain.BackEnd/Call.cs index c3cc4f61..7dd16697 100644 --- a/src/Domain/HydraScript.Domain.BackEnd/Call.cs +++ b/src/Domain/HydraScript.Domain.BackEnd/Call.cs @@ -1,11 +1,12 @@ using HydraScript.Domain.BackEnd.Impl.Addresses; +using HydraScript.Domain.BackEnd.Impl.Values; namespace HydraScript.Domain.BackEnd; public record Call( IAddress From, FunctionInfo To, - string? Where = null) + Name? Where = null) { public override string ToString() => $"{From}: {Where} => {To.Start}: {To.Id}"; diff --git a/src/Domain/HydraScript.Domain.BackEnd/HydraScript.Domain.BackEnd.csproj b/src/Domain/HydraScript.Domain.BackEnd/HydraScript.Domain.BackEnd.csproj index 77e6f58d..5e816d07 100644 --- a/src/Domain/HydraScript.Domain.BackEnd/HydraScript.Domain.BackEnd.csproj +++ b/src/Domain/HydraScript.Domain.BackEnd/HydraScript.Domain.BackEnd.csproj @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/IOutputWriter.cs b/src/Domain/HydraScript.Domain.BackEnd/IConsole.cs similarity index 71% rename from src/Domain/HydraScript.Domain.BackEnd/IOutputWriter.cs rename to src/Domain/HydraScript.Domain.BackEnd/IConsole.cs index 98a3caf4..e68bdf33 100644 --- a/src/Domain/HydraScript.Domain.BackEnd/IOutputWriter.cs +++ b/src/Domain/HydraScript.Domain.BackEnd/IConsole.cs @@ -1,8 +1,10 @@ namespace HydraScript.Domain.BackEnd; -public interface IOutputWriter +public interface IConsole { public void WriteLine(object? obj); public void WriteError(Exception e, string message); + + public string ReadLine(); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/IExecuteParams.cs b/src/Domain/HydraScript.Domain.BackEnd/IExecuteParams.cs index 8c58b105..5a97ac82 100644 --- a/src/Domain/HydraScript.Domain.BackEnd/IExecuteParams.cs +++ b/src/Domain/HydraScript.Domain.BackEnd/IExecuteParams.cs @@ -3,7 +3,10 @@ namespace HydraScript.Domain.BackEnd; public interface IExecuteParams { public Stack CallStack { get; } - public Stack Frames { get; } + public Queue Arguments { get; } - public IOutputWriter Writer { get; } + + public IConsole Console { get; } + + public IFrameContext FrameContext { get; } } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/IFrame.cs b/src/Domain/HydraScript.Domain.BackEnd/IFrame.cs new file mode 100644 index 00000000..2486bbc1 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/IFrame.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Domain.BackEnd; + +public interface IFrame +{ + object? this[string id] { get; set; } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/IFrameContext.cs b/src/Domain/HydraScript.Domain.BackEnd/IFrameContext.cs new file mode 100644 index 00000000..b8d42c73 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/IFrameContext.cs @@ -0,0 +1,10 @@ +namespace HydraScript.Domain.BackEnd; + +public interface IFrameContext +{ + IFrame Current { get; } + + void StepIn(); + + void StepOut(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/IValue.cs b/src/Domain/HydraScript.Domain.BackEnd/IValue.cs index 847ebd44..01187554 100644 --- a/src/Domain/HydraScript.Domain.BackEnd/IValue.cs +++ b/src/Domain/HydraScript.Domain.BackEnd/IValue.cs @@ -2,5 +2,5 @@ namespace HydraScript.Domain.BackEnd; public interface IValue : IEquatable { - object? Get(Frame? frame); + object? Get(); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/IVirtualMachine.cs b/src/Domain/HydraScript.Domain.BackEnd/IVirtualMachine.cs index e8263e32..194d14c1 100644 --- a/src/Domain/HydraScript.Domain.BackEnd/IVirtualMachine.cs +++ b/src/Domain/HydraScript.Domain.BackEnd/IVirtualMachine.cs @@ -3,5 +3,6 @@ namespace HydraScript.Domain.BackEnd; public interface IVirtualMachine { public IExecuteParams ExecuteParams { get; } + public void Run(AddressedInstructions instructions); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/ExecuteParams.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/ExecuteParams.cs index 19b4eeeb..1425fd88 100644 --- a/src/Domain/HydraScript.Domain.BackEnd/Impl/ExecuteParams.cs +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/ExecuteParams.cs @@ -1,9 +1,14 @@ namespace HydraScript.Domain.BackEnd.Impl; -public class ExecuteParams(IOutputWriter textWriter) : IExecuteParams +public class ExecuteParams( + IConsole console, + IFrameContext frameContext) : IExecuteParams { - public Stack CallStack { get; } = new(); - public Stack Frames { get; } = new(); - public Queue Arguments { get; } = new(); - public IOutputWriter Writer { get; } = textWriter; + public Stack CallStack { get; } = []; + + public Queue Arguments { get; } = []; + + public IConsole Console { get; } = console; + + public IFrameContext FrameContext { get; } = frameContext; } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/FrameContext.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/FrameContext.cs new file mode 100644 index 00000000..0f464c74 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/FrameContext.cs @@ -0,0 +1,17 @@ +using HydraScript.Domain.BackEnd.Impl.Frames; + +namespace HydraScript.Domain.BackEnd.Impl; + +public sealed class FrameContext : IFrameContext +{ + private readonly Stack