Чистая и естественная функциональность сценариев без разбора - PullRequest
2 голосов
/ 05 февраля 2012

Я экспериментирую с созданием полунатурального скриптового языка, в основном для собственных целей обучения и для развлечения.Подвох в том, что он должен быть в нативном C #, без синтаксического анализа или лексического анализа с моей стороны, поэтому все, что я делаю, должно быть в состоянии сделать с помощью обычного синтаксического сахара.предложение будет таким, чтобы его было легко читать и изучать, особенно для тех, кто не особенно хорошо разбирается в программировании, но я также хочу, чтобы полный функционал нативного кода был доступен пользователю.

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

When an enemy is within 10 units of player, the enemy attacks the player

В C #, позволяя такому предложению фактически выполнять то, что намеревается сценарий, почти наверняка потребует, чтобы это былострока, которая проходит через анализатор и лексический анализатор.Моя цель не в том, чтобы у меня было что-то такое естественное, и я не хочу, чтобы сценарист использовал строки для сценария.Я хочу, чтобы скрипт имел полный доступ к C # и имел такие вещи, как подсветка синтаксиса, intellisense, отладка в IDE и т. Д. Итак, я пытаюсь получить что-то, что легко читается, но есть в нативном C #.Пара основных препятствий, которые я не вижу, как преодолеть это избавление от периодов ., запятых , и скобок для пустых методов ().Например, что-то вроде этого возможно, но не очень читается:

// C#
When(Enemy.Condition(Conditions.isWithinDistance(Enemy, Player, 10))), Event(Attack(Enemy, Player))

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

// Scala
When(Enemy is WithinDistance(Player, 10)) => Then(Attack From(Enemy, Player))

Этот код на самом деле будет компилироваться, если вы настроите свой движок для его обработки, фактически вы можетеуговорить дальнейшие скобки и запятые из этого.Без синтаксического сахара в приведенном выше примере это было бы больше похоже на это в Scala:

