Cannot understand error message

B

Bill Davy

The following code produces an error message (using Idle with Py 2.4 and
2.5). "There's an error in your program: EOL while scanning single-quoted
string". It comes just after "s = ''" (put there to try and isolate the
broken string).

It would be good if the error message pointed me to the start of said single
quoted string.

The colouring in IDLE does not indicate a bad string.

Puzzled.

Bill


#

# The traceback module is used to provide a stack trace to

# show the user where the error occured. See Error().

#

import traceback

#

# The math module is used to convert numbers between the Python real format

# and the Keil real format. See KeilToFloatingPoint() and FloatingToKeil().

#

import math



LOAD_LIT = 1

LOAD_REG = 1

STORE_REG = 1

ADD_LIT_FP = 2 + 8

ADD_LIT_INT = 2 + 16

ADD_REG_FP = 2 + 32

ADD_REG_INT = 9

SUB_LIT_FP = 11

SUB_LIT_INT = 12

SUB_REG_FP = 13

SUB_REG_INT =14

MUL_LIT_FP = 11

MUL_LIT_INT = 12

MUL_REG_FP = 13

MUL_REG_INT =14

DIV_LIT_FP = 11

DIV_LIT_INT = 12

DIV_REG_FP = 13

DIV_REG_INT =14

AND_LIT_INT = 12

AND_REG_INT =14

OR_LIT_INT = 12

OR_REG_INT =14

NEGATE_FP = 11

NEGATE_INT = 12

ABSOLUTE_FP = 13

ABSOLUTE_INT = 14

INVERT_INT = 15

JUMP_OPCODE = 15

JLT_OPCODE = 15

JGT_OPCODE = 15

JLE_OPCODE = 15

JGE_OPCODE = 15

JEQ_OPCODE = 15

JNE_OPCODE = 15



BinaryOps={

"LOAD":{float:{"L":LOAD_LIT,"R":LOAD_REG},int:{"L":LOAD_LIT,"R":LOAD_REG}},

"STORE":{float:{"R":STORE_REG},int:{"R":STORE_REG}},

"ADD":{float:{"L":ADD_LIT_FP,"R":ADD_REG_FP},int:{"L":ADD_LIT_INT,"R":ADD_REG_INT}},

"SUB":{float:{"L":SUB_LIT_FP,"R":SUB_REG_FP},int:{"L":SUB_LIT_INT,"R":SUB_REG_INT}},

"MUL":{float:{"L":MUL_LIT_FP,"R":MUL_REG_FP},int:{"L":MUL_LIT_INT,"R":MUL_REG_INT}},

"DIV":{float:{"L":DIV_LIT_FP,"R":DIV_REG_FP},int:{"L":DIV_LIT_INT,"R":DIV_REG_INT}},

"AND":{int:{"L":AND_LIT_INT,"R":AND_REG_INT}},

"OR":{int:{"L":OR_LIT_INT,"R":OR_REG_INT}}

}

UnaryOps={

"NEGATE":{float:NEGATE_FP, int:NEGATE_INT},

"ABSOLUTE":{float:ABSOLUTE_FP, int:ABSOLUTE_INT},

"INVERT":{int:INVERT_INT}

}



JumpOps={

"JUMP":JUMP_OPCODE,

"JLT":JLT_OPCODE,

"JGT":JGT_OPCODE,

"JLE":JLE_OPCODE,

"JGE":JGE_OPCODE,

"JEQ":JEQ_OPCODE,

"JNE":JNE_OPCODE

}

def IsOpCode(s):

if ( s in BinaryOps ): return True;

if ( s in UnaryOps ): return True;

if ( s in JumpOps ): return True;

return False

class Register:

"""

This class provides us with a register (say) 0..32

In addtion to a number, a register can be given a name.

It allows LOAD(arg) and other opcodes to distinguish between

references to a register and a literal value.

"""

def __init__(self,Id,Name=None):

self.Number = Id

if ( Name == None):

self.Name = "R%d" % Id

else:

self.Name = Name

def RegisterNumber(self):

return self.Number



def RegisterName(self):

return self.Name

R0=Register(0)

R1=Register(1)

R2=Register(2)

Now=Register(2,"Now")

def IsRegister(arg):

