Название | 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() }; }
}
Не вопрос. Для начала - замыкания. Хинт: самый простой способ обработать замыкания (помимо перехвата анонимных пакованных классов с константами - ну.. вы увидите, когда будете дебажить) - проследить что очередной 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; }