// Scala (without syntactical sugar)
When(Enemy.is(WithinDistance(Player, 10)) => Then(Attack().From(Enemy, Player))

Суть в том, что я хочу максимально приблизиться к чему-то похожему на первый пример scala с использованием нативного C #.Может быть, на самом деле я ничего не могу сделать, но я готов попробовать все возможные трюки, чтобы сделать его более естественным, и вывести отсюда точки, скобки и запятые (кроме случаев, когда они имеют смыслдаже на естественном языке).

Я не настолько опытен в C #, как другие языки, поэтому я могу не знать о некоторых синтаксических приемах, таких как макросы в C ++.Не то чтобы макросы на самом деле были бы хорошим решением, они, вероятно, вызывали бы больше проблем, чем решали бы, и были бы кошмаром отладки, но вы понимаете, куда я иду с этим, по крайней мере в C ++ это было бы осуществимо.Это то, что я хочу, возможно даже в C #?

Вот пример, используя выражения LINQ и Lambda, вы можете иногда выполнять тот же объем работы с меньшим количеством строк, меньшим количеством символов и кодировать чтение ближе к английскому,Например, вот пример трех коллизий, которые происходят между парами объектов с идентификаторами, мы хотим собрать все коллизии с объектом с идентификатором 5, затем отсортировать эти коллизии по «первому» идентификатору в паре и затем вывестипар.Вот как бы вы сделали это без выражений LINQ и / или Lambra:

struct CollisionPair : IComparable, IComparer
{
    public int first;
    public int second;

    // Since we're sorting we'll need to write our own Comparer
    int IComparer.Compare( object one, object two )
    {
        CollisionPair pairOne = (CollisionPair)one;
        CollisionPair pairTwo = (CollisionPair)two;

        if (pairOne.first < pairTwo.first)
            return -1;
        else if (pairTwo.first < pairOne.first)
            return 1;
        else
            return 0;
    }

    // ...and our own compable
    int IComparable.CompareTo( object two )
    {
        CollisionPair pairTwo = (CollisionPair)two;

        if (this.first < pairTwo.first)
            return -1;
        else if (pairTwo.first < this.first)
            return 1;
        else
            return 0;
    }
}

static void Main( string[] args )
{           
    List<CollisionPair> collisions = new List<CollisionPair>
    {
        new CollisionPair { first = 1, second = 5 },
        new CollisionPair { first = 2, second = 3 },
        new CollisionPair { first = 5, second = 4 }
    };

    // In a script this would be all the code you needed, everything above
    // would be part of the game engine   
    List<CollisionPair> sortedCollisionsWithFive = new List<CollisionPair>();
    foreach (CollisionPair c in collisions)
    {
        if (c.first == 5 || c.second == 5)
        {
            sortedCollisionsWithFive.Add(c);
        }
    }
    sortedCollisionsWithFive.Sort();

    foreach (CollisionPair c in sortedCollisionsWithFive)
    {
        Console.WriteLine("Collision between " + c.first +
                          " and " + c.second);
    }
}

А теперь тот же пример с LINQ и Lambda.Обратите внимание, что в этом примере нам не нужно одновременно делать CollisionPair и IComparable и IComparer, и не нужно реализовывать методы Compare и CompareTo:

struct CollisionPair
{
    public int first;
    public int second;
}

static void Main( string[] args )
{           
    List<CollisionPair> collisions = new List<CollisionPair>
    {
        new CollisionPair { first = 1, second = 5 },
        new CollisionPair { first = 2, second = 3 },
        new CollisionPair { first = 5, second = 4 }
    };

    // In a script this would be all the code you needed, everything above
    // would be part of the game engine
    (from c in collisions 
    where ( c.first == 5 || c.second == 5 )
    orderby c.first select c).ForEach(c =>
        Console.WriteLine("Collision between " + c.first +
                          " and " + c.second));
}

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

Ответы [ 3 ]

2 голосов
/ 05 февраля 2012

Я не совсем понимаю ваше требование "написано на родном C #".Зачем?Возможно, вы хотите, чтобы он был написан на нативном .NET ?Я могу понять это, поскольку вы можете скомпилировать эти правила, написанные на «простом английском», в .NET без разбора и т. Д. Тогда ваш движок (вероятно, написанный на C #) сможет использовать эти правила, оценить их и т. Д. Только потому, что этовсе .NET, на самом деле не имеет значения, какой язык разработчик использовал.

Теперь, если C # на самом деле не является обязательным требованием, мы можем перестать придумывать, как сделать «уродливый-уродливый» синтаксис «просто уродливым»:)

Мы можем посмотреть, например, на F #.Он компилируется в .NET таким же образом, как C # или VB.NET, но больше подходит для решения подобных задач.

Вы дали нам 3 (безобразно выглядящих) примера в C # и Scala, вот одинв F # мне удалось написать с головы до головы за 5 минут:

When enemy (within 10<unit> player) (Then enemy attacks player)

Я потратил всего 5 минут, так что, вероятно, это может быть еще красивее.Никакого разбора не происходит, когда, внутри, Тогда атаки - это просто нормальные функции .NET (написанные на F #).

Вот весь код, который мне пришлось написать, чтобы сделать это возможным:

[<Measure>] type unit
type Position = int<unit>

type Actor  =
    | Enemy of Position
    | Player of Position

let getPosition actor =
    match actor with
        | Enemy x -> x
        | Player x -> x

let When actor condition positiveAction =
    if condition actor
    then positiveAction
    else ()

let Then actor action = action actor

let within distance actor1 actor2 =
    let pos1 = getPosition actor1
    let pos2 = getPosition actor2
    abs (pos1 - pos2) <= distance

let attacks victim agressor =
    printfn "%s attacks %s" (agressor.GetType().Name) (victim.GetType().Name)

Это действительно так, а не сотни и сотни строк кода, которые вы, вероятно, написали бы на C # :) Это прелесть .NET: вы можете использовать соответствующие языки для соответствующих задач.А F # является хорошим языком для DLS (именно то, что вам нужно здесь)

PS Вы даже можете определять функции, такие как «an», «the», «in» и т. Д., Чтобы он выглядел больше как английский (этифункции ничего не сделают, но вернут свой первый аргумент):

let an something = something
let the = an
let is = an

Удачи!

2 голосов
/ 08 февраля 2012

Другой подход заключается в использовании «шаблона» FluentInterface, реализующего что-то вроде:

When(enemy).IsWithin(10.units()).Of(player).Then(enemy).Attacks(player);

Если вы сделаете такие функции, как When, IsWithin, Of, Then, вернете некоторые интерфейсы, то вы сможете легко добавить новые методы расширения для расширения вашего языка правил.

Например, давайте посмотрим на функцию Тогда:

public IActiveActor Then(this ICondition condition, Actor actor) {
   /* keep the actor, etc */
}

public void Attacks(this IActiveActor who, Actor whom) {
   /* your business logic */
}

В будущем было бы легко реализовать другую функцию, скажем, RunAway (), не меняя ничего в вашем коде:

public void RunAway(this IActiveActor who) {
    /* perform runaway logic */
}

так что с этим небольшим дополнением вы сможете написать:

When(player).IsWithin(10.units()).Of(enemy).Then(player).RunAway();

То же самое для условий, при условии, что когда возвращается что-то вроде ICheckActor, вы можете вводить новые условия, просто определяя новые функции:

public ICondition IsStrongerThan(this ICheckActor me, Actor anotherGuy) {
    if (CompareStrength(me, anotherGuy) > 0)
       return TrueActorCondition(me);
    else
       return FalseActorCondition(me);
}

так что теперь вы можете сделать:

When(player)
  .IsWithin(10.units()).Of(enemy)
  .And(player).IsStrongerThan(enemy)
  .Then(player)
  .Attacks(enemy);

или

When(player)
  .IsWithin(10.units()).Of(enemy)
  .And(enemy).IsStrongerThan(player)
  .Then(player)
  .RunAway();

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

2 голосов
/ 05 февраля 2012

Честно говоря, я не думаю, что это хорошее направление для языка.Взгляните на AppleScript когда-нибудь.Они приложили много усилий, чтобы имитировать естественный язык, и в тривиальных примерах вы можете написать AppleScript, который читается как английский.В реальном использовании это кошмар.Это неудобно и громоздко в использовании.И это трудно учиться, потому что людям очень трудно «просто написать это невероятно ограниченное подмножество английского языка без отклонений от заданного шаблона».Проще узнать настоящий синтаксис C #, который является регулярным и предсказуемым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...