public void Visit(Program program)
        {
            // the init op code sets up the call stack, to read command line arguments
            var main = program.Definitions.Single(d => d.Name == "main");

            tacs.Add(Tac.Init(main.Formals.Count));
            // call main
            tacs.Add(Tac.BeginCall(main.Name, main.Formals.Count, "t0"));
            for (int i = 0; i < main.Formals.Count; i++)
            {
                tacs.Add(Tac.Param($"arg{i}"));
            }
            tacs.Add(Tac.Call("main", "t0"));
            // call print
            tacs.Add(Tac.BeginCall("print", 1, "t1"));
            tacs.Add(Tac.Param("t0"));
            tacs.Add(Tac.Call("print", "t1"));
            tacs.Add(Tac.Halt());

            // declare print function
            tacs.Add(Tac.BeginFunc("print", 1));
            tacs.Add(Tac.PrintVariable("arg0"));
            tacs.Add(Tac.EndFunc("print"));

            foreach (var definition in program.Definitions)
            {
                definition.Accept(this);
            }
        }
 public void Visit(Definition definition)
 {
     // temps are local to the function so reset the counter for each func
     // (makes it easy to work out where the temp is stored in the stack frame)
     tempCounter = 0;
     Ast.SymbolTable.CurrentFunction = definition.Name;
     tacs.Add(Tac.BeginFunc(definition.Name, definition.Formals.Count));
     definition.Body.Accept(this);
     tacs.Add(Tac.EndFunc(definition.Name));
 }