using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("WaveSabreConvert")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("WaveSabreConvert")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("cb486e3c-ac33-41ae-a2e0-dac07982bb82")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


<?php
class NamespaceCoverageNotPrivateTest extends PHPUnit_Framework_TestCase
{
    /**
     * @covers Foo\CoveredClass::<!private>
     */
    public function testSomething()
    {
        $o = new Foo\CoveredClass;
        $o->publicMethod();
    }
}


--DOC_GEN_OUTPUT --DOC_GEN_IMAGE --DOC_HIDE
local awful = {placement = require("awful.placement")} --DOC_HIDE

--DOC_HIDE no_offscreen is auto-called when startup is true, avoid this.
awesome.startup = false -- luacheck: globals awesome.startup --DOC_HIDE

local c = client.gen_fake {x = -30, y = -30, width= 100, height=100} --DOC_HIDE

print("Before:", "x="..c.x..", y="..c.y..", width="..c.width..", height="..c.height) --DOC_HIDE

awful.placement.no_offscreen(c, {honor_workarea=true, margins=40})

print("After:", "x="..c.x..", y="..c.y..", width="..c.width..", height="..c.height) --DOC_HIDE

--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80


/*jshint expr:true */

describe('Offline', function() {

  describe('App', function() {

    var events;

    beforeEach(function() {
      events = ['up', 'down', 'confirmed-down', 'confirmed-up'];
      window.app = new window.Encryptr();
      sinon.stub(window.app, 'setOffline');
      sinon.stub(window.app, 'setOnline');
      window.app.init();
    });

    afterEach(function() {
      window.app.setOffline.restore();
      window.app.setOnline.restore();
    });

    describe('offline module (offlineJs)', function() {

      describe('should have handler method', function(){

        beforeEach(function() {
          events = ['up', 'down', 'confirmed-down', 'confirmed-up'];
          window.app = new window.Encryptr();
          sinon.stub(window.app, 'setOffline');
          sinon.stub(window.app, 'setOnline');
          sinon.stub(window.Offline, 'on');
          window.app.init();
        });

        afterEach(function() {
          window.Offline.on.restore();
        });

        it('up', function() {
          window.Offline.on.calledWith('up').should.be.true();
        });

        it('down', function() {
          window.Offline.on.calledWith('down').should.be.true();
        });

        it('confirmed-up', function() {
          window.Offline.on.calledWith('confirmed-up').should.be.true();
        });

        it('confirmed-down', function() {
          window.Offline.on.calledWith('confirmed-down').should.be.true();
        });

      });

      it('should call setOnline mehod in up event', function() {
        window.Offline.markUp();
        window.app.setOnline.called.should.be.true();
      });

      it('should call setOnline mehod in confirmed-up event', function() {
        window.Offline.markUp();
        window.app.setOnline.called.should.be.true();
      });

      it('should call setOffline mehod in confirmed-down event', function() {
        window.Offline.markDown();
        window.app.setOffline.called.should.be.true();
      });

      it('should call setOffline mehod in down event', function() {
        window.Offline.markDown();
        window.app.setOffline.called.should.be.true();
      });

    });

    describe('loadOfflineData', function(){

      var data, file, original_sessionStorage, promose_function, username;

      beforeEach(function() {
        promise_function = function() {
          var promise = $.Deferred();
          promise.resolve(data);
          return promise;
        };
        data = 'data';
        username = 'username';
        file = 'encryptrusername.data';
        original_sessionStorage = JSON.parse(JSON.stringify(window.sessionStorage));
        window.sessionStorage.setItem = sinon.stub().returns(promise_function());
        window.sessionStorage.getItem = sinon.stub().returns(null);
        window.crypton.online = false;
      });

      afterEach(function() {
          window.sessionStorage = original_sessionStorage;
          window.crypton.online = true;
        });

      it('should have loadOfflineData method', function() {
        window.app.loadOfflineData.should.be.an('function');
      });

      describe('ios, andriod or bb10', function() {

        beforeEach(function() {
          $.os.ios = true;
          $.os.andriod = true;
          $.os.bb10 = true;
          sinon.stub(window.app, 'readOfflineDataCordova', promise_function);
        });

        afterEach(function() {
          $.os.ios = false;
          $.os.andriod = false;
          $.os.bb10 = false;
          window.app.readOfflineDataCordova.restore();
        });

        it('should call readOfflineDataCordova method', function(done) {
          window.app.loadOfflineData(username).then(function() {
            window.app.readOfflineDataCordova.called.should.be.true();
          }).then(done);
        });

        it('should call readOfflineDataCordova method with correct params', function(done) {
          window.app.loadOfflineData(username).then(function() {
            window.app.readOfflineDataCordova.calledWith(file).should.be.true();
          }).then(done);
        });

        it('should call window.sessionStorage.setItem method', function(done) {
          window.app.loadOfflineData(username).then(function() {
            window.sessionStorage.setItem.called.should.be.true();
          }).then(done);
        });

        it('should call window.sessionStorage.setItem method with correct params', function(done) {
          window.app.loadOfflineData(username).then(function() {
            window.sessionStorage.setItem.calledWith('crypton', data).should.be.true();
          }).then(done);
        });

      });

      describe('desktop', function() {

        beforeEach(function() {
          $.os.nodeWebkit = true;
          sinon.stub(window.app, 'readOfflineDataInDesktop', promise_function);
        });

        afterEach(function() {
          $.os.nodeWebkit = false;
          window.app.readOfflineDataInDesktop.restore();
        });

        it('should call readOfflineDataInDesktop method', function(done) {
          window.app.loadOfflineData(username).then(function() {
            window.app.readOfflineDataInDesktop.called.should.be.true();
          }).then(done);
        });

        it('should call readOfflineDataInDesktop method with correct params', function(done) {
          window.app.loadOfflineData(username).then(function() {
            window.app.readOfflineDataInDesktop.calledWith(file).should.be.true();
          }).then(done);
        });

        it('should call window.sessionStorage.setItem method', function(done) {
          window.app.loadOfflineData(username).then(function() {
            window.sessionStorage.setItem.called.should.be.true();
          }).then(done);
        });

        it('should call window.sessionStorage.setItem method with correct params', function(done) {
          window.app.loadOfflineData(username).then(function() {
            window.sessionStorage.setItem.calledWith('crypton', data).should.be.true();
          }).then(done);
        });

      });

    });

    describe('readCordovaFile', function(){

      var directory;

      beforeEach(function() {
        directory = 'cdb:///files/';
        window.resolveLocalFileSystemURL = sinon.stub();
      });

      afterEach(function() {
        window.resolveLocalFileSystemURL = undefined;
      });

      it('should have readCordovaFile method', function() {
        window.app.readCordovaFile.should.be.an('function');
      });

      it('should call window.resolveLocalFileSystemURL method', function(){
        window.app.readCordovaFile(directory);
        window.resolveLocalFileSystemURL.called.should.be.true();
      });

      it('should call window.resolveLocalFileSystemURL method with correct params', function(){
        window.app.readCordovaFile(directory);
        window.resolveLocalFileSystemURL.calledWith(directory).should.be.true();
      });

    });

    describe('readOfflineDataCordova', function(){

      var file;

      beforeEach(function() {
        fiel = 'encryptrusername.data';
        sinon.stub(window.app, 'readCordovaFile');
      });

      afterEach(function() {
        window.app.readCordovaFile.restore();
      });

      it('should have readOfflineDataCordova method', function() {
        window.app.readOfflineDataCordova.should.be.an('function');
      });

      it('should have readOfflineDataCordova method', function() {
        window.app.readOfflineDataCordova(file);
        window.app.readCordovaFile.called.should.be.true();
      });

      it('should have readOfflineDataCordova method with correct params', function() {
        window.app.readOfflineDataCordova(file);
        window.app.readCordovaFile.calledWith(cordova.file.dataDirectory, file).should.be.true();
      });

    });

    describe('readOfflineDataInDesktop', function(){

      var filePath, file;

      beforeEach(function() {
        file = 'encryptrusername.data'
        filePath = 'file://filePath';
        require = sinon.stub().returns({
          join: sinon.stub().returns(filePath),
          readFile: sinon.stub(),
          App: {
            dataPath: 'dataPath'
          }
        });
      });

      afterEach(function() {
        require = undefined;
      });

      it('should have readOfflineDataInDesktop method', function() {
        window.app.readOfflineDataInDesktop.should.be.an('function');
      });

      it('should call require nw.gui', function() {
        window.app.readOfflineDataInDesktop(file);
        require.calledWith('nw.gui').should.be.true();
      });

      it('should call require fs', function() {
        window.app.readOfflineDataInDesktop(file);
        require.calledWith('fs').should.be.true();
      });

      it('should call require path', function() {
        window.app.readOfflineDataInDesktop(file);
        require.calledWith('path').should.be.true();
      });

      it('should call path.join', function() {
        window.app.readOfflineDataInDesktop(file);
        require('path').join.called.should.be.true();
      });

      it('should call path.join with correct params', function() {
        var nw = require('nw.gui');
        window.app.readOfflineDataInDesktop(file);
        require('path').join.calledWith(nw.App.dataPath, file).should.be.true();
      });

      it('should call fs.readFile', function() {
        window.app.readOfflineDataInDesktop(file);
        require('fs').readFile.called.should.be.true();
      });

      it('should call fs.readFile', function() {
        window.app.readOfflineDataInDesktop(file);
        require('fs').readFile.calledWith(filePath, 'utf8').should.be.true();
      });

    });

    describe('checkonline', function(){

      var buttons;

      beforeEach(function() {
        window.app.offline_btns = [];
        window.app.online = true;
        buttons = ['btn1', 'btn2'];
      });

      it('should have checkonline method', function() {
        window.app.checkonline.should.be.an('function');
      });

      it('should add btns to offline_btns', function() {
        window.app.checkonline(buttons);
        window.app.offline_btns.should.be.eql(buttons);
      });

      it('should add btns to offline_btns and not repeat', function() {
        window.app.checkonline(buttons);
        window.app.checkonline(buttons);
        window.app.offline_btns.should.be.eql(buttons);
      });

      it('should call setOnline when online is true', function() {
        window.app.checkonline(buttons);
        window.app.setOnline.called.should.be.true();
      });

      it('should call setOffline when online is false', function() {
        window.app.online = false;
        window.app.checkonline(buttons);
        window.app.setOffline.called.should.be.true();
      });

    });

    describe('setOffline', function(){

      beforeEach(function() {
        window.app.setOffline.restore();
        window.app.setOnline.restore();
      });

      afterEach(function() {
        sinon.stub(window.app, 'setOffline');
        sinon.stub(window.app, 'setOnline');
      });

      it('should have setOffline method', function() {
        window.app.setOffline.should.be.an('function');
      });

      it('should set online in false', function() {
        window.app.setOffline();
        window.app.online.should.be.false();
      });

      it('should set window.crypton.online in false', function() {
        window.app.setOffline();
        window.crypton.online.should.be.false();
      });

      it('should set window.online in false', function() {
        window.app.setOffline();
        window.online.should.be.false();
      });

      it('should call loadOfflineData when getItem is null', function() {
        sinon.stub(app, 'loadOfflineData');
        window.app.setOffline();
        app.loadOfflineData.called.should.be.true();
        app.loadOfflineData.restore();
      });

    });

    describe('setOnline', function(){

      beforeEach(function() {
        window.app.setOffline.restore();
        window.app.setOnline.restore();
      });

      afterEach(function() {
        sinon.stub(window.app, 'setOffline');
        sinon.stub(window.app, 'setOnline');
      });

      it('should have setOnline method', function() {
        window.app.setOnline.should.be.an('function');
      });

      it('should set online in true', function() {
        window.app.setOnline();
        window.app.online.should.be.true();
      });

      it('should set window.crypton.online in true', function() {
        window.app.setOnline();
        window.crypton.online.should.be.true();
      });

      it('should set window.online in true', function() {
        window.app.setOnline();
        window.online.should.be.true();
      });

    });

  });

  describe('DialogConfirmView (DialogView.js)', function() {

    it('should call app.checkonline method in initialize', function() {
      sinon.stub(app, 'checkonline');
      try {
        new window.app.DialogConfirmView();
      } catch(e) {
        app.checkonline.called.should.be.true();
      }
      app.checkonline.restore();
    });

  });

  describe('EditView', function() {

    it('should call app.checkonline method in initialize', function() {
      sinon.stub(app, 'checkonline');
      try {
        new window.app.EditView();
      } catch(e) {
        app.checkonline.called.should.be.true();
      }
      app.checkonline.restore();
    });

  });

  describe('EntryView', function() {

    it('should call app.checkonline method in initialize', function() {
      sinon.stub(app, 'checkonline');
      try {
        new window.app.EntryView();
      } catch(e) {
        app.checkonline.called.should.be.true();
      }
      app.checkonline.restore();
    });

  });

  describe('SettingView', function() {

    it('should call app.checkonline method in initialize', function() {
      sinon.stub(app, 'checkonline');
      try {
        new window.app.SettingsView();
      } catch(e) {
        app.checkonline.called.should.be.true();
      }
      app.checkonline.restore();
    });

  });

  describe('MainView', function() {

    var view;

    beforeEach(function() {
      $.os.nodeWebkit = false;
      $.os.android = false;
      $.os.ios = false;
      $.os.bb10 = false;
      view = new window.app.MainView();
      window.app.mainView = view;
      sinon.stub(view, 'updateLocalStorage', promise_function);
    });

    it('should call app.checkonline method in initialize', function() {
      sinon.stub(app, 'checkonline');
      try {
        new window.app.MainView();
      } catch(e) {
        app.checkonline.called.should.be.true();
      }
      app.checkonline.restore();
    });

    describe('updateLocalStorage', function(){

      var file, data, username;

      beforeEach(function() {
        file = 'encryptrusername.data';
        data = null;
        username = 'username';
        window.crypton.online = false;
        sinon.stub(view, 'getEntries', promise_function);
        window.app.accountModel = {
          get: function() {
            return username;
          }
        };
        view.updateLocalStorage.restore();
        window.app.entriesCollection = {
          length: 1,
        };
        window.app.entriesView = {
          reloadIndex: promise_function
        };
      });

      afterEach(function() {
        view.getEntries.restore();
        window.crypton.online = true;
      });

      it('should have updateLocalStorage method', function() {
        view.updateLocalStorage.should.be.an('function');
      });

      describe('ios, andriod or bb10', function() {

        beforeEach(function() {
          $.os.ios = true;
          $.os.andriod = true;
          $.os.bb10 = true;
          sinon.stub(view, 'saveOfflineDataCordova', promise_function);
        });

        afterEach(function() {
          $.os.ios = false;
          $.os.andriod = false;
          $.os.bb10 = false;
          view.saveOfflineDataCordova.restore();
        });

        it('should call saveOfflineDataCordova method', function(done) {
          view.updateLocalStorage().then(function() {
            view.saveOfflineDataCordova.called.should.be.true();
          }).then(done);
        });

        it('should call saveOfflineDataCordova method with correct params', function(done) {
          view.updateLocalStorage().then(function() {
            view.saveOfflineDataCordova.calledWith(file, data).should.be.true();
          }).then(done);
        });

        it('should call window.sessionStorage.getItem method', function(done) {
          view.updateLocalStorage().then(function() {
            window.sessionStorage.getItem.called.should.be.true();
          }).then(done);
        });

      });

      describe('desktop', function() {

        beforeEach(function() {
          $.os.nodeWebkit = true;
          sinon.stub(view, 'saveOfflineDataInDesktop', promise_function);
        });

        afterEach(function() {
          $.os.nodeWebkit = false;
          view.saveOfflineDataInDesktop.restore();
        });

        it('should call saveOfflineDataInDesktop method', function(done) {
          view.updateLocalStorage().then(function() {
            view.saveOfflineDataInDesktop.called.should.be.true();
          }).then(done);
        });

        it('should call saveOfflineDataInDesktop method with correct params', function(done) {
          view.updateLocalStorage().then(function() {
            view.saveOfflineDataInDesktop.calledWith(file, data).should.be.true();
          }).then(done);
        });

        it('should call window.sessionStorage.getItem method', function(done) {
          view.updateLocalStorage().then(function() {
            window.sessionStorage.getItem.called.should.be.true();
          }).then(done);
        });

        it('should call window.sessionStorage.getItem method with correct params', function(done) {
          view.updateLocalStorage().then(function() {
            window.sessionStorage.getItem.calledWith('crypton').should.be.true();
          }).then(done);
        });

      });

    });

    describe('saveOfflineDataInDesktop', function(){

      var filePath, file, data;

      beforeEach(function() {
        file = 'encryptrusername.data';
        data = 'data';
        filePath = 'file://filePath';
        require = sinon.stub().returns({
          join: sinon.stub().returns(filePath),
          writeFile: sinon.stub(),
          App: {
            dataPath: 'dataPath'
          }
        });
      });

      afterEach(function() {
        require = undefined;
      });

      it('should have saveOfflineDataInDesktop method', function() {
        view.saveOfflineDataInDesktop.should.be.an('function');
      });

      it('should have saveOfflineDataInDesktop method', function() {
        view.saveOfflineDataInDesktop.should.be.an('function');
      });

      it('should call require nw.gui', function() {
        view.saveOfflineDataInDesktop(file, data);
        require.calledWith('nw.gui').should.be.true();
      });

      it('should call require fs', function() {
        view.saveOfflineDataInDesktop(file, data);
        require.calledWith('fs').should.be.true();
      });

      it('should call require path', function() {
        view.saveOfflineDataInDesktop(file, data);
        require.calledWith('path').should.be.true();
      });

      it('should call path.join', function() {
        view.saveOfflineDataInDesktop(file, data);
        require('path').join.called.should.be.true();
      });

      it('should call path.join with correct params', function() {
        var nw = require('nw.gui');
        view.saveOfflineDataInDesktop(file, data);
        require('path').join.calledWith(nw.App.dataPath, file).should.be.true();
      });

      it('should call fs.writeFile', function() {
        view.saveOfflineDataInDesktop(file, data);
        require('fs').writeFile.called.should.be.true();
      });

      it('should call fs.writeFile', function() {
        view.saveOfflineDataInDesktop(file, data);
        require('fs').writeFile.calledWith(filePath, data).should.be.true();
      });

    });

    describe('saveOfflineDataCordova', function(){

      var file, data;

      beforeEach(function() {
        file = 'encryptrusername.data';
        data = 'data';
        sinon.stub(view, 'writeCordovaFile');
      });

      afterEach(function() {
        view.writeCordovaFile.restore();
      });

      it('should have saveOfflineDataCordova method', function() {
        view.saveOfflineDataCordova.should.be.an('function');
      });

      it('should have writeCordovaFile method', function() {
        view.saveOfflineDataCordova(file, data);
        view.writeCordovaFile.called.should.be.true();
      });

      it('should have writeCordovaFile method with correct params', function() {
        view.saveOfflineDataCordova(file, data);
        view.writeCordovaFile.calledWith(cordova.file.dataDirectory, file, data).should.be.true();
      });

    });

  });

});


