├── .gitignore
├── PythonEmbrace.Tests.Debug
├── Program.cs
├── Properties
│ └── AssemblyInfo.cs
└── PythonEmbrace.Tests.Debug.csproj
├── PythonEmbrace.Tests
├── Properties
│ └── AssemblyInfo.cs
├── PythonEmbrace.Tests.csproj
├── SimpleTests.cs
└── packages.config
├── PythonEmbrace.sln
├── PythonEmbrace
├── LogicalLine.cs
├── Program.cs
├── Properties
│ └── AssemblyInfo.cs
├── PythonConverter.cs
└── PythonEmbrace.csproj
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
2 | [Bb]in/
3 | [Oo]bj/
4 |
5 | # mstest test results
6 | TestResults
7 |
8 | ## Ignore Visual Studio temporary files, build results, and
9 | ## files generated by popular Visual Studio add-ons.
10 |
11 | # User-specific files
12 | *.suo
13 | *.user
14 | *.sln.docstates
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Rr]elease/
19 | x64/
20 | *_i.c
21 | *_p.c
22 | *.ilk
23 | *.meta
24 | *.obj
25 | *.pch
26 | *.pdb
27 | *.pgc
28 | *.pgd
29 | *.rsp
30 | *.sbr
31 | *.tlb
32 | *.tli
33 | *.tlh
34 | *.tmp
35 | *.log
36 | *.vspscc
37 | *.vssscc
38 | .builds
39 |
40 | # Visual C++ cache files
41 | ipch/
42 | *.aps
43 | *.ncb
44 | *.opensdf
45 | *.sdf
46 |
47 | # Visual Studio profiler
48 | *.psess
49 | *.vsp
50 | *.vspx
51 |
52 | # Guidance Automation Toolkit
53 | *.gpState
54 |
55 | # ReSharper is a .NET coding add-in
56 | _ReSharper*
57 |
58 | # NCrunch
59 | *.ncrunch*
60 | .*crunch*.local.xml
61 |
62 | # Installshield output folder
63 | [Ee]xpress
64 |
65 | # DocProject is a documentation generator add-in
66 | DocProject/buildhelp/
67 | DocProject/Help/*.HxT
68 | DocProject/Help/*.HxC
69 | DocProject/Help/*.hhc
70 | DocProject/Help/*.hhk
71 | DocProject/Help/*.hhp
72 | DocProject/Help/Html2
73 | DocProject/Help/html
74 |
75 | # Click-Once directory
76 | publish
77 |
78 | # Publish Web Output
79 | *.Publish.xml
80 |
81 | # NuGet Packages Directory
82 | packages
83 |
84 | # Windows Azure Build Output
85 | csx
86 | *.build.csdef
87 |
88 | # Windows Store app package directory
89 | AppPackages/
90 |
91 | # Others
92 | [Bb]in
93 | [Oo]bj
94 | sql
95 | TestResults
96 | [Tt]est[Rr]esult*
97 | *.Cache
98 | ClientBin
99 | [Ss]tyle[Cc]op.*
100 | ~$*
101 | *.dbmdl
102 | Generated_Code #added for RIA/Silverlight projects
103 |
104 | # Backup & report files from converting an old project file to a newer
105 | # Visual Studio version. Backup files are not needed, because we have git ;-)
106 | _UpgradeReport_Files/
107 | Backup*/
108 | UpgradeLog*.XML
109 |
--------------------------------------------------------------------------------
/PythonEmbrace.Tests.Debug/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PythonEmbrace.Tests.Debug
7 | {
8 | ///
9 | /// Debug harness for investigating failing unit tests (VS Express sucks)
10 | ///
11 | class Program
12 | {
13 | static void Main(string[] args)
14 | {
15 | new PythonEmbrace.Tests.SimpleTests().BracketsAreImplicitLineContinuation();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/PythonEmbrace.Tests.Debug/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("PythonEmbrace.Tests.Debug")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("PythonEmbrace.Tests.Debug")]
13 | [assembly: AssemblyCopyright("Copyright © 2013")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("c697e419-e56f-492b-ba79-b88eaeaa0b00")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/PythonEmbrace.Tests.Debug/PythonEmbrace.Tests.Debug.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {FAD44F84-1C19-449A-85E3-89AD85B36537}
9 | Exe
10 | Properties
11 | PythonEmbrace.Tests.Debug
12 | PythonEmbrace.Tests.Debug
13 | v4.0
14 | Client
15 | 512
16 |
17 |
18 | x86
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | x86
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}
52 | PythonEmbrace.Tests
53 |
54 |
55 |
56 |
63 |
--------------------------------------------------------------------------------
/PythonEmbrace.Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("PythonEmbrace.Tests")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("PythonEmbrace.Tests")]
13 | [assembly: AssemblyCopyright("Copyright © 2013")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("377da7e7-ed31-4b8b-957e-d2c073d4d36e")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/PythonEmbrace.Tests/PythonEmbrace.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 8.0.30703
7 | 2.0
8 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}
9 | Library
10 | Properties
11 | PythonEmbrace.Tests
12 | PythonEmbrace.Tests
13 | v4.0
14 | 512
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ..\packages\xunit.1.9.1\lib\net20\xunit.dll
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}
55 | PythonEmbrace
56 |
57 |
58 |
59 |
66 |
--------------------------------------------------------------------------------
/PythonEmbrace.Tests/SimpleTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Xunit;
6 |
7 | namespace PythonEmbrace.Tests
8 | {
9 | public class SimpleTests
10 | {
11 | [Fact]
12 | public void SingleLine()
13 | {
14 | Test(@"
15 | ----
16 | self.stream.unget(c)
17 | ----
18 | self.stream.unget(c);
19 | ----
20 | ");
21 | }
22 |
23 | [Fact]
24 | public void SingleIf()
25 | {
26 | Test(@"
27 | ----
28 | if c != u';':
29 | self.stream.unget(c)
30 | ----
31 | if( c != u';')
32 | {
33 | self.stream.unget(c);
34 | }
35 | ----
36 | ");
37 | }
38 |
39 | [Fact]
40 | public void BracesBeforeBlankLines()
41 | {
42 | Test(@"
43 | ----
44 | if c != u';':
45 | self.stream.unget(c)
46 |
47 | self.stream.unget(c)
48 | ----
49 | if( c != u';')
50 | {
51 | self.stream.unget(c);
52 | }
53 |
54 | self.stream.unget(c);
55 | ----
56 | ");
57 | }
58 |
59 | [Fact]
60 | public void BlankLinesDoNotAffectIndentationLevel()
61 | {
62 | Test(@"
63 | ----
64 | if c != u';':
65 |
66 | self.stream.unget(c)
67 | ----
68 | if( c != u';')
69 | {
70 |
71 | self.stream.unget(c);
72 | }
73 | ----
74 | ");
75 | }
76 |
77 |
78 | [Fact]
79 | public void BracketsAreImplicitLineContinuation()
80 | {
81 | Test(@"
82 | ----
83 | if (c != u';' and
84 | c != ','):
85 | self.stream.unget(c)
86 | ----
87 | if (c != u';' and
88 | c != ',')
89 | {
90 | self.stream.unget(c);
91 | }
92 | ----
93 | ");
94 | }
95 |
96 | [Fact]
97 | public void BracketsInStringsAreIgnored()
98 | {
99 | Test(@"
100 | ----
101 | if (c != ')' and
102 | c != ','):
103 | self.stream.unget(c)
104 | ----
105 | if (c != ')' and
106 | c != ',')
107 | {
108 | self.stream.unget(c);
109 | }
110 | ----
111 | ");
112 | }
113 |
114 | [Fact]
115 | public void EscapedQuotesDontEndStringsIgnored()
116 | {
117 | Test(@"
118 | ----
119 | if (c != ')\'' and
120 | c != ','):
121 | self.stream.unget(c)
122 | ----
123 | if (c != ')\'' and
124 | c != ',')
125 | {
126 | self.stream.unget(c);
127 | }
128 | ----
129 | ");
130 | }
131 |
132 | [Fact]
133 | public void ExplicitLineContinuation()
134 | {
135 | Test(@"
136 | ----
137 | if c != \
138 | u';':
139 | self.stream.unget(c)
140 | ----
141 | if( c != \
142 | u';')
143 | {
144 | self.stream.unget(c);
145 | }
146 | ----
147 | ");
148 | }
149 |
150 | [Fact]
151 | public void SemiColonAfterString()
152 | {
153 | Test(@"
154 | ----
155 | foo = 'bar'
156 | ----
157 | foo = 'bar';
158 | ----
159 | ");
160 | }
161 |
162 | private void Test(string test)
163 | {
164 | string[] parts = test.Split(new string[] { "----" }, StringSplitOptions.None);
165 |
166 | Assert.Equal(4, parts.Length);
167 | Assert.Equal("", parts[0].Trim());
168 | Assert.Equal("", parts[3].Trim());
169 |
170 | string input = parts[1];
171 | string expected = parts[2];
172 |
173 | string actual = PythonConverter.ConvertString(input);
174 |
175 | Assert.Equal(expected, actual);
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/PythonEmbrace.Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/PythonEmbrace.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 11.00
3 | # Visual C# Express 2010
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PythonEmbrace", "PythonEmbrace\PythonEmbrace.csproj", "{965BFA02-5253-4D6E-AD18-EBE1BCB4A875}"
5 | EndProject
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PythonEmbrace.Tests", "PythonEmbrace.Tests\PythonEmbrace.Tests.csproj", "{B1552D7B-C79E-44B4-81F2-D9E9C13083BD}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PythonEmbrace.Tests.Debug", "PythonEmbrace.Tests.Debug\PythonEmbrace.Tests.Debug.csproj", "{FAD44F84-1C19-449A-85E3-89AD85B36537}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|Mixed Platforms = Debug|Mixed Platforms
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|Mixed Platforms = Release|Mixed Platforms
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}.Debug|Any CPU.ActiveCfg = Debug|x86
21 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
22 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}.Debug|Mixed Platforms.Build.0 = Debug|x86
23 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}.Debug|x86.ActiveCfg = Debug|x86
24 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}.Debug|x86.Build.0 = Debug|x86
25 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}.Release|Any CPU.ActiveCfg = Release|x86
26 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}.Release|Mixed Platforms.ActiveCfg = Release|x86
27 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}.Release|Mixed Platforms.Build.0 = Release|x86
28 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}.Release|x86.ActiveCfg = Release|x86
29 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}.Release|x86.Build.0 = Release|x86
30 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
33 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
34 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}.Debug|x86.ActiveCfg = Debug|Any CPU
35 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}.Release|Any CPU.Build.0 = Release|Any CPU
37 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
38 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}.Release|Mixed Platforms.Build.0 = Release|Any CPU
39 | {B1552D7B-C79E-44B4-81F2-D9E9C13083BD}.Release|x86.ActiveCfg = Release|Any CPU
40 | {FAD44F84-1C19-449A-85E3-89AD85B36537}.Debug|Any CPU.ActiveCfg = Debug|x86
41 | {FAD44F84-1C19-449A-85E3-89AD85B36537}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
42 | {FAD44F84-1C19-449A-85E3-89AD85B36537}.Debug|Mixed Platforms.Build.0 = Debug|x86
43 | {FAD44F84-1C19-449A-85E3-89AD85B36537}.Debug|x86.ActiveCfg = Debug|x86
44 | {FAD44F84-1C19-449A-85E3-89AD85B36537}.Debug|x86.Build.0 = Debug|x86
45 | {FAD44F84-1C19-449A-85E3-89AD85B36537}.Release|Any CPU.ActiveCfg = Release|x86
46 | {FAD44F84-1C19-449A-85E3-89AD85B36537}.Release|Mixed Platforms.ActiveCfg = Release|x86
47 | {FAD44F84-1C19-449A-85E3-89AD85B36537}.Release|Mixed Platforms.Build.0 = Release|x86
48 | {FAD44F84-1C19-449A-85E3-89AD85B36537}.Release|x86.ActiveCfg = Release|x86
49 | {FAD44F84-1C19-449A-85E3-89AD85B36537}.Release|x86.Build.0 = Release|x86
50 | EndGlobalSection
51 | GlobalSection(SolutionProperties) = preSolution
52 | HideSolutionNode = FALSE
53 | EndGlobalSection
54 | EndGlobal
55 |
--------------------------------------------------------------------------------
/PythonEmbrace/LogicalLine.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Text;
4 | using System.Diagnostics;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace PythonEmbrace
8 | {
9 | class LogicalLine
10 | {
11 | private int _lineNumber;
12 | private StringBuilder _text;
13 |
14 | public string Text
15 | {
16 | get
17 | {
18 | if (_lastSignificantCharIndex != -1)
19 | {
20 | string significantText = _text.ToString(0, _lastSignificantCharIndex + 1);
21 | string insignificantText = _text.ToString(significantText.Length, _text.Length - significantText.Length);
22 |
23 | if (_text[_lastSignificantCharIndex] == ':')
24 | {
25 | // Block start
26 | return ConvertBlockLine(significantText) + insignificantText;
27 | }
28 | else
29 | {
30 | // Add a semicolon
31 | return significantText + ";" + insignificantText;
32 | }
33 | }
34 | else
35 | {
36 | // Entirely whitespace, or just a comment
37 | return _text.ToString();
38 | }
39 | }
40 | }
41 |
42 | private static readonly string[] BracketedKeywords = { "if", "elif", "for", "while", "except" };
43 |
44 | private static readonly Regex BlockLinePattern =
45 | new Regex(@"^(\s*)(if|else|elif|def|for|while|try|except|class)(.*):$",
46 | RegexOptions.Compiled | RegexOptions.Singleline);
47 |
48 | private string ConvertBlockLine(string significantText)
49 | {
50 | if (BlockLinePattern.IsMatch(significantText))
51 | {
52 | // FIXME remove colon and brackets if necessary
53 | Match match = BlockLinePattern.Match(significantText);
54 |
55 | string whitespace = match.Groups[1].Value;
56 | string keyword = match.Groups[2].Value;
57 | string remainder = match.Groups[3].Value;
58 |
59 | bool needsBrackets = BracketedKeywords.Contains(keyword) && !remainder.TrimStart().StartsWith("(");
60 |
61 | if (keyword == "elif")
62 | {
63 | keyword = "else if";
64 | }
65 | else if (keyword == "except")
66 | {
67 | keyword = "catch";
68 | }
69 |
70 | return whitespace + keyword + (needsBrackets ? "(" : "") + remainder + (needsBrackets ? ")" : "");
71 |
72 | }
73 | else
74 | {
75 | throw new Exception("Line " + _lineNumber + " ends with a : but doesn't match BlockLinePattern");
76 | }
77 | }
78 |
79 | public LogicalLine(int lineNumber, string physicalLine)
80 | {
81 | _lineNumber = lineNumber;
82 | _text = new StringBuilder(physicalLine.TrimEnd());
83 | Parse();
84 | }
85 |
86 | public void Append(string physicalLine)
87 | {
88 | Debug.Assert( ! IsComplete );
89 | _text.AppendLine();
90 | _text.Append(physicalLine.TrimEnd());
91 | Parse();
92 | }
93 |
94 | public int IndentDepth
95 | {
96 | get
97 | {
98 | int i = 0;
99 | while (i < _text.Length && Char.IsWhiteSpace(_text[i]))
100 | {
101 | i++;
102 | }
103 | return i;
104 | }
105 | }
106 |
107 | private bool EndsWithLineContinuationCharacter
108 | {
109 | get { return _text.Length != 0 && _text[_text.Length - 1] == '\\'; }
110 | }
111 |
112 |
113 | public bool IsComplete
114 | {
115 | get { return _bracketBalance == 0 && (! EndsWithLineContinuationCharacter) && (! _unterminatedTripleQuotedString); }
116 | }
117 |
118 | public bool IsBlank
119 | {
120 | get { return IndentDepth == _text.Length; }
121 | }
122 |
123 |
124 | // Characters considered for implicit line continuation - see Python language spec 2.1.6
125 | private static readonly char[] Opening = new char[] { '{', '(', '[' };
126 | private static readonly char[] Closing = new char[] { '}', ')', ']' };
127 |
128 | private int _bracketBalance;
129 | private int _lastSignificantCharIndex; // Index of the last non-whitespace character before any EOL comment
130 | private bool _unterminatedTripleQuotedString;
131 |
132 | private void Parse()
133 | {
134 | _bracketBalance = 0;
135 | _lastSignificantCharIndex = -1;
136 |
137 | bool inString = false;
138 | bool inComment = false;
139 | char stringStart = '\0';
140 | char prev = '\0';
141 |
142 | for(int i=0; i < _text.Length; i++)
143 | {
144 | char c = _text[i];
145 |
146 | if (inComment)
147 | {
148 | if (c == '\n')
149 | {
150 | inComment = false;
151 | }
152 | }
153 | else if (!inString)
154 | {
155 | if (c == '#')
156 | {
157 | inComment = true;
158 | }
159 | else if (!Char.IsWhiteSpace(c))
160 | {
161 | _lastSignificantCharIndex = i;
162 |
163 | if (c == '"' || c == '\'')
164 | {
165 | // Start of normal string, or triple quoted string
166 | if (SkipTripleQuotedString(ref i))
167 | {
168 | _lastSignificantCharIndex = i;
169 | }
170 | else
171 | {
172 | // it is a normal string
173 | inString = true;
174 | stringStart = c;
175 | }
176 | }
177 | else if (Opening.Contains(c))
178 | {
179 | _bracketBalance++;
180 | }
181 | else if (Closing.Contains(c))
182 | {
183 | _bracketBalance--;
184 | }
185 | }
186 | }
187 | else
188 | {
189 | if (c == stringStart && prev != '\\')
190 | {
191 | inString = false;
192 | _lastSignificantCharIndex = i;
193 | }
194 | }
195 |
196 | if (c == '\\' && prev == '\\')
197 | {
198 | prev = '\0';
199 | }
200 | else
201 | {
202 | prev = c;
203 | }
204 | }
205 | }
206 |
207 |
208 | private bool SkipTripleQuotedString(ref int i)
209 | {
210 | char startChar = _text[i];
211 |
212 | Debug.Assert(startChar == '\'' || startChar == '"');
213 |
214 | // if next two characters are the same, then we have a triple quoted string
215 | if (TripleQuotesAtIndex(startChar, i))
216 | {
217 | i += 3; // Skip opening
218 |
219 | _unterminatedTripleQuotedString = true;
220 |
221 | while (i < _text.Length)
222 | {
223 | if (TripleQuotesAtIndex(startChar, i))
224 | {
225 | _unterminatedTripleQuotedString = false;
226 | i += 2; // Skip to last char of closing
227 | break;
228 | }
229 | i++;
230 | }
231 |
232 | return true;
233 | }
234 | else
235 | {
236 | return false; // No string to skip
237 | }
238 | }
239 |
240 | private bool TripleQuotesAtIndex(char startChar, int i)
241 | {
242 | return _text.Length > i + 2
243 | && _text[i + 0] == startChar
244 | && _text[i + 1] == startChar
245 | && _text[i + 2] == startChar;
246 | }
247 |
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/PythonEmbrace/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.IO;
6 |
7 | namespace PythonEmbrace
8 | {
9 | class Program
10 | {
11 | static void Main(string[] args)
12 | {
13 | PythonConverter.ConvertFile(args[0]);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/PythonEmbrace/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("PythonEmbrace")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("PythonEmbrace")]
13 | [assembly: AssemblyCopyright("Copyright © 2013")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("aaa785a1-302c-4f94-b4dd-3880d63fb2d6")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/PythonEmbrace/PythonConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.IO;
6 |
7 | namespace PythonEmbrace
8 | {
9 | public static class PythonConverter
10 | {
11 | public static string ConvertString(string pythonCode)
12 | {
13 | return Convert(pythonCode);
14 | }
15 |
16 | public static void ConvertFile(string pythonFilePath)
17 | {
18 | string outputFilePath =
19 | Path.Combine(
20 | Path.GetDirectoryName(pythonFilePath),
21 | Path.GetFileNameWithoutExtension(pythonFilePath) + ".cs"
22 | );
23 |
24 | File.WriteAllText(outputFilePath, Convert(File.ReadAllText(pythonFilePath)));
25 | }
26 |
27 | private static string Convert(string pythonCode)
28 | {
29 | StringBuilder output = new StringBuilder();
30 |
31 | Stack indentStack = new Stack(new[] { 0 });
32 |
33 | LogicalLine currentLine = null;
34 |
35 | int lineNumber = 0;
36 |
37 | int blankLineCount = 0;
38 |
39 | foreach (string physicalLine in pythonCode.Split('\n'))
40 | {
41 | lineNumber++;
42 |
43 | if (currentLine == null || currentLine.IsComplete)
44 | {
45 | currentLine = new LogicalLine(lineNumber, physicalLine);
46 | }
47 | else
48 | {
49 | currentLine.Append(physicalLine);
50 | }
51 |
52 | if (currentLine.IsComplete)
53 | {
54 | if (currentLine.IsBlank)
55 | {
56 | // Blank lines have no effect on indentation levels
57 | // Store them up, rather than rendering inline, because we want them to appear after any braces
58 | blankLineCount++;
59 | }
60 | else
61 | {
62 | if (currentLine.IndentDepth > indentStack.First())
63 | {
64 | output.AppendLine("{".PadLeft(indentStack.First() + 1));
65 | indentStack.Push(currentLine.IndentDepth);
66 | }
67 | else
68 | {
69 | while (currentLine.IndentDepth != indentStack.First())
70 | {
71 | indentStack.Pop();
72 | output.AppendLine("}".PadLeft(indentStack.First() + 1));
73 | }
74 | }
75 |
76 | while (blankLineCount > 0)
77 | {
78 | output.AppendLine();
79 | blankLineCount--;
80 | }
81 |
82 | output.AppendLine(currentLine.Text);
83 | }
84 | }
85 | }
86 |
87 | // Close off braces at end of file
88 | while (indentStack.Count > 1)
89 | {
90 | indentStack.Pop();
91 | output.AppendLine("}".PadLeft(indentStack.First() + 1));
92 | }
93 |
94 | return output.ToString();
95 | }
96 |
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/PythonEmbrace/PythonEmbrace.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {965BFA02-5253-4D6E-AD18-EBE1BCB4A875}
9 | Exe
10 | Properties
11 | PythonEmbrace
12 | PythonEmbrace
13 | v4.0
14 | Client
15 | 512
16 |
17 |
18 | x86
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | x86
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | PythonEmbrace
2 | =============
3 |
4 | A tool to help with the manual conversion of Python code to C family languages (C#, Java, Javascript, C++).
5 |
6 | It aims to take some of the grunt work out of the manual conversion. Run it on a *valid* python input file to produce an output file with:
7 | * Braces `{}` added, based on Python's indentation rules
8 | * Semicolons `;` added to the end of statements
9 | * Colons `:` removed from the end of lines that begin blocks
10 | * Brackets added to unbracked `if`, `elif`, `for`, `while`, and `except` statements
11 | * `except` changed into `catch`
12 | * `elif` changed into `else if`
13 |
14 | Usage: `PythonEmbrace.exe file.py`
15 |
16 | Creates a `file.cs` output file in the same directory
--------------------------------------------------------------------------------