Split out into an example

This commit is contained in:
Smaug123
2024-01-22 21:42:25 +00:00
parent 09782649a6
commit 45287e250a
10 changed files with 37 additions and 8 deletions

View File

@@ -0,0 +1,77 @@
namespace ParseExample
[<RequireQualifiedAccess>]
type Expr =
| Plus of Expr * Expr
| Times of Expr * Expr
| UnaryMinus of Expr
| Minus of Expr * Expr
| Int of int
| FunctionCall of Expr * Expr
| Var of string
| Factorial of Expr
| Paren of Expr
| IfThenElse of Expr * Expr * Expr
| IfThen of Expr * Expr
[<RequireQualifiedAccess>]
module Expr =
let plus a b = Expr.Plus (a, b)
let times a b = Expr.Times (a, b)
let unaryMinus a = Expr.UnaryMinus a
let minus a b = Expr.Minus (a, b)
let constInt a = Expr.Int a
let functionCall f x = Expr.FunctionCall (f, x)
let var name = Expr.Var name
let factorial a = Expr.Factorial a
let paren a = Expr.Paren a
let ifThenElse ifClause thenClause elseClause =
Expr.IfThenElse (ifClause, thenClause, elseClause)
let ifThen ifClause thenClause = Expr.IfThen (ifClause, thenClause)
[<RequireQualifiedAccess>]
type TokenType =
| Plus
| Minus
| Times
| ConstInt
| LeftBracket
| RightBracket
| Var
| Factorial
| If
| Then
| Else
type Token =
{
Type : TokenType
/// The token is represented in the string as s.[left .. left + len], i.e. inclusive.
Trivia : int * int
}
[<RequireQualifiedAccess>]
module Token =
let standalone (ty : TokenType) (left : int) (len : int) =
{
Type = ty
Trivia = (left, len)
}
let standalone' (ty : TokenType) (singleCharPos : int) =
{
Type = ty
Trivia = (singleCharPos, 1)
}
let (|SingleChar|_|) (i : int, c : char) : Token option =
match c with
| '(' -> standalone' TokenType.LeftBracket i |> Some
| ')' -> standalone' TokenType.RightBracket i |> Some
| '*' -> standalone' TokenType.Times i |> Some
| '+' -> standalone' TokenType.Plus i |> Some
| '-' -> standalone' TokenType.Minus i |> Some
| '!' -> standalone' TokenType.Factorial i |> Some
| _ -> None

View File

@@ -0,0 +1,67 @@
namespace ParseExample
open System
open System.Globalization
open PrattParser
[<RequireQualifiedAccess>]
module Example =
let private atom (inputString : string) (token : Token) : Expr option =
match token.Type with
| TokenType.ConstInt ->
let start, len = token.Trivia
Int32.Parse (inputString.AsSpan().Slice (start, len), NumberStyles.None, CultureInfo.InvariantCulture)
|> Expr.constInt
|> Some
| TokenType.Var ->
let start, len = token.Trivia
Some (Expr.var (inputString.Substring (start, len)))
| TokenType.Plus
| TokenType.Minus
| TokenType.Times
| TokenType.Factorial
| TokenType.If
| TokenType.Then
| TokenType.Else
| TokenType.LeftBracket
| TokenType.RightBracket -> None
let parser =
Parser.empty _.Type
|> Parser.defineAtoms atom
|> Parser.withUnaryPostfix TokenType.Factorial (7, ()) Expr.factorial
|> Parser.withUnaryPrefix TokenType.Plus ((), 5) id
|> Parser.withUnaryPrefix TokenType.Minus ((), 5) Expr.unaryMinus
|> Parser.withInfix TokenType.Plus (1, 2) Expr.plus
|> Parser.withInfix TokenType.Minus (1, 2) Expr.minus
|> Parser.withInfix TokenType.Times (1, 2) Expr.times
|> Parser.withBracketLike
TokenType.LeftBracket
{
ConsumeAfterFinalToken = false
BoundaryTokens = [ TokenType.RightBracket ]
Construct = Seq.exactlyOne >> Expr.paren
}
|> Parser.withBracketLike
TokenType.If
{
ConsumeAfterFinalToken = true
BoundaryTokens = [ TokenType.Then ; TokenType.Else ]
Construct =
fun s ->
match s with
| [ ifClause ; thenClause ; elseClause ] -> Expr.ifThenElse ifClause thenClause elseClause
| _ -> failwith "logic error"
}
|> Parser.withBracketLike
TokenType.If
{
ConsumeAfterFinalToken = true
BoundaryTokens = [ TokenType.Then ]
Construct =
fun s ->
match s with
| [ ifClause ; thenClause ] -> Expr.ifThen ifClause thenClause
| _ -> failwith "logic error"
}

View File

@@ -0,0 +1,56 @@
namespace ParseExample
[<RequireQualifiedAccess>]
module Lexer =
let private (|Digit|_|) (c : char) : byte option =
if '0' <= c && c <= '9' then
Some (byte c - byte '0')
else
None
let lex (s : string) : Token seq =
seq {
let mutable i = 0
while i < s.Length do
match i, s.[i] with
| Token.SingleChar token ->
i <- i + 1
yield token
| startI, Digit _ ->
i <- i + 1
let mutable shouldBreak = false
while i < s.Length && not shouldBreak do
match s.[i] with
| Digit _ -> i <- i + 1
| _ -> shouldBreak <- true
yield Token.standalone TokenType.ConstInt startI (i - startI)
| _, ' ' -> i <- i + 1
| _, 'i' when i < s.Length - 1 && s.[i + 1] = 'f' ->
yield Token.standalone TokenType.If i 2
i <- i + 2
| _, 't' when i < s.Length - 3 && s.[i + 1 .. i + 3] = "hen" ->
yield Token.standalone TokenType.Then i 4
i <- i + 4
| _, 'e' when i < s.Length - 3 && s.[i + 1 .. i + 3] = "lse" ->
yield Token.standalone TokenType.Else i 4
i <- i + 4
| startI, c ->
if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') then
i <- i + 1
let mutable shouldBreak = false
while i < s.Length && not shouldBreak do
let c = s.[i]
if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') then
i <- i + 1
else
shouldBreak <- true
yield Token.standalone TokenType.Var startI (i - startI)
else
failwithf "Could not tokenize, char %c, at position %i" c startI
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Domain.fs" />
<Compile Include="Lexer.fs" />
<Compile Include="Example.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PrattParser\PrattParser.fsproj" />
</ItemGroup>
</Project>