Subversion Repositories HomeAutomation

Rev

Rev 1060 | Rev 1402 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | SVN | RSS feed

  1. /***************************************************************************
  2.  *   Copyright (C) December 10, 2008 by Mattias Runge                             *
  3.  *   mattias@runge.se                                                      *
  4.  *   virtualmachine.cpp                                            *
  5.  *                                                                         *
  6.  *   This program is free software; you can redistribute it and/or modify  *
  7.  *   it under the terms of the GNU General Public License as published by  *
  8.  *   the Free Software Foundation; either version 2 of the License, or     *
  9.  *   (at your option) any later version.                                   *
  10.  *                                                                         *
  11.  *   This program is distributed in the hope that it will be useful,       *
  12.  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
  13.  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
  14.  *   GNU General Public License for more details.                          *
  15.  *                                                                         *
  16.  *   You should have received a copy of the GNU General Public License     *
  17.  *   along with this program; if not, write to the                         *
  18.  *   Free Software Foundation, Inc.,                                       *
  19.  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
  20.  ***************************************************************************/
  21.  
  22. #include "virtualmachine.h"
  23.  
  24. VirtualMachine* VirtualMachine::myInstance = NULL;
  25.  
  26. VirtualMachine& VirtualMachine::getInstance()
  27. {
  28.     if (myInstance == NULL)
  29.     {
  30.         myInstance = new VirtualMachine();
  31.     }
  32.  
  33.     return *myInstance;
  34. }
  35.  
  36. void VirtualMachine::deleteInstance()
  37. {
  38.     if (myInstance != NULL)
  39.     {
  40.         delete myInstance;
  41.         myInstance = NULL;
  42.     }
  43. }
  44.  
  45. VirtualMachine::VirtualMachine()
  46. {
  47. }
  48.  
  49. VirtualMachine::~VirtualMachine()
  50. {
  51.     stop();
  52.  
  53.     myCommandThread.stop();
  54.  
  55.     ///FIXME: Verify that this works and does not cause segfaults
  56.     for (map<unsigned long int, SocketThread*>::iterator iter = mySocketThreads.begin(); iter != mySocketThreads.end(); iter++)
  57.     {
  58.         iter->second->stop();
  59.         delete iter->second;
  60.         //mySocketThreads.erase(iter);
  61.     }
  62.  
  63.     mySocketThreads.clear();
  64.  
  65.     for (map<int, IntervalThread*>::iterator iter = myIntervalThreads.begin(); iter != myIntervalThreads.end(); iter++)
  66.     {
  67.         iter->second->stop();
  68.         delete iter->second;
  69.         //myIntervalThreads.erase(iter);
  70.     }
  71.  
  72.     myIntervalThreads.clear();
  73. }
  74.  
  75. void VirtualMachine::run()
  76. {
  77.     myGlobal = ObjectTemplate::New();
  78.     myGlobal->Set(String::New("_quit"), FunctionTemplate::New(VirtualMachine::_quit));
  79.     myGlobal->Set(String::New("_print"), FunctionTemplate::New(VirtualMachine::_print));
  80.     myGlobal->Set(String::New("log"), FunctionTemplate::New(VirtualMachine::_log));
  81.     myGlobal->Set(String::New("sendCanMessage"), FunctionTemplate::New(VirtualMachine::_sendCanMessage));
  82.     myGlobal->Set(String::New("sendCanNMTMessage"), FunctionTemplate::New(VirtualMachine::_sendCanNMTMessage));
  83.     myGlobal->Set(String::New("loadScript"), FunctionTemplate::New(VirtualMachine::_loadScript));
  84.     myGlobal->Set(String::New("startIntervalThread"), FunctionTemplate::New(VirtualMachine::_startIntervalThread));
  85.     myGlobal->Set(String::New("stopIntervalThread"), FunctionTemplate::New(VirtualMachine::_stopIntervalThread));
  86.     myGlobal->Set(String::New("setIntervalThreadTimeout"), FunctionTemplate::New(VirtualMachine::_setIntervalThreadTimeout));
  87.     myGlobal->Set(String::New("startSocketThread"), FunctionTemplate::New(VirtualMachine::_startSocketThread));
  88.     myGlobal->Set(String::New("stopSocketThread"), FunctionTemplate::New(VirtualMachine::_stopSocketThread));
  89.     myGlobal->Set(String::New("sendToSocketThread"), FunctionTemplate::New(VirtualMachine::_sendToSocketThread));
  90.     myGlobal->Set(String::New("loadDataStore"), FunctionTemplate::New(VirtualMachine::_loadDataStore));
  91.     myGlobal->Set(String::New("getFileContents"), FunctionTemplate::New(VirtualMachine::_getFileContents));
  92.     myGlobal->Set(String::New("uint2hex"), FunctionTemplate::New(VirtualMachine::_uint2hex));
  93.     myGlobal->Set(String::New("hex2bin"), FunctionTemplate::New(VirtualMachine::_hex2bin));
  94.     myGlobal->Set(String::New("bin2hex"), FunctionTemplate::New(VirtualMachine::_bin2hex));
  95.     myGlobal->Set(String::New("bin2float"), FunctionTemplate::New(VirtualMachine::_bin2float));
  96.     myGlobal->Set(String::New("float2bin"), FunctionTemplate::New(VirtualMachine::_float2bin));
  97.     myGlobal->Set(String::New("bin2uint"), FunctionTemplate::New(VirtualMachine::_bin2uint));
  98.     myGlobal->Set(String::New("uint2bin"), FunctionTemplate::New(VirtualMachine::_uint2bin));
  99.     myGlobal->Set(String::New("hex2uint"), FunctionTemplate::New(VirtualMachine::_hex2uint));
  100.     myGlobal->Set(String::New("sleep"), FunctionTemplate::New(VirtualMachine::_sleep));
  101.     myContext = Context::New(NULL, myGlobal);
  102.  
  103.  
  104.     if (loadScript("System/Base.js"))
  105.     {
  106.         loadScript("System/Startup.js");
  107.  
  108.         loadScript("Autostart.js");
  109.  
  110.         Context::Scope contextScope(myContext);
  111.  
  112.         myFunctionStartup = Handle<Function>::Cast(myContext->Global()->Get(String::New("startup")));
  113.     }
  114.     else
  115.     {
  116.         ///FIXME: We should probably exit the application here
  117.         return;
  118.     }
  119.  
  120.     const int argc = 0;
  121.     Handle<Value> argv[argc] = { };
  122.     Handle<Value> result = myFunctionStartup->Call(myFunctionStartup, argc, argv);
  123.  
  124.  
  125.     string commandPort = Settings::get("CommandPort");
  126.     if (commandPort != "")
  127.     {
  128.         myCommandThread.setSettings(stoi(commandPort), "Command console");
  129.         myCommandThread.start();
  130.     }
  131.     else
  132.     {
  133.         Logger &log = Logger::getInstance();
  134.         log.add("CommandPort is not defined, can not start Command console socket\n");
  135.     }
  136.  
  137.  
  138.     mySemaphore.lock();
  139.  
  140.     while (true)
  141.     {
  142.         while (myExpressions.size() > 0)
  143.         {
  144.             runExpression(myExpressions.pop());
  145.         }
  146.  
  147.         mySemaphore.wait();
  148.     }
  149.  
  150.     mySemaphore.unlock();
  151. }
  152.  
  153. void VirtualMachine::queueExpression(Expression expression)
  154. {
  155.     myExpressions.push(expression);
  156.     mySemaphore.broadcast();
  157. }
  158.  
  159. string VirtualMachine::formatException(TryCatch* tryCatch)
  160. {
  161.     string result;
  162.  
  163.     HandleScope handleScope;
  164.     String::Utf8Value exception(tryCatch->Exception());
  165.     Handle<v8::Message> message = tryCatch->Message();
  166.  
  167.     string strException = *exception;
  168.  
  169.     if (message.IsEmpty())
  170.     {
  171.         result += strException + "\n";
  172.     }
  173.     else
  174.     {
  175.         String::Utf8Value filename(message->GetScriptResourceName());
  176.         string strFilename = *filename;
  177.  
  178.         result += strFilename + ":" + itos(message->GetLineNumber()) + ": " + strException + "\n";
  179.  
  180.         String::Utf8Value sourceline(message->GetSourceLine());
  181.         string strSourceline = *sourceline;
  182.  
  183.         result += strSourceline + "\n";
  184.  
  185.         string underline = rpad("", message->GetStartColumn(), ' ');
  186.         underline = rpad(underline, message->GetEndColumn(), '^');
  187.  
  188.         result += underline + "\n";
  189.     }
  190.  
  191.     return result;
  192. }
  193.  
  194. bool VirtualMachine::loadDataStore(string storeName)
  195. {
  196.     Logger &log = Logger::getInstance();
  197.  
  198.     string basePath = Settings::get("BasePath") + "Services/DataStores/";
  199.     string scriptFileName = basePath + storeName + ".js";
  200.  
  201.     if (!file_exists(scriptFileName))
  202.     {
  203.         log.add("Failed to load datastore " + scriptFileName + ", file does not exist.\n");
  204.         return false;
  205.     }
  206.  
  207.     string scriptSource = file_get_contents(scriptFileName);
  208.  
  209.     scriptSource = "DataStore.addStore('" + storeName + "', " + str_replace("\n", "", scriptSource) + ");";
  210.  
  211.     Handle<String> source =  String::New(scriptSource.c_str(), scriptSource.length());
  212.     Handle<String> name = String::New(scriptFileName.c_str(), scriptFileName.length());
  213.  
  214.     Context::Scope contextScope(myContext);
  215.  
  216.     TryCatch tryCatch;
  217.  
  218.     Handle<Script> script = Script::Compile(source, name);
  219.  
  220.     if (script.IsEmpty())
  221.     {
  222.         log.add(formatException(&tryCatch));
  223.         return false;
  224.     }
  225.     else
  226.     {
  227.         Handle<Value> result = script->Run();
  228.  
  229.         if (result.IsEmpty())
  230.         {
  231.             log.add(formatException(&tryCatch));
  232.             return false;
  233.         }
  234.  
  235.         log.add("Loaded datastore " + storeName + "\n");
  236.     }
  237.  
  238.     return true;
  239. }
  240.  
  241. bool VirtualMachine::loadScript(string scriptName)
  242. {
  243.     Logger &log = Logger::getInstance();
  244.  
  245.     string basePath = Settings::get("BasePath") + "Services/";
  246.     string scriptFileName = basePath + scriptName;
  247.  
  248.     if (!file_exists(scriptFileName))
  249.     {
  250.         log.add("Failed to load " + scriptFileName + ", file does not exist.\n");
  251.         return false;
  252.     }
  253.  
  254.     string scriptSource = file_get_contents(scriptFileName);
  255.  
  256.     Handle<String> source =  String::New(scriptSource.c_str(), scriptSource.length());
  257.     Handle<String> name = String::New(scriptName.c_str(), scriptName.length());
  258.  
  259.     Context::Scope contextScope(myContext);
  260.  
  261.     TryCatch tryCatch;
  262.  
  263.     Handle<Script> script = Script::Compile(source, name);
  264.  
  265.     if (script.IsEmpty())
  266.     {
  267.         log.addToSyslog(formatException(&tryCatch));
  268.         return false;
  269.     }
  270.     else
  271.     {
  272.         Handle<Value> result = script->Run();
  273.  
  274.         if (result.IsEmpty())
  275.         {
  276.             log.addToSyslog(formatException(&tryCatch));
  277.             return false;
  278.         }
  279.  
  280.         log.add("Loaded " + scriptName + "\n");
  281.     }
  282.  
  283.     return true;
  284. }
  285.  
  286. unsigned int VirtualMachine::startIntervalThread(unsigned int timeout)
  287. {
  288.     IntervalThread *intervalThread = new IntervalThread(timeout);
  289.  
  290.     myIntervalThreads[intervalThread->getId()] = intervalThread;
  291.     myIntervalThreads[intervalThread->getId()]->start();
  292.  
  293.     return intervalThread->getId();
  294. }
  295.  
  296. bool VirtualMachine::stopIntervalThread(unsigned int id)
  297. {
  298.     if (myIntervalThreads.find(id) != myIntervalThreads.end())
  299.     {
  300.         myIntervalThreads[id]->stop();
  301.         delete myIntervalThreads[id];
  302.         myIntervalThreads.erase(id);
  303.  
  304.         return true;
  305.     }
  306.  
  307.     return false;
  308. }
  309.  
  310. bool VirtualMachine::setIntervalThreadTimeout(unsigned int id, unsigned int timeout)
  311. {
  312.     if (myIntervalThreads.find(id) != myIntervalThreads.end())
  313.     {
  314.         myIntervalThreads[id]->setTimeout(timeout);
  315.         return true;
  316.     }
  317.  
  318.     return false;
  319. }
  320.  
  321. unsigned int VirtualMachine::startSocketThread(string address, int port, unsigned int reconnectTimeout)
  322. {
  323.     SocketThread *socketThread = new SocketThread(address, port, reconnectTimeout);
  324.     mySocketThreads[socketThread->getId()] = socketThread;
  325.     mySocketThreads[socketThread->getId()]->start();
  326.     mySocketThreads[socketThread->getId()]->startSocket();
  327.  
  328.     return socketThread->getId();
  329. }
  330.  
  331. bool VirtualMachine::stopSocketThread(unsigned int id)
  332. {
  333.     if (mySocketThreads.find(id) != mySocketThreads.end())
  334.     {
  335.         delete mySocketThreads[id];
  336.         mySocketThreads.erase(id);
  337.         return true;
  338.     }
  339.  
  340.     return false;
  341. }
  342.  
  343. void VirtualMachine::sendToSocketThread(unsigned int id, string data)
  344. {
  345.     if (mySocketThreads.find(id) != mySocketThreads.end())
  346.     {
  347.         mySocketThreads[id]->send(data);
  348.     }
  349. }
  350.  
  351. void VirtualMachine::disconnectCommandClient(unsigned int id)
  352. {
  353.     myCommandThread.disconnectClient(id);
  354. }
  355.  
  356. void VirtualMachine::sendToCommandClient(unsigned int id, string data)
  357. {
  358.     myCommandThread.sendTo(id, data);
  359. }
  360.  
  361. void VirtualMachine::print(unsigned int id, string data)
  362. {
  363.     if (id == 0)
  364.     {
  365.         Logger &log = Logger::getInstance();
  366.         log.add(data);
  367.     }
  368.     else
  369.     {
  370.         VirtualMachine &vm = VirtualMachine::getInstance();
  371.         vm.sendToCommandClient(id, data);
  372.     }
  373. }
  374.  
  375. bool VirtualMachine::runExpression(Expression expression)
  376. {
  377.     string scriptName = "runExpression";
  378.  
  379.     string scriptData = expression.getScript();
  380.  
  381.     scriptData = "setClientId(" + itos(expression.getTargetId()) + ");\n" + scriptData + "\n";
  382.  
  383.     Handle<String> source =  String::New(scriptData.c_str(), scriptData.length());
  384.     Handle<String> name = String::New(scriptName.c_str(), scriptName.length());
  385.  
  386.     Context::Scope contextScope(myContext);
  387.  
  388.     TryCatch tryCatch;
  389.  
  390.     Handle<Script> script = Script::Compile(source, name);
  391.  
  392.     if (script.IsEmpty())
  393.     {
  394.         print(expression.getTargetId(), formatException(&tryCatch));
  395.         return false;
  396.     }
  397.     else
  398.     {
  399.         Handle<Value> result = script->Run();
  400.  
  401.         if (result.IsEmpty())
  402.         {
  403.             print(expression.getTargetId(), formatException(&tryCatch));
  404.             return false;
  405.         }
  406.         else if (expression.getTargetId() != 0)
  407.         {
  408.              String::AsciiValue ascii(result);
  409.              string resultString = *ascii;
  410.  
  411.              if (resultString != "undefined")
  412.              {
  413.                 print(expression.getTargetId(), resultString + "\n");
  414.              }
  415.         }
  416.     }
  417.  
  418.     return true;
  419. }
  420.  
  421.  
  422. Handle<Value> VirtualMachine::_quit(const Arguments& args)
  423. {
  424.     VirtualMachine &vm = VirtualMachine::getInstance();
  425.  
  426.     vm.disconnectCommandClient(args[0]->Uint32Value());
  427.  
  428.     return Undefined();
  429. }
  430.  
  431. Handle<Value> VirtualMachine::_log(const Arguments& args)
  432. {
  433.     Logger &log = Logger::getInstance();
  434.  
  435.     String::AsciiValue str(args[0]);
  436.  
  437.     log.add(*str);
  438.  
  439.     return Undefined();
  440. }
  441.  
  442. Handle<Value> VirtualMachine::_print(const Arguments& args)
  443. {
  444.     VirtualMachine &vm = VirtualMachine::getInstance();
  445.  
  446.     String::AsciiValue str(args[1]);
  447.  
  448.     vm.print(args[0]->Uint32Value(), *str);
  449.  
  450.     return Undefined();
  451. }
  452.  
  453. Handle<Value> VirtualMachine::_sendCanMessage(const Arguments& args)
  454. {
  455.     CanNetManager &canMan = CanNetManager::getInstance();
  456.  
  457.     CanMessage canMessage;
  458.  
  459.     String::AsciiValue className(args[0]);
  460.     canMessage.setClassName(*className);
  461.     String::AsciiValue directionFlag(args[1]);
  462.     canMessage.setDirectionFlag(*directionFlag);
  463.     String::AsciiValue moduleName(args[2]);
  464.     canMessage.setModuleName(*moduleName);
  465.     canMessage.setModuleId(args[3]->Uint32Value());
  466.     String::AsciiValue commandName(args[4]);
  467.     canMessage.setCommandName(*commandName);
  468.  
  469.     String::AsciiValue dataString(args[5]);
  470.  
  471.     vector<string> parts = explode(",", trim(*dataString, ','));
  472.     map<string, CanVariable> data;
  473.  
  474.     for (int n = 0; n < parts.size(); n++)
  475.     {
  476.         vector<string> keyAndValue = explode(":", parts[n]);
  477.  
  478.         data[keyAndValue[0]] = CanVariable(keyAndValue[0], keyAndValue[1]);
  479.     }
  480.  
  481.     canMessage.setData(data);
  482.    
  483.     canMan.sendMessage(canMessage);
  484.  
  485.     return Undefined();
  486. }
  487.  
  488. Handle<Value> VirtualMachine::_sendCanNMTMessage(const Arguments& args)
  489. {
  490.     CanNetManager &canMan = CanNetManager::getInstance();
  491.  
  492.     CanMessage canMessage;
  493.  
  494.     String::AsciiValue className(args[0]);
  495.     canMessage.setClassName(*className);
  496.     String::AsciiValue commandName(args[1]);
  497.     canMessage.setCommandName(*commandName);
  498.  
  499.     String::AsciiValue dataString(args[2]);
  500.  
  501.     vector<string> parts = explode(",", trim(*dataString, ','));
  502.     map<string, CanVariable> data;
  503.  
  504.     for (int n = 0; n < parts.size(); n++)
  505.     {
  506.         vector<string> keyAndValue = explode(":", parts[n]);
  507.  
  508.         data[keyAndValue[0]] = CanVariable(keyAndValue[0], keyAndValue[1]);
  509.     }
  510.  
  511.     canMessage.setData(data);
  512.  
  513.     canMan.sendMessage(canMessage);
  514.  
  515.     return Undefined();
  516. }
  517.  
  518. Handle<Value> VirtualMachine::_loadScript(const Arguments& args)
  519. {
  520.     VirtualMachine &vm = VirtualMachine::getInstance();
  521.  
  522.     String::AsciiValue str(args[0]);
  523.  
  524.     return Boolean::New(vm.loadScript(*str));
  525. }
  526.  
  527. Handle<Value> VirtualMachine::_loadDataStore(const Arguments& args)
  528. {
  529.     VirtualMachine &vm = VirtualMachine::getInstance();
  530.  
  531.     String::AsciiValue str(args[0]);
  532.  
  533.     return Boolean::New(vm.loadDataStore(*str));
  534. }
  535.  
  536. Handle<Value> VirtualMachine::_startIntervalThread(const Arguments& args)
  537. {
  538.     VirtualMachine &vm = VirtualMachine::getInstance();
  539.     return Integer::New(vm.startIntervalThread(args[0]->Uint32Value()));
  540. }
  541.  
  542. Handle<Value> VirtualMachine::_stopIntervalThread(const Arguments& args)
  543. {
  544.     VirtualMachine &vm = VirtualMachine::getInstance();
  545.     return Boolean::New(vm.stopIntervalThread(args[0]->Uint32Value()));
  546. }
  547.  
  548. Handle<Value> VirtualMachine::_setIntervalThreadTimeout(const Arguments& args)
  549. {
  550.     VirtualMachine &vm = VirtualMachine::getInstance();
  551.     return Integer::New(vm.setIntervalThreadTimeout(args[0]->Uint32Value(), args[1]->Uint32Value()));
  552. }
  553.  
  554. Handle<Value> VirtualMachine::_startSocketThread(const Arguments& args)
  555. {
  556.     VirtualMachine &vm = VirtualMachine::getInstance();
  557.  
  558.     String::AsciiValue address(args[0]);
  559.  
  560.     return Integer::New(vm.startSocketThread(*address, args[1]->Uint32Value(), args[2]->Uint32Value()));
  561. }
  562.  
  563. Handle<Value> VirtualMachine::_stopSocketThread(const Arguments& args)
  564. {
  565.     VirtualMachine &vm = VirtualMachine::getInstance();
  566.  
  567.     return Boolean::New(vm.stopSocketThread(args[0]->Uint32Value()));
  568. }
  569.  
  570. Handle<Value> VirtualMachine::_sendToSocketThread(const Arguments& args)
  571. {
  572.     VirtualMachine &vm = VirtualMachine::getInstance();
  573.  
  574.     String::AsciiValue data(args[1]);
  575.  
  576.     vm.sendToSocketThread(args[0]->Uint32Value(), *data);
  577.  
  578.     return Undefined();
  579. }
  580.  
  581. Handle<Value> VirtualMachine::_getFileContents(const Arguments& args)
  582. {
  583.     String::AsciiValue filename(args[0]);
  584.  
  585.     if (!file_exists(*filename))
  586.     {
  587.         return Undefined();
  588.     }
  589.  
  590.     string content = file_get_contents(*filename);
  591.  
  592.     return String::New(content.c_str());
  593. }
  594.  
  595. Handle<Value> VirtualMachine::_uint2hex(const Arguments& args)
  596. {
  597.     string hex = uint2hex(args[0]->Uint32Value(), args[1]->Uint32Value());
  598.     return String::New(hex.c_str());
  599. }
  600.  
  601. Handle<Value> VirtualMachine::_hex2bin(const Arguments& args)
  602. {
  603.     String::AsciiValue hex(args[0]);
  604.     string bin = hex2bin(*hex);
  605.     return String::New(bin.c_str());
  606. }
  607.  
  608. Handle<Value> VirtualMachine::_bin2hex(const Arguments& args)
  609. {
  610.     String::AsciiValue bin(args[0]);
  611.     string hex = bin2hex(*bin);
  612.     return String::New(hex.c_str());
  613. }
  614.  
  615. Handle<Value> VirtualMachine::_bin2float(const Arguments& args)
  616. {
  617.     String::AsciiValue bin(args[0]);
  618.     float f = bin2float(*bin);
  619.     return Number::New(f);
  620. }
  621.  
  622. Handle<Value> VirtualMachine::_float2bin(const Arguments& args)
  623. {
  624.     string bin = float2bin(args[0]->NumberValue(), args[1]->Uint32Value());
  625.     return String::New(bin.c_str());
  626. }
  627.  
  628. Handle<Value> VirtualMachine::_bin2uint(const Arguments& args)
  629. {
  630.     String::AsciiValue bin(args[0]);
  631.     unsigned int num = bin2uint(*bin);
  632.     return Number::New(num);
  633. }
  634.  
  635. Handle<Value> VirtualMachine::_uint2bin(const Arguments& args)
  636. {
  637.     string bin = uint2bin(args[0]->Uint32Value(), args[1]->Uint32Value());
  638.     return String::New(bin.c_str());
  639. }
  640.  
  641. Handle<Value> VirtualMachine::_hex2uint(const Arguments& args)
  642. {
  643.     String::AsciiValue hex(args[0]);
  644.     unsigned int num = hex2uint(*hex);
  645.     return Number::New(num);
  646. }
  647.  
  648. Handle<Value> VirtualMachine::_sleep(const Arguments& args)
  649. {
  650.     usleep(args[0]->Uint32Value());
  651.     return Undefined();
  652. }
  653.