Skip to content

Commit

Permalink
Core: fix FSharpLint warning
Browse files Browse the repository at this point in the history
Fixing the warning for MaxLinesInFile rule:

```
========== Linting C:\Users\PC\source\repos\FSharpLint\src\FSharpLint.Core\Framework\HintParser.fs ==========
File suggested to be less than 1001 lines long.
Error on line 1001 starting at column 0

^
See https://fsprojects.github.io/FSharpLint/how-tos/rules/FL0062.html
--------------------------------------------------------------------------------
```
  • Loading branch information
Mersho committed Feb 12, 2024
1 parent 7417682 commit e263e32
Show file tree
Hide file tree
Showing 8 changed files with 396 additions and 385 deletions.
2 changes: 2 additions & 0 deletions src/FSharpLint.Core/FSharpLint.Core.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<Compile Include="AssemblyInfo.fs" />
<Compile Include="Prelude.fs" />
<Compile Include="Framework/Utilities.fs" />
<Compile Include="Framework\HintParserTypes.fs" />
<Compile Include="Framework\HintParserUtilities.fs" />
<Compile Include="Framework/HintParser.fs" />
<Compile Include="Framework\Suggestion.fs" />
<Compile Include="Framework\Ast.fs" />
Expand Down
381 changes: 1 addition & 380 deletions src/FSharpLint.Core/Framework/HintParser.fs

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions src/FSharpLint.Core/Framework/HintParserTypes.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
namespace FSharpLint.Framework

module HintParserTypes =

type Constant =
| Byte of byte
| Bytes of byte[]
| Char of char
| Decimal of decimal
| Double of double
| Int16 of int16
| Int32 of int32
| Int64 of int64
| IntPtr of nativeint
| SByte of sbyte
| Single of single
| UInt16 of uint16
| UInt32 of uint32
| UInt64 of uint64
| UIntPtr of unativeint
| UserNum of bigint * char
| String of string
| Unit
| Bool of bool

[<RequireQualifiedAccess>]
type Pattern =
| Cons of Pattern * Pattern
| Or of Pattern * Pattern
| Wildcard
| Variable of char
| Identifier of string list
| Constant of Constant
| Parentheses of Pattern
| Tuple of Pattern list
| List of Pattern list
| Array of Pattern list
| Null

[<RequireQualifiedAccess>]
type Expression =
| FunctionApplication of Expression list
| InfixOperator of operatorIdentifier:Expression * Expression * Expression
| PrefixOperator of operatorIdentifier:Expression * Expression
| AddressOf of singleAmpersand:bool * Expression
| Wildcard
| Variable of char
| Identifier of string list
| Constant of Constant
| Parentheses of Expression
| Lambda of LambdaArg list * LambdaBody
| LambdaBody of Expression
| LambdaArg of Expression
| Tuple of Expression list
| List of Expression list
| Array of Expression list
| If of cond:Expression * body:Expression * ``else``:Expression option
| Else of Expression
| Null
and LambdaArg = LambdaArg of Expression
and LambdaBody = LambdaBody of Expression

type HintNode =
| HintPat of Pattern
| HintExpr of Expression

type Suggestion =
| Expr of Expression
| Message of string

type Hint =
{ MatchedNode:HintNode
Suggestion:Suggestion }
313 changes: 313 additions & 0 deletions src/FSharpLint.Core/Framework/HintParserUtilities.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
namespace FSharpLint.Framework

open HintParserTypes

/// Provides a way of creating a single list from any number of hint ASTs.
/// Means we can simply iterate over a single list for each node in the F# tree
/// when matching hints rather than check each hint AST for each node.
module MergeSyntaxTrees =

open System.Collections.Generic

type SyntaxHintNode =
| Identifier = 1uy
| Null = 2uy
| Expression = 3uy
| FuncApp = 4uy
| Unit = 5uy
| AddressOf = 6uy

| If = 10uy
| Else = 11uy

| Lambda = 20uy
| LambdaArg = 21uy
| LambdaBody = 22uy

| ArrayOrList = 30uy
| Tuple = 31uy

| Variable = 40uy
| Wildcard = 41uy

