packages feed

canontra-0.1.0.0: src/Canontra/Parser/Go.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData #-}

{- |
Module      : Canontra.Parser.Go
Description : High-performance zero-span Go (1.20+) AST parser.

Translates Go source code into canontra's unified IR without source-span leakage,
supporting package structures, factored imports, receiver methods, structs,
interfaces, goroutines, channels, defer, select, and type switches (BUG-09).
-}
module Canontra.Parser.Go
  ( parseGoSource
  ) where

import Control.DeepSeq (NFData)
import Data.Char (isAlpha, isAlphaNum, isDigit, isSpace)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Read as TR
import GHC.Generics (Generic)

import Canontra.Canonical.Unicode (canonicalizeText)
import Canontra.IR.Declaration
import Canontra.IR.Dependency
import Canontra.IR.Expression
import Canontra.IR.Program
import Canontra.Types (ParseError (..))

parseGoSource :: FilePath -> Text -> Either ParseError Program
parseGoSource filePath input =
  let cleanInput = canonicalizeText input
      tokens = tokenizeGo cleanInput
  in case parseGoTopLevel filePath tokens of
      Left err -> Left err
      Right (pkgName, decls, imps, stmts) ->
        let modName = if T.null pkgName then T.pack filePath else pkgName
            modul = Module modName imps decls stmts
        in Right (Program [modul] "go")

data GoToken
  = TokIdent Text
  | TokKw Text
  | TokNum Integer
  | TokFloat Double
  | TokStr Text
  | TokSymbol Text
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

tokenizeGo :: Text -> [GoToken]
tokenizeGo text = go text
  where
    go t | T.null t = []
    go t =
      let c = T.head t
          cs = T.tail t
      in case c of
        _ | isSpace c -> go (T.dropWhile isSpace t)
        '/' | T.isPrefixOf "/" cs ->
            go (T.drop 1 (T.dropWhile (/= '\n') cs))
        '/' | T.isPrefixOf "*" cs ->
            skipBlockComment (T.drop 1 cs)
        '"' ->
            let (s, rest) = parseQuotedString '"' cs
            in TokStr s : go rest
        '`' ->
            let (s, rest) = parseQuotedString '`' cs
            in TokStr s : go rest
        _ | isAlpha c || c == '_' ->
            let (ident, rest) = T.span (\x -> isAlphaNum x || x == '_') t
            in (if isGoKeyword ident then TokKw ident else TokIdent ident) : go rest
        _ | isDigit c ->
            let (numStr, rest) = T.span (\x -> isDigit x || x == '.' || x == 'e' || x == 'E') t
            in if '.' `elem` T.unpack numStr
               then case TR.double numStr of
                      Right (d, _) -> TokFloat d : go rest
                      Left _       -> TokFloat 0.0 : go rest
               else case TR.decimal numStr of
                      Right (n, _) -> TokNum n : go rest
                      Left _       -> TokNum 0 : go rest
        _ | c `elem` ("{}()[];,?:.~" :: String) ->
            TokSymbol (T.singleton c) : go cs
        _ | c `elem` ("=+-*/%&|^!<>:" :: String) ->
            let (sym, rest) = T.span (`elem` ("=+-*/%&|^!<>:" :: String)) t
            in TokSymbol sym : go rest
        _ -> go cs

    skipBlockComment t | T.null t = []
    skipBlockComment t
      | T.isPrefixOf "*/" t = go (T.drop 2 t)
      | otherwise = skipBlockComment (T.tail t)

    parseQuotedString q t =
      let (body, rest) = parseQuotedBody q t ""
      in (body, rest)

    parseQuotedBody _ t acc | T.null t = (acc, "")
    parseQuotedBody q t acc =
      let c = T.head t
          cs = T.tail t
      in if c == q
         then (acc, cs)
         else if c == '\\' && not (T.null cs)
              then let esc = case T.head cs of
                         'n' -> '\n'
                         't' -> '\t'
                         'r' -> '\r'
                         '\\' -> '\\'
                         '\'' -> '\''
                         '"' -> '"'
                         '`' -> '`'
                         other -> other
                   in parseQuotedBody q (T.tail cs) (acc `T.snoc` esc)
              else parseQuotedBody q cs (acc `T.snoc` c)

