├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── CSharpToSwift.csproj
├── Program.cs
├── README.md
├── TranspileExpression.cs
├── TranspileStatement.cs
├── TranspileType.cs
├── Transpiler.cs
└── global.json
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a .NET project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
3 |
4 | name: Build
5 |
6 | on:
7 | push:
8 | branches: [ "main" ]
9 | pull_request:
10 | branches: [ "main" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Setup .NET
20 | uses: actions/setup-dotnet@v3
21 | with:
22 | dotnet-version: 6.0.300
23 | - name: Restore dependencies
24 | run: dotnet restore
25 | - name: Build
26 | run: dotnet build --no-restore
27 | #- name: Test
28 | # run: dotnet test --no-build --verbosity normal
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from `dotnet new gitignore`
5 |
6 | # dotenv files
7 | .env
8 |
9 | # User-specific files
10 | *.rsuser
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # User-specific files (MonoDevelop/Xamarin Studio)
17 | *.userprefs
18 |
19 | # Mono auto generated files
20 | mono_crash.*
21 |
22 | # Build results
23 | [Dd]ebug/
24 | [Dd]ebugPublic/
25 | [Rr]elease/
26 | [Rr]eleases/
27 | x64/
28 | x86/
29 | [Ww][Ii][Nn]32/
30 | [Aa][Rr][Mm]/
31 | [Aa][Rr][Mm]64/
32 | bld/
33 | [Bb]in/
34 | [Oo]bj/
35 | [Ll]og/
36 | [Ll]ogs/
37 |
38 | # Visual Studio 2015/2017 cache/options directory
39 | .vs/
40 | # Uncomment if you have tasks that create the project's static files in wwwroot
41 | #wwwroot/
42 |
43 | # Visual Studio 2017 auto generated files
44 | Generated\ Files/
45 |
46 | # MSTest test Results
47 | [Tt]est[Rr]esult*/
48 | [Bb]uild[Ll]og.*
49 |
50 | # NUnit
51 | *.VisualState.xml
52 | TestResult.xml
53 | nunit-*.xml
54 |
55 | # Build Results of an ATL Project
56 | [Dd]ebugPS/
57 | [Rr]eleasePS/
58 | dlldata.c
59 |
60 | # Benchmark Results
61 | BenchmarkDotNet.Artifacts/
62 |
63 | # .NET
64 | project.lock.json
65 | project.fragment.lock.json
66 | artifacts/
67 |
68 | # Tye
69 | .tye/
70 |
71 | # ASP.NET Scaffolding
72 | ScaffoldingReadMe.txt
73 |
74 | # StyleCop
75 | StyleCopReport.xml
76 |
77 | # Files built by Visual Studio
78 | *_i.c
79 | *_p.c
80 | *_h.h
81 | *.ilk
82 | *.meta
83 | *.obj
84 | *.iobj
85 | *.pch
86 | *.pdb
87 | *.ipdb
88 | *.pgc
89 | *.pgd
90 | *.rsp
91 | *.sbr
92 | *.tlb
93 | *.tli
94 | *.tlh
95 | *.tmp
96 | *.tmp_proj
97 | *_wpftmp.csproj
98 | *.log
99 | *.tlog
100 | *.vspscc
101 | *.vssscc
102 | .builds
103 | *.pidb
104 | *.svclog
105 | *.scc
106 |
107 | # Chutzpah Test files
108 | _Chutzpah*
109 |
110 | # Visual C++ cache files
111 | ipch/
112 | *.aps
113 | *.ncb
114 | *.opendb
115 | *.opensdf
116 | *.sdf
117 | *.cachefile
118 | *.VC.db
119 | *.VC.VC.opendb
120 |
121 | # Visual Studio profiler
122 | *.psess
123 | *.vsp
124 | *.vspx
125 | *.sap
126 |
127 | # Visual Studio Trace Files
128 | *.e2e
129 |
130 | # TFS 2012 Local Workspace
131 | $tf/
132 |
133 | # Guidance Automation Toolkit
134 | *.gpState
135 |
136 | # ReSharper is a .NET coding add-in
137 | _ReSharper*/
138 | *.[Rr]e[Ss]harper
139 | *.DotSettings.user
140 |
141 | # TeamCity is a build add-in
142 | _TeamCity*
143 |
144 | # DotCover is a Code Coverage Tool
145 | *.dotCover
146 |
147 | # AxoCover is a Code Coverage Tool
148 | .axoCover/*
149 | !.axoCover/settings.json
150 |
151 | # Coverlet is a free, cross platform Code Coverage Tool
152 | coverage*.json
153 | coverage*.xml
154 | coverage*.info
155 |
156 | # Visual Studio code coverage results
157 | *.coverage
158 | *.coveragexml
159 |
160 | # NCrunch
161 | _NCrunch_*
162 | .*crunch*.local.xml
163 | nCrunchTemp_*
164 |
165 | # MightyMoose
166 | *.mm.*
167 | AutoTest.Net/
168 |
169 | # Web workbench (sass)
170 | .sass-cache/
171 |
172 | # Installshield output folder
173 | [Ee]xpress/
174 |
175 | # DocProject is a documentation generator add-in
176 | DocProject/buildhelp/
177 | DocProject/Help/*.HxT
178 | DocProject/Help/*.HxC
179 | DocProject/Help/*.hhc
180 | DocProject/Help/*.hhk
181 | DocProject/Help/*.hhp
182 | DocProject/Help/Html2
183 | DocProject/Help/html
184 |
185 | # Click-Once directory
186 | publish/
187 |
188 | # Publish Web Output
189 | *.[Pp]ublish.xml
190 | *.azurePubxml
191 | # Note: Comment the next line if you want to checkin your web deploy settings,
192 | # but database connection strings (with potential passwords) will be unencrypted
193 | *.pubxml
194 | *.publishproj
195 |
196 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
197 | # checkin your Azure Web App publish settings, but sensitive information contained
198 | # in these scripts will be unencrypted
199 | PublishScripts/
200 |
201 | # NuGet Packages
202 | *.nupkg
203 | # NuGet Symbol Packages
204 | *.snupkg
205 | # The packages folder can be ignored because of Package Restore
206 | **/[Pp]ackages/*
207 | # except build/, which is used as an MSBuild target.
208 | !**/[Pp]ackages/build/
209 | # Uncomment if necessary however generally it will be regenerated when needed
210 | #!**/[Pp]ackages/repositories.config
211 | # NuGet v3's project.json files produces more ignorable files
212 | *.nuget.props
213 | *.nuget.targets
214 |
215 | # Microsoft Azure Build Output
216 | csx/
217 | *.build.csdef
218 |
219 | # Microsoft Azure Emulator
220 | ecf/
221 | rcf/
222 |
223 | # Windows Store app package directories and files
224 | AppPackages/
225 | BundleArtifacts/
226 | Package.StoreAssociation.xml
227 | _pkginfo.txt
228 | *.appx
229 | *.appxbundle
230 | *.appxupload
231 |
232 | # Visual Studio cache files
233 | # files ending in .cache can be ignored
234 | *.[Cc]ache
235 | # but keep track of directories ending in .cache
236 | !?*.[Cc]ache/
237 |
238 | # Others
239 | ClientBin/
240 | ~$*
241 | *~
242 | *.dbmdl
243 | *.dbproj.schemaview
244 | *.jfm
245 | *.pfx
246 | *.publishsettings
247 | orleans.codegen.cs
248 |
249 | # Including strong name files can present a security risk
250 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
251 | #*.snk
252 |
253 | # Since there are multiple workflows, uncomment next line to ignore bower_components
254 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
255 | #bower_components/
256 |
257 | # RIA/Silverlight projects
258 | Generated_Code/
259 |
260 | # Backup & report files from converting an old project file
261 | # to a newer Visual Studio version. Backup files are not needed,
262 | # because we have git ;-)
263 | _UpgradeReport_Files/
264 | Backup*/
265 | UpgradeLog*.XML
266 | UpgradeLog*.htm
267 | ServiceFabricBackup/
268 | *.rptproj.bak
269 |
270 | # SQL Server files
271 | *.mdf
272 | *.ldf
273 | *.ndf
274 |
275 | # Business Intelligence projects
276 | *.rdl.data
277 | *.bim.layout
278 | *.bim_*.settings
279 | *.rptproj.rsuser
280 | *- [Bb]ackup.rdl
281 | *- [Bb]ackup ([0-9]).rdl
282 | *- [Bb]ackup ([0-9][0-9]).rdl
283 |
284 | # Microsoft Fakes
285 | FakesAssemblies/
286 |
287 | # GhostDoc plugin setting file
288 | *.GhostDoc.xml
289 |
290 | # Node.js Tools for Visual Studio
291 | .ntvs_analysis.dat
292 | node_modules/
293 |
294 | # Visual Studio 6 build log
295 | *.plg
296 |
297 | # Visual Studio 6 workspace options file
298 | *.opt
299 |
300 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
301 | *.vbw
302 |
303 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
304 | *.vbp
305 |
306 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
307 | *.dsw
308 | *.dsp
309 |
310 | # Visual Studio 6 technical files
311 | *.ncb
312 | *.aps
313 |
314 | # Visual Studio LightSwitch build output
315 | **/*.HTMLClient/GeneratedArtifacts
316 | **/*.DesktopClient/GeneratedArtifacts
317 | **/*.DesktopClient/ModelManifest.xml
318 | **/*.Server/GeneratedArtifacts
319 | **/*.Server/ModelManifest.xml
320 | _Pvt_Extensions
321 |
322 | # Paket dependency manager
323 | .paket/paket.exe
324 | paket-files/
325 |
326 | # FAKE - F# Make
327 | .fake/
328 |
329 | # CodeRush personal settings
330 | .cr/personal
331 |
332 | # Python Tools for Visual Studio (PTVS)
333 | __pycache__/
334 | *.pyc
335 |
336 | # Cake - Uncomment if you are using it
337 | # tools/**
338 | # !tools/packages.config
339 |
340 | # Tabs Studio
341 | *.tss
342 |
343 | # Telerik's JustMock configuration file
344 | *.jmconfig
345 |
346 | # BizTalk build output
347 | *.btp.cs
348 | *.btm.cs
349 | *.odx.cs
350 | *.xsd.cs
351 |
352 | # OpenCover UI analysis results
353 | OpenCover/
354 |
355 | # Azure Stream Analytics local run output
356 | ASALocalRun/
357 |
358 | # MSBuild Binary and Structured Log
359 | *.binlog
360 |
361 | # NVidia Nsight GPU debugger configuration file
362 | *.nvuser
363 |
364 | # MFractors (Xamarin productivity tool) working folder
365 | .mfractor/
366 |
367 | # Local History for Visual Studio
368 | .localhistory/
369 |
370 | # Visual Studio History (VSHistory) files
371 | .vshistory/
372 |
373 | # BeatPulse healthcheck temp database
374 | healthchecksdb
375 |
376 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
377 | MigrationBackup/
378 |
379 | # Ionide (cross platform F# VS Code tools) working folder
380 | .ionide/
381 |
382 | # Fody - auto-generated XML schema
383 | FodyWeavers.xsd
384 |
385 | # VS Code files for those working on multiple tools
386 | .vscode/*
387 | !.vscode/settings.json
388 | !.vscode/tasks.json
389 | !.vscode/launch.json
390 | !.vscode/extensions.json
391 | *.code-workspace
392 |
393 | # Local History for Visual Studio Code
394 | .history/
395 |
396 | # Windows Installer files from build outputs
397 | *.cab
398 | *.msi
399 | *.msix
400 | *.msm
401 | *.msp
402 |
403 | # JetBrains Rider
404 | *.sln.iml
405 | .idea
406 |
407 | ##
408 | ## Visual studio for Mac
409 | ##
410 |
411 |
412 | # globs
413 | Makefile.in
414 | *.userprefs
415 | *.usertasks
416 | config.make
417 | config.status
418 | aclocal.m4
419 | install-sh
420 | autom4te.cache/
421 | *.tar.gz
422 | tarballs/
423 | test-results/
424 |
425 | # Mac bundle stuff
426 | *.dmg
427 | *.app
428 |
429 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
430 | # General
431 | .DS_Store
432 | .AppleDouble
433 | .LSOverride
434 |
435 | # Icon must end with two \r
436 | Icon
437 |
438 |
439 | # Thumbnails
440 | ._*
441 |
442 | # Files that might appear in the root of a volume
443 | .DocumentRevisions-V100
444 | .fseventsd
445 | .Spotlight-V100
446 | .TemporaryItems
447 | .Trashes
448 | .VolumeIcon.icns
449 | .com.apple.timemachine.donotpresent
450 |
451 | # Directories potentially created on remote AFP share
452 | .AppleDB
453 | .AppleDesktop
454 | Network Trash Folder
455 | Temporary Items
456 | .apdisk
457 |
458 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
459 | # Windows thumbnail cache files
460 | Thumbs.db
461 | ehthumbs.db
462 | ehthumbs_vista.db
463 |
464 | # Dump file
465 | *.stackdump
466 |
467 | # Folder config file
468 | [Dd]esktop.ini
469 |
470 | # Recycle Bin used on file shares
471 | $RECYCLE.BIN/
472 |
473 | # Windows Installer files
474 | *.cab
475 | *.msi
476 | *.msix
477 | *.msm
478 | *.msp
479 |
480 | # Windows shortcuts
481 | *.lnk
482 |
483 | # Vim temporary swap files
484 | *.swp
485 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | // Use IntelliSense to find out which attributes exist for C# debugging
6 | // Use hover for the description of the existing attributes
7 | // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/bin/Debug/net6.0/CSharpToSwift.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}",
16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
17 | "console": "internalConsole",
18 | "stopAtEntry": false
19 | },
20 | {
21 | "name": ".NET Core Attach",
22 | "type": "coreclr",
23 | "request": "attach"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/CSharpToSwift.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary;ForceNoAlign"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/CSharpToSwift.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary;ForceNoAlign"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "--project",
36 | "${workspaceFolder}/CSharpToSwift.csproj"
37 | ],
38 | "problemMatcher": "$msCompile"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/CSharpToSwift.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | enable
7 | enable
8 |
9 | true
10 | cs2swift
11 | .\PackageOutput
12 | CSharpToSwift
13 | Convert C# code to Swift
14 | README.md
15 | Swift;iOS;Mac;Xamarin;
16 | https://github.com/praeclarum/CSharpToSwift
17 | MIT
18 | true
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Program.cs:
--------------------------------------------------------------------------------
1 | using CSharpToSwift;
2 |
3 | Console.WriteLine("C# to Swift Transpiler");
4 |
5 | static void PrintHelp() {
6 | Console.WriteLine("Usage: csharp2swift ");
7 | }
8 |
9 | if (args.Length < 2) {
10 | PrintHelp();
11 | return 1;
12 | }
13 | string projectFilePath = args[0];
14 | if (!File.Exists(projectFilePath)) {
15 | Console.WriteLine($"Project file {projectFilePath} does not exist.");
16 | return 1;
17 | }
18 | string outputDir = args[1];
19 | if (!Directory.Exists(outputDir)) {
20 | Directory.CreateDirectory(outputDir);
21 | }
22 |
23 | try {
24 | var transpiler = new Transpiler(projectFilePath, outputDir);
25 | await transpiler.TranspileAsync();
26 | return 0;
27 | }
28 | catch (Exception ex) {
29 | Console.WriteLine(ex.Message);
30 | return 2;
31 | }
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # C# to Swift Transpiler
2 |
3 | Performs a source to source translation of C# code to Swift.
4 |
5 | ## Usage
6 |
7 | ```bash
8 | cs2swift path/to/your/project.csproj
9 | ```
10 |
--------------------------------------------------------------------------------
/TranspileExpression.cs:
--------------------------------------------------------------------------------
1 | namespace CSharpToSwift;
2 |
3 | using System;
4 | using System.Text.RegularExpressions;
5 | using Microsoft.CodeAnalysis;
6 | using Microsoft.CodeAnalysis.CSharp;
7 | using Microsoft.CodeAnalysis.CSharp.Syntax;
8 |
9 | partial class Transpiler {
10 | string TranspileExpression(ExpressionSyntax value, SemanticModel model, string indent = "")
11 | {
12 | switch (value.Kind ()) {
13 | case SyntaxKind.AddExpression:
14 | var add = (BinaryExpressionSyntax)value;
15 | return $"{TranspileExpression(add.Left, model)} + {TranspileExpression(add.Right, model)}";
16 | case SyntaxKind.AddAssignmentExpression:
17 | var addAssign = (AssignmentExpressionSyntax)value;
18 | return $"{TranspileExpression(addAssign.Left, model)} += {TranspileExpression(addAssign.Right, model)}";
19 | case SyntaxKind.AndAssignmentExpression:
20 | var andAssign = (AssignmentExpressionSyntax)value;
21 | return $"{TranspileExpression(andAssign.Left, model)} &= {TranspileExpression(andAssign.Right, model)}";
22 | case SyntaxKind.ArrayCreationExpression:
23 | return TranspileArrayCreation((ArrayCreationExpressionSyntax)value, model, indent);
24 | case SyntaxKind.ArrayInitializerExpression:
25 | var aiElements = string.Join(", ", ((InitializerExpressionSyntax)value).Expressions.Select(x => TranspileExpression(x, model, indent)));
26 | return $"[{aiElements}]";
27 | case SyntaxKind.AsExpression:
28 | var ase = (BinaryExpressionSyntax)value;
29 | return $"{TranspileExpression(ase.Left, model)} as? {TranspileExpression(ase.Right, model)}";
30 | case SyntaxKind.BaseExpression:
31 | return "super";
32 | case SyntaxKind.BitwiseAndExpression:
33 | var bitAnd = (BinaryExpressionSyntax)value;
34 | return $"{TranspileExpression(bitAnd.Left, model)} & {TranspileExpression(bitAnd.Right, model)}";
35 | case SyntaxKind.BitwiseOrExpression:
36 | var bitOr = (BinaryExpressionSyntax)value;
37 | return $"{TranspileExpression(bitOr.Left, model)} | {TranspileExpression(bitOr.Right, model)}";
38 | case SyntaxKind.BitwiseNotExpression:
39 | var bitNot = (PrefixUnaryExpressionSyntax)value;
40 | return $"~{TranspileExpression(bitNot.Operand, model)}";
41 | case SyntaxKind.CastExpression:
42 | var cast = (CastExpressionSyntax)value;
43 | return $"{TranspileExpression(cast.Expression, model)} as {GetSwiftTypeName (cast.Type, model)}";
44 | case SyntaxKind.CharacterLiteralExpression:
45 | var charLit = (LiteralExpressionSyntax)value;
46 | return charLit.Token.Text.Replace('\'', '\"');
47 | case SyntaxKind.CoalesceExpression:
48 | var coalesce = (BinaryExpressionSyntax)value;
49 | return $"{TranspileExpression(coalesce.Left, model)} ?? {TranspileExpression(coalesce.Right, model)}";
50 | case SyntaxKind.ConditionalAccessExpression:
51 | var ca = (ConditionalAccessExpressionSyntax)value;
52 | return $"{TranspileExpression(ca.Expression, model)}?{TranspileExpression(ca.WhenNotNull, model)}";
53 | case SyntaxKind.ConditionalExpression:
54 | var cond = (ConditionalExpressionSyntax)value;
55 | return $"{TranspileExpression(cond.Condition, model)} ? {TranspileExpression(cond.WhenTrue, model)} : {TranspileExpression(cond.WhenFalse, model)}";
56 | case SyntaxKind.DivideExpression:
57 | var div = (BinaryExpressionSyntax)value;
58 | return $"{TranspileExpression(div.Left, model)} / {TranspileExpression(div.Right, model)}";
59 | case SyntaxKind.DefaultExpression:
60 | var def = (DefaultExpressionSyntax)value;
61 | return GetDefaultValue(model.GetTypeInfo(def.Type).Type);
62 | case SyntaxKind.DivideAssignmentExpression:
63 | var divAssign = (AssignmentExpressionSyntax)value;
64 | return $"{TranspileExpression(divAssign.Left, model)} /= {TranspileExpression(divAssign.Right, model)}";
65 | case SyntaxKind.ElementAccessExpression:
66 | var ea = (ElementAccessExpressionSyntax)value;
67 | var eaArgs = TranspileElementArguments(ea.ArgumentList, model, indent);
68 | return $"{TranspileExpression(ea.Expression, model)}[{eaArgs}]";
69 | case SyntaxKind.ElementBindingExpression:
70 | var eb = (ElementBindingExpressionSyntax)value;
71 | var ebArgs = TranspileElementArguments(eb.ArgumentList, model, indent);
72 | return $"[{ebArgs}]";
73 | case SyntaxKind.EqualsExpression:
74 | var eq = (BinaryExpressionSyntax)value;
75 | return $"{TranspileExpression(eq.Left, model)} == {TranspileExpression(eq.Right, model)}";
76 | case SyntaxKind.ExclusiveOrExpression:
77 | var xor = (BinaryExpressionSyntax)value;
78 | return $"{TranspileExpression(xor.Left, model)} ^ {TranspileExpression(xor.Right, model)}";
79 | case SyntaxKind.FalseLiteralExpression:
80 | return "false";
81 | case SyntaxKind.GreaterThanExpression:
82 | var gt = (BinaryExpressionSyntax)value;
83 | return $"{TranspileExpression(gt.Left, model)} > {TranspileExpression(gt.Right, model)}";
84 | case SyntaxKind.GreaterThanOrEqualExpression:
85 | var gte = (BinaryExpressionSyntax)value;
86 | return $"{TranspileExpression(gte.Left, model)} >= {TranspileExpression(gte.Right, model)}";
87 | case SyntaxKind.IdentifierName:
88 | var id = (IdentifierNameSyntax)value;
89 | return id.Identifier.ToString();
90 | case SyntaxKind.ImplicitArrayCreationExpression:
91 | var iac = (ImplicitArrayCreationExpressionSyntax)value;
92 | var iacArgs = string.Join(", ", iac.Initializer.Expressions.Select(e => TranspileExpression(e, model, indent)));
93 | return $"[{iacArgs}]";
94 | case SyntaxKind.InvocationExpression:
95 | var inv = (InvocationExpressionSyntax)value;
96 | return TranspileInvocation(TranspileExpression(inv.Expression, model), inv, inv.ArgumentList, model, indent);
97 | case SyntaxKind.IsExpression:
98 | var ise = (BinaryExpressionSyntax)value;
99 | return $"{TranspileExpression(ise.Left, model)} is {TranspileExpression(ise.Right, model)}";
100 | case SyntaxKind.IsPatternExpression:
101 | return TranspileIsPattern((IsPatternExpressionSyntax)value, model, indent);
102 | case SyntaxKind.LeftShiftExpression:
103 | var lshift = (BinaryExpressionSyntax)value;
104 | return $"{TranspileExpression(lshift.Left, model)} << {TranspileExpression(lshift.Right, model)}";
105 | case SyntaxKind.LeftShiftAssignmentExpression:
106 | var lshiftAssign = (AssignmentExpressionSyntax)value;
107 | return $"{TranspileExpression(lshiftAssign.Left, model)} <<= {TranspileExpression(lshiftAssign.Right, model)}";
108 | case SyntaxKind.LessThanExpression:
109 | var lt = (BinaryExpressionSyntax)value;
110 | return $"{TranspileExpression(lt.Left, model)} < {TranspileExpression(lt.Right, model)}";
111 | case SyntaxKind.LessThanOrEqualExpression:
112 | var lte = (BinaryExpressionSyntax)value;
113 | return $"{TranspileExpression(lte.Left, model)} <= {TranspileExpression(lte.Right, model)}";
114 | case SyntaxKind.LogicalAndExpression:
115 | var and = (BinaryExpressionSyntax)value;
116 | return $"{TranspileExpression(and.Left, model)} && {TranspileExpression(and.Right, model)}";
117 | case SyntaxKind.LogicalNotExpression:
118 | var not = (PrefixUnaryExpressionSyntax)value;
119 | return $"!{TranspileExpression(not.Operand, model)}";
120 | case SyntaxKind.LogicalOrExpression:
121 | var or = (BinaryExpressionSyntax)value;
122 | return $"{TranspileExpression(or.Left, model)} || {TranspileExpression(or.Right, model)}";
123 | case SyntaxKind.MemberBindingExpression:
124 | var mbe = (MemberBindingExpressionSyntax)value;
125 | return $".{TranspileExpression(mbe.Name, model)}";
126 | case SyntaxKind.ModuloExpression:
127 | var mod = (BinaryExpressionSyntax)value;
128 | return $"{TranspileExpression(mod.Left, model)} % {TranspileExpression(mod.Right, model)}";
129 | case SyntaxKind.MultiplyExpression:
130 | var mul = (BinaryExpressionSyntax)value;
131 | return $"{TranspileExpression(mul.Left, model)} * {TranspileExpression(mul.Right, model)}";
132 | case SyntaxKind.MultiplyAssignmentExpression:
133 | var mulAssign = (AssignmentExpressionSyntax)value;
134 | return $"{TranspileExpression(mulAssign.Left, model)} *= {TranspileExpression(mulAssign.Right, model)}";
135 | case SyntaxKind.NotEqualsExpression:
136 | var neq = (BinaryExpressionSyntax)value;
137 | return $"{TranspileExpression(neq.Left, model)} != {TranspileExpression(neq.Right, model)}";
138 | case SyntaxKind.NullLiteralExpression:
139 | return "nil";
140 | case SyntaxKind.NumericLiteralExpression:
141 | var nlit = (LiteralExpressionSyntax)value;
142 | {
143 | var ntext = nlit.Token.Text;
144 | if (ntext[0] == '.')
145 | ntext = "0" + ntext;
146 | if (ntext[^1] == 'f' && !ntext.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase))
147 | ntext = ntext.Substring(0, ntext.Length - 1);
148 | if (ntext.EndsWith("ul", StringComparison.InvariantCultureIgnoreCase))
149 | ntext = $"UInt64({ntext.Substring(0, ntext.Length - 2)})";
150 | else if (ntext.EndsWith("l", StringComparison.InvariantCultureIgnoreCase))
151 | ntext = $"Int64({ntext.Substring(0, ntext.Length - 1)})";
152 | else if (ntext.EndsWith("u", StringComparison.InvariantCultureIgnoreCase))
153 | ntext = $"UInt32({ntext.Substring(0, ntext.Length - 1)})";
154 | else if (ntext.EndsWith("m", StringComparison.InvariantCultureIgnoreCase))
155 | ntext = $"Int32({ntext.Substring(0, ntext.Length - 1)})";
156 | return ntext;
157 | }
158 | case SyntaxKind.ObjectCreationExpression:
159 | return TranspileObjectCreationExpression((ObjectCreationExpressionSyntax)value, model, indent);
160 | case SyntaxKind.OrAssignmentExpression:
161 | var orAssign = (AssignmentExpressionSyntax)value;
162 | return $"{TranspileExpression(orAssign.Left, model)} |= {TranspileExpression(orAssign.Right, model)}";
163 | case SyntaxKind.ParenthesizedExpression:
164 | var paren = (ParenthesizedExpressionSyntax)value;
165 | return $"({TranspileExpression(paren.Expression, model)})";
166 | case SyntaxKind.ParenthesizedLambdaExpression:
167 | var parenLambda = (ParenthesizedLambdaExpressionSyntax)value;
168 | return TranspileLambdaExpression(parenLambda.ParameterList.Parameters, parenLambda, model, indent);
169 | case SyntaxKind.PostDecrementExpression:
170 | var postDec = (PostfixUnaryExpressionSyntax)value;
171 | return $"{TranspileExpression(postDec.Operand, model)} -= 1";
172 | case SyntaxKind.PostIncrementExpression:
173 | var postInc = (PostfixUnaryExpressionSyntax)value;
174 | return $"{TranspileExpression(postInc.Operand, model)} += 1";
175 | case SyntaxKind.PredefinedType:
176 | return GetSwiftTypeName(value, model);
177 | case SyntaxKind.PreDecrementExpression:
178 | var preDec = (PrefixUnaryExpressionSyntax)value;
179 | return $"{TranspileExpression(preDec.Operand, model)} -= 1";
180 | case SyntaxKind.PreIncrementExpression:
181 | var preInc = (PrefixUnaryExpressionSyntax)value;
182 | return $"{TranspileExpression(preInc.Operand, model)} += 1";
183 | case SyntaxKind.RangeExpression:
184 | var range = (RangeExpressionSyntax)value;
185 | var rangeStart = range.LeftOperand is not null ? TranspileExpression(range.LeftOperand, model) : null;
186 | var rangeEnd = range.RightOperand is not null ? TranspileExpression(range.RightOperand, model) : null;
187 | if (rangeEnd is not null)
188 | return $"{rangeStart}..<{rangeEnd}";
189 | else
190 | return $"{rangeStart}...";
191 | case SyntaxKind.RightShiftExpression:
192 | var rshift = (BinaryExpressionSyntax)value;
193 | return $"{TranspileExpression(rshift.Left, model)} >> {TranspileExpression(rshift.Right, model)}";
194 | case SyntaxKind.RightShiftAssignmentExpression:
195 | var rshiftAssign = (AssignmentExpressionSyntax)value;
196 | return $"{TranspileExpression(rshiftAssign.Left, model)} >>= {TranspileExpression(rshiftAssign.Right, model)}";
197 | case SyntaxKind.SimpleAssignmentExpression:
198 | var sae = (AssignmentExpressionSyntax)value;
199 | return $"{TranspileExpression(sae.Left, model)} = {TranspileExpression(sae.Right, model)}";
200 | case SyntaxKind.SimpleLambdaExpression:
201 | var simpleLambda = (SimpleLambdaExpressionSyntax)value;
202 | return TranspileLambdaExpression(new[]{simpleLambda.Parameter}, simpleLambda, model, indent);
203 | case SyntaxKind.SimpleMemberAccessExpression:
204 | var sma = (MemberAccessExpressionSyntax)value;
205 | return $"{TranspileExpression(sma.Expression, model)}.{sma.Name.ToString()}";
206 | case SyntaxKind.StringLiteralExpression:
207 | var slit = (LiteralExpressionSyntax)value;
208 | {
209 | var stext = CSStringToSwift(slit.Token.Text, slit.Token.ValueText);
210 |
211 | return stext;
212 | }
213 | case SyntaxKind.SubtractAssignmentExpression:
214 | var subAssign = (AssignmentExpressionSyntax)value;
215 | return $"{TranspileExpression(subAssign.Left, model)} -= {TranspileExpression(subAssign.Right, model)}";
216 | case SyntaxKind.SubtractExpression:
217 | var sub = (BinaryExpressionSyntax)value;
218 | return $"{TranspileExpression(sub.Left, model)} - {TranspileExpression(sub.Right, model)}";
219 | case SyntaxKind.ThisExpression:
220 | return "self";
221 | case SyntaxKind.TrueLiteralExpression:
222 | return "true";
223 | case SyntaxKind.UnaryMinusExpression:
224 | var ume = (PrefixUnaryExpressionSyntax)value;
225 | return $"-{TranspileExpression(ume.Operand, model)}";
226 | default:
227 | Error($"Unsupported expression: {value.Kind()}");
228 | return $"nil/*{value.Kind()}: {value.ToString().Trim()}*/";
229 | }
230 | }
231 |
232 | string TranspileIsPattern(IsPatternExpressionSyntax value, SemanticModel model, string indent)
233 | {
234 | var exprCode = TranspileExpression(value.Expression, model, indent);
235 | switch (value.Pattern.Kind ()) {
236 | case SyntaxKind.DeclarationPattern:
237 | var dp = (DeclarationPatternSyntax)value.Pattern;
238 | var dpTypeName = GetSwiftTypeName(dp.Type, model);
239 | if (dp.Designation is SingleVariableDesignationSyntax svds) {
240 | return $"let {svds.Identifier} = {exprCode} as? {dpTypeName}";
241 | }
242 | else {
243 | Error($"Unsupported declaration pattern: {dp.Designation.ToString()}");
244 | return $"{exprCode}/* is {dp.Designation.ToString()}*/";
245 | }
246 | case SyntaxKind.ConstantPattern:
247 | var cp = (ConstantPatternSyntax)value.Pattern;
248 | switch (cp.Expression.Kind()) {
249 | case SyntaxKind.TrueLiteralExpression:
250 | return $"{exprCode}";
251 | case SyntaxKind.FalseLiteralExpression:
252 | return $"!{exprCode}";
253 | case SyntaxKind.NullLiteralExpression:
254 | return $"{exprCode} == nil";
255 | default:
256 | return $"{exprCode} == {TranspileExpression(cp.Expression, model)}";
257 | }
258 | default:
259 | Error($"Unsupported pattern: {value.Pattern.Kind()}");
260 | return $"{exprCode}/*{value.Pattern.Kind()}: {value.Pattern.ToString().Trim()}*/";
261 | }
262 | }
263 |
264 | static readonly Regex unicodeLiteralRegex = new (@"\\u([0-9a-fA-F]{4})", RegexOptions.Compiled);
265 |
266 | string CSStringToSwift(string csStringLiteralText, string csStringLiteralValueText) {
267 | var stext = csStringLiteralText;
268 | if (stext.Length > 0 && stext[0] == '@') {
269 | stext = "\"\"\"\n" + csStringLiteralValueText + "\n\"\"\"";
270 | }
271 | stext = unicodeLiteralRegex.Replace(stext, m => {
272 | var hex = m.Groups[1].Value;
273 | var code = int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
274 | return $"\\u{{{hex}}}";
275 | });
276 | return stext;
277 | }
278 |
279 | private string TranspileArrayCreation(ArrayCreationExpressionSyntax array, SemanticModel model, string indent)
280 | {
281 | var etypeSymbol = model.GetTypeInfo(array.Type.ElementType).Type;
282 | if (array.Type.RankSpecifiers.Count == 1 && array.Type.RankSpecifiers[0].Sizes is {Count:1} sizes) {
283 | var lengthExpr = sizes[0];
284 | var length = TryEvaluateConstantIntExpression(lengthExpr, model, 0);
285 | if (array.Initializer is {} init) {
286 | var sb = new System.Text.StringBuilder();
287 | sb.Append("[");
288 | var head = "";
289 | var num = 0;
290 | foreach (var e in init.Expressions) {
291 | sb.Append(head);
292 | sb.Append(TranspileExpression(e, model, indent));
293 | head = ", ";
294 | num++;
295 | }
296 | if (num < length) {
297 | var valueCode = GetDefaultValue(etypeSymbol);
298 | for (var i = num; i < length; i++) {
299 | sb.Append(head);
300 | sb.Append(valueCode);
301 | head = ", ";
302 | }
303 | }
304 | sb.Append("]");
305 | return sb.ToString();
306 | }
307 | else {
308 | var valueCode = GetDefaultValue(etypeSymbol);
309 | var lengthCode = TranspileExpression(lengthExpr, model);
310 | return $"Array(repeating: {valueCode}, count: {lengthCode})";
311 | }
312 | }
313 | else {
314 | Error($"Unsupported array creation: {array.ToString().Trim()}");
315 | return $"[]/*{array.ToString().Trim()}*/";
316 | }
317 | }
318 |
319 | string TranspileElementArguments(BracketedArgumentListSyntax argList, SemanticModel model, string indent)
320 | {
321 | return string.Join(", ", argList.Arguments.Select(x => TranspileExpression(x.Expression, model, indent)));
322 | }
323 |
324 | string TranspileLambdaExpression(IEnumerable parameters, LambdaExpressionSyntax lambda, SemanticModel model, string indent)
325 | {
326 | var parametersCode = string.Join(", ", parameters.Select(x => x.Identifier.ToString()));
327 | var sb = new System.Text.StringBuilder();
328 | sb.Append($"{{ {parametersCode} in");
329 | if (lambda.ExpressionBody is {} expr) {
330 | var exprCode = TranspileExpression(expr, model);
331 | sb.Append($" {exprCode} }}");
332 | }
333 | else if (lambda.Block is {} block) {
334 | using var sw = new StringWriter();
335 | TranspileBlock(block, model, indent, sw);
336 | var blockCode = sw.ToString();
337 | sb.Append($"\n{blockCode}{indent}}}");
338 | }
339 | else {
340 | Error($"Unsupported lambda expression: {lambda.ToString().Trim()}");
341 | sb.Append($" nil/*{lambda.ToString().Trim()}*/ }}");
342 | }
343 | return sb.ToString();
344 | }
345 |
346 | string TranspileObjectCreationExpression(ObjectCreationExpressionSyntax oc, SemanticModel model, string indent)
347 | {
348 | if (model.GetSymbolInfo(oc.Type).Symbol is ITypeSymbol ocTypeSymbol)
349 | {
350 | var ocInit = oc.Initializer;
351 | if (ocTypeSymbol.Name == "Dictionary" && ocTypeSymbol.ContainingNamespace.ToString() == "System.Collections.Generic") {
352 | var sb = new System.Text.StringBuilder();
353 | sb.Append("[");
354 | if (ocInit is not null && ocInit.Expressions.Count > 0)
355 | {
356 | var head = "";
357 | foreach (var kv in ocInit.Expressions)
358 | {
359 | sb.Append(head);
360 | if (kv is InitializerExpressionSyntax ie && ie.Expressions.Count == 2) {
361 | sb.Append($"{TranspileExpression(ie.Expressions[0], model)}: {TranspileExpression(ie.Expressions[1], model)}");
362 | }
363 | else {
364 | Error($"Unsupported dictionary initializer: {kv.Kind()}");
365 | sb.Append($"nil/*{kv.ToString().Trim()}*/");
366 | }
367 | head = ", ";
368 | }
369 | }
370 | sb.Append("]");
371 | return sb.ToString();
372 | }
373 | else {
374 | var ocName = GetSwiftTypeName(ocTypeSymbol);
375 | var ocCode = oc.ArgumentList != null ? TranspileInvocation(ocName, oc, oc.ArgumentList, model, indent) : $"{ocName}()";
376 | if (ocInit is not null && ocInit.Expressions.Count > 0)
377 | {
378 | var kw = ocTypeSymbol.IsReferenceType ? "let" : "var";
379 | var inits = ocInit.Expressions.Select(x => $"x.{TranspileExpression(x, model)}");
380 | var initsCode = string.Join("; ", inits);
381 | ocCode = $"{{ {kw} x = {ocCode}; {initsCode} }}()";
382 | }
383 | return ocCode;
384 | }
385 | }
386 | else
387 | {
388 | Error($"Unable to determine type of object creation expression: {oc.Type.ToString()}");
389 | return $"nil/*no type symbol: {oc.Type.ToString().Trim()}*/";
390 | }
391 | }
392 |
393 | string TranspileInvocation(string exprCode, SyntaxNode invokeNode, ArgumentListSyntax argList, SemanticModel model, string indent)
394 | {
395 | if (exprCode == "nameof" && argList.Arguments.Count == 1)
396 | return NameOf(argList.Arguments[0].Expression);
397 | var method = model.GetSymbolInfo(invokeNode).Symbol as IMethodSymbol;
398 | if (method == null) {
399 | Error($"Method resolution failed: {invokeNode}");
400 | var fargs = argList.Arguments.Select(a => TranspileExpression(a.Expression, model)).ToArray();
401 | return $"{exprCode}({string.Join(", ", fargs)})";
402 | }
403 | var parameters = method.Parameters;
404 | if (parameters.Length == 0) {
405 | return $"{exprCode}()";
406 | }
407 | var args = argList.Arguments;
408 | var nparams = parameters.Length;
409 | var nargs = args.Count;
410 | var sb = new System.Text.StringBuilder();
411 | var aindent = $"{indent} ";
412 | sb.Append(exprCode);
413 | sb.Append("(");
414 | for (var i = 0; i < nparams; i++) {
415 | var paramName = parameters[i].Name;
416 | if (i > 0)
417 | sb.Append(", ");
418 | sb.Append(paramName);
419 | sb.Append(": ");
420 | if (i < nargs) {
421 | var arg = args[i];
422 | sb.Append(TranspileExpression(arg.Expression, model, aindent));
423 | } else {
424 | Error("Missing argument value");
425 | sb.Append("nil/*missing*/");
426 | }
427 | }
428 | if (nargs > nparams) {
429 | Error("Too many arguments");
430 | for (var i = nparams; i < nargs; i++) {
431 | var arg = args[i];
432 | sb.Append(", ");
433 | sb.Append(TranspileExpression(arg.Expression, model, aindent));
434 | sb.Append("/*extra*/");
435 | }
436 | }
437 | sb.Append(")");
438 | return sb.ToString();
439 | }
440 |
441 | string NameOf(ExpressionSyntax value)
442 | {
443 | var name = value switch {
444 | IdentifierNameSyntax id => id.Identifier.ToString(),
445 | MemberAccessExpressionSyntax mae => mae.Name.ToString(),
446 | _ => value.ToString()
447 | };
448 | return $"\"{name}\"";
449 | }
450 |
451 | int TryEvaluateConstantIntExpression(ExpressionSyntax expr, SemanticModel model, int defaultInt)
452 | {
453 | if (expr is LiteralExpressionSyntax lit && lit.IsKind(SyntaxKind.NumericLiteralExpression)) {
454 | return int.Parse(lit.Token.ValueText);
455 | }
456 | return defaultInt;
457 | }
458 |
459 | string GetDefaultValue(ISymbol? type)
460 | {
461 | if (type == null) {
462 | return "nil";
463 | }
464 | switch (type.Kind) {
465 | case SymbolKind.ArrayType:
466 | return "[]";
467 | case SymbolKind.PointerType:
468 | return "nil";
469 | case SymbolKind.DynamicType:
470 | return "nil";
471 | case SymbolKind.TypeParameter:
472 | return "nil";
473 | case SymbolKind.ErrorType:
474 | return "nil";
475 | case SymbolKind.NamedType:
476 | var ntype = (INamedTypeSymbol)type;
477 | switch (type.Name) {
478 | case nameof(System.Boolean):
479 | return "false";
480 | case nameof(System.Byte):
481 | return "0";
482 | case nameof(System.Char):
483 | return "\"\\0\"";
484 | case nameof(System.Double):
485 | return "0.0";
486 | case nameof(System.Single):
487 | return "0.0";
488 | case nameof(System.Int16):
489 | return "0";
490 | case nameof(System.Int32):
491 | return "0";
492 | case nameof(System.Int64):
493 | return "0";
494 | case nameof(System.IntPtr):
495 | return "0";
496 | case nameof(System.UInt16):
497 | return "0";
498 | case nameof(System.UInt32):
499 | return "0";
500 | case nameof(System.UInt64):
501 | return "0";
502 | case nameof(System.UIntPtr):
503 | return "0";
504 | default:
505 | if (ntype.IsReferenceType) {
506 | return "nil";
507 | }
508 | else if (ntype.Name == "Nullable" && ntype.ContainingNamespace.Name == "System") {
509 | return "nil";
510 | }
511 | else if (ntype.Name == "Enum" && ntype.ContainingNamespace.Name == "System") {
512 | return "0";
513 | }
514 | else if (ntype.InstanceConstructors.FirstOrDefault(x => x.Parameters.Length == 0) is IMethodSymbol defaultCtor) {
515 | return $"{GetSwiftTypeName(type)}()";
516 | }
517 | else {
518 | Error($"Unsupported default value for named type: {type.Name}");
519 | return $"0/*NT:{type.Name}*/";
520 | }
521 | }
522 | default:
523 | Error($"Unsupported default value for type {type.Kind}");
524 | return $"nil/*T:{type.Kind}*/";
525 | }
526 | }
527 | }
528 |
--------------------------------------------------------------------------------
/TranspileStatement.cs:
--------------------------------------------------------------------------------
1 | namespace CSharpToSwift;
2 |
3 | using System;
4 | using Microsoft.CodeAnalysis;
5 | using Microsoft.CodeAnalysis.CSharp;
6 | using Microsoft.CodeAnalysis.CSharp.Syntax;
7 |
8 | partial class Transpiler {
9 | void TranspileBlock(BlockSyntax block, SemanticModel model, string indent, TextWriter w)
10 | {
11 | foreach (var stmt in block.Statements)
12 | {
13 | TranspileStatement(stmt, model, indent, w);
14 | }
15 | }
16 |
17 | void TranspileStatement(StatementSyntax stmt, SemanticModel model, string indent, TextWriter w)
18 | {
19 | switch (stmt.Kind()) {
20 | case SyntaxKind.Block:
21 | TranspileBlock((BlockSyntax)stmt, model, indent, w);
22 | break;
23 | case SyntaxKind.BreakStatement:
24 | w.WriteLine($"{indent}break");
25 | break;
26 | case SyntaxKind.ContinueStatement:
27 | w.WriteLine($"{indent}continue");
28 | break;
29 | case SyntaxKind.ExpressionStatement:
30 | TranspileExpressionStatement((ExpressionStatementSyntax)stmt, model, indent, w);
31 | break;
32 | case SyntaxKind.ForStatement:
33 | TranspileForStatement((ForStatementSyntax)stmt, model, indent, w);
34 | break;
35 | case SyntaxKind.ForEachStatement:
36 | TranspileForEachStatement((ForEachStatementSyntax)stmt, model, indent, w);
37 | break;
38 | case SyntaxKind.IfStatement:
39 | TranspileIfStatement((IfStatementSyntax)stmt, model, indent, w);
40 | break;
41 | case SyntaxKind.LocalDeclarationStatement:
42 | var ld = (LocalDeclarationStatementSyntax)stmt;
43 | TranspileVariableDeclaration(ld.Declaration, model, indent, w);
44 | break;
45 | case SyntaxKind.ReturnStatement:
46 | TranspileReturnStatement((ReturnStatementSyntax)stmt, model, indent, w);
47 | break;
48 | case SyntaxKind.SwitchStatement:
49 | TranspileSwitchStatement((SwitchStatementSyntax)stmt, model, indent, w);
50 | break;
51 | case SyntaxKind.ThrowStatement:
52 | var thrw = (ThrowStatementSyntax)stmt;
53 | if (thrw.Expression is {} expr) {
54 | w.WriteLine($"{indent}throw {TranspileExpression(thrw.Expression, model)}");
55 | }
56 | else {
57 | w.WriteLine($"{indent}throw");
58 | }
59 | break;
60 | case SyntaxKind.TryStatement:
61 | TranspileTryStatement((TryStatementSyntax)stmt, model, indent, w);
62 | break;
63 | case SyntaxKind.UsingStatement:
64 | TranspileUsingStatement((UsingStatementSyntax)stmt, model, indent, w);
65 | break;
66 | case SyntaxKind.WhileStatement:
67 | TranspileWhileStatement((WhileStatementSyntax)stmt, model, indent, w);
68 | break;
69 | default:
70 | Error($"Unsupported statement: {stmt.Kind()}");
71 | w.WriteLine($"{indent}/*{stmt.Kind()}: {stmt.ToString().Trim()}*/");
72 | break;
73 | }
74 | }
75 |
76 | void TranspileExpressionStatement(ExpressionStatementSyntax stmt, SemanticModel model, string indent, TextWriter w)
77 | {
78 | var expr = TranspileExpression(stmt.Expression, model);
79 | w.WriteLine($"{indent}{expr}");
80 | }
81 |
82 | void TranspileForStatement(ForStatementSyntax stmt, SemanticModel model, string indent, TextWriter w)
83 | {
84 | if (stmt.Declaration is {} decl) {
85 | TranspileVariableDeclaration(decl, model, indent, w);
86 | }
87 | var cond = stmt.Condition is not null ? TranspileExpression(stmt.Condition, model) : "true";
88 | w.WriteLine($"{indent}while {cond} {{");
89 | TranspileStatement(stmt.Statement, model, indent + " ", w);
90 | foreach (var incr in stmt.Incrementors) {
91 | var expr = TranspileExpression(incr, model);
92 | w.WriteLine($"{indent} {expr}");
93 | }
94 | w.WriteLine($"{indent}}}");
95 | }
96 |
97 | void TranspileForEachStatement(ForEachStatementSyntax stmt, SemanticModel model, string indent, TextWriter w)
98 | {
99 | var expr = stmt.Expression;
100 | var ident = stmt.Identifier;
101 | var exprCode = TranspileExpression(expr, model);
102 | w.WriteLine($"{indent}for {ident.ValueText} in {exprCode} {{");
103 | TranspileStatement(stmt.Statement, model, indent + " ", w);
104 | w.WriteLine($"{indent}}}");
105 | }
106 |
107 | void TranspileIfStatement(IfStatementSyntax stmt, SemanticModel model, string indent, TextWriter w)
108 | {
109 | var cond = TranspileExpression(stmt.Condition, model);
110 | w.WriteLine($"{indent}if {cond} {{");
111 | TranspileStatement(stmt.Statement, model, indent + " ", w);
112 | if (stmt.Else is { } elseClause)
113 | {
114 | w.WriteLine($"{indent}}} else {{");
115 | TranspileStatement(elseClause.Statement, model, indent + " ", w);
116 | }
117 | w.WriteLine($"{indent}}}");
118 | }
119 |
120 | void TranspileReturnStatement(ReturnStatementSyntax stmt, SemanticModel model, string indent, TextWriter w)
121 | {
122 | var expr = stmt.Expression;
123 | if (expr is null)
124 | w.WriteLine($"{indent}return");
125 | else
126 | w.WriteLine($"{indent}return {TranspileExpression(expr, model)}");
127 | }
128 |
129 | void TranspileSwitchStatement(SwitchStatementSyntax stmt, SemanticModel model, string indent, TextWriter w)
130 | {
131 | var expr = stmt.Expression;
132 | w.WriteLine($"{indent}switch {TranspileExpression(expr, model)} {{");
133 | foreach (var s in stmt.Sections)
134 | {
135 | var caseLabels = s.Labels.Where(x => !x.IsKind(SyntaxKind.DefaultSwitchLabel)).ToArray();
136 | var defLabel = s.Labels.FirstOrDefault(x => x.IsKind(SyntaxKind.DefaultSwitchLabel));
137 | if (caseLabels.Length > 0) {
138 | var labels = string.Join(", ", s.Labels.Select(x => {
139 | if (x.IsKind(SyntaxKind.DefaultSwitchLabel)) {
140 | return "default";
141 | } else if (x.IsKind(SyntaxKind.CaseSwitchLabel)) {
142 | return TranspileExpression(((CaseSwitchLabelSyntax)x).Value, model, indent);
143 | } else {
144 | Error($"Unsupported switch label: {x}");
145 | return x.ToString();
146 | }
147 | }));
148 | w.WriteLine($"{indent}case {labels}:");
149 | foreach (var cstmt in s.Statements) {
150 | TranspileStatement(cstmt, model, indent + " ", w);
151 | }
152 | }
153 | if (defLabel is not null) {
154 | w.WriteLine($"{indent}default:");
155 | foreach (var cstmt in s.Statements) {
156 | TranspileStatement(cstmt, model, indent + " ", w);
157 | }
158 | }
159 | }
160 | w.WriteLine($"{indent}}}");
161 | }
162 |
163 | void TranspileTryStatement(TryStatementSyntax stmt, SemanticModel model, string indent, TextWriter w)
164 | {
165 | var block = stmt.Block;
166 | var catches = stmt.Catches;
167 | if (catches.Count > 0) {
168 | w.WriteLine($"{indent}do {{");
169 | if (stmt.Finally is { } fin) {
170 | w.WriteLine($"{indent} defer {{");
171 | TranspileBlock(fin.Block, model, indent + " ", w);
172 | w.WriteLine($"{indent} }}");
173 | }
174 | TranspileBlock(block, model, indent + " ", w);
175 | foreach (var catchClause in catches) {
176 | w.Write($"{indent}}} catch ");
177 | if (catchClause.Declaration is { } decl) {
178 | w.Write (GetSwiftTypeName(decl.Type, model));
179 | }
180 | if (catchClause.Filter is { } filter) {
181 | var cond = TranspileExpression(filter.FilterExpression, model);
182 | w.WriteLine($"where {cond}");
183 | }
184 | w.WriteLine($" {{");
185 | TranspileBlock(catchClause.Block, model, indent + " ", w);
186 | w.WriteLine($"{indent}}}");
187 | }
188 | }
189 | else {
190 | w.WriteLine($"{indent}{{");
191 | if (stmt.Finally is { } fin) {
192 | w.WriteLine($"{indent} defer {{");
193 | TranspileBlock(fin.Block, model, indent + " ", w);
194 | w.WriteLine($"{indent} }}");
195 | }
196 | TranspileBlock(block, model, indent + " ", w);
197 | w.WriteLine($"{indent}}}");
198 | }
199 | }
200 |
201 | void TranspileUsingStatement(UsingStatementSyntax stmt, SemanticModel model, string indent, TextWriter w)
202 | {
203 | w.WriteLine($"{indent}{{");
204 | if (stmt.Expression is {} expr) {
205 | var exprCode = TranspileExpression(expr, model);
206 | w.WriteLine($"{indent} let using_ = {exprCode}");
207 | }
208 | else if (stmt.Declaration is {} decl) {
209 | TranspileVariableDeclaration(decl, model, indent + " ", w);
210 | }
211 | else {
212 | Error($"Unsupported using statement");
213 | }
214 | w.WriteLine($"{indent} defer {{");
215 | if (stmt.Expression is not null) {
216 | w.WriteLine($"{indent} using_.Dispose()");
217 | }
218 | else if (stmt.Declaration is not null) {
219 | foreach (var v in stmt.Declaration.Variables) {
220 | var name = v.Identifier.ValueText;
221 | w.WriteLine($"{indent} {name}.Dispose()");
222 | }
223 | }
224 | w.WriteLine($"{indent} }}");
225 | TranspileStatement(stmt.Statement, model, indent + " ", w);
226 | w.WriteLine($"{indent}}}");
227 | }
228 |
229 | void TranspileVariableDeclaration(VariableDeclarationSyntax decl, SemanticModel model, string indent, TextWriter w)
230 | {
231 | var declType = model.GetTypeInfo(decl.Type).Type;
232 | var declTypeName = GetSwiftTypeName(declType);
233 | foreach (var v in decl.Variables)
234 | {
235 | var vn = v.Identifier.ToString();
236 | var initCode = v.Initializer is not null ? TranspileExpression(v.Initializer.Value, model) : GetDefaultValue(declType);
237 | w.WriteLine($"{indent}var {vn}: {declTypeName} = {initCode}");
238 | }
239 | }
240 |
241 | void TranspileWhileStatement(WhileStatementSyntax stmt, SemanticModel model, string indent, TextWriter w)
242 | {
243 | var cond = TranspileExpression(stmt.Condition, model);
244 | w.WriteLine($"{indent}while {cond} {{");
245 | TranspileStatement(stmt.Statement, model, indent + " ", w);
246 | w.WriteLine($"{indent}}}");
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/TranspileType.cs:
--------------------------------------------------------------------------------
1 | namespace CSharpToSwift;
2 |
3 | using System;
4 | using Microsoft.CodeAnalysis;
5 | using Microsoft.CodeAnalysis.CSharp;
6 | using Microsoft.CodeAnalysis.CSharp.Syntax;
7 |
8 | partial class Transpiler {
9 | void TranspileClass(string swiftName, ClassDeclarationSyntax node, INamedTypeSymbol symbol, SemanticModel model, string indent, TextWriter w)
10 | {
11 | w.Write($"{indent}class {swiftName}");
12 | var head = " : ";
13 | if (symbol.BaseType is {} baseType && !(baseType.Name == "Object" && baseType.ContainingNamespace.Name == "System")) {
14 | var baseSwiftName = GetSwiftTypeName(baseType);
15 | w.Write($"{head}{baseSwiftName}");
16 | head = ", ";
17 | }
18 | foreach (var i in symbol.Interfaces) {
19 | var baseSwiftName = GetSwiftTypeName(i);
20 | w.Write($"{head}{baseSwiftName}");
21 | head = ", ";
22 | }
23 | w.WriteLine($" {{");
24 | foreach (var member in node.Members) {
25 | TranspileClassOrStructMember(member, swiftName, node, symbol, model, indent + " ", w, requireMethodBody: true);
26 | }
27 | w.WriteLine($"{indent}}}");
28 | }
29 |
30 | void TranspileInterface(string swiftName, InterfaceDeclarationSyntax node, INamedTypeSymbol symbol, SemanticModel model, string indent, TextWriter w)
31 | {
32 | w.Write($"{indent}protocol {swiftName}");
33 | var head = " : ";
34 | foreach (var i in symbol.Interfaces) {
35 | var baseSwiftName = GetSwiftTypeName(i);
36 | w.Write($"{head}{baseSwiftName}");
37 | head = ", ";
38 | }
39 | w.WriteLine($" {{");
40 | foreach (var member in node.Members) {
41 | TranspileClassOrStructMember(member, swiftName, node, symbol, model, indent + " ", w, requireMethodBody: false);
42 | }
43 | w.WriteLine($"{indent}}}");
44 | }
45 |
46 | void TranspileStruct(string swiftName, StructDeclarationSyntax node, INamedTypeSymbol symbol, SemanticModel model, string indent, TextWriter w)
47 | {
48 | w.Write($"{indent}struct {swiftName}");
49 | var head = " : ";
50 | foreach (var i in symbol.Interfaces) {
51 | var baseSwiftName = GetSwiftTypeName(i);
52 | w.Write($"{head}{baseSwiftName}");
53 | head = ", ";
54 | }
55 | w.WriteLine($" {{");
56 | foreach (var member in node.Members) {
57 | TranspileClassOrStructMember(member, swiftName, node, symbol, model, indent + " ", w, requireMethodBody: true);
58 | }
59 | w.WriteLine($"{indent}}}");
60 | }
61 |
62 | void TranspileClassOrStructMember(MemberDeclarationSyntax member, string typeName, TypeDeclarationSyntax node, INamedTypeSymbol typeSymbol, SemanticModel model, string indent, TextWriter w, bool requireMethodBody)
63 | {
64 | switch (member.Kind ()) {
65 | case SyntaxKind.ClassDeclaration:
66 | var classDecl = (ClassDeclarationSyntax)member;
67 | if (model.GetDeclaredSymbol(classDecl) is INamedTypeSymbol classSymbol) {
68 | var classSwiftName = GetSwiftTypeName(classSymbol);
69 | TranspileClass(classSwiftName, classDecl, classSymbol, model, indent, w);
70 | }
71 | else {
72 | Error($"Unable to get symbol for class: {classDecl.Identifier.Text}");
73 | w.WriteLine($"{indent}/*{classDecl.ToString().Trim()}*/");
74 | }
75 | break;
76 | case SyntaxKind.ConstructorDeclaration:
77 | TranspileCtor((ConstructorDeclarationSyntax)member, typeSymbol, model, indent, w, requireMethodBody: requireMethodBody);
78 | break;
79 | case SyntaxKind.FieldDeclaration:
80 | TranspileField((FieldDeclarationSyntax)member, typeSymbol, model, indent, w);
81 | break;
82 | case SyntaxKind.MethodDeclaration:
83 | TranspileMethod((MethodDeclarationSyntax)member, typeSymbol, model, indent, w, requireMethodBody: requireMethodBody);
84 | break;
85 | case SyntaxKind.PropertyDeclaration:
86 | TranspileProperty((PropertyDeclarationSyntax)member, typeSymbol, model, indent, w, requireMethodBody: requireMethodBody);
87 | break;
88 | case SyntaxKind.StructDeclaration:
89 | var structDecl = (StructDeclarationSyntax)member;
90 | if (model.GetDeclaredSymbol(structDecl) is INamedTypeSymbol structSymbol) {
91 | var structSwiftName = GetSwiftTypeName(structSymbol);
92 | TranspileStruct(structSwiftName, structDecl, structSymbol, model, indent, w);
93 | }
94 | else {
95 | Error($"Unable to get symbol for struct: {structDecl.Identifier.Text}");
96 | w.WriteLine($"{indent}/*{structDecl.ToString().Trim()}*/");
97 | }
98 | break;
99 | default:
100 | Error($"Unsupported member: {member.Kind()}");
101 | w.WriteLine($" /*{member.Kind()}: {member.ToString().Trim()}*/");
102 | break;
103 | }
104 | }
105 |
106 | void TranspileField(FieldDeclarationSyntax field, INamedTypeSymbol containerTypeSymbol, SemanticModel model, string indent, TextWriter w)
107 | {
108 | var docs = GetDocs(field);
109 | var type = model.GetSymbolInfo(field.Declaration.Type).Symbol;
110 |
111 | var ftypeName = GetSwiftTypeName(type);
112 | var isReadOnly = field.Modifiers.Any(x => x.IsKind(SyntaxKind.ReadOnlyKeyword));
113 | var isStatic = field.Modifiers.Any(x => x.IsKind(SyntaxKind.StaticKeyword));
114 | var decl = isReadOnly ? (isStatic ? "static let" : "let") : (isStatic ? "static var" : "var");
115 |
116 | foreach (var v in field.Declaration.Variables)
117 | {
118 | var fieldSymbol = model.GetDeclaredSymbol(v);
119 | var acc = GetAccessLevelModifier(fieldSymbol);
120 | var vn = v.Identifier.ToString();
121 | var initCode = v.Initializer is not null ? TranspileExpression(v.Initializer.Value, model, $"{indent} ") : null;
122 | if (initCode is null && !isReadOnly)
123 | initCode = GetDefaultValue(type);
124 | var typeSuffix = "";//TODO: Enable null checking. initCode == "nil" ? "?" : "";
125 | if (initCode is not null)
126 | initCode = " = " + initCode;
127 | if (docs.Length > 0)
128 | w.WriteLine($"{indent}/// {docs}");
129 | w.WriteLine($"{indent}{acc}{decl} {vn}: {ftypeName}{typeSuffix}{initCode}");
130 | }
131 | }
132 |
133 | void TranspileCtor(ConstructorDeclarationSyntax ctor, INamedTypeSymbol containerTypeSymbol, SemanticModel model, string indent, TextWriter w, bool requireMethodBody)
134 | {
135 | var docs = GetDocs(ctor);
136 | if (docs.Length > 0)
137 | w.WriteLine($"{indent}/// {docs}");
138 | var methodSymbol = model.GetDeclaredSymbol(ctor);
139 | var acc = GetAccessLevelModifier(methodSymbol);
140 | var isStatic = ctor.Modifiers.Any(x => x.IsKind(SyntaxKind.StaticKeyword));
141 | var slotType = isStatic ? "static " : "";
142 | w.Write($"{indent}{acc}{slotType}init(");
143 | TranspileParams(ctor.ParameterList, model, w);
144 | if (ctor.Body is null && !requireMethodBody) {
145 | w.WriteLine($")");
146 | }
147 | else {
148 | w.WriteLine($") {{");
149 | if (ctor.Body is {} block) {
150 | TranspileBlock(block, model, $"{indent} ", w);
151 | }
152 | w.WriteLine($"{indent}}}");
153 | }
154 | }
155 |
156 | void TranspileMethod(MethodDeclarationSyntax method, INamedTypeSymbol containerTypeSymbol, SemanticModel model, string indent, TextWriter w, bool requireMethodBody)
157 | {
158 | var docs = GetDocs(method);
159 | if (docs.Length > 0)
160 | w.WriteLine($"{indent}/// {docs}");
161 | var returnType = model.GetSymbolInfo(method.ReturnType).Symbol;
162 | var isVoid = IsTypeVoid(returnType);
163 | var returnTypeCode = isVoid ? "" : $" -> {GetSwiftTypeName(returnType)}";
164 | var methodSymbol = model.GetDeclaredSymbol(method);
165 | var acc = GetAccessLevelModifier(methodSymbol);
166 | var isStatic = method.Modifiers.Any(x => x.IsKind(SyntaxKind.StaticKeyword));
167 | var isOverride = method.Modifiers.Any(x => x.IsKind(SyntaxKind.OverrideKeyword));
168 | var isSealed = method.Modifiers.Any(x => x.IsKind(SyntaxKind.SealedKeyword));
169 | var isAbstract = method.Modifiers.Any(x => x.IsKind(SyntaxKind.AbstractKeyword));
170 | var isVirtual = method.Modifiers.Any(x => x.IsKind(SyntaxKind.VirtualKeyword));
171 | if (isAbstract)
172 | {
173 | // Warning("Abstract methods are not supported");
174 | }
175 | // acc = acc + $"/*{method.Modifiers}*/";
176 | var slotType = isStatic ? "static " : (isOverride ? "override " : (isAbstract ? "/*abstract*/ " : (isVirtual ? "" : (method.Body is not null ? "final " : ""))));
177 | w.Write($"{indent}{acc}{slotType}func {method.Identifier.ToString()}(");
178 | TranspileParams(method.ParameterList, model, w);
179 | w.Write($"){returnTypeCode}");
180 | if (method.Body is null && !requireMethodBody) {
181 | w.WriteLine();
182 | }
183 | else {
184 | w.WriteLine($" {{");
185 | if (method.Body is {} block) {
186 | TranspileBlock(block, model, $"{indent} ", w);
187 | }
188 | w.WriteLine($"{indent}}}");
189 | }
190 | }
191 |
192 | private void TranspileParams(ParameterListSyntax parameterList, SemanticModel model, TextWriter w)
193 | {
194 | var head = "";
195 | foreach (var p in parameterList.Parameters)
196 | {
197 | var pname = p.Identifier.ToString();
198 | if (p.Type is null) {
199 | Error($"Parameter has no type: {pname}");
200 | w.Write($"{head}{pname}: Int/*Error: not type*/");
201 | }
202 | else {
203 | var ptypeSymbol = model.GetSymbolInfo(p.Type).Symbol;
204 | var isArray = IsTypeArray(ptypeSymbol);
205 | var ptypeName = GetSwiftTypeName(ptypeSymbol);
206 | var refMod = isArray ? "inout " : "";
207 | w.Write($"{head}{pname}: {refMod}{ptypeName}");
208 | }
209 | head = ", ";
210 | }
211 | }
212 |
213 | void TranspileProperty(PropertyDeclarationSyntax prop, INamedTypeSymbol containerTypeSymbol, SemanticModel model, string indent, TextWriter w, bool requireMethodBody)
214 | {
215 | var docs = GetDocs(prop);
216 | if (docs.Length > 0)
217 | w.WriteLine($"{indent}/// {docs}");
218 | var returnType = model.GetSymbolInfo(prop.Type).Symbol;
219 | string slotType = GetSlotTypeModifier(prop);
220 | var vn = prop.Identifier.ToString();
221 | var initCode = prop.Initializer is not null ? TranspileExpression(prop.Initializer.Value, model) : null;
222 | if (initCode is not null)
223 | initCode = " = " + initCode;
224 | w.WriteLine($"{indent}{slotType}var {vn}: {GetSwiftTypeName(returnType)}{initCode} {{");
225 | if (prop.AccessorList is { } alist)
226 | {
227 | foreach (var accessor in alist.Accessors)
228 | {
229 | var accLevel = GetAccessLevelModifier(accessor, model);
230 | w.Write($"{indent} {accessor.Keyword}");
231 | if (accessor.Body is null && accessor.ExpressionBody is null && !requireMethodBody) {
232 | w.WriteLine();
233 | }
234 | else {
235 | w.WriteLine($" {{");
236 | if (accessor.Body is {} block) {
237 | TranspileBlock(block, model, $"{indent} ", w);
238 | }
239 | else if (accessor.ExpressionBody is {} ebody) {
240 | var eCode = TranspileExpression(ebody.Expression, model, $"{indent} ");
241 | w.WriteLine($"{indent} {eCode}");
242 | }
243 | w.WriteLine($"{indent} }}");
244 | }
245 | }
246 | }
247 | else if (prop.ExpressionBody is {} ebody) {
248 | var eCode = TranspileExpression(ebody.Expression, model, $"{indent} ");
249 | w.WriteLine($"{indent} get {{ {eCode} }}");
250 | }
251 | else {
252 | Error($"Property has no accessors: {vn}");
253 | w.WriteLine($"{indent} // No accessors");
254 | }
255 | w.WriteLine($"{indent}}}");
256 | }
257 |
258 | string GetSwiftTypeName(CSharpSyntaxNode type, SemanticModel model)
259 | {
260 | var typeSymbol = model.GetSymbolInfo(type).Symbol;
261 | return GetSwiftTypeName(typeSymbol);
262 | }
263 |
264 | string GetSwiftTypeName(ISymbol? s)
265 | {
266 | if (s == null) {
267 | Error($"No symbol type provided");
268 | return "AnyObject/*no symbol*/";
269 | }
270 | else if (s is IArrayTypeSymbol ats) {
271 | return $"[{GetSwiftTypeName(ats.ElementType)}]";
272 | }
273 | else if (s is INamedTypeSymbol nts) {
274 | var name = nts.Name;
275 | switch (name) {
276 | case nameof(System.Boolean):
277 | return "Bool";
278 | case nameof(System.Byte):
279 | return "UInt8";
280 | case nameof(System.Char):
281 | return "Character";
282 | case nameof(System.Int32):
283 | return "Int";
284 | case nameof(System.IntPtr):
285 | return "Int";
286 | case nameof(System.Object):
287 | return "AnyObject";
288 | case nameof(System.Single):
289 | return "Float";
290 | default:
291 | if (string.IsNullOrEmpty(name)) {
292 | Error($"No name for symbol: {s.GetType()}");
293 | return "AnyObject/*no name*/";
294 | }
295 | else if (name == "Nullable" && s.ContainingNamespace.Name == "System") {
296 | return GetSwiftTypeName(nts.TypeArguments.First()) + "?";
297 | }
298 | return name;
299 | }
300 | }
301 | else if (s is ITypeParameterSymbol tps) {
302 | return tps.Name;
303 | }
304 | else {
305 | Error($"Unsupported type symbol: {s.GetType()}");
306 | return $"AnyObject/*{s}*/";
307 | }
308 | }
309 |
310 | bool IsTypeVoid(ISymbol? typeSymbol)
311 | {
312 | return typeSymbol is null || (typeSymbol.Name == "Void" && typeSymbol.ContainingNamespace.Name == "System");
313 | }
314 |
315 | static bool IsTypeArray(ISymbol? typeSymbol)
316 | {
317 | return typeSymbol is IArrayTypeSymbol;
318 | }
319 | }
320 |
--------------------------------------------------------------------------------
/Transpiler.cs:
--------------------------------------------------------------------------------
1 | namespace CSharpToSwift;
2 |
3 | using System;
4 | using Microsoft.CodeAnalysis;
5 | using Microsoft.CodeAnalysis.CSharp;
6 | using Microsoft.CodeAnalysis.CSharp.Syntax;
7 | using Microsoft.CodeAnalysis.MSBuild;
8 |
9 | partial class Transpiler
10 | {
11 | readonly string projectFilePath;
12 | readonly string swiftPackageName;
13 | readonly string swiftPackageDir;
14 | readonly string sourcesDir;
15 | readonly MSBuildWorkspace workspace = MSBuildWorkspace.Create();
16 | public Transpiler(string projectFilePath, string outputDir)
17 | {
18 | this.projectFilePath = Path.GetFullPath(projectFilePath);
19 | this.swiftPackageName = Path.GetFileNameWithoutExtension(projectFilePath);
20 | this.swiftPackageDir = outputDir;
21 | this.sourcesDir = outputDir;
22 | }
23 | static Transpiler()
24 | {
25 | Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults();
26 | var _ = typeof(Microsoft.CodeAnalysis.CSharp.Formatting.CSharpFormattingOptions);
27 | }
28 | readonly Dictionary errorCounts = new Dictionary ();
29 | void Error(string message)
30 | {
31 | if (!errorCounts.ContainsKey(message)) {
32 | errorCounts[message] = 0;
33 | // Console.ForegroundColor = ConsoleColor.Red;
34 | // Console.WriteLine(message);
35 | // Console.ResetColor();
36 | }
37 | errorCounts[message]++;
38 | }
39 | void Info(string message)
40 | {
41 | Console.ForegroundColor = ConsoleColor.Green;
42 | Console.WriteLine(message);
43 | Console.ResetColor();
44 | }
45 |
46 | public async Task TranspileAsync()
47 | {
48 | Directory.CreateDirectory(sourcesDir);
49 | var projectFileName = Path.GetFileName(projectFilePath);
50 | Info($"Loading project {projectFileName}...");
51 | var project = await workspace.OpenProjectAsync(projectFilePath);
52 | var projectLoadErrors = workspace.Diagnostics.Where(x => x.Kind == WorkspaceDiagnosticKind.Failure);
53 | foreach (var d in projectLoadErrors) {
54 | Error(d.Message);
55 | }
56 | if (projectLoadErrors.Any()) {
57 | Console.WriteLine($"Failed to load project {projectFileName}");
58 | return;
59 | }
60 | Info($"Analyzing project {project.Name}...");
61 | var compilation = await project.GetCompilationAsync();
62 | if (compilation is null) {
63 | Error("Failed to get compilation");
64 | return;
65 | }
66 | var compErrors = compilation.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error).ToList();
67 | if (compErrors.Count > 0) {
68 | foreach (var d in compErrors) {
69 | Error(d.ToString());
70 | }
71 | return;
72 | }
73 |
74 | Info($"Transpiling...");
75 | var types = new List<(MemberDeclarationSyntax Syntax, SemanticModel Model)>();
76 | await GetTypeDeclarationsAsync (compilation, types);
77 |
78 | // var outputDir = System.IO.Path.GetDirectoryName(projectFilePath);
79 | TextWriter NewSwiftWriter(string swiftName) {
80 | var fileName = $"{swiftName}.swift";
81 | var filePath = System.IO.Path.Combine(sourcesDir, fileName);
82 | var w = new System.IO.StreamWriter(filePath);
83 | w.WriteLine($"// This file was generated from {Path.GetFileName(projectFileName)} by CSharpToSwift version {typeof(Transpiler).Assembly.GetName().Version}");
84 | return w;
85 | }
86 | using (var pw = new StreamWriter(Path.Combine(swiftPackageDir, "Package.swift"))) {
87 | pw.WriteLine($"// swift-tools-version: 5.6");
88 | pw.WriteLine();
89 | pw.WriteLine($"import PackageDescription");
90 | pw.WriteLine();
91 | pw.WriteLine($"let package = Package(");
92 | pw.WriteLine($" name: \"{swiftPackageName}\",");
93 | pw.WriteLine($" products: [");
94 | pw.WriteLine($" .library(name: \"{swiftPackageName}\",");
95 | pw.WriteLine($" targets: [\"{swiftPackageName}\"])");
96 | pw.WriteLine($" ],");
97 | pw.WriteLine($" dependencies: [");
98 | pw.WriteLine($" ],");
99 | pw.WriteLine($" targets: [");
100 | pw.WriteLine($" .target(name: \"{swiftPackageName}\",");
101 | pw.WriteLine($" dependencies: []),");
102 | pw.WriteLine($" ]");
103 | pw.WriteLine($")");
104 | }
105 | var swift = new StringWriter();
106 | swift.WriteLine("// This file was generated by CSharpToSwift");
107 | foreach (var (node, model) in types) {
108 | var symbol = (INamedTypeSymbol)model.GetDeclaredSymbol(node)!;
109 | var swiftName = GetSwiftTypeName(symbol);
110 | switch (node.Kind ()) {
111 | case SyntaxKind.ClassDeclaration:
112 | var c = (ClassDeclarationSyntax)node;
113 | using (var cw = NewSwiftWriter(swiftName)) {
114 | TranspileClass(swiftName, c, symbol, model, "", cw);
115 | }
116 | break;
117 | case SyntaxKind.InterfaceDeclaration:
118 | var intf = (InterfaceDeclarationSyntax)node;
119 | using (var sw = NewSwiftWriter(swiftName)) {
120 | TranspileInterface(swiftName, intf, symbol, model, "", sw);
121 | }
122 | break;
123 | case SyntaxKind.StructDeclaration:
124 | var s = (StructDeclarationSyntax)node;
125 | using (var sw = NewSwiftWriter(swiftName)) {
126 | TranspileStruct(swiftName, s, symbol, model, "", sw);
127 | }
128 | break;
129 | default:
130 | Error($"Unsupported type: {node.Kind()}");
131 | break;
132 | }
133 | }
134 | // Show errors sorted by count
135 | var totalErrors = 0;
136 | foreach (var kvp in errorCounts.OrderBy(x => x.Value)) {
137 | var count = kvp.Value;
138 | totalErrors += count;
139 | Console.ForegroundColor = ConsoleColor.Red;
140 | Console.Write($"Error:");
141 | Console.ResetColor();
142 | Console.WriteLine($" {kvp.Key} ({count}x)");
143 | }
144 | if (totalErrors > 0) {
145 | Console.ForegroundColor = ConsoleColor.Red;
146 | Console.WriteLine($"{totalErrors} errors");
147 | Console.ResetColor();
148 | }
149 | else {
150 | Console.ForegroundColor = ConsoleColor.Green;
151 | Console.WriteLine("OK");
152 | Console.ResetColor();
153 | }
154 | }
155 |
156 | static string GetDocs(CSharpSyntaxNode field)
157 | {
158 | var lines =
159 | field.GetLeadingTrivia()
160 | .Where(x => x.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia) || x.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia))
161 | .SelectMany(x => x.ToFullString().Split('\n'))
162 | .Select(x =>
163 | x
164 | .Replace("", "")
165 | .Replace("", "")
166 | .Replace("", "**")
167 | .Replace("", "**")
168 | .Replace("///", "")
169 | .Replace("\t", " ")
170 | .Trim())
171 | .Where(x => x.Length > 0);
172 | return string.Join(" ", lines);
173 | }
174 |
175 | static string GetSlotTypeModifier(MemberDeclarationSyntax member)
176 | {
177 | var isStatic = member.Modifiers.Any(x => x.IsKind(SyntaxKind.StaticKeyword));
178 | var isOverride = member.Modifiers.Any(x => x.IsKind(SyntaxKind.OverrideKeyword));
179 | var isSealed = member.Modifiers.Any(x => x.IsKind(SyntaxKind.SealedKeyword));
180 | var isAbstract = member.Modifiers.Any(x => x.IsKind(SyntaxKind.AbstractKeyword));
181 | var isVirtual = member.Modifiers.Any(x => x.IsKind(SyntaxKind.VirtualKeyword));
182 | var slotType = isStatic ? "static " : (isOverride ? "override " : (isAbstract ? "open " : ""));
183 | return slotType;
184 | }
185 |
186 | static string GetAccessLevelModifier(CSharpSyntaxNode node, SemanticModel model)
187 | {
188 | var memberSymbol = model.GetSymbolInfo(node).Symbol;
189 | return GetAccessLevelModifier(memberSymbol);
190 | }
191 |
192 | static string GetAccessLevelModifier(ISymbol? memberSymbol)
193 | {
194 | // access-level-modifier: private | fileprivate | internal | public | open
195 | if (memberSymbol is null)
196 | return "";
197 | return memberSymbol.DeclaredAccessibility switch
198 | {
199 | Accessibility.Private => "",
200 | Accessibility.Protected => "",
201 | Accessibility.Internal => "",
202 | Accessibility.Public => "",
203 | _ => "",
204 | };
205 | }
206 |
207 | async Task GetTypeDeclarationsAsync(Compilation compilation, List<(MemberDeclarationSyntax Syntax, SemanticModel Symbol)> types)
208 | {
209 | foreach (var s in compilation.SyntaxTrees.OfType()) {
210 | // Info($"Transpiling {s.FilePath}...");
211 | var m = compilation.GetSemanticModel(s);
212 | GetTypeDeclarations(await s.GetRootAsync().ConfigureAwait(false), m, compilation, types);
213 | }
214 | }
215 |
216 | void GetTypeDeclarations(CSharpSyntaxNode node, SemanticModel model, Compilation compilation, List<(MemberDeclarationSyntax Syntax, SemanticModel Symbol)> types)
217 | {
218 | switch (node.Kind ()) {
219 | case SyntaxKind.ClassDeclaration:
220 | var c = (ClassDeclarationSyntax)node;
221 | if (model.GetDeclaredSymbol(c) is INamedTypeSymbol ctype) {
222 | // Info($"Found class {ctype.ContainingNamespace}.{ctype.Name}");
223 | types.Add((c, model));
224 | }
225 | break;
226 | case SyntaxKind.StructDeclaration:
227 | var s = (StructDeclarationSyntax)node;
228 | if (model.GetDeclaredSymbol(s) is INamedTypeSymbol stype) {
229 | // Info($"Found struct {stype.ContainingNamespace}.{stype.Name}");
230 | types.Add((s, model));
231 | }
232 | break;
233 | case SyntaxKind.InterfaceDeclaration:
234 | var i = (InterfaceDeclarationSyntax)node;
235 | if (model.GetDeclaredSymbol(i) is INamedTypeSymbol itype) {
236 | // Info($"Found interface {itype.ContainingNamespace}.{itype.Name} {itype.GetType()}");
237 | types.Add((i, model));
238 | }
239 | break;
240 | case SyntaxKind.EnumDeclaration:
241 | var e = (EnumDeclarationSyntax)node;
242 | if (model.GetDeclaredSymbol(e) is INamedTypeSymbol etype) {
243 | // Info($"Found enum {etype.ContainingNamespace}.{etype.Name}");
244 | types.Add((e, model));
245 | }
246 | break;
247 | case SyntaxKind.DelegateDeclaration:
248 | var d = (DelegateDeclarationSyntax)node;
249 | if (model.GetDeclaredSymbol(d) is INamedTypeSymbol dtype) {
250 | // Info($"Found delegate {dtype.ContainingNamespace}.{dtype.Name}");
251 | types.Add((d, model));
252 | }
253 | break;
254 | case SyntaxKind.NamespaceDeclaration:
255 | var n = (NamespaceDeclarationSyntax)node;
256 | foreach (var m in n.Members) {
257 | GetTypeDeclarations(m, model, compilation, types);
258 | }
259 | break;
260 | case SyntaxKind.CompilationUnit:
261 | var cu = (CompilationUnitSyntax)node;
262 | foreach (var m in cu.Members) {
263 | GetTypeDeclarations(m, model, compilation, types);
264 | }
265 | break;
266 | default:
267 | break;
268 | }
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "6.0.300"
4 | }
5 | }
--------------------------------------------------------------------------------