diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,28 @@
 # Revision history for typst-hs
 
+## 0.3.1.0
+
+  * Allow multiplying a ratio by a length.
+
+  * Use `symModule` and `mathModule` directly when evaluating
+    Equation instead of looking up `sym` and `math`.
+
+  * Fix parsing of escapes in string literals. Symbols in general
+    can't be escaped. There is just a small list of valid escapes.
+
+  * Fix bugs in converting typst regexes to TDFA's format.
+
+  * Allow Symbol to be regex replacement text.
+
+  * Allow VString and VSymbol to be +'d.
+
+  * Update for toml-parser-1.2.0.0 API changes (#9, Eric Mertens).
+
+  * Derive the decoder for typst.toml (#7, Eric Mertens)
+
+  * Implement typst's `toml()` function (#8, Eric Mertens).
+
+
 ## 0.3.0.0
 
   * We now target typst 0.6.
diff --git a/src/Typst/Evaluate.hs b/src/Typst/Evaluate.hs
--- a/src/Typst/Evaluate.hs
+++ b/src/Typst/Evaluate.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE LambdaCase #-}
@@ -26,11 +27,13 @@
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as TE
 import qualified Data.Vector as V
+import GHC.Generics (Generic)
 import System.FilePath (replaceFileName, takeBaseName, takeDirectory, (</>))
 import Text.Parsec
 import Typst.Bind (destructuringBind)
 import Typst.Methods (getMethod)
-import Typst.Module.Standard (loadFileText, standardModule)
+import Typst.Module.Standard (loadFileText, standardModule, symModule)
+import Typst.Module.Math (mathModule)
 import Typst.Parse (parseTypst)
 import Typst.Regex (match)
 import Typst.Show (applyShowRules)
@@ -38,6 +41,8 @@
 import Typst.Types
 import Typst.Util (makeFunction, nthArg)
 import qualified Toml as Toml
+import qualified Toml.FromValue as Toml
+import qualified Toml.FromValue.Generic as Toml
 
 -- import Debug.Trace
 
@@ -233,10 +238,8 @@
                 [("level", VInteger (fromIntegral level))]
           }
     Equation display ms -> inBlock BlockScope $ do
-      VModule _ mathmod <- lookupIdentifier "math"
-      importModule mathmod
-      VModule _ symmod <- lookupIdentifier "sym"
-      importModule symmod
+      importModule mathModule
+      importModule symModule
       oldMath <- evalMath <$> getState
       updateState $ \st -> st {evalMath = True}
       content <- pInnerContents ms
@@ -888,6 +891,24 @@
           pure res
   pure fn
 
+-- | Type of TOML configuration file used in 'findPackageEntryPoint' saved as `typst.toml`
+data Config = Config {
+  package :: PackageConfig
+
+} deriving (Show, Generic)
+
+data PackageConfig = PackageConfig {
+  entrypoint :: String
+} deriving (Show, Generic)
+
+-- | Derived generically from record field names
+instance Toml.FromValue Config where
+  fromValue = Toml.parseTableFromValue Toml.genericParseTable
+
+-- | Derived generically from record field names
+instance Toml.FromValue PackageConfig where
+  fromValue = Toml.parseTableFromValue Toml.genericParseTable
+
 findPackageEntryPoint :: Monad m => Text -> MP m FilePath
 findPackageEntryPoint modname = do
   let (namespace, rest) = break (=='/') (drop 1 $ T.unpack modname)
@@ -931,15 +952,10 @@
                     "\nCompile with typst compile to bring the package into your local cache."
              -- TODO? fetch from CDN if not present in cache?
   tomlString <- T.unpack . TE.decodeUtf8 <$> lift (loadBytes operations tomlPath)
-  case Toml.parse tomlString of
-    Left e -> fail e
-    Right toptbl ->
-      case M.lookup "package" toptbl of
-        Just (Toml.Table tbl) ->
-          case M.lookup "entrypoint" tbl of
-            Just (Toml.String f) -> pure $ replaceFileName tomlPath f
-            _ -> fail "Could not find entrypoint"
-        _ -> fail "Could not find [package] table"
+  case Toml.decode tomlString of
+    Toml.Failure e -> fail (unlines ("Failure loading typst.toml" : e))
+    Toml.Success _warnings cfg -> -- ignores warnings like unused keys in TOML
+      pure (replaceFileName tomlPath (entrypoint (package cfg)))
 
 loadModule :: Monad m => Text
            -> MP m (Seq Content, (Identifier, M.Map Identifier Val))
diff --git a/src/Typst/Methods.hs b/src/Typst/Methods.hs
--- a/src/Typst/Methods.hs
+++ b/src/Typst/Methods.hs
@@ -234,6 +234,8 @@
               case replacement of
                 VString r ->
                   pure $ VString $ replaceRegex patt mbCount (const r) t
+                VSymbol (Symbol r _ _) ->
+                  pure $ VString $ replaceRegex patt mbCount (const r) t
                 VFunction _ _ f ->
                   pure $
                     VString $
diff --git a/src/Typst/Module/Standard.hs b/src/Typst/Module/Standard.hs
--- a/src/Typst/Module/Standard.hs
+++ b/src/Typst/Module/Standard.hs
@@ -6,6 +6,7 @@
 
 module Typst.Module.Standard
   ( standardModule,
+    symModule,
     loadFileText,
     applyPureFunction
   )
@@ -32,6 +33,7 @@
 import Text.Parsec (getPosition, getState, updateState, runParserT)
 import Text.Read (readMaybe)
 import qualified Text.XML as XML
+import qualified Toml
 import Typst.Emoji (typstEmojis)
 import Typst.Module.Calc (calcModule)
 import Typst.Module.Math (mathModule)
@@ -549,7 +551,14 @@
         t <- lift $ loadFileText fp
         pure $ VString t
     ),