/*
	This file is part of solidity.

	solidity is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	solidity is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#include <test/tools/ossfuzz/protoToYul.h>
#include <test/tools/ossfuzz/yulOptimizerFuzzDictionary.h>

#include <libyul/Exceptions.h>

#include <libsolutil/StringUtils.h>

#include <boost/algorithm/cxx11/all_of.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/range/algorithm_ext/erase.hpp>

using namespace std;
using namespace solidity::yul::test::yul_fuzzer;
using namespace solidity::yul::test;
using namespace solidity::langutil;
using namespace solidity::util;
using namespace solidity;

string ProtoConverter::dictionaryToken(HexPrefix _p)
{
	std::string token;
	// If dictionary constant is requested while converting
	// for loop condition, then return zero so that we don't
	// generate infinite for loops.
	if (m_inForCond)
		token = "0";
	else
	{
		unsigned indexVar = m_inputSize * m_inputSize + counter();
		token = hexDictionary[indexVar % hexDictionary.size()];
		yulAssert(token.size() <= 64, "Proto Fuzzer: Dictionary token too large");
	}

	return _p == HexPrefix::Add ? "0x" + token : token;
}

string ProtoConverter::createHex(string const& _hexBytes)
{
	string tmp{_hexBytes};
	if (!tmp.empty())
	{
		boost::range::remove_erase_if(tmp, [=](char c) -> bool {
			return !std::isxdigit(c);
		});
		tmp = tmp.substr(0, 64);
	}
	// We need this awkward if case because hex literals cannot be empty.
	// Use a dictionary token.
	if (tmp.empty())
		tmp = dictionaryToken(HexPrefix::DontAdd);
	// Hex literals must have even number of digits
	if (tmp.size() % 2)
		tmp.insert(0, "0");

	yulAssert(tmp.size() <= 64, "Proto Fuzzer: Dictionary token too large");
	return tmp;
}

string ProtoConverter::createAlphaNum(string const& _strBytes)
{
	string tmp{_strBytes};
	if (!tmp.empty())
	{
		boost::range::remove_erase_if(tmp, [=](char c) -> bool {
			return !(std::isalpha(c) || std::isdigit(c));
		});
		tmp = tmp.substr(0, 32);
	}
	return tmp;
}

EVMVersion ProtoConverter::evmVersionMapping(Program_Version const& _ver)
{
	switch (_ver)
	{
	case Program::HOMESTEAD:
		return EVMVersion::homestead();
	case Program::TANGERINE:
		return EVMVersion::tangerineWhistle();
	case Program::SPURIOUS:
		return EVMVersion::spuriousDragon();
	case Program::BYZANTIUM:
		return EVMVersion::byzantium();
	case Program::CONSTANTINOPLE:
		return EVMVersion::constantinople();
	case Program::PETERSBURG:
		return EVMVersion::petersburg();
	case Program::ISTANBUL:
		return EVMVersion::istanbul();
	case Program::BERLIN:
		return EVMVersion::berlin();
	}
}

string ProtoConverter::visit(Literal const& _x)
{
	switch (_x.literal_oneof_case())
	{
	case Literal::kIntval:
		return to_string(_x.intval());
	case Literal::kHexval:
		return "0x" + createHex(_x.hexval());
	case Literal::kStrval:
		return "\"" + createAlphaNum(_x.strval()) + "\"";
	case Literal::kBoolval:
		return _x.boolval() ? "true" : "false";
	case Literal::LITERAL_ONEOF_NOT_SET:
		return dictionaryToken();
	}
}

void ProtoConverter::consolidateVarDeclsInFunctionDef()
{
	m_currentFuncVars.clear();
	yulAssert(!m_funcVars.empty(), "Proto fuzzer: Invalid operation");

	auto const& scopes = m_funcVars.back();
	for (auto const& s: scopes)
		for (auto const& var: s)
			m_currentFuncVars.push_back(&var);
	yulAssert(!m_funcForLoopInitVars.empty(), "Proto fuzzer: Invalid operation");
	auto const& forinitscopes = m_funcForLoopInitVars.back();
	for (auto const& s: forinitscopes)
		for (auto const& var: s)
			m_currentFuncVars.push_back(&var);
}

void ProtoConverter::consolidateGlobalVarDecls()
{
	m_currentGlobalVars.clear();
	// Place pointers to all global variables that are in scope
	// into a single vector
	for (auto const& scope: m_globalVars)
		for (auto const& var: scope)
			m_currentGlobalVars.push_back(&var);
	// Place pointers to all variables declared in for-init blocks
	// that are still live into the same vector
	for (auto const& init: m_globalForLoopInitVars)
		for (auto const& var: init)
			m_currentGlobalVars.push_back(&var);
}

bool ProtoConverter::varDeclAvailable()
{
	if (m_inFunctionDef)
	{
		consolidateVarDeclsInFunctionDef();
		return m_currentFuncVars.size() > 0;
	}
	else
	{
		consolidateGlobalVarDecls();
		return m_currentGlobalVars.size() > 0;
	}
}

bool ProtoConverter::functionCallNotPossible(FunctionCall_Returns _type)
{
	return _type == FunctionCall::SINGLE ||
		(_type == FunctionCall::MULTIASSIGN && !varDeclAvailable());
}

unsigned ProtoConverter::numVarsInScope()
{
	if (m_inFunctionDef)
		return m_currentFuncVars.size();
	else
		return m_currentGlobalVars.size();
}

void ProtoConverter::visit(VarRef const& _x)
{
	if (m_inFunctionDef)
	{
		// Ensure that there is at least one variable declaration to reference in function scope.
		yulAssert(m_currentFuncVars.size() > 0, "Proto fuzzer: No variables to reference.");
		m_output << *m_currentFuncVars[static_cast<size_t>(_x.varnum()) % m_currentFuncVars.size()];
	}
	else
	{
		// Ensure that there is at least one variable declaration to reference in nested scopes.
		yulAssert(m_currentGlobalVars.size() > 0, "Proto fuzzer: No global variables to reference.");
		m_output << *m_currentGlobalVars[static_cast<size_t>(_x.varnum()) % m_currentGlobalVars.size()];
	}
}

void ProtoConverter::visit(Expression const& _x)
{
	switch (_x.expr_oneof_case())
	{
	case Expression::kVarref:
		// If the expression requires a variable reference that we cannot provide
		// (because there are no variables in scope), we silently output a literal
		// expression from the optimizer dictionary.
		if (!varDeclAvailable())
			m_output << dictionaryToken();
		else
			visit(_x.varref());
		break;
	case Expression::kCons:
		// If literal expression describes for-loop condition
		// then force it to zero, so we don't generate infinite
		// for loops
		if (m_inForCond)
			m_output << "0";
		else
			m_output << visit(_x.cons());
		break;
	case Expression::kBinop:
		visit(_x.binop());
		break;
	case Expression::kUnop:
		visit(_x.unop());
		break;
	case Expression::kTop:
		visit(_x.top());
		break;
	case Expression::kNop:
		visit(_x.nop());
		break;
	case Expression::kFuncExpr:
		// FunctionCall must return a single value, otherwise
		// we output a trivial expression "1".
		if (_x.func_expr().ret() == FunctionCall::SINGLE)
			visit(_x.func_expr());
		else
			m_output << dictionaryToken();
		break;
	case Expression::kLowcall:
		visit(_x.lowcall());
		break;
	case Expression::kCreate:
		visit(_x.create());
		break;
	case Expression::kUnopdata:
		if (m_isObject)
			visit(_x.unopdata());
		else
			m_output << dictionaryToken();
		break;
	case Expression::EXPR_ONEOF_NOT_SET:
		m_output << dictionaryToken();
		break;
	}
}

void ProtoConverter::visit(BinaryOp const& _x)
{
	BinaryOp_BOp op = _x.op();

	if ((op == BinaryOp::SHL || op == BinaryOp::SHR || op == BinaryOp::SAR) &&
		!m_evmVersion.hasBitwiseShifting())
	{
		m_output << dictionaryToken();
		return;
	}

	switch (op)
	{
	case BinaryOp::ADD:
		m_output << "add";
		break;
	case BinaryOp::SUB:
		m_output << "sub";
		break;
	case BinaryOp::MUL:
		m_output << "mul";
		break;
	case BinaryOp::DIV:
		m_output << "div";
		break;
	case BinaryOp::MOD:
		m_output << "mod";
		break;
	case BinaryOp::XOR:
		m_output << "xor";
		break;
	case BinaryOp::AND:
		m_output << "and";
		break;
	case BinaryOp::OR:
		m_output << "or";
		break;
	case BinaryOp::EQ:
		m_output << "eq";
		break;
	case BinaryOp::LT:
		m_output << "lt";
		break;
	case BinaryOp::GT:
		m_output << "gt";
		break;
	case BinaryOp::SHR:
		yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version");
		m_output << "shr";
		break;
	case BinaryOp::SHL:
		yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version");
		m_output << "shl";
		break;
	case BinaryOp::SAR:
		yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version");
		m_output << "sar";
		break;
	case BinaryOp::SDIV:
		m_output << "sdiv";
		break;
	case BinaryOp::SMOD:
		m_output << "smod";
		break;
	case BinaryOp::EXP:
		m_output << "exp";
		break;
	case BinaryOp::SLT:
		m_output << "slt";
		break;
	case BinaryOp::SGT:
		m_output << "sgt";
		break;
	case BinaryOp::BYTE:
		m_output << "byte";
		break;
	case BinaryOp::SI:
		m_output << "signextend";
		break;
	case BinaryOp::KECCAK:
		m_output << "keccak256";
		break;
	}
	m_output << "(";
	visit(_x.left());
	m_output << ",";
	visit(_x.right());
	m_output << ")";
}

void ProtoConverter::scopeVariables(vector<string> const& _varNames)
{
	// If we are inside a for-init block, there are two places
	// where the visited vardecl may have been defined:
	// - directly inside the for-init block
	// - inside a block within the for-init block
	// In the latter case, we don't scope extend. The flag
	// m_forInitScopeExtEnabled (= true) indicates whether we are directly
	// inside a for-init block e.g., for { let x } or (= false) inside a
	// nested for-init block e.g., for { { let x } }
	bool forInitScopeExtendVariable = m_inForInitScope && m_forInitScopeExtEnabled;

	// There are four cases that are tackled here
	// Case 1. We are inside a function definition and the variable declaration's
	// scope needs to be extended.
	// Case 2. We are inside a function definition but scope extension is disabled
	// Case 3. We are inside global scope and scope extension is required
	// Case 4. We are inside global scope but scope extension is disabled
	if (m_inFunctionDef)
	{
		// Variables declared directly in for-init block
		// are tracked separately because their scope
		// extends beyond the block they are defined in
		// to the rest of the for-loop statement.
		// Case 1
		if (forInitScopeExtendVariable)
		{
			yulAssert(
				!m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(),
				"Proto fuzzer: Invalid operation"
			);
			for (auto const& varName: _varNames)
				m_funcForLoopInitVars.back().back().push_back(varName);
		}
		// Case 2
		else
		{
			yulAssert(
				!m_funcVars.empty() && !m_funcVars.back().empty(),
				"Proto fuzzer: Invalid operation"
			);
			for (auto const& varName: _varNames)
				m_funcVars.back().back().push_back(varName);
		}
	}
	// If m_inFunctionDef is false, we are in global scope
	else
	{
		// Case 3
		if (forInitScopeExtendVariable)
		{
			yulAssert(!m_globalForLoopInitVars.empty(), "Proto fuzzer: Invalid operation");

			for (auto const& varName: _varNames)
				m_globalForLoopInitVars.back().push_back(varName);
		}
		// Case 4
		else
		{
			yulAssert(!m_globalVars.empty(), "Proto fuzzer: Invalid operation");

			for (auto const& varName: _varNames)
				m_globalVars.back().push_back(varName);
		}
	}
}

void ProtoConverter::visit(VarDecl const& _x)
{
	string varName = newVarName();
	m_output << "let " << varName << " := ";
	visit(_x.expr());
	m_output << "\n";
	scopeVariables({varName});
}

void ProtoConverter::visit(MultiVarDecl const& _x)
{
	m_output << "let ";
	vector<string> varNames;
	// We support up to 4 variables in a single
	// declaration statement.
	unsigned numVars = _x.num_vars() % 3 + 2;
	string delimiter = "";
	for (unsigned i = 0; i < numVars; i++)
	{
		string varName = newVarName();
		varNames.push_back(varName);
		m_output << delimiter << varName;
		if (i == 0)
			delimiter = ", ";
	}
	m_output << "\n";
	scopeVariables(varNames);
}

void ProtoConverter::visit(TypedVarDecl const& _x)
{
	string varName = newVarName();
	m_output << "let " << varName;
	switch (_x.type())
	{
	case TypedVarDecl::BOOL:
		m_output << ": bool := ";
		visit(_x.expr());
		m_output << " : bool\n";
		break;
	case TypedVarDecl::S8:
		m_output << ": s8 := ";
		visit(_x.expr());
		m_output << " : s8\n";
		break;
	case TypedVarDecl::S32:
		m_output << ": s32 := ";
		visit(_x.expr());
		m_output << " : s32\n";
		break;
	case TypedVarDecl::S64:
		m_output << ": s64 := ";
		visit(_x.expr());
		m_output << " : s64\n";
		break;
	case TypedVarDecl::S128:
		m_output << ": s128 := ";
		visit(_x.expr());
		m_output << " : s128\n";
		break;
	case TypedVarDecl::S256:
		m_output << ": s256 := ";
		visit(_x.expr());
		m_output << " : s256\n";
		break;
	case TypedVarDecl::U8:
		m_output << ": u8 := ";
		visit(_x.expr());
		m_output << " : u8\n";
		break;
	case TypedVarDecl::U32:
		m_output << ": u32 := ";
		visit(_x.expr());
		m_output << " : u32\n";
		break;
	case TypedVarDecl::U64:
		m_output << ": u64 := ";
		visit(_x.expr());
		m_output << " : u64\n";
		break;
	case TypedVarDecl::U128:
		m_output << ": u128 := ";
		visit(_x.expr());
		m_output << " : u128\n";
		break;
	case TypedVarDecl::U256:
		m_output << ": u256 := ";
		visit(_x.expr());
		m_output << " : u256\n";
		break;
	}
	// If we are inside a for-init block, there are two places
	// where the visited vardecl may have been defined:
	// - directly inside the for-init block
	// - inside a block within the for-init block
	// In the latter case, we don't scope extend.
	if (m_inFunctionDef)
	{
		// Variables declared directly in for-init block
		// are tracked separately because their scope
		// extends beyond the block they are defined in
		// to the rest of the for-loop statement.
		if (m_inForInitScope && m_forInitScopeExtEnabled)
		{
			yulAssert(
				!m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(),
				"Proto fuzzer: Invalid operation"
			);
			m_funcForLoopInitVars.back().back().push_back(varName);
		}
		else
		{
			yulAssert(
				!m_funcVars.empty() && !m_funcVars.back().empty(),
				"Proto fuzzer: Invalid operation"
			);
			m_funcVars.back().back().push_back(varName);
		}
	}
	else
	{
		if (m_inForInitScope && m_forInitScopeExtEnabled)
		{
			yulAssert(
				!m_globalForLoopInitVars.empty(),
				"Proto fuzzer: Invalid operation"
			);
			m_globalForLoopInitVars.back().push_back(varName);
		}
		else
		{
			yulAssert(
				!m_globalVars.empty(),
				"Proto fuzzer: Invalid operation"
			);
			m_globalVars.back().push_back(varName);
		}
	}
}

void ProtoConverter::visit(UnaryOp const& _x)
{
	UnaryOp_UOp op = _x.op();

	// Replace calls to extcodehash on unsupported EVMs with a dictionary
	// token.
	if (op == UnaryOp::EXTCODEHASH && !m_evmVersion.hasExtCodeHash())
	{
		m_output << dictionaryToken();
		return;
	}

	switch (op)
	{
	case UnaryOp::NOT:
		m_output << "not";
		break;
	case UnaryOp::MLOAD:
		m_output << "mload";
		break;
	case UnaryOp::SLOAD:
		m_output << "sload";
		break;
	case UnaryOp::ISZERO:
		m_output << "iszero";
		break;
	case UnaryOp::CALLDATALOAD:
		m_output << "calldataload";
		break;
	case UnaryOp::EXTCODESIZE:
		m_output << "extcodesize";
		break;
	case UnaryOp::EXTCODEHASH:
		m_output << "extcodehash";
		break;
	case UnaryOp::BALANCE:
		m_output << "balance";
		break;
	case UnaryOp::BLOCKHASH:
		m_output << "blockhash";
		break;
	}
	m_output << "(";
	visit(_x.operand());
	m_output << ")";
}

void ProtoConverter::visit(TernaryOp const& _x)
{
	switch (_x.op())
	{
	case TernaryOp::ADDM:
		m_output << "addmod";
		break;
	case TernaryOp::MULM:
		m_output << "mulmod";
		break;
	}
	m_output << "(";
	visit(_x.arg1());
	m_output << ", ";
	visit(_x.arg2());
	m_output << ", ";
	visit(_x.arg3());
	m_output << ")";
}

void ProtoConverter::visit(NullaryOp const& _x)
{
	switch (_x.op())
	{
	case NullaryOp::MSIZE:
		m_output << "msize()";
		break;
	case NullaryOp::GAS:
		m_output << "gas()";
		break;
	case NullaryOp::CALLDATASIZE:
		m_output << "calldatasize()";
		break;
	case NullaryOp::CODESIZE:
		m_output << "codesize()";
		break;
	case NullaryOp::RETURNDATASIZE:
		// If evm supports returndatasize, we generate it. Otherwise,
		// we output a dictionary token.
		if (m_evmVersion.supportsReturndata())
			m_output << "returndatasize()";
		else
			m_output << dictionaryToken();
		break;
	case NullaryOp::ADDRESS:
		m_output << "address()";
		break;
	case NullaryOp::ORIGIN:
		m_output << "origin()";
		break;
	case NullaryOp::CALLER:
		m_output << "caller()";
		break;
	case NullaryOp::CALLVALUE:
		m_output << "callvalue()";
		break;
	case NullaryOp::GASPRICE:
		m_output << "gasprice()";
		break;
	case NullaryOp::COINBASE:
		m_output << "coinbase()";
		break;
	case NullaryOp::TIMESTAMP:
		m_output << "timestamp()";
		break;
	case NullaryOp::NUMBER:
		m_output << "number()";
		break;
	case NullaryOp::DIFFICULTY:
		m_output << "difficulty()";
		break;
	case NullaryOp::GASLIMIT:
		m_output << "gaslimit()";
		break;
	case NullaryOp::SELFBALANCE:
		// Replace calls to selfbalance() on unsupported EVMs with a dictionary
		// token.
		if (m_evmVersion.hasSelfBalance())
			m_output << "selfbalance()";
		else
			m_output << dictionaryToken();
		break;
	case NullaryOp::CHAINID:
		// Replace calls to chainid() on unsupported EVMs with a dictionary
		// token.
		if (m_evmVersion.hasChainID())
			m_output << "chainid()";
		else
			m_output << dictionaryToken();
		break;
	}
}

void ProtoConverter::visit(CopyFunc const& _x)
{
	CopyFunc_CopyType type = _x.ct();

	// datacopy() is valid only if we are inside
	// a Yul object.
	if (type == CopyFunc::DATA && !m_isObject)
		return;

	// We don't generate code if the copy function is returndatacopy
	// and the underlying evm does not support it.
	if (type == CopyFunc::RETURNDATA && !m_evmVersion.supportsReturndata())
		return;

	switch (type)
	{
	case CopyFunc::CALLDATA:
		m_output << "calldatacopy";
		break;
	case CopyFunc::CODE:
		m_output << "codecopy";
		break;
	case CopyFunc::RETURNDATA:
		yulAssert(m_evmVersion.supportsReturndata(), "Proto fuzzer: Invalid evm version");
		m_output << "returndatacopy";
		break;
	case CopyFunc::DATA:
		m_output << "datacopy";
		break;
	}
	m_output << "(";
	visit(_x.target());
	m_output << ", ";
	visit(_x.source());
	m_output << ", ";
	visit(_x.size());
	m_output << ")\n";
}

void ProtoConverter::visit(ExtCodeCopy const& _x)
{
	m_output << "extcodecopy";
	m_output << "(";
	visit(_x.addr());
	m_output << ", ";
	visit(_x.target());
	m_output << ", ";
	visit(_x.source());
	m_output << ", ";
	visit(_x.size());
	m_output << ")\n";
}

void ProtoConverter::visit(LogFunc const& _x)
{
	switch (_x.num_topics())
	{
	case LogFunc::ZERO:
		m_output << "log0";
		m_output << "(";
		visit(_x.pos());
		m_output << ", ";
		visit(_x.size());
		m_output << ")\n";
		break;
	case LogFunc::ONE:
		m_output << "log1";
		m_output << "(";
		visit(_x.pos());
		m_output << ", ";
		visit(_x.size());
		m_output << ", ";
		visit(_x.t1());
		m_output << ")\n";
		break;
	case LogFunc::TWO:
		m_output << "log2";
		m_output << "(";
		visit(_x.pos());
		m_output << ", ";
		visit(_x.size());
		m_output << ", ";
		visit(_x.t1());
		m_output << ", ";
		visit(_x.t2());
		m_output << ")\n";
		break;
	case LogFunc::THREE:
		m_output << "log3";
		m_output << "(";
		visit(_x.pos());
		m_output << ", ";
		visit(_x.size());
		m_output << ", ";
		visit(_x.t1());
		m_output << ", ";
		visit(_x.t2());
		m_output << ", ";
		visit(_x.t3());
		m_output << ")\n";
		break;
	case LogFunc::FOUR:
		m_output << "log4";
		m_output << "(";
		visit(_x.pos());
		m_output << ", ";
		visit(_x.size());
		m_output << ", ";
		visit(_x.t1());
		m_output << ", ";
		visit(_x.t2());
		m_output << ", ";
		visit(_x.t3());
		m_output << ", ";
		visit(_x.t4());
		m_output << ")\n";
		break;
	}
}

void ProtoConverter::visit(AssignmentStatement const& _x)
{
	visit(_x.ref_id());
	m_output << " := ";
	visit(_x.expr());
	m_output << "\n";
}

void ProtoConverter::visitFunctionInputParams(FunctionCall const& _x, unsigned _numInputParams)
{
	// We reverse the order of function input visits since it helps keep this switch case concise.
	switch (_numInputParams)
	{
	case 4:
		visit(_x.in_param4());
		m_output << ", ";
		[[fallthrough]];
	case 3:
		visit(_x.in_param3());
		m_output << ", ";
		[[fallthrough]];
	case 2:
		visit(_x.in_param2());
		m_output << ", ";
		[[fallthrough]];
	case 1:
		visit(_x.in_param1());
		[[fallthrough]];
	case 0:
		break;
	default:
		yulAssert(false, "Proto fuzzer: Function call with too many input parameters.");
		break;
	}
}

bool ProtoConverter::functionValid(FunctionCall_Returns _type, unsigned _numOutParams)
{
	switch (_type)
	{
	case FunctionCall::ZERO:
		return _numOutParams == 0;
	case FunctionCall::SINGLE:
		return _numOutParams == 1;
	case FunctionCall::MULTIDECL:
	case FunctionCall::MULTIASSIGN:
		return _numOutParams > 1;
	}
}

void ProtoConverter::convertFunctionCall(
	FunctionCall const& _x,
	std::string _name,
	unsigned _numInParams,
	bool _newLine
)
{
	m_output << _name << "(";
	visitFunctionInputParams(_x, _numInParams);
	m_output << ")";
	if (_newLine)
		m_output << "\n";
}

vector<string> ProtoConverter::createVarDecls(unsigned _start, unsigned _end, bool _isAssignment)
{
	m_output << "let ";
	vector<string> varsVec = createVars(_start, _end);
	if (_isAssignment)
		m_output << " := ";
	else
		m_output << "\n";
	return varsVec;
}

void ProtoConverter::visit(FunctionCall const& _x)
{
	bool functionAvailable = m_functionSigMap.size() > 0;
	unsigned numInParams, numOutParams;
	string funcName;
	FunctionCall_Returns funcType = _x.ret();
	if (functionAvailable)
	{
		yulAssert(m_functions.size() > 0, "Proto fuzzer: No function in scope");
		funcName = m_functions[_x.func_index() % m_functions.size()];
		auto ret = m_functionSigMap.at(funcName);
		numInParams = ret.first;
		numOutParams = ret.second;
	}
	else
	{
		// If there are no functions available, calls to functions that
		// return a single value may be replaced by a dictionary token.
		if (funcType == FunctionCall::SINGLE)
			m_output << dictionaryToken();
		return;
	}

	// If function selected for function call does not meet interface
	// requirements (num output values) for the function type
	// specified, then we return early unless it is a function call
	// that returns a single value (which may be replaced by a
	// dictionary token.
	if (!functionValid(funcType, numOutParams))
	{
		if (funcType == FunctionCall::SINGLE)
			m_output << dictionaryToken();
		return;
	}

	// If we are here, it means that we have at least one valid
	// function for making the function call
	switch (funcType)
	{
	case FunctionCall::ZERO:
		convertFunctionCall(_x, funcName, numInParams);
		break;
	case FunctionCall::SINGLE:
		// Since functions that return a single value are used as expressions
		// we do not print a newline because it is done by the expression
		// visitor.
		convertFunctionCall(_x, funcName, numInParams, /*newLine=*/false);
		break;
	case FunctionCall::MULTIDECL:
	{
		// Ensure that the chosen function returns at most 4 values
		yulAssert(
			numOutParams <= 4,
			"Proto fuzzer: Function call with too many output params encountered."
		);

		// Obtain variable name suffix
		unsigned startIdx = counter();
		vector<string> varsVec = createVarDecls(
			startIdx,
			startIdx + numOutParams,
			/*isAssignment=*/true
		);

		// Create RHS of multi var decl
		convertFunctionCall(_x, funcName, numInParams);
		// Add newly minted vars in the multidecl statement to current scope
		addVarsToScope(varsVec);
		break;
	}
	case FunctionCall::MULTIASSIGN:
		// Ensure that the chosen function returns at most 4 values
		yulAssert(
			numOutParams <= 4,
			"Proto fuzzer: Function call with too many output params encountered."
		);

		// Return early if numOutParams > number of available variables
		if (numOutParams > numVarsInScope())
			return;

		// Copy variables in scope in order to prevent repeated references
		vector<string> variables;
		if (m_inFunctionDef)
			for (auto var: m_currentFuncVars)
				variables.push_back(*var);
		else
			for (auto var: m_currentGlobalVars)
				variables.push_back(*var);

		auto refVar = [](vector<string>& _var, unsigned _rand, bool _comma = true) -> string
			{
				auto index = _rand % _var.size();
				string ref = _var[index];
				_var.erase(_var.begin() + index);
				if (_comma)
					ref += ", ";
				return ref;
			};

		// Convert LHS of multi assignment
		// We reverse the order of out param visits since the order does not matter.
		// This helps reduce the size of this switch statement.
		switch (numOutParams)
		{
		case 4:
			m_output << refVar(variables, _x.out_param4().varnum());
			[[fallthrough]];
		case 3:
			m_output << refVar(variables, _x.out_param3().varnum());
			[[fallthrough]];
		case 2:
			m_output << refVar(variables, _x.out_param2().varnum());
			m_output << refVar(variables, _x.out_param1().varnum(), false);
			break;
		default:
			yulAssert(false, "Proto fuzzer: Function call with too many or too few input parameters.");
			break;
		}
		m_output << " := ";

		// Convert RHS of multi assignment
		convertFunctionCall(_x, funcName, numInParams);
		break;
	}
}

