【问题标题】:What is the name of this technique?这种技术叫什么名字?
【发布时间】:2010-07-31 01:46:54
【问题描述】:

我刚刚为我的 ECMAScript 实现编写了一个日期解析器。以前我写过一个正则表达式编译器,规范描述过程的方式给我留下了深刻的印象。本质上,输入是通过一系列测试字符串每个部分的延续传递的。我的日期解析器大致基于这个想法,我真的很想知道它叫什么。

注意:我只留下了解析器的核心以减少噪音。

public sealed class DateParser
{
    public double Parse()
    {
        using (var tokens = Tokenize().GetEnumerator())
        {
            var previous = new Result(ResultType.Success, HandleFirst);
            var next = default(Result);
            while (true)
            {
                if (!tokens.MoveNext())
                {
                    return previous.Type == ResultType.Optional ? Complete() : double.NaN;
                }
                next = previous.Continuation(tokens.Current);
                if (next.Type == ResultType.Complete)
                {
                    return Complete();
                }
                else if (next.Type == ResultType.MustFail)
                {
                    return double.NaN;
                }
                else if (next.Type == ResultType.CanFail)
                {
                    return previous.Type == ResultType.Optional ? Complete() : double.NaN;
                }
                previous = next;
            }
        }
    }

    private Result HandleFirst(DateToken token)
    {
        switch (token.Type)
        {
            case DateTokenType.Integer:
                return HandleYear(token);
            case DateTokenType.T:
                return HandleT(token);
            default:
                return new Result(ResultType.MustFail, null);
        }
    }

    private Result HandleYear(DateToken token)
    {
        if (token.Type == DateTokenType.Integer && token.Value.Length == 4)
        {
            _year = double.Parse(token.Value);
            return new Result(ResultType.Optional, HandleMonthHyphen);
        }
        return new Result(ResultType.MustFail, null);
    }

    private Result HandleMonthHyphen(DateToken token)
    {
        if (token.Type == DateTokenType.Hyphen)
        {
            return new Result(ResultType.Success, HandleMonth);
        }
        return new Result(ResultType.Complete, null);
    }

    private Result HandleMonth(DateToken token)
    {
        if (token.Type == DateTokenType.Integer && token.Value.Length == 2)
        {
            _month = double.Parse(token.Value);
            if (_month < 1 || _month > 12)
            {
                _month = null;
                return new Result(ResultType.MustFail, null);
            }
            return new Result(ResultType.Optional, HandleDayHyphen);
        }
        return new Result(ResultType.MustFail, null);
    }

    private Result HandleDayHyphen(DateToken token)
    {
        if (token.Type == DateTokenType.Hyphen)
        {
            return new Result(ResultType.Success, HandleDay);
        }
        return new Result(ResultType.CanFail, null);
    }

    private Result HandleDay(DateToken token)
    {
        if (token.Type == DateTokenType.Integer && token.Value.Length == 2)
        {
            _day = double.Parse(token.Value);
            if (_day < 1 || _day > 31)
            {
                _day = null;
                return new Result(ResultType.MustFail, null);
            }
            return new Result(ResultType.Optional, HandleT);
        }
        return new Result(ResultType.MustFail, null);
    }

    private Result HandleT(DateToken token)
    {
        if (token.Type == DateTokenType.T)
        {
            return new Result(ResultType.Success, HandleHour);
        }
        return new Result(ResultType.CanFail, null);
    }

    private Result HandleHour(DateToken token)
    {
        if (token.Type == DateTokenType.Integer && token.Value.Length == 2)
        {
            _hour = double.Parse(token.Value);
            if (_hour >= DatePrototype.HoursPerDay)
            {
                _hour = null;
                return new Result(ResultType.MustFail, null);
            }
            return new Result(ResultType.Success, HandleHourColon);
        }
        return new Result(ResultType.MustFail, null);
    }

    private Result HandleHourColon(DateToken token)
    {
        if (token.Type == DateTokenType.Colon)
        {
            return new Result(ResultType.Success, HandleMinute);
        }
        return new Result(ResultType.MustFail, null);
    }

    private Result HandleMinute(DateToken token)
    {
        if (token.Type == DateTokenType.Integer && token.Value.Length == 2)
        {
            _minute = double.Parse(token.Value);
            if (_minute >= DatePrototype.MinutesPerHour)
            {
                _minute = null;
                return new Result(ResultType.MustFail, null);
            }
            return new Result(ResultType.Optional, HandleSecondColonOrOffset);
        }
        return new Result(ResultType.MustFail, null);
    }