-    ("toml", makeFunction $ fail "unimplemented toml"),
+    ( "toml",
+      makeFunction $ do
+        fp <- nthArg 1
+        t <- lift $ loadFileText fp
+        case Toml.decode (T.unpack t) of
+          Toml.Failure e -> fail (unlines ("toml errors:" : e))
+          Toml.Success _ v -> pure v
+    ),
     ( "xml",
       makeFunction $ do
         fp <- nthArg 1
diff --git a/src/Typst/Parse.hs b/src/Typst/Parse.hs
--- a/src/Typst/Parse.hs
+++ b/src/Typst/Parse.hs
@@ -579,7 +579,8 @@
   try $
     char '\\'
       *> ( uniEsc
-             <|> satisfy isSpecial
+             <|> ('\\' <$ char '\\')
+             <|> ('"' <$ char '"')
              <|> ('\n' <$ char 'n')
              <|> ('\t' <$ char 't')
              <|> ('\r' <$ char 'r')
diff --git a/src/Typst/Regex.hs b/src/Typst/Regex.hs
--- a/src/Typst/Regex.hs
+++ b/src/Typst/Regex.hs
@@ -84,22 +84,39 @@
   where
     (caseSensitive, t') =
       if "(?i)" `T.isPrefixOf` t
-        then (False, T.pack . go . T.unpack $ T.drop 4 t)
-        else (True, T.pack . go . T.unpack $ t)
+        then (False, T.pack . go False . T.unpack $ T.drop 4 t)
+        else (True, T.pack . go False . T.unpack $ t)
     compopts = TDFA.defaultCompOpt {TDFA.caseSensitive = caseSensitive}
-    -- handle things not supported in TFFA (posix) regexes, e.g. \d \w \s, +, ?
-    go [] = []
-    go ('?' : cs) = "{0,1}" ++ go cs
-    go ('+' : cs) = "{1,}" ++ go cs
-    go ('\\' : c : cs)
-      | c == 'd' = "[[:digit:]]" ++ go cs
-      | c == 'D' = "[^[:digit:]]" ++ go cs
-      | c == 's' = "[[:space:]]" ++ go cs
-      | c == 'S' = "[^[:space:]]" ++ go cs
-      | c == 'w' = "[[:word:]]" ++ go cs
-      | c == 'W' = "[^[:word:]]" ++ go cs
-      | otherwise = '\\' : c : go cs
-    go (c : cs) = c : go cs
+
+    -- Handle things not supported in TFFA posix regexes, e.g. \d \w \s, +, ?
+    -- Note that we have to track whether we're in a character class, because
+    -- the expansions will be different in that case.  The first
+    -- parameter of `go` is True if in a character class.
+    go _ [] = []
+    go True (']' : cs) = ']' : go False cs
+    go False ('[' : cs) = '[' :
+      case cs of
+        '^':']':ds -> '^' : ']' : go True ds
+        '^':'\\':']':ds -> '^' : ']' : go True ds
+        ']':ds -> ']' : go True ds
+        '\\':']':ds -> ']' : go True ds
+        _ -> go True cs
+    go False ('?' : cs) = "{0,1}" ++ go False cs
+    go False ('+' : cs) = "{1,}" ++ go False cs
+    go inCharClass ('\\' : c : cs)
+      = let f = if inCharClass
+                   then id
+                   else \x -> "[" ++ x ++ "]"
+            r = case c of
+                  'd' -> f "[:digit:]"
+                  'D' -> f "^[:digit:]"
+                  's' -> f "[:space:]"
+                  'S' -> f "^[:space:]"
+                  'w' -> f "[:word:]"
+                  'W' -> f "^[:word:]"
+                  _ -> ['\\', c]
+        in r ++ go inCharClass cs
+    go inCharClass (c : cs) = c : go inCharClass cs
 
 match :: TDFA.RegexContext Regex source target => RE -> source -> target
 match (RE _ re) t = TDFA.match re t
diff --git a/src/Typst/Types.hs b/src/Typst/Types.hs
--- a/src/Typst/Types.hs
+++ b/src/Typst/Types.hs
@@ -70,11 +70,14 @@
 import Data.Vector (Vector)
 import qualified Data.Vector as V
 import Text.Parsec
+import qualified Toml
+import qualified Toml.FromValue as Toml
+import qualified Toml.Pretty as Toml
 import qualified Text.PrettyPrint as P
 import Text.Read (readMaybe)
 import Typst.Regex (RE, makeLiteralRE)
 import Typst.Syntax (Identifier (..), Markup)
-import Data.Time (UTCTime, Day, DiffTime)
+import Data.Time (UTCTime, Day, DiffTime, timeOfDayToTime, localDay, localTimeOfDay)
 import Data.Time.Format (defaultTimeLocale, formatTime)
 import System.Directory (XdgDirectory(..))
 
@@ -120,6 +123,22 @@
   parseJSON (Aeson.Bool b) = pure $ VBoolean b
   parseJSON Aeson.Null = pure VNone
 
+instance Toml.FromValue Val where
+  fromValue = pure . tomlToVal
+
+tomlToVal :: Toml.Value -> Val
+tomlToVal (Toml.Bool x) = VBoolean x
+tomlToVal (Toml.Integer x) = VInteger x
+tomlToVal (Toml.String x) = VString (T.pack x)
+tomlToVal (Toml.Float x) = VFloat x
+tomlToVal (Toml.TimeOfDay x) = VDateTime Nothing (Just (timeOfDayToTime x))
+tomlToVal (Toml.Day x) = VDateTime (Just x) Nothing
+tomlToVal (Toml.LocalTime x) = VDateTime (Just (localDay x)) (Just (timeOfDayToTime (localTimeOfDay x)))
+tomlToVal (Toml.Array x) = VArray (V.fromList (map tomlToVal x))
+tomlToVal (Toml.Table x) = VDict (OM.fromList [(Identifier (T.pack k), tomlToVal v) | (k,v) <- M.assocs x])
+  -- typst specifies that unsupported datetimes map to strings and we don't have a place for the timezone
+tomlToVal v@Toml.ZonedTime{} = VString (T.pack (show (Toml.prettyValue v)))
+
 data ValType
   = TNone
   | TAuto
@@ -386,6 +405,8 @@
   maybePlus (VContent c1) (VContent c2) = pure $ VContent (c1 <> c2)
   maybePlus (VString s1) (VContent c2) = pure $ VContent (Txt s1 Seq.<| c2)
   maybePlus (VContent c1) (VString s2) = pure $ VContent (c1 Seq.|> Txt s2)
+  maybePlus (VString s1) sym@(VSymbol{}) = pure $ VString (s1 <> repr sym)
+  maybePlus sym@(VSymbol{}) (VString s2) = pure $ VString (repr sym <> s2)
   maybePlus (VLength l1) (VLength l2) = pure $ VLength (l1 <> l2)
   maybePlus (VLength l1) (VRatio r1) = pure $ VLength (l1 <> LRatio r1)
   maybePlus (VRatio r1) (VLength l1) = pure $ VLength (l1 <> LRatio r1)
@@ -422,6 +443,8 @@
     | i >= 0 = pure $ VContent (mconcat $ replicate (fromIntegral i) c)
   maybeTimes (VInteger i) (VLength l) = pure $ VLength $ timesLength (fromIntegral i) l
   maybeTimes (VLength l) (VInteger i) = pure $ VLength $ timesLength (fromIntegral i) l
+  maybeTimes (VRatio r) (VLength l) = pure $ VLength $ timesLength (fromRational r) l
+  maybeTimes (VLength l) (VRatio r) = pure $ VLength $ timesLength (fromRational r) l
   maybeTimes (VFloat f) (VLength l) = pure $ VLength $ timesLength f l
   maybeTimes (VLength l) (VFloat f) = pure $ VLength $ timesLength f l
   maybeTimes (VInteger i) (VAngle a) = pure $ VAngle (fromIntegral i * a)
diff --git a/test/out/compute/data-08.out b/test/out/compute/data-08.out
--- a/test/out/compute/data-08.out
+++ b/test/out/compute/data-08.out
@@ -169,5 +169,34 @@
        ])
 , ParBreak
 ]