void ProtoConverter::visit(LowLevelCall const& _x)
{
	LowLevelCall_Type type = _x.callty();

	// Generate staticcall if it is supported by the underlying evm
	if (type == LowLevelCall::STATICCALL && !m_evmVersion.hasStaticCall())
	{
		// Since staticcall is supposed to return 0 on success and 1 on
		// failure, we can use counter value to emulate it
		m_output << ((counter() % 2) ? "0" : "1");
		return;
	}

	switch (type)
	{
	case LowLevelCall::CALL:
		m_output << "call(";
		break;
	case LowLevelCall::CALLCODE:
		m_output << "callcode(";
		break;
	case LowLevelCall::DELEGATECALL:
		m_output << "delegatecall(";
		break;
	case LowLevelCall::STATICCALL:
		yulAssert(m_evmVersion.hasStaticCall(), "Proto fuzzer: Invalid evm version");
		m_output << "staticcall(";
		break;
	}
	visit(_x.gas());
	m_output << ", ";
	visit(_x.addr());
	m_output << ", ";
	if (type == LowLevelCall::CALL || type == LowLevelCall::CALLCODE)
	{
		visit(_x.wei());
		m_output << ", ";
	}
	visit(_x.in());
	m_output << ", ";
	visit(_x.insize());
	m_output << ", ";
	visit(_x.out());
	m_output << ", ";
	visit(_x.outsize());
	m_output << ")";
}