| ConstantBool = 51uy
| ConstantByte = 52uy
| ConstantChar = 53uy
| ConstantDecimal = 54uy
| ConstantDouble = 55uy
| ConstantInt16 = 56uy
| ConstantInt32 = 57uy
| ConstantInt64 = 58uy
| ConstantIntPtr = 59uy
| ConstantSByte = 60uy
| ConstantSingle = 61uy
| ConstantString = 62uy
| ConstantUInt16 = 63uy
| ConstantUInt32 = 64uy
| ConstantUInt64 = 65uy
| ConstantUIntPtr = 66uy
| ConstantBytes = 67uy
| ConstantUserNum = 68uy

| Cons = 101uy
| And = 102uy
| Or = 103uy

[<NoComparison>]
type Node =
{ Edges:Edges
MatchedHint:Hint list }
and [<CustomEquality; NoComparison>] Edges =
{ Lookup:Dictionary<int, Node>
AnyMatch:(char option * Node) list }

override this.Equals(other) =
match other with
| :? Edges as rhs ->
let getList dict = Seq.toList dict |> List.map (fun (dictItems:KeyValuePair<_, _>) -> (dictItems.Key, dictItems.Value))

this.AnyMatch = rhs.AnyMatch &&
this.Lookup.Count = rhs.Lookup.Count &&
getList this.Lookup = getList rhs.Lookup
| _ -> false

override this.GetHashCode() = hash (this.AnyMatch, hash this.Lookup)

static member Empty = { Lookup = Dictionary<_, _>(); AnyMatch = List.Empty }

let private getConstKey = function
| Constant.Unit -> SyntaxHintNode.Unit
| Constant.Bool(_) -> SyntaxHintNode.ConstantBool
| Constant.Byte(_) -> SyntaxHintNode.ConstantByte
| Constant.Bytes(_) -> SyntaxHintNode.ConstantBytes
| Constant.Char(_) -> SyntaxHintNode.ConstantChar
| Constant.Decimal(_) -> SyntaxHintNode.ConstantDecimal
| Constant.Double(_) -> SyntaxHintNode.ConstantDouble
| Constant.Int16(_) -> SyntaxHintNode.ConstantInt16
| Constant.Int32(_) -> SyntaxHintNode.ConstantInt32
| Constant.Int64(_) -> SyntaxHintNode.ConstantInt64
| Constant.IntPtr(_) -> SyntaxHintNode.ConstantIntPtr
| Constant.SByte(_) -> SyntaxHintNode.ConstantSByte
| Constant.Single(_) -> SyntaxHintNode.ConstantSingle
| Constant.String(_) -> SyntaxHintNode.ConstantString
| Constant.UInt16(_) -> SyntaxHintNode.ConstantUInt16
| Constant.UInt32(_) -> SyntaxHintNode.ConstantUInt32
| Constant.UInt64(_) -> SyntaxHintNode.ConstantUInt64
| Constant.UIntPtr(_) -> SyntaxHintNode.ConstantUIntPtr
| Constant.UserNum(_) -> SyntaxHintNode.ConstantUserNum

let rec private getExprKey = function
| Expression.FunctionApplication(_)
| Expression.InfixOperator(_)
| Expression.PrefixOperator(_) -> SyntaxHintNode.FuncApp
| Expression.AddressOf(_) -> SyntaxHintNode.AddressOf
| Expression.Parentheses(expr) -> getExprKey expr
| Expression.Lambda(_) -> SyntaxHintNode.Lambda
| Expression.LambdaArg(_) -> SyntaxHintNode.LambdaArg
| Expression.LambdaBody(_) -> SyntaxHintNode.LambdaBody
| Expression.Tuple(_) -> SyntaxHintNode.Tuple
| Expression.Constant(constant) -> getConstKey constant
| Expression.List(_)
| Expression.Array(_) -> SyntaxHintNode.ArrayOrList
| Expression.If(_) -> SyntaxHintNode.If
| Expression.Else(_) -> SyntaxHintNode.Else
| Expression.Identifier(_) -> SyntaxHintNode.Identifier
| Expression.Null -> SyntaxHintNode.Null
| Expression.Wildcard -> SyntaxHintNode.Wildcard
| Expression.Variable(_) -> SyntaxHintNode.Variable