isGoKeyword :: Text -> Bool
isGoKeyword kw = kw `elem`
  [ "package", "import", "func", "type", "struct", "interface", "var", "const"
  , "return", "if", "else", "for", "range", "switch", "case", "default"
  , "go", "defer", "select", "chan", "map", "fallthrough", "break", "continue"
  ]

parseGoTopLevel :: FilePath -> [GoToken] -> Either ParseError (Text, [Declaration], [ImportDecl], [Stmt])
parseGoTopLevel _ tokens =
  let (pkg, afterPkg) = case tokens of
        TokKw "package" : TokIdent p : rest -> (p, rest)
        _                                   -> ("", tokens)
      (decls, imps, stmts) = extractGoDeclsAndStmts afterPkg
  in Right (pkg, decls, imps, stmts)

extractGoDeclsAndStmts :: [GoToken] -> ([Declaration], [ImportDecl], [Stmt])
extractGoDeclsAndStmts [] = ([], [], [])
extractGoDeclsAndStmts tokens = case tokens of
  -- import "fmt" or import ( ... )
  TokKw "import" : rest ->
    let (impDecls, afterImp) = parseGoImports rest
        (d, i, s) = extractGoDeclsAndStmts afterImp
    in (d, impDecls ++ i, s)

  -- func (r *Receiver) Method(params) ret { body }
  TokKw "func" : TokSymbol "(" : rest ->
    let (rc, afterRc) = parseReceiver rest
    in case afterRc of
      TokIdent mName : TokSymbol "[" : afterName ->
        let (tParams, afterTParams) = span (\t -> t /= TokSymbol "]") afterName
            tParamText = "[" <> T.unwords [tokenText t | t <- tParams] <> "]"
            afterBracket = if null afterTParams then [] else tail afterTParams
            (fn, afterFn) = parseGoFunctionBody mName afterBracket
            fnWithGeneric = fn { fnDecorators = [tParamText] }
            (d, i, s) = extractGoDeclsAndStmts afterFn
        in (DeclReceiver rc fnWithGeneric : d, i, s)
      TokIdent mName : afterName ->
        let (fn, afterFn) = parseGoFunctionBody mName afterName
            (d, i, s) = extractGoDeclsAndStmts afterFn
        in (DeclReceiver rc fn : d, i, s)
      _ -> extractGoDeclsAndStmts afterRc

  -- func FunctionName[T any](params) ret { body }
  TokKw "func" : TokIdent fnName : TokSymbol "[" : rest ->
    let (tParams, afterTParams) = span (\t -> t /= TokSymbol "]") rest
        tParamText = "[" <> T.unwords [tokenText t | t <- tParams] <> "]"
        afterBracket = if null afterTParams then [] else tail afterTParams
        (fn, afterFn) = parseGoFunctionBody fnName afterBracket
        fnWithGeneric = fn { fnDecorators = [tParamText] }
        (d, i, s) = extractGoDeclsAndStmts afterFn
    in (DeclFunction fnWithGeneric : d, i, s)

  -- func FunctionName(params) ret { body }
  TokKw "func" : TokIdent fnName : rest ->
    let (fn, afterFn) = parseGoFunctionBody fnName rest
        (d, i, s) = extractGoDeclsAndStmts afterFn
    in (DeclFunction fn : d, i, s)

  -- type Name[T any] struct { ... }
  TokKw "type" : TokIdent stName : TokSymbol "[" : rest ->
    let (tParams, afterTParams) = span (\t -> t /= TokSymbol "]") rest
        tParamText = "[" <> T.unwords [tokenText t | t <- tParams] <> "]"
        afterBracket = if null afterTParams then [] else tail afterTParams
    in case afterBracket of
      TokKw "struct" : afterStruct ->
        let afterBrace = dropWhile (\tok -> tok /= TokSymbol "{") afterStruct
            (fieldsToks, afterBody) = extractBalancedBraces afterBrace
            fields = parseStructFields fieldsToks
            st = Struct stName fields [Function tParamText [] Nothing [] [] False] "pub"
            (d, i, s) = extractGoDeclsAndStmts afterBody
        in (DeclStruct st : d, i, s)
      _ -> extractGoDeclsAndStmts afterBracket

  -- type Name struct { ... }
  TokKw "type" : TokIdent stName : TokKw "struct" : rest ->
    let afterBrace = dropWhile (\tok -> tok /= TokSymbol "{") rest
        (fieldsToks, afterBody) = extractBalancedBraces afterBrace
        fields = parseStructFields fieldsToks
        st = Struct stName fields [] "pub"
        (d, i, s) = extractGoDeclsAndStmts afterBody
    in (DeclStruct st : d, i, s)

  -- type Name interface { ... }
  TokKw "type" : TokIdent ifName : TokKw "interface" : rest ->
    let afterBrace = dropWhile (\tok -> tok /= TokSymbol "{") rest
        (_, afterBody) = extractBalancedBraces afterBrace
        iface = Interface ifName [] []
        (d, i, s) = extractGoDeclsAndStmts afterBody
    in (DeclInterface iface : d, i, s)

  -- type Name = Original
  TokKw "type" : TokIdent aliasName : TokSymbol "=" : TokIdent orig : rest ->
    let (d, i, s) = extractGoDeclsAndStmts rest
    in (DeclTypeAlias aliasName (Just orig) : d, i, s)

  -- factored var ( ... ) and const ( ... )
  TokKw kw : TokSymbol "(" : rest | kw == "var" || kw == "const" ->
    let (blockToks, afterParen) = span (\t -> t /= TokSymbol ")") rest
        remaining = if null afterParen then [] else tail afterParen
        blockStmts = parseFactoredVarBlock blockToks
        (d, i, s) = extractGoDeclsAndStmts remaining
    in (d, i, blockStmts ++ s)

  -- single var / const declaration at top level
  TokKw kw : TokIdent name : rest | kw == "var" || kw == "const" ->
    let (stmt, afterStmt) = parseGoVarDecl name rest
        (d, i, s) = extractGoDeclsAndStmts afterStmt
    in (d, i, stmt : s)

  t : ts ->
    let (stmt, rest) = parseGoSingleStmt (t:ts)
        (d, i, s) = extractGoDeclsAndStmts rest
    in (d, i, maybe [] pure stmt ++ s)