void ProtoConverter::visit(Create const& _x)
{
	Create_Type type = _x.createty();

	// Replace a call to create2 on unsupported EVMs with a dictionary
	// token.
	if (type == Create::CREATE2 && !m_evmVersion.hasCreate2())
	{
		m_output << dictionaryToken();
		return;
	}

	switch (type)
	{
	case Create::CREATE:
		m_output << "create(";
		break;
	case Create::CREATE2:
		m_output << "create2(";
		break;
	}
	visit(_x.wei());
	m_output << ", ";
	visit(_x.position());
	m_output << ", ";
	visit(_x.size());
	if (type == Create::CREATE2)
	{
		m_output << ", ";
		visit(_x.value());
	}
	m_output << ")";
}

void ProtoConverter::visit(IfStmt const& _x)
{
	m_output << "if ";
	visit(_x.cond());
	m_output << " ";
	visit(_x.if_body());
}

void ProtoConverter::visit(StoreFunc const& _x)
{
	switch (_x.st())
	{
	case StoreFunc::MSTORE:
		m_output << "mstore(";
		break;
	case StoreFunc::SSTORE:
		m_output << "sstore(";
		break;
	case StoreFunc::MSTORE8:
		m_output << "mstore8(";
		break;
	}
	visit(_x.loc());
	m_output << ", ";
	visit(_x.val());
	m_output << ")\n";
}

