parsing days +hrs

Apr 14, 2010 at 10:00 PM
Edited Apr 14, 2010 at 10:01 PM

Hi

I have the following scenario. I am creating cinema movies schedules. So i have schedules of the form

Thu-Wen 14.15 / 16.45, Thu & Mon-Wen 22.15 /00.45, Sat,Sun 12.15 3D translated

or

Thu-Wen 14.15 /16.45 Mon 14.15

given the characters TFSAMBW for each day of the week starting from thurday ->T ,friday->F and so on i would expect the following input for 1st schedule above

T-W14151645TMW22150045SA1215DL

this way the user input speed is almost maximun

but since i m a first time user i would need some help for creating that grammar. It could also be a nice very simple scenario for Irony source :)

Apr 14, 2010 at 11:23 PM

Some quick questions. 

1. Shouldn't the input text be as follows:

T-W14151645TM-W22150045SA1215DL

for    Thu-Wen 14.15 / 16.45, Thu & Mon-Wen 22.15 /00.45, Sat,Sun 12.15 3D

 

2. Are the times always 4 digits?  In other words, they can't just put 12 for 12.00.

3. Are there any other options like "3D", which appears to translate to "DL", correct?

 

-MindCore

Apr 15, 2010 at 12:37 AM

1. yes

2.12 should be acceptable also

3. no onlyt those 2

4. the combination of days and hrs may vary a lot, also the length of the sentence. I just gave u some examples and hope from your code to be able to build anything else i need

 

Coordinator
Apr 15, 2010 at 4:41 AM

I think you should use FixedLengthLiteral for dates/times, with proper DateFormat setting; for alphabetical symbols use direct terms ( Day.Rule = ToTerm("M") | "T" | "W"...)

If you want to exclude spaces (not allow them), you should set Grammar.WhitespaceChars to empty string. 

 

Apr 15, 2010 at 6:25 AM

I had to have some fun with the CustomTerminal class. Find below a grammar for the scenario you described. As you will see that I created two custom terminals, one for dates and date ranges and another one for times. This may be overkill, but it was a great exercise for me and I hope it will aid others in their own exploration of the Irony framework.

- MindCore

 

using System;
using System.Collections.Generic;
using System.Text;

using Irony.Parsing;

namespace Irony.Samples
{
    /// <summary>
    /// This class defines the Grammar for simple Movie Time entry language.
    /// </summary>
    /// <remarks>
    /// http://irony.codeplex.com/Thread/View.aspx?ThreadId=209377
    /// </remarks>
    [Language("Movie Times", "1.0", "Simple Movie Times grammar")]
    public class MovieTimesGrammar : Grammar
    {
        public MovieTimesGrammar()
        {

            #region Terminals

            var Time = new MovieTimeTerminal("Time");
            var Date = new MovieDateTerminal("Date or Date Range");
           
            #endregion

            #region Non-terminals

            var MOVIE = new NonTerminal("Movie");
            var DATES_AND_TIMES = new NonTerminal("Movie Dates & Times");
            var DATE_TIME_SET = new NonTerminal("Date/Time Set");
            var DATE = new NonTerminal("Date");
            var TIME = new NonTerminal("Time");

            var MISC = new NonTerminal("Misc");

            #endregion

            MISC.Rule = "DL" | Empty;


            TIME.Rule = MakePlusRule(TIME, Time);

            DATE.Rule = MakePlusRule(DATE, Date);

            DATE_TIME_SET.Rule = DATE + TIME;

            DATES_AND_TIMES.Rule = MakePlusRule(DATES_AND_TIMES, DATE_TIME_SET);

            MOVIE.Rule = DATES_AND_TIMES + MISC;
            

            this.Root = MOVIE;

            this.LanguageFlags = LanguageFlags.NewLineBeforeEOF;

        }

        public class MovieTimeTerminal : CustomTerminal
        {
            public MovieTimeTerminal(string name)
                : base(name, MovieTimeHandler, null)
            { }
        }