return hasattr( arg, "RegisterNumber")

assert not IsRegister(0)

assert not IsRegister(1.2)

assert IsRegister(R1)

assert IsRegister(Now)

#

# ErrorCount is global as it is shared by all slaves.

#

ErrorCount = 0

def Error(Message):

"""

work back through the traceback until you find a function whose name is
in one of the

opcode dictionaries and trace back from there. This will keep internal

implemenataion functions private but still allow the suer to define
functions

that generate code.

"""

global ErrorCount

ErrorCount += 1



"""

[

('<string>', 1, '?', None),

('C:\\Python24\\lib\\idlelib\\run.py', 90, 'main', 'ret = method(*args,
**kwargs)'),

('C:\\Python24\\lib\\idlelib\\run.py', 283, 'runcode', 'exec code in
self.locals'),

('H:\\Husky Experiments\\Viper1\\tmp.py', 434, '?', 'STORE(1)'),

('H:\\Husky Experiments\\Viper1\\tmp.py', 147, 'STORE',
'self.BINOP("STORE",Arg,Msg)'),

('H:\\Husky Experiments\\Viper1\\tmp.py', 198, 'BINOP', 'return
Error("Cannot perform %s on %s" % (Op,Arg))'),

('H:\\Husky Experiments\\Viper1\\tmp.py', 106, 'Error', 'tb =
traceback.extract_stack()')

]

"""

tb = traceback.extract_stack()

BeforePrinting = True

IsPrinting = False

for i in range(len(tb)-1,0,-1):

frame = tb

if ( BeforePrinting ):

# Looking to start

if IsOpCode(frame[2]): # start printing

IsPrinting = True

BeforePrinting = False

print "John: %s\nTrace back follows:" % Message

elif ( IsPrinting ):

print '\tFile "%s", line %u, %s' % (frame[0], frame[1],
frame[3])

# Stop when we find the curious function "?"

if (frame[2] == "?"):

break

if BeforePrinting:

print "John: %s (no trace back)" % Message



class Slave:

"This is a slave class"

Listing = ""

Number = 0

Mode = 0; # Will be int or float when defined

def __init__(self,Id):

self.Number = Id

self.Line = 0

#

# The listing, built as we go along

#

self.Listing = ""

self.Mode = None

def SetMode(self, arg):

self.Mode = arg



def LOAD(self,Arg,Msg=""):

self.BINOP("LOAD",Arg,Msg)

def STORE(self,Arg,Msg=""):

self.BINOP("STORE",Arg,Msg)

def ADD(self,Arg,Msg=""):

self.BINOP("ADD",Arg,Msg)

def SUB(self,Arg,Msg=""):

self.BINOP("SUB",Arg,Msg)

def MUL(self,Arg,Msg=""):

self.BINOP("MUL",Arg,Msg)

def DIV(self,Arg,Msg=""):

self.BINOP("DIV",Arg,Msg)

def AND(self,Arg,Msg=""):

self.BINOP("AND",Arg,Msg)

def OR(self,Arg,Msg=""):

selfBINOPOP("OR",Arg,Msg)



def NEGATE(self,Msg=""):

self.UNOP("NEGATE",Msg)

def ABSOLUTE(self,Msg=""):

self.UNOP("ABSOLUTE",Msg)

def INVERT(self,Msg=""):

self.UNOP("INVERT",Msg)



def JUMP(self,Arg,Msg=""):

self.JUMPOP("JUMP",Arg,Msg)

def JLT(self,Arg,Msg=""):

self.JUMPOP("JLT",Arg,Msg)

def JGT(self,Arg,Msg=""):

self.JUMPOP("JGT",Arg,Msg)

def JLE(self,Arg,Msg=""):

self.JUMPOP("JLE",Arg,Msg)

def JGE(self,Arg,Msg=""):

self.JUMPOP("JGE",Arg,Msg)

def JEQ(self,Arg,Msg=""):

self.JUMPOP("JEQ",Arg,Msg)

def JNE(self,Arg,Msg=""):

self.JUMPOP("JNE",Arg,Msg)



def BINOP(self,Op,Arg,Msg):

entry = BinaryOps.get(Op)

assert entry != None, "Who gave me %s to look up?" % op



