[Git][ghc/ghc][master] Parser: be more careful when lexing extended literals (#25258)
Marge Bot (@marge-bot)
gitlab at gitlab.haskell.org
Sat Sep 21 21:53:53 UTC 2024
Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
d7016e0d by Sylvain Henry at 2024-09-21T17:52:24-04:00
Parser: be more careful when lexing extended literals (#25258)
Previously we would lex invalid prefixes like "8#Int3" as [8#Int, 3].
A side-effect of this patch is that we now allow negative unsigned
extended literals. They trigger an overflow warning later anyway.
- - - - -
13 changed files:
- compiler/GHC/Data/StringBuffer.hs
- compiler/GHC/Parser/Lexer.x
- docs/users_guide/exts/extended_literals.rst
- + testsuite/tests/parser/should_compile/T25258.hs
- + testsuite/tests/parser/should_compile/T25258.stderr
- testsuite/tests/parser/should_compile/all.T
- + testsuite/tests/parser/should_fail/T25258a.hs
- + testsuite/tests/parser/should_fail/T25258a.stderr
- + testsuite/tests/parser/should_fail/T25258b.hs
- + testsuite/tests/parser/should_fail/T25258b.stderr
- + testsuite/tests/parser/should_fail/T25258c.hs
- + testsuite/tests/parser/should_fail/T25258c.stderr
- testsuite/tests/parser/should_fail/all.T
Changes:
=====================================
compiler/GHC/Data/StringBuffer.hs
=====================================
@@ -9,6 +9,7 @@ Buffers for scanning string input stored in external arrays.
{-# LANGUAGE CPP #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE LambdaCase #-}
{-# OPTIONS_GHC -O2 #-}
-- We always optimise this, otherwise performance of a non-optimised
@@ -47,6 +48,7 @@ module GHC.Data.StringBuffer
-- * Parsing integers
parseUnsignedInteger,
+ findHashOffset,
-- * Checking for bi-directional format characters
containsBidirectionalFormatChar,
@@ -417,3 +419,15 @@ parseUnsignedInteger (StringBuffer buf _ cur) len radix char_to_int
'_' -> go (i + 1) x -- skip "_" (#14473)
char -> go (i + 1) (x * radix + toInteger (char_to_int char))
in go 0 0
+
+-- | Find the offset of the '#' character in the StringBuffer.
+--
+-- Make sure that it contains one before calling this function!
+findHashOffset :: StringBuffer -> Int
+findHashOffset (StringBuffer buf _ cur)
+ = inlinePerformIO $ withForeignPtr buf $ \ptr -> do
+ let
+ go p = peek p >>= \case
+ (0x23 :: Word8) -> pure $! ((p `minusPtr` ptr) - cur)
+ _ -> go (p `plusPtr` 1)
+ go (ptr `plusPtr` cur)
=====================================
compiler/GHC/Parser/Lexer.x
=====================================
@@ -562,6 +562,18 @@ $unigraphic / { isSmartQuote } { smart_quote_error }
@octallit \# \# / { ifExtension MagicHashBit } { tok_primword 2 4 octal }
@hexadecimallit \# \# / { ifExtension MagicHashBit } { tok_primword 2 4 hexadecimal }
+ @decimal \# $idchar+ / { ifExtension ExtendedLiteralsBit } { tok_prim_num_ext positive 0 decimal }
+ @binarylit \# $idchar+ / { ifExtension ExtendedLiteralsBit `alexAndPred`
+ ifExtension BinaryLiteralsBit } { tok_prim_num_ext positive 2 binary }
+ @octallit \# $idchar+ / { ifExtension ExtendedLiteralsBit } { tok_prim_num_ext positive 2 octal }
+ @hexadecimallit \# $idchar+ / { ifExtension ExtendedLiteralsBit } { tok_prim_num_ext positive 2 hexadecimal }
+ @negative @decimal \# $idchar+ / { negHashLitPred ExtendedLiteralsBit } { tok_prim_num_ext negative 1 decimal }
+ @negative @binarylit \# $idchar+ / { negHashLitPred ExtendedLiteralsBit `alexAndPred`
+ ifExtension BinaryLiteralsBit } { tok_prim_num_ext negative 3 binary }
+ @negative @octallit \# $idchar+ / { negHashLitPred ExtendedLiteralsBit } { tok_prim_num_ext negative 3 octal }
+ @negative @hexadecimallit \# $idchar+ / { negHashLitPred ExtendedLiteralsBit } { tok_prim_num_ext negative 3 hexadecimal }
+
+
-- Unboxed floats and doubles (:: Float#, :: Double#)
-- prim_{float,double} work with signed literals
@floating_point \# / { ifExtension MagicHashBit } { tok_frac 1 tok_primfloat }
@@ -573,91 +585,6 @@ $unigraphic / { isSmartQuote } { smart_quote_error }
@negative 0[xX] @numspc @hex_floating_point \# / { ifExtension HexFloatLiteralsBit `alexAndPred` negHashLitPred MagicHashBit } { tok_frac 1 tok_prim_hex_float }
@negative 0[xX] @numspc @hex_floating_point \# \# / { ifExtension HexFloatLiteralsBit `alexAndPred` negHashLitPred MagicHashBit } { tok_frac 2 tok_prim_hex_double }
- @decimal \#"Int8" / { ifExtension ExtendedLiteralsBit } { tok_primint8 positive 0 decimal }
- @binarylit \#"Int8" / { ifExtension ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primint8 positive 2 binary }
- @octallit \#"Int8" / { ifExtension ExtendedLiteralsBit } { tok_primint8 positive 2 octal }
- @hexadecimallit \#"Int8" / { ifExtension ExtendedLiteralsBit } { tok_primint8 positive 2 hexadecimal }
- @negative @decimal \#"Int8" / { negHashLitPred ExtendedLiteralsBit } { tok_primint8 negative 1 decimal }
- @negative @binarylit \#"Int8" / { negHashLitPred ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primint8 negative 3 binary }
- @negative @octallit \#"Int8" / { negHashLitPred ExtendedLiteralsBit } { tok_primint8 negative 3 octal }
- @negative @hexadecimallit \#"Int8" / { negHashLitPred ExtendedLiteralsBit } { tok_primint8 negative 3 hexadecimal }
-
- @decimal \#"Int16" / { ifExtension ExtendedLiteralsBit } { tok_primint16 positive 0 decimal }
- @binarylit \#"Int16" / { ifExtension ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primint16 positive 2 binary }
- @octallit \#"Int16" / { ifExtension ExtendedLiteralsBit } { tok_primint16 positive 2 octal }
- @hexadecimallit \#"Int16" / { ifExtension ExtendedLiteralsBit } { tok_primint16 positive 2 hexadecimal }
- @negative @decimal \#"Int16" / { negHashLitPred ExtendedLiteralsBit} { tok_primint16 negative 1 decimal }
- @negative @binarylit \#"Int16" / { negHashLitPred ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primint16 negative 3 binary }
- @negative @octallit \#"Int16" / { negHashLitPred ExtendedLiteralsBit} { tok_primint16 negative 3 octal }
- @negative @hexadecimallit \#"Int16" / { negHashLitPred ExtendedLiteralsBit} { tok_primint16 negative 3 hexadecimal }
-
- @decimal \#"Int32" / { ifExtension ExtendedLiteralsBit } { tok_primint32 positive 0 decimal }
- @binarylit \#"Int32" / { ifExtension ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primint32 positive 2 binary }
- @octallit \#"Int32" / { ifExtension ExtendedLiteralsBit } { tok_primint32 positive 2 octal }
- @hexadecimallit \#"Int32" / { ifExtension ExtendedLiteralsBit } { tok_primint32 positive 2 hexadecimal }
- @negative @decimal \#"Int32" / { negHashLitPred ExtendedLiteralsBit } { tok_primint32 negative 1 decimal }
- @negative @binarylit \#"Int32" / { negHashLitPred ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primint32 negative 3 binary }
- @negative @octallit \#"Int32" / { negHashLitPred ExtendedLiteralsBit} { tok_primint32 negative 3 octal }
- @negative @hexadecimallit \#"Int32" / { negHashLitPred ExtendedLiteralsBit} { tok_primint32 negative 3 hexadecimal }
-
- @decimal \#"Int64" / { ifExtension ExtendedLiteralsBit } { tok_primint64 positive 0 decimal }
- @binarylit \#"Int64" / { ifExtension ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primint64 positive 2 binary }
- @octallit \#"Int64" / { ifExtension ExtendedLiteralsBit } { tok_primint64 positive 2 octal }
- @hexadecimallit \#"Int64" / { ifExtension ExtendedLiteralsBit } { tok_primint64 positive 2 hexadecimal }
- @negative @decimal \#"Int64" / { negHashLitPred ExtendedLiteralsBit } { tok_primint64 negative 1 decimal }
- @negative @binarylit \#"Int64" / { negHashLitPred ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primint64 negative 3 binary }
- @negative @octallit \#"Int64" / { negHashLitPred ExtendedLiteralsBit } { tok_primint64 negative 3 octal }
- @negative @hexadecimallit \#"Int64" / { negHashLitPred ExtendedLiteralsBit } { tok_primint64 negative 3 hexadecimal }
-
- @decimal \#"Int" / { ifExtension ExtendedLiteralsBit } { tok_primint positive 0 4 decimal }
- @binarylit \#"Int" / { ifExtension ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primint positive 2 6 binary }
- @octallit \#"Int" / { ifExtension ExtendedLiteralsBit } { tok_primint positive 2 6 octal }
- @hexadecimallit \#"Int" / { ifExtension ExtendedLiteralsBit } { tok_primint positive 2 6 hexadecimal }
- @negative @decimal \#"Int" / { negHashLitPred ExtendedLiteralsBit } { tok_primint negative 1 5 decimal }
- @negative @binarylit \#"Int" / { negHashLitPred ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primint negative 3 7 binary }
- @negative @octallit \#"Int" / { negHashLitPred ExtendedLiteralsBit } { tok_primint negative 3 7 octal }
- @negative @hexadecimallit \#"Int" / { negHashLitPred ExtendedLiteralsBit } { tok_primint negative 3 7 hexadecimal }
-
- @decimal \#"Word8" / { ifExtension ExtendedLiteralsBit } { tok_primword8 0 decimal }
- @binarylit \#"Word8" / { ifExtension ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primword8 2 binary }
- @octallit \#"Word8" / { ifExtension ExtendedLiteralsBit } { tok_primword8 2 octal }
- @hexadecimallit \#"Word8" / { ifExtension ExtendedLiteralsBit } { tok_primword8 2 hexadecimal }
-
- @decimal \#"Word16" / { ifExtension ExtendedLiteralsBit } { tok_primword16 0 decimal }
- @binarylit \#"Word16" / { ifExtension ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primword16 2 binary }
- @octallit \#"Word16" / { ifExtension ExtendedLiteralsBit } { tok_primword16 2 octal }
- @hexadecimallit \#"Word16" / { ifExtension ExtendedLiteralsBit } { tok_primword16 2 hexadecimal }
-
- @decimal \#"Word32" / { ifExtension ExtendedLiteralsBit } { tok_primword32 0 decimal }
- @binarylit \#"Word32" / { ifExtension ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primword32 2 binary }
- @octallit \#"Word32" / { ifExtension ExtendedLiteralsBit } { tok_primword32 2 octal }
- @hexadecimallit \#"Word32" / { ifExtension ExtendedLiteralsBit } { tok_primword32 2 hexadecimal }
-
- @decimal \#"Word64" / { ifExtension ExtendedLiteralsBit } { tok_primword64 0 decimal }
- @binarylit \#"Word64" / { ifExtension ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primword64 2 binary }
- @octallit \#"Word64" / { ifExtension ExtendedLiteralsBit } { tok_primword64 2 octal }
- @hexadecimallit \#"Word64" / { ifExtension ExtendedLiteralsBit } { tok_primword64 2 hexadecimal }
-
- @decimal \#"Word" / { ifExtension ExtendedLiteralsBit } { tok_primword 0 5 decimal }
- @binarylit \#"Word" / { ifExtension ExtendedLiteralsBit `alexAndPred`
- ifExtension BinaryLiteralsBit } { tok_primword 2 7 binary }
- @octallit \#"Word" / { ifExtension ExtendedLiteralsBit } { tok_primword 2 7 octal }
- @hexadecimallit \#"Word" / { ifExtension ExtendedLiteralsBit } { tok_primword 2 7 hexadecimal }
-
}
-- Strings and chars are lexed by hand-written code. The reason is
@@ -1905,21 +1832,56 @@ sym con span buf len _buf2 =
!fs = lexemeToFastString buf len
-- Variations on the integral numeric literal.
-tok_integral :: (SourceText -> Integer -> Token)
- -> (Integer -> Integer)
- -> Int -> Int
- -> (Integer, (Char -> Int))
- -> Action
-tok_integral itint transint transbuf translen (radix,char_to_int) span buf len _buf2 = do
+tok_integral
+ :: (SourceText -> Integer -> Token) -- ^ token constructor
+ -> (Integer -> Integer) -- ^ value transformation (e.g. negate)
+ -> Int -- ^ Offset of the unsigned value (e.g. 1 when we parsed "-", 2 for "0x", etc.)
+ -> Int -- ^ Number of non-numeric characters parsed (e.g. 6 in "-12#Int8")
+ -> (Integer, (Char -> Int)) -- ^ (radix, char_to_int parsing function)
+ -> Action
+tok_integral mk_token transval offset translen (radix,char_to_int) span buf len _buf2 = do
numericUnderscores <- getBit NumericUnderscoresBit -- #14473
let src = lexemeToFastString buf len
when ((not numericUnderscores) && ('_' `elem` unpackFS src)) $ do
pState <- getPState
let msg = PsErrNumUnderscores NumUnderscore_Integral
addError $ mkPlainErrorMsgEnvelope (mkSrcSpanPs (last_loc pState)) msg
- return $ L span $ itint (SourceText src)
- $! transint $ parseUnsignedInteger
- (offsetBytes transbuf buf) (subtract translen len) radix char_to_int
+ return $ L span $ mk_token (SourceText src)
+ $! transval $ parseUnsignedInteger
+ (offsetBytes offset buf) (subtract translen len) radix char_to_int
+
+-- | Helper to parse ExtendedLiterals (e.g. -0x10#Word32)
+--
+-- This function finds the offset of the "#" character and checks that the
+-- suffix is valid. Then it calls tok_integral with the appropriate suffix
+-- length taken into account.
+tok_prim_num_ext
+ :: (Integer -> Integer) -- ^ value transformation (e.g. negate)
+ -> Int -- ^ Offset of the unsigned value (e.g. 1 when we parsed "-", 2 for "0x", etc.)
+ -> (Integer, (Char -> Int)) -- ^ (radix, char_to_int parsing function)
+ -> Action
+tok_prim_num_ext transval offset (radix,char_to_int) span buf len buf2 = do
+ let !suffix_offset = findHashOffset buf + 1
+ let !suffix_len = len - suffix_offset
+ let !suffix = lexemeToFastString (offsetBytes suffix_offset buf) suffix_len
+
+ mk_token <- if
+ | suffix == fsLit "Word" -> pure ITprimword
+ | suffix == fsLit "Word8" -> pure ITprimword8
+ | suffix == fsLit "Word16" -> pure ITprimword16
+ | suffix == fsLit "Word32" -> pure ITprimword32
+ | suffix == fsLit "Word64" -> pure ITprimword64
+ | suffix == fsLit "Int" -> pure ITprimint
+ | suffix == fsLit "Int8" -> pure ITprimint8
+ | suffix == fsLit "Int16" -> pure ITprimint16
+ | suffix == fsLit "Int32" -> pure ITprimint32
+ | suffix == fsLit "Int64" -> pure ITprimint64
+ | otherwise -> srcParseFail
+
+ let !translen = suffix_len+offset+1
+ tok_integral mk_token transval offset translen (radix,char_to_int) span buf len buf2
+
+
tok_num :: (Integer -> Integer)
-> Int -> Int
@@ -1941,48 +1903,16 @@ tok_primint = tok_integral ITprimint
tok_primword :: Int -> Int
-> (Integer, (Char->Int)) -> Action
tok_primword = tok_integral ITprimword positive
+
positive, negative :: (Integer -> Integer)
positive = id
negative = negate
-decimal, octal, hexadecimal :: (Integer, Char -> Int)
-decimal = (10,octDecDigit)
-binary = (2,octDecDigit)
-octal = (8,octDecDigit)
-hexadecimal = (16,hexDigit)
--- | Helper for defining @IntX@ primitive literal parsers (specifically for
--- the ExtendedLiterals extension, such as @123#Int8@).
-tok_primintX :: (SourceText -> Integer -> Token)
- -> Int
- -> (Integer -> Integer)
- -> Int
- -> (Integer, (Char->Int)) -> Action
-tok_primintX itint addlen transint transbuf =
- tok_integral itint transint transbuf (transbuf+addlen)
-
-tok_primint8, tok_primint16, tok_primint32, tok_primint64
- :: (Integer -> Integer)
- -> Int -> (Integer, (Char->Int)) -> Action
-tok_primint8 = tok_primintX ITprimint8 5
-tok_primint16 = tok_primintX ITprimint16 6
-tok_primint32 = tok_primintX ITprimint32 6
-tok_primint64 = tok_primintX ITprimint64 6
-
--- | Helper for defining @WordX@ primitive literal parsers (specifically for
--- the ExtendedLiterals extension, such as @234#Word8@).
-tok_primwordX :: (SourceText -> Integer -> Token)
- -> Int
- -> Int
- -> (Integer, (Char->Int)) -> Action
-tok_primwordX itint addlen transbuf =
- tok_integral itint positive transbuf (transbuf+addlen)
-
-tok_primword8, tok_primword16, tok_primword32, tok_primword64
- :: Int -> (Integer, (Char->Int)) -> Action
-tok_primword8 = tok_primwordX ITprimword8 6
-tok_primword16 = tok_primwordX ITprimword16 7
-tok_primword32 = tok_primwordX ITprimword32 7
-tok_primword64 = tok_primwordX ITprimword64 7
+binary, octal, decimal, hexadecimal :: (Integer, Char -> Int)
+binary = (2,octDecDigit)
+octal = (8,octDecDigit)
+decimal = (10,octDecDigit)
+hexadecimal = (16,hexDigit)
-- readSignificandExponentPair can understand negative rationals, exponents, everything.
tok_frac :: Int -> (String -> Token) -> Action
=====================================
docs/users_guide/exts/extended_literals.rst
=====================================
@@ -35,11 +35,10 @@ The primitive numeric types allowed are:
- ``Word64#``
- ``Word#``
-All types permit any nonnegative Haskell integer lexeme, e.g. ``70``, ``0x2A``,
-``0o1276``, ``0b1010`` (with :extension:`BinaryLiterals`). The signed ``Int``
-types also permit negative integer lexemes. Defining a literal with a value that
-can't fit in its requested type will emit an overflow warning by default, the
-same as boxed numeric literals.
+All types permit any positive and negative Haskell integer lexeme. Defining a
+literal with a value that can't fit in its requested type will emit an overflow
+warning by default, the same as boxed numeric literals (see
+:ghc-flag:`-Woverflowed-literals`).
As with :extension:`MagicHash`, this extension does not bring anything into
scope, nor change any semantics. The syntax only applies to numeric literals.
=====================================
testsuite/tests/parser/should_compile/T25258.hs
=====================================
@@ -0,0 +1,18 @@
+{-# LANGUAGE MagicHash, ExtendedLiterals, RequiredTypeArguments #-}
+
+module T25258 where
+
+import GHC.Exts
+import GHC.Word
+
+f :: Int# -> forall t -> ()
+f _ _ = ()
+
+x4 :: forall t -> ()
+x4 = f 0x08#Int
+
+x5 :: ()
+x5 = f 0x08# Int
+
+x6 :: Word8
+x6 = W8# (-10#Word8) -- we now allow negative unsigned extended literals (with a warning)
=====================================
testsuite/tests/parser/should_compile/T25258.stderr
=====================================
@@ -0,0 +1,3 @@
+T25258.hs:18:11: warning: [GHC-97441] [-Woverflowed-literals (in -Wdefault)]
+ Literal -10 is out of the Word8# range 0..255
+
=====================================
testsuite/tests/parser/should_compile/all.T
=====================================
@@ -204,3 +204,4 @@ test('ListTuplePunsFamilies', [expect_broken(23135), extra_files(['ListTuplePuns
test('T22155', normal, compile, ['-dsuppress-uniques -ddump-simpl -dsuppress-all -dno-typeable-binds'])
test('T25132', normal, compile, [''])
+test('T25258', normal, compile, [''])
=====================================
testsuite/tests/parser/should_fail/T25258a.hs
=====================================
@@ -0,0 +1,7 @@
+{-# LANGUAGE MagicHash, ExtendedLiterals #-}
+
+module T25258a where
+
+import GHC.Exts
+
+x1 = 0x08#Misc -- invalid uppercase identifier
=====================================
testsuite/tests/parser/should_fail/T25258a.stderr
=====================================
@@ -0,0 +1,2 @@
+T25258a.hs:7:6: error: [GHC-58481] parse error on input ‘0x08#Misc’
+
=====================================
testsuite/tests/parser/should_fail/T25258b.hs
=====================================
@@ -0,0 +1,7 @@
+{-# LANGUAGE MagicHash, ExtendedLiterals #-}
+
+module T25258b where
+
+import GHC.Exts
+
+x2 = 0x80#Int3
=====================================
testsuite/tests/parser/should_fail/T25258b.stderr
=====================================
@@ -0,0 +1,2 @@
+T25258b.hs:7:6: error: [GHC-58481] parse error on input ‘0x80#Int3’
+
=====================================
testsuite/tests/parser/should_fail/T25258c.hs
=====================================
@@ -0,0 +1,7 @@
+{-# LANGUAGE MagicHash, ExtendedLiterals #-}
+
+module T25258c where
+
+import GHC.Exts
+
+x0 = 0x08#misc -- invalid lowercase identifier
=====================================
testsuite/tests/parser/should_fail/T25258c.stderr
=====================================
@@ -0,0 +1,2 @@
+T25258c.hs:7:6: error: [GHC-58481] parse error on input ‘0x08#misc’
+
=====================================
testsuite/tests/parser/should_fail/all.T
=====================================
@@ -234,3 +234,6 @@ test('OrPatInExprErr', normal, compile_fail, [''])
test('MultilineStringsError', [normalise_whitespace_fun(lambda s: s)], compile_fail, [''])
test('MultilineStringsSmartQuotes', normal, compile_fail, [''])
test('MultilineStringsInnerTab', normal, compile_fail, [''])
+test('T25258a', normal, compile_fail, [''])
+test('T25258b', normal, compile_fail, [''])
+test('T25258c', normal, compile_fail, [''])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d7016e0d41769f777c713158817bc0c43fb2a33f
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d7016e0d41769f777c713158817bc0c43fb2a33f
You're receiving this email because of your account on gitlab.haskell.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20240921/353d03e8/attachment-0001.html>
More information about the ghc-commits
mailing list