mirror of
https://github.com/Smaug123/WoofWare.PrattParser
synced 2025-10-05 09:28:41 +00:00
Initial WIP commit
This commit is contained in:
18
.config/dotnet-tools.json
Normal file
18
.config/dotnet-tools.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"fantomas": {
|
||||
"version": "6.3.0-alpha-005",
|
||||
"commands": [
|
||||
"fantomas"
|
||||
]
|
||||
},
|
||||
"fsharp-analyzers": {
|
||||
"version": "0.23.0",
|
||||
"commands": [
|
||||
"fsharp-analyzers"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
41
.editorconfig
Normal file
41
.editorconfig
Normal file
@@ -0,0 +1,41 @@
|
||||
root=true
|
||||
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=crlf
|
||||
trim_trailing_whitespace=true
|
||||
insert_final_newline=true
|
||||
indent_style=space
|
||||
indent_size=4
|
||||
|
||||
# ReSharper properties
|
||||
resharper_xml_indent_size=2
|
||||
resharper_xml_max_line_length=100
|
||||
resharper_xml_tab_width=2
|
||||
|
||||
[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[*.{fs,fsi}]
|
||||
fsharp_bar_before_discriminated_union_declaration=true
|
||||
fsharp_space_before_uppercase_invocation=true
|
||||
fsharp_space_before_class_constructor=true
|
||||
fsharp_space_before_member=true
|
||||
fsharp_space_before_colon=true
|
||||
fsharp_space_before_semicolon=true
|
||||
fsharp_multiline_bracket_style=aligned
|
||||
fsharp_newline_between_type_definition_and_members=true
|
||||
fsharp_align_function_signature_to_indentation=true
|
||||
fsharp_alternative_long_member_definitions=true
|
||||
fsharp_multi_line_lambda_closing_newline=true
|
||||
fsharp_experimental_keep_indent_in_branch=true
|
||||
fsharp_max_value_binding_width=80
|
||||
fsharp_max_record_width=0
|
||||
max_line_length=120
|
||||
end_of_line=lf
|
||||
|
||||
[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
tab_width=2
|
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
.idea/
|
||||
*.DotSettings.*
|
27
PrattParser.Test/PrattParser.Test.fsproj
Normal file
27
PrattParser.Test/PrattParser.Test.fsproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsPublishable>false</IsPublishable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="TestLexer.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FsUnit" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="NUnit" Version="4.0.1"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PrattParser\PrattParser.fsproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
66
PrattParser.Test/TestLexer.fs
Normal file
66
PrattParser.Test/TestLexer.fs
Normal file
@@ -0,0 +1,66 @@
|
||||
namespace PrattParser.Test
|
||||
|
||||
open PrattParser
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
|
||||
[<TestFixture>]
|
||||
module TestLexer =
|
||||
|
||||
[<Test>]
|
||||
let ``Lexer looks plausible`` () =
|
||||
let input = "g x y + a * func (b + 100)"
|
||||
|
||||
let expected =
|
||||
[
|
||||
{
|
||||
Type = TokenType.Var
|
||||
Trivia = (0, 1)
|
||||
}
|
||||
{
|
||||
Type = TokenType.Var
|
||||
Trivia = (2, 1)
|
||||
}
|
||||
{
|
||||
Type = TokenType.Var
|
||||
Trivia = (4, 1)
|
||||
}
|
||||
{
|
||||
Type = TokenType.Plus
|
||||
Trivia = (6, 1)
|
||||
}
|
||||
{
|
||||
Type = TokenType.Var
|
||||
Trivia = (8, 1)
|
||||
}
|
||||
{
|
||||
Type = TokenType.Times
|
||||
Trivia = (10, 1)
|
||||
}
|
||||
{
|
||||
Type = TokenType.Var
|
||||
Trivia = (12, 4)
|
||||
}
|
||||
{
|
||||
Type = TokenType.LeftBracket
|
||||
Trivia = (17, 1)
|
||||
}
|
||||
{
|
||||
Type = TokenType.Var
|
||||
Trivia = (18, 1)
|
||||
}
|
||||
{
|
||||
Type = TokenType.Plus
|
||||
Trivia = (20, 1)
|
||||
}
|
||||
{
|
||||
Type = TokenType.ConstInt
|
||||
Trivia = (22, 3)
|
||||
}
|
||||
{
|
||||
Type = TokenType.RightBracket
|
||||
Trivia = (25, 1)
|
||||
}
|
||||
]
|
||||
|
||||
Lexer.lex input |> shouldEqual expected
|
22
PrattParser.sln
Normal file
22
PrattParser.sln
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PrattParser", "PrattParser\PrattParser.fsproj", "{C8FBFA6B-2195-4350-BD05-709476A278D9}"
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PrattParser.Test", "PrattParser.Test\PrattParser.Test.fsproj", "{7317F801-6AB2-4C33-B6B2-DCB006880B42}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C8FBFA6B-2195-4350-BD05-709476A278D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C8FBFA6B-2195-4350-BD05-709476A278D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C8FBFA6B-2195-4350-BD05-709476A278D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C8FBFA6B-2195-4350-BD05-709476A278D9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7317F801-6AB2-4C33-B6B2-DCB006880B42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7317F801-6AB2-4C33-B6B2-DCB006880B42}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7317F801-6AB2-4C33-B6B2-DCB006880B42}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7317F801-6AB2-4C33-B6B2-DCB006880B42}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
59
PrattParser/Domain.fs
Normal file
59
PrattParser/Domain.fs
Normal file
@@ -0,0 +1,59 @@
|
||||
namespace PrattParser
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type Expr =
|
||||
| Plus of Expr * Expr
|
||||
| Times of Expr * Expr
|
||||
| UnaryMinus of Expr
|
||||
| Int of int
|
||||
| FunctionCall of Expr * Expr
|
||||
| Var of string
|
||||
|
||||
[<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 constInt a = Expr.Int a
|
||||
let functionCall f x = Expr.FunctionCall (f, x)
|
||||
let var name = Expr.Var name
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
type TokenType =
|
||||
| Plus
|
||||
| Minus
|
||||
| Times
|
||||
| ConstInt
|
||||
| LeftBracket
|
||||
| RightBracket
|
||||
| Var
|
||||
|
||||
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
|
||||
| _ -> None
|
47
PrattParser/Lexer.fs
Normal file
47
PrattParser/Lexer.fs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace PrattParser
|
||||
|
||||
[<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 not shouldBreak do
|
||||
match s.[i] with
|
||||
| Digit _ -> i <- i + 1
|
||||
| _ -> shouldBreak <- true
|
||||
|
||||
yield Token.standalone TokenType.ConstInt startI (i - startI)
|
||||
| _, ' ' -> i <- i + 1
|
||||
| startI, c ->
|
||||
if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') then
|
||||
i <- i + 1
|
||||
let mutable shouldBreak = false
|
||||
|
||||
while 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
|
||||
}
|
13
PrattParser/PrattParser.fsproj
Normal file
13
PrattParser/PrattParser.fsproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Domain.fs" />
|
||||
<Compile Include="Lexer.fs" />
|
||||
<None Include="Program.fs"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
95
PrattParser/Program.fs
Normal file
95
PrattParser/Program.fs
Normal file
@@ -0,0 +1,95 @@
|
||||
namespace PrattParser
|
||||
|
||||
open System.Collections.Generic
|
||||
|
||||
type Parser<'parser> = 'parser -> Token -> Expr option
|
||||
|
||||
type ParserSpec<'parser> =
|
||||
private
|
||||
{
|
||||
Prefixes : (Token -> Parser<'parser> option) list
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module ParserSpec =
|
||||
let empty<'parser> () =
|
||||
{
|
||||
Prefixes = []
|
||||
}
|
||||
|
||||
// TODO this is essentially duplicated which is a bit sad
|
||||
let private processPrefix (prefixOperation : Token) (operand : Expr) : Expr =
|
||||
match prefixOperation with
|
||||
| Token.Minus -> Expr.unaryMinus operand
|
||||
| token -> failwithf "logic error, this should not have happened: %+A, %+A" token operand
|
||||
|
||||
let addPrefixParser<'parser> (input : Token) (spec : ParserSpec<'parser>) =
|
||||
let parser (token : Token) =
|
||||
if token = input then
|
||||
Some (fun parser processPrefix token )
|
||||
else
|
||||
None
|
||||
{ spec with
|
||||
Prefixes = parser :: spec.Prefixes
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Parse =
|
||||
|
||||
/// Takes the current token.
|
||||
let prefixParser (parse : 'parser -> Expr) (toMatch : Token) : Parser<'parser> =
|
||||
fun parser token ->
|
||||
if token = toMatch then
|
||||
let operand = parse parser
|
||||
Some (processPrefix token operand)
|
||||
else None
|
||||
|
||||
let varParser (parse : 'parser -> Expr) (toMatch : Token) : Parser<'parser> =
|
||||
fun _ token ->
|
||||
match token with
|
||||
| Token.Var x -> Some (Expr.Var x)
|
||||
| _ -> None
|
||||
|
||||
let parserLookup (token : Token) (parser : ParserSpec<'parser>) : Parser<'parser> option =
|
||||
match parser.Prefixes token with
|
||||
| Some parser -> Some parser
|
||||
| None -> None
|
||||
|
||||
let parserSpec =
|
||||
ParserSpec.empty ()
|
||||
|> ParserSpec.addPrefixParser Token.Minus
|
||||
|
||||
let parseExpression (tokens : Token IEnumerator) : Expr =
|
||||
let token = tokens.MoveNext ()
|
||||
|
||||
module Program =
|
||||
|
||||
[<EntryPoint>]
|
||||
let main argv =
|
||||
// g x y + a * f (b + c)
|
||||
let tokens =
|
||||
[
|
||||
Token.Var "g"
|
||||
Token.Var "x"
|
||||
Token.Var "y"
|
||||
Token.Plus
|
||||
Token.Var "a"
|
||||
Token.Times
|
||||
Token.Var "f"
|
||||
Token.LeftBracket
|
||||
Token.Var "b"
|
||||
Token.Plus
|
||||
Token.Var "c"
|
||||
Token.RightBracket
|
||||
]
|
||||
|
||||
let expected =
|
||||
let gXY =
|
||||
Expr.functionCall (Expr.functionCall (Expr.var "g") (Expr.var "x")) (Expr.var "y")
|
||||
|
||||
let fAPlusB =
|
||||
Expr.functionCall (Expr.Var "f") (Expr.plus (Expr.Var "b") (Expr.Var "c"))
|
||||
|
||||
Expr.plus gXY (Expr.times (Expr.Var "a") fAPlusB)
|
||||
|
||||
0
|
Reference in New Issue
Block a user