1
1
module BlackFox.Stidgen.ConfigurationParser
2
2
3
3
open BlackFox.Stidgen .Description
4
+ open ExtCore.Control
4
5
5
6
type LineContent =
6
7
| TypeDefinition of visibility : string * namespace' : string * name : string * underlyingType : string
7
8
| Property of name : string * value : string
8
9
9
10
type Line =
10
11
{
12
+ Number : int
11
13
Text : string
14
+ }
15
+ override x.ToString () = sprintf " %i : %s " x.Number x.Text
16
+
17
+ type ParsedLine =
18
+ {
19
+ Line : Line
12
20
Content : LineContent
13
21
}
22
+ override x.ToString () = x.Line.ToString()
23
+
24
+ type ErrorText = string
25
+ type ParseError =
26
+ {
27
+ Line : Line
28
+ ErrorText : ErrorText
29
+ IsInvalidType : bool
30
+ }
31
+ static member fromString text line = { Line = line; ErrorText = text; IsInvalidType = false }
32
+ override x.ToString () = sprintf " Error line %i : %s " x.Line.Number x.ErrorText
14
33
15
34
module private TextParser =
16
35
let isEmpty ( line : string ) = System.String.IsNullOrWhiteSpace line
17
36
let isComment ( line : string ) = line.Trim() .StartsWith( " //" )
18
37
19
- let filterOut cond = Seq.filter ( fun x -> not ( cond x))
38
+ let filterOut cond = Seq.filter ( fun ( x : Line ) -> not ( cond x.Text ))
20
39
21
- let parseTypeDefinition ( line : string ) =
22
- let fail () = failwith ( " Invalid type definition:" + line)
40
+ let parseTypeDefinition ( line : string ) : Choice < LineContent , ErrorText > =
41
+ let fail details =
42
+ Choice2Of2 ( sprintf " Invalid type definition, should %s like 'public MyType<int>'" details)
23
43
24
44
match line.Split( ' ' ) with
25
45
| [| visibility; rest |] ->
26
46
match rest.Split( '<' ) with
27
47
| [| fullName; underlyingType |] ->
28
- if not ( underlyingType.EndsWith( " >" )) then fail()
29
- let underlyingType = underlyingType.Substring( 0 , underlyingType.Length - 1 )
30
- let lastDot = fullName.LastIndexOf( '.' )
31
- let namespace ' =
32
- if lastDot <> - 1 then
33
- fullName.Substring( 0 , lastDot)
34
- else
35
- " "
36
- let name = fullName.Substring( lastDot+ 1 )
48
+ match underlyingType with
49
+ | _ when underlyingType.Length = 0 -> fail " have a non-empty underlying type"
50
+ | _ when fullName.Length = 0 -> fail " have a non-empty type name"
51
+ | _ when not ( underlyingType.EndsWith( " >" )) -> fail " end with >"
52
+ | _ ->
53
+ let underlyingType = underlyingType.Substring( 0 , underlyingType.Length - 1 )
54
+ let lastDot = fullName.LastIndexOf( '.' )
55
+ let namespace ' =
56
+ if lastDot <> - 1 then
57
+ fullName.Substring( 0 , lastDot)
58
+ else
59
+ " "
60
+ let name = fullName.Substring( lastDot+ 1 )
37
61
38
- TypeDefinition( visibility, namespace', name, underlyingType)
62
+ Choice1Of2 ( TypeDefinition( visibility, namespace', name, underlyingType) )
39
63
40
- | _ -> fail()
41
- | _ -> fail()
64
+ | _ -> fail " contain an underlying type between <> "
65
+ | _ -> fail " contain one space "
42
66
43
- let parseProperty ( line : string ) =
67
+ let parseProperty ( line : string ) : Choice < LineContent , ErrorText > =
44
68
let separator = line.IndexOf( ':' )
45
69
match separator with
46
- | - 1 -> failwith ( " Invalid property definition: " + line )
70
+ | - 1 -> Choice2Of2 " Invalid property definition, should be 'name:value' "
47
71
| _ ->
48
72
let name = line.Substring( 0 , separator)
49
73
let value = line.Substring( separator+ 1 )
50
- Property( name, value)
74
+ Choice1Of2 ( Property( name, value) )
51
75
52
- let parseLine ( line : string ) =
53
- let firstChar = line.[ 0 ]
54
- let content =
55
- if firstChar = ' ' || firstChar = '\t' then
56
- parseProperty ( line.Trim())
57
- else
58
- parseTypeDefinition ( line.Trim())
76
+ let augmentChoice line isType contentOrErrorText =
77
+ match contentOrErrorText with
78
+ | Choice1Of2( content) -> Choice1Of2 { Line = line; Content = content }
79
+ | Choice2Of2( errorText) -> Choice2Of2 { Line = line; ErrorText = errorText; IsInvalidType = isType }
59
80
60
- { Text = line; Content = content }
81
+ let parseLine ( line : Line ) =
82
+ let text = line.Text
83
+ let firstChar = text.[ 0 ]
84
+
85
+ if firstChar = ' ' || firstChar = '\t' then
86
+ parseProperty ( text.Trim()) |> augmentChoice line false
87
+ else
88
+ parseTypeDefinition ( text.Trim()) |> augmentChoice line true
61
89
62
90
let parseLines lines =
63
91
lines
92
+ |> Seq.mapi ( fun i l -> { Number = i+ 1 ; Text = l})
64
93
|> filterOut isEmpty
65
94
|> filterOut isComment
66
95
|> Seq.map parseLine
@@ -69,106 +98,149 @@ type Configuration =
69
98
{
70
99
Path : string option
71
100
Types : IdType list
101
+ Errors : ParseError list
72
102
}
73
103
74
104
module private LineParser =
75
105
let parseCast ( text : string ) =
76
106
let lowerCasedText = text.ToLower()
77
107
match lowerCasedText with
78
- | " explicit" -> Cast.Explicit
79
- | " implicit" -> Cast.Implicit
80
- | _ -> failwith ( sprintf " Unknown cast type: '%s '" text)
108
+ | " explicit" -> Choice1Of2 Cast.Explicit
109
+ | " implicit" -> Choice1Of2 Cast.Implicit
110
+ | _ -> Choice2Of2 ( sprintf " Unknown cast type: '%s '" text)
81
111
82
112
let parseOptionalString ( text : string ) =
83
113
if System.String.IsNullOrWhiteSpace( text) then
84
114
Option.None
85
115
else
86
116
Some( text)
87
117
88
- let addProperty ' ( name : string ) ( value : string ) ( idType : IdType ) =
118
+ let addProperty ' ( name : string ) ( value : string ) ( idType : IdType ) = choice {
89
119
match name with
90
- | " ValueProperty" -> { idType with ValueProperty = value }
91
- | " AllowNull" -> { idType with AllowNull = bool.Parse( value) }
92
- | " InternString" -> { idType with InternString = bool.Parse( value) }
93
- | " EqualsUnderlying" -> { idType with EqualsUnderlying = bool.Parse( value) }
94
- | " CastToUnderlying" -> { idType with CastToUnderlying = parseCast( value) }
95
- | " CastFromUnderlying" -> { idType with CastFromUnderlying = parseCast( value) }
96
- | " FileName" -> { idType with FileName = parseOptionalString( value) }
97
- | _ -> failwith( sprintf " Property '%s ' isn't supported" name)
98
-
99
- let addProperty ( content : LineContent ) ( idType : IdType ) =
120
+ | " ValueProperty" -> return { idType with ValueProperty = value }
121
+ | " AllowNull" -> return { idType with AllowNull = bool.Parse( value) }
122
+ | " InternString" -> return { idType with InternString = bool.Parse( value) }
123
+ | " EqualsUnderlying" -> return { idType with EqualsUnderlying = bool.Parse( value) }
124
+ | " CastToUnderlying" ->
125
+ let! cast = parseCast( value)
126
+ return { idType with CastToUnderlying = cast }
127
+ | " CastFromUnderlying" ->
128
+ let! cast = parseCast( value)
129
+ return { idType with CastFromUnderlying = cast }
130
+ | " FileName" -> return { idType with FileName = parseOptionalString( value) }
131
+ | _ -> return ! Choice2Of2 ( sprintf " Property '%s ' isn't supported" name)
132
+ }
133
+
134
+ let addProperty ( content : LineContent ) ( idType : IdType ) = choice {
100
135
match content with
101
136
| Property( name, value) ->
102
- idType |> addProperty' ( name.Trim()) ( value.Trim())
103
- | _ -> failwith " Not a property definition"
104
-
105
- let parseUnderlyingType = function
106
- | " bool" -> typeof< bool>
107
- | " byte" -> typeof< byte>
108
- | " sbyte" -> typeof< sbyte>
109
- | " char" -> typeof< char>
110
- | " decimal" -> typeof< decimal>
111
- | " double" -> typeof< double>
112
- | " float" -> typeof< single>
113
- | " int" -> typeof< int>
114
- | " uint" -> typeof< System.UInt32>
115
- | " long" -> typeof< System.Int64>
116
- | " ulong" -> typeof< System.UInt64>
117
- | " object" -> typeof< obj>
118
- | " short" -> typeof< System.Int16>
119
- | " ushort" -> typeof< System.UInt16>
120
- | " string" -> typeof< string>
137
+ let! newType = idType |> addProperty' ( name.Trim()) ( value.Trim())
138
+ return newType
139
+ | _ -> return ! Choice2Of2 " Not a property definition"
140
+ }
141
+
142
+ let parseUnderlyingType typeName = choice {
143
+ match typeName with
144
+ | " bool" -> return typeof< bool>
145
+ | " byte" -> return typeof< byte>
146
+ | " sbyte" -> return typeof< sbyte>
147
+ | " char" -> return typeof< char>
148
+ | " decimal" -> return typeof< decimal>
149
+ | " double" -> return typeof< double>
150
+ | " float" -> return typeof< single>
151
+ | " int" -> return typeof< int>
152
+ | " uint" -> return typeof< System.UInt32>
153
+ | " long" -> return typeof< System.Int64>
154
+ | " ulong" -> return typeof< System.UInt64>
155
+ | " object" -> return typeof< obj>
156
+ | " short" -> return typeof< System.Int16>
157
+ | " ushort" -> return typeof< System.UInt16>
158
+ | " string" -> return typeof< string>
121
159
| s ->
122
160
let type ' = System.Type.GetType( s, false )
123
161
let type ' = if type' <> null then type' else System.Type.GetType( " System." + s, false )
124
162
125
163
if type' <> null then
126
- type'
164
+ return type'
127
165
else
128
- failwith ( sprintf " Type '%s ' not found." s)
166
+ return ! Choice2Of2 ( sprintf " Type '%s ' not found." s)
167
+ }
129
168
130
- let parseVisibility = function
131
- | " public" -> Public
132
- | " internal" -> Internal
133
- | s -> failwith ( sprintf " Visibility can't be parsed: '%s '" s)
169
+ let parseVisibility visibilityText = choice {
170
+ match visibilityText with
171
+ | " public" -> return Public
172
+ | " internal" -> return Internal
173
+ | s -> return ! Choice2Of2 ( sprintf " Visibility can't be parsed: '%s '" s)
174
+ }
134
175
135
- let typeDefinitionToType ( content : LineContent ) =
176
+ let typeDefinitionToType ( content : LineContent ) = choice {
136
177
match content with
137
178
| TypeDefinition( visibility, namespace', name, underlyingType) ->
138
- let underlyingType = parseUnderlyingType underlyingType
139
- makeIdType underlyingType ( fun idType ->
179
+ let! underlyingType = parseUnderlyingType underlyingType
180
+ let! visibility = parseVisibility visibility
181
+ return makeIdType underlyingType ( fun idType ->
140
182
{ idType with
141
183
Name = name
142
184
Namespace = namespace'
143
- Visibility = parseVisibility visibility
185
+ Visibility = visibility
144
186
}
145
187
)
146
- | _ -> failwith " Not a type definition"
188
+ | _ -> return ! Choice2Of2 " Not a type definition"
189
+ }
190
+
191
+ let propertyWithNoValidType ( line : ParsedLine ) =
192
+ {
193
+ Line = line.Line;
194
+ ErrorText = " Property line associated with no valid type" ;
195
+ IsInvalidType = false
196
+ }
147
197
148
- let rec linesToIdTypes ( currentType : IdType option ) ( remainingLines : Line list ) =
198
+ let rec linesToIdTypes ' newCurrentType remainingLines typeToAdd errorToAdd : IdType list * ParseError list =
199
+ let ( innerTypes , innerErrors ) = linesToIdTypes newCurrentType remainingLines
200
+ let innerTypes = match typeToAdd with | Some( t) -> t :: innerTypes | _ -> innerTypes
201
+ let innerErrors = match errorToAdd with | Some( t) -> t :: innerErrors | _ -> innerErrors
202
+ ( innerTypes, innerErrors)
203
+
204
+ and linesToIdTypes currentType remainingLines =
149
205
match remainingLines with
150
206
| line :: rest ->
151
- match line.Content with
152
- | Property(_,_) ->
153
- match currentType with
154
- | Some( idType) ->
155
- let idType = idType |> addProperty line.Content
156
- linesToIdTypes ( Some( idType)) rest
157
- | Option.None -> failwith ( " Found a property line before founding any type: " + line.Text)
158
- | TypeDefinition(_,_,_,_) ->
159
- let newType = typeDefinitionToType line.Content
160
- match currentType with
161
- | Some( idType) -> idType :: linesToIdTypes ( Some( newType)) rest
162
- | Option.None -> linesToIdTypes ( Some( newType)) rest
207
+ // Helper functions to make to rest clearer
208
+ let reportTypeError error = linesToIdTypes' Option.None rest currentType ( Some( error))
209
+ let reportPropertyError error = linesToIdTypes' currentType rest Option.None ( Some( error))
210
+ let finishCurrentType newType = linesToIdTypes' ( Some( newType)) rest currentType Option.None
211
+ let continueOnType type' = linesToIdTypes ( Some( type')) rest
212
+
213
+ match line with
214
+ | Choice1Of2( line) ->
215
+ match line.Content with
216
+ | Property(_,_) ->
217
+ match currentType with
218
+ | Some( idType) ->
219
+ let idType = idType |> addProperty line.Content
220
+ match idType with
221
+ | Choice1Of2( idType) -> continueOnType idType
222
+ | Choice2Of2( error) -> reportPropertyError ( ParseError.fromString error line.Line)
223
+ | Option.None -> reportPropertyError ( propertyWithNoValidType line)
224
+ | TypeDefinition(_,_,_,_) ->
225
+ let newType = typeDefinitionToType line.Content
226
+ match newType with
227
+ | Choice1Of2( newType) -> finishCurrentType newType
228
+ | Choice2Of2( error) -> reportTypeError ( ParseError.fromString error line.Line)
229
+ | Choice2Of2( error) ->
230
+ // If a type line was invalid we don't want the following properties to be associated with the previous type
231
+ // But if a property is invalid we want the following properties to be associated with the current type
232
+ match ( error.IsInvalidType, currentType) with
233
+ | ( true , Some( currentType)) -> reportTypeError error
234
+ | _ -> reportPropertyError error
163
235
| [] ->
164
236
match currentType with
165
- | Some( idType) -> [ idType]
166
- | Option.None -> []
237
+ | Some( idType) -> ( [ idType], [])
238
+ | Option.None -> ([], [])
167
239
168
- let linesToConfiguration ( lines : Line seq) : Configuration =
240
+ let linesToConfiguration ( lines : Choice < ParsedLine , ParseError > seq ) : Configuration =
169
241
let lines = lines |> List.ofSeq
170
- let types = linesToIdTypes Option.None lines
171
- { Path = Option.None; Types = types}
242
+ let ( types , errors ) = linesToIdTypes Option.None lines
243
+ { Path = Option.None; Types = types; Errors = errors }
172
244
173
245
let loadFromLines ( lines : string seq ) =
174
246
lines
0 commit comments