void ProtoConverter::visit(ForStmt const& _x)
{
	if (++m_numForLoops > s_maxForLoops)
		return;
	bool wasInForBody = m_inForBodyScope;
	bool wasInForInit = m_inForInitScope;
	bool wasForInitScopeExtEnabled = m_forInitScopeExtEnabled;
	m_inForBodyScope = false;
	m_inForInitScope = true;
	m_forInitScopeExtEnabled = true;
	m_inForCond = false;
	m_output << "for ";
	visit(_x.for_init());
	m_inForInitScope = false;
	m_forInitScopeExtEnabled = wasForInitScopeExtEnabled;
	m_inForCond = true;
	visit(_x.for_cond());
	m_inForCond = false;
	visit(_x.for_post());
	m_inForBodyScope = true;
	visit(_x.for_body());
	m_inForBodyScope = wasInForBody;
	m_inForInitScope = wasInForInit;
	if (m_inFunctionDef)
	{
		yulAssert(
			!m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(),
			"Proto fuzzer: Invalid data structure"
		);
		// Remove variables in for-init
		m_funcForLoopInitVars.back().pop_back();
	}
	else
	{
		yulAssert(!m_globalForLoopInitVars.empty(), "Proto fuzzer: Invalid data structure");
		m_globalForLoopInitVars.pop_back();
	}
}

