Giter Site home page Giter Site logo

lens's Introduction

LENS: Embeddable scripting for .NET NuGet

Welcome to the homepage for LENS embeddable compiler!

LENS stands for "Language for Embeddable .NET Scripting".

A few examples of the syntax

A basic script:

let a = 1
let b = 2
print "the result is: {0}" (a + b)

A loop:

for x in 10..0 do
    println "{0}..." x

println "blastoff!"

LINQ queries:

let squareSum = 1.to 100
    |> Where x -> x.even()
    |> Select x -> x ** 2
    |> Sum ()

Function declaration:

using System.Drawing

pure fun dist:double (p1:Point p2:Point) ->
    let x = p1.X - p2.X
    let y = p1.Y - p2.Y
    Math::Sqrt (x ** 2 + y ** 2)

let pA = new Point 1 2
let pB = new Point 10 20
print "The distance is: {0}" (dist pA pB)

Custom data structures:

record Store
    Name : string
    Stock : int

let stores = new [
    new Store "A" 10
    new Store "B" 42
    new Store "C" 5
]

for s in stores.OrderByDescending (x-> x.Stock) do
    println "Store {0} contains has {1} products in stock" s.Name s.Stock

Partial application and function composition:

let multiplier = (x:int y:int) -> x * y
let inv = (a:string b:string) -> b + a
let doubler = multiplier 2 _

// compose functions together
let invParse = inv :> int::Parse :> doubler :> (x -> println x)

invParse "1" "2" // 42

Pattern matching:

fun desribe:string (arr:object[]) ->
    match arr with
        case [] then "empty array"
        case [x:int] when x < 10 then fmt "array with 1 small int ({0})" x
        case [x] then "array with 1 item"
        case [x; y] then "array with 2 items"
        case [x; y; ...z] then fmt "array with {0}, {1} and {2} more items" x y z.Length

Why another language?

LENS provides an easy way to compile and execute a script within your application, and manages the interconnection between the app and the script. The language has a light, conscise, and type-safe syntax with rich functional features.

Oh really?

Why yes indeed! Here's a snippet that shows how to embed the compiler into your application:

try
{
    var x = 21;
    var y = 2;
    var result = 0;

    var cp = new LensCompiler();
    cp.RegisterProperty("x", () => x);
    cp.RegisterProperty("y", () => y);
    cp.RegisterProperty("res", () => result, r => result = r);

    var source = "res = x * y";
    var compiled = cp.Compile(source);
    compiled.Run();

    Console.WriteLine("The result is {0}", result);
}
catch(LensCompilerException e)
{
    Console.WriteLine("An error has occured: {0}", e.FullMessage);
}

The code above creates the compiler and registers local variables x, y, and result in the script. The body of the script is compiled into a native .NET object that can be invoked several times without recompilation. Finally, the result of the expression is printed out - and guess what the result is!

Why might one need an embeddable scripting language?

There are many cases in which your application can benefit from an embeddable scripting language:

  • Tasks automation

    Write scripts to execute tasks automatically within the application, like processing a batch of images in a graphical editor, backing up databases.

  • Formulas support

    Enable Excel-like formulas in your application, with functions and all kinds of cool features.

  • Easy tweaking

    Embeddable scripting is a much more powerful alternative to config files. Scripts can contain some logic which can be altered without recompiling the entire application. Especially useful in game engines!

What features does the language support?

The compiler already supports the following features:

  • Full access to any .NET types and assemblies referenced by your host project
  • Import of types, methods and even local variables from host into the script
  • Declaration of records and functions inside the script
  • Local type inference
  • Anonymous functions with closures
  • Extension methods and LINQ
  • Overloaded operators support
  • Partial function application and function composition
  • Pattern matching (with awesome regex support)
  • Automatic memoization support
  • Shorthand operators
  • Basic optimizations like constant unrolling
  • Safe mode: certain types or namespaces can be disabled for security reasons

