├── .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 --------------------------------------------------------------------------------