【问题标题】:Statically Typing a Scripting Language in Java在 Java 中静态键入脚本语言
【发布时间】:2012-06-26 04:13:35
【问题描述】:

我正在用 Java 为游戏构建脚本语言,目前正在研究解析器。玩家/模组制作者/我自己将使用该语言来创建自定义咒语和效果。但是,我很难想象如何在当前系统中顺利实现静态类型(性能需求驱动的痛苦必要性)。我不太在乎编译是否快,但实际执行需要尽可能快(至少在合理范围内。我希望很快完成。)

所以解析器有next()peek() 方法来遍历标记流。它目前由层次结构方法构建,这些方法以保留类型优先级的方式相互调用(“最底层”方法返回常量、变量等)。每个方法都返回一个IResolve,它具有一个泛型类型<T>,它“解析”到。例如,这里有一个处理“or”表达式的方法,其中“and”的耦合度更高:

protected final IResolve checkGrammar_Or() throws ParseException
{
    IResolve left = checkGrammar_And();

    if (left == null)
        return null;

    if (peek().type != TokenType.IDENTIFIER || !"or".equals((String)peek().value))
        return left;

    next();

    IResolve right = checkGrammar_Or();

    if (right == null)
        throwExpressionException();

    return new BinaryOperation(left, right, new LogicOr());
}

问题是当我需要实现一个依赖于类型的函数时。您可能已经注意到,解析器没有指定泛型类型,而是设计问题的一部分。在这个函数中,我希望做类似以下的事情(尽管由于泛型类型的擦除这不起作用......)

protected final IResolve checkGrammar_Comparison() throws ParseException
{
    IResolve left = checkGrammer_Term();

    if (left == null)
        return null;

    IBinaryOperationType op;

    switch (peek().type)
    {
    default:
        return left;

    case LOGIC_LT:

        //This ain't gonna work because of erasure
        if (left instanceof IResolve<Double>)
            op = new LogicLessThanDouble();

        break;

    //And the same for these
    case LOGIC_LT_OR_EQUAL:
    case LOGIC_GT:
    case LOGIC_GT_OR_EQUAL:
    }

    next();

    IResolve right = checkGrammar_Comparison();

    if (right == null)
        throwExpressionException();

    return new BinaryOperation(left, right, op);
}

我希望可以建立连接的问题点在 switch 语句中。我已经确定我需要使 IResolve 成为非泛型并给它一个“getType()”方法,该方法返回一个 int 或其他东西,特别是如果我想在将来支持用户定义的类。

问题是:

考虑到我当前的结构和对混合继承的需求(用户定义的类和接口,如 Java 和 C#),实现静态类型的最佳方法是什么?如果没有好的方法,我该如何改变甚至重建我的结构来实现它?

注意:我不声称自己知道自己陷入了什么困境,建设性的批评非常受欢迎。如果我需要澄清任何事情,请告诉我!

另一个注意事项:我知道你在想“为什么要静态类型?”,通常我会同意你的看法——但是,游戏世界是由体素组成的(准确地说,它是一个 Minecraft 模组)并使用他们需要快速。想象一个脚本,它是一个 O(n^2) 算法,每秒 20 次迭代超过 100 个块,用于 30 多个玩家在一个 已经 几乎没有吱吱声的廉价服务器上......或者,一个单一的,巨大的影响数千个块的爆炸,不可避免地导致可怕的延迟峰值。因此,后端类型检查或任何形式的鸭子类型都不会削减它(尽管我非常渴望它。)在这种特殊情况下,低级别的好处是必要的,尽管它是痛苦的。

【问题讨论】:

  • 天哪,感觉就像重写Java本身一样。你为什么要这样对自己?我可以看到想要一个游戏 DSL,但是用接口、类等重做所有面向对象的东西?我不明白为什么。
  • 嗯,这个决定是出于对安全性(现有用户群由数十万儿童组成)、灵活性、易用性以及呃,我玩得开心的渴望。我研究过只使用 Javascript,但似乎没有办法限制脚本的能力......面向对象的特性可以幸免,真的,虽然它会很好。静态类型是让我感到悲伤的一件事,我需要它是为了提高效率(游戏世界使用体素。)
  • 我认为静态类型被高估了。没有它,JavaScript 也能正常工作;其他语言也是如此。我不想发明一门新语言,除非你玩得开心并且只是想尝试一下。
  • 有什么理由让你手写解析器,而不是使用 Antlr 之类的东西?
  • 我建议选择任何具有成熟 IDE 的现有脚本语言,最终您将需要调试和代码糖以使您的迭代算法变得轻松。不想惹人烦就来看看Groovy

标签: java parsing interpreter scripting-language static-typing


【解决方案1】:

您可以通过在IResolve 中添加方法Class&lt;T&gt; getType() 来获得两全其美;它的实现者应该简单地返回适当的Class 对象。 (如果实现者本身是泛型的,则需要在构造函数或其他东西中获取对该对象的引用。)

然后你可以做left.getType().equals(Double.class)等。

这与您是否应该使用静态类型构建自己的解析器的问题完全不同,这个问题非常值得一问。

【讨论】:

  • 感谢您的回答 :) 它不支持继承系统(除非您可以通过反射实例化类,我很确定 Java 不允许这样做),但如果该解决方案发生,它将起作用太疯狂而无法实施。我理解玩世不恭,但我必须有静态类型。至于解析器生成,我找不到支持上述静态类型的解析器,也没有外部库等(除非我不知道我在寻找什么,这总是有可能的)
  • 您通常不会将类型检查器放入解析器中。解析器生成抽象语法树;然后,您使用类型检查算法检查 AST 的类型正确性。 (我唯一熟悉的这种算法是Hindley–Milner;我不知道Java用的是什么。)
  • 另外,为了清楚起见,“实现者”是指“实现IResolve 接口的类”。还有 Java does, in fact, allow you to instantiate classes via reflection.
  • 呃,让我换个说法:据我所知,Java 允许您通过反射来实例化类的 instances,但不能自己创建实际的类。
【解决方案2】:

正如一些人在 cmets 中建议的那样,我要采用的解决方案是将解析和输入分成不同的阶段,同时使用 enum 来表示我最初认为应该的类型。

虽然我很欣赏 Taymon 的回答,但如果我希望将来支持用户定义的类,我将无法使用它。

如果有人有更好的解决方案,我很乐意接受!

【讨论】:

    猜你喜欢
    • 2010-11-18
    • 2011-08-20
    • 2011-09-22
    • 2015-02-14
    • 2010-11-12
    • 2012-11-24
    • 2023-03-16
    • 2011-04-20
    • 2016-01-20
    相关资源
    最近更新 更多