let rec private getPatternKey = function
| Pattern.Cons(_) -> SyntaxHintNode.Cons
| Pattern.Or(_) -> SyntaxHintNode.Or
| Pattern.Wildcard -> SyntaxHintNode.Wildcard
| Pattern.Variable(_) -> SyntaxHintNode.Variable
| Pattern.Identifier(_) -> SyntaxHintNode.Identifier
| Pattern.Constant(constant) -> getConstKey constant
| Pattern.Parentheses(pattern) -> getPatternKey pattern
| Pattern.Tuple(_) -> SyntaxHintNode.Tuple
| Pattern.List(_)
| Pattern.Array(_) -> SyntaxHintNode.ArrayOrList
| Pattern.Null -> SyntaxHintNode.Null

let rec private getKey = function
| HintExpr(expr) -> getExprKey expr
| HintPat(pattern) -> getPatternKey pattern

let rec private getChildren = function
| HintExpr(Expression.Parentheses(expr)) -> getChildren <| HintExpr expr
| HintExpr(Expression.Lambda(args, LambdaBody(body))) ->
[ for LambdaArg(arg) in args -> HintExpr arg
yield HintExpr body ]
| HintExpr(Expression.LambdaArg(arg)) ->
[HintExpr arg]
| HintExpr(Expression.LambdaBody(body)) ->
[HintExpr body]
| HintExpr(Expression.InfixOperator(Expression.Identifier(["::"]) as ident, lhs, rhs)) ->
[HintExpr ident; HintExpr (Expression.Tuple([lhs; rhs]))]
| HintExpr(Expression.InfixOperator(ident, lhs, rhs)) ->
[HintExpr ident; HintExpr lhs; HintExpr rhs]
| HintExpr(Expression.PrefixOperator(ident, expr)) ->
[HintExpr ident; HintExpr expr]
| HintExpr(Expression.AddressOf(_, expr)) -> [HintExpr expr]
| HintExpr(Expression.FunctionApplication(exprs))
| HintExpr(Expression.Tuple(exprs))
| HintExpr(Expression.List(exprs))
| HintExpr(Expression.Array(exprs)) -> exprs |> List.map HintExpr
| HintExpr(Expression.If(ifCond, bodyExpr, Some(elseExpr))) ->
[HintExpr ifCond; HintExpr bodyExpr; HintExpr elseExpr]
| HintExpr(Expression.If(ifCond, bodyExpr, None)) ->
[HintExpr ifCond; HintExpr bodyExpr]
| HintExpr(Expression.Else(expression)) -> [HintExpr expression]
| HintExpr(Expression.Identifier(_))
| HintExpr(Expression.Constant(_))
| HintExpr(Expression.Null)
| HintExpr(Expression.Wildcard)
| HintExpr(Expression.Variable(_)) -> List.Empty
| HintPat(Pattern.Cons(lhs, rhs))
| HintPat(Pattern.Or(lhs, rhs)) -> [HintPat lhs; HintPat rhs]
| HintPat(Pattern.Array(patterns))
| HintPat(Pattern.List(patterns))
| HintPat(Pattern.Tuple(patterns)) -> patterns |> List.map HintPat
| HintPat(Pattern.Parentheses(pattern)) -> [HintPat pattern]
| HintPat(Pattern.Variable(_))
| HintPat(Pattern.Identifier(_))
| HintPat(Pattern.Constant(_))
| HintPat(Pattern.Wildcard)
| HintPat(Pattern.Null) -> List.Empty

let private getConstantHashCode = function
| Constant.Bool value -> hash value
| Constant.Byte value -> hash value
| Constant.Bytes value -> hash value
| Constant.Char value -> hash value
| Constant.Decimal value -> hash value
| Constant.Double value -> hash value
| Constant.Int16 value -> hash value
| Constant.Int32 value -> hash value
| Constant.Int64 value -> hash value
| Constant.IntPtr value -> hash value
| Constant.SByte value -> hash value
| Constant.Single value -> hash value
| Constant.String value -> hash value
| Constant.UInt16 value -> hash value
| Constant.UInt32 value -> hash value
| Constant.UInt64 value -> hash value
| Constant.UIntPtr value -> hash value
| Constant.UserNum(intValue, charValue) -> hash (intValue, charValue)
| _ -> 0

