Преобразователь C# в JavaScript

Название Jsecond
Баллы от 6 до 9
Командная?

Заставлять студентов писать полноценный транслятор из C# в JavaScript - кощунство. А вот использовать ExpressionVisitor и встроенные в C# деревья выражений - вполне себе хорошая идея.

В рамках данного задания вам нужно реализовать простенькое AST, используя ExpressionVisitor и ваши личные madskillz работы со стеком применить это всё добро для перевода шарповых лямбда-выражений в JavaScript-функции. Не пугайтесь, эта задача не требует от вас знания JavaScript более, чем необходимо для создания web-странички о вас и вашей собаке. Чтобы вы не мучались архитектурными вопросами, я приведу пример как это должно выглядеть. Итак, вам надо сделать несколько классов Js, с разным числом тип-параметров (до 8 будет достаточно) и единственным методом - Function, чтобы его можно было использовать вот так:

// Пусть у нас есть класс User, declared as follows
class User
{
    public int Age { get; set; }
    public string Name { get; set; }
    public decimal? Balance { get; set; }
    public Gender Gender { get; set; }
}

enum Gender { Male, Female }

static void Main(string[] args)
{
    // В переменной fn будет готовая к использованию JS-функция
    // В комментарях я пишу что именно должно быть в этой переменной
    // Кстати, помимо трансляции выражений надо иметь возможность указывать (опционально) имя функции
    string fn = null;

    // надо поддерживать все математические операции
    fn = Js<User>.Function(x => x.Age + 5);
    // function(x) { return (x.Age + 5); }

    // Все, я сказал!
    fn = Js<int, int>.Function((x, y) => (x % y / 2) >> 5);
    // function(x,y) { return (((x % y)/2) >> 5); }
    
    // И массивы (и словари тоже)
    fn = Js<User>.Function(x => x.Name[5]);
    // function (x) { return (x.Name[5]); }
    
    // И про null не забудьте
    fn = Js<User>.Function(x => x.Name == null);
    // function (x) { return (x.Gender == null); }

    // Надо иметь возможность указывать имя функции
    fn = Js<User>.Function(x => x.Age > 18, "isAdult");
    // function isAdult(x) { return (x.Age > 18); } 

    // Не забудьте про null-coalescing (и про тернарный оператор!)
    fn = Js<User>.Function(x => x.Balance ?? 0);
    // function(x) { return ((x.Balance==null||x.Balance==undefined)?0:(x.Balance)); } 

    // И про new тоже не забудьте (и чтобы Object Initializer поддерживало для строгих типов!)
    // Для строк - корректно транслировать .Length, .Trim() и .ToUpper() - для задания хватит
    fn = Js<User>.Function(x => new {NormalizedAge = x.Age - 18, UpperName = x.Name.ToUpper()});
    // function(x) { return { NormalizedAge: (x.Age - 18), UpperName = x.Name.toUpperCase() }; }
}

Хотите 9 баллов?

Не вопрос. Для начала - замыкания. Хинт: самый простой способ обработать замыкания (помимо перехвата анонимных пакованных классов с константами - ну.. вы увидите, когда будете дебажить) - проследить что очередной MemberExpression не строится от одного из параметров выражения. Коль такое встречается - делайте бесконтекстное лямбда-выражение, вызывайте у него .Compile и используйте DynamicInvoke (не реклама, но в моей статье про SQL можно почитать как это делается). Да, ваши замыкания должны распространяться только на простые типы (численные и строки). Если пользователь пытается впихнуть другой объект в замыкание - смело кидайтесь exception-ом.

// Надо корректно обрабатывать замыкания
var yearFrom = DateTime.Today.Year - 18;
fn = Js<User>.Function(x => DateTime.Today.Year - x.Age > yearFrom);
// function (x) { return ((2018 - x.Age) > 2000); }

// И перечисления
fn = Js<User>.Function(x => x.Gender == Gender.Female);
// function (x) { return (x.Gender == 1); }

И - не пугайтесь - простенький LINQ. Даже если у вас не получится сделать LINQ нормально - все равно несите и рассказывайте во что вы уперлись

// select и where вместе
fn = Js<User[], int>.Function((x, age) => from t in x where t.Age > age select t.Name);
// function(x,age) { var r = []; for(var i=0;i<x.length;i++) { if (x[i].Age > age) r.push(x[i].Name); } return r; }

// where отдельно 
fn = Js<User[], int>.Function((x, age) => x.Where(t => t.Age > age));
// function(x,age) { var r = []; for(var i=0;i<x.length;i++) { if (x[i].Age > age) r.push(x[i]); } return r; }

// select отдельно 
fn = Js<User[]>.Function((x) => x.Select(t => t.Name));
// function(x,age) { var r = []; for(var i=0;i<x.length;i++) { r.push(x[i].Name); } return r; }