void ProtoConverter::visit(BoundedForStmt const& _x)
{
	if (++m_numForLoops > s_maxForLoops)
		return;

	// Boilerplate for loop that limits the number of iterations to a maximum of 4.
	std::string loopVarName("i_" + std::to_string(m_numNestedForLoops++));
	m_output << "for { let " << loopVarName << " := 0 } "
	       << "lt(" << loopVarName << ", 0x60) "
	       << "{ " << loopVarName << " := add(" << loopVarName << ", 0x20) } ";
	// Store previous for body scope
	bool wasInForBody = m_inForBodyScope;
	bool wasInForInit = m_inForInitScope;
	m_inForBodyScope = true;
	m_inForInitScope = false;
	visit(_x.for_body());
	// Restore previous for body scope and init
	m_inForBodyScope = wasInForBody;
	m_inForInitScope = wasInForInit;
}

void ProtoConverter::visit(CaseStmt const& _x)
{
	string literal = visit(_x.case_lit());
	// u256 value of literal
	u256 literalVal;

	// Convert string to u256 before looking for duplicate case literals
	if (_x.case_lit().has_strval())
	{
		// Since string literals returned by the Literal visitor are enclosed within
		// double quotes (like this "\"<string>\""), their size is at least two in the worst case
		// that <string> is empty. Here we assert this invariant.
		yulAssert(literal.size() >= 2, "Proto fuzzer: String literal too short");
		// This variable stores the <string> part i.e., literal minus the first and last
		// double quote characters. This is used to compute the keccak256 hash of the
		// string literal. The hashing is done to check whether we are about to create
		// a case statement containing a case literal that has already been used in a
		// previous case statement. If the hash (u256 value) matches a previous hash,
		// then we simply don't create a new case statement.
		string noDoubleQuoteStr{""};
		if (literal.size() > 2)
		{
			// Ensure that all characters in the string literal except the first
			// and the last (double quote characters) are alphanumeric.
			yulAssert(
				boost::algorithm::all_of(literal.begin() + 1, literal.end() - 2, [=](char c) -> bool {
					return std::isalpha(c) || std::isdigit(c);
				}),
				"Proto fuzzer: Invalid string literal encountered"
			);

			// Make a copy because literal will need to be used later
			noDoubleQuoteStr = literal.substr(1, literal.size() - 2);
		}
		// Hash the result to check for duplicate case literal strings
		literalVal = u256(h256(noDoubleQuoteStr, h256::FromBinary, h256::AlignLeft));

		// Make sure that an empty string literal evaluates to zero. This is to detect creation of
		// duplicate case literals like so
		// switch (x)
		// {
		//    case "": { x := 0 }
		//    case 0: { x:= 1 } // Case statement with duplicate literal is invalid
		// } // This snippet will not be parsed successfully.
		if (noDoubleQuoteStr.empty())
			yulAssert(literalVal == 0, "Proto fuzzer: Empty string does not evaluate to zero");
	}
	else if (_x.case_lit().has_boolval())
		literalVal = _x.case_lit().boolval() ? u256(1) : u256(0);
	else
		literalVal = u256(literal);

	// Check if set insertion fails (case literal present) or succeeds (case literal
	// absent).
	bool isUnique = m_switchLiteralSetPerScope.top().insert(literalVal).second;

	// It is fine to bail out if we encounter a duplicate case literal because
	// we can be assured that the switch statement is well-formed i.e., contains
	// at least one case statement or a default block.
	if (isUnique)
	{
		m_output << "case " << literal << " ";
		visit(_x.case_block());
	}
}

void ProtoConverter::visit(SwitchStmt const& _x)
{
	if (_x.case_stmt_size() > 0 || _x.has_default_block())
	{
		std::set<u256> s;
		m_switchLiteralSetPerScope.push(s);
		m_output << "switch ";
		visit(_x.switch_expr());
		m_output << "\n";

		for (auto const& caseStmt: _x.case_stmt())
			visit(caseStmt);

		m_switchLiteralSetPerScope.pop();

		if (_x.has_default_block())
		{
			m_output << "default ";
			visit(_x.default_block());
		}
	}
}

void ProtoConverter::visit(StopInvalidStmt const& _x)
{
	switch (_x.stmt())
	{
	case StopInvalidStmt::STOP:
		m_output << "stop()\n";
		break;
	case StopInvalidStmt::INVALID:
		m_output << "invalid()\n";
		break;
	}
}

void ProtoConverter::visit(RetRevStmt const& _x)
{
	switch (_x.stmt())
	{
	case RetRevStmt::RETURN:
		m_output << "return";
		break;
	case RetRevStmt::REVERT:
		m_output << "revert";
		break;
	}
	m_output << "(";
	visit(_x.pos());
	m_output << ", ";
	visit(_x.size());
	m_output << ")\n";
}

void ProtoConverter::visit(SelfDestructStmt const& _x)
{
	m_output << "selfdestruct";
	m_output << "(";
	visit(_x.addr());
	m_output << ")\n";
}

void ProtoConverter::visit(TerminatingStmt const& _x)
{
	switch (_x.term_oneof_case())
	{
	case TerminatingStmt::kStopInvalid:
		visit(_x.stop_invalid());
		break;
	case TerminatingStmt::kRetRev:
		visit(_x.ret_rev());
		break;
	case TerminatingStmt::kSelfDes:
		visit(_x.self_des());
		break;
	case TerminatingStmt::TERM_ONEOF_NOT_SET:
		break;
	}
}

void ProtoConverter::visit(UnaryOpData const& _x)
{
	switch (_x.op())
	{
	case UnaryOpData::SIZE:
		m_output << Whiskers(R"(datasize("<id>"))")
			("id", getObjectIdentifier(_x.identifier()))
			.render();
		break;
	case UnaryOpData::OFFSET:
		m_output << Whiskers(R"(dataoffset("<id>"))")
			("id", getObjectIdentifier(_x.identifier()))
			.render();
		break;
	}
}

void ProtoConverter::visit(Statement const& _x)
{
	switch (_x.stmt_oneof_case())
	{
	case Statement::kDecl:
		visit(_x.decl());
		break;
	case Statement::kAssignment:
		// Create an assignment statement only if there is at least one variable
		// declaration that is in scope.
		if (varDeclAvailable())
			visit(_x.assignment());
		break;
	case Statement::kIfstmt:
		if (_x.ifstmt().if_body().statements_size() > 0)
			visit(_x.ifstmt());
		break;
	case Statement::kStorageFunc:
		visit(_x.storage_func());
		break;
	case Statement::kBlockstmt:
		if (_x.blockstmt().statements_size() > 0)
			visit(_x.blockstmt());
		break;
	case Statement::kForstmt:
		if (_x.forstmt().for_body().statements_size() > 0)
			visit(_x.forstmt());
		break;
	case Statement::kBoundedforstmt:
		if (_x.boundedforstmt().for_body().statements_size() > 0)
			visit(_x.boundedforstmt());
		break;
	case Statement::kSwitchstmt:
		visit(_x.switchstmt());
		break;
	case Statement::kBreakstmt:
		if (m_inForBodyScope)
			m_output << "break\n";
		break;
	case Statement::kContstmt:
		if (m_inForBodyScope)
			m_output << "continue\n";
		break;
	case Statement::kLogFunc:
		visit(_x.log_func());
		break;
	case Statement::kCopyFunc:
		visit(_x.copy_func());
		break;
	case Statement::kExtcodeCopy:
		visit(_x.extcode_copy());
		break;
	case Statement::kTerminatestmt:
		visit(_x.terminatestmt());
		break;
	case Statement::kFunctioncall:
		// Return early if a function call cannot be created
		if (functionCallNotPossible(_x.functioncall().ret()))
			return;
		visit(_x.functioncall());
		break;
	case Statement::kFuncdef:
		if (_x.funcdef().block().statements_size() > 0)
			if (!m_inForInitScope)
				visit(_x.funcdef());
		break;
	case Statement::kPop:
		visit(_x.pop());
		break;
	case Statement::kLeave:
		if (m_inFunctionDef)
			visit(_x.leave());
		break;
	case Statement::kMultidecl:
		visit(_x.multidecl());
		break;
	case Statement::STMT_ONEOF_NOT_SET:
		break;
	}
}

void ProtoConverter::openBlockScope()
{
	m_scopeFuncs.push_back({});

	// Create new block scope inside current function scope
	if (m_inFunctionDef)
	{
		yulAssert(
			!m_funcVars.empty(),
			"Proto fuzzer: Invalid data structure"
		);
		m_funcVars.back().push_back(vector<string>{});
		if (m_inForInitScope && m_forInitScopeExtEnabled)
		{
			yulAssert(
				!m_funcForLoopInitVars.empty(),
				"Proto fuzzer: Invalid data structure"
			);
			m_funcForLoopInitVars.back().push_back(vector<string>{});
		}
	}
	else
	{
		m_globalVars.push_back({});
		if (m_inForInitScope && m_forInitScopeExtEnabled)
			m_globalForLoopInitVars.push_back(vector<string>{});
	}
}

void ProtoConverter::openFunctionScope(vector<string> const& _funcParams)
{
	m_funcVars.push_back(vector<vector<string>>({_funcParams}));
	m_funcForLoopInitVars.push_back(vector<vector<string>>({}));
}

void ProtoConverter::updateFunctionMaps(string const& _var)
{
	unsigned erased = m_functionSigMap.erase(_var);

	for (auto const& i: m_functionDefMap)
		if (i.second == _var)
		{
			erased += m_functionDefMap.erase(i.first);
			break;
		}

	yulAssert(erased == 2, "Proto fuzzer: Function maps not updated");
}