let private getIdentifierHashCode = function
| identifier when (List.isEmpty >> not) identifier ->
match (Seq.tryLast identifier) with
| Some value -> value |> ExpressionUtilities.identAsCompiledOpName |> hash
| None -> failwith "There's no last element in identifier."
| _ -> 0

let rec private getHashCode node =
match node with
| HintExpr(Expression.Identifier(identifier))
| HintPat(Pattern.Identifier(identifier)) -> getIdentifierHashCode identifier
| HintExpr(Expression.Constant(constant))
| HintPat(Pattern.Constant(constant)) -> getConstantHashCode constant
| HintExpr(Expression.Parentheses(expr)) -> getHashCode <| HintExpr expr
| HintPat(Pattern.Parentheses(expr)) -> getHashCode <| HintPat expr
| _ -> 0

let private hintToList (hint:Hint) =
let nodes = Queue<_>()

let rec depthFirstTraversal expr depth =
let children = getChildren expr

nodes.Enqueue(expr, depth)

for child in children do
depthFirstTraversal child (depth + 1)

depthFirstTraversal hint.MatchedNode 0

(nodes |> Seq.toList, hint)

type private HintList = (HintNode * int) list * Hint

type private TransposedNode =
| HintNode of key:HintNode * depth:int * rest:HintList
| EndOfHint of Hint

/// Gets the head of each given list
let private transposeHead hintLists =
let rec transposeHead builtList = function
| (((key, depth)::tail), hint)::rest ->
let restOfHintList = (tail, hint)
let next = HintNode(key, depth, restOfHintList)::builtList
transposeHead next rest
| ([], hint)::rest ->
let next = EndOfHint(hint)::builtList
transposeHead next rest
| [] -> builtList

transposeHead List.Empty hintLists

let isAnyMatch = function
| ((SyntaxHintNode.Wildcard | SyntaxHintNode.Variable), _, _, _) -> true
| _ -> false

let getHints items = items |> Seq.map (fun (_, _, _, hint) -> hint) |> Seq.toList

let mergeHints hints =
let rec getEdges transposed =
let map = Dictionary<_, _>()

transposed
|> List.choose (function
| HintNode(expr, depth, rest) -> Some(getKey expr, expr, depth, rest)
| EndOfHint(_) -> None)
|> List.filter (isAnyMatch >> not)
|> Seq.groupBy (fun (key, expr, _, _) -> Utilities.hash2 key (getHashCode expr))
|> Seq.iter (fun (hashcode, items) -> map.Add(hashcode, mergeHints (getHints items)))

let anyMatches =
transposed
|> List.choose (function
| HintNode(expr, depth, rest) ->
match (getKey expr, expr) with
| (SyntaxHintNode.Wildcard as key), HintExpr(Expression.Wildcard)
| (SyntaxHintNode.Wildcard as key), HintPat(Pattern.Wildcard)
| (SyntaxHintNode.Variable as key), HintExpr(Expression.Variable(_))
| (SyntaxHintNode.Variable as key), HintPat(Pattern.Variable(_)) ->
Some(key, expr, depth, rest)
| _ -> None
| EndOfHint(_) -> None)
|> Seq.groupBy (fun (_, expr, _, _) -> expr)
|> Seq.choose
(fun (expr, items) ->
match expr with
| HintPat(Pattern.Wildcard)
| HintExpr(Expression.Wildcard) -> Some(None, mergeHints (getHints items))
| HintPat(Pattern.Variable(var))
| HintExpr(Expression.Variable(var)) -> Some(Some(var), mergeHints (getHints items))
| _ -> None)
|> Seq.toList

{ Lookup = map
AnyMatch = anyMatches }

and mergeHints hints =
let transposed = transposeHead hints

let edges = getEdges transposed

let matchedHints =
transposed
|> Seq.choose (function
| HintNode(_) -> None
| EndOfHint(hint) -> Some(hint))
|> Seq.toList

{ Edges = edges
MatchedHint = matchedHints }

let transposed =
hints |> List.map hintToList |> transposeHead

getEdges transposed
Loading

0 comments on commit e263e32

Please sign in to comment.