Please refer to the Wiki for the complete list of features.

Contributions are always welcome - especially if you would like to help create a text editor with code suggestions and syntax highlighting!

What NOT to expect

Being designed as an embeddable language, LENS does not support some features that are better implemented in the language of the host application. Here is a list of features that you will not see any time soon:

  • Unsafe code with pointers
  • Checked / unchecked semantics
  • Multiple source files support
  • Declarations of classes or interfaces
  • Access restrictions (private / public, etc)

Supporting documentation

lens's People

Contributors

fornever avatar impworks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lens's Issues

Парсинг Get\SetMemberNode

Посмотрел в *MemberNode, а там только название member'а и присваиваемое значение в set-варианте. Когда разбирается выражение типа (a + b).mbr, куда записывается (a + b)?

Имена идентификаторов

Не нашёл в документации, как правильно формируются имена переменных и типов. Считаю, это нужно обязательно где-то описать, а также сообщить мне для парсера.

Сейчас я использую такое определение:

identifierRef         := regex "[a-zA-Z_][0-9a-zA-Z_]*" >>=?
                            fun s -> if List.exists (fun k -> k = s) keywords then
                                         pzero
                                     else
                                         preturn s

Что означает:

  1. Имя должно начинаться с английской буквы или знака _.
  2. Имя должно соответствовать минимум из одного символа.
  3. Имя может содержать английские буквы, цифры и знак _.
  4. Имя не может совпадать с ключевым словом языка.

Продумать схему реализации замыканий

Нужно подумать на тему того, как лучше реализовать замыкания. Пока видится два варианта:

  1. Иметь специальный класс, который перестраивает дерево, заменяя обращения к локальной переменной на обращения к полю объекта специального класса.
  2. Иметь специальный класс, который обходит блок кода и помечает переменные как замкнутые, а в коде обращения к переменной будет прописано специальное условие, проверяющее этот флаг.

Первый более общий и более правильный - иметь гибкие механизмы перестраивания дерева кода открыло бы огромные возможности для всяких штук типа yield return или async, второй же вариант проще, хотя класс, отвечающий за обращение к переменным сильно замусорится.

Проблемы грамматики

Решил завести одну задачу для грамматики, и все проблемы писать сюда.

Итак, номер первый. Есть такой вот пример кода:

a = (1 + 2).someShit

В соответствии с текущей грамматикой, он не может быть распарсен. .someShit - это accessor_expr, а в соответствии с грамматикой, accessor_expr идёт только после статических или локальных идентификаторов:

lvalue              = ( type "::" identifier | identifier ) { accessor_expr }
accessor_expr       = "." identifier | "[" line_expr "]"

Сходу не могу придумать, как решить эту проблему.

Продумать способ передачи контекста компиляции

Многие методы в ходе своей работы требуют обращения к "контексту компиляции" - некой структуре, хранящей в себе различную информацию, в частности:

  • Список объявленных типов, методов, полей
  • Указатель на тело текущего метода для того, чтобы знать куда emit'ить код
  • Контекстную информацию (например, текущий блок try для того, чтобы определить допустимость rethrow)
  • Прочие интересности

В проекте Mirelle это называлось Emitter и передавалось в каждый метод Compile и GetExpressionType, что не очень удобно. Есть вариант хранить его статически, но он накрывает возможность создания двух экземпляров интерпретатора и использование его в многопоточной среде. Нужно подумать о том, насколько это важно.

Именование узлов синтаксического дерева

Классы пространства имён Lens.SyntaxTree.Literals названы как-то непоследовательно: у нас тут, например, и DoubleLiteral, и IntNode. Нужно назвать как-нибудь одинаково: или везде Node, или везде Literal. Лучше Node - так будет больше походить на соседние пространства имён (все имена из Lens.SyntaxTree.* заканчиваются на Node).

Возвращаемые значения из конструкций

