├── .editorconfig ├── .gitignore ├── .paket ├── Paket.Restore.targets ├── paket.bootstrapper.exe ├── paket.exe.config └── paket.targets ├── .travis.yml ├── LICENSE ├── LICENSE.md ├── LSON.fs ├── LSON.sln ├── README.md ├── RELEASE_NOTES.md ├── appveyor.yml ├── build.cmd ├── build.fsx ├── build.sh ├── paket.dependencies ├── paket.lock ├── src └── LSON │ ├── LSON.fsproj │ └── Library.fs └── tests ├── LSON.Tests ├── LSON.Tests.fsproj ├── Main.fs ├── SExpressions.fs └── Tests.fs └── Source.Tests └── Source.Tests.fsproj /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: 2 | http://EditorConfig.org 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Default settings: 8 | # A newline ending every file 9 | # Use 4 spaces as indentation 10 | [*] 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{fs,fsi,fsx,config}] 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | 19 | [*.{fs,fsi,fsx}] 20 | indent_size = 2 21 | 22 | [paket.*] 23 | trim_trailing_whitespace = true 24 | indent_size = 2 25 | 26 | [*.paket.references] 27 | trim_trailing_whitespace = true 28 | indent_size = 2 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | packages/ 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.pfx 193 | *.publishsettings 194 | node_modules/ 195 | orleans.codegen.cs 196 | 197 | # Since there are multiple workflows, uncomment next line to ignore bower_components 198 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 199 | #bower_components/ 200 | 201 | # RIA/Silverlight projects 202 | Generated_Code/ 203 | 204 | # Backup & report files from converting an old project file 205 | # to a newer Visual Studio version. Backup files are not needed, 206 | # because we have git ;-) 207 | _UpgradeReport_Files/ 208 | Backup*/ 209 | UpgradeLog*.XML 210 | UpgradeLog*.htm 211 | 212 | # SQL Server files 213 | *.mdf 214 | *.ldf 215 | 216 | # Business Intelligence projects 217 | *.rdl.data 218 | *.bim.layout 219 | *.bim_*.settings 220 | 221 | # Microsoft Fakes 222 | FakesAssemblies/ 223 | 224 | # GhostDoc plugin setting file 225 | *.GhostDoc.xml 226 | 227 | # Node.js Tools for Visual Studio 228 | .ntvs_analysis.dat 229 | 230 | # Visual Studio 6 build log 231 | *.plg 232 | 233 | # Visual Studio 6 workspace options file 234 | *.opt 235 | 236 | # Visual Studio LightSwitch build output 237 | **/*.HTMLClient/GeneratedArtifacts 238 | **/*.DesktopClient/GeneratedArtifacts 239 | **/*.DesktopClient/ModelManifest.xml 240 | **/*.Server/GeneratedArtifacts 241 | **/*.Server/ModelManifest.xml 242 | _Pvt_Extensions 243 | 244 | # Paket dependency manager 245 | .paket/paket.exe 246 | paket-files/ 247 | 248 | # FAKE - F# Make 249 | .fake/ 250 | 251 | # JetBrains Rider 252 | .idea/ 253 | *.sln.iml 254 | 255 | TestResults.xml 256 | 257 | dist/ 258 | .ionide/ 259 | -------------------------------------------------------------------------------- /.paket/Paket.Restore.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 8 | 9 | $(MSBuildVersion) 10 | 15.0.0 11 | false 12 | true 13 | 14 | true 15 | $(MSBuildThisFileDirectory) 16 | $(MSBuildThisFileDirectory)..\ 17 | $(PaketRootPath)paket-files\paket.restore.cached 18 | $(PaketRootPath)paket.lock 19 | classic 20 | proj 21 | assembly 22 | native 23 | /Library/Frameworks/Mono.framework/Commands/mono 24 | mono 25 | 26 | 27 | $(PaketRootPath)paket.bootstrapper.exe 28 | $(PaketToolsPath)paket.bootstrapper.exe 29 | $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\ 30 | 31 | "$(PaketBootStrapperExePath)" 32 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" 33 | 34 | 35 | 36 | 37 | true 38 | true 39 | 40 | 41 | True 42 | 43 | 44 | False 45 | 46 | $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/')) 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | $(PaketRootPath)paket 56 | $(PaketToolsPath)paket 57 | 58 | 59 | 60 | 61 | 62 | $(PaketRootPath)paket.exe 63 | $(PaketToolsPath)paket.exe 64 | 65 | 66 | 67 | 68 | 69 | <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json")) 70 | <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"')) 71 | <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | <_PaketCommand>dotnet paket 83 | 84 | 85 | 86 | 87 | 88 | $(PaketToolsPath)paket 89 | $(PaketBootStrapperExeDir)paket 90 | 91 | 92 | paket 93 | 94 | 95 | 96 | 97 | <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) 98 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)" 99 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 100 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)" 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | true 122 | $(NoWarn);NU1603;NU1604;NU1605;NU1608 123 | false 124 | true 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) 134 | 135 | 136 | 137 | 138 | 139 | 141 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``)) 142 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``)) 143 | 144 | 145 | 146 | 147 | %(PaketRestoreCachedKeyValue.Value) 148 | %(PaketRestoreCachedKeyValue.Value) 149 | 150 | 151 | 152 | 153 | true 154 | false 155 | true 156 | 157 | 158 | 162 | 163 | true 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached 183 | 184 | $(MSBuildProjectFullPath).paket.references 185 | 186 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 187 | 188 | $(MSBuildProjectDirectory)\paket.references 189 | 190 | false 191 | true 192 | true 193 | references-file-or-cache-not-found 194 | 195 | 196 | 197 | 198 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) 199 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) 200 | references-file 201 | false 202 | 203 | 204 | 205 | 206 | false 207 | 208 | 209 | 210 | 211 | true 212 | target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths) 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | false 224 | true 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) 236 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) 237 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) 238 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) 239 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) 240 | 241 | 242 | %(PaketReferencesFileLinesInfo.PackageVersion) 243 | All 244 | runtime 245 | runtime 246 | true 247 | true 248 | 249 | 250 | 251 | 252 | $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) 262 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) 263 | 264 | 265 | %(PaketCliToolFileLinesInfo.PackageVersion) 266 | 267 | 268 | 269 | 273 | 274 | 275 | 276 | 277 | 278 | false 279 | 280 | 281 | 282 | 283 | 284 | <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/> 285 | 286 | 287 | 288 | 289 | 290 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile) 291 | true 292 | false 293 | true 294 | false 295 | true 296 | false 297 | true 298 | false 299 | true 300 | $(PaketIntermediateOutputPath)\$(Configuration) 301 | $(PaketIntermediateOutputPath) 302 | 303 | 304 | 305 | <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/> 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 363 | 364 | 407 | 408 | 450 | 451 | 492 | 493 | 494 | 495 | -------------------------------------------------------------------------------- /.paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/LSON/a611f3942ee61141b54ed91a08f371106e306a0f/.paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /.paket/paket.exe.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.paket/paket.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | $(MSBuildThisFileDirectory) 8 | $(MSBuildThisFileDirectory)..\ 9 | /Library/Frameworks/Mono.framework/Commands/mono 10 | mono 11 | 12 | 13 | 14 | 15 | $(PaketRootPath)paket.exe 16 | $(PaketToolsPath)paket.exe 17 | "$(PaketExePath)" 18 | $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 19 | 20 | 21 | 22 | 23 | 24 | $(MSBuildProjectFullPath).paket.references 25 | 26 | 27 | 28 | 29 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 30 | 31 | 32 | 33 | 34 | $(MSBuildProjectDirectory)\paket.references 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 47 | $(MSBuildProjectDirectory)\paket.references 48 | $(MSBuildStartupDirectory)\paket.references 49 | $(MSBuildProjectFullPath).paket.references 50 | $(PaketCommand) restore --references-files "$(PaketReferences)" 51 | 52 | RestorePackages; $(BuildDependsOn); 53 | 54 | 55 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | dotnet: 3.1.102 3 | 4 | script: 5 | - sh ./build.sh 6 | 7 | matrix: 8 | fast_finish: true 9 | 10 | branches: 11 | only: 12 | - master 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 F# Community Project Incubation Space 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LSON.fs: -------------------------------------------------------------------------------- 1 | module internal LSON 2 | open System 3 | // https://github.com/AshleyF/FScheme/blob/master/FScheme.fs 4 | 5 | type Token = 6 | | Open | Close 7 | | Quote | Unquote 8 | | String of string 9 | | Symbol of string 10 | 11 | let private tokenize source = 12 | let rec string acc = function 13 | | '\\' :: '"' :: t -> string (acc + "\"") t // escaped quote becomes quote 14 | | '\\' :: 'b' :: t -> string (acc + "\b") t // escaped backspace 15 | | '\\' :: 'f' :: t -> string (acc + "\f") t // escaped formfeed 16 | | '\\' :: 'n' :: t -> string (acc + "\n") t // escaped newline 17 | | '\\' :: 'r' :: t -> string (acc + "\r") t // escaped return 18 | | '\\' :: 't' :: t -> string (acc + "\t") t // escaped tab 19 | | '\\' :: '\\' :: t -> string (acc + "\\") t // escaped backslash 20 | | '\\' :: 'v' :: t -> string (acc + "\u000B") t // 21 | | '"' :: t -> acc, t // closing quote terminates 22 | | c :: t -> string (acc + (c.ToString())) t // otherwise accumulate chars 23 | | _ -> failwith "Malformed string." 24 | let rec comment = function 25 | | '\r' :: t | '\n' :: t -> t // terminated by line end 26 | | [] -> [] // or by EOF 27 | | _ :: t -> comment t 28 | let rec token acc = function 29 | | (')' :: _) as t -> acc, t // closing paren terminates 30 | | w :: t when Char.IsWhiteSpace(w) -> acc, t // whitespace terminates 31 | | [] -> acc, [] // end of list terminates 32 | | c :: t -> token (acc + (c.ToString())) t // otherwise accumulate chars 33 | let rec tokenize' acc = function 34 | | w :: t when Char.IsWhiteSpace(w) -> tokenize' acc t // skip whitespace 35 | | '(' :: t -> tokenize' (Open :: acc) t 36 | | ')' :: t -> tokenize' (Close :: acc) t 37 | | '\'' :: t -> tokenize' (Quote :: acc) t 38 | | ',' :: t -> tokenize' (Unquote :: acc) t 39 | | ';' :: t -> comment t |> tokenize' acc // skip over comments 40 | | '"' :: t -> // start of string 41 | let s, t' = string "" t 42 | tokenize' (Token.String(s) :: acc) t' 43 | | s :: t -> // otherwise start of symbol 44 | let s, t' = token (s.ToString()) t 45 | tokenize' (Token.Symbol(s) :: acc) t' 46 | | [] -> List.rev acc // end of list terminates 47 | tokenize' [] source 48 | 49 | type SExpr = 50 | | String of string 51 | | Token of string 52 | | List of SExpr list 53 | 54 | let parse (source:string) : SExpr= 55 | let map = function 56 | | Token.String(s) -> SExpr.String(s) 57 | | Token.Symbol(s) -> SExpr.Token(s) 58 | | _ -> failwith "Syntax error." 59 | let rec list f t acc = 60 | let e, t' = parse' [] t 61 | parse' (List(f e) :: acc) t' 62 | and parse' acc = function 63 | | Open :: t -> list id t acc 64 | | Close :: t -> (List.rev acc), t 65 | | Quote :: Open :: t -> list (fun e -> [Token("quote"); List(e)]) t acc 66 | | Quote :: h :: t -> parse' (List([Token("quote"); map h]) :: acc) t 67 | | Unquote :: Open :: t -> list (fun e -> [Token("unquote"); List(e)]) t acc 68 | | Unquote :: h :: t -> parse' (List([Token("unquote"); map h]) :: acc) t 69 | | h :: t -> parse' ((map h) :: acc) t 70 | | [] -> (List.rev acc), [] 71 | let result, _ = parse' [] (tokenize ( source.ToCharArray() |> List.ofArray )) 72 | match result with 73 | | [v] -> v 74 | | [] -> failwith "Syntax error (nothing parsed)." 75 | | _ -> failwith "Syntax error (to many s-expressions)." 76 | 77 | type internal ME= System.Text.RegularExpressions.MatchEvaluator 78 | type internal Regex= System.Text.RegularExpressions.Regex 79 | 80 | let rec stringify (expr:SExpr) :string= 81 | let escapeChars = Regex("[\"]") 82 | let escape s = escapeChars.Replace(s, ME(fun m->sprintf "\\%s" m.Value)) 83 | 84 | match expr with 85 | | Token t -> t 86 | | List ls -> let innerStrings = List.map stringify ls 87 | sprintf "(%s)" <| String.concat " " innerStrings 88 | | String s when isNull s -> "\"\"" 89 | | String s -> sprintf "\"%s\"" <| escape s 90 | -------------------------------------------------------------------------------- /LSON.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A35E8AF5-2050-4CDD-B4CC-9EA903C0E3DA}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LSON", "src\LSON\LSON.fsproj", "{FB5D8256-25A7-4A2A-816F-1654F368D15A}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F59FF34F-DEA1-4050-A8CB-B2E97E1DA104}" 11 | EndProject 12 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LSON.Tests", "tests\LSON.Tests\LSON.Tests.fsproj", "{30F326F7-D289-466B-8939-87179A9564B1}" 13 | EndProject 14 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Source.Tests", "tests\Source.Tests\Source.Tests.fsproj", "{4BFAB978-432C-4E01-9EF1-25B572F4F4E3}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|x64 = Debug|x64 20 | Debug|x86 = Debug|x86 21 | Release|Any CPU = Release|Any CPU 22 | Release|x64 = Release|x64 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Debug|x64.ActiveCfg = Debug|x64 32 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Debug|x64.Build.0 = Debug|x64 33 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Debug|x86.ActiveCfg = Debug|x86 34 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Debug|x86.Build.0 = Debug|x86 35 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Release|x64.ActiveCfg = Release|x64 38 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Release|x64.Build.0 = Release|x64 39 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Release|x86.ActiveCfg = Release|x86 40 | {FB5D8256-25A7-4A2A-816F-1654F368D15A}.Release|x86.Build.0 = Release|x86 41 | {30F326F7-D289-466B-8939-87179A9564B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {30F326F7-D289-466B-8939-87179A9564B1}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {30F326F7-D289-466B-8939-87179A9564B1}.Debug|x64.ActiveCfg = Debug|x64 44 | {30F326F7-D289-466B-8939-87179A9564B1}.Debug|x64.Build.0 = Debug|x64 45 | {30F326F7-D289-466B-8939-87179A9564B1}.Debug|x86.ActiveCfg = Debug|x86 46 | {30F326F7-D289-466B-8939-87179A9564B1}.Debug|x86.Build.0 = Debug|x86 47 | {30F326F7-D289-466B-8939-87179A9564B1}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {30F326F7-D289-466B-8939-87179A9564B1}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {30F326F7-D289-466B-8939-87179A9564B1}.Release|x64.ActiveCfg = Release|x64 50 | {30F326F7-D289-466B-8939-87179A9564B1}.Release|x64.Build.0 = Release|x64 51 | {30F326F7-D289-466B-8939-87179A9564B1}.Release|x86.ActiveCfg = Release|x86 52 | {30F326F7-D289-466B-8939-87179A9564B1}.Release|x86.Build.0 = Release|x86 53 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Debug|x64.ActiveCfg = Debug|x64 56 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Debug|x64.Build.0 = Debug|x64 57 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Debug|x86.ActiveCfg = Debug|x86 58 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Debug|x86.Build.0 = Debug|x86 59 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 60 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Release|Any CPU.Build.0 = Release|Any CPU 61 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Release|x64.ActiveCfg = Release|x64 62 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Release|x64.Build.0 = Release|x64 63 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Release|x86.ActiveCfg = Release|x86 64 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3}.Release|x86.Build.0 = Release|x86 65 | EndGlobalSection 66 | GlobalSection(NestedProjects) = preSolution 67 | {FB5D8256-25A7-4A2A-816F-1654F368D15A} = {A35E8AF5-2050-4CDD-B4CC-9EA903C0E3DA} 68 | {30F326F7-D289-466B-8939-87179A9564B1} = {F59FF34F-DEA1-4050-A8CB-B2E97E1DA104} 69 | {4BFAB978-432C-4E01-9EF1-25B572F4F4E3} = {F59FF34F-DEA1-4050-A8CB-B2E97E1DA104} 70 | EndGlobalSection 71 | EndGlobal 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LSON 2 | 3 | Lisp inspired serialization. Lisp has a wonderfully simple syntax. This can be used to [write code](https://www.amazon.com/Little-LISPer-Third-Daniel-Friedman/dp/0023397632) or potentially a simple alternative when a simple format is needed. The use of S-Expressions as a format for data has been outlined in the following [draft](https://people.csail.mit.edu/rivest/Sexp.txt). 4 | 5 | MacOS/Linux | Windows 6 | --- | --- 7 | [![Travis Badge](https://travis-ci.org/fsprojects/LSON.svg?branch=master)](https://travis-ci.org/fsprojects/LSON) | [![Build status](https://ci.appveyor.com/api/projects/status/github/fsprojects/LSON?svg=true)](https://ci.appveyor.com/project/fsprojects/LSON) 8 | [![Build History](https://buildstats.info/travisci/chart/fsprojects/LSON)](https://travis-ci.org/fsprojects/LSON/builds) | [![Build History](https://buildstats.info/appveyor/chart/wallymathieu/lson)](https://ci.appveyor.com/project/wallymathieu/lson) 9 | 10 | 11 | ## Nuget 12 | 13 | Stable | Prerelease 14 | --- | --- 15 | [![NuGet Badge](https://buildstats.info/nuget/LSON)](https://www.nuget.org/packages/LSON/) | [![NuGet Badge](https://buildstats.info/nuget/LSON?includePreReleases=true)](https://www.nuget.org/packages/LSON/) 16 | 17 | 18 | 19 | Does the thing 20 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | #### 0.1.2 - 19.03.2020 2 | * Treat null strings as empty strings 3 | 4 | #### 0.1.1 - 16.03.2018 5 | * Implementation release 6 | 7 | #### 0.1.0 - 17.03.2017 8 | * Initial release 9 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2019 2 | init: 3 | - git config --global core.autocrlf input 4 | build_script: 5 | - cmd: build.cmd 6 | test: off 7 | version: 0.0.1.{build} 8 | artifacts: 9 | - path: bin 10 | name: bin 11 | branches: 12 | only: 13 | - master 14 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | 4 | .paket\paket.bootstrapper.exe 5 | if errorlevel 1 ( 6 | exit /b %errorlevel% 7 | ) 8 | 9 | .paket\paket.exe restore 10 | if errorlevel 1 ( 11 | exit /b %errorlevel% 12 | ) 13 | 14 | IF NOT EXIST build.fsx ( 15 | .paket\paket.exe update 16 | packages\build\FAKE\tools\FAKE.exe init.fsx 17 | ) 18 | packages\build\FAKE\tools\FAKE.exe build.fsx %* 19 | -------------------------------------------------------------------------------- /build.fsx: -------------------------------------------------------------------------------- 1 | #r @"packages/build/FAKE/tools/FakeLib.dll" 2 | open Fake 3 | open Fake.Git 4 | open Fake.AssemblyInfoFile 5 | open Fake.ReleaseNotesHelper 6 | open Fake.UserInputHelper 7 | open System 8 | 9 | let release = LoadReleaseNotes "RELEASE_NOTES.md" 10 | let srcGlob = "src/**/*.fsproj" 11 | let testsGlob = "tests/**/*.fsproj" 12 | 13 | Target "Clean" (fun _ -> 14 | ["bin"; "temp" ;"dist"] 15 | |> CleanDirs 16 | 17 | !! srcGlob 18 | ++ testsGlob 19 | |> Seq.collect(fun p -> 20 | ["bin";"obj"] 21 | |> Seq.map(fun sp -> 22 | IO.Path.GetDirectoryName p @@ sp) 23 | ) 24 | |> CleanDirs 25 | 26 | ) 27 | 28 | Target "DotnetRestore" (fun _ -> 29 | !! srcGlob 30 | ++ testsGlob 31 | |> Seq.iter (fun proj -> 32 | DotNetCli.Restore (fun c -> 33 | { c with 34 | Project = proj 35 | //This makes sure that Proj2 references the correct version of Proj1 36 | AdditionalArgs = [sprintf "/p:PackageVersion=%s" release.NugetVersion] 37 | }) 38 | )) 39 | 40 | Target "DotnetBuild" (fun _ -> 41 | !! srcGlob 42 | |> Seq.iter (fun proj -> 43 | DotNetCli.Build (fun c -> 44 | { c with 45 | Project = proj 46 | //This makes sure that Proj2 references the correct version of Proj1 47 | AdditionalArgs = [sprintf "/p:PackageVersion=%s" release.NugetVersion] 48 | }) 49 | )) 50 | 51 | let invoke f = f () 52 | let invokeAsync f = async { f () } 53 | 54 | type TargetFramework = 55 | | Full of string 56 | | Core of string 57 | 58 | let (|StartsWith|_|) (prefix:string) (s: string) = 59 | if s.StartsWith prefix then Some () else None 60 | 61 | let getTargetFramework tf = 62 | match tf with 63 | | StartsWith "net4" -> Full tf 64 | | StartsWith "netcoreapp" -> Core tf 65 | | _ -> failwithf "Unknown TargetFramework %s" tf 66 | 67 | let getTargetFrameworksFromProjectFile (projFile : string)= 68 | let doc = Xml.XmlDocument() 69 | doc.Load(projFile) 70 | doc.GetElementsByTagName("TargetFrameworks").[0].InnerText.Split(';') 71 | |> Seq.map getTargetFramework 72 | |> Seq.toList 73 | 74 | let selectRunnerForFramework tf = 75 | let runMono = sprintf "mono -f %s -c Release" 76 | let runCore = sprintf "run -f %s -c Release" 77 | match tf with 78 | | Full t when isMono-> runMono t 79 | | Full t -> runCore t 80 | | Core t -> runCore t 81 | 82 | 83 | let runTests modifyArgs = 84 | !! testsGlob 85 | |> Seq.map(fun proj -> proj, getTargetFrameworksFromProjectFile proj) 86 | |> Seq.collect(fun (proj, targetFrameworks) -> 87 | targetFrameworks 88 | |> Seq.map selectRunnerForFramework 89 | |> Seq.map(fun args -> fun () -> 90 | DotNetCli.RunCommand (fun c -> 91 | { c with 92 | WorkingDir = IO.Path.GetDirectoryName proj 93 | }) (modifyArgs args)) 94 | ) 95 | 96 | 97 | Target "DotnetTest" (fun _ -> 98 | runTests id 99 | |> Seq.iter (invoke) 100 | ) 101 | let execProcAndReturnMessages filename args = 102 | let args' = args |> String.concat " " 103 | ProcessHelper.ExecProcessAndReturnMessages 104 | (fun psi -> 105 | psi.FileName <- filename 106 | psi.Arguments <-args' 107 | ) (TimeSpan.FromMinutes(1.)) 108 | 109 | let pkill args = 110 | execProcAndReturnMessages "pkill" args 111 | 112 | let killParentsAndChildren processId= 113 | pkill [sprintf "-P %d" processId] 114 | 115 | 116 | Target "WatchTests" (fun _ -> 117 | runTests (sprintf "watch %s") 118 | |> Seq.iter (invokeAsync >> Async.Catch >> Async.Ignore >> Async.Start) 119 | 120 | printfn "Press enter to stop..." 121 | Console.ReadLine() |> ignore 122 | 123 | if isWindows |> not then 124 | startedProcesses 125 | |> Seq.iter(fst >> killParentsAndChildren >> ignore ) 126 | else 127 | //Hope windows handles this right? 128 | () 129 | ) 130 | 131 | Target "DotnetPack" (fun _ -> 132 | !! srcGlob 133 | |> Seq.iter (fun proj -> 134 | DotNetCli.Pack (fun c -> 135 | { c with 136 | Project = proj 137 | Configuration = "Release" 138 | OutputPath = IO.Directory.GetCurrentDirectory() @@ "dist" 139 | AdditionalArgs = 140 | [ 141 | sprintf "/p:PackageVersion=%s" release.NugetVersion 142 | sprintf "/p:PackageReleaseNotes=\"%s\"" (String.Join("\n",release.Notes)) 143 | "/p:SourceLinkCreate=true" 144 | ] 145 | }) 146 | ) 147 | ) 148 | 149 | Target "Publish" (fun _ -> 150 | Paket.Push(fun c -> 151 | { c with 152 | PublishUrl = "https://www.nuget.org" 153 | WorkingDir = "dist" 154 | } 155 | ) 156 | ) 157 | 158 | Target "Release" (fun _ -> 159 | 160 | if Git.Information.getBranchName "" <> "master" then failwith "Not on master" 161 | 162 | StageAll "" 163 | Git.Commit.Commit "" (sprintf "Bump version to %s" release.NugetVersion) 164 | Branches.push "" 165 | 166 | Branches.tag "" release.NugetVersion 167 | Branches.pushTag "" "origin" release.NugetVersion 168 | ) 169 | 170 | "Clean" 171 | ==> "DotnetRestore" 172 | ==> "DotnetBuild" 173 | ==> "DotnetTest" 174 | ==> "DotnetPack" 175 | ==> "Publish" 176 | ==> "Release" 177 | 178 | "DotnetRestore" 179 | ==> "WatchTests" 180 | 181 | RunTargetOrDefault "DotnetPack" 182 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | cd "$(dirname "$0")" 6 | 7 | PAKET_BOOTSTRAPPER_EXE=.paket/paket.bootstrapper.exe 8 | PAKET_EXE=.paket/paket.exe 9 | FAKE_EXE=packages/build/FAKE/tools/FAKE.exe 10 | 11 | FSIARGS="" 12 | FSIARGS2="" 13 | OS=${OS:-"unknown"} 14 | 15 | # echo $OSTYPE 16 | if [ "$OS" != "Windows_NT" ] 17 | then 18 | # Can't use FSIARGS="--fsiargs -d:MONO" in zsh, so split it up 19 | # (Can't use arrays since dash can't handle them) 20 | FSIARGS="--fsiargs" 21 | FSIARGS2="-d:MONO" 22 | # Allows NETFramework like net45 to be built using dotnet core tooling with mono 23 | export FrameworkPathOverride=$(dirname $(which mono))/../lib/mono/4.5/ 24 | 25 | fi 26 | 27 | run() { 28 | if [ "$OS" != "Windows_NT" ] 29 | then 30 | mono "$@" 31 | else 32 | "$@" 33 | fi 34 | } 35 | 36 | yesno() { 37 | # NOTE: Defaults to NO 38 | read -p "$1 [y/N] " ynresult 39 | case "$ynresult" in 40 | [yY]*) true ;; 41 | *) false ;; 42 | esac 43 | } 44 | 45 | set +e 46 | run $PAKET_BOOTSTRAPPER_EXE 47 | bootstrapper_exitcode=$? 48 | set -e 49 | 50 | if [ "$OS" != "Windows_NT" ] && 51 | [ $bootstrapper_exitcode -ne 0 ] && 52 | [ $(certmgr -list -c Trust | grep X.509 | wc -l) -le 1 ] && 53 | [ $(certmgr -list -c -m Trust | grep X.509 | wc -l) -le 1 ] 54 | then 55 | echo "Your Mono installation has no trusted SSL root certificates set up." 56 | echo "This may result in the Paket bootstrapper failing to download Paket" 57 | echo "because Github's SSL certificate can't be verified. One way to fix" 58 | echo "this issue would be to download the list of SSL root certificates" 59 | echo "from the Mozilla project by running the following command:" 60 | echo "" 61 | echo " mozroots --import --sync" 62 | echo "" 63 | echo "This will import over 100 SSL root certificates into your Mono" 64 | echo "certificate repository." 65 | echo "" 66 | if yesno "Run 'mozroots --import --sync' now?" 67 | then 68 | mozroots --import --sync 69 | else 70 | echo "Attempting to continue without running mozroots. This might fail." 71 | fi 72 | # Re-run bootstrapper whether or not the user ran mozroots, because maybe 73 | # they fixed the problem in a separate terminal window. 74 | run $PAKET_BOOTSTRAPPER_EXE 75 | fi 76 | 77 | run $PAKET_EXE restore 78 | 79 | [ ! -e build.fsx ] && run $PAKET_EXE update 80 | [ ! -e build.fsx ] && run $FAKE_EXE init.fsx 81 | run $FAKE_EXE "$@" $FSIARGS $FSIARGS2 build.fsx 82 | 83 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://www.nuget.org/api/v2 2 | 3 | group Build 4 | source https://www.nuget.org/api/v2 5 | nuget FAKE 6 | -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | 2 | 3 | GROUP Build 4 | NUGET 5 | remote: https://www.nuget.org/api/v2 6 | FAKE (4.60) 7 | -------------------------------------------------------------------------------- /src/LSON/LSON.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0;netstandard1.6;net461 4 | 5 | 6 | 7 | LSON 8 | LSON is intended to be a S-Expression serialization library 9 | f#, fsharp, sexp, s-expression 10 | https://github.com/fsprojects/LSON 11 | https://github.com/fsprojects/LSON/blob/master/LICENSE.md 12 | false 13 | git 14 | wallymathieu 15 | https://github.com/fsprojects/LSON 16 | 0.1.0 17 | $(PackageVersion) 18 | 19 | 20 | $(VersionPrefix)-$(VersionSuffix) 21 | $(VersionPrefix) 22 | $(VersionPrefix).0 23 | $(VersionPrefix).0 24 | $(PackageReleaseNotes) 25 | 26 | 27 | true 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/LSON/Library.fs: -------------------------------------------------------------------------------- 1 | module LSON 2 | open FParsec 3 | 4 | type SExpr = 5 | | Token of System.String 6 | | String of System.String 7 | | List of SExpr list 8 | 9 | let str s = pstring s 10 | 11 | let stringLiteral = 12 | let escape = anyOf "\"\\/abfnrtv" 13 | |>> function 14 | | 'a' -> "\u0007" 15 | | 'b' -> "\b" 16 | | 'f' -> "\u000C" 17 | | 'n' -> "\n" 18 | | 'r' -> "\r" 19 | | 't' -> "\t" 20 | | 'v' -> "\u000B" 21 | | c -> string c // every other char is mapped to itself 22 | 23 | let unicodeEscape = 24 | /// converts a hex char ([0-9a-fA-F]) to its integer number (0-15) 25 | let hex2int c = (int c &&& 15) + (int c >>> 6)*9 26 | 27 | str "x" >>. pipe4 hex hex hex hex (fun h3 h2 h1 h0 -> 28 | (hex2int h3)*4096 + (hex2int h2)*256 + (hex2int h1)*16 + hex2int h0 29 | |> char |> string 30 | ) 31 | 32 | let escapedCharSnippet = str "\\" >>. (escape <|> unicodeEscape) 33 | let normalCharSnippet = manySatisfy (fun c -> c <> '"' && c <> '\\') 34 | 35 | between (str "\"") (str "\"") 36 | (stringsSepBy normalCharSnippet escapedCharSnippet) 37 | 38 | let sString = stringLiteral |>> SExpr.String 39 | let ws = spaces // skips any whitespace 40 | let sValue, sValueRef = createParserForwardedToRef() 41 | let ws1 = skipMany1Satisfy System.Char.IsWhiteSpace 42 | 43 | let sToken = 44 | let symbol = "!$%&|*+-/:<=>?@^_~#" 45 | let isIdentifierStart = fun c -> isAsciiLetter c || isAnyOf symbol c 46 | let isIdentifierCont = fun c -> isAsciiLetter c || isDigit c || isAnyOf symbol c 47 | let identifierString = many1Satisfy2 isIdentifierStart isIdentifierCont 48 | identifierString |>> Token 49 | 50 | let sList = 51 | between (skipChar '(') (skipChar ')') 52 | (sepBy sValue ws1 |>> SExpr.List) 53 | do sValueRef := choice [ 54 | sString 55 | sToken 56 | sList 57 | ] 58 | let sExpr = ws >>. sValue .>> ws .>> eof 59 | let parse str = 60 | let r= run sExpr str 61 | match r with 62 | | Success (v,_,_)->v 63 | | Failure (str,err,_)-> failwithf "%s %A" str err 64 | type internal ME= System.Text.RegularExpressions.MatchEvaluator 65 | type internal Regex= System.Text.RegularExpressions.Regex 66 | let rec stringify (expr:SExpr) :string= 67 | let escapeChars = Regex("[\"]") 68 | let escape s = escapeChars.Replace(s, ME(fun m->sprintf "\\%s" m.Value)) 69 | 70 | match expr with 71 | | Token t -> t 72 | | List ls -> let innerStrings = List.map stringify ls 73 | sprintf "(%s)" <| String.concat " " innerStrings 74 | | String s when isNull s -> "\"\"" 75 | | String s -> sprintf "\"%s\"" <| escape s 76 | -------------------------------------------------------------------------------- /tests/LSON.Tests/LSON.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/LSON.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | module ExpectoTemplate 2 | open Expecto 3 | 4 | [] 5 | let main argv = 6 | Tests.runTestsInAssembly defaultConfig argv 7 | -------------------------------------------------------------------------------- /tests/LSON.Tests/SExpressions.fs: -------------------------------------------------------------------------------- 1 | module SExpressions 2 | open Expecto 3 | open LSON 4 | type internal E=SExpr 5 | 6 | type Foo={ alpha:string; beta:string} 7 | 8 | type Bar={ gamma:string; foo:Foo} 9 | 10 | type Baz={ alpha:string; beta:string} 11 | 12 | 13 | type Qux = 14 | |Ena of string 15 | |Dio of Foo 16 | |Trea of Bar 17 | |Tessera of Baz 18 | 19 | module internal Z= 20 | module Foo= 21 | let serialize (this:Foo)= 22 | E.List [ 23 | E.Token "alpha"; E.String this.alpha 24 | E.Token "beta"; E.String this.beta 25 | ] 26 | let deSerialize(v:E) : Foo option= 27 | match v with 28 | | E.List [ 29 | E.Token "alpha"; E.String alpha 30 | E.Token "beta"; E.String beta 31 | ] -> Some { alpha=alpha; beta=beta } 32 | | _ -> None 33 | 34 | module Bar= 35 | let serialize this= 36 | E.List [ 37 | E.Token "gamma"; E.String this.gamma 38 | E.Token "foo"; Foo.serialize this.foo 39 | ] 40 | let deSerialize(v:E)= 41 | match v with 42 | | E.List [ 43 | E.Token "gamma"; E.String gamma 44 | E.Token "foo"; foo 45 | ] -> 46 | Foo.deSerialize foo |> Option.map (fun foo-> { gamma=gamma; foo= foo}) 47 | | _ -> None 48 | 49 | module Baz= 50 | let serialize this= 51 | E.List [ 52 | E.Token "alpha"; E.String this.alpha 53 | E.Token "beta"; E.String this.beta 54 | ] 55 | let deSerialize(v:E)= 56 | match v with 57 | | E.List [ 58 | E.Token "alpha"; E.String alpha 59 | E.Token "beta"; E.String beta 60 | ] -> Some { alpha=alpha; beta=beta } 61 | | _ -> None 62 | module Qux= 63 | let serialize (this:Qux)= 64 | match this with 65 | | Ena s-> E.List [ E.Token "Ena"; E.String s ] 66 | | Dio foo -> E.List [ E.Token "Dio"; Foo.serialize foo ] 67 | | Trea bar -> E.List [ E.Token "Trea"; Bar.serialize bar ] 68 | | Tessera baz -> E.List [ E.Token "Tessera"; Baz.serialize baz ] 69 | let deSerialize(v:E)= 70 | match v with 71 | | E.List [ E.Token "Ena"; E.String s ] -> Some <| Ena s 72 | | E.List [ E.Token "Dio"; foo ] -> Foo.deSerialize foo |> Option.map Dio 73 | | E.List [ E.Token "Trea"; bar ] -> Bar.deSerialize bar |> Option.map Trea 74 | | E.List [ E.Token "Tessera"; baz ] -> Baz.deSerialize baz |> Option.map Tessera 75 | | _ -> None 76 | 77 | 78 | [] 79 | let tests = 80 | testList "S-Expressions" [ 81 | testProperty "Can serialize and deserialize structure Union" <| 82 | fun (x :Qux) -> 83 | (Some x) = ( Z.Qux.serialize x |> Z.Qux.deSerialize ) 84 | testProperty "Can serialize and deserialize structure Record" <| 85 | fun (x :Baz) -> 86 | (Some x) = ( Z.Baz.serialize x |> Z.Baz.deSerialize ) 87 | testCase "Can parse more complex example" <| fun _ -> 88 | let c= """(((S) (NP VP)) 89 | ((VP) (V)) 90 | ((VP) (V NP)) 91 | ((V) died) 92 | ((V) employed) 93 | ((NP) nurses) 94 | ((NP) patients) 95 | ((NP) Medicenter) 96 | ((NP) "Dr Chan"))""" 97 | 98 | LSON.parse c |> ignore 99 | testCase "Can parse (\"S\")" <| fun _ -> 100 | let c= "(\"S\")" 101 | let res = LSON.parse c 102 | Expect.equal res (E.List [E.String "S"]) "should be able to interpret (\"S\")" 103 | 104 | testCase "Can parse (S)" <| fun _ -> 105 | let c= "(S)" 106 | let res = LSON.parse c 107 | Expect.equal res (E.List [E.Token "S"]) "should be able to interpret (S)" 108 | 109 | testCase "Can parse empty ()" <| fun _ -> 110 | let c= "()" 111 | let res = LSON.parse c 112 | Expect.equal res (E.List []) "should be able to interpret empty ()" 113 | testCase "Can parse empty string" <| fun _ -> 114 | let c= "\"\"" 115 | let res = LSON.parse c 116 | Expect.equal res (E.String "") "should be able to interpret empty string" 117 | testCase "Can parse null string" <| fun _ -> 118 | let c= E.String null |> stringify 119 | let res = LSON.parse c 120 | Expect.equal res (E.String "") "null string should be the same as empty string" 121 | testCase "Can parse identifier" <| fun _ -> 122 | let c= "identifier" 123 | let res = LSON.parse c 124 | Expect.equal res (E.Token "identifier") "should be able to interpret identifier" 125 | ] 126 | 127 | -------------------------------------------------------------------------------- /tests/LSON.Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | open Expecto 3 | let sampleStrings = 4 | [ 5 | "(symbol \"value\")" 6 | "(\"value with \\\" \")" 7 | //, \n, \v, \f, \r 8 | ] 9 | let testCaseOfSample sample= 10 | testCase <| sprintf "sample %s" sample <| fun _ -> 11 | Expect.equal sample (LSON.parse sample |> LSON.stringify) <| sprintf "should be able to parse and stringify %s" sample 12 | 13 | [] 14 | let tests = 15 | testList "samples" <| 16 | (seq { 17 | for sample in sampleStrings do yield testCaseOfSample sample 18 | } |> Seq.toList) 19 | 20 | -------------------------------------------------------------------------------- /tests/Source.Tests/Source.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------