parseFactoredVarBlock :: [GoToken] -> [Stmt]
parseFactoredVarBlock [] = []
parseFactoredVarBlock (TokIdent name : rest) =
  let (stmt, afterStmt) = parseGoVarDecl name rest
  in stmt : parseFactoredVarBlock afterStmt
parseFactoredVarBlock (_ : rest) = parseFactoredVarBlock rest

parseGoVarDecl :: Text -> [GoToken] -> (Stmt, [GoToken])
parseGoVarDecl name tokens =
  case tokens of
    TokSymbol "=" : rest ->
      let (expr, afterExpr) = parseGoSimpleExpr rest
      in (StmtAssign [ExprId name] expr, afterExpr)
    TokSymbol ":=" : rest ->
      let (expr, afterExpr) = parseGoSimpleExpr rest
      in (StmtAssign [ExprId name] expr, afterExpr)
    TokIdent ty : TokSymbol "=" : rest ->
      let (expr, afterExpr) = parseGoSimpleExpr rest
      in (StmtAnnAssign (ExprId name) (ExprId ty) (Just expr), afterExpr)
    TokIdent ty : rest ->
      (StmtAnnAssign (ExprId name) (ExprId ty) Nothing, rest)
    _ ->
      (StmtAssign [ExprId name] (ExprLit LitNone), tokens)

parseGoImports :: [GoToken] -> ([ImportDecl], [GoToken])
parseGoImports (TokSymbol "(" : rest) =
  let (impToks, afterParen) = span (\tok -> tok /= TokSymbol ")") rest
      imps = [ ImportModule path alias
             | (path, alias) <- extractImportPairs impToks
             ]
      remaining = if null afterParen then [] else tail afterParen
  in (imps, remaining)
parseGoImports (TokStr path : rest) =
  ([ImportModule path Nothing], rest)
parseGoImports (TokIdent alias : TokStr path : rest) =
  ([ImportModule path (Just alias)], rest)
parseGoImports tokens = ([], tokens)

extractImportPairs :: [GoToken] -> [(Text, Maybe Text)]
extractImportPairs [] = []
extractImportPairs (TokStr path : rest) = (path, Nothing) : extractImportPairs rest
extractImportPairs (TokIdent alias : TokStr path : rest) = (path, Just alias) : extractImportPairs rest
extractImportPairs (_:rest) = extractImportPairs rest