Сейчас у нас в языке предусмотрено, что if или while могут стоять по правую сторону от знака присваивания и результатом такого действия будет последняя выполненная команда \ строка. Однако такой подход сопряжен с определенными неудобствами.

Во-первых, если каждая строка будет оставлять что-то на стеке в надежде, что кто-то этим воспользуется, байткод получится неоптимальным и будет изобиловать операторами pop.

Во-вторых, система вывода типов не позволит сделать такое условие:

if(a)
    do_something 1 2
else
    do_nothing

Если у функций do_something и do_nothing разный возвращаемый тип. В данном случае очевидно, что мы не присваиваем ничего и нам нахрен не нужно их возвращаемое значение.

Видимо, в метод Compile() помимо указателя на CompilerContext придется передавать еще и флаг, оставлять ли значение на стеке или нет.

Следует ли использовать :: в качестве разделителя namespace?

Вопрос возник из следующей проблемы: допустим, есть выражение:

alpha.beta.gamma.delta.eps = 1

В данном случае, из-за рекурсивного подхода оно будет воспринято парсером так:

(((alpha.beta).gamma).delta).eps = 1

Что верно, если это всё объекты, но с другой стороны, alpha.beta.gamma может вполне оказаться неймспейсом, а delta - именем класса. Или же alpha.beta - неймспейс, gamma - класс, delta - имя свойства типа X, у которого в свою очередь есть свойство eps.

Разрешение таких идентификаторов становится вопросом нетривиальным. Причем разрешение должно начинаться с верхнего уровня, где выражение выглядит как X.Y = Z и мы пытаемся узнать, что такое X, спускаясь постепенно вниз и вниз.

Насколько я знаю, неймспейс - штука неделимая. Объявление namespace alpha.beta.gamma не гарантирует создания базовых неймспейсов alpha и alpha.beta, поэтому как нода, представляющая собой alpha будет разруливать, что она - часть неймспейса - одному богу ведомо. Даже если я и ошибаюсь, алгоритм все равно получается жутко рекурсивный.

Отсюда вариант: записывать доступ к неймспейсам так:

alpha::beta::gamma::delta.eps = 1
alpha::beta::gamma.delta.eps = 1

Плюсы:

  1. Упрощение парсера
  2. Визуальная наглядность

Минусы:

  1. Несоответствие неймспейса его "каноническому виду", несущественно
  2. На 1 символ длинее

@ForNeVeR, что скажешь? Я пока что за.

Написать метод, определяющий расстояние между типами

Расстояние между типами - это метрика, которая позволяет определить наиболее подходящий оверрайд метод для вызова. Чем меньше расстояние - тем больше метод "подходит".

Для вычисления расстояний используются следующие правила:

dist(T, T) = 0
dist(T1, T2) = 1 + dist(T1, T2.Parent)
dist(object, T(value)) = 1
dist(IType, Type(IType)) = 1
dist(X<in T1>, X<in T2>) = dist(T2, T1)
dist(T1[], T2[]) = dist(T1, T2)
dist(X<out T1>, X<out T2>) = dist(T1, T2)

Есть также особые случаи:

  • Для генериков с N аргументами расстояние считается как сумма N расстояний между соответствующими типами.
  • Для базовых типов расстояние считается по цепочке расширений типа: byte -> short -> int -> long -> decimal, single -> double. Переходы с int на single и с long на double также считаются за единицу.

LENS и Fluid Style а-ля LINQ

Поскольку параметры в функции передаются без скобок, LINQ-вызов на LENS получится довольно громоздким и неудобным:

(((Enumerable.Range 1 100).Where x:int -> x > 10).Select x:int -> x * 2

В F# для этого используется оператор |>, и код выглядит так:

let data = Emumerable.Range 1 100
let res = data
|> IEnumerable.filter (fun x -> x > 10)
|> IEnumerable.map (fun x -> x * 2)

У нас же оператор |> сейчас используется для обратной цели - перед ним стоит функция, а после него - аргумент, не поместившийся на строку.

Возможно, есть смысл пересмотреть эту концепцию. Для передачи многих параметров в функцию использовать <|, а для передачи значения по цепочке функций использовать оператор |>? Тогда linq-вызов выглядел бы так:

var data = Enumerable.Range 1 100
let res = data
|> IEnumerable.Where x:int -> x > 10
|> IEnumerable.Select x:int -> x * 2

А передача - так:

var result = somefx
<| param1
<| param 2

Баги парсера, выявленные тестами

Здесь буду отписываться по мере появления багов в парсере, ради которых не хочется создавать отдельные тикеты.

  1. На парсятся дробные числа. Тест NewObjectDeclaration проходится, если 13.37 заменить на 13.

Непонятные узлы

Непонятно, зачем нужны узлы NewDictionaryNode, NewListNode - в грамматике ничего такого нет (по крайней мере, я не нашёл).

Если и правда не нужны - предлагаю пока удалить.

Изменения в грамматике

Здесь буду описывать изменения в грамматике, которые требуется синхронизировать с кодом парсера. Не хочется плодить массу тикетов.

Тесты для парсера

Парсер уже скоро будет готов. Для того, чтобы побыстрее его допинать, я отложил написание unit-тестов. Но написать их будет нужно обязательно, потому что есть много мелочей, которые я мог не заметить (например, перепутать узлы синтаксического дерева).

Один я буду слишком долго писать тесты, так что, @impworks, мне потребуется твоя помощь.

Когда закончу парсер - залью его в ветку master, и отдельно уведомлю о начале этапа активной разработки тестов.

Многострочные аргументы функции

Есть такой тест:

test
    <| true
    <| (a:double) ->
        logger.log a
        a ** 2
    <| false

Он не проходит из-за того, что аргументы функции должны быть в скобках (обсуждалось в #37).

Взял лямбду в скобки - парсинг стал проходить. Результат таков:

test
    <| true
    <| ((a:double) ->
        logger.log a
        a ** 2)
    <| false

Является ли это поведение правильным? Прошу учесть, что на текущий момент любого рода сложные выражения (например, 2 + 2) тоже не будут работать без скобок даже при многострочной передаче параметров.

Добавить промежуточный слой абстракции между контекстом и сборкой

Сейчас класс Context представляет собой сборную солянку, которая чуть-чуть повышает уровень абстракции:

  1. Отделяет типы, записи и функции в отдельные хранилища
  2. Ресолвит типы и кеширует результаты
  3. Хранит указатели на Assembly Entities - сборку, модуль, основной тип и точку входа
  4. Выполняет другие служебные функции

Такой подход прост, но по мере того, как Lens обрастает функционалом, в нем будет все проще и проще запутаться. Например, существуют следующие проблемы:

  1. Типы ресолвятся и кешируются в Dictionary, как полагается, а поля и методы нет
  2. Нет удобного и очевидного способа генерировать встроенные методы в записи и типы
  3. Непонятно, как и где учитывать дополнительные методы и типы, создаваемые в результате обнаружения замыканий и каррированных функций
  4. Во встроенных типах появляется иерархия (Type > Label), но мы вынуждены использовать костыль для ее учета в методах поиска подходящих типов для аргументов и т.д.

Для исправления ситуации предлагается создать честную модель системы типов, описанных в скрипте, заменяющую собой неработающие методы Reflection'а у классов, получаемых через Emit.

Костяк будет состоять примерно из следующих классов:

Context
   TypeReference (name, parent)
       MethodReference (name, args, return)
       ConstructorReference (args)
       FieldReference (name, type)
       PropertyReference (name, type)

Соответственно, Context теперь будет работать следующим образом:

  1. Создается пустой контекст
  2. В контексте сохраняется дерево Node'ов
  3. Выполняется первый проход (Structurize)
    1. TypeNode, RecordNode и TypeLabel превращаются в TypeReference
    2. RecordEntry превращаются в FieldReference
    3. FunctionNode превращаются в MethodReference
    4. LambdaNode превращаются в хитроназванные TypeReference и MethodReference в них
  4. Выполняется второй проход (Expand):
    1. Обнаруживаются замыкания, создаются FieldReference, в MethodReference.Context отмечаются замкнутые переменные
    2. Обнаруживаются каррированные функции, создаются MethodReference.
  5. Выполняется третий проход (Prepare), в который для всех элементов дерева наконец-то создаются Assembly Entities
  6. Выполняется заключительный четвертый проход (Compile), в который собственно генерится код.

Используя такой подход, мы получаем следующие преимущества:

  1. Можно использовать Node'ы для описания логики встроенных методов, не придется шпарить их на голом MSIL'е.
  2. Кеширование простое и однозначное на всех уровнях. Для поиска типа нужен один dictionary lookup, для поиска поля - два, для метода - чуть сложнее, но все равно просто и универсально.
  3. Поиск подходящих типов для аргументов будет работать универсально и без костылей.

Ничего я не упустил? Может быть, осталась какая-то неоднозначность?

Null

Нет узла синтаксического дерева для обозначения значения null.

UnitNode

Класс Lens.SyntaxTree.SyntaxTree.Literals.UnitNode почему-то оказался internal.

Пустые строки в исходном коде

Для большей наглядности я написал в тестах код таким образом:

var src = @"
type hello
    | tag1
    | tag2";

И все тесты моментально попапали.

Продумать взаимодействие с host-стороной

Скрипт должен иметь возможность взаимодействовать с вызывающей его стороной. Для этого интерпретатору перед исполнением должны скармливаться типы и методы, которыми скрипт сможет манипулировать.

Необходимо продумать то, как сгенерированная с помощью Reflection.Emit сборка сможет получать к ним доступ.

Хотелось бы, например, сделать вот так:

script.AddType(typeof(System.Math));
script.AddMethod("log", (string s) => Console.WriteLine(s));

Вопросы, связанные с параметрами функций

  1. Дефолтные значения для параметров?
  2. Следует ли позволять пользователю объявлять ref и out параметры? Как в этом случае быть с частичным применением?
  3. Как хранить переменные при описании функции? Каждая переменная - это модификатор, само имя и тип. Ошибка может быть вызвана любой из этих трех частей, стоит ли подсвечивать сразу всё или выделить три лексемы?

Пробелы в грамматике

Нигде в грамматике не обнаружил мест, в которых разрешены (или запрещены) пробелы, табуляции и прочий вайтспейс. Нужно обсудить этот вопрос. Вижу возможность ввести обработку двумя способами:

  1. Уточнить грамматику, дополнив вайтспейсом в нужных местах. Недостаток этого способа: грамматика станет слишком уж переполнена ненужными и неинтересными описаниями вайтспейса.
  2. Сделать допущение, что каждый узел грамматики может содержать любое количество вайтспейса после себя. Это легко будет внедрить в коде на глобальном уровне.

И ещё - что мы считаем вайтспейсом? Есть какие-то формальные определения?

Extension Methods

По тексту задачи #9 я заметил, что ты планируешь использовать LINQ в Lens. Для этого придётся поддерживать Extension Methods. В курсе ли ты, как это вообще реализуется?

Приделать метод Prepare

Требуется дополнительный обход дерева, предшествующий компиляции. Этот обход должен выполнять следующий действия:

  1. Регистрировать Assembly Entities для типов и функций
  2. Заносить все локальные переменные и константы в Scope.
  3. Обнаруживать и регистрировать в виде AE замыкания.
  4. Обнаруживать и регистрировать в виде AE каррированные методы.

Как пользоваться алгебраическими типами данных без pattern matching?

Никак не могу придумать, что делать с алгебраическими типами данных. @ForNeVeR, может быть, у тебя есть какие-то мысли?

Возьмем, например, такой тип:

type Multitude
    | Integer of int
    | Fractional of double
    | None

А потом мы делаем так:

var some1 = Integer 1
var some2 = Fractional 1.337

Но что теперь с ними делать? Какие поля будут у переменных some1 и some2? Как проверить их тип?

Варианта я вижу два:

  1. Сделать расово верную поддержку pattern matching хотя бы для алгебраических типов. Если останется время, можно будет вкорячить туда также разбор IEnumerable и Tuple. Для него понадобится дополнительный синтаксис, ноды, правки в парсере и тесты.
  2. Эмулировать недостающее существующими возможностями:
    1. К каждому алгебраическому типу SomeType будет прилагаться enum вида SomeTypeKind.
    2. У объекта типа SomeType будет поле Kind типа SomeTypeKind для проверки типа.
    3. Также для каждого поля Label, имеющего тег, будет доступно поле типа LabelTag.

Что скажешь?

Синтаксис record

В документации приведён такой пример для record:

record Student =
    Name : string
    Age : int

Однако в файле Grammar.txt нет упоминаний о знаке равенства:

recorddef           = "record" identifier NL recorddef_stmt { recorddef_stmt }

Нужно или исправить документацию, или исправить грамматику.

Флаг mustReturn

Недавно ввел в интерфейс NodeBase.Compile() флаг mustReturn. По сути, он нужен был для такого случая:

fun log str:string ->
    Console.WriteLine str
    str

fun log val:int ->
    Console.WriteLine val
    val

if (Math.RandomBool ())
    log "hello"
else
    log 1

По сути, разные ветки выражения if будут иметь разный тип, и если бы его результат присваивался в переменную, это было бы недопустимо. Однако в теле метода такой код вполне имеет право на жизнь и не хотелось бы уродовать его таким образом:

if (Math.RandomBool ())
    log "hello"
    () 
else
    log 1
    ()

В таком случае CodeBlockNode вызывал бы все ноды, кроме последней, со значением false, а например LetNode вызывала бы Value.Compile() со значением true.

Вроде выглядит удобно и понятно, но меня смущает тот факт, что у механизма, проходящего через все без исключения ноды, есть только одна точка применения - и все. Не является ли это оверкиллом?

Ключевые слова

При написании парсера следует учесть, что ключевые слова формально являются идентификаторами. Нужно поставить проверку на то, чтобы они таковыми не считались.

  • using
  • record
  • type
  • of
  • fun
  • let
  • var
  • while
  • if
  • else
  • try
  • catch
  • throw
  • new
  • not
  • typeof
  • default
  • as
  • ref
  • out
  • true
  • false
  • null

Также в языке будут разрешены классические object, string, int, float, long, double - но в качестве алиасов для имен типов, а не в качестве ключевых слов.

Ключевые слова являются регистро-зависимыми. Синтаксиса типа @keyword не предполагается. Идентификатор может начинаться с ключевого слова, т.е. types - валидное имя для переменной.

Узел для using

Для выражения using на данный момент не существует узла синтаксического дерева. Вероятно, нужно его добавить.

invoke_list

Нашёл в Grammar.txt потенциально опасную конструкцию:

invoke_list         = { value_expr } | ( { NL "<|" value_expr } NL )

Она опасна тем, что под форму { value_expr } подходит пустая строка - следовательно, эта форма будет матчиться вечно (на самом деле, fparsec просто бросит исключение, обнаружив такой парсер). Необходимо чем-то укрепить эту ветвь, чтобы она не могла сматчиться на пустом месте.

Среда разработки

Нужно утвердить ряд вопросов, связанных со средой разработки.

  1. Visual Studio 2010 или 2012?
  2. .NET 4 или 4.5?
  3. Пишем ли мы unit-тесты.
  4. Если пишем, то будем ли делать CI-сервер?
  5. Каков порядок внесения изменений в проект. Будем ли делать код-ревью?

Я бы предпочёл использование VS2012, .NET4 с unit-тестами и интеграцией. Для интеграции можно использовать Jenkins, у меня есть минимальный опыт его использования с .NET-проектами.

Вообще, вопросы 3, 4 и 5 у меня возникли после того, как я понаблюдал за разработкой scala - там у ребят все серьёзно, и, как мне кажется, такой подход приносит ощутимые результаты как в плане развития проекта, так и в плане общей осведомлённости разработчиков о том, как идут дела в соседних компонентах.

Равенство узлов синтаксического дерева

В тестах возникла задача сравнивать узлы синтаксического дерева. Для этого у них, наверное, придётся перегрузить метод Equals.

Нормально ли это?

Не пригодится ли где-нибудь ещё?

Если не пригодится (или будет мешать) - можно для тестов написать какую-нибудь фигню, которая бы сравнивала объекты рефлекшоном.

Тест ManyParameters

На мой взгляд, тестовый код какой-то кривой:

test 1337 true "hello" new(13.37; new [1; 2])

Я предлагаю такое исправление:

test 1337 true "hello" (new(13.37; new [1; 2]))

(после такого фикса тест начинает худо-бедно работать)

Должно ли быть возможным использование аргумента функции в таком контексте без скобок? F#, например, этого не позволяет, и грамматика у нас составлена соответствующим образом.

Вопросы, связанные с совместимостью с .NET

Внезапно выяснилось несколько неоднозначных моментов, связанных с совместимостью с фреймворком .NET:

  1. Мы поддерживаем только одномерные массивы или массивы массивов. Критично ли отсутствие поддержки для объявления N-мерных массивов?
  2. Нужно ли поддерживать функции с out и ref-параметрами?
  3. Нужно ли поддерживать атрибуты? (полагаю что нет)
  4. Что делать с подписями типов? Мне пока не приходит на ум нормального способа узнать на уровне парсера, что такое A<B - выражение "а меньше б", или же часть подписи типа A<B> ?

Частичное применение функций

Только мозг дошел до того, чтобы обдумать конкретным образом частичное применение функций в LENS. Пока ничего путного не приходит.

Основная проблема в сочетании ЧПФ и полиморфизма. Допустим, есть код:

fun test a:int b:int -> print a+b
fun test a:int b:long -> print a+b
fun test a:string b:string -> print a+b

var a1 = test
var a2 = test 1

Должна ли такая ситуация обрабатываться? Если да, то какого типа будут эти переменные и можно ли будет на их основе делать дальнейшее ЧПФ?

Пока есть идея, как сделать ЧПФ со следующими ограничениями:

  1. Можно делать ЧП-версии только глобально объявленных функций по их имени
  2. У функции с таким именем не должно быть оверрайдов с другими типами параметров
  3. Следствие пункта 1: ЧП-версия функции не может быть повторно использована в новом ЧПФ.

@ForNeVeR: Стоит ли оно того при таком раскладе? Или пока отложить весь механизм до следующих версий?

typeof и sizeof

Нужны ли нам эти операторы?
Если typeof нужен, то придется также допускать специальный формат записи типа - generic-тип без опциональных параметров, как то:

var tupleGeneric = typeof(Tuple<,,,>); // кортеж из 4 элементов

Облагородить сообщения об ошибках парсинга

Нашего препода безумно интересует адекватность сообщений об ошибках. В этой связи нужно сделать так, чтобы выдавался не список возможных лексем, а какие-то человекопонятные сообщения.

Желательно сделать этот способ расширяемым и легко дописываемым, потому что по мере тестирования вылезет много случаев, в которых было бы логично видеть кастомизированное сообщение об ошибке.

Тесты

Нужно написать тесты, покрывающие все возможности LENS:

  • Как минимум по 1 тесту на каждую ноду для независимой проверки.
  • Собирательные тесты для проверки взаимодействия нод.

Эти тесты можно будет впоследствие представить в дипломе в качестве доказательства работоспособности программы: там есть отдельный раздел "отладка и тестирование".

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.