        public static Token MovieTimeHandler(Terminal terminal, ParsingContext context, ISourceStream source)
        {
            var pos = context.Source.PreviewPosition;
            string text = string.Empty;

            for (int i = 0; i < 4; i++)
            {
                if (context.Source.EOF()) return null;
                text += context.Source.PreviewChar;
                context.Source.PreviewPosition++;
            }
            
            int raw;

            // Is the text numeric?
            if (!int.TryParse(text, out raw))
                return null;

            // Validate Hours
            var hrs = Convert.ToInt16(raw / 100);
            if (hrs < 0 || hrs > 23)
                return null;

            // Validate Minutes
            var min = raw - (hrs * 100);
            if (min < 0 || min > 60)
                return null;

            // Create a Token with a TimeSpan object
            var line = context.Source.Location.Line;
            var col = context.Source.Location.Column;
            var sl = new SourceLocation(pos, line, col);
            var time = new TimeSpan(hrs, min, 0);

            return new Token(terminal, sl, text, time);
        }

        public class MovieDateTerminal : CustomTerminal
        {
            public MovieDateTerminal(string name)
                : base(name, MovieDateHandler, null)
            { }
        }

        public static Token MovieDateHandler(Terminal terminal, ParsingContext context, ISourceStream source)
        {
            // Set up a date code Dictionary
            var valid_date_codes = new Dictionary<char,string>();
            valid_date_codes.Add('M', "Mon");
            valid_date_codes.Add('B', "Tue");
            valid_date_codes.Add('W', "Wed");
            valid_date_codes.Add('T', "Thur");
            valid_date_codes.Add('F', "Fri");
            valid_date_codes.Add('S', "Sat");
            valid_date_codes.Add('A', "Sun");
            
            var pos = context.Source.Location.Position;
            var from_date = context.Source.PreviewChar;

            // Validate first character against the Dictionary?
            if (!valid_date_codes.ContainsKey(from_date))
                return null;

            var dates = valid_date_codes[from_date];
            var text = from_date.ToString();
            context.Source.PreviewPosition++;

            // Determine if it is a range by validating that the 
            // second preview character is a dash
            if (context.Source.PreviewChar == '-')
            {
                var thru_date = context.Source.NextPreviewChar;

                // Validate third character against the Dictionary?
                if (!valid_date_codes.ContainsKey(thru_date))
                    return null;

                text += "-" + thru_date.ToString();
                dates += " through " + valid_date_codes[thru_date];
                context.Source.PreviewPosition++;
                context.Source.PreviewPosition++;               
            }

            // Create a Token
            var line = context.Source.Location.Line;
            var col = context.Source.Location.Column;
            var sl = new SourceLocation(pos, line, col);

            return new Token(terminal, sl, text, dates);
        }
    }
}

 

Coordinator
Apr 15, 2010 at 6:37 AM

Looks good. Couple of things

Why you need to subclass CustomTerminal? you can use CustomTerminal itself directly in the grammar (two instances) and specify MatchHandlers for each. 

Names for non-terminals - it's better use names without spaces. With spaces the printout of states/productions in Parser States page will be quite confusing

In MovieDateHandler, at the end - calling new Token(...) - the last param is token Value; it should normally be a binary, parsed value, to be usable by processing code down the line; you put just a string there, just to see it in parse tree I guess, but for real code it should be some data structure for (dateFrom, dateUntil) pair.

 

Apr 15, 2010 at 8:55 AM

thnks for the code. but still need some help here. What the recommended way to debug this? what i have done is copy the new moviegrammar to Irony.Samples.Console and add a 3 option for it. then i run it and

>M
.M1200
Root AST node is null, cannot evaluate.

i hit M + enter which is not a valid entry but the cursor move to the next line. then i corrected the entry but i got the next message

Apr 15, 2010 at 2:01 PM

Roman,  thanks for the suggestions.  As for the first suggestion, I did this for the sake of demonstration as I would assume that typically the Custom Terminals and their Match Handlers would be in a separate file.  As for the last suggestion, you are correct, it was just to spit out the text to the parse tree;  it would be useless down stream. So, this would have to be adjusted depending on how it's to be used later.  You date pair suggestion would probably be most feasible.

Tolisss,  can you better describe how you added your 3 new options?  It appears that the AST Node construction has been enabled, yet the only thing I've provided is a simple parser grammar and two terminals.  You would still have to create AST nodes for the two custom terminals. Without the token to AST Node conversion, Irony doesn't know how to continue and would error out.

Thanks,

MindCore

Apr 15, 2010 at 2:04 PM

Hi