    private Result HandleSecondColonOrOffset(DateToken token)
    {
        if (token.Type == DateTokenType.Colon)
        {
            return new Result(ResultType.Success, HandleSecond);
        }
        else
        {
            var result = HandleOffset(token);
            if (result.Type == ResultType.CanFail)
            {
                return new Result(ResultType.MustFail, null);
            }
            return result;
        }
    }

    private Result HandleSecond(DateToken token)
    {
        if (token.Type == DateTokenType.Integer && token.Value.Length == 2)
        {
            _second = double.Parse(token.Value);
            if (_second >= DatePrototype.SecondsPerMinute)
            {
                _second = null;
                return new Result(ResultType.MustFail, null);
            }
            return new Result(ResultType.Optional, HandleSecondDotOrOffset);
        }
        return new Result(ResultType.MustFail, null);
    }

    private Result HandleSecondDotOrOffset(DateToken token)
    {
        if (token.Type == DateTokenType.Dot)
        {
            return new Result(ResultType.Success, HandleMillisecond);
        }
        else
        {
            var result = HandleOffset(token);
            if (result.Type == ResultType.CanFail)
            {
                return new Result(ResultType.MustFail, null);
            }
            return result;
        }
    }

    private Result HandleMillisecond(DateToken token)
    {
        if (token.Type == DateTokenType.Integer && token.Value.Length == 3)
        {
            _millisecond = double.Parse(token.Value);
            if (_millisecond >= DatePrototype.MsPerMinute)
            {
                _millisecond = null;
                return new Result(ResultType.MustFail, null);
            }
            return new Result(ResultType.Optional, HandleOffset);
        }
        return new Result(ResultType.MustFail, null);
    }

    private Result HandleOffset(DateToken token)
    {
        switch (token.Type)
        {
            case DateTokenType.Z:
                _offset = 0.0;
                return new Result(ResultType.Success, null);
            case DateTokenType.Plus:
                _offset = 0.0;
                return new Result(ResultType.Success, HandleOffsetHour);
            case DateTokenType.Hyphen:
                _offset = -0.0;
                return new Result(ResultType.Success, HandleOffsetHour);
            default:
                return new Result(ResultType.CanFail, null);
        }
    }

    private Result HandleOffsetHour(DateToken token)
    {
        if (token.Type == DateTokenType.Integer && token.Value.Length == 2)
        {
            _offset += double.Parse(token.Value) * DatePrototype.MsPerHour;
            return new Result(ResultType.Success, HandleOffsetHourColon);
        }
        return new Result(ResultType.MustFail, null);
    }

    private Result HandleOffsetHourColon(DateToken token)
    {
        if (token.Type == DateTokenType.Colon)
        {
            return new Result(ResultType.Success, HandleOffsetMinute);
        }
        return new Result(ResultType.MustFail, null);
    }

    private Result HandleOffsetMinute(DateToken token)
    {
        if (token.Type == DateTokenType.Integer && token.Value.Length == 2)
        {
            _offset += double.Parse(token.Value) * DatePrototype.MsPerMinute;
            return new Result(ResultType.Complete, null);
        }
        return new Result(ResultType.MustFail, null);
    }
} 

【问题讨论】:

    标签: c# javascript parsing datetime ecma262


    【解决方案1】:

    我认为您正在寻找的术语是 Finite State Machine,基本上类似于:

    while( tokensAvailable ){
        // look at current token
        // do something maybe relating to state
        // loop
    }
    

    【讨论】:

    • 这是设计的总称吗?是否可以使用更具体的术语来指导我的研究?
    • 是的。为什么不从 wikipedia 文章开始您的研究,或者从提供的术语开始您可以想到的任何其他策略。
    • 我会这样做的。没有正规计算机科学教育的最大缺点之一是不懂行话……哦,那张该死的纸。 :)
    • @ChaosPandion:当然,我也没有计算机科学背景。只是教育自己。
    • 我总是尝试自我教育。即使在我上电子学的时候。我只需要一个好的参考点即可开始。
    【解决方案2】:

    试试recursive descent parser

    Niklaus Wirth 的 free ebook (pdf) 很好地介绍了一些不同的技术。

    【讨论】:

    • @lukas:我只是在描述我在代码中看到的内容。虽然,对于这样一个简单的“语法”,它似乎并没有递归。 :-) 也许只是自上而下的解析。
    • 语法非常简单,他们甚至都懒得用 BNF 表示法对其进行形式化。 是的,我对解析进行了一些研究。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多