2015-03-05 15:18:41 -07:00
|
|
|
#!/usr/bin/env python2
|
2018-03-23 08:36:45 -07:00
|
|
|
#
|
|
|
|
# (C) Copyright 2015-2018 Sei Lisa. All rights reserved.
|
2015-03-05 15:18:41 -07:00
|
|
|
#
|
|
|
|
# This file is part of LSL PyOptimizer.
|
|
|
|
#
|
|
|
|
# LSL PyOptimizer 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.
|
|
|
|
#
|
|
|
|
# LSL PyOptimizer 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 LSL PyOptimizer. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
# This is the main executable program that imports the libraries.
|
2014-07-25 17:43:44 -07:00
|
|
|
|
2017-10-01 15:40:59 -07:00
|
|
|
from lslopt.lslparse import parser,EParse
|
2014-07-25 17:43:44 -07:00
|
|
|
from lslopt.lsloutput import outscript
|
2014-07-26 12:29:35 -07:00
|
|
|
from lslopt.lsloptimizer import optimizer
|
2015-03-14 22:18:55 -07:00
|
|
|
import sys, os, getopt, re
|
2015-03-06 12:29:54 -07:00
|
|
|
import lslopt.lslcommon
|
2017-10-20 07:26:05 -07:00
|
|
|
import lslopt.lslloadlib
|
2014-07-25 17:43:44 -07:00
|
|
|
|
2015-03-14 15:17:15 -07:00
|
|
|
|
2018-01-18 08:02:38 -07:00
|
|
|
VERSION = '0.2.2beta'
|
2015-03-14 22:18:55 -07:00
|
|
|
|
|
|
|
|
2016-01-01 19:39:47 -07:00
|
|
|
def ReportError(script, e):
|
2017-10-01 15:40:59 -07:00
|
|
|
linestart = script.rfind(b'\n', 0, e.errorpos) + 1
|
|
|
|
lineend = script.find(b'\n', e.errorpos)
|
|
|
|
if lineend == -1: lineend = len(script) # may hit EOF
|
|
|
|
|
|
|
|
# When the encoding of stderr is unknown (e.g. when redirected to a file),
|
|
|
|
# output will be encoded in UTF-8; otherwise the terminal's encoding will
|
|
|
|
# be used.
|
|
|
|
enc = sys.stderr.encoding if sys.stderr.encoding is not None else 'utf8'
|
|
|
|
|
|
|
|
# Synchronize the UTF-8 encoded line with the output line in the
|
|
|
|
# terminal's encoding. We need to compensate for the fact that the
|
|
|
|
# reported column applies to the UTF-8 version of the script.
|
|
|
|
# 1. Trim the UTF-8 line.
|
|
|
|
err_frag = script[linestart:e.errorpos]
|
|
|
|
# 2. Convert to Unicode; encode in the target encoding with replacing.
|
|
|
|
err_frag = err_frag.decode('utf8').encode(enc, 'backslashreplace')
|
|
|
|
# 3. Collect our prize: the length of that in characters.
|
|
|
|
cno = len(err_frag.decode(enc))
|
|
|
|
|
|
|
|
# Write the whole line in the target encoding.
|
|
|
|
err_line = script[linestart:lineend] + b'\n'
|
|
|
|
sys.stderr.write(err_line.decode('utf8').encode(enc, 'backslashreplace'))
|
|
|
|
sys.stderr.write(u" " * cno + u"^\n")
|
2016-12-25 12:49:56 -07:00
|
|
|
sys.stderr.write(e.args[0] + u"\n")
|
2016-01-01 19:39:47 -07:00
|
|
|
|
2015-03-15 12:01:41 -07:00
|
|
|
class UniConvScript(object):
|
2016-12-20 11:22:48 -07:00
|
|
|
"""Converts the script to Unicode, setting the properties required by
|
2015-03-15 12:01:41 -07:00
|
|
|
EParse to report a meaningful error position.
|
2016-12-20 11:22:48 -07:00
|
|
|
"""
|
2017-10-10 20:04:13 -07:00
|
|
|
def __init__(self, script, options = (), filename = '<stdin>'):
|
|
|
|
self.linedir = []
|
|
|
|
self.filename = filename
|
|
|
|
# We don't interpret #line here. In case of an encode error,
|
|
|
|
# we're in the dark about which file it comes from. User needs
|
|
|
|
# --preshow to view the #line directives and find the correspondence
|
|
|
|
# themselves.
|
|
|
|
#self.processpre = 'processpre' in options
|
|
|
|
self.processpre = False
|
2015-03-15 12:01:41 -07:00
|
|
|
self.script = script
|
|
|
|
|
|
|
|
def to_unicode(self):
|
|
|
|
if type(self.script) is not unicode:
|
|
|
|
try:
|
|
|
|
self.script = self.script.decode('utf8')
|
|
|
|
except UnicodeDecodeError as e:
|
|
|
|
self.errorpos = e.start
|
2016-12-20 11:22:48 -07:00
|
|
|
raise EParse(self, u"Invalid UTF-8 in script")
|
2015-03-15 12:01:41 -07:00
|
|
|
return self.script
|
|
|
|
|
2015-03-14 22:18:55 -07:00
|
|
|
def PreparePreproc(script):
|
|
|
|
s = ''
|
|
|
|
nlines = 0
|
|
|
|
col = 0
|
|
|
|
|
|
|
|
# Trigraphs make our life really difficult.
|
2015-03-20 09:45:38 -07:00
|
|
|
# We join lines that have \<return> or ??/<return> inside strings,
|
|
|
|
# and we also replace regular <return> inside strings with \n, counting how
|
|
|
|
# many lines we join, to add them back at the end of the string in order to
|
|
|
|
# keep the line count exact prior to preprocessing. We also preserve the
|
2015-03-24 13:56:35 -07:00
|
|
|
# original column of the text after the string, by adding as many spaces as
|
|
|
|
# necessary.
|
2015-03-20 09:45:38 -07:00
|
|
|
# We could let the preprocessor do the line joining on backslash-newline,
|
|
|
|
# but by eliminating all newlines, we have control over the output column
|
|
|
|
# of the text that follows the string and can report an accurate column
|
2015-03-24 13:56:35 -07:00
|
|
|
# and line position in case of error.
|
2015-03-20 09:45:38 -07:00
|
|
|
# The REs skip as much as possible in one go every time, only stopping to
|
|
|
|
# analyze critical tokens.
|
2015-03-24 13:56:35 -07:00
|
|
|
# We don't follow the C convention that backslash-return is analyzed first.
|
|
|
|
# In c, the string "a\\<return>nb" is the same as "a\nb" which prints as
|
|
|
|
# a<return>b. But in LSL, forgetting about the preprocessor, the string
|
|
|
|
# "a\\<return>nb" is valid and stands for a\<return>nb. The principle of
|
|
|
|
# least surprise seems to suggest to accept valid LSL strings as LSL
|
2017-01-03 21:07:50 -07:00
|
|
|
# instead of reproducing that C quirk. This also matches what FS is doing
|
|
|
|
# currently, so it's good for compatibility.
|
2015-03-20 09:45:38 -07:00
|
|
|
tok = re.compile(
|
2017-11-26 06:10:33 -07:00
|
|
|
ur'(?:'
|
|
|
|
ur'/(?:\?\?/\n|\\\n)*\*.*?\*(?:\?\?/\n|\\\n)*/'
|
|
|
|
ur'|/(?:\?\?/\n|\\\n)*/(?:\?\?/\n|\\\n|[^\n])*\n'
|
|
|
|
ur'|[^"]'
|
|
|
|
ur')+'
|
|
|
|
ur'|"'
|
2015-03-14 22:18:55 -07:00
|
|
|
, re.S)
|
2015-03-20 09:45:38 -07:00
|
|
|
# RE used inside strings.
|
2015-03-14 22:18:55 -07:00
|
|
|
tok2 = re.compile(
|
2017-11-26 06:10:33 -07:00
|
|
|
ur'(?:'
|
|
|
|
ur"\?\?[='()!<>-]" # valid trigraph except ??/ (backslash)
|
|
|
|
ur"|(?:\?\?/|\\)(?:\?\?[/='()!<>-]|[^\n])"
|
2015-03-20 09:45:38 -07:00
|
|
|
# backslash trigraph or actual backslash,
|
|
|
|
# followed by any trigraph or non-newline
|
2017-11-26 06:10:33 -07:00
|
|
|
ur'|(?!\?\?/\n|\\\n|"|\n).'
|
2015-03-20 09:45:38 -07:00
|
|
|
# any character that doesn't start a trigraph/
|
|
|
|
# backslash escape followed by a newline
|
|
|
|
# or is a newline or double quote, as we're
|
|
|
|
# interested in all those individually.
|
2017-11-26 06:10:33 -07:00
|
|
|
ur')+' # as many of those as possible
|
|
|
|
ur'|\?\?/\n|\\\n|\n|"' # or any of those individually
|
2015-03-14 22:18:55 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
pos = 0
|
|
|
|
match = tok.search(script, pos)
|
|
|
|
while match:
|
|
|
|
matched = match.group(0)
|
|
|
|
pos += len(matched)
|
2017-11-26 06:10:33 -07:00
|
|
|
if matched == u'"':
|
2015-03-14 22:18:55 -07:00
|
|
|
s += matched
|
|
|
|
nlines = col = 0
|
|
|
|
match2 = tok2.search(script, pos)
|
|
|
|
while match2:
|
|
|
|
matched2 = match2.group(0)
|
|
|
|
pos += len(matched2)
|
|
|
|
|
2017-11-26 06:10:33 -07:00
|
|
|
if matched2 == u'\\\n' or matched2 == u'??/\n':
|
2015-03-14 22:18:55 -07:00
|
|
|
nlines += 1
|
|
|
|
col = 0
|
|
|
|
match2 = tok2.search(script, pos)
|
|
|
|
continue
|
2017-11-26 06:10:33 -07:00
|
|
|
if matched2 == u'"':
|
2015-03-14 22:18:55 -07:00
|
|
|
if nlines:
|
2017-11-26 06:10:33 -07:00
|
|
|
if script[pos:pos+1] == u'\n':
|
2015-03-14 22:18:55 -07:00
|
|
|
col = -1 # don't add spaces if not necessary
|
|
|
|
# col misses the quote added here, so add 1
|
2017-11-26 06:10:33 -07:00
|
|
|
s += u'"' + u'\n'*nlines + u' '*(col+1)
|
2015-03-14 22:18:55 -07:00
|
|
|
else:
|
2017-11-26 06:10:33 -07:00
|
|
|
s += u'"'
|
2015-03-14 22:18:55 -07:00
|
|
|
break
|
2017-11-26 06:10:33 -07:00
|
|
|
if matched2 == u'\n':
|
2015-03-14 22:18:55 -07:00
|
|
|
nlines += 1
|
|
|
|
col = 0
|
2017-11-26 06:10:33 -07:00
|
|
|
s += u'\\n'
|
2015-03-14 22:18:55 -07:00
|
|
|
else:
|
|
|
|
col += len(matched2)
|
|
|
|
s += matched2
|
|
|
|
match2 = tok2.search(script, pos)
|
|
|
|
|
|
|
|
else:
|
|
|
|
s += matched
|
|
|
|
match = tok.search(script, pos)
|
2015-03-14 15:17:15 -07:00
|
|
|
|
2015-03-14 22:18:55 -07:00
|
|
|
return s
|
|
|
|
|
|
|
|
def ScriptHeader(script, avname):
|
|
|
|
if avname:
|
|
|
|
avname = ' - ' + avname
|
|
|
|
return ('//start_unprocessed_text\n/*'
|
2017-08-09 08:48:23 -07:00
|
|
|
# + re.sub(r'([*/])(?=[*|/])', r'\1|', script) # FS's algorithm
|
|
|
|
# HACK: This won't break strings containing ** or /* or // like URLs,
|
|
|
|
# while still being compatible with FS.
|
|
|
|
+ re.sub(r'([*/]\||\*(?=/))', r'\1|', script)
|
2015-03-14 22:18:55 -07:00
|
|
|
+ '*/\n//end_unprocessed_text\n//nfo_preprocessor_version 0\n'
|
|
|
|
'//program_version LSL PyOptimizer v' + VERSION + avname
|
|
|
|
+ '\n//mono\n\n')
|
2015-03-14 15:17:15 -07:00
|
|
|
|
2016-12-24 17:12:17 -07:00
|
|
|
def Usage(progname, about = None):
|
2015-03-14 15:17:15 -07:00
|
|
|
if about is None:
|
2015-03-05 12:45:38 -07:00
|
|
|
sys.stderr.write(
|
2016-12-20 11:22:48 -07:00
|
|
|
ur"""LSL optimizer v{version}
|
2015-03-05 15:18:41 -07:00
|
|
|
|
2018-03-23 08:36:45 -07:00
|
|
|
(C) Copyright 2015-2018 Sei Lisa. All rights reserved.
|
2015-03-05 15:18:41 -07:00
|
|
|
|
|
|
|
This program comes with ABSOLUTELY NO WARRANTY.
|
|
|
|
This is free software, and you are welcome to redistribute it
|
|
|
|
under certain conditions; see the file COPYING for details.
|
|
|
|
This program is licensed under the GNU General Public License
|
|
|
|
version 3.
|
2014-07-27 17:13:08 -07:00
|
|
|
|
2015-03-14 15:17:15 -07:00
|
|
|
Usage: {progname}
|
2015-03-14 22:18:55 -07:00
|
|
|
[-O|--optimizer-options=[+|-]option[,[+|-]option[,...]]]
|
|
|
|
optimizer options (use '-O help' for help)
|
|
|
|
[-h|--help] print this help
|
|
|
|
[--version] print this program's version
|
|
|
|
[-o|--output=<filename>] output to file rather than stdout
|
2017-04-28 14:43:15 -07:00
|
|
|
[-b|--builtins=<filename>] use a builtins file other than builtins.txt
|
2017-10-21 01:00:31 -07:00
|
|
|
[-L|--libdata=<filename>] use a function data file other than fndata.txt
|
2015-03-18 20:11:29 -07:00
|
|
|
[-H|--header] add the script as a comment in Firestorm format
|
2015-12-25 11:04:24 -07:00
|
|
|
[-T|--timestamp] add a timestamp as a comment at the beginning
|
2017-01-11 17:55:52 -07:00
|
|
|
[-y|--python-exceptions] when an exception is raised, show a stack trace
|
2015-03-18 20:11:29 -07:00
|
|
|
[-p|--preproc=mode] run external preprocessor (see below for modes)
|
|
|
|
(resets the preprocessor command line so far)
|
2015-03-14 22:18:55 -07:00
|
|
|
[-P|--prearg=<arg>] add parameter to preprocessor's command line
|
2015-03-18 20:11:29 -07:00
|
|
|
[--precmd=<cmd>] preprocessor command ('cpp' by default)
|
|
|
|
[--prenodef] no LSL specific defines (__AGENTKEY__ etc.)
|
2015-05-05 17:45:37 -07:00
|
|
|
[--preshow] show preprocessor output, and stop
|
2017-01-07 21:23:50 -07:00
|
|
|
[--avid=<UUID>] * specify UUID of avatar saving the script
|
|
|
|
[--avname=<name>] * specify name of avatar saving the script
|
|
|
|
[--assetid=<UUID>] * specify the asset UUID of the script
|
|
|
|
[--shortname=<name>] * specify the script's short file name
|
2017-11-19 11:48:35 -07:00
|
|
|
[--prettify] Prettify source file. Disables all -O options.
|
2018-05-11 13:37:09 -07:00
|
|
|
[--bom] Prefix script with a UTF-8 byte-order mark
|
2015-03-14 22:18:55 -07:00
|
|
|
filename input file
|
2014-07-28 08:28:12 -07:00
|
|
|
|
2017-01-07 21:23:50 -07:00
|
|
|
Options marked with * are used to define the preprocessor macros __AGENTID__,
|
|
|
|
__AGENTKEY__, __AGENTIDRAW__, __AGENTNAME__, __ASSETID__ and __SHORTFILE__,
|
|
|
|
and have no effect if --prenodef is specified.
|
|
|
|
|
2017-01-11 18:03:39 -07:00
|
|
|
Using --prenodef before -p causes no macros whatsoever to be defined. If used
|
|
|
|
after -p, the type casting macros string(...), key(...), etc. will be defined.
|
|
|
|
|
2014-07-28 08:28:12 -07:00
|
|
|
If filename is a dash (-) then standard input is used.
|
2017-01-07 21:23:50 -07:00
|
|
|
Use: {progname} -O help for help on the optimizer control options.
|
2014-07-27 17:13:08 -07:00
|
|
|
|
2015-03-14 22:18:55 -07:00
|
|
|
Preprocessor modes:
|
2015-03-18 20:11:29 -07:00
|
|
|
ext Invoke preprocessor
|
|
|
|
mcpp Invoke mcpp as preprocessor, setting default parameters pertinent
|
|
|
|
to it. Implies --precmd=mcpp
|
|
|
|
gcpp Invoke GNU cpp as preprocessor, setting default parameters
|
|
|
|
pertinent to it. Implies --precmd=cpp
|
2015-03-24 13:56:35 -07:00
|
|
|
none No preprocessing (default)
|
|
|
|
|
2016-06-27 18:20:21 -07:00
|
|
|
Normally, running the preprocessor needs the option 'processpre' active, to
|
|
|
|
make the output readable by the optimizer. This option is active by default.
|
2016-12-24 17:12:17 -07:00
|
|
|
""".format(progname=progname, version=VERSION))
|
2015-03-14 15:17:15 -07:00
|
|
|
return
|
|
|
|
|
|
|
|
if about == 'optimizer-options':
|
|
|
|
sys.stderr.write(
|
2016-12-20 11:22:48 -07:00
|
|
|
ur"""
|
2016-06-27 18:45:39 -07:00
|
|
|
Optimizer control options.
|
|
|
|
+ means active by default, - means inactive by default.
|
|
|
|
Case insensitive.
|
2015-03-04 17:37:32 -07:00
|
|
|
|
|
|
|
Syntax extensions options:
|
|
|
|
|
2016-06-27 18:45:39 -07:00
|
|
|
ExtendedGlobalExpr + Enables arbitrary expressions in globals (as opposed to
|
2014-07-27 17:13:08 -07:00
|
|
|
dull simple expressions allowed by regular LSL). Needs
|
2015-04-15 13:52:49 -07:00
|
|
|
constant folding active for the result to be compilable.
|
2016-06-27 18:45:39 -07:00
|
|
|
BreakCont - Allow break/continue statements for loops. Note that
|
2015-03-05 18:56:58 -07:00
|
|
|
when active, 'break' and 'continue' become reserved
|
|
|
|
words, but when inactive they can be used as variables.
|
2016-06-27 18:45:39 -07:00
|
|
|
ExtendedTypeCast + Allows extended typecast syntax e.g. (string)(integer)a
|
2014-07-27 17:13:08 -07:00
|
|
|
is valid with this option.
|
2016-06-27 18:45:39 -07:00
|
|
|
ExtendedAssignment + Enables &=, |=, ^=, <<=, >>= assignment operators.
|
|
|
|
AllowKeyConcat + Allow string + key and key + string (both return string)
|
|
|
|
AllowMultiStrings + Allow C-like string juxtaposition, e.g. "ab" "cd" means
|
2014-07-27 17:13:08 -07:00
|
|
|
"abcd", no concatenation involved. Very useful when used
|
2015-02-28 12:01:51 -07:00
|
|
|
with a preprocessor. Similar to addstrings, but this one
|
|
|
|
is not an optimization, it introduces new syntax.
|
2016-06-27 18:45:39 -07:00
|
|
|
DupLabels - Normally, a duplicate label within a function is allowed
|
2017-01-07 21:23:50 -07:00
|
|
|
by the syntax by using {{}} blocks; however, the server
|
2015-03-05 18:56:58 -07:00
|
|
|
will just refuse to save the script (under Mono) or do
|
2017-05-06 07:19:42 -07:00
|
|
|
something completely unexpected (under LSO: only the
|
|
|
|
last jump will execute, and it will go to the last label
|
|
|
|
with that name). This flag works around that limitation
|
|
|
|
by replacing the names of the labels in the output with
|
|
|
|
unique ones.
|
2015-03-05 18:56:58 -07:00
|
|
|
|
2015-03-07 14:55:25 -07:00
|
|
|
Deprecated / compatibility syntax extensions options:
|
2015-03-05 18:56:58 -07:00
|
|
|
|
2016-06-27 18:45:39 -07:00
|
|
|
LazyLists - Support syntax like mylist[index] = 5; rather than using
|
2015-03-04 17:37:32 -07:00
|
|
|
llListReplaceList. Only assignment supported. The list
|
|
|
|
is extended when the argument is greater than the list
|
|
|
|
length, by inserting integer zeros. This is implemented
|
|
|
|
for compatibility with Firestorm, but its use is not
|
|
|
|
recommended, as it adds a new function, wasting memory
|
|
|
|
against the very spirit of this program.
|
2016-06-27 18:45:39 -07:00
|
|
|
EnableSwitch - Support C-like switch() syntax, with some limitations.
|
2015-03-04 17:37:32 -07:00
|
|
|
Like lazylists, it's implemented for compatibility with
|
|
|
|
Firestorm, but not recommended. Note that the operand to
|
|
|
|
switch() may be evaluated more than once.
|
2016-06-27 18:45:39 -07:00
|
|
|
ErrMissingDefault + Throw an error in case the 'default:' label of a switch
|
2017-05-05 11:37:38 -07:00
|
|
|
statement is missing. Only meaningful with EnableSwitch.
|
2016-06-27 18:45:39 -07:00
|
|
|
FuncOverride - Allow duplicate function definitions to override the
|
2015-03-26 16:26:56 -07:00
|
|
|
previous definition. For compatibility with Firestorm's
|
|
|
|
optimizer.
|
2015-03-04 17:37:32 -07:00
|
|
|
|
|
|
|
Optimization options
|
|
|
|
|
2016-06-27 18:45:39 -07:00
|
|
|
Optimize + Runs the optimizer.
|
|
|
|
OptSigns + Optimize signs in float and integer constants.
|
|
|
|
OptFloats + Optimize floats that represent an integral value.
|
|
|
|
ConstFold + Fold constant expressions to their values, and simplify
|
2015-03-05 12:45:38 -07:00
|
|
|
some expressions and statements.
|
2016-06-27 18:45:39 -07:00
|
|
|
DCR + Dead code removal. This option removes several instances
|
2015-03-04 17:37:32 -07:00
|
|
|
of code that will never execute, and performs other
|
|
|
|
optimizations like removal of unused variables,
|
|
|
|
functions or expressions.
|
2016-06-27 18:45:39 -07:00
|
|
|
ShrinkNames - Reduces script memory by shrinking identifiers. In the
|
2014-07-31 20:07:50 -07:00
|
|
|
process, it turns the script into unreadable gibberish,
|
|
|
|
hard to debug, but this gets big savings for complex
|
|
|
|
scripts.
|
2016-06-27 18:45:39 -07:00
|
|
|
AddStrings - Concatenate strings together when possible. Note that
|
2015-03-04 17:37:32 -07:00
|
|
|
such an optimization can be counter-productive in some
|
2015-03-05 12:45:38 -07:00
|
|
|
cases, that's why it's disabled by default. For example:
|
2015-03-04 17:37:32 -07:00
|
|
|
string a="a"+"longstring"; string b="b"+"longstring";
|
|
|
|
would keep a single copy of "longstring", while if the
|
|
|
|
strings are added, both "alongstring" and "blongstring"
|
|
|
|
take memory.
|
2017-05-07 07:08:05 -07:00
|
|
|
ListLength + Optimize llGetListLength(arg) to arg!=[]. Needs constant
|
|
|
|
folding active to work.
|
2017-09-15 13:30:22 -07:00
|
|
|
ListAdd + Convert [a,b,c...] to (list)a + b + c... if possible.
|
2015-03-04 17:37:32 -07:00
|
|
|
|
|
|
|
Miscellaneous options
|
|
|
|
|
2016-06-27 18:45:39 -07:00
|
|
|
FoldTabs - Tabs can't be copy-pasted, so expressions that produce
|
2015-03-14 15:17:15 -07:00
|
|
|
tabs, e.g. llUnescapeURL("%09"), aren't optimized by
|
2015-03-04 17:37:32 -07:00
|
|
|
default. This option overrides that check, enabling
|
2015-03-05 12:45:38 -07:00
|
|
|
expansion of functions that produce strings with tabs.
|
|
|
|
The resulting source isn't guaranteed to be
|
|
|
|
copy-paste-able to the viewer.
|
2016-12-25 11:21:16 -07:00
|
|
|
WarnTabs + Warn when a function can't be optimized because it
|
|
|
|
generates a string or list with a tab, or when a string
|
|
|
|
contains a tab.
|
2016-06-27 18:45:39 -07:00
|
|
|
ProcessPre + Process some preprocessor directives in the source. This
|
2016-06-27 18:20:21 -07:00
|
|
|
enables usage of #pragma/#line preprocessor directives,
|
|
|
|
and is probably necessary if the script is itself the
|
|
|
|
output of a preprocessor. Note that this option does not
|
|
|
|
make the optimizer process macros.
|
2016-06-27 18:45:39 -07:00
|
|
|
ExplicitCast - Add explicit casts where they are implicit. This option
|
2015-03-05 18:56:58 -07:00
|
|
|
is useless with 'optimize' and 'optsigns', and is of
|
|
|
|
basically no use in general, other than to see where
|
|
|
|
automatic casts happen.
|
2016-12-25 12:40:15 -07:00
|
|
|
Clear - Set all options to inactive, Normally used as the first
|
|
|
|
option, to start afresh. Note that this sets to inactive
|
|
|
|
even the options that are active by default.
|
2017-01-07 21:23:50 -07:00
|
|
|
|
|
|
|
For example:
|
|
|
|
{progname} -O -DCR,+BreakCont scriptname.lsl
|
|
|
|
would turn off dead code removal (which is active by default) and turn on the
|
|
|
|
break/continue syntax extension (which is inactive by default).
|
|
|
|
""".format(progname=progname))
|
2015-03-14 15:17:15 -07:00
|
|
|
return
|
2014-07-27 17:13:08 -07:00
|
|
|
|
2016-12-25 13:34:42 -07:00
|
|
|
validoptions = frozenset(('extendedglobalexpr','breakcont','extendedtypecast',
|
|
|
|
'extendedassignment','allowkeyconcat','allowmultistrings','duplabels',
|
|
|
|
'lazylists','enableswitch','errmissingdefault','funcoverride','optimize',
|
|
|
|
'optsigns','optfloats','constfold','dcr','shrinknames','addstrings',
|
2017-09-15 13:30:22 -07:00
|
|
|
'foldtabs','warntabs','processpre','explicitcast','listlength','listadd',
|
2017-05-05 11:37:38 -07:00
|
|
|
'help',
|
|
|
|
# undocumented
|
|
|
|
'lso','expr','rsrclimit',
|
2016-12-25 13:34:42 -07:00
|
|
|
# 'clear' is handled as a special case
|
2017-11-19 11:48:35 -07:00
|
|
|
# 'prettify' is internal, as it's a user flag
|
2016-12-25 13:34:42 -07:00
|
|
|
))
|
|
|
|
|
2016-12-24 17:12:17 -07:00
|
|
|
def main(argv):
|
2016-12-20 11:22:48 -07:00
|
|
|
"""Main executable."""
|
2015-03-18 20:11:29 -07:00
|
|
|
|
2015-03-14 15:17:15 -07:00
|
|
|
# If it's good to append the basename to it, it's good to append the
|
|
|
|
# auxiliary files' names to it, which should be located where this file is.
|
|
|
|
lslopt.lslcommon.DataPath = __file__[:-len(os.path.basename(__file__))]
|
|
|
|
|
|
|
|
# Default options
|
2014-07-28 09:19:50 -07:00
|
|
|
options = set(('extendedglobalexpr','extendedtypecast','extendedassignment',
|
2016-06-27 18:20:21 -07:00
|
|
|
'allowkeyconcat','allowmultistrings','processpre','warntabs','optimize',
|
2016-05-06 17:38:54 -07:00
|
|
|
'optsigns','optfloats','constfold','dcr','errmissingdefault',
|
2017-09-15 13:30:22 -07:00
|
|
|
'listlength','listadd',
|
2014-07-28 09:19:50 -07:00
|
|
|
))
|
|
|
|
|
2016-12-25 13:34:42 -07:00
|
|
|
assert not (options - validoptions), (u"Default options not present in"
|
|
|
|
u" validoptions: '%s'"
|
|
|
|
% (b"', '".join(options - validoptions)).decode('utf8'))
|
|
|
|
|
2015-03-14 15:17:15 -07:00
|
|
|
try:
|
2017-10-21 01:00:31 -07:00
|
|
|
opts, args = getopt.gnu_getopt(argv[1:], 'hO:o:p:P:HTyb:L:',
|
2015-03-14 22:18:55 -07:00
|
|
|
('optimizer-options=', 'help', 'version', 'output=', 'header',
|
2018-05-11 13:37:09 -07:00
|
|
|
'timestamp', 'python-exceptions', 'prettify', 'bom',
|
2015-05-05 17:45:37 -07:00
|
|
|
'preproc=', 'precmd=', 'prearg=', 'prenodef', 'preshow',
|
2017-04-28 14:43:15 -07:00
|
|
|
'avid=', 'avname=', 'assetid=', 'shortname=', 'builtins='
|
2017-10-21 01:00:31 -07:00
|
|
|
'libdata='))
|
2016-12-25 12:47:54 -07:00
|
|
|
except getopt.GetoptError as e:
|
2016-12-24 17:12:17 -07:00
|
|
|
Usage(argv[0])
|
2016-12-25 12:47:54 -07:00
|
|
|
sys.stderr.write(u"\nError: " + str(e).decode('utf8') + u"\n")
|
2015-03-14 15:17:15 -07:00
|
|
|
return 1
|
2015-03-06 12:29:54 -07:00
|
|
|
|
2015-03-14 15:17:15 -07:00
|
|
|
outfile = '-'
|
2015-03-14 22:18:55 -07:00
|
|
|
avid = '00000000-0000-0000-0000-000000000000'
|
|
|
|
avname = ''
|
|
|
|
shortname = ''
|
|
|
|
assetid = '00000000-0000-0000-0000-000000000000'
|
2017-11-26 05:34:37 -07:00
|
|
|
preproc_command = 'cpp'
|
|
|
|
preproc_user_args = []
|
2015-03-18 20:11:29 -07:00
|
|
|
preproc = 'none'
|
|
|
|
predefines = True
|
|
|
|
script_header = ''
|
2015-12-25 11:04:24 -07:00
|
|
|
script_timestamp = ''
|
2015-05-05 17:45:37 -07:00
|
|
|
preshow = False
|
2017-01-11 17:55:52 -07:00
|
|
|
raise_exception = False
|
2017-11-19 11:48:35 -07:00
|
|
|
prettify = False
|
2018-05-11 13:37:09 -07:00
|
|
|
bom = False
|
2017-04-28 14:43:15 -07:00
|
|
|
builtins = None
|
2017-10-21 01:00:31 -07:00
|
|
|
libdata = None
|
2015-03-14 15:17:15 -07:00
|
|
|
|
|
|
|
for opt, arg in opts:
|
2015-03-15 12:01:41 -07:00
|
|
|
if type(opt) is unicode:
|
|
|
|
opt = opt.encode('utf8')
|
|
|
|
if type(arg) is unicode:
|
|
|
|
arg = arg.encode('utf8')
|
2015-03-14 15:17:15 -07:00
|
|
|
|
|
|
|
if opt in ('-O', '--optimizer-options'):
|
2016-12-25 12:40:15 -07:00
|
|
|
optchanges = arg.lower().split(',')
|
2015-03-14 15:17:15 -07:00
|
|
|
for chg in optchanges:
|
2016-12-25 13:34:42 -07:00
|
|
|
if not chg:
|
|
|
|
continue
|
2016-12-25 12:40:15 -07:00
|
|
|
if chg in ('clear', '+clear'):
|
|
|
|
options = set()
|
|
|
|
continue
|
|
|
|
if chg == '-clear':
|
|
|
|
# ignore
|
|
|
|
continue
|
2016-12-25 13:34:42 -07:00
|
|
|
chgfix = chg
|
|
|
|
if chgfix[0] not in ('+', '-'):
|
|
|
|
chgfix = '+' + chgfix
|
|
|
|
if chgfix[1:] not in validoptions:
|
|
|
|
Usage(argv[0], 'optimizer-options')
|
|
|
|
sys.stderr.write(u"\nError: Unrecognized"
|
2017-01-10 17:36:24 -07:00
|
|
|
u" optimizer option: %s\n" % chg.decode('utf8'))
|
2016-12-25 13:34:42 -07:00
|
|
|
return 1
|
|
|
|
if chgfix[0] == '-':
|
|
|
|
options.discard(chgfix[1:])
|
2015-03-14 15:17:15 -07:00
|
|
|
else:
|
2016-12-25 13:34:42 -07:00
|
|
|
options.add(chgfix[1:])
|
|
|
|
del chgfix
|
2015-03-14 15:17:15 -07:00
|
|
|
|
|
|
|
elif opt in ('-h', '--help'):
|
2016-12-24 17:12:17 -07:00
|
|
|
Usage(argv[0])
|
2015-03-14 15:17:15 -07:00
|
|
|
return 0
|
|
|
|
|
2015-03-14 22:18:55 -07:00
|
|
|
elif opt == '--version':
|
2015-03-15 12:28:53 -07:00
|
|
|
sys.stdout.write('LSL PyOptimizer version %s\n' % VERSION)
|
2015-03-14 15:17:15 -07:00
|
|
|
return 0
|
|
|
|
|
|
|
|
elif opt in ('-o', '--output'):
|
|
|
|
outfile = arg
|
2015-03-14 22:18:55 -07:00
|
|
|
|
2017-04-28 14:43:15 -07:00
|
|
|
elif opt in ('-b', '--builtins'):
|
|
|
|
builtins = arg
|
|
|
|
|
2017-10-21 01:00:31 -07:00
|
|
|
elif opt in ('-L', '--libdata'):
|
|
|
|
libdata = arg
|
2017-04-28 14:43:15 -07:00
|
|
|
|
2017-01-11 17:55:52 -07:00
|
|
|
elif opt in ('-y', '--python-exceptions'):
|
|
|
|
raise_exception = True
|
|
|
|
|
2015-03-14 22:18:55 -07:00
|
|
|
elif opt in ('-p', '--preproc'):
|
|
|
|
preproc = arg.lower()
|
2016-12-25 12:47:54 -07:00
|
|
|
supported = ('ext', 'mcpp', 'gcpp', 'none')
|
|
|
|
if preproc not in supported:
|
2016-12-24 17:12:17 -07:00
|
|
|
Usage(argv[0])
|
2016-12-25 12:47:54 -07:00
|
|
|
sys.stderr.write(u"\nUnknown --preproc option: '%s'."
|
|
|
|
u" Only '%s' supported.\n"
|
|
|
|
% (preproc, u"', '".join(supported)))
|
2015-03-14 22:18:55 -07:00
|
|
|
return 1
|
|
|
|
|
2015-03-18 20:11:29 -07:00
|
|
|
if preproc == 'gcpp':
|
2017-11-26 05:34:37 -07:00
|
|
|
preproc_command = 'cpp'
|
2015-03-18 20:11:29 -07:00
|
|
|
|
|
|
|
elif preproc == 'mcpp':
|
2017-11-26 05:34:37 -07:00
|
|
|
preproc_command = 'mcpp'
|
2015-03-15 12:28:53 -07:00
|
|
|
|
2015-03-18 20:11:29 -07:00
|
|
|
elif opt == '--precmd':
|
2017-11-26 05:34:37 -07:00
|
|
|
preproc_command = arg
|
2015-03-14 22:18:55 -07:00
|
|
|
|
|
|
|
elif opt in ('-P', '--prearg'):
|
2017-11-26 05:34:37 -07:00
|
|
|
preproc_user_args.append(arg)
|
2015-03-14 22:18:55 -07:00
|
|
|
|
2015-03-18 20:11:29 -07:00
|
|
|
elif opt == '--prenodef':
|
|
|
|
predefines = False
|
|
|
|
|
2015-05-05 17:45:37 -07:00
|
|
|
elif opt == '--preshow':
|
|
|
|
preshow = True
|
|
|
|
|
2015-03-14 22:18:55 -07:00
|
|
|
elif opt in ('-H', '--header'):
|
|
|
|
script_header = True
|
|
|
|
|
2015-12-25 11:04:24 -07:00
|
|
|
elif opt in ('-T', '--timestamp'):
|
|
|
|
script_timestamp = True
|
|
|
|
|
2015-03-14 22:18:55 -07:00
|
|
|
elif opt == '--avid':
|
|
|
|
avid = arg
|
|
|
|
|
|
|
|
elif opt == '--avname':
|
|
|
|
avname = arg
|
|
|
|
|
|
|
|
elif opt == '--assetid':
|
|
|
|
assetid = arg
|
|
|
|
|
|
|
|
elif opt == '--shortname':
|
|
|
|
shortname = arg
|
2017-11-19 11:48:35 -07:00
|
|
|
|
|
|
|
elif opt == '--prettify':
|
|
|
|
prettify = True
|
2018-05-11 13:37:09 -07:00
|
|
|
|
|
|
|
elif opt == '--bom':
|
|
|
|
bom = True
|
2015-03-14 15:17:15 -07:00
|
|
|
del opts
|
|
|
|
|
2017-11-19 11:48:35 -07:00
|
|
|
if prettify:
|
|
|
|
options &= set(('rsrclimit',))
|
|
|
|
options.add('prettify')
|
|
|
|
|
2017-01-11 17:55:52 -07:00
|
|
|
try:
|
2017-01-03 21:08:09 -07:00
|
|
|
|
2017-05-05 11:37:38 -07:00
|
|
|
if 'rsrclimit' in options:
|
|
|
|
import resource
|
|
|
|
resource.setrlimit(resource.RLIMIT_CPU, (5, 5))
|
|
|
|
resource.setrlimit(resource.RLIMIT_STACK, (393216, 393216))
|
|
|
|
resource.setrlimit(resource.RLIMIT_DATA, (4096, 4096))
|
|
|
|
resource.setrlimit(resource.RLIMIT_AS, (20001000, 20001000))
|
|
|
|
|
2017-01-11 17:55:52 -07:00
|
|
|
if 'lso' in options:
|
|
|
|
lslopt.lslcommon.LSO = True
|
|
|
|
options.remove('lso')
|
2017-01-03 21:08:09 -07:00
|
|
|
|
2017-01-11 17:55:52 -07:00
|
|
|
if 'expr' in options:
|
|
|
|
lslopt.lslcommon.IsCalc = True
|
|
|
|
options.remove('expr')
|
2016-12-25 12:43:24 -07:00
|
|
|
|
2017-01-11 17:55:52 -07:00
|
|
|
if 'help' in options:
|
|
|
|
Usage(argv[0], 'optimizer-options')
|
|
|
|
return 0
|
2015-03-14 15:17:15 -07:00
|
|
|
|
2017-01-11 17:55:52 -07:00
|
|
|
fname = args[0] if args else None
|
|
|
|
if fname is None:
|
|
|
|
Usage(argv[0])
|
|
|
|
sys.stderr.write(u"\nError: Input file not specified. Use -"
|
|
|
|
u" if you want to use stdin.\n")
|
2016-12-25 12:29:43 -07:00
|
|
|
return 1
|
|
|
|
|
2017-01-11 17:55:52 -07:00
|
|
|
del args
|
|
|
|
|
|
|
|
script = ''
|
|
|
|
if fname == '-':
|
|
|
|
script = sys.stdin.read()
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
f = open(fname, 'r')
|
|
|
|
except IOError as e:
|
|
|
|
if e.errno == 2:
|
|
|
|
sys.stderr.write('Error: File not found: %s\n' % fname)
|
|
|
|
return 2
|
|
|
|
raise
|
|
|
|
try:
|
|
|
|
script = f.read()
|
|
|
|
finally:
|
|
|
|
f.close()
|
|
|
|
del f
|
|
|
|
|
|
|
|
if script_header:
|
|
|
|
script_header = ScriptHeader(script, avname)
|
|
|
|
|
|
|
|
if script_timestamp:
|
|
|
|
import time
|
|
|
|
tmp = time.time()
|
|
|
|
script_timestamp = time.strftime(
|
|
|
|
'// Generated on %Y-%m-%dT%H:%M:%S.{0:06d}Z\n'
|
|
|
|
.format(int(tmp % 1 * 1000000)), time.gmtime(tmp))
|
|
|
|
del tmp
|
|
|
|
|
|
|
|
if shortname == '':
|
|
|
|
shortname = os.path.basename(fname)
|
|
|
|
|
2017-11-26 05:34:37 -07:00
|
|
|
# Build preprocessor command line
|
2017-11-26 06:02:04 -07:00
|
|
|
preproc_cmdline = [preproc_command] + preproc_user_args
|
2017-01-11 17:55:52 -07:00
|
|
|
if predefines:
|
2017-11-26 05:34:37 -07:00
|
|
|
if preproc == 'gcpp':
|
|
|
|
preproc_cmdline += [
|
|
|
|
'-undef', '-x', 'c', '-std=c99', '-nostdinc',
|
|
|
|
'-trigraphs', '-dN', '-fno-extended-identifiers',
|
|
|
|
]
|
|
|
|
|
|
|
|
elif preproc == 'mcpp':
|
|
|
|
preproc_cmdline += [
|
|
|
|
'-e', 'UTF-8', '-I-', '-N', '-3', '-j',
|
|
|
|
'-V199901L',
|
|
|
|
]
|
|
|
|
|
|
|
|
preproc_cmdline += [
|
|
|
|
'-Dinteger(...)=((integer)(__VA_ARGS__))',
|
|
|
|
'-Dfloat(...)=((float)(__VA_ARGS__))',
|
|
|
|
'-Dstring(...)=((string)(__VA_ARGS__))',
|
|
|
|
'-Dkey(...)=((key)(__VA_ARGS__))',
|
|
|
|
'-Drotation(...)=((rotation)(__VA_ARGS__))',
|
|
|
|
'-Dquaternion(...)=((quaternion)(__VA_ARGS__))',
|
|
|
|
'-Dvector(...)=((vector)(__VA_ARGS__))',
|
|
|
|
'-Dlist(...)=((list)(__VA_ARGS__))',
|
|
|
|
]
|
|
|
|
preproc_cmdline.append('-D__AGENTKEY__="%s"' % avid)
|
|
|
|
preproc_cmdline.append('-D__AGENTID__="%s"' % avid)
|
2017-01-11 17:55:52 -07:00
|
|
|
preproc_cmdline.append('-D__AGENTIDRAW__=' + avid)
|
2017-11-26 05:34:37 -07:00
|
|
|
preproc_cmdline.append('-D__AGENTNAME__="%s"' % avname)
|
2017-01-11 17:55:52 -07:00
|
|
|
preproc_cmdline.append('-D__ASSETID__=' + assetid)
|
2017-11-26 05:34:37 -07:00
|
|
|
preproc_cmdline.append('-D__SHORTFILE__="%s"' % shortname)
|
2017-01-11 17:55:52 -07:00
|
|
|
preproc_cmdline.append('-D__OPTIMIZER__=LSL PyOptimizer')
|
|
|
|
preproc_cmdline.append('-D__OPTIMIZER_VERSION__=' + VERSION)
|
|
|
|
|
|
|
|
if type(script) is unicode:
|
|
|
|
script = script.encode('utf8')
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
# Try converting the script to Unicode, to report any encoding
|
|
|
|
# errors with accurate line information. At this point we don't
|
|
|
|
# need the result.
|
2017-10-10 20:04:13 -07:00
|
|
|
UniConvScript(script, options,
|
|
|
|
fname if fname != '-' else '<stdin>').to_unicode()
|
2017-01-11 17:55:52 -07:00
|
|
|
except EParse as e:
|
|
|
|
# We don't call ReportError to prevent problems due to
|
|
|
|
# displaying invalid UTF-8
|
|
|
|
sys.stderr.write(e.args[0] + u"\n")
|
|
|
|
return 1
|
|
|
|
|
|
|
|
if preproc != 'none':
|
|
|
|
# At this point, for the external preprocessor to work we need the
|
|
|
|
# script as a byte array, not as unicode, but it should be UTF-8.
|
2017-11-26 06:10:33 -07:00
|
|
|
script = PreparePreproc(script.decode('utf8')).encode('utf8')
|
2017-11-26 05:33:18 -07:00
|
|
|
if preproc == 'mcpp':
|
2017-01-11 17:55:52 -07:00
|
|
|
# As a special treatment for mcpp, we force it to output its
|
|
|
|
# macros so we can read if USE_xxx are defined. With GCC that
|
|
|
|
# is achieved with -dN, but mcpp has no command line option.
|
|
|
|
script += '\n#pragma MCPP put_defines\n'
|
|
|
|
|
|
|
|
# Invoke the external preprocessor
|
|
|
|
import subprocess
|
|
|
|
|
|
|
|
p = subprocess.Popen(preproc_cmdline, stdin=subprocess.PIPE,
|
|
|
|
stdout=subprocess.PIPE)
|
|
|
|
script = p.communicate(input=script)[0]
|
|
|
|
status = p.wait()
|
|
|
|
if status:
|
|
|
|
return status
|
|
|
|
del p, status
|
|
|
|
|
|
|
|
# This method is very imperfect, in several senses. However, since
|
|
|
|
# it's applied to the output of the preprocessor, all of the
|
|
|
|
# concerns should be addressed:
|
|
|
|
# - \s includes \n, but \n should not be allowed.
|
|
|
|
# - Comments preceding the directive should not cause problems.
|
|
|
|
# e.g.: /* test */ #directive
|
|
|
|
# - #directive within a comment or string should be ignored.
|
|
|
|
for x in re.findall(r'(?:(?<=\n)|^)\s*#\s*define\s+('
|
|
|
|
r'USE_SWITCHES'
|
|
|
|
r'|USE_LAZY_LISTS'
|
|
|
|
r')(?:$|[^A-Za-z0-9_])', script, re.S):
|
|
|
|
if x == 'USE_SWITCHES':
|
|
|
|
options.add('enableswitch')
|
|
|
|
elif x == 'USE_LAZY_LISTS':
|
|
|
|
options.add('lazylists')
|
|
|
|
|
|
|
|
if not preshow:
|
|
|
|
|
2017-10-21 01:00:31 -07:00
|
|
|
lib = lslopt.lslloadlib.LoadLibrary(builtins, libdata)
|
2017-10-20 07:26:05 -07:00
|
|
|
p = parser(lib)
|
2017-01-11 17:55:52 -07:00
|
|
|
try:
|
2017-10-10 20:04:13 -07:00
|
|
|
ts = p.parse(script, options,
|
|
|
|
fname if fname != '-' else '<stdin>')
|
2017-01-11 17:55:52 -07:00
|
|
|
except EParse as e:
|
|
|
|
ReportError(script, e)
|
|
|
|
return 1
|
|
|
|
del p, script
|
|
|
|
|
2017-10-27 12:13:07 -07:00
|
|
|
opt = optimizer(lib)
|
2017-01-11 17:55:52 -07:00
|
|
|
ts = opt.optimize(ts, options)
|
|
|
|
del opt
|
|
|
|
|
|
|
|
outs = outscript()
|
|
|
|
script = script_header + script_timestamp + outs.output(ts, options)
|
|
|
|
del outs, ts
|
|
|
|
|
|
|
|
del script_header, script_timestamp
|
|
|
|
|
2018-05-11 13:37:09 -07:00
|
|
|
if bom:
|
|
|
|
script = b'\xEF\xBB\xBF' + script
|
|
|
|
|
2017-01-11 17:55:52 -07:00
|
|
|
if outfile == '-':
|
|
|
|
sys.stdout.write(script)
|
|
|
|
else:
|
|
|
|
outf = open(outfile, 'w')
|
|
|
|
try:
|
|
|
|
outf.write(script)
|
|
|
|
finally:
|
|
|
|
outf.close()
|
|
|
|
return 0
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
if raise_exception:
|
|
|
|
raise
|
|
|
|
sys.stderr.write(e.__class__.__name__ + ': ' + str(e) + '\n')
|
|
|
|
return 1
|
2014-07-25 17:43:44 -07:00
|
|
|
|
2015-03-14 15:17:15 -07:00
|
|
|
if __name__ == '__main__':
|
2016-12-24 17:12:17 -07:00
|
|
|
ret = main(sys.argv)
|
2015-03-14 15:17:15 -07:00
|
|
|
if ret:
|
|
|
|
sys.exit(ret)
|