void ProtoConverter::closeBlockScope()
{
	// Remove functions declared in the block that is going
	// out of scope from the global function map.
	for (auto const& f: m_scopeFuncs.back())
	{
		unsigned numFuncsRemoved = m_functions.size();
		m_functions.erase(remove(m_functions.begin(), m_functions.end(), f), m_functions.end());
		numFuncsRemoved -= m_functions.size();
		yulAssert(
			numFuncsRemoved == 1,
			"Proto fuzzer: Nothing or too much went out of scope"
		);
		updateFunctionMaps(f);
	}
	// Pop back the vector of scoped functions.
	if (!m_scopeFuncs.empty())
		m_scopeFuncs.pop_back();

	// If block belongs to function body, then remove
	// local variables in function body that are going out of scope.
	if (m_inFunctionDef)
	{
		yulAssert(!m_funcVars.empty(), "Proto fuzzer: Invalid data structure");
		if (!m_funcVars.back().empty())
			m_funcVars.back().pop_back();
	}
	// Remove variables declared in vanilla block from current
	// global scope.
	else
	{
		yulAssert(!m_globalVars.empty(), "Proto fuzzer: Invalid data structure");
		m_globalVars.pop_back();
	}
}

void ProtoConverter::closeFunctionScope()
{
	yulAssert(!m_funcVars.empty(), "Proto fuzzer: Invalid data structure");
	m_funcVars.pop_back();
	yulAssert(!m_funcForLoopInitVars.empty(), "Proto fuzzer: Invalid data structure");
	m_funcForLoopInitVars.pop_back();
}

void ProtoConverter::addVarsToScope(vector<string> const& _vars)
{
	// If we are in function definition, add the new vars to current function scope
	if (m_inFunctionDef)
	{
		// If we are directly in for-init block, add the newly created vars to the
		// stack of for-init variables.
		if (m_inForInitScope && m_forInitScopeExtEnabled)
		{
			yulAssert(
				!m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(),
				"Proto fuzzer: Invalid data structure"
			);
			m_funcForLoopInitVars.back().back().insert(
				m_funcForLoopInitVars.back().back().end(),
				_vars.begin(),
				_vars.end()
			);
		}
		else
		{
			yulAssert(
				!m_funcVars.empty() && !m_funcVars.back().empty(),
				"Proto fuzzer: Invalid data structure"
			);
			m_funcVars.back().back().insert(
				m_funcVars.back().back().end(),
				_vars.begin(),
				_vars.end()
			);
		}
	}
	// If we are in a vanilla block, add the new vars to current global scope
	else
	{
		if (m_inForInitScope && m_forInitScopeExtEnabled)
		{
			yulAssert(
				!m_globalForLoopInitVars.empty(),
				"Proto fuzzer: Invalid data structure"
			);
			m_globalForLoopInitVars.back().insert(
				m_globalForLoopInitVars.back().end(),
				_vars.begin(),
				_vars.end()
			);
		}
		else
		{
			yulAssert(
				!m_globalVars.empty(),
				"Proto fuzzer: Invalid data structure"
			);
			m_globalVars.back().insert(
				m_globalVars.back().end(),
				_vars.begin(),
				_vars.end()
			);
		}
	}
}

void ProtoConverter::visit(Block const& _x)
{
	openBlockScope();

	// Register function declarations in this scope unless this
	// scope belongs to for-init (in which function declarations
	// are forbidden).
	for (auto const& statement: _x.statements())
		if (statement.has_funcdef() && statement.funcdef().block().statements_size() > 0 && !m_inForInitScope)
			registerFunction(&statement.funcdef());

	if (_x.statements_size() > 0)
	{
		m_output << "{\n";
		bool wasForInitScopeExtEnabled = m_forInitScopeExtEnabled;
		for (auto const& st: _x.statements())
		{
			// If statement is block or introduces one and we are in for-init block
			// then temporarily disable scope extension if it is not already disabled.
			if (
				(st.has_blockstmt() || st.has_switchstmt() || st.has_ifstmt()) &&
				m_inForInitScope &&
				m_forInitScopeExtEnabled
			)
				m_forInitScopeExtEnabled = false;
			visit(st);
			m_forInitScopeExtEnabled = wasForInitScopeExtEnabled;
		}
		m_output << "}\n";
	}
	else
		m_output << "{}\n";
	closeBlockScope();
}

vector<string> ProtoConverter::createVars(unsigned _startIdx, unsigned _endIdx)
{
	yulAssert(_endIdx > _startIdx, "Proto fuzzer: Variable indices not in range");
	string varsStr = suffixedVariableNameList("x_", _startIdx, _endIdx);
	m_output << varsStr;
	vector<string> varsVec;
	boost::split(
		varsVec,
		varsStr,
		boost::algorithm::is_any_of(", "),
		boost::algorithm::token_compress_on
	);

	yulAssert(
		varsVec.size() == (_endIdx - _startIdx),
		"Proto fuzzer: Variable count mismatch during function definition"
	);
	m_counter += varsVec.size();
	return varsVec;
}

void ProtoConverter::registerFunction(FunctionDef const* _x)
{
	unsigned numInParams = _x->num_input_params() % s_modInputParams;
	unsigned numOutParams = _x->num_output_params() % s_modOutputParams;
	NumFunctionReturns numReturns;
	if (numOutParams == 0)
		numReturns = NumFunctionReturns::None;
	else if (numOutParams == 1)
		numReturns = NumFunctionReturns::Single;
	else
		numReturns = NumFunctionReturns::Multiple;

	// Generate function name
	string funcName = functionName(numReturns);

	// Register function
	auto ret = m_functionSigMap.emplace(make_pair(funcName, make_pair(numInParams, numOutParams)));
	yulAssert(ret.second, "Proto fuzzer: Function already exists.");
	m_functions.push_back(funcName);
	m_scopeFuncs.back().push_back(funcName);
	m_functionDefMap.emplace(make_pair(_x, funcName));
}

void ProtoConverter::fillFunctionCallInput(unsigned _numInParams)
{
	for (unsigned i = 0; i < _numInParams; i++)
	{
		// Throw a 4-sided dice to choose whether to populate function input
		// argument from a pseudo-randomly chosen slot in one of the following
		// locations: calldata, memory, storage, or Yul optimizer dictionary.
		unsigned diceValue = counter() % 4;
		// Pseudo-randomly choose one of the first ten 32-byte
		// aligned slots.
		string slot = to_string((counter() % 10) * 32);
		switch (diceValue)
		{
		case 0:
			m_output << "calldataload(" << slot << ")";
			break;
		case 1:
			m_output << "mload(" << slot << ")";
			break;
		case 2:
			m_output << "sload(" << slot << ")";
			break;
		case 3:
			// Call to dictionaryToken() automatically picks a token
			// at a pseudo-random location.
			m_output << dictionaryToken();
			break;
		}
		if (i < _numInParams - 1)
			m_output << ",";
	}
}

void ProtoConverter::saveFunctionCallOutput(vector<string> const& _varsVec)
{
	for (auto const& var: _varsVec)
	{
		// Flip a dice to choose whether to save output values
		// in storage or memory.
		bool coinFlip = counter() % 2 == 0;
		// Pseudo-randomly choose one of the first ten 32-byte
		// aligned slots.
		string slot = to_string((counter() % 10) * 32);
		if (coinFlip)
			m_output << "sstore(" << slot << ", " << var << ")\n";
		else
			m_output << "mstore(" << slot << ", " << var << ")\n";
	}
}

void ProtoConverter::createFunctionCall(
	string _funcName,
	unsigned _numInParams,
	unsigned _numOutParams
)
{
	vector<string> varsVec{};
	if (_numOutParams > 0)
	{
		unsigned startIdx = counter();
		// Prints the following to output stream "let x_i,...,x_n := "
		varsVec = createVarDecls(
			startIdx,
			startIdx + _numOutParams,
			/*isAssignment=*/true
		);
	}

	// Call the function with the correct number of input parameters
	m_output << _funcName << "(";
	if (_numInParams > 0)
		fillFunctionCallInput(_numInParams);
	m_output << ")\n";

	if (!varsVec.empty())
	{
		// Save values returned by function so that they are reflected
		// in the interpreter trace.
		saveFunctionCallOutput(varsVec);
		// Add newly minted vars to current scope
		addVarsToScope(varsVec);
	}
	else
		yulAssert(_numOutParams == 0, "Proto fuzzer: Function return value not saved");
}

void ProtoConverter::createFunctionDefAndCall(
	FunctionDef const& _x,
	unsigned _numInParams,
	unsigned _numOutParams
)
{
	yulAssert(
		((_numInParams <= s_modInputParams - 1) && (_numOutParams <= s_modOutputParams - 1)),
		"Proto fuzzer: Too many function I/O parameters requested."
	);

	// Obtain function name
	yulAssert(m_functionDefMap.count(&_x), "Proto fuzzer: Unregistered function");
	string funcName = m_functionDefMap.at(&_x);

	vector<string> varsVec = {};
	m_output << "function " << funcName << "(";
	unsigned startIdx = counter();
	if (_numInParams > 0)
		varsVec = createVars(startIdx, startIdx + _numInParams);
	m_output << ")";

	vector<string> outVarsVec = {};
	// This creates -> x_n+1,...,x_r
	if (_numOutParams > 0)
	{
		m_output << " -> ";
		if (varsVec.empty())
		{
			yulAssert(_numInParams == 0, "Proto fuzzer: Input parameters not processed correctly");
			varsVec = createVars(startIdx, startIdx + _numOutParams);
		}
		else
		{
			outVarsVec = createVars(startIdx + _numInParams, startIdx + _numInParams + _numOutParams);
			varsVec.insert(varsVec.end(), outVarsVec.begin(), outVarsVec.end());
		}
	}
	yulAssert(varsVec.size() == _numInParams + _numOutParams, "Proto fuzzer: Function parameters not processed correctly");

	m_output << "\n";

	// If function definition is in for-loop body, update
	bool wasInForBody = m_inForBodyScope;
	m_inForBodyScope = false;

	bool wasInFunctionDef = m_inFunctionDef;
	m_inFunctionDef = true;

	// Create new function scope and add function input and return
	// parameters to it.
	openFunctionScope(varsVec);
	// Visit function body
	visit(_x.block());
	closeFunctionScope();

	m_inForBodyScope = wasInForBody;
	m_inFunctionDef = wasInFunctionDef;

	yulAssert(
		!m_inForInitScope,
		"Proto fuzzer: Trying to create function call inside a for-init block"
	);
	if (_x.force_call())
		createFunctionCall(funcName, _numInParams, _numOutParams);
}