entry = entry.get(self.Mode)

if entry == None :

return Error("Cannot perform %s on %s, mode=%s" % (

Op,Arg,self.Mode))

if ( IsRegister(Arg) ):

ot = entry.get("R") # Register

if ot == None:

return Error("Cannot perform %s on %s" % (Op,Arg))

self.Listing += "%x \t \t%s\t%s\t# %s\n" % (

len(self.Memory), Op,Arg.RegisterName(), Msg)

self.Memory.append(ot)

self.Memory.append(Arg.RegisterNumber())

else:

ot = entry.get("L") # Literal

if ot == None:

return Error("Cannot perform %s on %s" % (Op,Arg))



self.Listing += "%x \t \t%s\t%s\t# %s\n" % (

len(self.Memory), Op, Arg, Msg)

self.Memory.append(ot)

cArg = self.Mode(Arg)

if self.Mode == int:

try:

iArg = int(Arg)

except TypeError:

return Error("Argument %r is not an integer" % Arg)

except:

return Error("Unexpected error:", sys.exc_info()[0])

if ( iArg != Arg ):

return Error("Argument %r is not an integer" % Arg)

self.Memory.append((iArg >> 24) & 0xFF)

self.Memory.append((iArg >> 16) & 0xFF)

self.Memory.append((iArg >> 8) & 0xFF)

self.Memory.append((iArg >> 0) & 0xFF)

elif self.Mode == float:

try:

fArg = float(Arg)

except TypeError:

return Error("Argument %r is not a float" % Arg)

except:

return Error("Unexpected error:", sys.exc_info()[0])

if ( fArg != Arg ):

return Error("Argument %r is not a float" % Arg)

FourBytes = FloatingToKeil(fArg)

self.Memory.append(FourBytes[0])

self.Memory.append(FourBytes[1])

self.Memory.append(FourBytes[2])

self.Memory.append(FourBytes[3])

else:

return Error("No or bad mode (%r)set for slave %u" % (

self.Mode,self.SlaveNumber))



def JUMPOP(self,Op,Name,Msg):

entry = JumpOps.get(Op)

assert entry != None, "Who gave me %s to look up?" % op



"""

Generate code for a jump to the given target

May enter target into the table.

"""

self.Listing += "%x \t \t%s\t%s\t# %s\n" % (

len(self.Memory),Op,Name, Msg)

self.Memory.append(entry)

self.AddReference(Name,len(self.Memory)) # for patching up later

self.Memory.append(0) # Space for destination of jump

#

# The memory image of this Slave.

#

Memory = []



def ListProgram(self):

"""Generate a user friendly listing of the program for this Slave.

""""

s = ''



print "%s" % self.Number

print "%s\n" % ("#" * 64)

print "%s" % self.Listing

pass



def EmitProgram(self):

"""Emit a C code data structure of the program for this Slave.

"""

s=\

"%s\n/* C data structure for slave %d */\n"\

"struct\n"\

"\t{\n"\

"\tunsigned SlaveId;\n"\

"\tunsigned nBytes;\n"\

"\tunsigned char[%d];\n"\

"\t} Slave%d=\n"\

"\t{%u,%u\n"\

"\t{\n\t"\

% ('#'*64, self.Number, len(self.Memory), self.Number, self.Number,
len(self.Memory))

for i in range(0,len(self.Memory)):

if (i>0) : s+= ", "

s += "0x%x" % (self.Memory)

if ( (i % 16) == 15 ):

s += '\n\t'

s+="\n\t}\n\t};\n"

print s;

#

# Dictionary of Labels

# Maps Name onto (Set of Locations, List of references))

# Users define a label using Label(Name)

# Users reference a label using Jump(Name)

#

LabelTable = {}



def AddReference(self,Name,Reference):

"""

Add a reference to the given Name.

Name may not yet be defined

"""

Entry = self.LabelTable.get(Name)

if ( Entry == None ):

self.LabelTable[Name] = [ [] , [Reference] ]

else:

Entry[1].append(Reference)

def AddDefinition(self,Name,Location):

"""

Add a definition to the given Name.

Name may not yet be defined.

If it is defined, it may not be given a different Location.

"""

Entry = self.LabelTable.get(Name)

if ( Entry == None ):

# Add location to a new name

self.LabelTable[Name] = [ [Location] , [] ]

elif len(Entry[0]) == 0:

# Provide an initial location for a known name

Entry[0].append(Location)

elif Entry[0].count(Location) == 1:

# The same location, harmless but odd

pass

else:

# This is an additional definition but that does not matter

# unless it is used and we find that out when we call
FixUpLabels()

# at the end.

Entry[0].append(Location)

def Label(self, Name, Msg=""):

"""

Lays down a label for the user.

"""

self.AddDefinition(Name,len(self.Memory))

self.Listing += "%x \t%s: \t \t \t# %s\n" % (len(self.Memory), Name,
Msg)

def EmitLabelTable(self):

print "%s\nSymbol table for Slave %u" % ('#' * 64, self.Number)

for Name in self.LabelTable.keys():

s = "Name=%s" % Name

entry = self.LabelTable[Name]

if ( len(entry[0])==1 and len(entry[1]) == 0):

s += ", is not referenced"

if ( len(entry[0])==0 and len(entry[1]) > 0):

s += ", is not defined"

if ( len(entry[0])>1 and len(entry[1]) > 0):

s += ", is multiply-defined"

if ( len(entry[0]) > 0):

s += ", Location:"

for i in entry[0]:

s += " %x" % i

if ( len(entry[1]) > 0 ):

s += ", references:"

for i in range(0,len(entry[1])):

s += " %x" % entry[1]

print s

def FixUpLabels(self):

print "%s\nFixing labels for Slave %u" % ('#' * 64, self.Number)

for Name in self.LabelTable.keys():

entry = self.LabelTable[Name]

if ( len(entry[0])==1 and len(entry[1]) == 0):

print "Warning: %s is not referenced" % Name

elif ( len(entry[0])==0 and len(entry[1]) > 0):

Error("%s is not defined" % Name)

elif ( len(entry[0])>1 and len(entry[1]) > 0):

Error("%s is multiply-defined" % Name)

else:

for i in range(0,len(entry[1])):

self.Memory[entry[1]] = entry[0][0]

#

# Construct a vector of Slaves

#

S=[(Slave(i)) for i in range(6)]

def SetSlave(New):

"""

Make global functions generate code for a specified slave.

"""

#

# This sets the mode for the assembler and applies in the order of

# assembly, not in the execution order. The mode is embedded in the
opcode.

#

global SetMode

SetMode = S[New].SetMode

#

# Labels are local to a slave.

#

global Label

Label = S[New].Label;

global LOAD, STORE, ADD, SUB, MUL, DIV, AND, OR

global NEGATE, ABSOLUTE, INVERT

global JUMP, JLT, JGT, JLE, JGE, JEQ, JNE

LOAD = S[New].LOAD

JUMP = S[New].JUMP

LOAD = S[New].LOAD

STORE = S[New].STORE

ADD = S[New].ADD

SUB = S[New].SUB

MUL = S[New].MUL

DIV = S[New].DIV

AND = S[New].AND

OR = S[New].OR

NEGATE = S[New].NEGATE

ABSOLUTE = S[New].ABSOLUTE

INVERT = S[New].INVERT

JUMP = S[New].JUMP

JLT = S[New].JLT

JGT = S[New].JGT

JLE = S[New].JLE

JGE = S[New].JGE

JEQ = S[New].JEQ

JNE = S[New].JNE

print "hi"

SetSlave(1)

SetMode(int)

LOAD(4,"Into S1 hopefully")

Label("Loop")

LOAD(R1,"Comment")

JUMP("Loop")

LOAD(Now)

JUMP("Loop")

STORE(1)

Label("Loop")

class TemporaryLabelClass:

DefaultBaseName = "TL_"

Instances = {DefaultBaseName:0}



def __init__(self, BaseName=None):

de = self.Instances.get(BaseName)

if de == None:

self.Instances[BaseName] = 0 # new temporary BaseName

else:

self.Instances[BaseName] += 1 # using an existing BaseName again

self.BaseName = BaseName;



def Instance(self):

return self.Instances[self.BaseName]



def TemporaryLabel(BaseName=None):

t = TemporaryLabelClass(BaseName)

if ( BaseName == None ):

BaseName = t.DefaultBaseName

elif ( BaseName == t.DefaultBaseName):

return Error("Do not call TemporaryLabel(%s)" % t.DefaultBaseName)

return "%s%u" % (BaseName,t.Instance())

a = TemporaryLabel()

b = TemporaryLabel("MY")

c = TemporaryLabel()

d = TemporaryLabel("MY")

e = TemporaryLabel()

f = TemporaryLabel("TL_")

del a, b, c, d, e, f

def BusyIdleLoop(Time, Register):

label = TemporaryLabel()

LOAD(Now)

ADD(Time)

STORE(Register)

Label(label)

LOAD(Now)

SUB(Register)

JLT(label)

BusyIdleLoop(5000,R0)

S[1].FixUpLabels()

S[1].EmitProgram()

S[1].EmitLabelTable()

S[1].ListProgram()

def KeilToFloatingPoint(Bytes):

#print Bytes

temp = ((((((Bytes[0]<<8)+Bytes[1])<<8)+Bytes[2])<<8)+Bytes[3])

if ( temp == 0):

return 0.0

Mantissa = 0x800000 + (temp&0x7fffff)

if ( temp & 0x80000000 ):

Mantissa = -Mantissa

temp -= 0x80000000

Exponent = (0xFF & (temp>>23)) - 127;

Mantissa = float(Mantissa) / (1<<23)

#print "Mantissa=%f" % Mantissa

#print "Exponent=%x" % Exponent

return math.ldexp(Mantissa, Exponent)

def FloatingToKeil(arg):

"""

The flooating point format used by Keil follows

the IEEE-754 standard

The fields are (from MSB to LSB, and first byte to last):

S (one bit) 1 is posirtive, 0 is negative

E (eight bits) Exponent + 127

M (23 bits) The twenty four bit (MSB omitted) mantissa

"""

Mantissa, Exponent = math.frexp( arg )

Mantissa *= 2

Exponent -= 1

if ( Mantissa >= 0 ):

if ( Exponent == 0 ):

return (0,0,0,0)

S = 0

else:

S = 1

Mantissa = -Mantissa

Mantissa *= (1<<23)

Mantissa = int(Mantissa + 0.5) # round

if ( Mantissa >> 24 ):

# Renormalise

Mantissa >>= 1

Exponent += 1

assert ( (Mantissa >> 24) == 0)

Mantissa -= (1<<23) # Remove the "assumed" 1 before the binary point

assert ( (Mantissa >> 23) == 0 )

Exponent += 127

if ( Exponent < 0 ):

# Number is too small to represent; call it zero

FourBytes = 0,0,0,0

temp = KeilToFloatingPoint(FourBytes)

print "Truncating %r to %r" % (arg, temp)

elif ( Exponent > 0xFF):

# Number is too big to represent; make the maximum with the right
sign

FourBytes = (S<<7)+0x7F,0xFF,0xFF,0xFF

temp = KeilToFloatingPoint(FourBytes)

print "Truncating %r to %r" % (arg, temp)

else:

temp = (S<<31) + (Exponent<<23) + Mantissa

FourBytes = ((temp>>24),0xFF&(temp>>16),0xFF&(temp>>8),0xFF&(temp))

temp = KeilToFloatingPoint(FourBytes)

return FourBytes





FloatingToKeil(-12.5)

FloatingToKeil(-1e-200) ## should become zero

FloatingToKeil(-1e+200) ## should saturate

FloatingToKeil(math.ldexp((1<<24)-1,-24)) ## Exact conversion

FloatingToKeil(math.ldexp((1<<25)-1,-25)) ## Should make renormalise happen

print "Byeee"
 
C

Chris

It doesn't like all that text in the previous one...

Just before s = '' you have 4 double quotes to close to doc-string
instead of 3.
 
S

Steven D'Aprano

The following code produces an error message (using Idle with Py 2.4 and
2.5). "There's an error in your program: EOL while scanning
single-quoted string". It comes just after "s = ''" (put there to try
and isolate the broken string).

It would be good if the error message pointed me to the start of said
single quoted string.

You know what would also be good? If you didn't expect people to read
over 500 lines of code to find YOUR problem, because you can't be
bothered to isolate the error.

When you have a problem, send the SMALLEST piece of code the exhibits the
problem, not the entire application. You can't rely on Chris (who
apparently has no life, no offense intended Chris) being around all the
time.
 
C

Chris

You know what would also be good? If you didn't expect people to read
over 500 lines of code to find YOUR problem, because you can't be
bothered to isolate the error.

When you have a problem, send the SMALLEST piece of code the exhibits the
problem, not the entire application. You can't rely on Chris (who
apparently has no life, no offense intended Chris) being around all the
time.

Hehe, no offense taken Steven. :p
 
B

Bruno Desthuilliers

Steven D'Aprano a écrit :
You know what would also be good? If you didn't expect people to read
over 500 lines of code to find YOUR problem, because you can't be
bothered to isolate the error.

When you have a problem, send the SMALLEST piece of code the exhibits the
problem, not the entire application.

And *please* post the *full* traceback.
 
D

Dennis Lee Bieber

The colouring in IDLE does not indicate a bad string.
I don't use IDLE, but PythonWin's editor identified it quite easily
after I pasted this into it -- just required me to scroll down until I
found a line with "open string" coloring.

SciTE also highlighted the bad line.
SPE highlighted it too (actually, it almost looks like all three are
using the same "editer" core)

OTOH: PyDev/Eclipse did NOT highlight the bad line...
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
B

Bill Davy

Chris said:
It doesn't like all that text in the previous one...

Just before s = '' you have 4 double quotes to close to doc-string
instead of 3.


Doh.

I had put in the s = '' to see if I could force IDLE to say something more,
and perhaps if I had put in "" I would have done better.

Apologies to anyone who imagined I expected them to read 500 lines of my
novice Python.

Interesting that some colourers work better than others. It must be quite a
challenge.

IDLE did not offer a full traceback just a pop-up. If it had, I would
either have soved the problem or posted it. I'm not afraid of long (boring)
posts.

Many thanks for the help. Must get a bigger screen and new eyes.

Bill
 
B

Bruno Desthuilliers

Bill Davy a écrit :
(snip)
Doh.
(snip)

Interesting that some colourers work better than others. It must be quite a
challenge.

IDLE did not offer a full traceback just a pop-up.

Mmm... That sure is bad. Isn't there an option to get a better behaviour?

Anyway, just trying to run your script from the command-line, or import
it from the (command-line) Python shell would have get you the full
traceback.
If it had, I would
either have soved the problem or posted it. I'm not afraid of long (boring)
posts.

Many thanks for the help. Must get a bigger screen and new eyes.

Or a better code editor.
 
B

Bruno Desthuilliers

Dennis Lee Bieber a écrit :
I don't use IDLE, but PythonWin's editor identified it quite easily
after I pasted this into it -- just required me to scroll down until I
found a line with "open string" coloring.

SciTE also highlighted the bad line.
SPE highlighted it too (actually, it almost looks like all three are
using the same "editer" core)

That's quite possible - SciTE started as a demo of the Scintilla code
editor widget, which is quite powerfull and widely used.
OTOH: PyDev/Eclipse did NOT highlight the bad line...

<mode="troll">
That's what you get for using some 800pounds/2GB gorilla...
</mode>
 
B

Bill Davy

Bruno Desthuilliers said:
Bill Davy a écrit :
(snip)

Mmm... That sure is bad. Isn't there an option to get a better behaviour?

Anyway, just trying to run your script from the command-line, or import it
from the (command-line) Python shell would have get you the full
traceback.


Just tried importing it into the Python window of IDLE and I do indeed get a
caret for the initial (unmatched) '"'. Something I shall watch out for and
use in future.
Many thanks.
Or a better code editor.

I just grabbed what was on the shelf. Is there a Python plug-in for MSVC?
;-)

Bill
 
D

Dennis Lee Bieber

<mode="troll">
That's what you get for using some 800pounds/2GB gorilla...
</mode>

The only thing I really use Eclipse for is to burn network time
about once a quarter, when I run the "update" function on it <G> {Can't
they at least put in a time-out and default host for those modules that
are available in multiple servers rather than insist I sit there to hit
"okay"}

--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,961
Messages
2,570,130
Members
46,689
Latest member
liammiller

Latest Threads

Top