├── .gitignore ├── README.md ├── example.swift ├── simple_example.swift ├── swiftscript.py └── transformations.py /.gitignore: -------------------------------------------------------------------------------- 1 | tsconfig.json 2 | *.ts 3 | *.js 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftScript 2 | 3 | ## Usage: 4 | 5 | `./swiftscript.py example.swift` will output `example.swift.ts`. 6 | 7 | `./swiftscript.py --toJS example.swift` will output `example.swift.ts` and attempt to run `example.swift.ts` through the TypeScript compiler, outputting `example.swift.js`. 8 | 9 | WARNING: SwiftScript is not smart enough to make sure that it always outputs valid TypeScript! (It's literally just a few regular expressions. For now, garbage in -> garbage out :) 10 | 11 | ## About: 12 | 13 | A Swift-to-JavaScript compiler would be pretty cool, since it could allow development for mobile, desktop, and web in one language. Fortunately the [TypeScript](http://www.typescriptlang.org) folks have done almost all of the hard work, and conveniently (a limited subset of) Swift can be converted to TypeScript with nothing but syntax transformations. 14 | 15 | This project tries to be an easy-to-understand and easy-to-use tool for **writing TypeScript in Swift syntax**. I don't currently plan to add support for all of Swift's long list of advanced features, but we'll see how far it goes. 16 | 17 | Below are the syntax transformations that will be supported, with Swift code on the first line and the equivalent TypeScript code on the second. 18 | 19 | --- 20 | 21 | ```Swift 22 | let x = 5 23 | ``` 24 | ```TypeScript 25 | const x = 5; 26 | ``` 27 | 28 | --- 29 | 30 | ```Swift 31 | println("Hello") 32 | ``` 33 | ```TypeScript 34 | console.log("Hello"); 35 | ``` 36 | 37 | --- 38 | 39 | ```Swift 40 | func test(cat: String, dog: String) -> String {} 41 | test("binx", "fido") 42 | ``` 43 | ```TypeScript 44 | function test(cat: string, dog: string): string {} 45 | test("binx", "fido"); 46 | ``` 47 | 48 | --- 49 | 50 | ```Swift 51 | func test(#cat: String, canine dog: String) -> String { 52 | return dog + cat 53 | } 54 | test(cat: "bijou", canine: "fido") 55 | ``` 56 | 57 | ```TypeScript 58 | function test(args: {cat: string, canine: string}): string { 59 | var cat = args.cat; 60 | var dog = args.canine; 61 | return dog + cat; 62 | } 63 | test({cat: "binx", canine: "fido"}) 64 | ``` 65 | 66 | --- 67 | 68 | ```Swift 69 | "Hello \(a+b) world" 70 | ``` 71 | 72 | ```TypeScript 73 | `Hello ${a+b} world` 74 | ``` 75 | 76 | --- 77 | 78 | `Bool` maps to `boolean`, `String` maps to `string`, and `Int`/`Float`/`Double` map to `number`. 79 | 80 | --- 81 | 82 | ## Todo: 83 | 84 | - if/else statements 85 | - for/while loops 86 | 87 | 88 | ## Wishlist: 89 | 90 | - variadic functions 91 | - optional arguments 92 | - parameter default values 93 | - `var [x,y] = [10,20]` 94 | - structs to interfaces 95 | - classes to classes 96 | - lambdas 97 | -------------------------------------------------------------------------------- /example.swift: -------------------------------------------------------------------------------- 1 | // this is a single line comment using two slashes. 2 | 3 | // Swift variables are declared with "var" 4 | // this is followed by a name, a type, and a value 5 | var explicitDouble: Double = 70 6 | 7 | // If the type is omitted, Swift will infer it from 8 | // the variable's initial value 9 | var implicitInteger = 70 10 | var implicitDouble = 70.0 11 | var 國 = "美國" 12 | 13 | // Swift constants are declared with "let" 14 | // followed by a name, a type, and a value 15 | let numberOfBananas: Int = 10 16 | 17 | // Like variables, if the type of a constant is omitted, 18 | // Swift will infer it from the constant's value 19 | let numberOfApples = 3 20 | let numberOfOranges = 5 21 | 22 | // Values of variables and constants can both be 23 | // interpolated in strings as follows 24 | let appleSummary = "I have \(numberOfApples) apples." 25 | let fruitSummary = "I have \(numberOfApples + numberOfOranges) pieces of fruit." 26 | 27 | // In "playgrounds", code can be placed in the global scope 28 | println("Hello, world") 29 | 30 | // This is an array variable 31 | var fruits = ["mango", "kiwi", "avocado"] 32 | 33 | // Define a dictionary with four items: 34 | // Each item has a person's name and age 35 | let people = ["Anna": 67, "Beto": 8, "Jack": 33, "Sam": 25] 36 | 37 | // Functions and methods are both declared with the 38 | // "func" syntax, and the return type is specified with -> 39 | func sayHello(personName: String) -> String { 40 | let greeting = "Hello, " + personName + "!" 41 | return greeting 42 | } 43 | 44 | // prints "Hello, Dilan!" 45 | print(sayHello("Dilan")) 46 | 47 | // Parameter names can be made external and required 48 | // for calling. 49 | // The external name can be the same as the parameter 50 | // name by prefixing with an octothorpe (#) 51 | // - or it can be defined separately. 52 | 53 | func sayAge(#personName: String, personAge age: Int) -> String { 54 | let result = "\(personName) is \(age) years old." 55 | return result 56 | } 57 | 58 | sayAge(personName:"Jaden", personAge:19) 59 | 60 | // We can also specify the name of the parameter 61 | 62 | print(sayAge(personName: "Dilan", personAge: 42)) 63 | -------------------------------------------------------------------------------- /simple_example.swift: -------------------------------------------------------------------------------- 1 | func test(cat: String, dog: String) -> String { 2 | println("Hello \(cat) world") 3 | return dog 4 | } 5 | 6 | var a = func test2(#cat: String, canine dog: String) -> String { 7 | return cat + dog 8 | } 9 | 10 | let x = 5 11 | let y = 10.5 12 | let s: String = "Hello \(x+y) world" 13 | 14 | var q = test("binx", "fido") 15 | 16 | var r = a(cat: "bijou", canine: s) 17 | -------------------------------------------------------------------------------- /swiftscript.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys, os, argparse 4 | from subprocess import call 5 | from transformations import transformations 6 | 7 | __author__ = 'Alex Ryan' 8 | 9 | 10 | # Check for command-line options 11 | # --toJS runs through tsc at the end 12 | def get_CL_args(): 13 | parser = argparse.ArgumentParser(description='SwiftScript converts a subset of Swift to TypeScript.') 14 | parser.add_argument('-j','--toJS', help='Postprocess with TypeScript compiler', required=False, action='store_true') 15 | parser.add_argument('input_filename', help="Input filename", nargs=1) 16 | args = parser.parse_args() 17 | return args 18 | 19 | 20 | def transform_line(line): 21 | # Ignore comments 22 | if line.startswith("//"): 23 | return line 24 | # Apply our syntax transformations, one line at a time 25 | for trans in transformations: 26 | line = trans(line) 27 | return line 28 | 29 | 30 | # Run the transformations on every line of a file, and write the output 31 | def transform_file(name): 32 | output = "" 33 | output_name = name + ".ts" 34 | with open(name, 'r') as f: 35 | for line in f: 36 | output += transform_line(line) 37 | with open(output_name, 'w') as f: 38 | f.write(output) 39 | print "" 40 | print " Swift to TypeScript transformation complete!" 41 | print " Remember to check the TypeScript file for correctness." 42 | print "" 43 | 44 | 45 | def invokeTSC(name): 46 | if call(["which", "tsc"], stdout=open(os.devnull, 'w')): 47 | # `which tsc` returned non-zero exit code 48 | print "" 49 | print " tsc not found - install typescript to use the --toJS option." 50 | print " Try sudo npm install -g typescript" 51 | print "" 52 | else: 53 | # `which tsc` returned zero -> safe to call tsc 54 | print "" 55 | print " Beginning TypeScript compilation..." 56 | call(["tsc", name + ".ts"]) 57 | 58 | 59 | def main(): 60 | args = get_CL_args() 61 | 62 | transform_file(args.input_filename[0]) 63 | 64 | if args.toJS: 65 | invokeTSC(args.input_filename[0]) 66 | 67 | if __name__ == "__main__": 68 | main() 69 | -------------------------------------------------------------------------------- /transformations.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | def remove_substrings(line, spans): 4 | num_removed_chars = 0 5 | for span in spans: 6 | start = span[0] - num_removed_chars 7 | end = span[1] - num_removed_chars 8 | line = line[:start] + line[end:] 9 | num_removed_chars += (span[1] - span[0]) 10 | return line 11 | 12 | transformations = [] 13 | 14 | def trans1(line): 15 | return line.replace("let", "const") 16 | transformations.append(trans1) 17 | 18 | def trans2(line): 19 | return line.replace("println", "console.log").replace("print", "console.log") 20 | transformations.append(trans2) 21 | 22 | def trans3(line): 23 | return line.replace(" -> ", ": ") 24 | transformations.append(trans3) 25 | 26 | def trans4(line): 27 | return line.replace(": Bool", ": boolean") 28 | transformations.append(trans4) 29 | 30 | def trans5(line): 31 | return line.replace(": String", ": string") 32 | transformations.append(trans5) 33 | 34 | def trans6(line): 35 | return line.replace(": Int", ": number") 36 | transformations.append(trans6) 37 | 38 | def trans7(line): 39 | return line.replace(": Float", ": number") 40 | transformations.append(trans7) 41 | 42 | def trans8(line): 43 | return line.replace(": Double", ": number") 44 | transformations.append(trans8) 45 | 46 | # Transform function definitions 47 | def trans9(line): 48 | p = re.compile(r'func (\w\S*) ?\((.*)\)(.*)') 49 | # This regex returns three groups, the first is the function name 50 | # and the second is the arguments and the third is the type and etc. 51 | m = p.search(line) 52 | if m: 53 | name = m.group(1) 54 | args = m.group(2) 55 | type = m.group(3) 56 | namedArguments = False 57 | for arg in args.split(", "): 58 | if arg[0] == "#" or len(arg.split(" ")) == 3: 59 | namedArguments = True 60 | if namedArguments: 61 | a = "" 62 | restore = "" 63 | for arg in args.split(", "): 64 | if arg[0] == "#": 65 | arg = arg[1:] 66 | arg_name = arg.split(":")[0] 67 | a += arg + ", " 68 | restore += " var " + arg_name + " = a." + arg_name + ";\n" 69 | elif len(arg.split(" ")) == 3: 70 | arg = arg.replace(":", "") 71 | arg = arg.split(" ") 72 | arg_external_name = arg[0] 73 | arg_internal_name = arg[1] 74 | arg_type = arg[2] 75 | a += arg_external_name + ": " + arg_type + ", " 76 | restore += " var " + arg_internal_name + " = a." + arg_external_name + ";\n" 77 | a = a[:-2] #remove trailing , 78 | return line.replace(m.group(0), "function " + name + "(a: {" + a + "})" + type) + restore 79 | else: 80 | return line.replace("func ", "function ") 81 | else: 82 | return line 83 | transformations.append(trans9) 84 | 85 | # Transform function calls 86 | def trans10(line): 87 | if "function" in line: 88 | return line 89 | else: 90 | p = re.compile(r'(console.log\()(.*)(\))') 91 | m = p.search(line) 92 | if m: 93 | target = remove_substrings(line, [m.span(1), m.span(3)]) 94 | else: 95 | target = line 96 | p = re.compile(r'(\w\S*) ?\((.*)\)') 97 | # This regex matches a function call and returns two groups, 98 | # the function name and the list of arguments 99 | m = p.search(target) 100 | if m: 101 | name = m.group(1) 102 | args = m.group(2) 103 | if ":" in args: 104 | return line.replace(m.group(0), name + "({" + args + "})") 105 | else: 106 | return line 107 | else: 108 | return line 109 | transformations.append(trans10) 110 | 111 | # Replace format strings 112 | def trans11(line): 113 | p = re.compile(r'["`](.*)\\\((.*)\)(.*)["`]') 114 | m = p.search(line) 115 | if m: 116 | return trans11(line.replace(m.group(0), "`" + m.group(1) + "${" + m.group(2) + "}" + m.group(3) + "`")) 117 | else: 118 | return line 119 | transformations.append(trans11) 120 | 121 | # Add semicolons 122 | def trans12(line): 123 | if line.strip().endswith("{") or line.strip().endswith("}") or line.strip().endswith(";") or line == "\n": 124 | return line 125 | else: 126 | return line[:-1] + ";\n" 127 | transformations.append(trans12) 128 | 129 | # Turn dictionaries into objects 130 | def trans13(line): 131 | p = re.compile(r'\[(.*:.*)\]') 132 | m = p.search(line) 133 | if m: 134 | return line.replace(m.group(0), "{" + m.group(1) + "}") 135 | else: 136 | return line 137 | transformations.append(trans13) 138 | --------------------------------------------------------------------------------