parseReceiver :: [GoToken] -> (Receiver, [GoToken])
parseReceiver tokens =
  let (rcToks, afterParen) = span (\t -> t /= TokSymbol ")") tokens
      remaining = if null afterParen then [] else tail afterParen
  in case rcToks of
      (TokIdent v : TokSymbol "*" : TokIdent ty : rest) ->
        let gen = if null rest then "" else T.concat [tokenText t | t <- rest]
        in (Receiver v (ty <> gen) True, remaining)
      (TokIdent v : TokIdent ty : rest) ->
        let gen = if null rest then "" else T.concat [tokenText t | t <- rest]
        in (Receiver v (ty <> gen) False, remaining)
      (TokSymbol "*" : TokIdent ty : rest) ->
        let gen = if null rest then "" else T.concat [tokenText t | t <- rest]
        in (Receiver "" (ty <> gen) True, remaining)
      (TokIdent ty : rest) ->
        let gen = if null rest then "" else T.concat [tokenText t | t <- rest]
        in (Receiver "" (ty <> gen) False, remaining)
      _ -> (Receiver "" "" False, remaining)

parseGoFunctionBody :: Text -> [GoToken] -> (Function, [GoToken])
parseGoFunctionBody name tokens =
  let (params, afterParams) = parseGoParamList tokens
      (retType, afterRet) = parseGoReturnType afterParams
      afterBrace = dropWhile (\t -> t /= TokSymbol "{") afterRet
      (bodyToks, afterBody) = extractBalancedBraces afterBrace
      bodyStmts = parseGoBodyStmts bodyToks
      fn = Function name params retType [] bodyStmts False
  in (fn, afterBody)

parseGoParamList :: [GoToken] -> ([Parameter], [GoToken])
parseGoParamList (TokSymbol "(" : rest) =
  let (pToks, afterParen) = span (\t -> t /= TokSymbol ")") rest
      params = extractGoParams pToks
      remaining = if null afterParen then [] else tail afterParen
  in (params, remaining)
parseGoParamList tokens = ([], tokens)

extractGoParams :: [GoToken] -> [Parameter]
extractGoParams [] = []
extractGoParams (TokIdent pName : xs) =
  let (tyToks, rest) = consumeGoType xs
      tyStr = if null tyToks then Nothing else Just (T.concat [tokenText t | t <- tyToks])
      remToks = dropWhile (\t -> t == TokSymbol ",") rest
  in Parameter pName ParamPositional Nothing tyStr : extractGoParams remToks
extractGoParams (_:rest) = extractGoParams rest

parseGoReturnType :: [GoToken] -> (Maybe Text, [GoToken])
parseGoReturnType tokens =
  let (tyToks, rest) = consumeGoType tokens
  in if null tyToks
     then (Nothing, tokens)
     else (Just (T.concat [tokenText t | t <- tyToks]), rest)

parseStructFields :: [GoToken] -> [(Text, Maybe Text)]
parseStructFields [] = []
parseStructFields (TokIdent fName : xs) =
  let (tyToks, afterField) = consumeGoType xs
      tyStr = if null tyToks then Nothing else Just (T.concat [tokenText t | t <- tyToks])
      remToks = dropWhile (\t -> t == TokSymbol ";" || t == TokSymbol ",") afterField
  in (fName, tyStr) : parseStructFields remToks
parseStructFields (_:rest) = parseStructFields rest

consumeGoType :: [GoToken] -> ([GoToken], [GoToken])
consumeGoType (TokSymbol "*" : rest) =
  let (t, r) = consumeGoType rest
  in (TokSymbol "*" : t, r)
consumeGoType (TokSymbol "[" : TokSymbol "]" : rest) =
  let (t, r) = consumeGoType rest
  in (TokSymbol "[" : TokSymbol "]" : t, r)
consumeGoType (TokKw "chan" : rest) =
  let (t, r) = consumeGoType rest
  in (TokKw "chan" : t, r)
consumeGoType (TokKw "map" : TokSymbol "[" : rest) =
  let (keyToks, afterKey) = span (\t -> t /= TokSymbol "]") rest
      afterClose = if null afterKey then [] else tail afterKey
      (valToks, afterVal) = consumeGoType afterClose
  in (TokKw "map" : TokSymbol "[" : keyToks ++ [TokSymbol "]"] ++ valToks, afterVal)