-"test/typ/compute/data-08.typ" (line 3, column 2):
-unimplemented toml
+--- evaluated ---
+{ text(body: [
+]), 
+  text(body: [
+]), 
+  text(body: [✅]), 
+  text(body: [
+]), 
+  text(body: [✅]), 
+  text(body: [
+]), 
+  text(body: [✅]), 
+  text(body: [
+]), 
+  text(body: [✅]), 
+  text(body: [
+]), 
+  text(body: [✅]), 
+  text(body: [
+]), 
+  text(body: [✅]), 
+  text(body: [
+]), 
+  text(body: [✅]), 
+  text(body: [
+]), 
+  text(body: [✅]), 
+  text(body: [
+]), 
+  text(body: [✅]), 
+  parbreak() }
diff --git a/typst.cabal b/typst.cabal
--- a/typst.cabal
+++ b/typst.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               typst
-version:            0.3.0.0
+version:            0.3.1.0
 synopsis:           Parsing and evaluating typst syntax.
 description:        A library for parsing and evaluating typst syntax.
                     Typst (<https://typst.app>) is a document layout and
@@ -70,7 +70,7 @@
                       scientific,
                       xml-conduit,
                       yaml,
-                      toml-parser,
+                      toml-parser ^>= 1.2.0.0,
                       regex-tdfa,
                       array,
                       time,
