Q:

Interactive input using C#?

I'm a C# and ANTLR newbie, and I can't figure out how to perform interactive line-by-line parsing using ANTLR4, in the same way that is shown in the ANTLR 4 Book's actions/tools/calc.java example in chapter 10, which uses the BufferedReader class (as far as I can tell .NET/C# don't have an equivalent) where each line of input is parsed immediately rather than all parsed at the end. Clearly, I could just instantiate new instances of the input stream, lexer, token stream, and parser for each line of input, but that doesn't seem to be the most efficient solution. What is the correct C# way to do this? 
Is there a repository somewhere that contains C# translations of the example programs? Also, is there a document that describes the differences between the Java runtime and the C# runtime? (e.g. in the calc.java example, the author has this line:
ExprParser parser = new ExprParser(null); // share single parser instance

which doesn't work with the C# runtime)
Here is the calc.java code for reference:
/***
 * Excerpted from "The Definitive ANTLR 4 Reference",
 * published by The Pragmatic Bookshelf.
 * Copyrights apply to this code. It may not be used to create training material, 
 * courses, books, articles, and the like. Contact us if you are in doubt.
 * We make no guarantees that this code is fit for any purpose. 
 * Visit http://www.pragmaticprogrammer.com/titles/tpantlr2 for more book information.
***/
package tools;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Calc {
    public static void main(String[] args) throws Exception {
        String inputFile = null;
        if ( args.length>0 ) inputFile = args[0];
        InputStream is = System.in;
        if ( inputFile!=null ) {
            is = new FileInputStream(inputFile);
        }

        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String expr = br.readLine();              // get first expression
        int line = 1;                             // track input expr line numbers

        ExprParser parser = new ExprParser(null); // share single parser instance
        parser.setBuildParseTree(false);          // don't need trees

        while ( expr!=null ) {             // while we have more expressions
            // create new lexer and token stream for each line (expression)
            ANTLRInputStream input = new ANTLRInputStream(expr+"\n");
            ExprLexer lexer = new ExprLexer(input);
            lexer.setLine(line);           // notify lexer of input position
            lexer.setCharPositionInLine(0);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            parser.setInputStream(tokens); // notify parser of new token stream
            parser.stat();                 // start the parser
            expr = br.readLine();          // see if there's another line
            line++;
        }
    }
}

Here is the grammar:
/** Grammar from tour chapter augmented with actions */

grammar Expr;

options
    {
    language = CSharp;              // The semantic actions are written in C#, so this grammar can only be used with the C# code generator
    }

@parser::members 
    {
        /** "memory" for our calculator; variable/value pairs go here */
        Dictionary <string, int> memory = new Dictionary <string, int> ();

        int eval (int left, int op, int right)
            {
            switch (op)
                {
                case MUL : return left * right;
                case DIV : return left / right;
                case ADD : return left + right;
                case SUB : return left - right;
                }
            return 0;
            }   // End eval

    }

stat:   e NEWLINE           {Console.WriteLine ($e.v);}
    |   ID '=' e NEWLINE    {memory.Add ($ID.text, $e.v);}
    |   NEWLINE                   
    ;

e returns [int v]
    : a=e op=('*'|'/') b=e  {$v = eval ($a.v, $op.type, $b.v);}
    | a=e op=('+'|'-') b=e  {$v = eval ($a.v, $op.type, $b.v);}  
    | INT                   {$v = $INT.int;}    
    | ID
      {
      string id = $ID.text;
      $v = memory.ContainsKey (id) ? memory [id] : 0;
      }
    | '(' e ')'             {$v = $e.v;}       
    ; 

MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;

ID  :   [a-zA-Z]+ ;      // match identifiers
INT :   [0-9]+ ;         // match integers
NEWLINE:'\r'? '\n' ;     // return newlines to parser (is end-statement signal)
WS  :   [ \t]+ -> skip ; // toss out whitespace

A:

Here is the code that I came up with that solves the problem. There are probably more efficient ways to do this, and I'll probably be embarrassed by this in a few years ;-)
//
// C# version of code\actions\tools\Calc.java in Chapter 10 of "The Definitive ANTLR 4 Reference"
//

using System;
using System.IO;
using System.Text;

using Antlr4.Runtime;

namespace Calc
    {
    class Program
        {
        static void Main (string [] Args)
            {
            StreamReader  input_src;

            //
            // If there is a file name on the command line, then use it as the input source; otherwise,
            // use the console (keyboard) as the input source
            //

            if (Args.Length > 0)
                {
                input_src = File.OpenText (Args [0]);
                }
            else
                {
                Console.WriteLine ("Enter expressions to evaluate");
                input_src = new StreamReader (Console.OpenStandardInput (), Console.InputEncoding);
                }

            //
            // Read the first line from the input source
            //

            string              input = input_src.ReadLine ();
            int                 cur_line = 1;                                           // Needed when parsing lines in a file

            //
            // Create a parser without a token source. This allows us to instantiate the parser just
            // once, preserving the @parser::members declared in the grammar. Later, we'll attach the
            // parser to a token stream
            //

            ExprParser          parser = new ExprParser (null);

            parser.BuildParseTree = false;

            //
            // Loop getting input from the input source (console or file) until end of file (or CTRL-Z if input is console)
            //

            while (input != null)
                {

                //
                // The grammar is expecting a NEWLINE as a statement terminator, but that isn't included by ReadLine so add a NEWLINE
                // to the end of the input string
                //

                input = input + "\n";

                //
                // Turn the input string into a stream compatible with ANTLR
                //

                byte []             input_bytes = Encoding.ASCII.GetBytes (input);
                MemoryStream        mem_stream = new MemoryStream (input_bytes);

                //
                // Attach ANTLR to the memory stream
                //

                AntlrInputStream    input_stream = new AntlrInputStream (mem_stream);   // Create a stream that reads from the input source
                ExprLexer           lexer = new ExprLexer (input_stream);               // Create a lexer that feeds off of the input stream

                //
                // When reading from a file the line number is important for error messages. Normally, we would read the entire file into
                // a string and then parse it, but we're not doing that; we are parsing each line as we read it, so tell the lexer the current
                // line number and character position before it lexes each input line. If we didn't do this, the error reporting mechanism 
                // would always report that the error was on line 1
                //

                lexer.Line = cur_line;
                lexer.Column = 0;

                CommonTokenStream   tokens = new CommonTokenStream (lexer);             // Create a buffer of tokens pulled from the lexer

                //
                // Attach the parser to the new token stream (the current line), and start the parse by calling the 'stat' rule in the grammar
                // The semantic actions will then do all the work of outputting the results from processing the expressions
                //

                parser.TokenStream = tokens;
                parser.stat ();

                //
                // Get the next line of input from the input source
                //

                input = input_src.ReadLine ();
                cur_line = cur_line + 1;
                }   // End while

            }   // End Main

        }   // End class Program

    }   // End namespace Calc