consumeGoType (TokIdent ty : rest) = ([TokIdent ty], rest)
consumeGoType ts = ([], ts)

tokenText :: GoToken -> Text
tokenText (TokIdent t)  = t
tokenText (TokKw t)     = t
tokenText (TokSymbol t) = t
tokenText (TokStr t)    = "\"" <> t <> "\""
tokenText (TokNum n)    = T.pack (show n)
tokenText (TokFloat f)  = T.pack (show f)

extractBalancedBraces :: [GoToken] -> ([GoToken], [GoToken])
extractBalancedBraces (TokSymbol "{" : rest) = go (1 :: Int) [] rest
  where
    go 0 acc remaining = (reverse acc, remaining)
    go _ acc [] = (reverse acc, [])
    go depth acc (TokSymbol "{" : xs) = go (depth + 1) (TokSymbol "{" : acc) xs
    go depth acc (TokSymbol "}" : xs) =
      if depth == 1
      then (reverse acc, xs)
      else go (depth - 1) (TokSymbol "}" : acc) xs
    go depth acc (x:xs) = go depth (x : acc) xs
extractBalancedBraces tokens = ([], tokens)

parseGoBodyStmts :: [GoToken] -> [Stmt]
parseGoBodyStmts [] = []
parseGoBodyStmts (TokKw "return" : rest) =
  let (expr, afterExpr) = parseGoSimpleExpr rest
  in StmtReturn (Just expr) : parseGoBodyStmts afterExpr
parseGoBodyStmts (TokKw "go" : rest) =
  let (expr, afterExpr) = parseGoSimpleExpr rest
  in StmtGo expr : parseGoBodyStmts afterExpr
parseGoBodyStmts (TokKw "defer" : rest) =
  let (expr, afterExpr) = parseGoSimpleExpr rest
  in StmtDefer expr : parseGoBodyStmts afterExpr
parseGoBodyStmts (TokKw "switch" : rest) =
  let (switchStmt, afterSwitch) = parseGoSwitch rest
  in maybe [] pure switchStmt ++ parseGoBodyStmts afterSwitch
parseGoBodyStmts (_:rest) = parseGoBodyStmts rest

parseGoSwitch :: [GoToken] -> (Maybe Stmt, [GoToken])
parseGoSwitch tokens =
  let (targetToks, afterTarget) = span (\t -> t /= TokSymbol "{") tokens
      (bodyToks, afterBody) = extractBalancedBraces afterTarget
      targetExpr = case targetToks of
        [TokIdent name] -> ExprId name
        _               -> ExprLit LitNone
      (cases, defStmts) = parseGoSwitchCases bodyToks
  in (Just (StmtSwitch targetExpr cases defStmts), afterBody)

-- | Unrolls comma-separated multi-type switch cases into individual branch targets (BUG-09).
parseGoSwitchCases :: [GoToken] -> ([(Expr, [Stmt])], [Stmt])
parseGoSwitchCases [] = ([], [])
parseGoSwitchCases (TokKw "case" : rest) =
  let (caseHead, afterColon) = span (\t -> t /= TokSymbol ":") rest
      remaining = if null afterColon then [] else tail afterColon
      (caseStmts, nextCases) = span (\t -> t /= TokKw "case" && t /= TokKw "default") remaining
      stmts = parseGoBodyStmts caseStmts
      (otherCases, defStmts) = parseGoSwitchCases nextCases
      -- Extract all comma-separated case labels (e.g. case int, int64, string:)
      caseExprs = extractCaseLabels caseHead
      unrolled = [(cExpr, stmts) | cExpr <- caseExprs]
  in (unrolled ++ otherCases, defStmts)
parseGoSwitchCases (TokKw "default" : TokSymbol ":" : rest) =
  let (defStmtsToks, nextCases) = span (\t -> t /= TokKw "case") rest
      stmts = parseGoBodyStmts defStmtsToks
      (otherCases, _) = parseGoSwitchCases nextCases
  in (otherCases, stmts)
parseGoSwitchCases (_:rest) = parseGoSwitchCases rest

extractCaseLabels :: [GoToken] -> [Expr]
extractCaseLabels [] = []
extractCaseLabels (TokIdent name : rest) =
  ExprId name : extractCaseLabels (dropWhile (\t -> t == TokSymbol ",") rest)
