mirror of
https://github.com/Smaug123/WoofWare.PrattParser
synced 2025-10-05 17:38:41 +00:00
Fully generic
This commit is contained in:
@@ -27,7 +27,7 @@ module Example =
|
|||||||
| TokenType.RightBracket -> None
|
| TokenType.RightBracket -> None
|
||||||
|
|
||||||
let parser =
|
let parser =
|
||||||
Parser.empty
|
Parser.empty _.Type
|
||||||
|> Parser.defineAtoms atom
|
|> Parser.defineAtoms atom
|
||||||
|> Parser.withUnaryPostfix TokenType.Factorial (7, ()) Expr.factorial
|
|> Parser.withUnaryPostfix TokenType.Factorial (7, ()) Expr.factorial
|
||||||
|> Parser.withUnaryPrefix TokenType.Plus ((), 5) id
|
|> Parser.withUnaryPrefix TokenType.Plus ((), 5) id
|
||||||
|
@@ -1,30 +1,32 @@
|
|||||||
namespace PrattParser
|
namespace PrattParser
|
||||||
|
|
||||||
open System
|
type BracketLikeParser<'tokenTag, 'expr> =
|
||||||
open System.Globalization
|
|
||||||
|
|
||||||
type BracketLikeParser =
|
|
||||||
{
|
{
|
||||||
/// Whether to consume input after the final token, e.g. like `if...then...else...` consumes,
|
/// Whether to consume input after the final token, e.g. like `if...then...else...` consumes,
|
||||||
/// whereas `(...)` does not
|
/// whereas `(...)` does not
|
||||||
ConsumeAfterFinalToken : bool
|
ConsumeAfterFinalToken : bool
|
||||||
BoundaryTokens : TokenType list
|
BoundaryTokens : 'tokenTag list
|
||||||
Construct : Expr list -> Expr
|
Construct : 'expr list -> 'expr
|
||||||
}
|
}
|
||||||
|
|
||||||
type Parser =
|
type Parser<'tokenTag, 'token, 'expr> when 'tokenTag : comparison =
|
||||||
{
|
{
|
||||||
UnaryPrefix : Map<TokenType, (unit * int) * (Expr -> Expr)>
|
GetTag : 'token -> 'tokenTag
|
||||||
UnaryPostfix : Map<TokenType, (int * unit) * (Expr -> Expr)>
|
UnaryPrefix : Map<'tokenTag, (unit * int) * ('expr -> 'expr)>
|
||||||
Infix : Map<TokenType, (int * int) * (Expr -> Expr -> Expr)>
|
UnaryPostfix : Map<'tokenTag, (int * unit) * ('expr -> 'expr)>
|
||||||
Atom : string -> Token -> Expr option
|
Infix : Map<'tokenTag, (int * int) * ('expr -> 'expr -> 'expr)>
|
||||||
BracketLike : Map<TokenType, BracketLikeParser list>
|
Atom : string -> 'token -> 'expr option
|
||||||
|
BracketLike : Map<'tokenTag, BracketLikeParser<'tokenTag, 'expr> list>
|
||||||
}
|
}
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Parser =
|
module Parser =
|
||||||
let empty =
|
let empty<'tokenTag, 'token, 'expr when 'tokenTag : comparison>
|
||||||
|
(getTag : 'token -> 'tokenTag)
|
||||||
|
: Parser<'tokenTag, 'token, 'expr>
|
||||||
|
=
|
||||||
{
|
{
|
||||||
|
GetTag = getTag
|
||||||
UnaryPrefix = Map.empty
|
UnaryPrefix = Map.empty
|
||||||
UnaryPostfix = Map.empty
|
UnaryPostfix = Map.empty
|
||||||
Infix = Map.empty
|
Infix = Map.empty
|
||||||
@@ -32,7 +34,12 @@ module Parser =
|
|||||||
BracketLike = Map.empty
|
BracketLike = Map.empty
|
||||||
}
|
}
|
||||||
|
|
||||||
let withUnaryPrefix (tokenType : TokenType) (precedence : unit * int) (construct : Expr -> Expr) (parser : Parser) =
|
let withUnaryPrefix<'tokenTag, 'token, 'expr when 'tokenTag : comparison>
|
||||||
|
(tokenType : 'tokenTag)
|
||||||
|
(precedence : unit * int)
|
||||||
|
(construct : 'expr -> 'expr)
|
||||||
|
(parser : Parser<'tokenTag, 'token, 'expr>)
|
||||||
|
=
|
||||||
{ parser with
|
{ parser with
|
||||||
UnaryPrefix =
|
UnaryPrefix =
|
||||||
parser.UnaryPrefix
|
parser.UnaryPrefix
|
||||||
@@ -45,11 +52,11 @@ module Parser =
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let withUnaryPostfix
|
let withUnaryPostfix<'tokenTag, 'token, 'expr when 'tokenTag : comparison>
|
||||||
(tokenType : TokenType)
|
(tokenType : 'tokenTag)
|
||||||
(precedence : int * unit)
|
(precedence : int * unit)
|
||||||
(construct : Expr -> Expr)
|
(construct : 'expr -> 'expr)
|
||||||
(parser : Parser)
|
(parser : Parser<'tokenTag, 'token, 'expr>)
|
||||||
=
|
=
|
||||||
{ parser with
|
{ parser with
|
||||||
UnaryPostfix =
|
UnaryPostfix =
|
||||||
@@ -63,12 +70,12 @@ module Parser =
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let withInfix
|
let withInfix<'tokenTag, 'token, 'expr when 'tokenTag : comparison>
|
||||||
(tokenType : TokenType)
|
(tokenType : 'tokenTag)
|
||||||
(precedence : int * int)
|
(precedence : int * int)
|
||||||
(construct : Expr -> Expr -> Expr)
|
(construct : 'expr -> 'expr -> 'expr)
|
||||||
(parser : Parser)
|
(parser : Parser<'tokenTag, 'token, 'expr>)
|
||||||
: Parser
|
: Parser<'tokenTag, 'token, 'expr>
|
||||||
=
|
=
|
||||||
{ parser with
|
{ parser with
|
||||||
Infix =
|
Infix =
|
||||||
@@ -82,7 +89,12 @@ module Parser =
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let withBracketLike (tokenType : TokenType) (toAdd : BracketLikeParser) (parser : Parser) : Parser =
|
let withBracketLike<'tokenTag, 'token, 'expr when 'tokenTag : comparison>
|
||||||
|
(tokenType : 'tokenTag)
|
||||||
|
(toAdd : BracketLikeParser<'tokenTag, 'expr>)
|
||||||
|
(parser : Parser<'tokenTag, 'token, 'expr>)
|
||||||
|
: Parser<'tokenTag, 'token, 'expr>
|
||||||
|
=
|
||||||
{ parser with
|
{ parser with
|
||||||
BracketLike =
|
BracketLike =
|
||||||
parser.BracketLike
|
parser.BracketLike
|
||||||
@@ -95,18 +107,22 @@ module Parser =
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let defineAtoms (atom : string -> Token -> Expr option) (parser : Parser) : Parser =
|
let defineAtoms<'tokenTag, 'token, 'expr when 'tokenTag : comparison>
|
||||||
|
(atom : string -> 'token -> 'expr option)
|
||||||
|
(parser : Parser<'tokenTag, 'token, 'expr>)
|
||||||
|
: Parser<'tokenTag, 'token, 'expr>
|
||||||
|
=
|
||||||
{ parser with
|
{ parser with
|
||||||
Atom = atom
|
Atom = atom
|
||||||
}
|
}
|
||||||
|
|
||||||
let rec parseBracketLike
|
let rec private parseBracketLike
|
||||||
(parser : Parser)
|
(parser : Parser<'tokenTag, 'token, 'expr>)
|
||||||
(inputString : string)
|
(inputString : string)
|
||||||
(subParsers : BracketLikeParser list)
|
(subParsers : BracketLikeParser<'tokenTag, 'expr> list)
|
||||||
(exprsSoFar : Expr list)
|
(exprsSoFar : 'expr list)
|
||||||
(tokens : Token list)
|
(tokens : 'token list)
|
||||||
: (Expr * Token list) list
|
: ('expr * 'token list) list
|
||||||
=
|
=
|
||||||
let subParsersEnded, subParsersContinuing =
|
let subParsersEnded, subParsersContinuing =
|
||||||
subParsers |> List.partition _.BoundaryTokens.IsEmpty
|
subParsers |> List.partition _.BoundaryTokens.IsEmpty
|
||||||
@@ -136,7 +152,7 @@ module Parser =
|
|||||||
match subParser.BoundaryTokens with
|
match subParser.BoundaryTokens with
|
||||||
| [] -> failwith "logic error, this was ruled out earlier"
|
| [] -> failwith "logic error, this was ruled out earlier"
|
||||||
| head :: boundary ->
|
| head :: boundary ->
|
||||||
if head = next.Type then
|
if head = parser.GetTag next then
|
||||||
Some
|
Some
|
||||||
{ subParser with
|
{ subParser with
|
||||||
BoundaryTokens = boundary
|
BoundaryTokens = boundary
|
||||||
@@ -164,12 +180,12 @@ module Parser =
|
|||||||
fromSubParsersContinuing
|
fromSubParsersContinuing
|
||||||
|
|
||||||
/// The input string is only required so that the tokens have something to slice into.
|
/// The input string is only required so that the tokens have something to slice into.
|
||||||
and parseInner
|
and private parseInner
|
||||||
(parser : Parser)
|
(parser : Parser<'tokenTag, 'token, 'expr>)
|
||||||
(inputString : string)
|
(inputString : string)
|
||||||
(tokens : Token list)
|
(tokens : 'token list)
|
||||||
(minBinding : int)
|
(minBinding : int)
|
||||||
: Expr * Token list
|
: 'expr * 'token list
|
||||||
=
|
=
|
||||||
match tokens with
|
match tokens with
|
||||||
| [] -> failwith "cannot parse an empty list of tokens"
|
| [] -> failwith "cannot parse an empty list of tokens"
|
||||||
@@ -182,7 +198,7 @@ module Parser =
|
|||||||
token, rest
|
token, rest
|
||||||
| None ->
|
| None ->
|
||||||
|
|
||||||
match parser.BracketLike.TryGetValue firstToken.Type with
|
match parser.BracketLike.TryGetValue (parser.GetTag firstToken) with
|
||||||
| true, parse ->
|
| true, parse ->
|
||||||
// This is an ambiguous parse if multiple parsers genuinely matched.
|
// This is an ambiguous parse if multiple parsers genuinely matched.
|
||||||
// (We already filter to the longest possible matching parser.)
|
// (We already filter to the longest possible matching parser.)
|
||||||
@@ -195,7 +211,7 @@ module Parser =
|
|||||||
firstToken
|
firstToken
|
||||||
| false, _ ->
|
| false, _ ->
|
||||||
|
|
||||||
match parser.UnaryPrefix.TryGetValue firstToken.Type with
|
match parser.UnaryPrefix.TryGetValue (parser.GetTag firstToken) with
|
||||||
| true, (((), precedence), assemble) ->
|
| true, (((), precedence), assemble) ->
|
||||||
printfn "Parsing a prefix op: %+A" firstToken
|
printfn "Parsing a prefix op: %+A" firstToken
|
||||||
let rhs, rest = parseInner parser inputString rest precedence
|
let rhs, rest = parseInner parser inputString rest precedence
|
||||||
@@ -203,12 +219,12 @@ module Parser =
|
|||||||
assemble rhs, rest
|
assemble rhs, rest
|
||||||
| false, _ -> failwithf "didn't get an atom or prefix, got: %+A" firstToken
|
| false, _ -> failwithf "didn't get an atom or prefix, got: %+A" firstToken
|
||||||
|
|
||||||
let rec go (lhs : Expr) (tokens : Token list) : Expr * Token list =
|
let rec go (lhs : 'expr) (tokens : 'token list) : 'expr * 'token list =
|
||||||
match tokens with
|
match tokens with
|
||||||
| [] -> lhs, []
|
| [] -> lhs, []
|
||||||
| op :: rest ->
|
| op :: rest ->
|
||||||
|
|
||||||
match parser.UnaryPostfix.TryGetValue op.Type with
|
match parser.UnaryPostfix.TryGetValue (parser.GetTag op) with
|
||||||
| true, ((precedence, ()), construct) ->
|
| true, ((precedence, ()), construct) ->
|
||||||
if precedence < minBinding then
|
if precedence < minBinding then
|
||||||
printfn "Hit a postfix op which does not bind: %+A" op
|
printfn "Hit a postfix op which does not bind: %+A" op
|
||||||
@@ -218,7 +234,7 @@ module Parser =
|
|||||||
go (construct lhs) rest
|
go (construct lhs) rest
|
||||||
| false, _ ->
|
| false, _ ->
|
||||||
|
|
||||||
match parser.Infix.TryGetValue op.Type with
|
match parser.Infix.TryGetValue (parser.GetTag op) with
|
||||||
| true, ((leftBinding, rightBinding), construct) ->
|
| true, ((leftBinding, rightBinding), construct) ->
|
||||||
if leftBinding < minBinding then
|
if leftBinding < minBinding then
|
||||||
printfn "Hit an infix op which does not bind on the left: %+A" op
|
printfn "Hit an infix op which does not bind on the left: %+A" op
|
||||||
|
Reference in New Issue
Block a user