void ProtoConverter::visit(FunctionDef const& _x)
{
	unsigned numInParams = _x.num_input_params() % s_modInputParams;
	unsigned numOutParams = _x.num_output_params() % s_modOutputParams;
	createFunctionDefAndCall(_x, numInParams, numOutParams);
}

void ProtoConverter::visit(PopStmt const& _x)
{
	m_output << "pop(";
	visit(_x.expr());
	m_output << ")\n";
}

void ProtoConverter::visit(LeaveStmt const&)
{
	m_output << "leave\n";
}

string ProtoConverter::getObjectIdentifier(unsigned _x)
{
	unsigned currentId = currentObjectId();
	string currentObjName = "object" + to_string(currentId);
	yulAssert(
		m_objectScope.count(currentObjName) && m_objectScope.at(currentObjName).size() > 0,
		"Yul proto fuzzer: Error referencing object"
	);
	vector<string> objectIdsInScope = m_objectScope.at(currentObjName);
	return objectIdsInScope[_x % objectIdsInScope.size()];
}

void ProtoConverter::visit(Code const& _x)
{
	m_output << "code {\n";
	visit(_x.block());
	m_output << "}\n";
}

void ProtoConverter::visit(Data const& _x)
{
	// TODO: Generate random data block identifier
	m_output << "data \"" << s_dataIdentifier << "\" hex\"" << createHex(_x.hex()) << "\"\n";
}

void ProtoConverter::visit(Object const& _x)
{
	// object "object<n>" {
	// ...
	// }
	m_output << "object " << newObjectId() << " {\n";
	visit(_x.code());
	if (_x.has_data())
		visit(_x.data());
	for (auto const& subObj: _x.sub_obj())
		visit(subObj);
	m_output << "}\n";
}

void ProtoConverter::buildObjectScopeTree(Object const& _x)
{
	// Identifies object being visited
	string objectName = newObjectId(false);
	vector<string> node{objectName};
	if (_x.has_data())
		node.push_back(s_dataIdentifier);
	for (auto const& subObj: _x.sub_obj())
	{
		// Identifies sub object whose numeric suffix is
		// m_objectId
		unsigned subObjectId = m_objectId;
		string subObjectName = "object" + to_string(subObjectId);
		node.push_back(subObjectName);
		buildObjectScopeTree(subObj);
		// Add sub-object to object's ancestors
		yulAssert(m_objectScope.count(subObjectName), "Yul proto fuzzer: Invalid object hierarchy");
		for (string const& item: m_objectScope.at(subObjectName))
			if (item != subObjectName)
				node.push_back(subObjectName + "." + item);
	}
	m_objectScope.emplace(objectName, node);
}

void ProtoConverter::visit(Program const& _x)
{
	// Initialize input size
	m_inputSize = _x.ByteSizeLong();

	// Record EVM Version
	m_evmVersion = evmVersionMapping(_x.ver());

	// Program is either a Yul object or a block of
	// statements.
	switch (_x.program_oneof_case())
	{
	case Program::kBlock:
		m_output << "{\n";
		visit(_x.block());
		m_output << "}\n";
		break;
	case Program::kObj:
		m_isObject = true;
		buildObjectScopeTree(_x.obj());
		// Reset object id counter
		m_objectId = 0;
		visit(_x.obj());
		break;
	case Program::PROGRAM_ONEOF_NOT_SET:
		// {} is a trivial Yul program
		m_output << "{}";
		break;
	}
}

string ProtoConverter::programToString(Program const& _input)
{
	visit(_input);
	return m_output.str();
}

std::string ProtoConverter::functionTypeToString(NumFunctionReturns _type)
{
	switch (_type)
	{
	case NumFunctionReturns::None:
		return "n";
	case NumFunctionReturns::Single:
		return "s";
	case NumFunctionReturns::Multiple:
		return "m";
	}
}


--TEST--
Test fscanf() function: usage variations - char formats with float values
--FILE--
<?php

/*
  Prototype: mixed fscanf ( resource $handle, string $format [, mixed &$...] );
  Description: Parses input from a file according to a format
*/

/* Test fscanf() to scan float values using different char format types */

$file_path = dirname(__FILE__);

echo "*** Test fscanf(): different char format types with float values ***\n";

// create a file
$filename = "$file_path/fscanf_variation21.tmp";
$file_handle = fopen($filename, "w");
if($file_handle == false)
  exit("Error:failed to open file $filename");

// array of float type values

$float_values = array (
  -2147483649,
  2147483648,
  -0x80000001, // float value, beyond max negative int
  0x800000001, // float value, beyond max positive int
  020000000001, // float value, beyond max positive int
  -020000000001, // float value, beyond max negative int
  0.0,
  -0.1,
  1.0,
  1e5,
  -1e6,
  1E8,
  -1E9,
  10.0000000000000000005,
  10.5e+5
);

$char_formats = array( "%c",
                       "%hc", "%lc", "%Lc",
		       " %c", "%c ", "% c",
 	 	       "\t%c", "\n%c", "%4c",
 		       "%30c", "%[a-zA-Z@#$&0-9]", "%*c");

$counter = 1;

// writing to the file
foreach($float_values as $value) {
  @fprintf($file_handle, $value);
  @fprintf($file_handle, "\n");
}
// closing the file
fclose($file_handle);

// opening the file for reading
$file_handle = fopen($filename, "r");
if($file_handle == false) {
  exit("Error:failed to open file $filename");
}

$counter = 1;
// reading the values from file using different char formats
foreach($char_formats as $char_format) {
  // rewind the file so that for every foreach iteration the file pointer starts from bof
  rewind($file_handle);
  echo "\n-- iteration $counter --\n";
  while( !feof($file_handle) ) {
    var_dump( fscanf($file_handle,$char_format) );
  }
  $counter++;
}

echo "\n*** Done ***";
?>
--CLEAN--
<?php
$file_path = dirname(__FILE__);
$filename = "$file_path/fscanf_variation21.tmp";
unlink($filename);
?>
--EXPECTF--
*** Test fscanf(): different char format types with float values ***

-- iteration 1 --
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "3"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
bool(false)

-- iteration 2 --
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "3"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
bool(false)

-- iteration 3 --
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "3"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
bool(false)

-- iteration 4 --
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "3"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
bool(false)

-- iteration 5 --
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "3"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
bool(false)

-- iteration 6 --
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "3"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
bool(false)

-- iteration 7 --

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL

Warning: fscanf(): Bad scan conversion character " " in %s on line %d
NULL
bool(false)

-- iteration 8 --
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "3"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
bool(false)

-- iteration 9 --
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "3"
}
array(1) {
  [0]=>
  string(1) "2"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "-"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(1) "1"
}
bool(false)

-- iteration 10 --
array(1) {
  [0]=>
  string(4) "-214"
}
array(1) {
  [0]=>
  string(4) "2147"
}
array(1) {
  [0]=>
  string(4) "-214"
}
array(1) {
  [0]=>
  string(4) "3435"
}
array(1) {
  [0]=>
  string(4) "2147"
}
array(1) {
  [0]=>
  string(4) "-214"
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  string(4) "-0.1"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(4) "1000"
}
array(1) {
  [0]=>
  string(4) "-100"
}
array(1) {
  [0]=>
  string(4) "1000"
}
array(1) {
  [0]=>
  string(4) "-100"
}
array(1) {
  [0]=>
  string(2) "10"
}
array(1) {
  [0]=>
  string(4) "1050"
}
bool(false)

-- iteration 11 --
array(1) {
  [0]=>
  string(11) "-2147483649"
}
array(1) {
  [0]=>
  string(10) "2147483648"
}
array(1) {
  [0]=>
  string(11) "-2147483649"
}
array(1) {
  [0]=>
  string(11) "34359738369"
}
array(1) {
  [0]=>
  string(10) "2147483649"
}
array(1) {
  [0]=>
  string(11) "-2147483649"
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  string(4) "-0.1"
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(6) "100000"
}
array(1) {
  [0]=>
  string(8) "-1000000"
}
array(1) {
  [0]=>
  string(9) "100000000"
}
array(1) {
  [0]=>
  string(11) "-1000000000"
}
array(1) {
  [0]=>
  string(2) "10"
}
array(1) {
  [0]=>
  string(7) "1050000"
}
bool(false)

-- iteration 12 --
array(1) {
  [0]=>
  NULL
}
array(1) {
  [0]=>
  string(10) "2147483648"
}
array(1) {
  [0]=>
  NULL
}
array(1) {
  [0]=>
  string(11) "34359738369"
}
array(1) {
  [0]=>
  string(10) "2147483649"
}
array(1) {
  [0]=>
  NULL
}
array(1) {
  [0]=>
  string(1) "0"
}
array(1) {
  [0]=>
  NULL
}
array(1) {
  [0]=>
  string(1) "1"
}
array(1) {
  [0]=>
  string(6) "100000"
}
array(1) {
  [0]=>
  NULL
}
array(1) {
  [0]=>
  string(9) "100000000"
}
array(1) {
  [0]=>
  NULL
}
array(1) {
  [0]=>
  string(2) "10"
}
array(1) {
  [0]=>
  string(7) "1050000"
}
bool(false)

-- iteration 13 --
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
bool(false)

*** Done ***


## Support for multiple broadcast receivers

If multiple sources need to register a broadcast receiver for the `INSTALL_REFERRER` intent in your app, you will have to implement your own
`BroadcastReceiver`, which calls all the other receivers you want to support. If you have your own broadcast receiver defined in your manifest file (similar to the one stated below):

```xml
<receiver
    android:name="com.your.app.InstallReceiver"
    android:permission="android.permission.INSTALL_PACKAGES"
    android:exported="true" >
    <intent-filter>
        <action android:name="com.android.vending.INSTALL_REFERRER" />
    </intent-filter>
</receiver>
```

please make sure to inform the Adjust broadcast receiver (and any other that might need the information) of your intent content like so:

```java
public class InstallReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // Adjust receiver.
        new AdjustReferrerReceiver().onReceive(context, intent);
        // Google Analytics receiver.
        new CampaignTrackingReceiver().onReceive(context, intent);
        // And any other receiver which needs the intent.
    }
}
```