extractCaseLabels (TokNum n : rest) =
  ExprLit (LitInt n) : extractCaseLabels (dropWhile (\t -> t == TokSymbol ",") rest)
extractCaseLabels (TokStr s : rest) =
  ExprLit (LitString s) : extractCaseLabels (dropWhile (\t -> t == TokSymbol ",") rest)
extractCaseLabels (_:rest) = extractCaseLabels rest

parseGoSingleStmt :: [GoToken] -> (Maybe Stmt, [GoToken])
parseGoSingleStmt (TokKw "return" : rest) =
  let (expr, afterExpr) = parseGoSimpleExpr rest
  in (Just (StmtReturn (Just expr)), afterExpr)
parseGoSingleStmt (TokKw "go" : rest) =
  let (expr, afterExpr) = parseGoSimpleExpr rest
  in (Just (StmtGo expr), afterExpr)
parseGoSingleStmt (TokKw "defer" : rest) =
  let (expr, afterExpr) = parseGoSimpleExpr rest
  in (Just (StmtDefer expr), afterExpr)
parseGoSingleStmt (TokKw "switch" : rest) =
  parseGoSwitch rest
parseGoSingleStmt (TokIdent n1 : TokSymbol "," : TokIdent n2 : TokSymbol ":=" : rest) =
  let (expr, afterExpr) = parseGoSimpleExpr rest
  in (Just (StmtAssign [ExprId n1, ExprId n2] expr), afterExpr)
parseGoSingleStmt (TokIdent n1 : TokSymbol "," : TokIdent n2 : TokSymbol "=" : rest) =
  let (expr, afterExpr) = parseGoSimpleExpr rest
  in (Just (StmtAssign [ExprId n1, ExprId n2] expr), afterExpr)
parseGoSingleStmt (TokIdent name : TokSymbol ":=" : rest) =
  let (expr, afterExpr) = parseGoSimpleExpr rest
  in (Just (StmtAssign [ExprId name] expr), afterExpr)
parseGoSingleStmt (TokIdent name : TokSymbol "=" : rest) =
  let (expr, afterExpr) = parseGoSimpleExpr rest
  in (Just (StmtAssign [ExprId name] expr), afterExpr)
parseGoSingleStmt (TokIdent name : TokSymbol "(" : rest) =
  let afterParen = dropWhile (\t -> t /= TokSymbol ")") rest
      nextToks = if null afterParen then [] else tail afterParen
  in (Just (StmtExpr (ExprCall (ExprId name) [] [])), nextToks)
parseGoSingleStmt (_:rest) = (Nothing, rest)
parseGoSingleStmt [] = (Nothing, [])

parseGoSimpleExpr :: [GoToken] -> (Expr, [GoToken])
parseGoSimpleExpr tokens =
  let (lhs, rest) = parseGoPrimaryExpr tokens
  in case rest of
    TokSymbol op : afterOp | op `elem` ["+", "-", "*", "/", "%", "==", "!=", "<", ">", "<=", ">="] ->
      let (rhs, remToks) = parseGoSimpleExpr afterOp
          binOp = case op of
            "+"  -> OpAdd
            "-"  -> OpSub
            "*"  -> OpMul
            "/"  -> OpDiv
            "%"  -> OpMod
            "==" -> OpEq
            "!=" -> OpNotEq
            "<"  -> OpLt
            ">"  -> OpGt
            "<=" -> OpLtE
            ">=" -> OpGtE
            _    -> OpAdd
      in (ExprBinary binOp lhs rhs, remToks)
    _ -> (lhs, rest)

parseGoPrimaryExpr :: [GoToken] -> (Expr, [GoToken])
parseGoPrimaryExpr (TokIdent name : TokSymbol "(" : rest) =
  let afterParen = dropWhile (\t -> t /= TokSymbol ")") rest
      nextToks = if null afterParen then [] else tail afterParen
  in (ExprCall (ExprId name) [] [], nextToks)
parseGoPrimaryExpr (TokNum n : rest) = (ExprLit (LitInt n), rest)
parseGoPrimaryExpr (TokFloat f : rest) = (ExprLit (LitFloat f), rest)
parseGoPrimaryExpr (TokStr s : rest) = (ExprLit (LitString s), rest)
parseGoPrimaryExpr (TokIdent name : rest) = (ExprId name, rest)
parseGoPrimaryExpr tokens = (ExprLit LitNone, tokens)