Commit e446c076 authored by Poul-Henning Kamp's avatar Poul-Henning Kamp

More restructuring

parent 98388b66
...@@ -45,8 +45,6 @@ import random ...@@ -45,8 +45,6 @@ import random
import copy import copy
import json import json
strict_abi = True
AMBOILERPLATE = ''' AMBOILERPLATE = '''
# Boilerplate generated by vmodtool.py - changes will be overwritten # Boilerplate generated by vmodtool.py - changes will be overwritten
...@@ -166,9 +164,18 @@ def lwrap(s, width=64): ...@@ -166,9 +164,18 @@ def lwrap(s, width=64):
p = " " p = " "
if len(s) > 0: if len(s) > 0:
ll.append(p + s) ll.append(p + s)
return ll return "\n".join(ll) + "\n"
def fmt_cstruct(fo, mn, x):
"""
Align fields in C struct
"""
a = "\ttd_" + mn + "_" + x
while len(a.expandtabs()) < 40:
a += "\t"
fo.write("%s*%s;\n" % (a, x))
####################################################################### #######################################################################
...@@ -184,21 +191,11 @@ def err(str, warn=True): ...@@ -184,21 +191,11 @@ def err(str, warn=True):
else: else:
print("WARNING: " + str, file=sys.stderr) print("WARNING: " + str, file=sys.stderr)
def fmt_cstruct(fo, mn, x):
a = "\ttd_" + mn + "_" + x
while len(a.expandtabs()) < 40:
a += "\t"
fo.write("%s*%s;\n" % (a, x))
####################################################################### #######################################################################
enum_values = {}
class ctype(object): class ctype(object):
def __init__(self, wl): def __init__(self, wl, enums):
self.nm = None self.nm = None
self.defval = None self.defval = None
self.spec = None self.spec = None
...@@ -210,7 +207,7 @@ class ctype(object): ...@@ -210,7 +207,7 @@ class ctype(object):
if len(wl) > 0 and wl[0] == "{": if len(wl) > 0 and wl[0] == "{":
if self.vt != "ENUM": if self.vt != "ENUM":
err("Only ENUMs take {...} specs", warn=False) err("Only ENUMs take {...} specs", warn=False)
self.add_spec(wl) self.add_spec(wl, enums)
def __str__(self): def __str__(self):
s = "<" + self.vt s = "<" + self.vt
...@@ -222,15 +219,7 @@ class ctype(object): ...@@ -222,15 +219,7 @@ class ctype(object):
s += " SPEC=" + str(self.spec) s += " SPEC=" + str(self.spec)
return s + ">" return s + ">"
def set_defval(self, x): def add_spec(self, wl, enums):
if self.vt == "ENUM":
if x[0] == '"' and x[-1] == '"':
x = x[1:-1]
elif x[0] == "'" and x[-1] == "'":
x = x[1:-1]
self.defval = x
def add_spec(self, wl):
assert self.vt == "ENUM" assert self.vt == "ENUM"
assert wl.pop(0) == "{" assert wl.pop(0) == "{"
self.spec = [] self.spec = []
...@@ -242,7 +231,7 @@ class ctype(object): ...@@ -242,7 +231,7 @@ class ctype(object):
x = x[1:-1] x = x[1:-1]
assert len(x) > 0 assert len(x) > 0
self.spec.append(x) self.spec.append(x)
enum_values[x] = True enums[x] = True
w = wl.pop(0) w = wl.pop(0)
if w == "}": if w == "}":
break break
...@@ -261,10 +250,48 @@ class ctype(object): ...@@ -261,10 +250,48 @@ class ctype(object):
return self.vt return self.vt
def json(self, jl): def json(self, jl):
jl.append([self.vt, self.nm, self.defval, self.spec]) jl.append([self.vt])
while jl[-1][-1] is None: while jl[-1][-1] is None:
jl[-1].pop(-1) jl[-1].pop(-1)
#######################################################################
class arg(ctype):
def __init__(self, wl, argnames, enums, end):
super(arg, self).__init__(wl, enums)
if wl[0] == end:
return
x = wl.pop(0)
if x in argnames:
err("Duplicate argument name '%s'" % x, warn=False)
argnames[x] = True
self.nm = x
if wl[0] == end:
return
x = wl.pop(0)
if x != "=":
err("Expected '=' got '%s'" % x, warn=False)
x = wl.pop(0)
if self.vt == "ENUM":
if x[0] == '"' and x[-1] == '"':
x = x[1:-1]
elif x[0] == "'" and x[-1] == "'":
x = x[1:-1]
self.defval = x
def json(self, jl):
jl.append([self.vt, self.nm, self.defval, self.spec])
while jl[-1][-1] is None:
jl[-1].pop(-1)
#######################################################################
def lex(l): def lex(l):
wl = [] wl = []
...@@ -309,65 +336,49 @@ def lex(l): ...@@ -309,65 +336,49 @@ def lex(l):
err("Syntax error at char", i, "'%s'" % c, warn=False) err("Syntax error at char", i, "'%s'" % c, warn=False)
return wl return wl
#######################################################################
class prototype(object): class prototype(object):
def __init__(self, st, retval=True, prefix=""): def __init__(self, st, retval=True, prefix=""):
global inputline
self.st = st self.st = st
self.obj = None self.obj = None
inputline = st.line[1] self.args = []
wl = lex(st.line[1]) wl = lex(st.line[1])
if retval: if retval:
self.retval = ctype(wl) self.retval = ctype(wl, st.vcc.enums)
else:
self.retval = ctype(['VOID'], st.vcc.enums)
self.bname = wl.pop(0) self.bname = wl.pop(0)
if not re.match("^[a-zA-Z.][a-zA-Z0-9_]*$", self.bname): if not re.match("^[a-zA-Z.][a-zA-Z0-9_]*$", self.bname):
err("%s(): Illegal name\n" % self.nname, warn=False) err("%s(): Illegal name\n" % self.nname, warn=False)
self.prefix = prefix self.name = prefix + self.bname
self.name = self.prefix + self.bname
self.vcc = st.vcc
if not re.match('^[a-zA-Z_][a-zA-Z0-9_]*$', self.cname()): if not re.match('^[a-zA-Z_][a-zA-Z0-9_]*$', self.cname()):
err("%s(): Illegal C-name\n" % self.cname(), warn=False) err("%s(): Illegal C-name\n" % self.cname(), warn=False)
x = wl.pop(0) if len(wl) == 2 and wl[0] == '(' and wl[1] == ')':
if x != "(": return
err("Syntax error: Expected '(', got '%s'" % x, warn=False)
x = wl.pop(-1) if wl[0] != "(":
if x != ")": err("Syntax error: Expected '(', got '%s'" % wl[0], warn=False)
err("Syntax error: Expected ')', got '%s'" % x, warn=False) wl[0] = ','
if wl[-1] != ")":
err("Syntax error: Expected ')', got '%s'" % wl[-1], warn=False)
wl[-1] = ','
names = {} names = {}
self.args = []
while len(wl) > 0: while len(wl) > 0:
t = ctype(wl)
self.args.append(t)
if not len(wl):
break
x = wl.pop(0) x = wl.pop(0)
if x == ",": if x != ',':
continue err("Expected ',' found '%s'" % x, warn=False)
if x in names: if len(wl) == 0:
err("%s(): Duplicate argument name" % x, warn=False)
names[x] = True
t.nm = x
if not len(wl):
break
if wl[0] == "=":
wl.pop(0)
t.set_defval(wl.pop(0))
if not len(wl):
break break
assert wl.pop(0) == "," t = arg(wl, names, st.vcc.enums, ',')
inputline = None self.args.append(t)
def cname(self, pfx=False):
r = self.name.replace(".", "_")
if pfx:
return self.vcc.sympfx + r
return r
def vcl_proto(self, short, pfx=""): def vcl_proto(self, short, pfx=""):
if type(self.st) == s_method: if type(self.st) == s_method:
...@@ -420,28 +431,39 @@ class prototype(object): ...@@ -420,28 +431,39 @@ class prototype(object):
fo.write(self.vcl_proto(True, pfx=" ") + "\n") fo.write(self.vcl_proto(True, pfx=" ") + "\n")
fo.write(" \n") fo.write(" \n")
def c_ret(self): def cname(self, pfx=False):
return self.retval.ct r = self.name.replace(".", "_")
if pfx:
return self.st.vcc.sympfx + r
return r
def c_args(self, a=[]): def proto(self, args, name):
ll = list(a) s = self.retval.ct + " " + name + '('
ll = args
for i in self.args: for i in self.args:
ll.append(i.ct) ll.append(i.ct)
return ", ".join(ll) s += ", ".join(ll)
return s + ');'
def c_fn(self, args=[], h=False):
s = fn = '' def typedef(self, args):
if not h: tn = 'td_' + self.st.vcc.modname + '_' + self.cname()
s += 'typedef ' return "typedef " + self.proto(args, name=tn)
fn += 'td_' + self.vcc.modname + '_'
fn += self.cname(pfx=h) def cstuff(self, args, where):
s += '%s %s(%s);' % (self.c_ret(), fn, self.c_args(args)) if where == 'h':
return "\n".join(lwrap(s)) + "\n" s = self.proto(args, self.cname(True))
elif where == 'c':
s = self.typedef(args)
elif where == 'o':
s = self.typedef(args)
else:
assert False
return lwrap(s)
def json(self, jl, cfunc): def json(self, jl, cfunc):
ll = [] ll = []
self.retval.json(ll) self.retval.json(ll)
ll.append('Vmod_%s_Func.%s' % (self.vcc.modname, cfunc)) ll.append('Vmod_%s_Func.%s' % (self.st.vcc.modname, cfunc))
for i in self.args: for i in self.args:
i.json(ll) i.json(ll)
jl.append(ll) jl.append(ll)
...@@ -492,13 +514,10 @@ class stanza(object): ...@@ -492,13 +514,10 @@ class stanza(object):
if self.proto is not None: if self.proto is not None:
self.proto.synopsis(fo, man) self.proto.synopsis(fo, man)
def hfile(self, fo): def cstuff(self, fo, where):
return return
def cstruct(self, fo): def cstruct(self, fo, define):
return
def cstruct_init(self, fo):
return return
def json(self, jl): def json(self, jl):
...@@ -566,11 +585,10 @@ class s_module(stanza): ...@@ -566,11 +585,10 @@ class s_module(stanza):
class s_abi(stanza): class s_abi(stanza):
def parse(self): def parse(self):
global strict_abi
if self.line[1] not in ('strict', 'vrt'): if self.line[1] not in ('strict', 'vrt'):
err("Valid ABI types are 'strict' or 'vrt', got '%s'\n" % err("Valid ABI types are 'strict' or 'vrt', got '%s'\n" %
self.line[1]) self.line[1])
strict_abi = self.line[1] == 'strict' self.vcc.strict_abi = self.line[1] == 'strict'
self.vcc.contents.append(self) self.vcc.contents.append(self)
...@@ -590,14 +608,15 @@ class s_event(stanza): ...@@ -590,14 +608,15 @@ class s_event(stanza):
err("Not emitting .RST for $Event %s\n" % err("Not emitting .RST for $Event %s\n" %
self.event_func) self.event_func)
def hfile(self, fo): def cstuff(self, fo, where):
fo.write("vmod_event_f %s;\n" % self.event_func) if where == 'h':
fo.write("vmod_event_f %s;\n" % self.event_func)
def cstruct(self, fo):
fo.write("\tvmod_event_f\t\t\t*_event;\n")
def cstruct_init(self, fo): def cstruct(self, fo, define):
fo.write("\t%s,\n" % self.event_func) if define:
fo.write("\tvmod_event_f\t\t\t*_event;\n")
else:
fo.write("\t%s,\n" % self.event_func)
def json(self, jl): def json(self, jl):
jl.append([ jl.append([
...@@ -612,17 +631,15 @@ class s_function(stanza): ...@@ -612,17 +631,15 @@ class s_function(stanza):
self.rstlbl = "func_" + self.proto.name self.rstlbl = "func_" + self.proto.name
self.vcc.contents.append(self) self.vcc.contents.append(self)
def hfile(self, fo): def cstuff(self, fo, where):
fo.write(self.proto.c_fn(['VRT_CTX'], True)) if where in ('h', 'c'):
fo.write(self.proto.cstuff(['VRT_CTX'], where))
def cfile(self, fo):
fo.write(self.proto.c_fn(['VRT_CTX']))
def cstruct(self, fo): def cstruct(self, fo, define):
fmt_cstruct(fo, self.vcc.modname, self.proto.cname()) if define:
fmt_cstruct(fo, self.vcc.modname, self.proto.cname())
def cstruct_init(self, fo): else:
fo.write("\t" + self.proto.cname(pfx=True) + ",\n") fo.write("\t" + self.proto.cname(pfx=True) + ",\n")
def json(self, jl): def json(self, jl):
jl.append(["$FUNC", "%s" % self.proto.name]) jl.append(["$FUNC", "%s" % self.proto.name])
...@@ -632,7 +649,6 @@ class s_function(stanza): ...@@ -632,7 +649,6 @@ class s_function(stanza):
class s_object(stanza): class s_object(stanza):
def parse(self): def parse(self):
self.proto = prototype(self, retval=False) self.proto = prototype(self, retval=False)
self.proto.retval = ctype(['VOID'])
self.proto.obj = "x" + self.proto.name self.proto.obj = "x" + self.proto.name
self.init = copy.copy(self.proto) self.init = copy.copy(self.proto)
...@@ -662,35 +678,27 @@ class s_object(stanza): ...@@ -662,35 +678,27 @@ class s_object(stanza):
for i in self.methods: for i in self.methods:
i.proto.synopsis(fo, man) i.proto.synopsis(fo, man)
def chfile(self, fo, h): def cstuff(self, fo, w):
sn = self.vcc.sympfx + self.vcc.modname + "_" + self.proto.name sn = self.vcc.sympfx + self.vcc.modname + "_" + self.proto.name
fo.write("struct %s;\n" % sn) fo.write("struct %s;\n" % sn)
fo.write(self.init.c_fn( fo.write(self.init.cstuff(
['VRT_CTX', 'struct %s **' % sn, 'const char *'], h)) ['VRT_CTX', 'struct %s **' % sn, 'const char *'], w))
fo.write(self.fini.c_fn(['struct %s **' % sn], h)) fo.write(self.fini.cstuff(['struct %s **' % sn], w))
for i in self.methods: for i in self.methods:
fo.write(i.proto.c_fn(['VRT_CTX', 'struct %s *' % sn], h)) fo.write(i.proto.cstuff(['VRT_CTX', 'struct %s *' % sn], w))
fo.write("\n") fo.write("\n")
def hfile(self, fo): def cstruct(self, fo, define):
self.chfile(fo, True) if define:
fmt_cstruct(fo, self.vcc.modname, self.init.name)
def cfile(self, fo): fmt_cstruct(fo, self.vcc.modname, self.fini.name)
self.chfile(fo, False) else:
p = "\t" + self.vcc.sympfx
def cstruct(self, fo): fo.write(p + self.init.name + ",\n")
fmt_cstruct(fo, self.vcc.modname, self.init.name) fo.write(p + self.fini.name + ",\n")
fmt_cstruct(fo, self.vcc.modname, self.fini.name)
for i in self.methods:
i.cstruct(fo)
def cstruct_init(self, fo):
p = "\t" + self.vcc.sympfx
fo.write(p + self.init.name + ",\n")
fo.write(p + self.fini.name + ",\n")
for i in self.methods: for i in self.methods:
i.cstruct_init(fo) i.cstruct(fo, define)
fo.write("\n") fo.write("\n")
def json(self, jl): def json(self, jl):
...@@ -719,6 +727,8 @@ class s_object(stanza): ...@@ -719,6 +727,8 @@ class s_object(stanza):
for i in self.methods: for i in self.methods:
i.dump() i.dump()
#######################################################################
class s_method(stanza): class s_method(stanza):
def parse(self): def parse(self):
...@@ -730,11 +740,11 @@ class s_method(stanza): ...@@ -730,11 +740,11 @@ class s_method(stanza):
self.rstlbl = "func_" + self.proto.name self.rstlbl = "func_" + self.proto.name
p.methods.append(self) p.methods.append(self)
def cstruct(self, fo): def cstruct(self, fo, define):
fmt_cstruct(fo, self.vcc.modname, self.proto.cname()) if define:
fmt_cstruct(fo, self.vcc.modname, self.proto.cname())
def cstruct_init(self, fo): else:
fo.write('\t' + self.proto.cname(pfx=True) + ",\n") fo.write('\t' + self.proto.cname(pfx=True) + ",\n")
def json(self, jl): def json(self, jl):
jl.append(["$METHOD", self.proto.name[len(self.pfx)+1:]]) jl.append(["$METHOD", self.proto.name[len(self.pfx)+1:]])
...@@ -763,6 +773,8 @@ class vcc(object): ...@@ -763,6 +773,8 @@ class vcc(object):
self.contents = [] self.contents = []
self.commit_files = [] self.commit_files = []
self.copyright = "" self.copyright = ""
self.enums = {}
self.strict_abi = True
def openfile(self, fn): def openfile(self, fn):
self.commit_files.append(fn) self.commit_files.append(fn)
...@@ -773,6 +785,7 @@ class vcc(object): ...@@ -773,6 +785,7 @@ class vcc(object):
os.rename(i + ".tmp", i) os.rename(i + ".tmp", i)
def parse(self): def parse(self):
global inputline
a = "\n" + open(self.inputfile, "r").read() a = "\n" + open(self.inputfile, "r").read()
s = a.split("\n$") s = a.split("\n$")
self.copyright = s.pop(0).strip() self.copyright = s.pop(0).strip()
...@@ -783,11 +796,13 @@ class vcc(object): ...@@ -783,11 +796,13 @@ class vcc(object):
i += 1 i += 1
else: else:
i = len(ss) i = len(ss)
inputline = ss[:i]
c = ss[:i].split() c = ss[:i].split()
m = dispatch.get(c[0]) m = dispatch.get(c[0])
if m is None: if m is None:
err("Unknown stanze $%s" % ss[:i]) err("Unknown stanze $%s" % ss[:i])
m([c[0], " ".join(c[1:])], ss[i:].split('\n'), self) m([c[0], " ".join(c[1:])], ss[i:].split('\n'), self)
inputline = None
def rst_copyright(self, fo): def rst_copyright(self, fo):
write_rst_hdr(fo, "COPYRIGHT", "=") write_rst_hdr(fo, "COPYRIGHT", "=")
...@@ -818,8 +833,7 @@ class vcc(object): ...@@ -818,8 +833,7 @@ class vcc(object):
fo.close() fo.close()
def amboilerplate(self): def amboilerplate(self):
fn = "automake_boilerplate.am" fo = self.openfile("automake_boilerplate.am")
fo = self.openfile(fn)
fo.write(AMBOILERPLATE.replace("XXX", self.modname)) fo.write(AMBOILERPLATE.replace("XXX", self.modname))
fo.close() fo.close()
...@@ -835,30 +849,28 @@ class vcc(object): ...@@ -835,30 +849,28 @@ class vcc(object):
fo.write("#endif\n") fo.write("#endif\n")
fo.write("\n") fo.write("\n")
for j in sorted(enum_values): for j in sorted(self.enums):
fo.write("extern VCL_ENUM %senum_%s;\n" % (self.sympfx, j)) fo.write("extern VCL_ENUM %senum_%s;\n" % (self.sympfx, j))
fo.write("\n") fo.write("\n")
for j in self.contents: for j in self.contents:
j.hfile(fo) j.cstuff(fo, 'h')
fo.close() fo.close()
def cstruct(self, fo, csn): def cstruct(self, fo, csn):
fo.write("\n%s {\n" % csn) fo.write("\n%s {\n" % csn)
for j in self.contents: for j in self.contents:
j.cstruct(fo) j.cstruct(fo, True)
fo.write("\n") for j in sorted(self.enums):
for j in sorted(enum_values):
fo.write("\tVCL_ENUM\t\t\t*enum_%s;\n" % j) fo.write("\tVCL_ENUM\t\t\t*enum_%s;\n" % j)
fo.write("};\n") fo.write("};\n")
def cstruct_init(self, fo, csn): def cstruct_init(self, fo, csn):
fo.write("\nstatic const %s Vmod_Func = {\n" % csn) fo.write("\nstatic const %s Vmod_Func = {\n" % csn)
for j in self.contents: for j in self.contents:
j.cstruct_init(fo) j.cstruct(fo, False)
fo.write("\n") fo.write("\n")
for j in sorted(enum_values): for j in sorted(self.enums):
fo.write("\t&%senum_%s,\n" % (self.sympfx, j)) fo.write("\t&%senum_%s,\n" % (self.sympfx, j))
fo.write("};\n") fo.write("};\n")
...@@ -887,14 +899,13 @@ class vcc(object): ...@@ -887,14 +899,13 @@ class vcc(object):
fo.write(j + "\n") fo.write(j + "\n")
fo.write("\n") fo.write("\n")
def api(self, fo): def vmod_data(self, fo):
vmd = "Vmod_%s_Data" % self.modname
for i in (714, 759, 765): for i in (714, 759, 765):
fo.write("\n/*lint -esym(%d, Vmod_%s_Data) */\n" % fo.write("\n/*lint -esym(%d, %s) */\n" % (i, vmd))
(i, self.modname)) fo.write("\nextern const struct vmod_data %s;\n" % vmd)
fo.write("\nextern const struct vmod_data Vmod_%s_Data;\n" % fo.write("\nconst struct vmod_data %s = {\n" % vmd)
(self.modname)) if self.strict_abi:
fo.write("\nconst struct vmod_data Vmod_%s_Data = {\n" % self.modname)
if strict_abi:
fo.write("\t.vrt_major =\t0,\n") fo.write("\t.vrt_major =\t0,\n")
fo.write("\t.vrt_minor =\t0,\n") fo.write("\t.vrt_minor =\t0,\n")
else: else:
...@@ -917,59 +928,55 @@ class vcc(object): ...@@ -917,59 +928,55 @@ class vcc(object):
fo.write("};\n") fo.write("};\n")
def cfile(self): def cfile(self):
fn = self.pfx + ".c" fno = self.pfx + ".c"
fo = self.openfile(fn) fo = self.openfile(fno)
write_c_file_warning(fo) fnx = fno + ".tmp2"
fx = open(fnx, "w")
fn2 = fn + ".tmp2" write_c_file_warning(fo)
fo.write('#include "config.h"\n') fo.write('#include "config.h"\n')
fo.write('#include <stdio.h>\n') fo.write('#include <stdio.h>\n')
for i in ["vdef", "vrt", self.pfx, "vmod_abi"]: for i in ["vdef", "vrt", self.pfx, "vmod_abi"]:
fo.write('#include "%s.h"\n' % i) fo.write('#include "%s.h"\n' % i)
fo.write("\n") fo.write("\n")
for j in sorted(enum_values): for j in sorted(self.enums):
fo.write('VCL_ENUM %senum_%s = "%s";\n' % (self.sympfx, j, j)) fo.write('VCL_ENUM %senum_%s = "%s";\n' % (self.sympfx, j, j))
fo.write("\n") fo.write("\n")
fx = open(fn2, "w")
for i in self.contents: for i in self.contents:
if type(i) == s_object: if type(i) == s_object:
i.cfile(fo) i.cstuff(fo, 'c')
i.cfile(fx) i.cstuff(fx, 'c')
fx.write("/* Functions */\n") fx.write("/* Functions */\n")
for i in self.contents: for i in self.contents:
if type(i) == s_function: if type(i) == s_function:
i.cfile(fo) i.cstuff(fo, 'c')
i.cfile(fx) i.cstuff(fx, 'c')
csn = "Vmod_%s_Func" % self.modname csn = "Vmod_%s_Func" % self.modname
scsn = "struct " + csn
self.cstruct(fo, "struct " + csn) self.cstruct(fo, scsn)
self.cstruct(fx, scsn)
self.cstruct(fx, "struct " + csn)
fo.write("\n/*lint -esym(754, Vmod_" + self.modname + "_Func::*) */\n") fo.write("\n/*lint -esym(754, " + csn + "::*) */\n")
self.cstruct_init(fo, "struct " + csn) self.cstruct_init(fo, scsn)
fx.close() fx.close()
fo.write("\nstatic const char Vmod_Proto[] =\n") fo.write("\nstatic const char Vmod_Proto[] =\n")
fi = open(fn2) for i in open(fnx):
for i in fi:
fo.write('\t"%s\\n"\n' % i.rstrip()) fo.write('\t"%s\\n"\n' % i.rstrip())
fi.close()
fo.write('\t"static struct %s %s;";\n' % (csn, csn)) fo.write('\t"static struct %s %s;";\n' % (csn, csn))
os.remove(fn2) os.remove(fnx)
self.json(fo) self.json(fo)
self.api(fo) self.vmod_data(fo)
fo.close() fo.close()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment