Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
C
capnproto
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
capnproto
Commits
f0877237
Commit
f0877237
authored
Feb 16, 2013
by
Kenton Varda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Bunch of little things.
parent
2e3f671c
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
146 additions
and
31 deletions
+146
-31
capnproto-compiler.cabal
compiler/capnproto-compiler.cabal
+2
-1
Compiler.hs
compiler/src/Compiler.hs
+0
-0
Grammar.hs
compiler/src/Grammar.hs
+14
-2
Lexer.hs
compiler/src/Lexer.hs
+1
-0
Main.hs
compiler/src/Main.hs
+23
-2
Parser.hs
compiler/src/Parser.hs
+57
-22
Semantics.hs
compiler/src/Semantics.hs
+11
-3
Token.hs
compiler/src/Token.hs
+8
-1
Util.hs
compiler/src/Util.hs
+30
-0
No files found.
compiler/capnproto-compiler.cabal
View file @
f0877237
...
@@ -19,5 +19,6 @@ executable capnproto-compiler
...
@@ -19,5 +19,6 @@ executable capnproto-compiler
Grammar,
Grammar,
Parser,
Parser,
Compiler,
Compiler,
Semantics
Semantics,
Util
compiler/src/Compiler.hs
View file @
f0877237
This diff is collapsed.
Click to expand it.
compiler/src/Grammar.hs
View file @
f0877237
...
@@ -39,8 +39,9 @@ data FieldValue = VoidFieldValue
...
@@ -39,8 +39,9 @@ data FieldValue = VoidFieldValue
|
IntegerFieldValue
Integer
|
IntegerFieldValue
Integer
|
FloatFieldValue
Double
|
FloatFieldValue
Double
|
StringFieldValue
String
|
StringFieldValue
String
|
ArrayFieldValue
[
FieldValue
]
|
IdentifierFieldValue
String
|
RecordFieldValue
[(
String
,
FieldValue
)]
|
ListFieldValue
[
Located
FieldValue
]
|
RecordFieldValue
[(
Located
String
,
Located
FieldValue
)]
deriving
(
Show
)
deriving
(
Show
)
data
Declaration
=
AliasDecl
(
Located
String
)
DeclName
data
Declaration
=
AliasDecl
(
Located
String
)
DeclName
...
@@ -56,3 +57,14 @@ data Declaration = AliasDecl (Located String) DeclName
...
@@ -56,3 +57,14 @@ data Declaration = AliasDecl (Located String) DeclName
TypeExpression
[
Declaration
]
TypeExpression
[
Declaration
]
|
OptionDecl
DeclName
(
Located
FieldValue
)
|
OptionDecl
DeclName
(
Located
FieldValue
)
deriving
(
Show
)
deriving
(
Show
)
declarationName
::
Declaration
->
Maybe
(
Located
String
)
declarationName
(
AliasDecl
n
_
)
=
Just
n
declarationName
(
ConstantDecl
n
_
_
)
=
Just
n
declarationName
(
EnumDecl
n
_
)
=
Just
n
declarationName
(
EnumValueDecl
n
_
_
)
=
Just
n
declarationName
(
StructDecl
n
_
)
=
Just
n
declarationName
(
FieldDecl
n
_
_
_
_
)
=
Just
n
declarationName
(
InterfaceDecl
n
_
)
=
Just
n
declarationName
(
MethodDecl
n
_
_
_
_
)
=
Just
n
declarationName
(
OptionDecl
_
_
)
=
Nothing
compiler/src/Lexer.hs
View file @
f0877237
...
@@ -86,6 +86,7 @@ token = keyword
...
@@ -86,6 +86,7 @@ token = keyword
<|>
liftM
(
const
Colon
)
(
symbol
":"
)
<|>
liftM
(
const
Colon
)
(
symbol
":"
)
<|>
liftM
(
const
Period
)
(
symbol
"."
)
<|>
liftM
(
const
Period
)
(
symbol
"."
)
<|>
liftM
(
const
EqualsSign
)
(
symbol
"="
)
<|>
liftM
(
const
EqualsSign
)
(
symbol
"="
)
<|>
liftM
(
const
MinusSign
)
(
symbol
"-"
)
<?>
"token"
<?>
"token"
locatedToken
=
located
token
locatedToken
=
located
token
...
...
compiler/src/Main.hs
View file @
f0877237
...
@@ -25,6 +25,10 @@ module Main ( main ) where
...
@@ -25,6 +25,10 @@ module Main ( main ) where
import
System.Environment
import
System.Environment
import
Compiler
import
Compiler
import
Util
(
delimit
)
import
Text.Parsec.Pos
import
Text.Parsec.Error
import
Text.Printf
(
printf
)
main
::
IO
()
main
::
IO
()
main
=
do
main
=
do
...
@@ -35,5 +39,22 @@ handleFile filename = do
...
@@ -35,5 +39,22 @@ handleFile filename = do
text
<-
readFile
filename
text
<-
readFile
filename
case
parseAndCompileFile
filename
text
of
case
parseAndCompileFile
filename
text
of
Active
desc
[]
->
print
desc
Active
desc
[]
->
print
desc
Active
_
e
->
mapM_
print
e
Active
_
e
->
mapM_
printError
e
Failed
e
->
mapM_
print
e
Failed
e
->
mapM_
printError
e
--printError e = mapM_ printMessage (errorMessages e) where
-- pos = errorPos e
-- f = sourceName pos
-- l = sourceLine pos
-- c = sourceColumn pos
-- printMessage :: Message -> IO ()
-- printMessage m = printf "%s:%d:%d: %s\n" f l c (messageString m)
printError
e
=
printf
"%s:%d:%d: %s
\n
"
f
l
c
m'
where
pos
=
errorPos
e
f
=
sourceName
pos
l
=
sourceLine
pos
c
=
sourceColumn
pos
m
=
showErrorMessages
"or"
"Unknown parse error"
"Expected"
"Unexpected"
"end of expression"
(
errorMessages
e
)
m'
=
delimit
"; "
(
lines
m
)
compiler/src/Parser.hs
View file @
f0877237
...
@@ -25,12 +25,31 @@ module Parser (parseFile) where
...
@@ -25,12 +25,31 @@ module Parser (parseFile) where
import
Text.Parsec
hiding
(
tokens
)
import
Text.Parsec
hiding
(
tokens
)
import
Token
import
Token
import
Control.Monad
(
liftM
)
import
Grammar
import
Grammar
import
Lexer
(
lexer
)
import
Lexer
(
lexer
)
import
Control.Monad.Identity
tokenParser
::
(
Located
Token
->
Maybe
a
)
->
Parsec
[
Located
Token
]
u
a
tokenParser
::
(
Located
Token
->
Maybe
a
)
->
Parsec
[
Located
Token
]
u
a
tokenParser
=
token
(
show
.
locatedValue
)
locatedPos
tokenParser
=
token
(
tokenErrorString
.
locatedValue
)
locatedPos
tokenErrorString
(
Identifier
s
)
=
"identifier
\"
"
++
s
++
"
\"
"
tokenErrorString
(
ParenthesizedList
_
)
=
"parenthesized list"
tokenErrorString
(
BracketedList
_
)
=
"bracketed list"
tokenErrorString
(
LiteralInt
i
)
=
"integer literal "
++
show
i
tokenErrorString
(
LiteralFloat
f
)
=
"float literal "
++
show
f
tokenErrorString
(
LiteralString
s
)
=
"string literal "
++
show
s
tokenErrorString
AtSign
=
"
\"
@
\"
"
tokenErrorString
Colon
=
"
\"
:
\"
"
tokenErrorString
Period
=
"
\"
.
\"
"
tokenErrorString
EqualsSign
=
"
\"
=
\"
"
tokenErrorString
MinusSign
=
"
\"
-
\"
"
tokenErrorString
ImportKeyword
=
"
\"
import
\"
"
tokenErrorString
UsingKeyword
=
"
\"
using
\"
"
tokenErrorString
ConstKeyword
=
"
\"
const
\"
"
tokenErrorString
EnumKeyword
=
"
\"
enum
\"
"
tokenErrorString
StructKeyword
=
"
\"
struct
\"
"
tokenErrorString
InterfaceKeyword
=
"
\"
interface
\"
"
tokenErrorString
OptionKeyword
=
"
\"
option
\"
"
type
TokenParser
=
Parsec
[
Located
Token
]
[
ParseError
]
type
TokenParser
=
Parsec
[
Located
Token
]
[
ParseError
]
...
@@ -49,22 +68,23 @@ matchLiteralFloat t = case locatedValue t of { (LiteralFloat v) -> Jus
...
@@ -49,22 +68,23 @@ matchLiteralFloat t = case locatedValue t of { (LiteralFloat v) -> Jus
matchLiteralString
t
=
case
locatedValue
t
of
{
(
LiteralString
v
)
->
Just
v
;
_
->
Nothing
}
matchLiteralString
t
=
case
locatedValue
t
of
{
(
LiteralString
v
)
->
Just
v
;
_
->
Nothing
}
matchSimpleToken
expected
t
=
if
locatedValue
t
==
expected
then
Just
()
else
Nothing
matchSimpleToken
expected
t
=
if
locatedValue
t
==
expected
then
Just
()
else
Nothing
identifier
=
tokenParser
matchIdentifier
identifier
=
tokenParser
matchIdentifier
<?>
"identifier"
literalInt
=
tokenParser
matchLiteralInt
literalInt
=
tokenParser
matchLiteralInt
<?>
"integer"
literalFloat
=
tokenParser
matchLiteralFloat
literalFloat
=
tokenParser
matchLiteralFloat
<?>
"floating-point number"
literalString
=
tokenParser
matchLiteralString
literalString
=
tokenParser
matchLiteralString
<?>
"string"
atSign
=
tokenParser
(
matchSimpleToken
AtSign
)
atSign
=
tokenParser
(
matchSimpleToken
AtSign
)
<?>
"
\"
@
\"
"
colon
=
tokenParser
(
matchSimpleToken
Colon
)
colon
=
tokenParser
(
matchSimpleToken
Colon
)
<?>
"
\"
:
\"
"
period
=
tokenParser
(
matchSimpleToken
Period
)
period
=
tokenParser
(
matchSimpleToken
Period
)
<?>
"
\"
.
\"
"
equalsSign
=
tokenParser
(
matchSimpleToken
EqualsSign
)
equalsSign
=
tokenParser
(
matchSimpleToken
EqualsSign
)
<?>
"
\"
=
\"
"
importKeyword
=
tokenParser
(
matchSimpleToken
ImportKeyword
)
minusSign
=
tokenParser
(
matchSimpleToken
MinusSign
)
<?>
"
\"
=
\"
"
usingKeyword
=
tokenParser
(
matchSimpleToken
UsingKeyword
)
importKeyword
=
tokenParser
(
matchSimpleToken
ImportKeyword
)
<?>
"
\"
import
\"
"
constKeyword
=
tokenParser
(
matchSimpleToken
ConstKeyword
)
usingKeyword
=
tokenParser
(
matchSimpleToken
UsingKeyword
)
<?>
"
\"
using
\"
"
enumKeyword
=
tokenParser
(
matchSimpleToken
EnumKeyword
)
constKeyword
=
tokenParser
(
matchSimpleToken
ConstKeyword
)
<?>
"
\"
const
\"
"
structKeyword
=
tokenParser
(
matchSimpleToken
StructKeyword
)
enumKeyword
=
tokenParser
(
matchSimpleToken
EnumKeyword
)
<?>
"
\"
enum
\"
"
interfaceKeyword
=
tokenParser
(
matchSimpleToken
InterfaceKeyword
)
structKeyword
=
tokenParser
(
matchSimpleToken
StructKeyword
)
<?>
"
\"
struct
\"
"
optionKeyword
=
tokenParser
(
matchSimpleToken
OptionKeyword
)
interfaceKeyword
=
tokenParser
(
matchSimpleToken
InterfaceKeyword
)
<?>
"
\"
interface
\"
"
optionKeyword
=
tokenParser
(
matchSimpleToken
OptionKeyword
)
<?>
"
\"
option
\"
"
parenthesizedList
parser
=
do
parenthesizedList
parser
=
do
items
<-
tokenParser
matchParenthesizedList
items
<-
tokenParser
matchParenthesizedList
...
@@ -155,16 +175,22 @@ fieldDecl statements = do
...
@@ -155,16 +175,22 @@ fieldDecl statements = do
children
<-
parseBlock
fieldLine
statements
children
<-
parseBlock
fieldLine
statements
return
(
FieldDecl
name
ordinal
t
value
children
)
return
(
FieldDecl
name
ordinal
t
value
children
)
negativeFieldValue
=
liftM
(
IntegerFieldValue
.
negate
)
literalInt
<|>
liftM
(
FloatFieldValue
.
negate
)
literalFloat
fieldValue
=
liftM
IntegerFieldValue
literalInt
fieldValue
=
liftM
IntegerFieldValue
literalInt
<|>
liftM
FloatFieldValue
literalFloat
<|>
liftM
FloatFieldValue
literalFloat
<|>
liftM
StringFieldValue
literalString
<|>
liftM
StringFieldValue
literalString
<|>
liftM
ArrayFieldValue
(
bracketedList
fieldValue
)
<|>
liftM
IdentifierFieldValue
identifier
<|>
liftM
ListFieldValue
(
bracketedList
(
located
fieldValue
))
<|>
liftM
RecordFieldValue
(
parenthesizedList
fieldAssignment
)
<|>
liftM
RecordFieldValue
(
parenthesizedList
fieldAssignment
)
<|>
(
minusSign
>>
negativeFieldValue
)
<?>
"default value"
fieldAssignment
=
do
fieldAssignment
=
do
name
<-
identifier
name
<-
located
identifier
equalsSign
equalsSign
value
<-
fieldValue
value
<-
located
fieldValue
return
(
name
,
value
)
return
(
name
,
value
)
fieldLine
::
Maybe
[
Located
Statement
]
->
TokenParser
Declaration
fieldLine
::
Maybe
[
Located
Statement
]
->
TokenParser
Declaration
...
@@ -186,6 +212,7 @@ methodDecl statements = do
...
@@ -186,6 +212,7 @@ methodDecl statements = do
atSign
atSign
ordinal
<-
located
literalInt
ordinal
<-
located
literalInt
params
<-
parenthesizedList
paramDecl
params
<-
parenthesizedList
paramDecl
colon
t
<-
typeExpression
t
<-
typeExpression
children
<-
parseBlock
methodLine
statements
children
<-
parseBlock
methodLine
statements
return
(
MethodDecl
name
ordinal
params
t
children
)
return
(
MethodDecl
name
ordinal
params
t
children
)
...
@@ -227,8 +254,16 @@ parseBlock parser statements = finish where
...
@@ -227,8 +254,16 @@ parseBlock parser statements = finish where
return
[
result
|
Right
(
result
,
_
)
<-
results
]
return
[
result
|
Right
(
result
,
_
)
<-
results
]
parseCollectingErrors
::
TokenParser
a
->
[
Located
Token
]
->
Either
ParseError
(
a
,
[
ParseError
])
parseCollectingErrors
::
TokenParser
a
->
[
Located
Token
]
->
Either
ParseError
(
a
,
[
ParseError
])
parseCollectingErrors
parser
=
runParser
parser'
[]
""
where
parseCollectingErrors
parser
tokens
=
runParser
parser'
[]
""
tokens
where
parser'
=
do
parser'
=
do
-- Work around Parsec bug: Text.Parsec.Print.token is supposed to produce a parser that
-- sets the position by using the provided function to extract it from each token. However,
-- it doesn't bother to call this function for the *first* token, only subsequent tokens.
-- The first token is always assumed to be at 1:1. To fix this, set it manually.
case
tokens
of
Located
pos
_
:
_
->
setPosition
pos
[]
->
return
()
result
<-
parser
result
<-
parser
eof
eof
errors
<-
getState
errors
<-
getState
...
...
compiler/src/Semantics.hs
View file @
f0877237
...
@@ -30,6 +30,7 @@ import Data.Word (Word8, Word16, Word32, Word64)
...
@@ -30,6 +30,7 @@ import Data.Word (Word8, Word16, Word32, Word64)
import
Data.Char
(
chr
)
import
Data.Char
(
chr
)
import
Text.Printf
(
printf
)
import
Text.Printf
(
printf
)
import
Control.Monad
(
join
)
import
Control.Monad
(
join
)
import
Util
(
delimit
)
type
ByteString
=
[
Word8
]
type
ByteString
=
[
Word8
]
...
@@ -81,7 +82,7 @@ data BuiltinType = BuiltinVoid | BuiltinBool
...
@@ -81,7 +82,7 @@ data BuiltinType = BuiltinVoid | BuiltinBool
|
BuiltinInt8
|
BuiltinInt16
|
BuiltinInt32
|
BuiltinInt64
|
BuiltinInt8
|
BuiltinInt16
|
BuiltinInt32
|
BuiltinInt64
|
BuiltinUInt8
|
BuiltinUInt16
|
BuiltinUInt32
|
BuiltinUInt64
|
BuiltinUInt8
|
BuiltinUInt16
|
BuiltinUInt32
|
BuiltinUInt64
|
BuiltinFloat32
|
BuiltinFloat64
|
BuiltinFloat32
|
BuiltinFloat64
|
BuiltinText
|
Builtin
Bytes
|
BuiltinText
|
Builtin
Data
deriving
(
Show
,
Enum
,
Bounded
,
Eq
)
deriving
(
Show
,
Enum
,
Bounded
,
Eq
)
builtinTypes
=
[
minBound
::
BuiltinType
..
maxBound
::
BuiltinType
]
builtinTypes
=
[
minBound
::
BuiltinType
..
maxBound
::
BuiltinType
]
...
@@ -103,7 +104,10 @@ data ValueDesc = VoidDesc
...
@@ -103,7 +104,10 @@ data ValueDesc = VoidDesc
|
Float32Desc
Float
|
Float32Desc
Float
|
Float64Desc
Double
|
Float64Desc
Double
|
TextDesc
String
|
TextDesc
String
|
BytesDesc
ByteString
|
DataDesc
ByteString
|
EnumValueValueDesc
EnumValueDesc
|
StructValueDesc
[(
FieldDesc
,
ValueDesc
)]
|
ListDesc
[
ValueDesc
]
deriving
(
Show
)
deriving
(
Show
)
valueString
VoidDesc
=
error
"Can't stringify void value."
valueString
VoidDesc
=
error
"Can't stringify void value."
...
@@ -119,7 +123,11 @@ valueString (UInt64Desc i) = show i
...
@@ -119,7 +123,11 @@ valueString (UInt64Desc i) = show i
valueString
(
Float32Desc
x
)
=
show
x
valueString
(
Float32Desc
x
)
=
show
x
valueString
(
Float64Desc
x
)
=
show
x
valueString
(
Float64Desc
x
)
=
show
x
valueString
(
TextDesc
s
)
=
show
s
valueString
(
TextDesc
s
)
=
show
s
valueString
(
BytesDesc
s
)
=
show
(
map
(
chr
.
fromIntegral
)
s
)
valueString
(
DataDesc
s
)
=
show
(
map
(
chr
.
fromIntegral
)
s
)
valueString
(
EnumValueValueDesc
v
)
=
enumValueName
v
valueString
(
StructValueDesc
l
)
=
"("
++
delimit
", "
(
map
assignmentString
l
)
++
")"
where
assignmentString
(
field
,
value
)
=
fieldName
field
++
" = "
++
valueString
value
valueString
(
ListDesc
l
)
=
"["
++
delimit
", "
(
map
valueString
l
)
++
"]"
where
data
TypeDesc
=
BuiltinType
BuiltinType
data
TypeDesc
=
BuiltinType
BuiltinType
|
EnumType
EnumDesc
|
EnumType
EnumDesc
...
...
compiler/src/Token.hs
View file @
f0877237
...
@@ -26,11 +26,17 @@ module Token where
...
@@ -26,11 +26,17 @@ module Token where
import
Text.Parsec.Pos
(
SourcePos
,
sourceLine
,
sourceColumn
)
import
Text.Parsec.Pos
(
SourcePos
,
sourceLine
,
sourceColumn
)
import
Text.Printf
(
printf
)
import
Text.Printf
(
printf
)
data
Located
t
=
Located
{
locatedPos
::
SourcePos
,
locatedValue
::
t
}
deriving
(
Eq
)
data
Located
t
=
Located
{
locatedPos
::
SourcePos
,
locatedValue
::
t
}
instance
Show
t
=>
Show
(
Located
t
)
where
instance
Show
t
=>
Show
(
Located
t
)
where
show
(
Located
pos
x
)
=
printf
"%d:%d:%s"
(
sourceLine
pos
)
(
sourceColumn
pos
)
(
show
x
)
show
(
Located
pos
x
)
=
printf
"%d:%d:%s"
(
sourceLine
pos
)
(
sourceColumn
pos
)
(
show
x
)
instance
Eq
a
=>
Eq
(
Located
a
)
where
Located
_
a
==
Located
_
b
=
a
==
b
instance
Ord
a
=>
Ord
(
Located
a
)
where
compare
(
Located
_
a
)
(
Located
_
b
)
=
compare
a
b
data
Token
=
Identifier
String
data
Token
=
Identifier
String
|
ParenthesizedList
[[
Located
Token
]]
|
ParenthesizedList
[[
Located
Token
]]
|
BracketedList
[[
Located
Token
]]
|
BracketedList
[[
Located
Token
]]
...
@@ -41,6 +47,7 @@ data Token = Identifier String
...
@@ -41,6 +47,7 @@ data Token = Identifier String
|
Colon
|
Colon
|
Period
|
Period
|
EqualsSign
|
EqualsSign
|
MinusSign
|
ImportKeyword
|
ImportKeyword
|
UsingKeyword
|
UsingKeyword
|
ConstKeyword
|
ConstKeyword
...
...
compiler/src/Util.hs
0 → 100644
View file @
f0877237
-- Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice, this
-- list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright notice,
-- this list of conditions and the following disclaimer in the documentation
-- and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-- ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module
Util
where
delimit
delimiter
list
=
concat
$
loop
list
where
loop
(
""
:
t
)
=
loop
t
loop
(
a
:
""
:
t
)
=
loop
(
a
:
t
)
loop
(
a
:
b
:
t
)
=
a
:
delimiter
:
loop
(
b
:
t
)
loop
a
=
a
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment