summaryrefslogtreecommitdiff
path: root/src/lib/ConcreteSyntax.hs
blob: 5209466d73e10aa9767ec5613797c5498f6d8433 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
-- Copyright 2022 Google LLC
--
-- Use of this source code is governed by a BSD-style
-- license that can be found in the LICENSE file or at
-- https://developers.google.com/open-source/licenses/bsd

module ConcreteSyntax (
  mustParseit, sourceBlocks, sourceBlock,
  keyWordStrs, showPrimName,
  parseUModule, parseUModuleDeps,
  finishUModuleParse, preludeImportBlock, mustParseSourceBlock,
  pattern Identifier) where

import Control.Monad.Combinators.Expr qualified as Expr
import Control.Monad.Reader
import Data.Char
import Data.Either
import Data.Functor
import Data.List.NonEmpty (NonEmpty (..))
import Data.String (fromString)
import Data.Text (Text)
import Data.Text          qualified as T
import Data.Text.Encoding qualified as T
import Data.Void
import Text.Megaparsec hiding (Label, State)
import Text.Megaparsec.Char hiding (space, eol)

import Err
import Lexing
import Types.Core
import Types.Source
import Types.Primitives
import Util

-- TODO: implement this more efficiently rather than just parsing the whole
-- thing and then extracting the deps.
parseUModuleDeps :: ModuleSourceName -> File -> [ModuleSourceName]
parseUModuleDeps name file = deps
  where UModule _ deps _ = parseUModule name $ T.decodeUtf8 $ fContents file
{-# SCC parseUModuleDeps #-}

finishUModuleParse :: UModulePartialParse -> UModule
finishUModuleParse (UModulePartialParse name _ file) =
  parseUModule name (T.decodeUtf8 $ fContents file)

parseUModule :: ModuleSourceName -> Text -> UModule
parseUModule name s = do
  let blocks = mustParseit s sourceBlocks
  let preamble = case name of
        Prelude -> []
        _ -> [preludeImportBlock]
  let blocks' = preamble ++ blocks
  let imports = flip foldMap blocks' \b -> case sbContents b of
                  Misc (ImportModule moduleName) -> [moduleName]
                  _ -> []
  UModule name imports blocks'
{-# SCC parseUModule #-}

preludeImportBlock :: SourceBlock
preludeImportBlock = SourceBlock 0 0 "" mempty (Misc $ ImportModule Prelude)

sourceBlocks :: Parser [SourceBlock]
sourceBlocks = manyTill (sourceBlock <* outputLines) eof
{-# SCC sourceBlocks #-}

mustParseSourceBlock :: Text -> SourceBlock
mustParseSourceBlock s = mustParseit s sourceBlock

-- === helpers for target ADT ===

interpOperator :: SrcId -> String -> ([SrcId], Bin)
interpOperator sid = \case
  "&>"  -> atomic DepAmpersand
  "."   -> atomic Dot
  ",>"  -> atomic DepComma
  ":"   -> atomic Colon
  "|"   -> atomic Pipe
  "::"  -> atomic DoubleColon
  "$"   -> atomic Dollar
  "->>" -> atomic ImplicitArrow
  "=>"  -> atomic FatArrow
  "="   -> atomic CSEqual
  name  -> ([], EvalBinOp $ WithSrc sid $ fromString $ "(" <> name <> ")")
  where
    atomic :: Bin -> ([SrcId], Bin)
    atomic b = ([sid], b)

pattern Identifier :: SourceName -> GroupW
pattern Identifier name <- (WithSrcs _ _ (CLeaf (CIdentifier name)))

-- === Parser (top-level structure) ===

sourceBlock :: Parser SourceBlock
sourceBlock = do
  offset <- getOffset
  pos <- getSourcePos
  (src, (lexInfo, b)) <- withSource $ withLexemeInfo $ withRecovery recover $ sourceBlock'
  let lexInfo' = lexInfo { lexemeInfo = lexemeInfo lexInfo <&> \(t, (l, r)) -> (t, (l-offset, r-offset))}
  return $ SourceBlock (unPos (sourceLine pos)) offset src lexInfo' b

recover :: ParseError Text Void -> Parser SourceBlock'
recover e = do
  pos <- liftM statePosState getParserState
  reachedEOF <-  try (mayBreak sc >> eof >> return True)
             <|> return False
  consumeTillBreak
  let errmsg = errorBundlePretty (ParseErrorBundle (e :| []) pos)
  return $ UnParseable reachedEOF errmsg

importModule :: Parser SourceBlock'
importModule = Misc . ImportModule . OrdinaryModule <$> do
  keyWord ImportKW
  WithSrc _ s <- anyCaseName
  eol
  return s

declareForeign :: Parser SourceBlock'
declareForeign = do
  keyWord ForeignKW
  foreignName <- strLit
  b <- anyName
  void $ label "type annotation" $ sym ":"
  ty <- cGroup
  eol
  return $ DeclareForeign (fmap fromString foreignName) b ty

declareCustomLinearization :: Parser SourceBlock'
declareCustomLinearization = do
  zeros <- (keyWord CustomLinearizationSymbolicKW $> SymbolicZeros)
       <|> (keyWord CustomLinearizationKW $> InstantiateZeros)
  fun <- anyCaseName
  linearization <- cGroup
  eol
  return $ DeclareCustomLinearization fun zeros linearization

consumeTillBreak :: Parser ()
consumeTillBreak = void $ manyTill anySingle $ eof <|> void (try (eol >> eol))

sourceBlock' :: Parser SourceBlock'
sourceBlock' =
      proseBlock
  <|> topLevelCommand
  <|> liftM TopDecl topDecl
  <|> topLetOrExpr <* eolf
  <|> hidden (some eol >> return (Misc EmptyLines))
  <|> hidden (sc >> eol >> return (Misc CommentLine))

topDecl :: Parser CTopDeclW
topDecl = withSrcs topDecl' <* eolf

topDecl' :: Parser CTopDecl
topDecl' =
      dataDef
  <|> structDef
  <|> interfaceDef
  <|> (CInstanceDecl <$> instanceDef True)
  <|> (CInstanceDecl <$> instanceDef False)

proseBlock :: Parser SourceBlock'
proseBlock = label "prose block" $
  char '\'' >> fmap (Misc . ProseBlock . fst) (withSource consumeTillBreak)

topLevelCommand :: Parser SourceBlock'
topLevelCommand =
      importModule
  <|> declareForeign
  <|> declareCustomLinearization
  -- <|> (Misc . QueryEnv <$> envQuery)
  <|> explicitCommand
  <?> "top-level command"

_envQuery :: Parser EnvQuery
_envQuery = error "not implemented"
-- string ":debug" >> sc >> (
--       (DumpSubst        <$  (string "env"   >> sc))
--   <|> (InternalNameInfo <$> (string "iname" >> sc >> rawName))
--   <|> (SourceNameInfo   <$> (string "sname" >> sc >> anyName)))
--        <* eol
--   where
--     rawName :: Parser RawName
--     rawName = RawName <$> (fromString <$> anyName) <*> intLit

explicitCommand :: Parser SourceBlock'
explicitCommand = do
  cmdName <- char ':' >> nameString
  cmd <- case cmdName of
    "p"       -> return $ EvalExpr (Printed Nothing)
    "pp"      -> return $ EvalExpr (Printed (Just PrintHaskell))
    "pcodegen"-> return $ EvalExpr (Printed (Just PrintCodegen))
    "t"       -> return $ GetType
    "html"    -> return $ EvalExpr RenderHtml
    "export"  -> ExportFun <$> nameString
    _ -> fail $ "unrecognized command: " ++ show cmdName
  b <- cBlock <* eolf
  e <- case b of
    ExprBlock e -> return e
    IndentedBlock sid decls -> withSrcs $ return $ CDo $ IndentedBlock sid decls
  return $ case (e, cmd) of
    (WithSrcs sid _ (CLeaf (CIdentifier v)), GetType) -> Misc $ GetNameType (WithSrc sid v)
    _ -> Command cmd e

type CDefBody = ([(SourceNameW, GroupW)], [(LetAnn, CDef)])
structDef :: Parser CTopDecl
structDef = do
  keyWord StructKW
  tyName <- anyName
  (params, givens) <- typeParams
  (fields, defs) <- oneLiner <|> multiLiner
  return $ CStruct tyName params givens fields defs
  where
    oneLiner :: Parser CDefBody
    oneLiner = do
      field <- nameAndType
      return ([field], [])

    multiLiner :: Parser CDefBody
    multiLiner = partitionEithers <$> onePerLine do
      (    (Left  <$> nameAndType)
       <|> (Right <$> funDefLetWithAnn))

funDefLetWithAnn :: Parser (LetAnn, CDef)
funDefLetWithAnn = do
  ann <- topLetAnn <|> return PlainLet
  def <- funDefLet
  return (ann, def)

dataDef :: Parser CTopDecl
dataDef = do
  keyWord DataKW
  tyName <- anyName
  (params, givens) <- typeParams
  dataCons <- onePerLine do
    dataConName <- anyName
    dataConArgs <- optional explicitParams
    return (dataConName, dataConArgs)
  return $ CData tyName params givens dataCons

interfaceDef :: Parser CTopDecl
interfaceDef = do
  keyWord InterfaceKW
  className <- anyName
  params <- explicitParams
  methodDecls <- try (withIndent (keyWord PassKW) >> return [])
    <|> onePerLine do
      methodName <- anyName
      void $ label "type annotation" $ sym ":"
      ty <- cGroup
      return (methodName, ty)
  return $ CInterface className params methodDecls

nameAndType :: Parser (SourceNameW, GroupW)
nameAndType = do
  n <- anyName
  sym ":"
  arg <- cGroup
  return (n, arg)

topLetOrExpr :: Parser SourceBlock'
topLetOrExpr = topLet >>= \case
  WithSrcs _ _ (CSDecl ann (CExpr e)) -> do
    when (ann /= PlainLet) $ fail "Cannot annotate expressions"
    return $ Command (EvalExpr (Printed Nothing)) e
  d -> return $ TopDecl d

topLet :: Parser CTopDeclW
topLet = withSrcs do
  lAnn <- topLetAnn <|> return PlainLet
  decl <- cDecl
  return $ CSDecl lAnn decl

topLetAnn :: Parser LetAnn
topLetAnn = do
  void $ char '@'
  ann <-  (string "inline"   $> InlineLet)
      <|> (string "noinline" $> NoInlineLet)
  nextLine
  return ann

onePerLine :: Parser a -> Parser [a]
onePerLine p =   liftM (:[]) p
             <|> (withIndent $ p `sepBy1` try nextLine)
{-# INLINE onePerLine #-}

-- === Groups ===

cBlock :: Parser CSBlock
cBlock = indentedBlock <|> ExprBlock <$> cGroup

indentedBlock :: Parser CSBlock
indentedBlock = withIndent do
  WithSrcs sid _ decls <- withSrcs $ withSrcs cDecl `sepBy1` (void semicolon <|> try nextLine)
  return $ IndentedBlock sid decls

cDecl :: Parser CSDecl
cDecl =   (CDefDecl <$> funDefLet)
      <|> simpleLet
      <|> (keyWord PassKW >> return CPass)

simpleLet :: Parser CSDecl
simpleLet = do
  lhs <- cGroupNoEqual
  next <- nextChar
  case next of
    '=' -> sym  "=" >> CLet  lhs <$> cBlock
    '<' -> sym "<-" >> CBind lhs <$> cBlock
    _   -> return $ CExpr lhs

instanceDef :: Bool -> Parser CInstanceDef
instanceDef isNamed = do
  optNameAndArgs <- case isNamed of
    False -> keyWord InstanceKW $> Nothing
    True  -> keyWord NamedInstanceKW >> do
      name <- anyName
      args <-  (sym ":" >> return Nothing)
           <|> ((Just <$> parenList cParenGroup) <* sym "->")
      return $ Just (name, args)
  className <- anyName
  args <- argList
  givens <- optional givenClause
  methods <- withIndent $ (withSrcs cDecl) `sepBy1` try nextLine
  return $ CInstanceDef className args givens methods optNameAndArgs

funDefLet :: Parser CDef
funDefLet = label "function definition" do
  keyWord DefKW
  mayBreak do
    name   <- anyName
    params <- explicitParams
    rhs    <- optional do
      expl     <- explicitness
      effs     <- optional cEffs
      resultTy <- cGroupNoEqual
      return (expl, effs, resultTy)
    givens <- optional givenClause
    mayNotBreak do
      sym "="
      body <- cBlock
      return $ CDef name params rhs givens body

explicitness :: Parser AppExplicitness
explicitness =   (sym "->"  $> ExplicitApp)
             <|> (sym "->>" $> ImplicitApp)

-- Intended for occurrences, like `foo(x, y, z)` (cf. defParamsList).
argList :: Parser [GroupW]
argList = do
  WithSrcs _ _ gs <- withSrcs $ bracketedGroup immediateLParen rParen cParenGroup
  return gs

immediateLParen :: Parser ()
immediateLParen = label "'(' (without preceding whitespace)" do
  nextChar >>= \case
    '(' -> precededByWhitespace >>= \case
      True -> empty
      False -> lParen
    _ -> empty

-- Putting `sym =` inside the cases gives better errors.
typeParams :: Parser (Maybe ExplicitParams, Maybe GivenClause)
typeParams =
      (explicitParamsAndGivens <* sym "=")
  <|> (return (Nothing, Nothing)    <* sym "=")

explicitParamsAndGivens :: Parser (Maybe ExplicitParams, Maybe GivenClause)
explicitParamsAndGivens = (,) <$> (Just <$> explicitParams) <*> optional givenClause

explicitParams :: Parser ExplicitParams
explicitParams = label "parameter list in parentheses (without preceding whitespace)" $
  withSrcs $ bracketedGroup immediateLParen rParen cParenGroup

parenList :: Parser GroupW -> Parser BracketedGroup
parenList p = withSrcs $ bracketedGroup lParen rParen p

bracketedGroup :: Parser () -> Parser () -> Parser GroupW -> Parser [GroupW]
bracketedGroup l r p = bracketed l r $ commaSep p

noGap :: Parser ()
noGap = precededByWhitespace >>= \case
  True -> fail "Unexpected whitespace"
  False -> return ()

givenClause :: Parser GivenClause
givenClause = do
  keyWord GivenKW
  (,) <$> parenList cGroup <*> optional (parenList cGroup)

withClause :: Parser WithClause
withClause = keyWord WithKW >> parenList cGroup

cEffs :: Parser CEffs
cEffs = withSrcs $ braces do
  effs <- commaSep cGroupNoPipe
  effTail <- optional $ sym "|" >> cGroup
  return (effs, effTail)

commaSep :: Parser a -> Parser [a]
commaSep p = sepBy p (sym ",")

cParenGroup :: Parser GroupW
cParenGroup = (withSrcs (CGivens <$> givenClause)) <|> cGroup

cGroup :: Parser GroupW
cGroup = makeExprParser leafGroup ops

cGroupNoJuxt :: Parser GroupW
cGroupNoJuxt = makeExprParser leafGroup $
  withoutOp "space" $ withoutOp "." ops

cGroupNoEqual :: Parser GroupW
cGroupNoEqual = makeExprParser leafGroup $
  withoutOp "=" ops

cGroupNoPipe :: Parser GroupW
cGroupNoPipe = makeExprParser leafGroup $
  withoutOp "|" ops

cGroupNoArrow :: Parser GroupW
cGroupNoArrow = makeExprParser leafGroup $
  withoutOp "->" ops

cNullaryLam :: Parser Group
cNullaryLam = do
  void $ sym "\\."
  body <- cBlock
  return $ CLambda [] body

cLam :: Parser Group
cLam = do
  void $ sym "\\"
  bs <- many cGroupNoJuxt
  void $ mayNotBreak $ sym "."
  body <- cBlock
  return $ CLambda bs body

cFor :: Parser Group
cFor = do
  kw <- forKW
  indices <- many cGroupNoJuxt
  void $ mayNotBreak $ sym "."
  body <- cBlock
  return $ CFor kw indices body
  where forKW =     keyWord ForKW  $> KFor
                <|> keyWord For_KW $> KFor_
                <|> keyWord RofKW  $> KRof
                <|> keyWord Rof_KW $> KRof_

cDo :: Parser Group
cDo = do
  keyWord DoKW
  CDo <$> cBlock

cCase :: Parser Group
cCase = do
  keyWord CaseKW
  scrut <- cGroup
  keyWord OfKW
  alts <- onePerLine cAlt
  return $ CCase scrut alts

cAlt :: Parser CaseAlt
cAlt = do
  pat <- cGroupNoArrow
  sym "->"
  body <- cBlock
  return (pat, body)

-- see [Note if-syntax]
cIf :: Parser Group
cIf = mayNotBreak do
  keyWord IfKW
  predicate <- cGroup
  (cons, alt) <- thenSameLine <|> thenNewLine
  return $ CIf predicate cons alt

thenSameLine :: Parser (CSBlock, Maybe CSBlock)
thenSameLine = do
  void $ keyWord ThenKW
  cBlock >>= \case
    IndentedBlock sid blk -> do
      let msg = ("No `else` may follow same-line `then` and indented consequent"
                  ++ "; indent and align both `then` and `else`, or write the "
                  ++ "whole `if` on one line.")
      mayBreak $ noElse msg
      return (IndentedBlock sid blk, Nothing)
    ExprBlock ex -> do
      alt <- optional
        $ (keyWord ElseKW >> cBlock)
          <|> (lookAhead (try $ withIndent (keyWord ElseKW))
               >> withIndent (keyWord ElseKW >> cBlock))
      return (ExprBlock ex, alt)

thenNewLine :: Parser (CSBlock, Maybe CSBlock)
thenNewLine = withIndent $ do
  void $ keyWord ThenKW
  cBlock >>= \case
    IndentedBlock sid blk -> do
      alt <- do
        -- With `mayNotBreak`, this just forbids inline else
        noElse ("Same-line `else` may not follow indented consequent;"
                ++ " put the `else` on the next line.")
        optional $ do
          void $ try $ nextLine >> keyWord ElseKW
          cBlock
      return (IndentedBlock sid blk, alt)
    ExprBlock ex -> do
      void $ optional $ try nextLine
      alt <- optional $ keyWord ElseKW >> cBlock
      return (ExprBlock ex, alt)

noElse :: String -> Parser ()
noElse msg = (optional $ try $ sc >> keyWord ElseKW) >>= \case
  Just _ -> fail msg
  Nothing -> return ()

leafGroup :: Parser GroupW
leafGroup = leafGroup' >>= appendPostfixGroups
 where
  leafGroup' :: Parser GroupW
  leafGroup' = do
    next <- nextChar
    case next of
      '_'  ->  withSrcs $ CLeaf <$> (underscore >> pure CHole)
      '('  ->  toCLeaf CIdentifier <$> symName
           <|> cParens
      '['  -> cBrackets
      '\"' -> toCLeaf CString <$> strLit
      '\'' -> toCLeaf CChar   <$> charLit
      '%'  -> do
        WithSrc sid name <- primName
        case strToPrimName name of
          Just prim -> WithSrcs sid [] <$> CPrim prim <$> argList
          Nothing   -> fail $ "Unrecognized primitive: " ++ name
      _ | isDigit next -> (    toCLeaf CNat   <$> natLit
                           <|> toCLeaf CFloat <$> doubleLit)
      '\\' -> withSrcs (cNullaryLam <|> cLam)
      -- For exprs include for, rof, for_, rof_
      'f'  -> (withSrcs cFor)  <|> cIdentifier
      'd'  -> (withSrcs cDo)   <|> cIdentifier
      'r'  -> (withSrcs cFor)  <|> cIdentifier
      'c'  -> (withSrcs cCase) <|> cIdentifier
      'i'  -> (withSrcs cIf)   <|> cIdentifier
      _    -> cIdentifier

  appendPostfixGroups :: GroupW -> Parser GroupW
  appendPostfixGroups g =
        (noGap >> appendPostfixGroup g >>= appendPostfixGroups)
    <|> return g

  appendPostfixGroup :: GroupW -> Parser GroupW
  appendPostfixGroup g = withSrcs $
        (CJuxtapose False g <$> cParens)
    <|> (CJuxtapose False g <$> cBrackets)
    <|> appendFieldAccess g

  appendFieldAccess :: GroupW -> Parser Group
  appendFieldAccess g = try do
    dot
    field <- cFieldName
    return $ CBin Dot g field

cFieldName :: Parser GroupW
cFieldName = cIdentifier <|> (toCLeaf CNat <$> natLit)

cIdentifier :: Parser GroupW
cIdentifier = toCLeaf CIdentifier <$> anyName

toCLeaf :: (a -> CLeaf) -> WithSrc a -> GroupW
toCLeaf toLeaf (WithSrc sid leaf) = WithSrcs sid [] $ CLeaf $ toLeaf leaf

cParens :: Parser GroupW
cParens = withSrcs $ CParens <$> bracketedGroup lParen rParen cParenGroup

cBrackets :: Parser GroupW
cBrackets = withSrcs $ CBrackets <$> bracketedGroup lBracket rBracket cGroup

-- A `PrecTable` is enough information to (i) remove or replace
-- operators for special contexts, and (ii) build the input structure
-- for Expr.makeExprParser.
type PrecTable a = [[(SourceName, Expr.Operator Parser a)]]

makeExprParser :: Parser a -> PrecTable a -> Parser a
makeExprParser p tbl = Expr.makeExprParser p tbl' where
  tbl' = map (map snd) tbl

withoutOp :: SourceName -> PrecTable a -> PrecTable a
withoutOp op tbl = map (filter ((/= op) . fst)) tbl

ops :: PrecTable GroupW
ops =
  [ [symOpL   "!"]
  , [juxtaposition]
  , [unOpPre  "-", unOpPre  "+"]
  , [backquote]
  -- Other ops with default fixity
  , [other]
  , [symOpL   "*", symOpL   "/"]
  , [symOpL  ".*", symOpL  "*."]
  , [symOpL ".**", symOpL "**."]
  , [symOpL  "**"]
  , [symOpL   "+", symOpL   "-"]
  , [symOpL  "-|"]
  , [symOpL "+>>"]
  , [symOpL  "<>"]
  , [symOpN  "~~"]
  , [symOpN   "<", symOpN  "<=", symOpN ">", symOpN ">="]
  , [symOpN  "==", symOpN  "!="]
  , [symOpL  "&&"]
  , [symOpL  "||"]
  , [symOpR  "=>"]
  , [arrow, symOpR "->>"]
  , [symOpL ">>>"]
  , [symOpL "<<<"]
  , [symOpL   "@"]
  , [symOpN  "::"]
  , [symOpR   "$"]
  , [symOpN  "+=", symOpN  ":="]
  -- Associate right so the mistaken utterance foo : i:Fin 4 => (..i)
  -- groups as a bad pi type rather than a bad binder
  , [symOpR   ":"]
  , [symOpL   "|"]
  , [symOpR  ",>"]
  , [symOpR  "&>"]
  , [withClausePostfix]
  , [symOpL   "="]
  ] where
  other = ("other", anySymOp)
  backquote = ("backquote", Expr.InfixL backquoteOp)
  juxtaposition  = ("space", Expr.InfixL $ sc >> addSrcIdToBinOp (return \x y -> ([], CJuxtapose True x y)))
  withClausePostfix = ("with", Expr.Postfix withClausePostfixOp)
  arrow = ("->", Expr.InfixR arrowOp)

addSrcIdToBinOp :: Parser (GroupW -> GroupW -> ([SrcId], Group)) -> Parser (GroupW -> GroupW -> GroupW)
addSrcIdToBinOp op = do
  f <- op
  sid <- freshSrcId
  return \x y -> do
    let (atomicSids, g) = f x y
    WithSrcs sid atomicSids g
{-# INLINE addSrcIdToBinOp #-}

addSrcIdToUnOp :: Parser (GroupW -> Group) -> Parser (GroupW -> GroupW)
addSrcIdToUnOp op = do
  f <- op
  sid <- freshSrcId
  return \x -> WithSrcs sid [] $ f x
{-# INLINE addSrcIdToUnOp #-}

backquoteOp :: Parser (GroupW -> GroupW -> GroupW)
backquoteOp = binApp do
  fname <- backquoteName
  return ([], EvalBinOp fname)

anySymOp :: Expr.Operator Parser GroupW
anySymOp = Expr.InfixL $ binApp do
  WithSrc sid s <- label "infix operator" (mayBreak anySym)
  return $ interpOperator sid s

symOpN :: String -> (SourceName, Expr.Operator Parser GroupW)
symOpN s = (fromString s, Expr.InfixN $ symOp s)

symOpL :: String -> (SourceName, Expr.Operator Parser GroupW)
symOpL s = (fromString s, Expr.InfixL $ symOp s)

symOpR :: String -> (SourceName, Expr.Operator Parser GroupW)
symOpR s = (fromString s, Expr.InfixR $ symOp s)

symOp :: String -> Parser (GroupW -> GroupW -> GroupW)
symOp s = binApp do
  sid <- label "infix operator" (mayBreak $ symWithId $ T.pack s)
  return $ interpOperator sid s

arrowOp :: Parser (GroupW -> GroupW -> GroupW)
arrowOp = addSrcIdToBinOp do
  sid <- symWithId "->"
  optEffs <- optional cEffs
  return \lhs rhs -> ([sid], CArrow lhs optEffs rhs)

unOpPre :: String -> (SourceName, Expr.Operator Parser GroupW)
unOpPre s = (fromString s, Expr.Prefix $ prefixOp s)

prefixOp :: String -> Parser (GroupW -> GroupW)
prefixOp s = addSrcIdToUnOp do
  symId <- symWithId (fromString s)
  return $ CPrefix (WithSrc symId $ fromString s)

binApp :: Parser ([SrcId], Bin) -> Parser (GroupW -> GroupW -> GroupW)
binApp f = addSrcIdToBinOp do
  (sids, op) <- f
  return \x y -> (sids, CBin op x y)

withClausePostfixOp :: Parser (GroupW -> GroupW)
withClausePostfixOp = addSrcIdToUnOp do
  rhs <- withClause
  return \lhs -> CWith lhs rhs

withSrcs :: Parser a -> Parser (WithSrcs a)
withSrcs p = do
  sid <- freshSrcId
  (sids, result) <- collectAtomicLexemeIds p
  return $ WithSrcs sid sids result

-- === notes ===

-- note [if-syntax]
-- We support the following syntaxes for `if`:
-- - 1-armed then-newline
--     if predicate
--       then consequent
--     if predicate
--       then
--         consequent1
--         consequent2
-- - 2-armed then-newline else-newline
--     if predicate
--       then consequent
--       else alternate
--   and the three other versions where the consequent or the
--   alternate are themselves blocks
-- - 1-armed then-inline
--     if predicate then consequent
--     if predicate then
--       consequent1
--       consequent2
-- - 2-armed then-inline else-inline
--     if predicate then consequent else alternate
--     if predicate then consequent else
--       alternate1
--       alternate2
-- - Notably, an imagined 2-armed then-inline else-newline
--     if predicate then
--       consequent1
--       consequent2
--     else alternate
--   is not an option, because the indentation lines up badly.  To wit,
--   one would want the `else` to be indented relative to the `if`,
--   but outdented relative to the consequent block, and if the `then` is
--   inline there is no indentation level that does that.
-- - Last candiate is
--      if predicate
--        then consequent else alternate
--      if predicate
--        then consequent else
--          alternate1
--          alternate2