i have added the grammar at 020.Irony.Samples project compile and change the init code at 025.Irony.Samples.Console to

      Console.Title = "Irony Console Sample";
      Console.WriteLine("Irony Console Sample.");
      Console.WriteLine("");
      Console.WriteLine("Select a grammar to load:");
      Console.WriteLine("  1. Expression Evaluator");
      Console.WriteLine("  2. mini-Python");
      Console.WriteLine("  Or press any other key to exit.");
      Console.WriteLine("");
      Console.Write("?");
      var choice = Console.ReadLine();
      Grammar grammar; 
      switch (choice) {
        case "1": 
          grammar = new ExpressionEvaluatorGrammar();
          break;
        case "2":
          grammar = new MovieTimesGrammar();
          break; 
        default:
          return;
      }
      Console.Clear(); 
      var commandLine = new CommandLine(grammar);
      commandLine.Run();

Apr 15, 2010 at 2:29 PM

Ok,

Sorry, I misunderstood you when you said you added 3 options.  The 025.Irony.Samples.Console will do a full evaluation on the text you type in; it assumes that the Parser & Interpreter are fully functional.  Unfortunately, all I provided you was the Parser portion.

If you would like to see the parser function, set your start-up project to 030.Irony.GrammarExplorer, then run the solution.  At the top of the window is an area to add and select grammars.  Add your grammar from your compiled Irony.Samples.dll and select it.  Then go over to the Test tab, enter your text in the textarea, and click the parse button.  If there are no syntax errors, you should see the Parse Tree on the right.  You will also notice that the AST Node Tree isn't populated upon parsing and the evaluation doesn't run because this would still need to be done.

Let me know if that helps.

-MindCore

Apr 15, 2010 at 4:36 PM

Hi i look how u do it on Grammar explorer and i ll replicate in my app.

I have one more question on translate , 3D

what i really want to do is

MISC.Rule = ToTerm("D") |"L"|"DL"|"LD"|Empty;

does that sound right to u? Or i could make it more clever by introducing more NonTerminals?

Apr 15, 2010 at 5:55 PM

It really depends on how you intend to use the terminals.  Do the "D" and "L" signify different Terminals (does "D" mean one thing and "L" mean another - 2 unique terminals) or does each Terminal mean something different ("D","L", "DL" , or "LD" - 4 terminals)?  What you are specifying in the change is the latter.

If the first one is what you intend, you could do the following:

OPTION_D.Rule = ToTerm("D");
OPTION_L.Rule = ToTerm("L");
OPTION_DL.Rule = OPTION_D + OPTION_L;  // represents DL
OPTION_LD.Rule = OPTION_L + OPTION_D;  // represents LD
MISC.Rule = Empty | OPTION_D | OPTION_L | OPTION_DL | OPTION_LD;  // this would allow for only one instance for each D and L

 

One thing to note - the MakeStarRule approach probably won't suit your needs because it would allow multiple D's and L's

Example:
MISC.Rule = MakeStarRule(MISC , ToTerm("D") | ToTerm("L"));    //  this would allow for inputs such as DLDD or LL

-MindCore

Apr 19, 2010 at 1:56 PM

Hi again

thnks for your great support , but i would need to introduce one more symbol and cannot find the correct way . I want to introduce the & symbol just after days so the input may be now like. Take a note that & is not required

T-W14151645TM-W&22150045SA&1215DL. Could u please modify the code ?

Apr 19, 2010 at 2:29 PM

Here you are. You may be able to make the BNF terms more concise by changing DATE_AMP to something like DATE_AMP.Rule = DATE + (Empty | ToTerm("&")); however, it's usually better to start everything out in it's simplest form and then clean it up.

- MindCore

 

AMP_OPT.Rule = Empty | ToTerm("&");

OPTION_D.Rule = ToTerm("D");

OPTION_L.Rule = ToTerm("L");

OPTION_DL.Rule = OPTION_D + OPTION_L;

OPTION_LD.Rule = OPTION_L + OPTION_D;

MISC.Rule =  Empty | OPTION_D | OPTION_L | OPTION_DL | OPTION_LD;

TIME.Rule = MakePlusRule(TIME, Time);

DATE.Rule = MakePlusRule(DATE, Date);

DATE_AMP.Rule = DATE + AMP_OPT;

DATE_TIME_SET.Rule = DATE_AMP + TIME;

DATES_AND_TIMES.Rule = MakePlusRule(DATES_AND_TIMES, DATE_TIME_SET);

MOVIE.Rule = DATES_AND_TIMES + MISC;