├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── ast.d.ts ├── ast.pegjs ├── builtins.ts ├── builtins ├── Array.ts ├── Bool.ts ├── Character.ts ├── ClosedRange.ts ├── DefaultStringInterpolation.ts ├── Dictionary.ts ├── Hasher.ts ├── IndexingIterator.ts ├── Optional.ts ├── String.ts ├── _OptionalNilComparisonType.ts ├── common.ts ├── floats.ts ├── functions.ts ├── integers.ts └── protocols.ts ├── declaration.d.ts ├── declaration.pegjs ├── functions.ts ├── package-lock.json ├── package.json ├── parse.ts ├── reified.ts ├── scope.ts ├── swift-to-js.test.ts ├── swift-to-js.ts ├── test.swift ├── tests ├── arrays │ ├── empty.js │ ├── empty.swift │ ├── for-in.js │ ├── for-in.swift │ ├── int-count.js │ ├── int-count.swift │ ├── int-get.js │ ├── int-get.swift │ ├── int-set.js │ ├── int-set.swift │ ├── one-int.js │ ├── one-int.swift │ ├── two-ints.js │ ├── two-ints.swift │ ├── update.js │ └── update.swift ├── builtins │ ├── discard.js │ ├── discard.swift │ ├── escaping.js │ ├── escaping.swift │ ├── extend.js │ ├── extend.swift │ ├── magic.js │ ├── magic.swift │ ├── swap.js │ ├── swap.swift │ ├── unique.js │ └── unique.swift ├── class │ ├── computed-getter.js │ ├── computed-getter.swift │ ├── copy-and-edit.js │ ├── copy-and-edit.swift │ ├── deinit.js │ ├── deinit.swift │ ├── distance.js │ ├── distance.swift │ ├── fail-init.js │ ├── fail-init.swift │ ├── in-array.js │ ├── in-array.swift │ ├── in-tuple.js │ ├── in-tuple.swift │ ├── make-struct.js │ ├── make-struct.swift │ ├── var.js │ ├── var.swift │ ├── virtual-dispatch.js │ ├── virtual-dispatch.swift │ ├── weak.js │ └── weak.swift ├── complex │ ├── factorial-functional.js │ ├── factorial-functional.swift │ ├── fizzbuzz.js │ ├── fizzbuzz.swift │ ├── sequence-map.js │ └── sequence-map.swift ├── dictionaries │ ├── empty.js │ ├── empty.swift │ ├── int-count.js │ ├── int-count.swift │ ├── int-get.js │ ├── int-get.swift │ ├── int-set.js │ ├── int-set.swift │ ├── keys.js │ ├── keys.swift │ ├── one-int.js │ ├── one-int.swift │ ├── optional-string-dict.js │ ├── optional-string-dict.swift │ ├── string-dict.js │ └── string-dict.swift ├── enums │ ├── arithmetic.js │ ├── arithmetic.swift │ ├── field-requires-copy.js │ ├── field-requires-copy.swift │ ├── numeric.js │ ├── numeric.swift │ ├── with-simple-data.js │ └── with-simple-data.swift ├── errors │ ├── defer.js │ ├── defer.swift │ ├── do.js │ ├── do.swift │ ├── force.js │ ├── force.swift │ ├── guard.js │ ├── guard.swift │ ├── optional.js │ ├── optional.swift │ ├── precondition.js │ ├── precondition.swift │ ├── recover.js │ ├── recover.swift │ ├── rethrows.js │ ├── rethrows.swift │ ├── throw.js │ └── throw.swift ├── generics │ ├── equatable-call.js │ ├── equatable-call.swift │ ├── equatable.js │ ├── equatable.swift │ ├── for-in.js │ ├── for-in.swift │ ├── negate.js │ └── negate.swift ├── numbers │ ├── conversions.js │ ├── conversions.swift │ ├── double-increment.js │ ├── double-increment.swift │ ├── factorial-recursive.js │ ├── factorial-recursive.swift │ ├── generic.js │ ├── generic.swift │ ├── inout.js │ ├── inout.swift │ ├── int-increment-until-zero.js │ ├── int-increment-until-zero.swift │ ├── int-increment.js │ ├── int-increment.swift │ ├── integer-ranges.js │ ├── integer-ranges.swift │ ├── more-silly-math.js │ ├── more-silly-math.swift │ ├── negate.js │ ├── negate.swift │ ├── repeat.js │ ├── repeat.swift │ ├── silly-math.js │ ├── silly-math.swift │ ├── wrapped.js │ └── wrapped.swift ├── optionals │ ├── chaining.js │ ├── chaining.swift │ ├── coalesce.js │ ├── coalesce.swift │ ├── compare.js │ ├── compare.swift │ ├── double.js │ ├── double.swift │ ├── extract-double.js │ ├── extract-double.swift │ ├── extract-single.js │ ├── extract-single.swift │ ├── force-extract.js │ ├── force-extract.swift │ ├── from-int.js │ ├── from-int.swift │ ├── has-value.js │ ├── has-value.swift │ ├── nil-literals.js │ └── nil-literals.swift ├── strings │ ├── codepoint-length.js │ ├── codepoint-length.swift │ ├── concat.js │ ├── concat.swift │ ├── encoded-length.js │ ├── encoded-length.swift │ ├── hash.js │ ├── hash.swift │ ├── interpolated.js │ ├── interpolated.swift │ ├── lowercase.js │ ├── lowercase.swift │ ├── parse-bool.js │ ├── parse-bool.swift │ ├── print.js │ ├── print.swift │ ├── static-string.js │ ├── static-string.swift │ ├── string-length.js │ ├── string-length.swift │ ├── unicode-literal.js │ ├── unicode-literal.swift │ ├── uppercase.js │ └── uppercase.swift ├── struct │ ├── computed-getter.js │ ├── computed-getter.swift │ ├── copy-and-edit.js │ ├── copy-and-edit.swift │ ├── distance.js │ ├── distance.swift │ ├── in-array.js │ ├── in-array.swift │ ├── in-tuple.js │ ├── in-tuple.swift │ ├── make-struct.js │ ├── make-struct.swift │ ├── var.js │ └── var.swift └── tuples │ ├── binary-calculation.js │ ├── binary-calculation.swift │ ├── binary-init.js │ ├── binary-init.swift │ ├── binary-pattern.js │ ├── binary-pattern.swift │ ├── empty-init.js │ ├── empty-init.swift │ ├── make-binary.js │ ├── make-binary.swift │ ├── make-unary.js │ ├── make-unary.swift │ ├── read-binary-first.js │ ├── read-binary-first.swift │ ├── read-binary-pattern.js │ ├── read-binary-pattern.swift │ ├── read-binary-second.js │ ├── read-binary-second.swift │ ├── unary-init.js │ └── unary-init.swift ├── tsconfig.json ├── tslint.json ├── types.d.ts ├── types.pegjs ├── utils.ts └── values.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | *.swift.ast 5 | *.js.map 6 | *.mjs.map 7 | *.mjs 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: dist/swift-to-js.js dist/ast.js dist/types.js dist/declaration.js 2 | 3 | clean: 4 | rm -rf dist/ 5 | 6 | test: all 7 | npm run test 8 | npm run lint 9 | 10 | coverage: all 11 | npm run test:coverage 12 | 13 | .PHONY: all clean test 14 | 15 | 16 | dist/: 17 | mkdir -p dist/ 18 | 19 | dist/swift-to-js.js: dist/ package.json tsconfig.json *.ts builtins/*.ts 20 | npm run build:ts 21 | 22 | dist/ast.js: dist/ ast.pegjs 23 | npm run build:ast 24 | 25 | dist/types.js: dist/ types.pegjs 26 | npm run build:types 27 | 28 | dist/declaration.js: dist/ declaration.pegjs 29 | npm run build:declaration 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | swift-to-js 2 | =========== 3 | This is an experimental JavaScript backend for the standard Swift compiler. It's not ready for even beta use yet as much of the language is missing. 4 | 5 | Installation 6 | ------------ 7 | #### Install Swift 8 | Choose and install a compatible Swift 5.0 snapshot from [swift.org/download/](https://swift.org/download/). [2018-12-16](https://swift.org/builds/swift-5.0-branch/xcode/swift-5.0-DEVELOPMENT-SNAPSHOT-2018-12-16-a/swift-5.0-DEVELOPMENT-SNAPSHOT-2018-12-16-a-osx.pkg)'s snapshot build is known to be compatible. For macOS, a recent version of Xcode is required. 9 | 10 | #### Install Node & NPM 11 | You know what to do... 12 | 13 | #### Clone and run the project 14 | ```bash 15 | $ git clone git@github.com:rpetrich/swift-to-js.git 16 | $ cd swift-to-js 17 | $ npm install 18 | $ npm run build 19 | $ node . test.swift 20 | ``` 21 | 22 | #### Run the tests 23 | ```bash 24 | $ npm test 25 | ``` 26 | 27 | ## Development Notes 28 | Approach used in swift-to-js is to translate the AST produced by `swiftc -dump-ast -- file.swift` incrementally using the type information provided by the compiler at each step to determine how to translate. Walking each AST node produces a partially-translated value on which peephole optimization can be performed before baking into the node to insert into an ever-growing babylon AST. Generated code uses ES6 features including module syntax. Basic support for source maps is supported, but not all mappings are applied correctly. 29 | 30 | It relies heavily on swiftc's proper pretty-printing of the AST. As the AST isn't documented or supported and changes from version-to-version—staying up to date with the latest swift releases may prove a chore! PEG grammars are provided for swift's pretty-printed AST, declaration reference syntax, and type declaration syntax. These were built piecemeal by examining output from the compiler and likely aren't exhaustive. Similarly, many details of the standard library's internal structure must be replicated directly and can drift from upstream. 31 | 32 | Types are categorized into simple value types, complex value types and reference types. Simple value types are mapped directly to a single primitive type in JavaScript. Many Swift types are represented natively as the closest JavaScript types, and appropriate conversion/checking/boxing code is integrated when values are converted between types. Complex types are deep copied by value whenever they are returned or assigned into another value, including when inside data structures or wrapper types. Reference types will be implemented as ES6 classes (was in previous SSA-based implementation) 33 | 34 | Simple value types are represented as the closest JavaScript equivalent. `Bool` becomes `boolean`, `Double` becomes `number` and `String` becomes `string`. Similarly, `Int`, `Float`, and `UInt` also become number, and optional runtime checks/conversions are inserted automatically to ensure values stay in the proper ranges. 35 | 36 | Structs are represented as JavaScript objects containing the appropriate fields and are automatically copied when necessary to preserve Swift's semantics. Much work has been done to remove many superfluous copies. Empty structs are represented as `undefined` and unary structs are represented as the underlying value so that wrapper and trait-only types can be zero overhead. 37 | 38 | Basic support for consuming generic APIs is implemented (via `Optional`, `Array`, `Dictionary` and numerous protocol types) including cases where the full type is only known at runtime. Support for calling generic functions at runtime is via witness tables that are generated at the call site. Mutation through a witness table isn't entirely correct, but will be made correct in the future. 39 | 40 | Optionals are implemented by representing `.none` as `null` and `.some(T)` as the underlying value for `T`. `null` is chosen instead of `undefined` somewhat arbitrarily. Nested optionals are implemented by boxing inside an array to avoid the ambiguity between `.none` and `.some(.none)`. `Bool??.none` becomes `[]`, `Bool??.some(Bool?.none)` becomes `[null]`, `Bool??.some(Bool?.some(true))` becomes `[true]`. Optimization for most operations on `Optional` is implemented. 41 | 42 | Tuples are implemented as arrays of the appropriate length, except unary tuples are represented as the underlying value and empty tuples (the unit type) are represented as `undefined`. Tuples are considered a complex value type and will be deep copied as needed. Binary and larger tuples containing only simple values are optimized into `.slice()` calls when copied. 43 | 44 | Array types are implemented as JavaScript arrays, with deep copying of embedded value types implemented if necessary. All array operations are bounds-checked with panics when the array is accessed outside its range—reads are allowed within the bounds and writes are allowed one passed the end to match Swift's behaviour. 45 | 46 | Dictionary types are implemented as a JavaScript object and support only primitive keys known at compiletype. For non-string keys, appropriate conversion functions are implemented when reading keys. swift-to-js may migrate to using ES6 Maps for some types, but will need a custom map to support compound value types as keys. 47 | 48 | Simple enums are implemented as a number representing each case. Enums that contain fields are implemented as an array with the first element representing the discriminant index and case fields stored starting at element index 1. If any field of any case requires copying a deep copy helper will be emitted for every assignment, otherwise a simple call to `slice`. In the future it may be possible that enums containing values with disjoint representations could even be stored unboxed. This requires more research. 49 | 50 | Exceptions will be implemented as normal JavaScript exceptions and try/catch blocks (was in previous SSA-based implementation). Panics are, unfortunately, also implemented as exceptions leading to collisions in any code that recovers from an exception. More work will need to be done to discriminate Swift exceptions from panics and JavaScript exceptions. 51 | 52 | Constructors, private functions, any functions that call themselves recursively, and any library functions implemented in `builtins.ts` not marked with `noinline` will be inlined into calling functions. This avoids some amount of code bloat with Swift's wrapper type functions present in the AST and allows copies of complex value types to be elided. In the future, better inlining decisions can be made. 53 | 54 | `inout` parameters are supported only in cases where the type is known fully. Intention is to use the runtime type information to determine how to create boxes and to read/write through boxes. 55 | 56 | Destructors aren't supported and won't ever be due to JavaScript's GC model. Intention is to warn during compilation if any complex/impure destructors are discovered in the generated AST. 57 | 58 | Weak references are converted to strong references due to JavaScript's GC model and lack of support for weak references. 59 | 60 | Names are mangled based on a few simple rules. Symbols that aren't supported in JavaScript identifiers are converted to `$name$` format. For example, the `==` function is converted to `$equals$`. Method/function names are mangled to include all named arguments separated by `$`. Internal helper functions are always prefixed by a `$` symbol, because `$` obviously represents Swift and not JQuery. 61 | 62 | Source maps are produced by populating the bablyon AST's `loc` properties with data the Swift compiler includes in it's AST's `range` properties. When peephole optimizations remove operations, the inner-most source location is generally preserved. Mapping information will be missing on purely generated code and any code inlined from standard library functions. -------------------------------------------------------------------------------- /ast.d.ts: -------------------------------------------------------------------------------- 1 | export function parse(text: string): Term; 2 | 3 | export interface Term { 4 | name: string; 5 | args: string[]; 6 | properties: { [name: string]: Property }; 7 | children: Term[]; 8 | location: Location; 9 | } 10 | 11 | export type Property = true | string | Range | string[]; 12 | 13 | export interface Range { 14 | from: string; 15 | to: string; 16 | } 17 | 18 | 19 | export interface Location { 20 | start: Position; 21 | end: Position; 22 | } 23 | 24 | export interface Position { 25 | offset: number; 26 | line: number; 27 | column: number; 28 | } 29 | -------------------------------------------------------------------------------- /ast.pegjs: -------------------------------------------------------------------------------- 1 | Program = _ term:Term _ { return term; } 2 | 3 | Term 4 | = "(" _ name:BareString _ headTokens:WhitespaceAndAttributeOrArgument* _ children:WhitespaceAndTerm* _ tailTokens:WhitespaceAndAttributeOrArgument* _ ")" { 5 | const props = {}, args = []; 6 | function addToken(token) { 7 | if (typeof token == "string") { 8 | args.push(token); 9 | } else { 10 | if (token.value !== true || !Object.hasOwnProperty.call(props, token.key)) { 11 | props[token.key] = token.value; 12 | } 13 | } 14 | } 15 | headTokens.forEach(addToken); 16 | tailTokens.forEach(addToken); 17 | return { name: name, args:args, properties: props, children: children, location: location() }; 18 | } 19 | WhitespaceAndTerm 20 | = _ term:Term { return term; } 21 | 22 | AttributeOrArgument 23 | = Attribute / Argument 24 | WhitespaceAndAttributeOrArgument 25 | = _ value:AttributeOrArgument { return value; } 26 | 27 | Argument 28 | = argument:(DoubleQuotedString / SingleQuotedString / TypeArgumentString / AddressLiteral) { return argument; } 29 | 30 | AddressLiteral 31 | = '0x' [0-9a-f]+ { return text() } 32 | 33 | Attribute 34 | = key:Identifier value:AttributeValue? { return { key: key, value: value === null ? true : value } } 35 | AttributeValue 36 | = ('=' / ': ' / ' #') value: ('( )' / Range / List / String / ParenthesizedBareString / EmptyString) { return value; } 37 | 38 | EmptyString "empty string" 39 | = "" { return ""; } 40 | 41 | String "string" 42 | = BareString / DoubleQuotedString / SingleQuotedString 43 | 44 | EscapeSequence "escape sequence" 45 | = StandardEscapeSequence / UnicodeEscapeSequence 46 | StandardEscapeSequence 47 | = "\\" sequence:['"trn\\0] { 48 | return { 49 | "'": "'", 50 | '"': '"', 51 | "t": "\t", 52 | "r": "\r", 53 | "n": "\n", 54 | "\\": "\\", 55 | "0": "\0", 56 | }[sequence]; 57 | } 58 | UnicodeEscapeSequence 59 | = '\\u{' digits:[0-9A-F]+ '}' { return String.fromCodePoint(parseInt(digits.join(""), 16)); } 60 | 61 | DoubleQuotedString "doublequote string" 62 | = '"' content:(EscapeSequence / [^"])* '"' { return content.join(""); } 63 | WhitespaceAndDoubleQuotedString 64 | = _ str:DoubleQuotedString { return str; } 65 | 66 | SingleQuotedString "singlequote string" 67 | = "'" content:(EscapeSequence / [^'])* "'" { return content.join(""); } 68 | 69 | TypeArgumentString "type argument" 70 | = "<" value:[^>]* ">" { return "<" + value.join("") + ">" } 71 | 72 | List "list" 73 | = BracketedList / CommaSeparatedBareString / EmptyBracketedList 74 | 75 | CommaSeparatedBareString 76 | = head:(SingleQuotedString / BareString) tail:CommaPrefixedBareString+ { return [head].concat(tail); } 77 | CommaPrefixedBareString 78 | = ',' str:(SingleQuotedString / BareString) { return str; } 79 | 80 | BracketedList 81 | = '[' _ head:BareStringNoWhitespace tail:BracketedListTail* _ ']' { return [head].concat(tail); } 82 | BracketedListTail 83 | = ',' _ string:BareStringNoWhitespace { return string; } 84 | 85 | EmptyBracketedList 86 | = '[]' { return []; } 87 | 88 | Identifier "identifier" 89 | = prefix:[a-zA-Z_@.] remaining:[a-zA-Z_\-@.]* { return prefix + remaining.join(""); } 90 | 91 | BareString "bare string" 92 | = prefix:BareStringToken remaining:BareStringTail* { return prefix + remaining.join(""); } 93 | BareStringToken 94 | = ': ' / ', ' / [#a-zA-Z0-9_.:@*<>~$%&+\-!?/] 95 | BareStringTail 96 | = BareStringToken / '=' / BareStringParenPair / BareStringSquarePair / BareStringExtension 97 | BareStringTailWhitespace 98 | = BareStringTail / " " 99 | BareStringParenPair "parenthesized string component" 100 | = '(' body:BareStringTailWhitespace* ')' { return "(" + body.join("") + ")"; } 101 | BareStringSquarePair "subscripted bare string" 102 | = ws: _ '[' body:BareStringTailWhitespace* ']' { return ws + "[" + body.join("") + "]"; } 103 | BareStringExtension 104 | = ' extension.' / ' closure discriminator=' // Special case for declaration syntax that includes spaces 105 | 106 | BareStringNoWhitespace "bare string no whitespace" 107 | = prefix:BareStringNoWhitespaceToken remaining:BareStringNoWhitespaceTail* { return prefix + remaining.join(""); } 108 | BareStringNoWhitespaceToken 109 | = [a-zA-Z0-9_.:@*<>~$%&+\-!?/] 110 | BareStringNoWhitespaceTail 111 | = BareStringNoWhitespaceToken 112 | 113 | ParenthesizedBareString "parenthesized bare string" 114 | = all:('(' BareString ')') { return all.join(""); } 115 | 116 | Range "range" 117 | = '[' _ from:String ' ' _ '- ' _ to:String ']' { return { from: from, to: to }; } 118 | 119 | _ "whitespace" 120 | = [ \t\n\r]* 121 | -------------------------------------------------------------------------------- /builtins.ts: -------------------------------------------------------------------------------- 1 | import { primitive, PossibleRepresentation, TypeMap } from "./reified"; 2 | import { Scope } from "./scope"; 3 | import { binary, literal } from "./values"; 4 | 5 | import { OptionalNilComparisonType as OptionalNilComparisonTypeBuiltin } from "./builtins/_OptionalNilComparisonType"; 6 | import { Array as ArrayBuiltin } from "./builtins/Array"; 7 | import { Bool as BoolBuiltin } from "./builtins/Bool"; 8 | import { Character as CharacterBuiltin } from "./builtins/Character"; 9 | import { ClosedRange as ClosedRangeBuiltin } from "./builtins/ClosedRange"; 10 | import { cachedBuilder } from "./builtins/common"; 11 | import { DefaultStringInterpolation as DefaultStringInterpolationBuiltin } from "./builtins/DefaultStringInterpolation"; 12 | import { Dictionary as DictionaryBuiltin } from "./builtins/Dictionary"; 13 | import { buildFloatingType } from "./builtins/floats"; 14 | import { functions } from "./builtins/functions"; 15 | import { Hasher as HasherBuiltin } from "./builtins/Hasher"; 16 | import { IndexingIterator as IndexingIteratorBuiltin } from "./builtins/IndexingIterator"; 17 | import { buildIntegerType } from "./builtins/integers"; 18 | import { Optional as OptionalBuiltin } from "./builtins/Optional"; 19 | import { addDefaultProtocols } from "./builtins/protocols"; 20 | import { String as StringBuiltin } from "./builtins/String"; 21 | 22 | export interface BuiltinConfiguration { 23 | checkedIntegers: boolean; 24 | simpleStrings: boolean; 25 | } 26 | 27 | function defaultTypes({ checkedIntegers, simpleStrings }: BuiltinConfiguration): TypeMap { 28 | const protocolTypes: TypeMap = Object.create(null); 29 | 30 | addDefaultProtocols(protocolTypes); 31 | 32 | const BoolType = cachedBuilder(BoolBuiltin); 33 | 34 | return { 35 | ...protocolTypes, 36 | Bool: BoolType, 37 | Int1: BoolType, 38 | UInt: cachedBuilder((globalScope) => buildIntegerType(globalScope, 0, 4294967295, 32, checkedIntegers, (value, scope) => binary(">>>", value, literal(0), scope))), 39 | Int: cachedBuilder((globalScope) => buildIntegerType(globalScope, -2147483648, 2147483647, 32, checkedIntegers, (value, scope) => binary("|", value, literal(0), scope))), 40 | UInt8: cachedBuilder((globalScope) => buildIntegerType(globalScope, 0, 255, 8, checkedIntegers, (value, scope) => binary("&", value, literal(0xFF), scope))), 41 | Int8: cachedBuilder((globalScope) => buildIntegerType(globalScope, -128, 127, 8, checkedIntegers, (value, scope) => binary(">>", binary("<<", value, literal(24), scope), literal(24), scope))), 42 | UInt16: cachedBuilder((globalScope) => buildIntegerType(globalScope, 0, 65535, 16, checkedIntegers, (value, scope) => binary("&", value, literal(0xFFFF), scope))), 43 | Int16: cachedBuilder((globalScope) => buildIntegerType(globalScope, -32768, 32767, 16, checkedIntegers, (value, scope) => binary(">>", binary("<<", value, literal(16), scope), literal(16), scope))), 44 | UInt32: cachedBuilder((globalScope) => buildIntegerType(globalScope, 0, 4294967295, 32, checkedIntegers, (value, scope) => binary(">>>", value, literal(0), scope))), 45 | Int32: cachedBuilder((globalScope) => buildIntegerType(globalScope, -2147483648, 2147483647, 32, checkedIntegers, (value, scope) => binary("|", value, literal(0), scope))), 46 | UInt64: cachedBuilder((globalScope) => buildIntegerType(globalScope, 0, Number.MAX_SAFE_INTEGER, 53, checkedIntegers, (value) => value)), // 53-bit integers 47 | Int64: cachedBuilder((globalScope) => buildIntegerType(globalScope, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, 53, checkedIntegers, (value) => value)), // 53-bit integers 48 | Float: cachedBuilder(buildFloatingType), 49 | Double: cachedBuilder(buildFloatingType), 50 | String: StringBuiltin(simpleStrings), 51 | StaticString: cachedBuilder(() => primitive(PossibleRepresentation.String, literal(""), { 52 | })), 53 | DefaultStringInterpolation: DefaultStringInterpolationBuiltin, 54 | Character: CharacterBuiltin, 55 | Optional: OptionalBuiltin, 56 | _OptionalNilComparisonType: OptionalNilComparisonTypeBuiltin, 57 | Array: ArrayBuiltin, 58 | IndexingIterator: IndexingIteratorBuiltin, 59 | Dictionary: DictionaryBuiltin, 60 | Error: cachedBuilder((globalScope) => primitive(PossibleRepresentation.Number, literal(0), { 61 | hashValue(scope, arg) { 62 | return arg(0, "self"); 63 | }, 64 | })), 65 | ClosedRange: ClosedRangeBuiltin, 66 | Hasher: HasherBuiltin, 67 | }; 68 | } 69 | 70 | export function newScopeWithBuiltins(): Scope { 71 | return { 72 | name: "global", 73 | declarations: Object.create(null), 74 | types: defaultTypes({ 75 | checkedIntegers: false, 76 | simpleStrings: true, 77 | }), 78 | functions: Object.assign(Object.create(null), functions), 79 | functionUsage: Object.create(null), 80 | mapping: Object.create(null), 81 | parent: undefined, 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /builtins/Bool.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { primitive, PossibleRepresentation } from "../reified"; 3 | import { Scope } from "../scope"; 4 | import { binary, call, callable, conditional, literal, logical, member, unary, undefinedValue } from "../values"; 5 | import { applyDefaultConformances, binaryBuiltin, cachedBuilder, reuseArgs } from "./common"; 6 | 7 | export function Bool(globalScope: Scope) { 8 | return primitive(PossibleRepresentation.Boolean, literal(false), { 9 | "init(_builtinBooleanLiteral:)": wrapped((scope, arg) => arg(0, "value"), "(Bool) -> Bool"), 10 | "init(_:)": wrapped((scope, arg) => { 11 | // Optional init from string 12 | return reuseArgs(arg, 0, scope, ["string"], (stringValue) => { 13 | return logical("||", 14 | binary("===", 15 | stringValue, 16 | literal("True"), 17 | scope, 18 | ), 19 | logical("&&", 20 | binary("!==", 21 | stringValue, 22 | literal("False"), 23 | scope, 24 | ), 25 | literal(null), 26 | scope, 27 | ), 28 | scope, 29 | ); 30 | }); 31 | }, "(String) -> Self?"), 32 | "_getBuiltinLogicValue()": (scope, arg, type) => callable(() => arg(0, "literal"), "() -> Int1"), 33 | "&&": wrapped((scope, arg) => logical("&&", arg(0, "lhs"), call(arg(1, "rhs"), [], [], scope), scope), "(Bool, () -> Bool) -> Bool"), 34 | "||": wrapped((scope, arg) => logical("||", arg(0, "lhs"), call(arg(1, "rhs"), [], [], scope), scope), "(Bool, () -> Bool) -> Bool"), 35 | "!": wrapped((scope, arg) => unary("!", arg(0, "value"), scope), "(Self) -> Bool"), 36 | "random": wrapped((scope, arg) => binary("<", call(member("Math", "random", scope), [], [], scope), literal(0.5), scope), "() -> Bool"), 37 | "description": wrapped((scope, arg) => { 38 | return conditional(arg(0, "self"), literal("True"), literal("False"), scope); 39 | }, "(Bool) -> String"), 40 | }, applyDefaultConformances({ 41 | Equatable: { 42 | functions: { 43 | "==": wrapped(binaryBuiltin("===", 0), "(Bool, Bool) -> Bool"), 44 | "!=": wrapped(binaryBuiltin("!==", 0), "(Bool, Bool) -> Bool"), 45 | }, 46 | requirements: [], 47 | }, 48 | ExpressibleByBooleanLiteral: { 49 | functions: { 50 | "init(booleanLiteral:)": wrapped((scope, arg) => arg(0, "value"), "(Bool) -> Bool"), 51 | }, 52 | requirements: [], 53 | }, 54 | }, globalScope), { 55 | Type: cachedBuilder(() => primitive(PossibleRepresentation.Undefined, undefinedValue)), 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /builtins/Character.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { primitive, PossibleRepresentation } from "../reified"; 3 | import { literal } from "../values"; 4 | import { binaryBuiltin, cachedBuilder } from "./common"; 5 | 6 | export const Character = cachedBuilder((globalScope) => { 7 | return primitive(PossibleRepresentation.String, literal(""), { 8 | "init(_:)": wrapped((scope, arg) => { 9 | return arg(0, "character"); 10 | }, "(String) -> Character"), 11 | "==": wrapped(binaryBuiltin("===", 0), "(Character, Character) -> Bool"), 12 | "!=": wrapped(binaryBuiltin("!==", 0), "(Character, Character) -> Bool"), 13 | "<": wrapped(binaryBuiltin("<", 0), "(Character, Character) -> Bool"), 14 | "<=": wrapped(binaryBuiltin("<=", 0), "(Character, Character) -> Bool"), 15 | ">": wrapped(binaryBuiltin(">", 0), "(Character, Character) -> Bool"), 16 | ">=": wrapped(binaryBuiltin(">=", 0), "(Character, Character) -> Bool"), 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /builtins/ClosedRange.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { primitive, PossibleRepresentation, ReifiedType, TypeParameterHost } from "../reified"; 3 | import { addVariable, lookup, uniqueName, DeclarationFlags, Scope } from "../scope"; 4 | import { concat } from "../utils"; 5 | import { binary, call, callable, conformance, expr, functionValue, ignore, isPure, literal, logical, member, read, set, statements, tuple, typeValue, Value } from "../values"; 6 | import { applyDefaultConformances, reuseArgs } from "./common"; 7 | 8 | import { arrayPattern, blockStatement, forStatement, returnStatement, updateExpression, variableDeclaration, variableDeclarator, Statement } from "@babel/types"; 9 | 10 | const dummyType = typeValue({ kind: "name", name: "Dummy" }); 11 | 12 | function closedRangeIterate(type: Value, range: Value, scope: Scope, body: (value: Value) => Statement): Statement[] { 13 | let end; 14 | const contents = []; 15 | const i = uniqueName(scope, "i"); 16 | if (range.kind === "tuple" && range.values.length === 2) { 17 | contents.push(addVariable(scope, i, "Int", range.values[0])); 18 | const endExpression = read(range.values[1], scope); 19 | if (isPure(endExpression)) { 20 | end = expr(endExpression); 21 | } else { 22 | const endIdentifier = uniqueName(scope, "end"); 23 | contents.push(addVariable(scope, endIdentifier, "Int", expr(endExpression))); 24 | end = lookup(endIdentifier, scope); 25 | } 26 | } else { 27 | addVariable(scope, i, "Int"); 28 | const iExpression = read(lookup(i, scope), scope); 29 | if (iExpression.type !== "Identifier") { 30 | throw new TypeError(`Expected i to be an identifier, got a ${iExpression.type}`); 31 | } 32 | const endIdentifier = uniqueName(scope, "end"); 33 | addVariable(scope, endIdentifier, "Int"); 34 | const endIdentifierExpression = read(lookup(endIdentifier, scope), scope); 35 | if (endIdentifierExpression.type !== "Identifier") { 36 | throw new TypeError(`Expected end to be an identifier, got a ${endIdentifierExpression.type}`); 37 | } 38 | contents.push(variableDeclaration("const", [variableDeclarator(arrayPattern([iExpression, endIdentifierExpression]), read(range, scope))])); 39 | end = lookup(endIdentifier, scope); 40 | } 41 | const result = forStatement( 42 | contents.length === 1 ? contents[0] : undefined, 43 | read(binary("<=", lookup(i, scope), end, scope), scope), 44 | updateExpression("++", read(lookup(i, scope), scope)), 45 | body(lookup(i, scope)), 46 | ); 47 | if (contents.length === 1) { 48 | return [result]; 49 | } else { 50 | return concat(contents as Statement[], [result]); 51 | } 52 | } 53 | 54 | export function ClosedRange(globalScope: Scope, typeParameters: TypeParameterHost): ReifiedType { 55 | const [ valueType ] = typeParameters("Value"); 56 | return primitive(PossibleRepresentation.Array, tuple([literal(0), literal(0)]), { 57 | }, applyDefaultConformances({ 58 | // TODO: Implement Equatable 59 | Equatable: { 60 | functions: { 61 | "==": wrapped((scope, arg) => { 62 | const collectionConformance = conformance(valueType, "Equatable", scope); 63 | const equalsMethod = call(functionValue("==", collectionConformance, "(Type) -> (Self, Self) -> Bool"), [valueType], ["Type"], scope); 64 | return reuseArgs(arg, 0, scope, ["lhs", "rhs"], (lhs, rhs) => { 65 | return logical("&&", 66 | call(equalsMethod, [member(lhs, literal(0), scope), member(rhs, literal(0), scope)], ["Self", "Self"], scope), 67 | call(equalsMethod, [member(lhs, literal(1), scope), member(rhs, literal(1), scope)], ["Self", "Self"], scope), 68 | scope, 69 | ); 70 | }); 71 | }, "(Self, Self) -> Bool"), 72 | }, 73 | requirements: [], 74 | }, 75 | Sequence: { 76 | functions: { 77 | reduce: (scope, arg, type) => { 78 | const range = arg(2, "range"); 79 | return callable((innerScope, innerArg) => { 80 | const result = uniqueName(innerScope, "result"); 81 | const initialResult = innerArg(0, "initialResult"); 82 | const next = innerArg(1, "next"); 83 | return statements(concat( 84 | [addVariable(innerScope, result, dummyType, initialResult)], 85 | closedRangeIterate(valueType, range, innerScope, (i) => blockStatement( 86 | ignore(set(lookup(result, scope), call(next, [lookup(result, scope), i], [dummyType, dummyType], scope), scope), scope), 87 | )), 88 | [returnStatement(read(lookup(result, scope), scope))], 89 | )); 90 | }, "(Result, (Result, Self.Element) -> Result) -> Result"); 91 | }, 92 | }, 93 | requirements: [], 94 | }, 95 | Collection: { 96 | functions: { 97 | map: (scope, arg, type) => { 98 | const range = arg(2, "range"); 99 | return callable((innerScope, innerArg) => { 100 | const mapped = uniqueName(innerScope, "mapped"); 101 | const callback = innerArg(0, "callback"); 102 | return statements(concat( 103 | [addVariable(innerScope, mapped, dummyType, literal([]), DeclarationFlags.Const)], 104 | closedRangeIterate(valueType, range, innerScope, (i) => blockStatement( 105 | ignore(call( 106 | member(lookup(mapped, scope), "push", scope), 107 | [call(callback, [i], [dummyType], scope)], 108 | [dummyType], 109 | scope, 110 | ), scope), 111 | )), 112 | [returnStatement(read(lookup(mapped, scope), scope))], 113 | )); 114 | }, "((Self) -> V) -> [V]"); 115 | }, 116 | }, 117 | requirements: [], 118 | }, 119 | }, globalScope)); 120 | } 121 | -------------------------------------------------------------------------------- /builtins/DefaultStringInterpolation.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { primitive, PossibleRepresentation } from "../reified"; 3 | import { literal, set, statements } from "../values"; 4 | import { cachedBuilder } from "./common"; 5 | 6 | export const DefaultStringInterpolation = cachedBuilder((globalScope) => primitive(PossibleRepresentation.String, literal(""), { 7 | "init(literalCapacity:interpolationCount:)": wrapped(() => literal(""), `(Int, Int) -> Self`), 8 | "appendLiteral": wrapped((scope, arg, type, argTypes, outerArg) => { 9 | const interpolationArg = outerArg(0, "interpolation"); 10 | const literalArg = arg(0, "literal"); 11 | if (literalArg.kind === "expression" && literalArg.expression.type === "StringLiteral" && literalArg.expression.value === "") { 12 | return statements([]); 13 | } else { 14 | return set(interpolationArg, literalArg, scope, "+="); 15 | } 16 | }, `(String) -> Void`), 17 | "appendInterpolation": wrapped((scope, arg, type, argTypes, outerArg) => { 18 | return set(outerArg(1, "interpolation"), arg(0, "value"), scope, "+="); 19 | }, `(String) -> Void`), 20 | })); 21 | -------------------------------------------------------------------------------- /builtins/Dictionary.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { expressionSkipsCopy, inheritLayout, withPossibleRepresentations, FunctionMap, PossibleRepresentation, ReifiedType, TypeParameterHost } from "../reified"; 3 | import { addVariable, lookup, uniqueName, Scope } from "../scope"; 4 | import { concat, lookupForMap } from "../utils"; 5 | import { call, conditional, conformance, copy, expr, expressionLiteralValue, functionValue, hasRepresentation, ignore, literal, logical, member, read, representationsForTypeValue, reuse, set, statements, stringifyValue, typeFromValue, typeTypeValue, typeValue, unary, Value } from "../values"; 6 | import { applyDefaultConformances, isEmptyFromLength, readLengthField, reuseArgs, startIndexOfZero } from "./common"; 7 | import { emptyOptional, optionalIsSome, unwrapOptional, wrapInOptional } from "./Optional"; 8 | 9 | import { blockStatement, breakStatement, forInStatement, identifier, ifStatement, isLiteral, returnStatement, Identifier, Node } from "@babel/types"; 10 | 11 | export function Dictionary(globalScope: Scope, typeParameters: TypeParameterHost): ReifiedType { 12 | const [ keyType, valueType ] = typeParameters("Key", "Value"); 13 | if (keyType.kind !== "type") { 14 | // TODO: Support runtime types 15 | throw new TypeError(`Runtime types are not supported as K in [K: V]`); 16 | } 17 | if (valueType.kind !== "type") { 18 | // TODO: Support runtime types 19 | throw new TypeError(`Runtime types are not supported as V in [K: V]`); 20 | } 21 | const keysType = typeValue({ kind: "array", type: keyType.type }); 22 | const reifiedValueType = typeFromValue(valueType, globalScope); 23 | function objectDictionaryImplementation(converter?: Value): ReifiedType { 24 | const reifiedKeysType = typeFromValue(keysType, globalScope); 25 | return { 26 | functions: lookupForMap({ 27 | "subscript(_:)": wrapped((scope, arg, type) => { 28 | return reuseArgs(arg, 0, scope, ["dict", "index"], (dict, index) => { 29 | return conditional( 30 | call( 31 | member( 32 | member( 33 | expr(identifier("Object")), 34 | "hasOwnProperty", 35 | scope, 36 | ), 37 | "call", 38 | scope, 39 | ), 40 | [dict, index], 41 | ["Any", "String"], 42 | scope, 43 | ), 44 | wrapInOptional(copy(member(dict, index, scope), valueType), valueType, scope), 45 | emptyOptional(valueType, scope), 46 | scope, 47 | ); 48 | }); 49 | }, "(Self, Self.Key) -> Self.Value?"), 50 | "subscript(_:)_set": wrapped((scope, arg, type) => { 51 | const dict = arg(0, "dict"); 52 | const index = arg(1, "index"); 53 | const valueExpression = read(arg(2, "value"), scope); 54 | const valueIsOptional = hasRepresentation(valueType, PossibleRepresentation.Null, scope); 55 | if (valueIsOptional.kind === "expression" && valueIsOptional.expression.type === "BooleanLiteral") { 56 | if (valueIsOptional.expression.value) { 57 | if (valueExpression.type === "ArrayExpression" && valueExpression.elements.length === 0) { 58 | return unary("delete", member(dict, index, scope), scope); 59 | } 60 | } else { 61 | if (valueExpression.type === "NullLiteral") { 62 | return unary("delete", member(dict, index, scope), scope); 63 | } 64 | } 65 | } 66 | if (isLiteral(valueExpression) || valueExpression.type === "ArrayExpression" || valueExpression.type === "ObjectExpression") { 67 | return set(member(dict, index, scope), expr(valueExpression), scope); 68 | } 69 | return reuse(expr(valueExpression), scope, "value", (reusableValue) => { 70 | return conditional( 71 | optionalIsSome(reusableValue, valueType, scope), 72 | set(member(dict, index, scope), copy(unwrapOptional(reusableValue, valueType, scope), valueType), scope), 73 | unary("delete", member(dict, index, scope), scope), 74 | scope, 75 | ); 76 | }); 77 | }, "(Self, Self.Key, Self.Value?) -> Void"), 78 | "count": wrapped((scope, arg) => { 79 | return member(call(member("Object", "keys", scope), [arg(0, "self")], ["[String]"], scope), "length", scope); 80 | }, "(Self) -> Int"), 81 | "keys": wrapped((scope, arg) => { 82 | return call(member("Object", "keys", scope), [arg(0, "self")], ["[String]"], scope); 83 | }, "(Self) -> Self.Keys"), 84 | } as FunctionMap), 85 | conformances: withPossibleRepresentations(applyDefaultConformances({ 86 | // TODO: Implement Equatable 87 | Equatable: { 88 | functions: { 89 | "==": wrapped((innerScope, arg) => { 90 | return reuseArgs(arg, 0, innerScope, ["lhs", "rhs"], (lhs, rhs) => { 91 | const key = uniqueName(innerScope, "key"); 92 | const equal = uniqueName(innerScope, "equal"); 93 | return statements([ 94 | addVariable(innerScope, equal, "Bool", literal(true)), 95 | addVariable(innerScope, key, "T"), 96 | forInStatement( 97 | read(lookup(key, innerScope), innerScope) as Node as Identifier, 98 | read(lhs, innerScope), 99 | blockStatement([ 100 | ifStatement( 101 | read( 102 | logical( 103 | "||", 104 | unary("!", call(member(member("Object", "hasOwnProperty", innerScope), "call", innerScope), [rhs, lookup(key, innerScope)], ["Self", "String"], innerScope), innerScope), 105 | call( 106 | call( 107 | functionValue("!=", conformance(valueType, "Equatable", innerScope), "(Type) -> (Self, Self) -> Bool"), 108 | [valueType], 109 | [typeTypeValue], 110 | innerScope, 111 | ), 112 | [ 113 | member(lhs, lookup(key, innerScope), innerScope), 114 | member(rhs, lookup(key, innerScope), innerScope), 115 | ], 116 | [ 117 | valueType, 118 | valueType, 119 | ], 120 | innerScope, 121 | ), 122 | innerScope, 123 | ), 124 | innerScope, 125 | ), 126 | blockStatement(concat( 127 | ignore(set(lookup(equal, innerScope), literal(false), innerScope), innerScope), 128 | [breakStatement()], 129 | )), 130 | ), 131 | ]), 132 | ), 133 | ifStatement( 134 | read(lookup(equal, innerScope), innerScope), 135 | forInStatement( 136 | read(lookup(key, innerScope), innerScope) as Node as Identifier, 137 | read(rhs, innerScope), 138 | blockStatement([ 139 | ifStatement( 140 | read( 141 | unary("!", call(member(member("Object", "hasOwnProperty", innerScope), "call", innerScope), [lhs, lookup(key, innerScope)], ["Self", "String"], innerScope), innerScope), 142 | innerScope, 143 | ), 144 | blockStatement(concat( 145 | ignore(set(lookup(equal, innerScope), literal(false), innerScope), innerScope), 146 | [breakStatement()], 147 | )), 148 | ), 149 | ]), 150 | ), 151 | ), 152 | returnStatement(read(lookup(equal, innerScope), innerScope)), 153 | ]); 154 | }); 155 | }, "(Self, Self) -> Bool"), 156 | }, 157 | requirements: [], 158 | }, 159 | }, globalScope), PossibleRepresentation.Object), 160 | defaultValue() { 161 | return literal({}); 162 | }, 163 | copy(value, scope) { 164 | const expression = read(value, scope); 165 | if (expressionSkipsCopy(expression)) { 166 | return expr(expression); 167 | } 168 | if (reifiedValueType.copy) { 169 | throw new TypeError(`Copying dictionaries with non-simple values is not yet implemented!`); 170 | } 171 | return call( 172 | member("Object", "assign", scope), 173 | [literal({}), expr(expression)], 174 | ["Any", "Any"], 175 | scope, 176 | ); 177 | }, 178 | innerTypes: { 179 | Keys() { 180 | return inheritLayout(reifiedKeysType, { 181 | count: readLengthField, 182 | isEmpty: isEmptyFromLength, 183 | startIndex: startIndexOfZero, 184 | endIndex: readLengthField, 185 | first: wrapped((scope, arg) => { 186 | return reuseArgs(arg, 0, scope, ["keys"], (keys) => { 187 | const stringKey = member(keys, 0, scope); 188 | const convertedKey = typeof converter !== "undefined" ? call(converter, [stringKey], ["String"], scope) : stringKey; 189 | return conditional( 190 | member(keys, "length", scope), 191 | wrapInOptional(convertedKey, keyType, scope), 192 | emptyOptional(keyType, scope), 193 | scope, 194 | ); 195 | }); 196 | }, "(Self) -> Self.Wrapped?"), 197 | underestimatedCount: wrapped((scope, arg) => { 198 | return member(arg(0, "self"), "length", scope); 199 | }, "(Self) -> Int"), 200 | }); 201 | }, 202 | }, 203 | }; 204 | } 205 | const representationsValue = expressionLiteralValue(read(representationsForTypeValue(keyType, globalScope), globalScope)); 206 | switch (representationsValue) { 207 | case PossibleRepresentation.String: 208 | return objectDictionaryImplementation(); 209 | case PossibleRepresentation.Boolean: 210 | return objectDictionaryImplementation(expr(identifier("Boolean"))); 211 | case PossibleRepresentation.Number: 212 | return objectDictionaryImplementation(expr(identifier("Number"))); 213 | default: 214 | throw new Error(`No dictionary implementation for keys of type ${stringifyValue(keyType)}`); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /builtins/Hasher.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { primitive, PossibleRepresentation } from "../reified"; 3 | import { array, binary, literal, member, set } from "../values"; 4 | import { cachedBuilder, reuseArgs } from "./common"; 5 | 6 | export const Hasher = cachedBuilder((globalScope) => primitive(PossibleRepresentation.Array, array([literal(0)], globalScope), { 7 | "combine()": wrapped((scope, arg) => { 8 | return reuseArgs(arg, 0, scope, ["hasher"], (hasher) => { 9 | return set( 10 | member(hasher, 0, scope), 11 | binary("-", 12 | binary("+", 13 | binary("<<", 14 | member(hasher, 0, scope), 15 | literal(5), 16 | scope, 17 | ), 18 | arg(1, "value"), // TODO: Call hashValue 19 | scope, 20 | ), 21 | member(hasher, 0, scope), 22 | scope, 23 | ), 24 | scope, 25 | ); 26 | }); 27 | }, "(inout Hasher, Int) -> Void"), 28 | "finalize()": wrapped((scope, arg) => { 29 | return binary("|", member(arg(0, "hasher"), 0, scope), literal(0), scope); 30 | }, "(Hasher) -> Int"), 31 | })); 32 | -------------------------------------------------------------------------------- /builtins/IndexingIterator.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { withPossibleRepresentations, PossibleRepresentation, ReifiedType, TypeParameterHost } from "../reified"; 3 | import { Scope } from "../scope"; 4 | import { lookupForMap } from "../utils"; 5 | import { binary, call, conditional, conformance, expr, functionValue, literal, member, read, reuse, transform, tuple } from "../values"; 6 | import { applyDefaultConformances } from "./common"; 7 | import { emptyOptional, wrapInOptional } from "./Optional"; 8 | 9 | import { identifier, objectExpression, objectProperty, updateExpression } from "@babel/types"; 10 | 11 | export function IndexingIterator(globalScope: Scope, typeParameters: TypeParameterHost): ReifiedType { 12 | const [ elementsType ] = typeParameters("Elements"); 13 | return { 14 | functions: lookupForMap({ 15 | "init(_elements:)": wrapped((scope, arg) => { 16 | const collectionConformance = conformance(elementsType, "Collection", scope); 17 | const startIndexFunction = call(functionValue("startIndex", collectionConformance, "(Type) -> (Self) -> Self.Index"), [elementsType], ["Type"], scope); 18 | return transform(arg(0, "elements"), scope, (elementsValue) => { 19 | return expr(objectExpression([ 20 | objectProperty(identifier("elements"), elementsValue), 21 | objectProperty(identifier("position"), read(call(startIndexFunction, [expr(elementsValue)], [elementsType], scope), scope)), 22 | ])); 23 | }); 24 | }, "(Self.Elements) -> Self"), 25 | "init(_elements:_position:)": wrapped((scope, arg) => { 26 | return transform(arg(0, "elements"), scope, (elementsValue) => { 27 | return transform(arg(1, "position"), scope, (positionValue) => { 28 | return expr(objectExpression([ 29 | objectProperty(identifier("elements"), elementsValue), 30 | objectProperty(identifier("position"), positionValue), 31 | ])); 32 | }); 33 | }); 34 | }, "(Self.Elements, Self.Elements.Index) -> Self"), 35 | }), 36 | conformances: withPossibleRepresentations(applyDefaultConformances({ 37 | IteratorProtocol: { 38 | functions: { 39 | "next()": wrapped((scope, arg) => { 40 | return reuse(arg(0, "iterator"), scope, "iterator", (iterator) => { 41 | const collectionConformance = conformance(elementsType, "Collection", scope); 42 | const elementTypeFunction = call(functionValue("Element", collectionConformance, "(Type) -> () -> Type"), [elementsType], ["Type"], scope); 43 | const elementType = call(elementTypeFunction, [], [], scope); 44 | const endIndexFunction = call(functionValue("endIndex", collectionConformance, "(Type) -> (Self) -> Self.Index"), [elementsType], ["Type"], scope); 45 | return conditional( 46 | binary("===", 47 | member(iterator, "position", scope), 48 | call(endIndexFunction, [member(iterator, "elements", scope)], [elementsType], scope), 49 | scope, 50 | ), 51 | emptyOptional(elementType, scope), 52 | wrapInOptional(member(member(iterator, "elements", scope), expr(updateExpression("++", read(member(iterator, "position", scope), scope))), scope), elementType, scope), 53 | scope, 54 | ); 55 | }); 56 | }, "(inout Self) -> Self.Element?"), 57 | }, 58 | requirements: [], 59 | }, 60 | }, globalScope), PossibleRepresentation.Object), 61 | defaultValue() { 62 | return tuple([]); 63 | }, 64 | copy(value, scope) { 65 | return call(member(expr(identifier("Object")), "assign", scope), [literal({}), value], ["Self", "Self"], scope); 66 | }, 67 | innerTypes: {}, 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /builtins/Optional.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { expressionSkipsCopy, FunctionMap, PossibleRepresentation, ReifiedType, TypeParameterHost } from "../reified"; 3 | import { Scope } from "../scope"; 4 | import { lookupForMap } from "../utils"; 5 | import { binary, call, conditional, conformance, expr, functionValue, literal, logical, optional, read, representationsForTypeValue, reuse, typeFromValue, typeIsDirectlyComparable, typeTypeValue, ArgGetter, Value } from "../values"; 6 | import { applyDefaultConformances, binaryBuiltin, returnTodo, reuseArgs } from "./common"; 7 | 8 | export function unwrapOptional(value: Value, type: Value, scope: Scope): Value { 9 | return call(functionValue("Swift.(swift-to-js).unwrapOptional()", undefined, "(T.Type, T?) -> T"), [type, value], [type, type], scope); 10 | } 11 | 12 | export function optionalIsNone(value: Value, type: Value, scope: Scope): Value { 13 | return call(functionValue("Swift.(swift-to-js).optionalIsNone()", undefined, "(T.Type, T?) -> Bool"), [type, value], [type, type], scope); 14 | } 15 | 16 | export function optionalIsSome(value: Value, type: Value, scope: Scope): Value { 17 | return call(functionValue("Swift.(swift-to-js).optionalIsSome()", undefined, "(T.Type, T?) -> Bool"), [type, value], [type, type], scope); 18 | } 19 | 20 | function copyOptional(value: Value, type: Value, scope: Scope): Value { 21 | return call(functionValue("Swift.(swift-to-js).copyOptional()", undefined, "(T.Type, T?) -> T?"), [type, value], [type, type], scope); 22 | } 23 | 24 | export function emptyOptional(type: Value, scope: Scope) { 25 | return optional(type, undefined); 26 | } 27 | 28 | export function wrapInOptional(value: Value, type: Value, scope: Scope): Value { 29 | return optional(type, value); 30 | } 31 | 32 | export function Optional(globalScope: Scope, typeParameters: TypeParameterHost): ReifiedType { 33 | const [ wrappedType ] = typeParameters("Wrapped"); 34 | const reified = typeFromValue(wrappedType, globalScope); 35 | if (wrappedType.kind !== "type") { 36 | // TODO: Support runtime types 37 | throw new TypeError(`Runtime types are not supported as Self in Self?`); 38 | } 39 | // Assume values that can be represented as boolean, number or string can be value-wise compared 40 | const isDirectlyComparable = typeIsDirectlyComparable(wrappedType, globalScope); 41 | const compareEqual = wrapped(isDirectlyComparable ? binaryBuiltin("===", 0) : (scope: Scope, arg: ArgGetter) => { 42 | const equalMethod = call(functionValue("==", conformance(wrappedType, "Equatable", scope), "() -> () -> Bool"), [wrappedType], [typeTypeValue], scope); 43 | return reuseArgs(arg, 0, scope, ["lhs", "rhs"], (lhs, rhs) => { 44 | return conditional( 45 | optionalIsNone(lhs, wrappedType, scope), 46 | optionalIsNone(rhs, wrappedType, scope), 47 | logical("&&", 48 | optionalIsSome(rhs, wrappedType, scope), 49 | call(equalMethod, [ 50 | unwrapOptional(lhs, wrappedType, scope), 51 | unwrapOptional(rhs, wrappedType, scope), 52 | ], [wrappedType, wrappedType], scope), 53 | scope, 54 | ), 55 | scope, 56 | ); 57 | }); 58 | }, "(Self?, Self?) -> Bool"); 59 | const compareUnequal = wrapped(isDirectlyComparable ? binaryBuiltin("!==", 0) : (scope: Scope, arg: ArgGetter) => { 60 | const unequalMethod = call(functionValue("!=", conformance(wrappedType, "Equatable", scope), "() -> () -> Bool"), [wrappedType], [typeTypeValue], scope); 61 | return reuseArgs(arg, 0, scope, ["lhs", "rhs"], (lhs, rhs) => { 62 | return conditional( 63 | optionalIsNone(lhs, wrappedType, scope), 64 | optionalIsSome(rhs, wrappedType, scope), 65 | logical("||", 66 | optionalIsNone(rhs, wrappedType, scope), 67 | call(unequalMethod, [ 68 | unwrapOptional(lhs, wrappedType, scope), 69 | unwrapOptional(rhs, wrappedType, scope), 70 | ], [wrappedType, wrappedType], scope), 71 | scope, 72 | ), 73 | scope, 74 | ); 75 | }); 76 | }, "(Self?, Self?) -> Bool"); 77 | return { 78 | functions: lookupForMap({ 79 | "none": (scope) => emptyOptional(wrappedType, scope), 80 | "some": wrapped((scope, arg) => wrapInOptional(arg(0, "wrapped"), wrappedType, scope), "(Self) -> Self?"), 81 | "==": compareEqual, 82 | "!=": compareUnequal, 83 | "flatMap": returnTodo, 84 | } as FunctionMap), 85 | conformances: applyDefaultConformances({ 86 | Object: { 87 | functions: { 88 | ":rep": wrapped((innerScope) => { 89 | return conditional( 90 | binary("&", 91 | representationsForTypeValue(wrappedType, innerScope), 92 | literal(PossibleRepresentation.Null), 93 | innerScope, 94 | ), 95 | literal(PossibleRepresentation.Array), 96 | binary("|", 97 | representationsForTypeValue(wrappedType, innerScope), 98 | literal(PossibleRepresentation.Null), 99 | innerScope, 100 | ), 101 | innerScope, 102 | ); 103 | }, "() -> Int"), 104 | }, 105 | requirements: [], 106 | }, 107 | ExpressibleByNilLiteral: { 108 | functions: { 109 | "init(nilLiteral:)": wrapped((scope) => emptyOptional(wrappedType, scope), "() -> Self"), 110 | }, 111 | requirements: [], 112 | }, 113 | Equatable: { 114 | functions: { 115 | "==": compareEqual, 116 | "!=": compareUnequal, 117 | }, 118 | requirements: [], 119 | }, 120 | }, globalScope), 121 | defaultValue(scope) { 122 | return emptyOptional(wrappedType, scope); 123 | }, 124 | copy: /*reified.copy || wrappedIsOptional*/ true ? (value, scope) => { 125 | const expression = read(value, scope); 126 | if (expressionSkipsCopy(expression)) { 127 | return expr(expression); 128 | } 129 | const copier = reified.copy; 130 | if (copier) { 131 | // Nested optionals require special support since they're stored as [] for .none, [null] for .some(.none) and [v] for .some(.some(v)) 132 | return reuse(expr(expression), scope, "copyValue", (source) => { 133 | return conditional( 134 | optionalIsNone(source, wrappedType, scope), 135 | emptyOptional(wrappedType, scope), 136 | wrapInOptional(copier.call(reified, source, scope), wrappedType, scope), 137 | scope, 138 | ); 139 | }); 140 | } else { 141 | return copyOptional(value, wrappedType, scope); 142 | } 143 | } : undefined, 144 | innerTypes: {}, 145 | }; 146 | } 147 | -------------------------------------------------------------------------------- /builtins/_OptionalNilComparisonType.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { primitive, PossibleRepresentation } from "../reified"; 3 | import { literal, undefinedValue } from "../values"; 4 | import { cachedBuilder } from "./common"; 5 | 6 | export const OptionalNilComparisonType = cachedBuilder(() => { 7 | return primitive(PossibleRepresentation.Null, literal(null), { 8 | "init(nilLiteral:)": wrapped((scope, arg, type) => literal(null), "() -> _OptionalNilComparisonType"), 9 | }, Object.create(null), { 10 | Type: cachedBuilder(() => primitive(PossibleRepresentation.Undefined, undefinedValue)), 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /builtins/common.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { reifyType, ProtocolConformanceMap, ReifiedType } from "../reified"; 3 | import { mangleName, MappedNameValue, Scope } from "../scope"; 4 | import { Tuple } from "../types"; 5 | import { concat } from "../utils"; 6 | import { binary, call, expr, functionValue, literal, member, reuse, set, stringifyValue, typeFromValue, typeValue, update, updateOperatorForBinaryOperator, ArgGetter, BinaryOperator, ExpressionValue, Value } from "../values"; 7 | 8 | export function returnTodo(scope: Scope, arg: ArgGetter, name: string): Value { 9 | console.error(name); 10 | return call(expr(mangleName("todo_missing_builtin$" + name)), [], [], scope); 11 | } 12 | 13 | export function binaryBuiltin(operator: BinaryOperator, typeArgumentCount: number, valueChecker?: (value: Value, scope: Scope) => Value) { 14 | return (scope: Scope, arg: ArgGetter) => { 15 | const unchecked = binary(operator, 16 | arg(typeArgumentCount, "lhs"), 17 | arg(typeArgumentCount + 1, "rhs"), 18 | scope, 19 | ); 20 | return typeof valueChecker !== "undefined" ? valueChecker(unchecked, scope) : unchecked; 21 | }; 22 | } 23 | 24 | export function updateBuiltin(operator: keyof typeof updateOperatorForBinaryOperator, typeArgumentCount: number, valueChecker?: (value: Value, scope: Scope) => Value) { 25 | if (typeof valueChecker !== "undefined") { 26 | return (scope: Scope, arg: ArgGetter) => update(arg(typeArgumentCount, "target"), scope, (value) => valueChecker(binary(operator, value, arg(typeArgumentCount + 1, "value"), scope), scope)); 27 | } 28 | return (scope: Scope, arg: ArgGetter) => set(arg(typeArgumentCount, "target"), arg(typeArgumentCount + 1, "value"), scope, updateOperatorForBinaryOperator[operator]); 29 | } 30 | 31 | export const readLengthField = wrapped((scope: Scope, arg: ArgGetter) => { 32 | return member(arg(0, "self"), "length", scope); 33 | }, "(Any) -> Int"); 34 | 35 | export const isEmptyFromLength = wrapped((scope: Scope, arg: ArgGetter) => { 36 | return binary("!==", member(arg(0, "self"), "length", scope), literal(0), scope); 37 | }, "(Any) -> Bool"); 38 | 39 | export const startIndexOfZero = wrapped((scope: Scope, arg: ArgGetter) => { 40 | return literal(0); 41 | }, "(Any) -> Int"); 42 | 43 | export const voidType: Tuple = { kind: "tuple", types: [] }; 44 | 45 | export const forceUnwrapFailed: Value = functionValue("Swift.(swift-to-js).forceUnwrapFailed()", undefined, { kind: "function", arguments: voidType, return: voidType, throws: true, rethrows: false, attributes: [] }); 46 | 47 | export function cachedBuilder(fn: (scope: Scope) => ReifiedType): (scope: Scope) => ReifiedType { 48 | let value: ReifiedType | undefined; 49 | return (scope: Scope) => { 50 | if (typeof value === "undefined") { 51 | return value = fn(scope); 52 | } 53 | return value; 54 | }; 55 | } 56 | 57 | export function resolveMethod(type: Value, name: string, scope: Scope, additionalArgs: Value[] = [], additionalTypes: Value[] = []) { 58 | const functionBuilder = typeFromValue(type, scope).functions(name); 59 | if (typeof functionBuilder !== "function") { 60 | throw new TypeError(`Could not find ${name} in ${stringifyValue(type)}`); 61 | } 62 | return functionBuilder(scope, (i) => { 63 | if (i === 0) { 64 | return type; 65 | } 66 | if (i > additionalArgs.length) { 67 | throw new RangeError(`Asked for argument ${i}, but only ${additionalArgs.length} are available (shifted for hidden protocol self value)`); 68 | } 69 | return additionalArgs[i - 1]; 70 | }, name, concat([typeValue("Type")], additionalTypes)); 71 | } 72 | 73 | export function applyDefaultConformances(conformances: ProtocolConformanceMap, scope: Scope): ProtocolConformanceMap { 74 | const result: ProtocolConformanceMap = Object.create(null); 75 | for (const key of Object.keys(conformances)) { 76 | const reified = reifyType(key, scope); 77 | if (!Object.hasOwnProperty.call(reified.conformances, key)) { 78 | throw new TypeError(`${key} is not a protocol`); 79 | } 80 | const base = conformances[key]; 81 | result[key] = { 82 | functions: {...reified.conformances[key].functions, ...base.functions}, 83 | requirements: base.requirements, 84 | }; 85 | } 86 | return result; 87 | } 88 | 89 | export function reuseArgs(arg: ArgGetter, offset: number, scope: Scope, names: T, callback: (...values: { [P in keyof T]: (ExpressionValue | MappedNameValue) }) => Value): Value { 90 | if (names.length === 0) { 91 | return (callback as () => Value)(); 92 | } 93 | const [name, ...remaining] = names; 94 | if (names.length === 1) { 95 | return reuse(arg(offset, name), scope, name, callback as unknown as (value: ExpressionValue | MappedNameValue) => Value); 96 | } 97 | return reuse(arg(offset, name), scope, name, (value) => reuseArgs(arg, offset + 1, scope, remaining, (callback as unknown as (identifier: ExpressionValue | MappedNameValue, ...remaining: Array) => Value).bind(null, value))); 98 | } 99 | 100 | -------------------------------------------------------------------------------- /builtins/floats.ts: -------------------------------------------------------------------------------- 1 | import { wrapped } from "../functions"; 2 | import { withPossibleRepresentations, FunctionMap, PossibleRepresentation, ReifiedType } from "../reified"; 3 | import { addVariable, lookup, uniqueName, DeclarationFlags, Scope } from "../scope"; 4 | import { lookupForMap } from "../utils"; 5 | import { binary, call, callable, conditional, expr, expressionLiteralValue, literal, member, read, statements, unary } from "../values"; 6 | import { applyDefaultConformances, binaryBuiltin, updateBuiltin } from "./common"; 7 | 8 | import { identifier, returnStatement } from "@babel/types"; 9 | 10 | export function buildFloatingType(globalScope: Scope): ReifiedType { 11 | const reifiedType: ReifiedType = { 12 | functions: lookupForMap({ 13 | "init(_:)": wrapped((scope, arg) => arg(0, "value"), "(Self) -> Self"), 14 | "init(_builtinIntegerLiteral:)": wrapped((scope, arg) => arg(0, "value"), "(Self) -> Self"), 15 | "init(_builtinFloatLiteral:)": wrapped((scope, arg) => arg(0, "value"), "(Self) -> Self"), 16 | "+": wrapped((scope, arg, type) => binary("+", arg(0, "lhs"), arg(1, "rhs"), scope), "(Self, Self) -> Self"), 17 | "-": wrapped((scope, arg, type, argTypes) => { 18 | if (argTypes.length === 1) { 19 | return unary("-", arg(0, "value"), scope); 20 | } 21 | return binary("-", arg(0, "lhs"), arg(1, "rhs"), scope); 22 | }, "(Self, Self) -> Self"), 23 | "*": wrapped((scope, arg, type) => binary("*", arg(0, "lhs"), arg(1, "rhs"), scope), "(Self, Self) -> Self"), 24 | "/": wrapped((scope, arg, type) => binary("/", arg(0, "lhs"), arg(1, "rhs"), scope), "(Self, Self) -> Self"), 25 | "%": wrapped(binaryBuiltin("%", 0), "(Self, Self) -> Self"), 26 | "<": wrapped(binaryBuiltin("<", 0), "(Self, Self) -> Bool"), 27 | ">": wrapped(binaryBuiltin(">", 0), "(Self, Self) -> Bool"), 28 | "<=": wrapped(binaryBuiltin("<=", 0), "(Self, Self) -> Bool"), 29 | ">=": wrapped(binaryBuiltin(">=", 0), "(Self, Self) -> Bool"), 30 | "&": wrapped(binaryBuiltin("&", 0), "(Self, Self) -> Self"), 31 | "|": wrapped(binaryBuiltin("|", 0), "(Self, Self) -> Self"), 32 | "^": wrapped(binaryBuiltin("^", 0), "(Self, Self) -> Self"), 33 | "+=": wrapped(updateBuiltin("+", 0), "(inout Self, Self) -> Void"), 34 | "-=": wrapped(updateBuiltin("-", 0), "(inout Self, Self) -> Void"), 35 | "*=": wrapped(updateBuiltin("*", 0), "(inout Self, Self) -> Void"), 36 | "/=": wrapped(updateBuiltin("/", 0), "(inout Self, Self) -> Void"), 37 | "hashValue": wrapped((scope, arg) => { 38 | // TODO: Find a good hash strategy for floating point types 39 | return binary("|", arg(0, "float"), literal(0), scope); 40 | }, "(Self) -> Int"), 41 | } as FunctionMap), 42 | conformances: withPossibleRepresentations(applyDefaultConformances({ 43 | Equatable: { 44 | functions: { 45 | "==": wrapped(binaryBuiltin("===", 0), "(Self, Self) -> Bool"), 46 | "!=": wrapped(binaryBuiltin("!==", 0), "(Self, Self) -> Bool"), 47 | }, 48 | requirements: [], 49 | }, 50 | SignedNumeric: { 51 | functions: { 52 | "-": wrapped((scope, arg) => unary("-", arg(0, "value"), scope), "(Self) -> Self"), 53 | }, 54 | requirements: [], 55 | }, 56 | FloatingPoint: { 57 | functions: { 58 | "==": wrapped(binaryBuiltin("===", 0), "(Self, Self) -> Bool"), 59 | "!=": wrapped(binaryBuiltin("!==", 0), "(Self, Self) -> Bool"), 60 | "squareRoot()": (scope, arg, type) => { 61 | return callable(() => call(member("Math", "sqrt", scope), [arg(1, "value")], ["Double"], scope), "() -> Self"); 62 | }, 63 | }, 64 | requirements: [], 65 | }, 66 | LosslessStringConvertible: { 67 | functions: { 68 | "init(_:)": wrapped((scope, arg) => { 69 | const input = read(arg(0, "description"), scope); 70 | const value = expressionLiteralValue(input); 71 | if (typeof value === "string") { 72 | const convertedValue = Number(value); 73 | return literal(isNaN(convertedValue) ? null : convertedValue); 74 | } 75 | const result = uniqueName(scope, "number"); 76 | return statements([ 77 | addVariable(scope, result, "Int", call(expr(identifier("Number")), [ 78 | expr(input), 79 | ], ["String"], scope), DeclarationFlags.Const), 80 | returnStatement( 81 | read(conditional( 82 | binary("===", 83 | lookup(result, scope), 84 | lookup(result, scope), 85 | scope, 86 | ), 87 | literal(null), 88 | lookup(result, scope), 89 | scope, 90 | ), scope), 91 | ), 92 | ]); 93 | }, "(String) -> Self?"), 94 | }, 95 | requirements: [], 96 | }, 97 | }, globalScope), PossibleRepresentation.Number), 98 | defaultValue() { 99 | return literal(0); 100 | }, 101 | innerTypes: { 102 | }, 103 | }; 104 | return reifiedType; 105 | } 106 | -------------------------------------------------------------------------------- /builtins/functions.ts: -------------------------------------------------------------------------------- 1 | import { customInlined, noinline } from "../functions"; 2 | import { FunctionMap, PossibleRepresentation } from "../reified"; 3 | import { addVariable, lookup, uniqueName, DeclarationFlags, Scope } from "../scope"; 4 | import { concat } from "../utils"; 5 | import { array, binary, call, callable, conditional, conformance, expr, expressionLiteralValue, functionValue, hasRepresentation, ignore, literal, logical, member, read, representationsForTypeValue, set, statements, stringifyValue, typeValue, unary, ArgGetter, Value } from "../values"; 6 | 7 | import { arrayBoundsFailed } from "./Array"; 8 | import { reuseArgs } from "./common"; 9 | import { optionalIsSome, unwrapOptional } from "./Optional"; 10 | 11 | import { blockStatement, expressionStatement, identifier, ifStatement, newExpression, returnStatement, throwStatement } from "@babel/types"; 12 | 13 | function unavailableFunction(scope: Scope, arg: ArgGetter, name: string): Value { 14 | throw new Error(`${name} is not available`); 15 | } 16 | 17 | function throwHelper(type: "Error" | "TypeError" | "RangeError", text: string) { 18 | return noinline((scope, arg) => statements([throwStatement(newExpression(identifier(type), [literal(text).expression]))]), "() throws -> Void"); 19 | } 20 | 21 | const dummyType = typeValue({ kind: "name", name: "Dummy" }); 22 | 23 | function hasStaticRepresentation(scope: Scope, arg: ArgGetter): boolean { 24 | const representations = read(representationsForTypeValue(arg(0, "T"), scope), scope); 25 | const value = expressionLiteralValue(representations); 26 | return value !== undefined; 27 | } 28 | 29 | export const functions: FunctionMap = { 30 | "Swift.(swift-to-js).numericRangeFailed()": throwHelper("RangeError", "Not enough bits to represent the given value"), 31 | "Swift.(swift-to-js).forceUnwrapFailed()": throwHelper("TypeError", "Unexpectedly found nil while unwrapping an Optional value"), 32 | "Swift.(swift-to-js).arrayBoundsFailed()": throwHelper("RangeError", "Array index out of range"), 33 | "Swift.(swift-to-js).stringBoundsFailed()": throwHelper("RangeError", "String index out of range"), 34 | "Swift.(swift-to-js).notImplemented()": throwHelper("Error", "Not implemented!"), 35 | "Swift.(swift-to-js).arrayInsertAt()": noinline((scope, arg) => { 36 | return statements([ 37 | ifStatement( 38 | read(logical("||", 39 | binary(">", 40 | arg(2, "i"), 41 | member(arg(0, "array"), "length", scope), 42 | scope, 43 | ), 44 | binary("<", 45 | arg(2, "i"), 46 | literal(0), 47 | scope, 48 | ), 49 | scope, 50 | ), scope), 51 | blockStatement( 52 | ignore(arrayBoundsFailed(scope), scope), 53 | ), 54 | blockStatement( 55 | ignore(call( 56 | // TODO: Remove use of splice, since it's slow 57 | member(arg(0, "array"), "splice", scope), 58 | [ 59 | arg(2, "i"), 60 | literal(0), 61 | arg(1, "newElement"), 62 | ], 63 | [ 64 | "Int", 65 | "Int", 66 | "Any", 67 | ], 68 | scope, 69 | ), scope), 70 | ), 71 | ), 72 | ]); 73 | }, "(inout Self, Self.Element, Int) -> Void"), 74 | "Swift.(swift-to-js).arrayRemoveAt()": noinline((scope, arg) => { 75 | return statements([ 76 | ifStatement( 77 | read(logical("||", 78 | binary(">=", 79 | arg(1, "i"), 80 | member(arg(0, "array"), "length", scope), 81 | scope, 82 | ), 83 | binary("<", 84 | arg(1, "i"), 85 | literal(0), 86 | scope, 87 | ), 88 | scope, 89 | ), scope), 90 | blockStatement( 91 | ignore(arrayBoundsFailed(scope), scope), 92 | ), 93 | ), 94 | // TODO: Remove use of splice, since it's slow 95 | returnStatement( 96 | read(member( 97 | call( 98 | member(arg(0, "array"), "splice", scope), 99 | [ 100 | arg(1, "i"), 101 | literal(1), 102 | ], 103 | [ 104 | "Int", 105 | "Int", 106 | ], 107 | scope, 108 | ), 109 | literal(0), 110 | scope, 111 | ), scope), 112 | ), 113 | ]); 114 | }, "(inout Self, Int) -> Self.Element"), 115 | "Swift.(swift-to-js).unwrapOptional()": customInlined((scope, arg) => { 116 | const value = arg(1, "value"); 117 | if (value.kind === "optional") { 118 | if (value.value === undefined) { 119 | throw new TypeError(`Attempted to unwrap optional value that is provably empty at compile-time: ${stringifyValue(value)}`); 120 | } 121 | return value.value; 122 | } 123 | const type = arg(0, "T"); 124 | const hasNull = hasRepresentation(type, PossibleRepresentation.Null, scope); 125 | return conditional( 126 | hasNull, 127 | member(value, 0, scope), 128 | value, 129 | scope, 130 | ); 131 | }, "(T.Type, T?) -> T", hasStaticRepresentation), 132 | "Swift.(swift-to-js).optionalIsNone()": customInlined((scope, arg) => { 133 | const value = arg(1, "value"); 134 | if (value.kind === "optional") { 135 | return literal(value.value === undefined); 136 | } 137 | const type = arg(0, "T"); 138 | const hasNull = hasRepresentation(type, PossibleRepresentation.Null, scope); 139 | return conditional( 140 | hasNull, 141 | binary("===", member(value, "length", scope), literal(0), scope), 142 | binary("===", value, literal(null), scope), 143 | scope, 144 | ); 145 | }, "(T.Type, T?) -> Bool", hasStaticRepresentation), 146 | "Swift.(swift-to-js).optionalIsSome()": customInlined((scope, arg) => { 147 | const value = arg(1, "value"); 148 | if (value.kind === "optional") { 149 | return literal(value.value !== undefined); 150 | } 151 | const type = arg(0, "T"); 152 | const hasNull = hasRepresentation(type, PossibleRepresentation.Null, scope); 153 | return conditional( 154 | hasNull, 155 | binary("!==", member(value, "length", scope), literal(0), scope), 156 | binary("!==", value, literal(null), scope), 157 | scope, 158 | ); 159 | }, "(T.Type, T?) -> Bool", hasStaticRepresentation), 160 | "Swift.(swift-to-js).copyOptional()": customInlined((scope, arg) => { 161 | const value = arg(1, "value"); 162 | if (value.kind === "optional") { 163 | return value; 164 | } 165 | const type = arg(0, "T"); 166 | const hasNull = hasRepresentation(type, PossibleRepresentation.Null, scope); 167 | return conditional( 168 | hasNull, 169 | call(member(value, "slice", scope), [], [], scope), 170 | value, 171 | scope, 172 | ); 173 | }, "(T.Type, T?) -> Bool", hasStaticRepresentation), 174 | "Swift.(swift-to-js).emptyOptional()": customInlined((scope, arg) => { 175 | const type = arg(0, "T"); 176 | const hasNull = hasRepresentation(type, PossibleRepresentation.Null, scope); 177 | return conditional(hasNull, literal([]), literal(null), scope); 178 | }, "(T.Type) -> T?", hasStaticRepresentation), 179 | "Swift.(swift-to-js).someOptional()": customInlined((scope, arg) => { 180 | const type = arg(0, "T"); 181 | const value = arg(1, "value"); 182 | const hasNull = hasRepresentation(type, PossibleRepresentation.Null, scope); 183 | return conditional(hasNull, array([value], scope), value, scope); 184 | }, "(T.Type, T) -> T?", hasStaticRepresentation), 185 | "Sequence.reduce": (scope, arg, type) => callable((innerScope, innerArg) => { 186 | return call(expr(identifier("Sequence$reduce")), [arg(0), arg(1)], [dummyType, dummyType], scope); 187 | }, "(Result, (Result, Self.Element) -> Result) -> Result"), 188 | "??": (scope, arg, type) => { 189 | const typeArg = arg(0, "type"); 190 | if (typeArg.kind !== "type") { 191 | throw new TypeError(`Expected a type, got a ${typeArg.kind}`); 192 | } 193 | return reuseArgs(arg, 1, scope, ["lhs"], (lhs) => { 194 | return conditional( 195 | optionalIsSome(lhs, typeArg, scope), 196 | unwrapOptional(lhs, typeArg, scope), 197 | call(arg(2, "rhs"), [], [], scope), 198 | scope, 199 | ); 200 | }); 201 | }, 202 | "~=": (scope, arg) => { 203 | const T = arg(0, "T"); 204 | const result = call(functionValue("~=", conformance(T, "Equatable", scope), "(T.Type) -> (T, T) -> Bool"), [T], [dummyType], scope); 205 | return call(result, [arg(1, "pattern"), arg(2, "value")], [T, T], scope); 206 | }, 207 | "print(_:separator:terminator:)": (scope, arg, type) => call(member("console", "log", scope), [arg(0, "items")], [dummyType], scope), 208 | "precondition(_:_:file:line:)": (scope, arg, type) => statements([ 209 | ifStatement( 210 | read(unary("!", call(arg(0, "condition"), [], [], scope), scope), scope), 211 | blockStatement([ 212 | expressionStatement(identifier("debugger")), 213 | throwStatement(newExpression(identifier("Error"), [ 214 | read(call(arg(1, "message"), [], [], scope), scope), 215 | read(arg(2, "file"), scope), 216 | read(arg(3, "line"), scope), 217 | ])), 218 | ]), 219 | ), 220 | ]), 221 | "preconditionFailed(_:file:line:)": (scope, arg, type) => statements([ 222 | expressionStatement(identifier("debugger")), 223 | throwStatement(newExpression(identifier("Error"), [ 224 | read(call(arg(0, "message"), [], [], scope), scope), 225 | read(arg(1, "file"), scope), 226 | read(arg(2, "line"), scope), 227 | ])), 228 | ]), 229 | "fatalError(_:file:line:)": (scope, arg, type) => statements([ 230 | expressionStatement(identifier("debugger")), 231 | throwStatement(newExpression(identifier("Error"), [ 232 | read(call(arg(0, "message"), [], [], scope), scope), 233 | read(arg(1, "file"), scope), 234 | read(arg(2, "line"), scope), 235 | ])), 236 | ]), 237 | "isKnownUniquelyReferenced": () => literal(false), 238 | "withExtendedLifetime": (scope, arg) => call(arg(3, "body"), [ 239 | arg(2, "preserve"), 240 | ], ["Any"], scope), 241 | "withUnsafePointer": unavailableFunction, 242 | "withUnsafeMutablePointer": unavailableFunction, 243 | "withUnsafeBytes": unavailableFunction, 244 | "withUnsafeMutableBytes": unavailableFunction, 245 | "unsafeDowncast(to:)": unavailableFunction, 246 | "unsafeBitCast(to:)": unavailableFunction, 247 | "withVaList": unavailableFunction, 248 | "getVaList": unavailableFunction, 249 | "swap": (scope, arg) => { 250 | const type = arg(0, "type"); 251 | const a = arg(1, "a"); 252 | const b = arg(2, "b"); 253 | const temp = uniqueName(scope, "temp"); 254 | return statements(concat( 255 | [addVariable(scope, temp, type, a, DeclarationFlags.Const)], 256 | ignore(set(a, b, scope), scope), 257 | ignore(set(b, lookup(temp, scope), scope), scope), 258 | )); 259 | }, 260 | }; 261 | 262 | -------------------------------------------------------------------------------- /declaration.d.ts: -------------------------------------------------------------------------------- 1 | export function parse(text: string): Declaration; 2 | 3 | export interface Declaration { 4 | type?: string; 5 | member?: string; 6 | local?: string; 7 | substitutions?: ReadonlyArray; 8 | signature?: ReadonlyArray; 9 | } 10 | 11 | export interface Substitution { 12 | from: string; 13 | to: string; 14 | } 15 | 16 | export interface GenericConformance { 17 | name: string; 18 | protocol?: string; 19 | } 20 | -------------------------------------------------------------------------------- /declaration.pegjs: -------------------------------------------------------------------------------- 1 | Top 2 | = _ Name '.(file).' body:(FunctionAndLocalReference / TypeAndMemberReference / FunctionReference / Name) specialization:Specialization? PathReference? afterSpecialization:Specialization? _ { return { ...body, ...specialization || afterSpecialization || undefined }; } 3 | 4 | FunctionAndLocalReference 5 | = member:(TypeAndMemberReference / FunctionReference) '.' ('explicit closure discriminator=' [0-9]+ '.')? local:Name { return { type: member.type, member: member.member, local: local }; } 6 | 7 | TypeAndMemberReference 8 | = type:Name (__ 'extension.' / '.') member:(FunctionReference / Name) { return { type: type, member: member.member, local: member.local }; } 9 | 10 | FunctionReference 11 | = name:PermissiveName ('(' NamedArgument* ')')? { return { type: undefined, member: text(), local: undefined }; } 12 | 13 | NamedArgument 14 | = Name? ':' 15 | 16 | PathReference 17 | = '@' [^:]* [:0-9]* 18 | 19 | Specialization 20 | = __ '[with' __ '(substitution_map' __ signature:GenericSignature substitutions:Substitution* ')' _ ']' { return { substitutions: substitutions, signature: signature }; } 21 | 22 | GenericSignature 23 | = 'generic_signature=<' head:ConformanceClause tail:ConformanceClauseTail* '>' { return [head].concat(tail); } 24 | 25 | ConformanceClauseTail 26 | = ',' _ conformance:ConformanceClause { return conformance; } 27 | ConformanceClause 28 | = ConformanceClauseReal / ConformanceClauseFake 29 | 30 | ConformanceClauseReal 31 | = name:Name predicate:ConformancePredicate? !'.' { return { name, protocol: predicate || undefined }; } 32 | 33 | ConformanceClauseFake 34 | = [^>,]* { return undefined; } 35 | 36 | ConformancePredicate 37 | = (__ 'where' __ Name)? suffix:ConformanceSuffix? ConformanceClauseFake { return suffix || undefined; } 38 | ConformanceSuffix 39 | = _ ':' _ value:Name { return value; } 40 | 41 | Substitution 42 | = _ '(substitution' __ from:Name _ '->' _ to:SubstitutionValue _ ')' { return { from: from, to: to }; } 43 | 44 | SubstitutionValue 45 | = [^()@]* SubstitutionValueParameterize* [^()@]* { return text(); } 46 | 47 | SubstitutionValueParameterize 48 | = '(' SubstitutionValue ')' 49 | 50 | Name 51 | = ([^ .()@:>,]+ / '...') { return text(); } 52 | PermissiveName 53 | = ([^ .()@:]+ / '...') { return text(); } 54 | 55 | _ "whitespace" 56 | = [ \t\n\r]* { } 57 | 58 | __ "whitespace" 59 | = [ \t\n\r]* { } 60 | -------------------------------------------------------------------------------- /functions.ts: -------------------------------------------------------------------------------- 1 | import { Term } from "./ast"; 2 | import { parseFunctionType } from "./parse"; 3 | import { TypeMap } from "./reified"; 4 | import { addDeclaration, lookup, mangleName, newScope, rootScope, DeclarationFlags, Scope } from "./scope"; 5 | import { Function, Type } from "./types"; 6 | import { boxed, call, callable, expr, read, reuse, stringifyType, typeFromValue, typeValue, ArgGetter, Location, Value } from "./values"; 7 | 8 | import { blockStatement, functionDeclaration, identifier, returnStatement, Identifier, Statement } from "@babel/types"; 9 | 10 | export type FunctionBuilder = (scope: Scope, arg: ArgGetter, name: string, argumentTypes: Value[]) => Value; 11 | 12 | export function statementsInValue(value: Value, scope: Scope): Statement[] { 13 | return value.kind === "statements" ? value.statements : [returnStatement(read(value, scope))]; 14 | } 15 | 16 | export function functionize(scope: Scope, name: string, expression: (scope: Scope, arg: ArgGetter) => Value, functionType: Function | string, types?: TypeMap, location?: Location | Term): [Identifier[], Statement[]] { 17 | const type = parseFunctionType(functionType); 18 | const args: Identifier[] = []; 19 | return [args, statementsInValue(newScope(name, scope, (inner) => { 20 | let usedCount = 0; 21 | const identifiers: { [index: number]: Identifier } = Object.create(null); 22 | const newValue = expression(inner, (i, argumentName) => { 23 | if (usedCount === -1) { 24 | throw new Error(`Requested access to scope after it was generated!`); 25 | } 26 | if (usedCount <= i) { 27 | usedCount = i + 1; 28 | } 29 | if (i < 0) { 30 | throw new RangeError(`Asked for a negative argument index`); 31 | } 32 | if (i >= type.arguments.types.length) { 33 | throw new RangeError(`Asked for argument ${i + 1} of ${name}, but only ${type.arguments.types.length} arguments provided in ${stringifyType(type)}`); 34 | } 35 | let result: Identifier; 36 | if (Object.hasOwnProperty.call(identifiers, i)) { 37 | result = identifiers[i]; 38 | } else { 39 | result = identifiers[i] = identifier(typeof argumentName === "string" ? argumentName : "$" + String(i)); 40 | } 41 | const argType = type.arguments.types[i]; 42 | return argType.kind === "modified" && argType.modifier === "inout" ? boxed(expr(result), typeValue(argType.type)) : expr(result); 43 | }); 44 | for (let i = 0; i < usedCount; i++) { 45 | args[i] = Object.hasOwnProperty.call(identifiers, i) ? identifiers[i] : identifier("$" + String(i)); 46 | } 47 | usedCount = -1; 48 | return newValue; 49 | }, types), scope)]; 50 | } 51 | 52 | export function insertFunction(name: string, scope: Scope, builder: FunctionBuilder, functionType: Function | string, location?: Location | Term, shouldExport: boolean = false): Value { 53 | if (typeof builder === "undefined") { 54 | throw new Error(`Cannot find function named ${name}`); 55 | } 56 | if (Object.hasOwnProperty.call(scope.functionUsage, name)) { 57 | return lookup(name, scope); 58 | } 59 | scope.functionUsage[name] = true; 60 | const globalScope = rootScope(scope); 61 | const type = parseFunctionType(functionType); 62 | const [args, statements] = functionize(globalScope, name, (inner, arg) => builder(inner, arg, name, type.arguments.types.map((typeArgument) => typeValue(typeArgument))), type, undefined, location); 63 | return addDeclaration(globalScope, name, (id) => functionDeclaration(id, args, blockStatement(statements)), shouldExport ? DeclarationFlags.Export : DeclarationFlags.None); 64 | } 65 | 66 | export function customInlined(builder: FunctionBuilder, functionType: string | Function, shouldInline: (scope: Scope, arg: ArgGetter) => boolean): FunctionBuilder { 67 | const type = parseFunctionType(functionType); 68 | return (scope, arg, name, argumentTypes) => { 69 | if (type.kind !== "function") { 70 | throw new Error(`Expected function, got ${stringifyType(type)}`); 71 | } 72 | if (shouldInline(scope, arg)) { 73 | return builder(scope, arg, name, argumentTypes); 74 | } 75 | return call(insertFunction(name, scope, builder, type), type.arguments.types.map((_, i) => arg(i)), type.arguments.types.map((innerType) => typeValue(innerType)), scope); 76 | }; 77 | } 78 | 79 | function never() { 80 | return false; 81 | } 82 | 83 | export function noinline(builder: FunctionBuilder, functionType: string | Function) { 84 | return customInlined(builder, functionType, never); 85 | } 86 | 87 | export function wrapped(fn: (scope: Scope, arg: ArgGetter, typeArgument: Value, argTypes: Value[], outerArg: ArgGetter) => Value, functionType: string | Function): FunctionBuilder { 88 | return (scope: Scope, arg: ArgGetter, name: string): Value => { 89 | const typeArgument = arg(0, "Self"); 90 | const innerType = parseFunctionType(functionType); 91 | return callable((innerScope, innerArg, argTypes) => newScope("wrapped", innerScope, (innerInner) => fn(innerInner, innerArg, typeArgument, argTypes, arg), { 92 | Self(innerInner) { 93 | return typeFromValue(typeArgument, innerInner); 94 | }, 95 | }), innerType); 96 | }; 97 | } 98 | 99 | export function wrappedSelf(fn: (scope: Scope, arg: ArgGetter, typeArgument: Value, self: Value, argTypes: Value[]) => Value, functionType: string | Function): FunctionBuilder { 100 | return (scope: Scope, arg: ArgGetter, name: string): Value => { 101 | const typeArgument = arg(0, "Self"); 102 | const selfArgument = arg(1, "self"); 103 | const innerType = parseFunctionType(functionType); 104 | return callable((innerScope, innerArg, argTypes) => newScope("wrapped", innerScope, (innerInner) => { 105 | return reuse(selfArgument, innerInner, "self", (self) => { 106 | return fn(innerInner, innerArg, typeArgument, self, argTypes); 107 | }); 108 | }, { 109 | Self(innerInner) { 110 | return typeFromValue(typeArgument, innerInner); 111 | }, 112 | }), innerType); 113 | }; 114 | } 115 | 116 | export const abstractMethod: FunctionBuilder = (scope, arg, name) => { 117 | return expr(mangleName("abstract:" + name)); 118 | throw new TypeError(`Abstract method ${name} not overridden`); 119 | }; 120 | 121 | export function returnType(type: Type) { 122 | if (type.kind === "function") { 123 | return type.return; 124 | } 125 | throw new Error(`Expected a function type, got a ${type.kind} type`); 126 | } 127 | 128 | export function returnFunctionType(type: Type): Function { 129 | const result = returnType(type); 130 | if (result.kind !== "function") { 131 | throw new Error(`Expected to recieve a function that returns a function, instead it returns ${stringifyType(result)}`); 132 | } 133 | return result; 134 | } 135 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swift-to-js", 3 | "version": "0.0.1", 4 | "description": "JavaScript backend for Swift", 5 | "main": "dist/swift-to-js.js", 6 | "scripts": { 7 | "build:ts": "tsc", 8 | "build:ast": "pegjs --output dist/ast.js ast.pegjs", 9 | "build:types": "pegjs --output dist/types.js types.pegjs", 10 | "build:declaration": "pegjs --output dist/declaration.js declaration.pegjs", 11 | "build": "mkdir -p dist && npm-run-all --parallel build:ts build:ast build:types build:declaration", 12 | "test": "jest dist/*.test.js", 13 | "test:coverage": "jest --coverage dist/*.test.js", 14 | "lint": "tslint --project tsconfig.json --fix" 15 | }, 16 | "author": "Ryan Petrich ", 17 | "license": "Apache-2.0", 18 | "devDependencies": { 19 | "@types/babel__generator": "^7.0.1", 20 | "@types/jest": "^23.3.13", 21 | "@types/node": "^10.12.18", 22 | "jest": "^24.0.0", 23 | "npm-run-all": "^4.1.5", 24 | "pegjs": "^0.10.0", 25 | "tslint": "^5.12.1", 26 | "typescript": "^3.2.4" 27 | }, 28 | "dependencies": { 29 | "@babel/generator": "^7.3.0", 30 | "@babel/types": "^7.3.0" 31 | }, 32 | "jest": { 33 | "testEnvironment": "node", 34 | "coveragePathIgnorePatterns": [ 35 | "ast.js", 36 | "declaration.js", 37 | "types.js" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /parse.ts: -------------------------------------------------------------------------------- 1 | import { parse as ast } from "./ast"; 2 | import { parse as declaration } from "./declaration"; 3 | import { parse as type, Function } from "./types"; 4 | 5 | export const parseDeclaration = parse(declaration, "declaration"); 6 | export const parseType = parse(type, "type", "Bool", "Int", "String", "Hashable", "Hasher", "Equatable", "Comparable", "BinaryInteger", "Numeric", "SignedInteger", "SignedNumeric", "FixedWidthInteger", "Strideable", "CustomStringConvertible", "LosslessStringConvertible", "Collection", "BidirectionalCollection", "Type", "T", "T.Type", "() -> Void", "(Self, Self) -> Self", "(inout Self, Self) -> Void", "(Self, Self) -> Bool", "(Self, inout Hasher) -> Bool", "(Self) -> Int"); 7 | export const parseAST = parse(ast, "ast"); 8 | 9 | export function parseFunctionType(text: string | Function): Function { 10 | const result = parseType(text); 11 | if (result.kind !== "function") { 12 | throw new TypeError(`Expected a function, got a ${result.kind} from ${text}`); 13 | } 14 | return result; 15 | } 16 | 17 | function parse(parser: (text: string) => T, description: string, ...precomputedKeys: string[]): (text: string | T) => T { 18 | if (precomputedKeys.length === 0) { 19 | return (text: string | T): T => { 20 | if (typeof text !== "string") { 21 | return text; 22 | } 23 | try { 24 | return parser(text); 25 | } catch (e) { 26 | console.error(`${description}: ${text}`); 27 | throw e; 28 | } 29 | }; 30 | } 31 | const precomputed: { [name: string]: T } = Object.create(null); 32 | for (const key of precomputedKeys) { 33 | precomputed[key] = parser(key); 34 | } 35 | return (text: string | T): T => { 36 | if (typeof text !== "string") { 37 | return text; 38 | } 39 | if (Object.hasOwnProperty.call(precomputed, text)) { 40 | return precomputed[text as string]; 41 | } 42 | try { 43 | return parser(text); 44 | } catch (e) { 45 | console.error(`${description}: ${text}`); 46 | throw e; 47 | } 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /reified.ts: -------------------------------------------------------------------------------- 1 | import { wrapped, FunctionBuilder } from "./functions"; 2 | import { parseType } from "./parse"; 3 | import { lookup, Scope } from "./scope"; 4 | import { Type } from "./types"; 5 | import { concat, lookupForMap } from "./utils"; 6 | import { array, call, conditional, conformance, expr, extractContentOfBox, literal, member, read, reuse, set, stringifyType, stringifyValue, typeFromValue, typeRequiresBox, typeValue, undefinedValue, Value } from "./values"; 7 | 8 | import { isLiteral, Expression } from "@babel/types"; 9 | 10 | export enum PossibleRepresentation { 11 | None, 12 | Undefined = 1 << 0, 13 | Boolean = 1 << 1, 14 | Number = 1 << 2, 15 | String = 1 << 3, 16 | Function = 1 << 4, // Not used currently 17 | Object = 1 << 5, 18 | Symbol = 1 << 6, // Not used currently, possibly ever 19 | Null = 1 << 7, // Not referenced by typeof, but modeled in our system 20 | Array = 1 << 8, // Supported via Array.isArray 21 | All = PossibleRepresentation.Undefined | PossibleRepresentation.Boolean | PossibleRepresentation.Number | PossibleRepresentation.String | PossibleRepresentation.Function | PossibleRepresentation.Object | PossibleRepresentation.Symbol | PossibleRepresentation.Null | PossibleRepresentation.Array, 22 | } 23 | 24 | export interface ReifiedType { 25 | functions: (name: string) => FunctionBuilder | undefined; 26 | conformances: ProtocolConformanceMap; 27 | innerTypes: Readonly; 28 | cases?: ReadonlyArray; 29 | defaultValue?(scope: Scope, consume: (fieldName: string) => Expression | undefined): Value; 30 | copy?(value: Value, scope: Scope): Value; 31 | store?(target: Value, value: Value, scope: Scope): Value; 32 | } 33 | 34 | export type TypeParameterHost = >(...args: T) => { [K in keyof T]: T[K] extends string ? Value : T[K] } & Iterable; 35 | 36 | export interface TypeMap { 37 | [name: string]: (globalScope: Scope, typeParameters: TypeParameterHost) => ReifiedType; 38 | } 39 | 40 | export interface FunctionMap { 41 | [name: string]: FunctionBuilder; 42 | } 43 | 44 | export interface ProtocolConformance { 45 | functions: { [functionName: string]: FunctionBuilder }; 46 | requirements: string[]; 47 | } 48 | 49 | export interface ProtocolConformanceMap { 50 | [protocolName: string]: ProtocolConformance; 51 | } 52 | 53 | export interface EnumCase { 54 | name: string; 55 | fieldTypes: ReadonlyArray; 56 | } 57 | 58 | const emptyConformances: ProtocolConformanceMap = Object.create(null); 59 | const noFunctions: Readonly = Object.create(null); 60 | const noInnerTypes: Readonly = Object.create(null); 61 | 62 | export function withPossibleRepresentations(conformances: ProtocolConformanceMap, possibleRepresentations: PossibleRepresentation): ProtocolConformanceMap { 63 | return { 64 | ...conformances, 65 | Object: { 66 | functions: { 67 | ":rep": wrapped(() => literal(possibleRepresentations), "() -> Int"), 68 | }, 69 | requirements: [], 70 | }, 71 | }; 72 | } 73 | 74 | export function primitive(possibleRepresentations: PossibleRepresentation, defaultValue: Value, functions: FunctionMap = noFunctions, conformances: ProtocolConformanceMap = emptyConformances, innerTypes: Readonly = noInnerTypes): ReifiedType { 75 | return { 76 | functions: functions !== noFunctions ? lookupForMap(functions) : alwaysUndefined, 77 | conformances: withPossibleRepresentations(conformances, possibleRepresentations), 78 | defaultValue() { 79 | return defaultValue; 80 | }, 81 | innerTypes, 82 | }; 83 | } 84 | 85 | export function protocol(protocolName: string, conformances: ProtocolConformanceMap = emptyConformances): ReifiedType { 86 | return { 87 | functions(functionName) { 88 | return (scope, arg, name, argTypes) => { 89 | const typeArg = conformance(arg(0, "Self"), protocolName, scope); 90 | const fn = typeFromValue(typeArg, scope).functions(functionName); 91 | if (typeof fn !== "function") { 92 | throw new TypeError(`Could not find ${functionName} on ${stringifyValue(typeArg)}`); 93 | } 94 | return fn(scope, arg, name, argTypes); 95 | }; 96 | }, 97 | conformances: withPossibleRepresentations(conformances, PossibleRepresentation.Object), 98 | innerTypes: noInnerTypes, 99 | }; 100 | } 101 | 102 | export function inheritLayout(type: ReifiedType, functions: FunctionMap = noFunctions, conformances: ProtocolConformanceMap = emptyConformances, innerTypes: Readonly = noInnerTypes) { 103 | return { 104 | functions: functions !== noFunctions ? lookupForMap(functions) : alwaysUndefined, 105 | conformances: { 106 | ...conformances, 107 | // TODO: Call through with proper types 108 | Object: type.conformances.Object, 109 | }, 110 | defaultValue: type.defaultValue, 111 | copy: type.copy, 112 | store: type.store, 113 | innerTypes, 114 | }; 115 | } 116 | 117 | function cannotDefaultInstantiateClass(): never { 118 | throw new TypeError(`Cannot default instantiate a class`); 119 | } 120 | 121 | export function newClass(functions: FunctionMap = noFunctions, conformances: ProtocolConformanceMap = emptyConformances, innerTypes: Readonly = noInnerTypes, defaultValue: (scope: Scope, consume: (fieldName: string) => Expression | undefined) => Value = cannotDefaultInstantiateClass): ReifiedType { 122 | return { 123 | functions: lookupForMap(functions), 124 | conformances: withPossibleRepresentations(conformances, PossibleRepresentation.Object), 125 | defaultValue, 126 | innerTypes, 127 | }; 128 | } 129 | 130 | export function expressionSkipsCopy(expression: Expression): boolean { 131 | switch (expression.type) { 132 | case "ObjectExpression": 133 | case "ArrayExpression": 134 | case "CallExpression": 135 | return true; 136 | case "ConditionalExpression": 137 | return expressionSkipsCopy(expression.consequent) && expressionSkipsCopy(expression.alternate); 138 | default: 139 | return isLiteral(expression); 140 | } 141 | } 142 | 143 | export function store(dest: Value, source: Value, type: Value, scope: Scope): Value { 144 | switch (dest.kind) { 145 | case "boxed": 146 | return conditional( 147 | typeRequiresBox(dest.type, scope), 148 | store(extractContentOfBox(dest, scope), source, type, scope), 149 | store(dest.contents, source, type, scope), 150 | scope, 151 | ); 152 | case "direct": 153 | const reified = typeFromValue(type, scope); 154 | if (reified.store) { 155 | return reified.store(dest, source, scope); 156 | } else { 157 | return set(dest, source, scope, "="); 158 | } 159 | case "subscript": 160 | return set(dest, source, scope, "="); 161 | default: 162 | throw new TypeError(`Unable to store to a ${dest.kind} value`); 163 | } 164 | } 165 | 166 | export function defaultInstantiateType(type: Value, scope: Scope, consume: (fieldName: string) => Expression | undefined): Value { 167 | const reified = typeFromValue(type, scope); 168 | if (!reified.defaultValue) { 169 | throw new Error(`Cannot default instantiate ${stringifyValue(type)}`); 170 | } 171 | return reified.defaultValue(scope, consume); 172 | } 173 | 174 | function typeArgumentsForArray(args: ReadonlyArray): TypeParameterHost { 175 | return ((...requested: string[]) => { 176 | if (requested.length > args.length) { 177 | throw new TypeError(`Requested ${requested.length} type arguments from array that only contains ${args.length} elements`); 178 | } 179 | return args.slice(0, args.length); 180 | }) as TypeParameterHost; 181 | } 182 | 183 | export function reifyType(typeOrTypeName: Type | string, scope: Scope, typeArguments: ReadonlyArray = [], types?: ReadonlyArray>): ReifiedType { 184 | const type = typeof typeOrTypeName === "string" ? parseType(typeOrTypeName) : typeOrTypeName; 185 | switch (type.kind) { 186 | case "name": 187 | if (typeof types !== "undefined") { 188 | // Search the provided types only 189 | for (const map of types) { 190 | if (Object.hasOwnProperty.call(map, type.name)) { 191 | return map[type.name](scope, typeArgumentsForArray(typeArguments)); 192 | } 193 | } 194 | } else { 195 | // Search up the scope chain 196 | let currentScope: Scope | undefined = scope; 197 | while (typeof currentScope !== "undefined") { 198 | const map = currentScope.types; 199 | if (Object.hasOwnProperty.call(map, type.name)) { 200 | return map[type.name](scope, typeArgumentsForArray(typeArguments)); 201 | } 202 | currentScope = currentScope.parent; 203 | } 204 | } 205 | return typeFromValue(lookup(type.name, scope), scope); 206 | // throw new TypeError(`Cannot resolve type named ${type.name}`); 207 | case "array": 208 | return reifyType({ kind: "name", name: "Array" }, scope, [typeValue(type.type)]); 209 | case "modified": 210 | return reifyType(type.type, scope); 211 | case "dictionary": 212 | return reifyType({ kind: "name", name: "Dictionary" }, scope, [typeValue(type.keyType), typeValue(type.valueType)]); 213 | case "tuple": 214 | const reifiedTypes = type.types.map((inner) => reifyType(inner, scope)); 215 | switch (type.types.length) { 216 | case 0: 217 | return primitive(PossibleRepresentation.Undefined, undefinedValue); 218 | case 1: 219 | return reifiedTypes[0]; 220 | default: 221 | return { 222 | functions: lookupForMap(noFunctions), 223 | conformances: withPossibleRepresentations({}, PossibleRepresentation.Array), 224 | defaultValue(innerScope) { 225 | return array(reifiedTypes.map((inner, i) => { 226 | const defaultValue = inner.defaultValue; 227 | if (typeof defaultValue === "undefined") { 228 | throw new TypeError(`Tuple field ${i} of type ${stringifyType(type.types[i])} is not default instantiable`); 229 | } 230 | return defaultValue(innerScope, alwaysUndefined); 231 | }), innerScope); 232 | }, 233 | copy(value, innerScope) { 234 | if (value.kind === "tuple") { 235 | return value; 236 | } 237 | const expression = read(value, innerScope); 238 | if (expressionSkipsCopy(expression)) { 239 | return expr(expression); 240 | } 241 | if (!reifiedTypes.some((elementType) => typeof elementType.copy !== "undefined")) { 242 | return call(member(expr(expression), "slice", scope), [], [], scope); 243 | } 244 | return reuse(expr(expression), innerScope, "copySource", (source) => { 245 | return array(reifiedTypes.map((elementType, index) => { 246 | const fieldValue = member(source, index, innerScope); 247 | return elementType.copy ? elementType.copy(fieldValue, innerScope) : fieldValue; 248 | }), scope); 249 | }); 250 | }, 251 | innerTypes: noInnerTypes, 252 | }; 253 | } 254 | case "generic": 255 | return reifyType(type.base, scope, concat(typeArguments, type.arguments.map((innerType) => typeValue(innerType)))); 256 | case "metatype": 257 | const reified = reifyType(type.base, scope, typeArguments); 258 | return reified; 259 | // if (!Object.hasOwnProperty.call(reified.innerTypes, type.as)) { 260 | // throw new TypeError(`${stringifyType(type.base)} does not have a ${type.as} inner type`); 261 | // } 262 | // return reified.innerTypes[type.as](scope, typeArgumentsForArray([])); 263 | case "function": 264 | return primitive(PossibleRepresentation.Function, undefinedValue); 265 | case "namespaced": 266 | return reifyType(type.type, scope, [], [reifyType(type.namespace, scope, typeArguments).innerTypes]); 267 | case "optional": 268 | return reifyType({ kind: "name", name: "Optional" }, scope, [typeValue(type.type)]); 269 | default: 270 | throw new TypeError(`Received an unexpected type ${(type as Type).kind}`); 271 | } 272 | } 273 | 274 | function alwaysUndefined(): undefined { 275 | return undefined; 276 | } 277 | -------------------------------------------------------------------------------- /scope.ts: -------------------------------------------------------------------------------- 1 | import { exportNamedDeclaration, identifier, returnStatement, variableDeclaration, variableDeclarator, Declaration, Identifier, Statement } from "@babel/types"; 2 | import { parseType } from "./parse"; 3 | import { FunctionMap, TypeMap } from "./reified"; 4 | import { concat } from "./utils"; 5 | import { array, boxed, conditional, expr, expressionLiteralValue, read, statements, typeRequiresBox, typeValue, undefinedValue, BoxedValue, ConformanceValue, ExpressionValue, SubscriptValue, TypeValue, Value, VariableValue } from "./values"; 6 | 7 | export enum DeclarationFlags { 8 | None = 0, 9 | Export = 1 << 0, 10 | Const = 1 << 1, 11 | Boxed = 1 << 2, 12 | } 13 | 14 | export type MappedNameValue = BoxedValue | ExpressionValue | SubscriptValue | VariableValue | TypeValue | ConformanceValue; 15 | 16 | export interface Scope { 17 | name: string; 18 | declarations: { [name: string]: { flags: DeclarationFlags; declaration?: Declaration; } }; 19 | types: TypeMap; 20 | functions: FunctionMap; 21 | functionUsage: { [name: string]: true }; 22 | mapping: { [name: string]: MappedNameValue }; 23 | parent: Scope | undefined; 24 | } 25 | 26 | export function addDeclaration(scope: Scope, name: string, callback: (id: Identifier) => Declaration, flags: DeclarationFlags = DeclarationFlags.None) { 27 | if (Object.hasOwnProperty.call(scope.declarations, name)) { 28 | throw new Error(`Declaration of ${name} already exists`); 29 | } 30 | const id = mangleName(name); 31 | const result = expr(id); 32 | scope.mapping[name] = result; 33 | scope.declarations[name] = { flags, declaration: callback(id) }; 34 | return result; 35 | } 36 | 37 | export function addVariable(scope: Scope, name: string, typeOrTypeString: string | Value, init?: Value, flags: DeclarationFlags = DeclarationFlags.None) { 38 | if (Object.hasOwnProperty.call(scope.declarations, name)) { 39 | throw new Error(`Declaration of ${name} already exists`); 40 | } 41 | const type = typeof typeOrTypeString === "string" ? typeValue(parseType(typeOrTypeString)) : typeOrTypeString; 42 | const isBoxed = flags & DeclarationFlags.Boxed; 43 | const mangled = mangleName(name); 44 | scope.mapping[name] = isBoxed ? boxed(expr(mangled), type) : expr(mangled); 45 | scope.declarations[name] = { flags, declaration: undefined }; 46 | if (isBoxed) { 47 | // Create a box for the initializer, of the type requires it 48 | const requiresBox = typeRequiresBox(type, scope); 49 | const definitelyBoxed = array(typeof init !== "undefined" ? [init] : [], scope); 50 | const possiblyBoxed = conditional(requiresBox, definitelyBoxed, init || undefinedValue, scope); 51 | if (requiresBox.kind === "expression") { 52 | const storedAsBox = expressionLiteralValue(requiresBox.expression); 53 | if (typeof storedAsBox === "undefined") { 54 | init = possiblyBoxed; 55 | } else if (storedAsBox) { 56 | init = definitelyBoxed; 57 | flags |= DeclarationFlags.Const; 58 | } 59 | } else { 60 | init = possiblyBoxed; 61 | } 62 | } 63 | const initExpression = typeof init !== "undefined" ? read(init, scope) : undefined; 64 | return variableDeclaration( 65 | flags & DeclarationFlags.Const ? "const" : "let", 66 | [variableDeclarator(mangled, typeof initExpression !== "undefined" && (initExpression.type !== "Identifier" || initExpression.name !== "undefined") ? initExpression : undefined)], 67 | ); 68 | } 69 | 70 | export function rootScope(scope: Scope) { 71 | let result = scope; 72 | while (typeof result.parent !== "undefined") { 73 | result = result.parent; 74 | } 75 | return result; 76 | } 77 | 78 | export function newScope(name: string, parent: Scope, callback: (scope: Scope) => Value, types: TypeMap = parent.types): Value { 79 | const scope: Scope = { 80 | name, 81 | declarations: Object.create(null), 82 | types, 83 | functions: parent.functions, 84 | functionUsage: parent.functionUsage, 85 | mapping: Object.create(null), 86 | parent, 87 | }; 88 | return emitScope(scope, callback(scope)); 89 | } 90 | 91 | export function hasNameInScope(scope: Scope, name: string): boolean { 92 | let current: Scope | undefined = scope; 93 | while (typeof current !== "undefined") { 94 | if (Object.hasOwnProperty.call(current.declarations, name)) { 95 | return true; 96 | } 97 | current = current.parent; 98 | } 99 | return false; 100 | } 101 | 102 | export function fullPathOfScope(scope: Scope) { 103 | const result: string[] = []; 104 | let current: Scope | undefined = scope; 105 | do { 106 | result.unshift(current.name); 107 | current = current.parent; 108 | } while (current); 109 | if (result.length > 1) { 110 | result.shift(); 111 | } 112 | return result.join("."); 113 | } 114 | 115 | const mangledSymbols: { [symbol: string]: string } = { 116 | "Swift.(file).": "$$", 117 | "Swift.(swift-to-js).": "$$", 118 | "_:": "", 119 | "()": "", 120 | ":": "$", 121 | ".": "$", 122 | "_": "_", 123 | "(": "$", 124 | ")": "", 125 | "[": "$open$", 126 | "]": "$close$", 127 | "$": "$dollar$", 128 | " ": "$space$", 129 | "+": "$plus$", 130 | "-": "$minus$", 131 | "*": "$multiply$", 132 | "/": "$divide$", 133 | "%": "$mod$", 134 | "<": "$less$", 135 | ">": "$greater$", 136 | "=": "$equal$", 137 | "&": "$and$", 138 | "|": "$or$", 139 | "^": "$xor$", 140 | "!": "$not$", 141 | "?": "$question$", 142 | ",": "$comma$", 143 | "~": "$tilde$", 144 | "==": "$equals$", 145 | "!=": "$notequals$", 146 | "~=": "$match$", 147 | "<=": "$lessequal$", 148 | ">=": "$greaterequal$", 149 | "+=": "$added$", 150 | "-=": "$subtracted$", 151 | "*=": "$multiplied$", 152 | "/=": "$divided$", 153 | "<<": "$leftshift$", 154 | ">>": "$rightshift$", 155 | }; 156 | 157 | function mangleSymbol(symbol: string): string { 158 | if (Object.hasOwnProperty.call(mangledSymbols, symbol)) { 159 | return mangledSymbols[symbol]; 160 | } 161 | if (symbol.length > 2 && symbol[0] === "[") { 162 | return "$" + mangleName(symbol.substring(1, symbol.length - 1)).name + "$"; 163 | } 164 | return "$" + String(symbol.charCodeAt(0)) + "$"; 165 | } 166 | 167 | export function mangleName(name: string) { 168 | return identifier(name.replace(/\b_:/g, mangleSymbol).replace(/(\[.*\])|(Swift\.\((file|swift-to-js)\).|[=!~<>+\-*/]=|<<|>>|\(\)|\W)/g, mangleSymbol)); 169 | } 170 | 171 | export function mappedValueForName(name: string, scope: Scope): MappedNameValue | undefined { 172 | let targetScope: Scope | undefined = scope; 173 | do { 174 | if (Object.hasOwnProperty.call(targetScope.mapping, name)) { 175 | return targetScope.mapping[name]; 176 | } 177 | targetScope = targetScope.parent; 178 | } while (targetScope); 179 | return undefined; 180 | } 181 | 182 | export function lookup(name: string, scope: Scope): MappedNameValue { 183 | const result = mappedValueForName(name, scope); 184 | if (typeof result !== "undefined") { 185 | return result; 186 | } 187 | return expr(mangleName(name)); 188 | } 189 | 190 | export function uniqueName(scope: Scope, prefix: string = "$temp") { 191 | let i = 0; 192 | let name = prefix; 193 | while (hasNameInScope(scope, name)) { 194 | name = prefix + String(i++); 195 | } 196 | return name; 197 | } 198 | 199 | export function emitScope(scope: Scope, value: Value): Value { 200 | const keys = Object.keys(scope.declarations); 201 | if (keys.length === 0) { 202 | return value; 203 | } 204 | // Because reading can add declarations 205 | const tail = value.kind !== "statements" ? [returnStatement(read(value, scope))] : []; 206 | const result: Statement[] = []; 207 | for (const key of keys) { 208 | const declaration = scope.declarations[key]; 209 | if (typeof declaration.declaration !== "undefined") { 210 | result.push(declaration.flags & DeclarationFlags.Export ? exportNamedDeclaration(declaration.declaration, []) : declaration.declaration); 211 | } 212 | } 213 | if (value.kind === "statements") { 214 | return statements(concat(result, value.statements)); 215 | } 216 | return statements(concat(result, tail)); 217 | } 218 | -------------------------------------------------------------------------------- /swift-to-js.test.ts: -------------------------------------------------------------------------------- 1 | import { compile } from "./swift-to-js"; 2 | 3 | import { readdirSync, readFile as readFile_, statSync, unlink as unlink_, writeFile as writeFile_ } from "fs"; 4 | import { basename } from "path"; 5 | import { promisify } from "util"; 6 | 7 | const writeOutput = false; 8 | 9 | const readFile = promisify(readFile_); 10 | const writeFile = promisify(writeFile_); 11 | const unlink = promisify(unlink_); 12 | 13 | const swiftFilePattern = /\.swift$/; 14 | 15 | for (const category of readdirSync("./tests/")) { 16 | if (statSync(`./tests/${category}`).isDirectory()) { 17 | describe(category, () => { 18 | for (const file of readdirSync(`./tests/${category}`)) { 19 | if (swiftFilePattern.test(file)) { 20 | test(file.replace(swiftFilePattern, ""), async () => { 21 | const swiftPath = `./tests/${category}/${file}`; 22 | const jsPath = `./tests/${category}/${file.replace(swiftFilePattern, ".mjs")}`; 23 | function addSourceMapping(code: string) { 24 | return `${code}\n//# sourceMappingURL=${basename(jsPath)}.map`; 25 | } 26 | const result = compile(swiftPath); 27 | try { 28 | if (writeOutput) { 29 | try { 30 | const { code, ast, map } = await result; 31 | await Promise.all([ 32 | writeFile(jsPath, addSourceMapping(code)), 33 | writeFile(`./tests/${category}/${file.replace(swiftFilePattern, ".js")}`, code), 34 | writeFile(swiftPath + ".ast", ast), 35 | writeFile(jsPath + ".map", JSON.stringify(map)), 36 | ]); 37 | expect((map as { mappings: string }).mappings).not.toEqual(""); 38 | } catch (e) { 39 | if (e && e.ast) { 40 | await writeFile(swiftPath + ".ast", e.ast); 41 | } 42 | try { 43 | statSync(jsPath); 44 | await unlink(jsPath); 45 | } catch (e) { 46 | // Ignore 47 | } 48 | throw e; 49 | } 50 | } else { 51 | const expected = readFile(jsPath); 52 | const { code, map } = await result; 53 | expect((map as { mappings: string }).mappings).not.toEqual(""); 54 | expect(addSourceMapping(code)).toEqual((await expected).toString("utf-8")); 55 | } 56 | } finally { 57 | await result; 58 | } 59 | }); 60 | } 61 | } 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/arrays/empty.js: -------------------------------------------------------------------------------- 1 | export function emptyIntArray() { 2 | return []; 3 | } -------------------------------------------------------------------------------- /tests/arrays/empty.swift: -------------------------------------------------------------------------------- 1 | public func emptyIntArray() -> [Int] { 2 | return [] 3 | } 4 | -------------------------------------------------------------------------------- /tests/arrays/for-in.js: -------------------------------------------------------------------------------- 1 | export function sum$array$(array) { 2 | const result = [0]; 3 | const iterator = { 4 | elements: array, 5 | position: 0 6 | }; 7 | 8 | for (let element; (element = iterator.position === iterator.elements.length ? null : iterator.elements[iterator.position++]) !== null;) { 9 | result[0] += element; 10 | } 11 | 12 | return result[0]; 13 | } -------------------------------------------------------------------------------- /tests/arrays/for-in.swift: -------------------------------------------------------------------------------- 1 | public func sum(array: [Int]) -> Int { 2 | var result = 0 3 | for element in array { 4 | result += element 5 | } 6 | return result 7 | } 8 | -------------------------------------------------------------------------------- /tests/arrays/int-count.js: -------------------------------------------------------------------------------- 1 | export function arrayCount(array) { 2 | return array.length; 3 | } -------------------------------------------------------------------------------- /tests/arrays/int-count.swift: -------------------------------------------------------------------------------- 1 | public func arrayCount(_ array: [Int]) -> Int { 2 | return array.count 3 | } 4 | -------------------------------------------------------------------------------- /tests/arrays/int-get.js: -------------------------------------------------------------------------------- 1 | function $$arrayBoundsFailed() { 2 | throw new RangeError("Array index out of range"); 3 | } 4 | 5 | export function arrayGet$array$index$(array, index) { 6 | return array[array.length > index && index >= 0 ? index : $$arrayBoundsFailed()]; 7 | } -------------------------------------------------------------------------------- /tests/arrays/int-get.swift: -------------------------------------------------------------------------------- 1 | public func arrayGet(array: [Int], index: Int) -> Int { 2 | return array[index] 3 | } 4 | -------------------------------------------------------------------------------- /tests/arrays/int-set.js: -------------------------------------------------------------------------------- 1 | function $$arrayBoundsFailed() { 2 | throw new RangeError("Array index out of range"); 3 | } 4 | 5 | export function arraySet$array$index$value$(array, index, value) { 6 | array[array.length >= index && index >= 0 ? index : $$arrayBoundsFailed()] = value; 7 | } -------------------------------------------------------------------------------- /tests/arrays/int-set.swift: -------------------------------------------------------------------------------- 1 | public func arraySet(array: inout [Int], index: Int, value: Int) -> () { 2 | array[index] = value 3 | } 4 | -------------------------------------------------------------------------------- /tests/arrays/one-int.js: -------------------------------------------------------------------------------- 1 | export function oneInt$value$(value) { 2 | return [value]; 3 | } -------------------------------------------------------------------------------- /tests/arrays/one-int.swift: -------------------------------------------------------------------------------- 1 | public func oneInt(value: Int) -> [Int] { 2 | return [value] 3 | } 4 | -------------------------------------------------------------------------------- /tests/arrays/two-ints.js: -------------------------------------------------------------------------------- 1 | export function twoInts$first$second$(first, second) { 2 | return [first, second]; 3 | } -------------------------------------------------------------------------------- /tests/arrays/two-ints.swift: -------------------------------------------------------------------------------- 1 | public func twoInts(first: Int, second: Int) -> [Int] { 2 | return [first, second] 3 | } 4 | -------------------------------------------------------------------------------- /tests/arrays/update.js: -------------------------------------------------------------------------------- 1 | function $$arrayBoundsFailed() { 2 | throw new RangeError("Array index out of range"); 3 | } 4 | 5 | export function arrayIncrement$array$index$amount$(array, index, amount) { 6 | array[array.length >= index && index >= 0 ? index : $$arrayBoundsFailed()] = array[array.length > index && index >= 0 ? index : $$arrayBoundsFailed()] + amount; 7 | } 8 | export function arrayDecrement$array$index$amount$(array, index, amount) { 9 | array[array.length >= index && index >= 0 ? index : $$arrayBoundsFailed()] = array[array.length > index && index >= 0 ? index : $$arrayBoundsFailed()] - amount; 10 | } -------------------------------------------------------------------------------- /tests/arrays/update.swift: -------------------------------------------------------------------------------- 1 | public func arrayIncrement(array: inout [Int], index: Int, amount: Int) { 2 | array[index] += amount 3 | } 4 | 5 | public func arrayDecrement(array: inout [Int], index: Int, amount: Int) { 6 | array[index] -= amount 7 | } 8 | -------------------------------------------------------------------------------- /tests/builtins/discard.js: -------------------------------------------------------------------------------- 1 | export function callAndDiscard(predicate) { 2 | predicate(42); 3 | } -------------------------------------------------------------------------------- /tests/builtins/discard.swift: -------------------------------------------------------------------------------- 1 | public func callAndDiscard(_ predicate: (Int) -> Bool) { 2 | _ = withoutActuallyEscaping(predicate) { escapablePredicate in 3 | escapablePredicate(42) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/builtins/escaping.js: -------------------------------------------------------------------------------- 1 | export function test(predicate) { 2 | return predicate(42); 3 | } -------------------------------------------------------------------------------- /tests/builtins/escaping.swift: -------------------------------------------------------------------------------- 1 | public func test(_ predicate: (Int) -> Bool) -> Bool { 2 | return withoutActuallyEscaping(predicate) { escapablePredicate in 3 | escapablePredicate(42) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/builtins/extend.js: -------------------------------------------------------------------------------- 1 | export function callExtended$callback$(foo, callback) { 2 | return callback(foo); 3 | } 4 | export class Foo {} -------------------------------------------------------------------------------- /tests/builtins/extend.swift: -------------------------------------------------------------------------------- 1 | public class Foo { 2 | } 3 | 4 | public func callExtended(_ foo: Foo, callback: () -> Bool) -> Bool { 5 | return withExtendedLifetime(foo, callback) 6 | } 7 | -------------------------------------------------------------------------------- /tests/builtins/magic.js: -------------------------------------------------------------------------------- 1 | export function getMagic() { 2 | return ["./tests/builtins/magic.swift", "getMagic()", 2, 35]; 3 | } -------------------------------------------------------------------------------- /tests/builtins/magic.swift: -------------------------------------------------------------------------------- 1 | public func getMagic() -> (String, String, Int, Int) { 2 | return (#file, #function, #line, #column) 3 | } 4 | -------------------------------------------------------------------------------- /tests/builtins/swap.js: -------------------------------------------------------------------------------- 1 | export function swapInts$a$b$(a, b) { 2 | const temp = a[0]; 3 | a[0] = b[0]; 4 | b[0] = temp; 5 | } 6 | export function swapInts() { 7 | const a = [0]; 8 | const b = [1]; 9 | const temp = a[0]; 10 | a[0] = b[0]; 11 | b[0] = temp; 12 | return a[0]; 13 | } -------------------------------------------------------------------------------- /tests/builtins/swap.swift: -------------------------------------------------------------------------------- 1 | public func swapInts(a: inout Int, b: inout Int) { 2 | swap(&a, &b) 3 | } 4 | 5 | public func swapInts() -> Int { 6 | var a = 0 7 | var b = 1 8 | swap(&a, &b) 9 | return a 10 | } 11 | -------------------------------------------------------------------------------- /tests/builtins/unique.js: -------------------------------------------------------------------------------- 1 | export function isUnique(foo) { 2 | return false; 3 | } 4 | export class Foo {} -------------------------------------------------------------------------------- /tests/builtins/unique.swift: -------------------------------------------------------------------------------- 1 | public class Foo { 2 | } 3 | 4 | public func isUnique(_ foo: inout Foo) -> Bool { 5 | return isKnownUniquelyReferenced(&foo) 6 | } 7 | -------------------------------------------------------------------------------- /tests/class/computed-getter.js: -------------------------------------------------------------------------------- 1 | export function isEmpty$size$(size) { 2 | return size.isEmpty; 3 | } 4 | export function isEmptyInlined$size$(size) { 5 | return size.width === 0 && size.height === 0; 6 | } 7 | export class Size { 8 | get isEmpty() { 9 | return this.width === 0 && this.height === 0; 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /tests/class/computed-getter.swift: -------------------------------------------------------------------------------- 1 | public class Size { 2 | var width: Double 3 | var height: Double 4 | 5 | init(width: Double, height: Double) { 6 | } 7 | 8 | public var isEmpty: Bool { 9 | return width == 0 && height == 0 10 | } 11 | 12 | var isEmptyInlined: Bool { 13 | return width == 0 && height == 0 14 | } 15 | } 16 | 17 | public func isEmpty(size: Size) -> Bool { 18 | return size.isEmpty 19 | } 20 | 21 | public func isEmptyInlined(size: Size) -> Bool { 22 | return size.isEmptyInlined 23 | } 24 | -------------------------------------------------------------------------------- /tests/class/copy-and-edit.js: -------------------------------------------------------------------------------- 1 | export function pointOffsetFromOrigin$x$y$(x, y) { 2 | const _x = origin.x; 3 | const _y = origin.y; 4 | const point0 = new Point(); 5 | point0.x = _x; 6 | point0.y = _y; 7 | const result = point0; 8 | result.x = result.x + x; 9 | result.y = result.y + y; 10 | return result; 11 | } 12 | export class Point {} 13 | const point = new Point(); 14 | point.x = 0; 15 | point.y = 0; 16 | const origin = point; -------------------------------------------------------------------------------- /tests/class/copy-and-edit.swift: -------------------------------------------------------------------------------- 1 | public class Point { 2 | var x: Double 3 | var y: Double 4 | 5 | init(x _x: Double, y _y: Double) { 6 | x = _x 7 | y = _y 8 | } 9 | } 10 | 11 | let origin = Point(x: 0, y: 0) 12 | 13 | public func pointOffsetFromOrigin(x: Double, y: Double) -> Point { 14 | let result = Point(x: origin.x, y: origin.y) 15 | result.x += x 16 | result.y += y 17 | return result 18 | } 19 | -------------------------------------------------------------------------------- /tests/class/deinit.js: -------------------------------------------------------------------------------- 1 | export function allocate() { 2 | let deinit = new Deinit(); 3 | console.log("init called"); 4 | return deinit; 5 | } 6 | export class Deinit {} -------------------------------------------------------------------------------- /tests/class/deinit.swift: -------------------------------------------------------------------------------- 1 | public class Deinit { 2 | init() { 3 | print("init called") 4 | } 5 | deinit { 6 | print("deinit called") 7 | } 8 | } 9 | 10 | public func allocate() -> Deinit { 11 | return Deinit() 12 | } 13 | -------------------------------------------------------------------------------- /tests/class/distance.js: -------------------------------------------------------------------------------- 1 | export function distance$first$second$(first, second) { 2 | const _x = first.x - second.x; 3 | 4 | const _y = first.y - second.y; 5 | 6 | const point = new Point(); 7 | point.x = _x; 8 | point.y = _y; 9 | const delta = point; 10 | return Math.sqrt(delta.x * delta.x + delta.y * delta.y); 11 | } 12 | export class Point {} -------------------------------------------------------------------------------- /tests/class/distance.swift: -------------------------------------------------------------------------------- 1 | public class Point { 2 | var x: Double 3 | var y: Double 4 | public init() { 5 | x = 0 6 | y = 0 7 | } 8 | public init(x _x: Double, y _y: Double) { 9 | x = _x 10 | y = _y 11 | } 12 | var isOrigin: Bool { 13 | get { 14 | return x == 0 && y == 0 15 | } 16 | } 17 | } 18 | 19 | public func distance(first: Point, second: Point) -> Double { 20 | let delta = Point(x: first.x - second.x, y: first.y - second.y) 21 | return (delta.x * delta.x + delta.y * delta.y).squareRoot() 22 | } 23 | -------------------------------------------------------------------------------- /tests/class/fail-init.js: -------------------------------------------------------------------------------- 1 | export function allocate$successfully$(successfully) { 2 | const possible = new Possible(); 3 | possible.foo = 0; 4 | 5 | if (!successfully) { 6 | return null; 7 | } 8 | 9 | return possible; 10 | } 11 | export function allocateAlways() { 12 | const possible0 = new Possible(); 13 | possible0.foo = 0; 14 | return possible0; 15 | } 16 | export class Possible {} -------------------------------------------------------------------------------- /tests/class/fail-init.swift: -------------------------------------------------------------------------------- 1 | public class Possible { 2 | let foo: Int = 0 3 | init?(successfully: Bool) { 4 | if (!successfully) { 5 | return nil 6 | } 7 | } 8 | init() { 9 | } 10 | } 11 | 12 | public func allocate(successfully: Bool) -> Possible? { 13 | return Possible(successfully: successfully) 14 | } 15 | 16 | public func allocateAlways() -> Possible? { 17 | return Possible() 18 | } 19 | -------------------------------------------------------------------------------- /tests/class/in-array.js: -------------------------------------------------------------------------------- 1 | export function makeSizes$w1$h1$w2$h2$(w1, h1, w2, h2) { 2 | const size = new Size(); 3 | size.width = w1; 4 | size.height = h1; 5 | const size0 = new Size(); 6 | size0.width = w2; 7 | size0.height = h2; 8 | return [size, size0]; 9 | } 10 | export function countSizes$sizes$(sizes) { 11 | return sizes.length; 12 | } 13 | export function copySizes$sizes$(sizes) { 14 | return sizes.slice(); 15 | } 16 | export class Size {} -------------------------------------------------------------------------------- /tests/class/in-array.swift: -------------------------------------------------------------------------------- 1 | public class Size { 2 | var width: Double 3 | var height: Double 4 | public init(width w: Double, height h: Double) { 5 | width = w 6 | height = h 7 | } 8 | } 9 | 10 | public func makeSizes(w1: Double, h1: Double, w2: Double, h2: Double) -> [Size] { 11 | return [Size(width: w1, height: h1), Size(width: w2, height: h2)] 12 | } 13 | 14 | public func countSizes(sizes: [Size]) -> Int { 15 | return sizes.count 16 | } 17 | 18 | public func copySizes(sizes: [Size]) -> [Size] { 19 | return sizes 20 | } 21 | -------------------------------------------------------------------------------- /tests/class/in-tuple.js: -------------------------------------------------------------------------------- 1 | export function makeSizes$w1$h1$w2$h2$(w1, h1, w2, h2) { 2 | const size = new Size(); 3 | size.width = w1; 4 | size.height = h1; 5 | const size0 = new Size(); 6 | size0.width = w2; 7 | size0.height = h2; 8 | return [size, size0]; 9 | } 10 | export function sumSizes$sizes$(sizes) { 11 | const w = sizes[0].width + sizes[1].height; 12 | const h = sizes[0].height + sizes[1].height; 13 | const size1 = new Size(); 14 | size1.width = w; 15 | size1.height = h; 16 | return size1; 17 | } 18 | export function copySizes$sizes$(sizes) { 19 | return sizes.slice(); 20 | } 21 | export class Size {} -------------------------------------------------------------------------------- /tests/class/in-tuple.swift: -------------------------------------------------------------------------------- 1 | public class Size { 2 | var width: Double 3 | var height: Double 4 | public init(width w: Double, height h: Double) { 5 | width = w 6 | height = h 7 | } 8 | } 9 | 10 | public func makeSizes(w1: Double, h1: Double, w2: Double, h2: Double) -> (Size, Size) { 11 | return (Size(width: w1, height: h1), Size(width: w2, height: h2)) 12 | } 13 | 14 | public func sumSizes(sizes: (Size, Size)) -> Size { 15 | return Size(width: sizes.0.width + sizes.1.height, height: sizes.0.height + sizes.1.height) 16 | } 17 | 18 | public func copySizes(sizes: (Size, Size)) -> (Size, Size) { 19 | return sizes 20 | } 21 | -------------------------------------------------------------------------------- /tests/class/make-struct.js: -------------------------------------------------------------------------------- 1 | export function makeSize$w$h$(w, h) { 2 | const size = new Size(); 3 | size.width = w; 4 | size.height = h; 5 | return size; 6 | } 7 | export class Size {} -------------------------------------------------------------------------------- /tests/class/make-struct.swift: -------------------------------------------------------------------------------- 1 | public class Size { 2 | var width: Double 3 | var height: Double 4 | public init(width w: Double, height h: Double) { 5 | width = w 6 | height = h 7 | } 8 | } 9 | 10 | public func makeSize(w: Double, h: Double) -> Size { 11 | return Size(width: w, height: h) 12 | } 13 | -------------------------------------------------------------------------------- /tests/class/var.js: -------------------------------------------------------------------------------- 1 | export class Point {} 2 | const point = new Point(); 3 | point.x = 0; 4 | point.y = 0; 5 | export let origin = point; -------------------------------------------------------------------------------- /tests/class/var.swift: -------------------------------------------------------------------------------- 1 | public class Point { 2 | var x: Double 3 | var y: Double 4 | public init() { 5 | x = 0 6 | y = 0 7 | } 8 | } 9 | 10 | public var origin = Point() 11 | -------------------------------------------------------------------------------- /tests/class/virtual-dispatch.js: -------------------------------------------------------------------------------- 1 | export function makeVirtualCall$onFoo$passthrough$(foo, passthrough) { 2 | return "Result: " + foo.virtualCall$passthrough$(passthrough); 3 | } 4 | export function makeVirtualCall$onBar$passthrough$(bar, passthrough) { 5 | return "Result: " + bar.virtualCall$passthrough$(passthrough); 6 | } 7 | export function makeStaticCall$onFoo$(foo) { 8 | return "Result: Static"; 9 | } 10 | export function makeStaticCall$onBar$(bar) { 11 | return "Result: Static"; 12 | } 13 | export function isBar$foo$(foo) { 14 | return foo instanceof Bar; 15 | } 16 | export function makeCastAndCall$onFoo$(foo) { 17 | if (foo instanceof Bar) { 18 | const bar = foo; 19 | return "New method called on Bar"; 20 | } else { 21 | return "Not a Bar"; 22 | } 23 | } 24 | export class Foo { 25 | virtualCall$passthrough$(passthrough) { 26 | return "Foo"; 27 | } 28 | 29 | } 30 | export class Bar extends Foo { 31 | virtualCall$passthrough$(passthrough) { 32 | if (passthrough) { 33 | return super.virtualCall$passthrough$(passthrough); 34 | } else { 35 | return "Bar"; 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /tests/class/virtual-dispatch.swift: -------------------------------------------------------------------------------- 1 | public class Foo { 2 | public func virtualCall(passthrough: Bool) -> String { 3 | return "Foo" 4 | } 5 | public final func staticCall() -> String { 6 | return "Static" 7 | } 8 | } 9 | 10 | public class Bar: Foo { 11 | public override func virtualCall(passthrough: Bool) -> String { 12 | if (passthrough) { 13 | return super.virtualCall(passthrough: passthrough) 14 | } else { 15 | return "Bar" 16 | } 17 | } 18 | public final func newMethod() -> String { 19 | return "New method called on Bar" 20 | } 21 | } 22 | 23 | public func makeVirtualCall(onFoo foo: Foo, passthrough: Bool) -> String { 24 | return "Result: " + foo.virtualCall(passthrough: passthrough) 25 | } 26 | 27 | public func makeVirtualCall(onBar bar: Bar, passthrough: Bool) -> String { 28 | return "Result: " + bar.virtualCall(passthrough: passthrough) 29 | } 30 | 31 | public func makeStaticCall(onFoo foo: Foo) -> String { 32 | return "Result: " + foo.staticCall() 33 | } 34 | 35 | public func makeStaticCall(onBar bar: Bar) -> String { 36 | return "Result: " + bar.staticCall() 37 | } 38 | 39 | public func isBar(foo: Foo) -> Bool { 40 | return foo is Bar 41 | } 42 | 43 | public func makeCastAndCall(onFoo foo: Foo) -> String { 44 | if let bar = foo as? Bar { 45 | return bar.newMethod() 46 | } else { 47 | return "Not a Bar" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/class/weak.js: -------------------------------------------------------------------------------- 1 | export function allocate() { 2 | const weakSelf = new WeakSelf(); 3 | weakSelf.property = null; 4 | const result = weakSelf; 5 | result.property = result; 6 | return result; 7 | } 8 | export function read$weakSelf$(weakSelf) { 9 | return weakSelf.property; 10 | } 11 | export class WeakSelf {} -------------------------------------------------------------------------------- /tests/class/weak.swift: -------------------------------------------------------------------------------- 1 | public class WeakSelf { 2 | weak var property: WeakSelf? 3 | init() { 4 | } 5 | } 6 | 7 | public func allocate() -> WeakSelf { 8 | let result = WeakSelf() 9 | result.property = result 10 | return result 11 | } 12 | 13 | public func read(weakSelf: WeakSelf) -> WeakSelf? { 14 | return weakSelf.property 15 | } 16 | -------------------------------------------------------------------------------- /tests/complex/factorial-functional.js: -------------------------------------------------------------------------------- 1 | export function factorial$of$(number) { 2 | let result = 1; 3 | 4 | for (let i = 2; i <= number; i++) { 5 | result = result * i; 6 | } 7 | 8 | return result; 9 | } 10 | console.log(factorial$of$(20)); -------------------------------------------------------------------------------- /tests/complex/factorial-functional.swift: -------------------------------------------------------------------------------- 1 | public func factorial(of number: UInt) -> UInt { 2 | return (2...number).reduce(1, *) 3 | } 4 | 5 | print(factorial(of: 20)) 6 | -------------------------------------------------------------------------------- /tests/complex/fizzbuzz.js: -------------------------------------------------------------------------------- 1 | export function fizzBuzz() { 2 | const mapped = []; 3 | 4 | for (let i = 1; i <= 100; i++) { 5 | mapped.push(i % 3 === 0 ? i % 5 === 0 ? "FizzBuzz" : "Fizz" : i % 5 === 0 ? "Buzz" : String(i)); 6 | } 7 | 8 | return mapped; 9 | } -------------------------------------------------------------------------------- /tests/complex/fizzbuzz.swift: -------------------------------------------------------------------------------- 1 | public func fizzBuzz() -> [String] { 2 | return (1...100).map { (num: Int) -> String in return num % 3 == 0 ? num % 5 == 0 ? "FizzBuzz" : "Fizz" : num % 5 == 0 ? "Buzz" : String(num) }; 3 | } 4 | -------------------------------------------------------------------------------- /tests/complex/sequence-map.js: -------------------------------------------------------------------------------- 1 | export function stringSequence$until$(limit) { 2 | const mapped = []; 3 | 4 | for (let i = 1; i <= limit; i++) { 5 | mapped.push(String(i)); 6 | } 7 | 8 | return mapped.join(" "); 9 | } -------------------------------------------------------------------------------- /tests/complex/sequence-map.swift: -------------------------------------------------------------------------------- 1 | public func stringSequence(until limit: Int) -> String { 2 | return (1...limit).map(String.init).joined(separator: " ") 3 | } 4 | -------------------------------------------------------------------------------- /tests/dictionaries/empty.js: -------------------------------------------------------------------------------- 1 | export function emptyIntToIntDict() { 2 | return {}; 3 | } -------------------------------------------------------------------------------- /tests/dictionaries/empty.swift: -------------------------------------------------------------------------------- 1 | public func emptyIntToIntDict() -> [Int: Int] { 2 | return [:] 3 | } 4 | -------------------------------------------------------------------------------- /tests/dictionaries/int-count.js: -------------------------------------------------------------------------------- 1 | export function dictionaryCount(dict) { 2 | return Object.keys(dict).length; 3 | } -------------------------------------------------------------------------------- /tests/dictionaries/int-count.swift: -------------------------------------------------------------------------------- 1 | public func dictionaryCount(_ dict: [Int: Int]) -> Int { 2 | return dict.count 3 | } 4 | -------------------------------------------------------------------------------- /tests/dictionaries/int-get.js: -------------------------------------------------------------------------------- 1 | export function dictGet$key$(dict, key) { 2 | return Object.hasOwnProperty.call(dict, key) ? dict[key] : null; 3 | } 4 | export function doubleDictGet$key$(dict, key) { 5 | return Object.hasOwnProperty.call(dict, key) ? [dict[key]] : []; 6 | } -------------------------------------------------------------------------------- /tests/dictionaries/int-get.swift: -------------------------------------------------------------------------------- 1 | public func dictGet(_ dict: [Int: Int], key: Int) -> Int? { 2 | return dict[key] 3 | } 4 | 5 | public func doubleDictGet(_ dict: [Int: Int?], key: Int) -> Int?? { 6 | return dict[key] 7 | } 8 | -------------------------------------------------------------------------------- /tests/dictionaries/int-set.js: -------------------------------------------------------------------------------- 1 | export function dictSet$key$value$(dict, key, value) { 2 | if (value !== null) { 3 | dict[key] = value; 4 | } else { 5 | delete dict[key]; 6 | } 7 | } -------------------------------------------------------------------------------- /tests/dictionaries/int-set.swift: -------------------------------------------------------------------------------- 1 | public func dictSet(_ dict: inout [Int: Int], key: Int, value: Int) -> () { 2 | dict[key] = value 3 | } 4 | -------------------------------------------------------------------------------- /tests/dictionaries/keys.js: -------------------------------------------------------------------------------- 1 | export function dictionaryKeys(dict) { 2 | return Object.keys(dict); 3 | } 4 | export function firstKey(dict) { 5 | const keys = Object.keys(dict); 6 | return keys.length ? Number(keys[0]) : null; 7 | } -------------------------------------------------------------------------------- /tests/dictionaries/keys.swift: -------------------------------------------------------------------------------- 1 | public func dictionaryKeys(_ dict: [Int: Int]) -> Dictionary.Keys { 2 | return dict.keys 3 | } 4 | 5 | public func firstKey(_ dict: [Int: Int]) -> Int? { 6 | return dict.keys.first 7 | } 8 | -------------------------------------------------------------------------------- /tests/dictionaries/one-int.js: -------------------------------------------------------------------------------- 1 | export function oneInt$key$value$(key, value) { 2 | return { 3 | [key]: value 4 | }; 5 | } -------------------------------------------------------------------------------- /tests/dictionaries/one-int.swift: -------------------------------------------------------------------------------- 1 | public func oneInt(key: Int, value: Int) -> [Int: Int] { 2 | return [key: value] 3 | } 4 | -------------------------------------------------------------------------------- /tests/dictionaries/optional-string-dict.js: -------------------------------------------------------------------------------- 1 | export function newEmpty() { 2 | return {}; 3 | } 4 | export function newSingle$key$value$(key, value) { 5 | return { 6 | [key]: value 7 | }; 8 | } 9 | export function dictGet$key$(dict, key) { 10 | return Object.hasOwnProperty.call(dict, key) ? [dict[key]] : []; 11 | } 12 | export function dictSet$key$value$(dict, key, value) { 13 | if (value !== null) { 14 | dict[key] = value; 15 | } else { 16 | delete dict[key]; 17 | } 18 | } 19 | export function count(dict) { 20 | return Object.keys(dict).length; 21 | } 22 | export function allKeys(dict) { 23 | return Object.keys(dict); 24 | } 25 | export function firstKey(dict) { 26 | const keys = Object.keys(dict); 27 | return keys.length ? keys[0] : null; 28 | } -------------------------------------------------------------------------------- /tests/dictionaries/optional-string-dict.swift: -------------------------------------------------------------------------------- 1 | public func newEmpty() -> [String: String?] { 2 | return [:] 3 | } 4 | 5 | public func newSingle(key: String, value: String?) -> [String: String?] { 6 | return [key: value] 7 | } 8 | 9 | public func dictGet(_ dict: [String: String?], key: String) -> String?? { 10 | return dict[key] 11 | } 12 | 13 | public func dictSet(_ dict: inout [String: String], key: String, value: String?) -> () { 14 | dict[key] = value 15 | } 16 | 17 | public func count(_ dict: [String: String?]) -> Int { 18 | return dict.count 19 | } 20 | 21 | public func allKeys(_ dict: [String: String?]) -> Dictionary.Keys { 22 | return dict.keys 23 | } 24 | 25 | public func firstKey(_ dict: [String: String?]) -> String? { 26 | return dict.keys.first 27 | } 28 | -------------------------------------------------------------------------------- /tests/dictionaries/string-dict.js: -------------------------------------------------------------------------------- 1 | export function newEmpty() { 2 | return {}; 3 | } 4 | export function newSingle$key$value$(key, value) { 5 | return { 6 | [key]: value 7 | }; 8 | } 9 | export function dictGet$key$(dict, key) { 10 | return Object.hasOwnProperty.call(dict, key) ? dict[key] : null; 11 | } 12 | export function dictSet$key$value$(dict, key, value) { 13 | if (value !== null) { 14 | dict[key] = value; 15 | } else { 16 | delete dict[key]; 17 | } 18 | } 19 | export function count(dict) { 20 | return Object.keys(dict).length; 21 | } 22 | export function allKeys(dict) { 23 | return Object.keys(dict); 24 | } 25 | export function firstKey(dict) { 26 | const keys = Object.keys(dict); 27 | return keys.length ? keys[0] : null; 28 | } -------------------------------------------------------------------------------- /tests/dictionaries/string-dict.swift: -------------------------------------------------------------------------------- 1 | public func newEmpty() -> [String: String] { 2 | return [:] 3 | } 4 | 5 | public func newSingle(key: String, value: String) -> [String: String] { 6 | return [key: value] 7 | } 8 | 9 | public func dictGet(_ dict: [String: String], key: String) -> String? { 10 | return dict[key] 11 | } 12 | 13 | public func dictSet(_ dict: inout [String: String], key: String, value: String) -> () { 14 | dict[key] = value 15 | } 16 | 17 | public func count(_ dict: [String: String]) -> Int { 18 | return dict.count 19 | } 20 | 21 | public func allKeys(_ dict: [String: String]) -> Dictionary.Keys { 22 | return dict.keys 23 | } 24 | 25 | public func firstKey(_ dict: [String: String]) -> String? { 26 | return dict.keys.first 27 | } 28 | -------------------------------------------------------------------------------- /tests/enums/arithmetic.js: -------------------------------------------------------------------------------- 1 | export function literal(value) { 2 | return [0, value]; 3 | } 4 | 5 | function ArithmeticExpression$copy(source) { 6 | return source[0] === 2 ? [2, ArithmeticExpression$copy(source[1]), ArithmeticExpression$copy(source[2])] : source[0] === 1 ? [1, ArithmeticExpression$copy(source[1]), ArithmeticExpression$copy(source[2])] : source.slice(); 7 | } 8 | 9 | export function add(left, right) { 10 | return [1, ArithmeticExpression$copy(left), ArithmeticExpression$copy(right)]; 11 | } 12 | export function multiply(left, right) { 13 | return [2, ArithmeticExpression$copy(left), ArithmeticExpression$copy(right)]; 14 | } 15 | export function eval(expression) { 16 | var $match = expression; 17 | 18 | if ($match[0] === 0) { 19 | const value = $match[1]; 20 | return value; 21 | } else if ($match[0] === 1) { 22 | const l = ArithmeticExpression$copy($match[1]), 23 | r = ArithmeticExpression$copy($match[2]); 24 | return eval(l) + eval(r); 25 | } else if ($match[0] === 2) { 26 | const l = ArithmeticExpression$copy($match[1]), 27 | r = ArithmeticExpression$copy($match[2]); 28 | return eval(l) * eval(r); 29 | } 30 | } 31 | export function silly(expression) { 32 | var $match = expression; 33 | 34 | if ($match[0] === 0) { 35 | let value = $match[1]; 36 | value += 10; 37 | return value; 38 | } else if ($match[0] === 1) { 39 | let l = ArithmeticExpression$copy($match[1]); 40 | l = literal(10); 41 | return eval(l); 42 | } else { 43 | return 0; 44 | } 45 | } -------------------------------------------------------------------------------- /tests/enums/arithmetic.swift: -------------------------------------------------------------------------------- 1 | public enum ArithmeticExpression { 2 | case number(Double) 3 | indirect case addition(ArithmeticExpression, ArithmeticExpression) 4 | indirect case multiplication(ArithmeticExpression, ArithmeticExpression) 5 | } 6 | 7 | public func literal(_ value: Double) -> ArithmeticExpression { 8 | return .number(value) 9 | } 10 | 11 | public func add(_ left: ArithmeticExpression, _ right: ArithmeticExpression) -> ArithmeticExpression { 12 | return .addition(left, right) 13 | } 14 | 15 | public func multiply(_ left: ArithmeticExpression, _ right: ArithmeticExpression) -> ArithmeticExpression { 16 | return .multiplication(left, right) 17 | } 18 | 19 | public func eval(_ expression: ArithmeticExpression) -> Double { 20 | switch expression { 21 | case .number(let value): 22 | return value 23 | case .addition(let l, let r): 24 | return eval(l) + eval(r) 25 | case .multiplication(let l, let r): 26 | return eval(l) * eval(r) 27 | } 28 | } 29 | 30 | public func silly(_ expression: ArithmeticExpression) -> Double { 31 | switch expression { 32 | case .number(var value): 33 | value += 10 34 | return value 35 | case .addition(var l, _): 36 | l = literal(10) 37 | return eval(l) 38 | default: 39 | return 0 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/enums/field-requires-copy.js: -------------------------------------------------------------------------------- 1 | function Position$copy(source) { 2 | return source[0] === 2 ? [2, { 3 | x: source[1].x, 4 | y: source[1].y 5 | }, source[2]] : source[0] === 1 ? [1, { 6 | x: source[1].x, 7 | y: source[1].y 8 | }] : source.slice(); 9 | } 10 | 11 | export function makeCopy$ofPosition$(position) { 12 | const copy = Position$copy(position); 13 | return copy; 14 | } 15 | export function makeCopyDirect$ofPosition$(position) { 16 | return Position$copy(position); 17 | } -------------------------------------------------------------------------------- /tests/enums/field-requires-copy.swift: -------------------------------------------------------------------------------- 1 | public struct Point { 2 | var x: Double 3 | var y: Double 4 | public init(x _x: Double, y _y: Double) { 5 | x = _x 6 | y = _y 7 | } 8 | } 9 | 10 | public enum Position { 11 | case empty 12 | case twoDimensional(Point) 13 | case threeDimensional(Point, Double) 14 | } 15 | 16 | public func makeCopy(ofPosition position: Position) -> Position { 17 | let copy = position 18 | return copy 19 | } 20 | 21 | public func makeCopyDirect(ofPosition position: Position) -> Position { 22 | return position 23 | } 24 | -------------------------------------------------------------------------------- /tests/enums/numeric.js: -------------------------------------------------------------------------------- 1 | export function select_value$num$(num) { 2 | var $match = num % 15; 3 | 4 | if (0 === $match) { 5 | return 3; 6 | } else if (3 === $match || 6 === $match || 9 === $match || 12 === $match) { 7 | return 1; 8 | } else if (5 === $match || 10 === $match) { 9 | return 2; 10 | } else { 11 | return 0; 12 | } 13 | } 14 | export function describe$divisible$(divisible) { 15 | var $match = divisible; 16 | 17 | if ($match === 0) { 18 | return "divisible by neither three or five"; 19 | } else if ($match === 1) { 20 | return "divisible by three, but not five"; 21 | } else if ($match === 2) { 22 | return "divisible by five, but not three"; 23 | } else if ($match === 3) { 24 | return "divisible by both three and five"; 25 | } 26 | } 27 | export function rawValue$of$(divisible) { 28 | return divisible; 29 | } 30 | export function hashValue$of$(divisible) { 31 | return divisible; 32 | } -------------------------------------------------------------------------------- /tests/enums/numeric.swift: -------------------------------------------------------------------------------- 1 | public enum Divisible: Int { 2 | case byNone = 0 3 | case byThree = 1 4 | case byFive = 2 5 | case byBoth = 3 6 | } 7 | 8 | public func select_value(num: Int) -> Divisible { 9 | switch num % 15 { 10 | case 0: 11 | return Divisible.byBoth 12 | case 3, 6, 9, 12: 13 | return Divisible.byThree 14 | case 5, 10: 15 | return Divisible.byFive 16 | default: 17 | return Divisible.byNone 18 | } 19 | } 20 | 21 | public func describe(divisible: Divisible) -> String { 22 | switch divisible { 23 | case .byNone: 24 | return "divisible by neither three or five" 25 | case .byThree: 26 | return "divisible by three, but not five" 27 | case .byFive: 28 | return "divisible by five, but not three" 29 | case .byBoth: 30 | return "divisible by both three and five" 31 | } 32 | } 33 | 34 | public func rawValue(of divisible: Divisible) -> Int { 35 | return divisible.rawValue 36 | } 37 | 38 | public func hashValue(of divisible: Divisible) -> Int { 39 | return divisible.hashValue 40 | } 41 | -------------------------------------------------------------------------------- /tests/enums/with-simple-data.js: -------------------------------------------------------------------------------- 1 | export function makeEmpty() { 2 | return [0]; 3 | } 4 | export function makeUpc$numberSystem$manufacturer$product$check$(numberSystem, manufacturer, product, check) { 5 | return [1, numberSystem, manufacturer, product, check]; 6 | } 7 | export function makeQr$value$(value) { 8 | return [2, value]; 9 | } 10 | export function describe$barcode$(barcode) { 11 | var $match = barcode; 12 | 13 | if ($match[0] === 0) { 14 | return "Empty"; 15 | } else if ($match[0] === 1) { 16 | const numberSystem = $match[1], 17 | manufacturer = $match[2], 18 | product = $match[3], 19 | check = $match[4]; 20 | return "UPC:" + String(numberSystem) + "-" + String(manufacturer) + "-" + String(product) + "-" + String(check); 21 | } else if ($match[0] === 2) { 22 | const value = $match[1]; 23 | return "QR:" + value; 24 | } 25 | } -------------------------------------------------------------------------------- /tests/enums/with-simple-data.swift: -------------------------------------------------------------------------------- 1 | public enum Barcode { 2 | case empty 3 | case upc(Int, Int, Int, Int) 4 | case qrCode(String) 5 | } 6 | 7 | public func makeEmpty() -> Barcode { 8 | return .empty 9 | } 10 | 11 | public func makeUpc(numberSystem: Int, manufacturer: Int, product: Int, check: Int) -> Barcode { 12 | return .upc(numberSystem, manufacturer, product, check) 13 | } 14 | 15 | public func makeQr(value: String) -> Barcode { 16 | return .qrCode(value) 17 | } 18 | 19 | public func describe(barcode: Barcode) -> String { 20 | switch barcode { 21 | case .empty: 22 | return "Empty" 23 | case .upc(let numberSystem, let manufacturer, let product, let check): 24 | return "UPC:" + String(numberSystem) + "-" + String(manufacturer) + "-" + String(product) + "-" + String(check) 25 | case .qrCode(let value): 26 | return "QR:" + value 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/errors/defer.js: -------------------------------------------------------------------------------- 1 | export function attempt$shouldThrow$(shouldThrow) { 2 | if (shouldThrow) { 3 | throw 0; 4 | } 5 | 6 | return true; 7 | } 8 | export function defers$shouldThrow$(shouldThrow) { 9 | try { 10 | processing[0] = true; 11 | let $try; 12 | 13 | try { 14 | $try = attempt$shouldThrow$(shouldThrow); 15 | } catch (e) { 16 | $try = null; 17 | } 18 | 19 | return $try; 20 | } finally { 21 | processing[0] = false; 22 | } 23 | } 24 | const processing = [false]; -------------------------------------------------------------------------------- /tests/errors/defer.swift: -------------------------------------------------------------------------------- 1 | public enum BasicError: Error { 2 | case requested 3 | } 4 | 5 | public func attempt(shouldThrow: Bool) throws -> Bool { 6 | if shouldThrow { 7 | throw BasicError.requested 8 | } 9 | return true 10 | } 11 | 12 | var processing: Bool 13 | 14 | public func defers(shouldThrow: Bool) -> Bool? { 15 | processing = true 16 | defer { 17 | processing = false 18 | } 19 | return try? attempt(shouldThrow: shouldThrow) 20 | } 21 | -------------------------------------------------------------------------------- /tests/errors/do.js: -------------------------------------------------------------------------------- 1 | export function attempt$shouldThrow$(shouldThrow) { 2 | if (shouldThrow) { 3 | throw 0; 4 | } 5 | } 6 | export function recover$shouldThrow$(shouldThrow) { 7 | attempt$shouldThrow$(shouldThrow); 8 | return 1; 9 | } -------------------------------------------------------------------------------- /tests/errors/do.swift: -------------------------------------------------------------------------------- 1 | public enum BasicError: Error { 2 | case requested 3 | } 4 | 5 | public func attempt(shouldThrow: Bool) throws -> () { 6 | if shouldThrow { 7 | throw BasicError.requested 8 | } 9 | } 10 | 11 | public func recover(shouldThrow: Bool) throws -> Int { 12 | do { 13 | try attempt(shouldThrow: shouldThrow) 14 | return 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/errors/force.js: -------------------------------------------------------------------------------- 1 | export function attempt$shouldThrow$(shouldThrow) { 2 | if (shouldThrow) { 3 | throw 0; 4 | } 5 | 6 | return true; 7 | } 8 | export function force$shouldThrow$(shouldThrow) { 9 | return attempt$shouldThrow$(shouldThrow); 10 | } -------------------------------------------------------------------------------- /tests/errors/force.swift: -------------------------------------------------------------------------------- 1 | public enum BasicError: Error { 2 | case requested 3 | } 4 | 5 | public func attempt(shouldThrow: Bool) throws -> Bool { 6 | if shouldThrow { 7 | throw BasicError.requested 8 | } 9 | return true 10 | } 11 | 12 | public func force(shouldThrow: Bool) -> Bool { 13 | return try! attempt(shouldThrow: shouldThrow) 14 | } 15 | -------------------------------------------------------------------------------- /tests/errors/guard.js: -------------------------------------------------------------------------------- 1 | export function attempt$shouldSucceed$(shouldSucceed) { 2 | if (!shouldSucceed) { 3 | throw 0; 4 | } 5 | } -------------------------------------------------------------------------------- /tests/errors/guard.swift: -------------------------------------------------------------------------------- 1 | public enum BasicError: Error { 2 | case requested 3 | } 4 | 5 | public func attempt(shouldSucceed: Bool) throws -> () { 6 | guard shouldSucceed else { 7 | throw BasicError.requested 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/errors/optional.js: -------------------------------------------------------------------------------- 1 | export function attempt$shouldThrow$(shouldThrow) { 2 | if (shouldThrow) { 3 | throw 0; 4 | } 5 | 6 | return true; 7 | } 8 | export function recover$shouldThrow$(shouldThrow) { 9 | let $try; 10 | 11 | try { 12 | $try = attempt$shouldThrow$(shouldThrow); 13 | } catch (e) { 14 | $try = null; 15 | } 16 | 17 | return $try; 18 | } -------------------------------------------------------------------------------- /tests/errors/optional.swift: -------------------------------------------------------------------------------- 1 | public enum BasicError: Error { 2 | case requested 3 | } 4 | 5 | public func attempt(shouldThrow: Bool) throws -> Bool { 6 | if shouldThrow { 7 | throw BasicError.requested 8 | } 9 | return true 10 | } 11 | 12 | public func recover(shouldThrow: Bool) -> Bool? { 13 | return try? attempt(shouldThrow: shouldThrow) 14 | } 15 | -------------------------------------------------------------------------------- /tests/errors/precondition.js: -------------------------------------------------------------------------------- 1 | export function attempt$shouldSucceed$(shouldSucceed) { 2 | if (!shouldSucceed) { 3 | debugger; 4 | throw new Error("Should succeed", "", 0); 5 | } 6 | 7 | return shouldSucceed; 8 | } -------------------------------------------------------------------------------- /tests/errors/precondition.swift: -------------------------------------------------------------------------------- 1 | public func attempt(shouldSucceed: Bool) throws -> Bool { 2 | precondition(shouldSucceed, "Should succeed") 3 | return shouldSucceed 4 | } 5 | -------------------------------------------------------------------------------- /tests/errors/recover.js: -------------------------------------------------------------------------------- 1 | export function attempt$shouldThrow$(shouldThrow) { 2 | if (shouldThrow) { 3 | throw 0; 4 | } 5 | } 6 | export function recover$shouldThrow$(shouldThrow) { 7 | try { 8 | attempt$shouldThrow$(shouldThrow); 9 | return 1; 10 | } catch (error) { 11 | return 0; 12 | } 13 | } -------------------------------------------------------------------------------- /tests/errors/recover.swift: -------------------------------------------------------------------------------- 1 | public enum BasicError: Error { 2 | case requested 3 | } 4 | 5 | public func attempt(shouldThrow: Bool) throws -> () { 6 | if shouldThrow { 7 | throw BasicError.requested 8 | } 9 | } 10 | 11 | public func recover(shouldThrow: Bool) -> Int { 12 | do { 13 | try attempt(shouldThrow: shouldThrow) 14 | return 1 15 | } catch { 16 | return 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/errors/rethrows.js: -------------------------------------------------------------------------------- 1 | export function attempt$shouldThrow$(shouldThrow) { 2 | if (shouldThrow) { 3 | throw 0; 4 | } 5 | 6 | return true; 7 | } 8 | export function rethrowing$shouldThrow$(shouldThrow) { 9 | return attempt$shouldThrow$(shouldThrow); 10 | } -------------------------------------------------------------------------------- /tests/errors/rethrows.swift: -------------------------------------------------------------------------------- 1 | public enum BasicError: Error { 2 | case requested 3 | } 4 | 5 | public func attempt(shouldThrow: Bool) throws -> Bool { 6 | if shouldThrow { 7 | throw BasicError.requested 8 | } 9 | return true 10 | } 11 | 12 | public func rethrowing(shouldThrow: Bool) throws -> Bool { 13 | return try attempt(shouldThrow: shouldThrow) 14 | } 15 | -------------------------------------------------------------------------------- /tests/errors/throw.js: -------------------------------------------------------------------------------- 1 | export function attempt$shouldThrow$(shouldThrow) { 2 | if (shouldThrow) { 3 | throw 0; 4 | } 5 | } -------------------------------------------------------------------------------- /tests/errors/throw.swift: -------------------------------------------------------------------------------- 1 | public enum BasicError: Error { 2 | case requested 3 | } 4 | 5 | public func attempt(shouldThrow: Bool) throws -> () { 6 | if shouldThrow { 7 | throw BasicError.requested 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/generics/equatable-call.js: -------------------------------------------------------------------------------- 1 | export function equal$lhs$rhs$(T, lhs, rhs) { 2 | return T.Equatable.$equals$(T, lhs, rhs); 3 | } 4 | export function match$lhs$rhs$(T, lhs, rhs) { 5 | return T.Equatable.$equals$(T, lhs, rhs); 6 | } 7 | export function matchInts$lhs$rhs$(lhs, rhs) { 8 | return lhs === rhs; 9 | } 10 | export function matchOptionals$lhs$rhs$(lhs, rhs) { 11 | return lhs === rhs; 12 | } -------------------------------------------------------------------------------- /tests/generics/equatable-call.swift: -------------------------------------------------------------------------------- 1 | public func equal(lhs: T, rhs: T) -> Bool { 2 | return lhs == rhs 3 | } 4 | 5 | public func match(lhs: T, rhs: T) -> Bool { 6 | return lhs ~= rhs 7 | } 8 | 9 | public func matchInts(lhs: Int, rhs: Int) -> Bool { 10 | return lhs ~= rhs 11 | } 12 | 13 | public func matchOptionals(lhs: Bool?, rhs: Bool?) -> Bool { 14 | return lhs ~= rhs 15 | } 16 | -------------------------------------------------------------------------------- /tests/generics/equatable.swift: -------------------------------------------------------------------------------- 1 | public func equal(lhs: T, rhs: T) -> Bool { 2 | return lhs == rhs 3 | } 4 | 5 | public func match(lhs: T, rhs: T) -> Bool { 6 | return lhs ~= rhs 7 | } 8 | 9 | public func integerEqual(lhs: Int, rhs: Int) -> Bool { 10 | return equal(lhs: lhs, rhs: rhs) 11 | } 12 | 13 | public func optionalDoubleEqual(lhs: Double?, rhs: Double?) -> Bool { 14 | return equal(lhs: lhs, rhs: rhs) 15 | } 16 | 17 | public func stringArrayEqual(lhs: [String], rhs: [String]) -> Bool { 18 | return equal(lhs: lhs, rhs: rhs) 19 | } 20 | 21 | public struct Point: Equatable { 22 | var x: Double 23 | var y: Double 24 | 25 | public static func ==(lhs: Point, rhs: Point) -> Bool { 26 | return lhs.x == rhs.x && lhs.y == rhs.y 27 | } 28 | } 29 | 30 | public func pointEqual(lhs: Point, rhs: Point) -> Bool { 31 | return equal(lhs: lhs, rhs: rhs) 32 | } 33 | 34 | public func pointEqualDirect(lhs: Point, rhs: Point) -> Bool { 35 | return lhs == rhs 36 | } 37 | 38 | public func pointNotEqualDirect(lhs: Point, rhs: Point) -> Bool { 39 | return lhs != rhs 40 | } 41 | 42 | public func pointArrayEqual(lhs: [Point], rhs: [Point]) -> Bool { 43 | return equal(lhs: lhs, rhs: rhs) 44 | } 45 | 46 | public func arrayEqual(lhs: [T], rhs: [T]) -> Bool { 47 | return equal(lhs: lhs, rhs: rhs) 48 | } 49 | 50 | -------------------------------------------------------------------------------- /tests/generics/for-in.js: -------------------------------------------------------------------------------- 1 | function $$emptyOptional(T) { 2 | return (T.Object.$rep(T) & 128) !== 0 ? [] : null; 3 | } 4 | 5 | function $$someOptional(T, value) { 6 | return (T.Object.$rep(T) & 128) !== 0 ? [value] : value; 7 | } 8 | 9 | function $$optionalIsSome(T, value) { 10 | return (T.Object.$rep(T) & 128) !== 0 ? value.length !== 0 : value !== null; 11 | } 12 | 13 | function $$unwrapOptional(T, value) { 14 | return (T.Object.$rep(T) & 128) !== 0 ? value[0] : value; 15 | } 16 | 17 | export function sum$array$(T, array) { 18 | let result = (T.Object.$rep(T) & 143) !== 0 ? [T.zero(T)] : T.zero(T); 19 | const iterator = { 20 | elements: array, 21 | position: 0 22 | }; 23 | 24 | for (let element; $$optionalIsSome(T, element = iterator.position === iterator.elements.length ? $$emptyOptional(T) : $$someOptional(T, iterator.elements[iterator.position++]));) { 25 | element = $$unwrapOptional(T, element); 26 | 27 | if ((T.Object.$rep(T) & 143) !== 0) { 28 | result[0] = T.AdditiveArithmetic.$plus$(T, result, element); 29 | } else { 30 | result = T.AdditiveArithmetic.$plus$(T, result, element); 31 | } 32 | } 33 | 34 | return (T.Object.$rep(T) & 143) !== 0 ? result[0] : result; 35 | } -------------------------------------------------------------------------------- /tests/generics/for-in.swift: -------------------------------------------------------------------------------- 1 | public func sum(array: [T]) -> T { 2 | var result = T.zero 3 | for element in array { 4 | result += element 5 | } 6 | return result 7 | } 8 | -------------------------------------------------------------------------------- /tests/generics/negate.swift: -------------------------------------------------------------------------------- 1 | public func negate(number: T) -> T { 2 | return -number 3 | } 4 | 5 | public func negate(integer: Int) -> Int { 6 | return negate(number: integer) 7 | } 8 | 9 | public func negate(double: Double) -> Double { 10 | return negate(number: double) 11 | } 12 | 13 | public func negateDirect(double: Double) -> Double { 14 | return -double 15 | } 16 | -------------------------------------------------------------------------------- /tests/numbers/conversions.js: -------------------------------------------------------------------------------- 1 | function $$numericRangeFailed() { 2 | throw new RangeError("Not enough bits to represent the given value"); 3 | } 4 | 5 | export function makeUInt8$fromUInt16$(value) { 6 | return value > 255 ? $$numericRangeFailed() : value; 7 | } 8 | export function makeInt32$fromInt$(value) { 9 | return value; 10 | } 11 | export function makeInt16$fromInt$(value) { 12 | return value < -32768 || value > 32767 ? $$numericRangeFailed() : value; 13 | } 14 | export function makeClampedInt16$fromInt$(value) { 15 | return value > 32767 ? 32767 : value < -32768 ? -32768 : value; 16 | } 17 | export function makeOptionalUInt8$fromUInt16$(value) { 18 | return value > 255 ? null : value; 19 | } -------------------------------------------------------------------------------- /tests/numbers/conversions.swift: -------------------------------------------------------------------------------- 1 | public func makeUInt8(fromUInt16 value: UInt16) -> UInt8 { 2 | return UInt8(value) 3 | } 4 | 5 | public func makeInt32(fromInt value: Int) -> Int32 { 6 | return Int32(value) 7 | } 8 | 9 | public func makeInt16(fromInt value: Int) -> Int16 { 10 | return Int16(value) 11 | } 12 | 13 | public func makeClampedInt16(fromInt value: Int) -> Int16 { 14 | return Int16(clamping: value) 15 | } 16 | 17 | public func makeOptionalUInt8(fromUInt16 value: UInt16) -> UInt8? { 18 | return UInt8(exactly: value) 19 | } 20 | -------------------------------------------------------------------------------- /tests/numbers/double-increment.js: -------------------------------------------------------------------------------- 1 | export function increment$number$(number) { 2 | return number + 1; 3 | } -------------------------------------------------------------------------------- /tests/numbers/double-increment.swift: -------------------------------------------------------------------------------- 1 | public func increment(number: Double) -> Double { 2 | return number + 1.0 3 | } 4 | -------------------------------------------------------------------------------- /tests/numbers/factorial-recursive.js: -------------------------------------------------------------------------------- 1 | export function factorial_recursive$number$(number) { 2 | if (number <= 1) { 3 | return 1; 4 | } 5 | 6 | return number * factorial_recursive$number$(number - 1); 7 | } -------------------------------------------------------------------------------- /tests/numbers/factorial-recursive.swift: -------------------------------------------------------------------------------- 1 | public func factorial_recursive(number: Int) -> Int { 2 | if (number <= 1) { 3 | return 1 4 | } 5 | return number * factorial_recursive(number: number - 1) 6 | } 7 | -------------------------------------------------------------------------------- /tests/numbers/generic.js: -------------------------------------------------------------------------------- 1 | export function add$lhs$rhs$(T, lhs, rhs) { 2 | return T.AdditiveArithmetic.$plus$(T, lhs, rhs); 3 | } 4 | export function subtract$lhs$rhs$(T, lhs, rhs) { 5 | return T.AdditiveArithmetic.$minus$(T, lhs, rhs); 6 | } 7 | export function double$target$(T, target) { 8 | if ((T.Object.$rep(T) & 143) !== 0) { 9 | target[0] = T.AdditiveArithmetic.$plus$(T, target, (T.Object.$rep(T) & 143) !== 0 ? target[0] : target); 10 | } else { 11 | target = T.AdditiveArithmetic.$plus$(T, target, (T.Object.$rep(T) & 143) !== 0 ? target[0] : target); 12 | } 13 | } 14 | const $Int$Type = { 15 | AdditiveArithmetic: { 16 | $plus$(Self, lhs, rhs) { 17 | return lhs + rhs; 18 | }, 19 | 20 | $minus$(Self, lhs, rhs) { 21 | return lhs - rhs; 22 | }, 23 | 24 | zero(Self) { 25 | return 0; 26 | } 27 | 28 | }, 29 | BinaryInteger: { 30 | $mod$(Self, lhs, rhs) { 31 | return lhs % rhs; 32 | }, 33 | 34 | $and$(Self, lhs, rhs) { 35 | return lhs & rhs; 36 | }, 37 | 38 | $multiply$(Self, lhs, rhs) { 39 | return lhs * rhs; 40 | }, 41 | 42 | $plus$(Self, lhs, rhs) { 43 | return lhs + rhs; 44 | }, 45 | 46 | $minus$(Self, lhs, rhs) { 47 | return lhs - rhs; 48 | }, 49 | 50 | $divide$(Self, lhs, rhs) { 51 | return lhs / rhs | 0; 52 | }, 53 | 54 | $less$(Self, lhs, rhs) { 55 | return lhs < rhs; 56 | }, 57 | 58 | $leftshift$(Self, lhs, rhs) { 59 | return lhs << rhs; 60 | }, 61 | 62 | $lessequal$(Self, lhs, rhs) { 63 | return lhs <= rhs; 64 | }, 65 | 66 | $greater$(Self, lhs, rhs) { 67 | return lhs > rhs; 68 | }, 69 | 70 | $greaterequal$(Self, lhs, rhs) { 71 | return lhs >= rhs; 72 | }, 73 | 74 | $rightshift$(Self, lhs, rhs) { 75 | return lhs >> rhs; 76 | }, 77 | 78 | $xor$(Self, lhs, rhs) { 79 | return lhs ^ rhs; 80 | }, 81 | 82 | init$clamping$(Self, T, value) { 83 | return value > T.SignedInteger.max(T) ? T.SignedInteger.max(T) : value < T.SignedInteger.min(T) ? T.SignedInteger.min(T) : value; 84 | }, 85 | 86 | init$exactly$(Self, T, value) { 87 | return value > T.SignedInteger.min(T) || value < T.SignedInteger.max(T) ? null : value; 88 | }, 89 | 90 | init$truncatingIfNeeded$(Self, source) { 91 | return source | 0; 92 | }, 93 | 94 | isSigned(Self) { 95 | return true; 96 | }, 97 | 98 | quotientAndRemainder$dividingBy$(Self, lhs, rhs) { 99 | return [lhs / rhs | 0, lhs % rhs]; 100 | }, 101 | 102 | signum(Self, self) { 103 | return self > 0 ? 1 : self < 0 ? -1 : self; 104 | }, 105 | 106 | $or$(Self, lhs, rhs) { 107 | return lhs | rhs; 108 | }, 109 | 110 | $tilde$(Self, self) { 111 | return ~self; 112 | } 113 | 114 | }, 115 | Comparable: { 116 | $$$(Self, minimum, maximum) { 117 | return [minimum, maximum]; 118 | }, 119 | 120 | $less$(Self, lhs, rhs) { 121 | return lhs < rhs; 122 | }, 123 | 124 | $lessequal$(Self, lhs, rhs) { 125 | return lhs <= rhs; 126 | }, 127 | 128 | $greater$(Self, lhs, rhs) { 129 | return lhs > rhs; 130 | }, 131 | 132 | $greaterequal$(Self, lhs, rhs) { 133 | return lhs >= rhs; 134 | } 135 | 136 | }, 137 | CustomStringConvertible: { 138 | description(Self, self) { 139 | return String(self); 140 | } 141 | 142 | }, 143 | Equatable: { 144 | $notequals$(Self, lhs, rhs) { 145 | return lhs !== rhs; 146 | }, 147 | 148 | $equals$(Self, lhs, rhs) { 149 | return lhs === rhs; 150 | } 151 | 152 | }, 153 | FixedWidthInteger: { 154 | $and$$multiply$(Self, lhs, rhs) { 155 | return lhs * rhs | 0; 156 | }, 157 | 158 | $and$$plus$(Self, lhs, rhs) { 159 | return lhs + rhs | 0; 160 | }, 161 | 162 | $and$$minus$(Self, lhs, rhs) { 163 | return lhs - rhs | 0; 164 | }, 165 | 166 | $and$$leftshift$(Self, lhs, rhs) { 167 | return lhs << rhs; 168 | }, 169 | 170 | $and$$leftshift$$equal$(Self, lhs, rhs) { 171 | lhs[0] = lhs[0] << rhs; 172 | }, 173 | 174 | $and$$rightshift$(Self, lhs, rhs) { 175 | return lhs >> rhs; 176 | }, 177 | 178 | $and$$rightshift$$equal$(Self, lhs, rhs) { 179 | lhs[0] = lhs[0] >> rhs; 180 | }, 181 | 182 | addingReportingOverflow(Self, lhs, rhs) { 183 | const full = lhs + rhs; 184 | const truncated = full | 0; 185 | return [truncated, truncated !== full]; 186 | }, 187 | 188 | bigEndian(Self, value) { 189 | return value >> 24 & 255 | value >> 8 & 65280 | value << 8 & 16711680 | value << 24; 190 | }, 191 | 192 | bitWidth(Self) { 193 | return 32; 194 | }, 195 | 196 | byteSwapped(Self, value) { 197 | return value >> 24 & 255 | value >> 8 & 65280 | value << 8 & 16711680 | value << 24; 198 | }, 199 | 200 | dividedReportingOverflow$by$(Self, lhs, rhs) { 201 | const full = lhs / rhs | 0; 202 | const truncated = full | 0; 203 | return [truncated, truncated !== full]; 204 | }, 205 | 206 | dividingFullWidth(Self) { 207 | return $$notImplemented(); 208 | }, 209 | 210 | init$radix$(Self, text, radix) { 211 | const integer = parseInt(text, radix); 212 | return integer !== integer ? null : integer; 213 | }, 214 | 215 | init$bigEndian$(Self, value) { 216 | return value >> 24 & 255 | value >> 8 & 65280 | value << 8 & 16711680 | value << 24; 217 | }, 218 | 219 | init$clamping$(Self, $1) { 220 | return $1 > $1.SignedInteger.max($1) ? $1.SignedInteger.max($1) : $1 < $1.SignedInteger.min($1) ? $1.SignedInteger.min($1) : $1; 221 | }, 222 | 223 | init$littleEndian$(Self, value) { 224 | return value; 225 | }, 226 | 227 | leadingZeroBitCount(Self, value) { 228 | let shift = 32; 229 | 230 | while (value >> --shift === 0 && shift >= 0) {} 231 | 232 | return 31 - shift; 233 | }, 234 | 235 | littleEndian(Self, self) { 236 | return self; 237 | }, 238 | 239 | max(Self) { 240 | return 2147483647; 241 | }, 242 | 243 | min(Self) { 244 | return -2147483648; 245 | }, 246 | 247 | multipliedFullWidth$by$(Self, lhs, rhs) { 248 | return [lhs * rhs / 4294967296 | 0, Math.imul(lhs, rhs)]; 249 | }, 250 | 251 | multipliedReportingOverflow$by$(Self, lhs, rhs) { 252 | const full = lhs * rhs; 253 | const truncated = full | 0; 254 | return [truncated, truncated !== full]; 255 | }, 256 | 257 | nonzeroBitCount(Self, value) { 258 | let current = value; 259 | let count = 0; 260 | 261 | while (current) { 262 | count++; 263 | current &= current - 1; 264 | } 265 | 266 | return count; 267 | }, 268 | 269 | remainderReportingOverflow$dividingBy$(Self, lhs, rhs) { 270 | const full = lhs % rhs; 271 | const truncated = full | 0; 272 | return [truncated, truncated !== full]; 273 | }, 274 | 275 | subtractingReportingOverflow(Self, lhs, rhs) { 276 | const full = lhs - rhs; 277 | const truncated = full | 0; 278 | return [truncated, truncated !== full]; 279 | } 280 | 281 | }, 282 | Hashable: { 283 | hash$into$(Self, self, hasher) { 284 | hasher[0] = (hasher[0] << 5) + self - hasher[0]; 285 | }, 286 | 287 | hashValue(Self, self) { 288 | return self; 289 | } 290 | 291 | }, 292 | LosslessStringConvertible: { 293 | init(Self, description) { 294 | const integer = parseInt(description, 10); 295 | return integer !== integer ? null : integer; 296 | } 297 | 298 | }, 299 | Numeric: { 300 | $multiply$(Self, lhs, rhs) { 301 | return lhs * rhs; 302 | }, 303 | 304 | init$exactly$(Self, T, value) { 305 | return value > T.SignedInteger.min(T) || value < T.SignedInteger.max(T) ? null : value; 306 | } 307 | 308 | }, 309 | Object: { 310 | $rep(Self) { 311 | return 4; 312 | } 313 | 314 | }, 315 | SignedInteger: { 316 | $and$$plus$(Self, lhs, rhs) { 317 | return lhs + rhs | 0; 318 | }, 319 | 320 | $and$$minus$(Self, lhs, rhs) { 321 | return lhs - rhs | 0; 322 | }, 323 | 324 | init(Self, T, value) { 325 | return value < T.SignedInteger.min(T) || value > T.SignedInteger.max(T) ? $$numericRangeFailed() : value; 326 | }, 327 | 328 | init$exactly$(Self, T, value) { 329 | return value > T.SignedInteger.min(T) || value < T.SignedInteger.max(T) ? null : value; 330 | }, 331 | 332 | max(Self) { 333 | return 2147483647; 334 | }, 335 | 336 | min(Self) { 337 | return -2147483648; 338 | } 339 | 340 | }, 341 | SignedNumeric: { 342 | $minus$(Self, value) { 343 | return -value; 344 | }, 345 | 346 | negate(Self, self) { 347 | self[0] = -self[0]; 348 | } 349 | 350 | }, 351 | Strideable: { 352 | $plus$(Self, lhs, rhs) { 353 | return lhs + rhs; 354 | }, 355 | 356 | $minus$(Self, lhs, rhs) { 357 | return lhs - rhs; 358 | }, 359 | 360 | $$$(Self, start, end) { 361 | return [start, end]; 362 | }, 363 | 364 | $equals$(Self, lhs, rhs) { 365 | return lhs === rhs; 366 | }, 367 | 368 | advanced$by$(Self, lhs, rhs) { 369 | return lhs + rhs; 370 | }, 371 | 372 | distance$to$(Self, lhs, rhs) { 373 | return rhs - lhs; 374 | } 375 | 376 | } 377 | }; 378 | 379 | function $$notImplemented() { 380 | throw new Error("Not implemented!"); 381 | } 382 | 383 | function $$numericRangeFailed() { 384 | throw new RangeError("Not enough bits to represent the given value"); 385 | } 386 | 387 | export function addInts$lhs$rhs$(lhs, rhs) { 388 | return add$lhs$rhs$($Int$Type, lhs, rhs); 389 | } 390 | export function subtractInts$lhs$rhs$(lhs, rhs) { 391 | return subtract$lhs$rhs$($Int$Type, lhs, rhs); 392 | } 393 | export function double$int$(int) { 394 | double$target$($Int$Type, int); 395 | } 396 | export function double$ofInt$(int) { 397 | const temp = [int]; 398 | double$target$($Int$Type, temp); 399 | return temp[0]; 400 | } -------------------------------------------------------------------------------- /tests/numbers/generic.swift: -------------------------------------------------------------------------------- 1 | public func add(lhs: T, rhs: T) -> T { 2 | return lhs + rhs 3 | } 4 | 5 | public func subtract(lhs: T, rhs: T) -> T { 6 | return lhs - rhs 7 | } 8 | 9 | public func double(target: inout T) { 10 | target += target 11 | } 12 | 13 | public func addInts(lhs: Int, rhs: Int) -> Int { 14 | return add(lhs: lhs, rhs: rhs) 15 | } 16 | 17 | public func subtractInts(lhs: Int, rhs: Int) -> Int { 18 | return subtract(lhs: lhs, rhs: rhs) 19 | } 20 | 21 | public func double(int: inout Int) { 22 | double(target: &int) 23 | } 24 | 25 | public func double(ofInt int: Int) -> Int { 26 | var temp = int 27 | double(target: &temp) 28 | return temp 29 | } 30 | -------------------------------------------------------------------------------- /tests/numbers/inout.js: -------------------------------------------------------------------------------- 1 | export function addOne$to$(int) { 2 | int[0]++; 3 | } 4 | export function incremented$integer$(integer) { 5 | const copy = [integer]; 6 | addOne$to$(copy); 7 | return copy[0]; 8 | } -------------------------------------------------------------------------------- /tests/numbers/inout.swift: -------------------------------------------------------------------------------- 1 | public func addOne(to int: inout Int) { 2 | int += 1 3 | } 4 | 5 | public func incremented(integer: Int) -> Int { 6 | var copy = integer 7 | addOne(to: ©) 8 | return copy 9 | } 10 | -------------------------------------------------------------------------------- /tests/numbers/int-increment-until-zero.js: -------------------------------------------------------------------------------- 1 | function increment$number$(number) { 2 | return number + 1; 3 | } 4 | 5 | export function increment_until_zero$number$(number) { 6 | if (number < 0) { 7 | return increment$number$(number); 8 | } 9 | 10 | return number; 11 | } -------------------------------------------------------------------------------- /tests/numbers/int-increment-until-zero.swift: -------------------------------------------------------------------------------- 1 | func increment(number: Int) -> Int { 2 | return number + 1 3 | } 4 | 5 | public func increment_until_zero(number: Int) -> Int { 6 | if (number < 0) { 7 | return increment(number: number) 8 | } 9 | return number 10 | } 11 | -------------------------------------------------------------------------------- /tests/numbers/int-increment.js: -------------------------------------------------------------------------------- 1 | export function increment$number$(number) { 2 | return number + 1; 3 | } -------------------------------------------------------------------------------- /tests/numbers/int-increment.swift: -------------------------------------------------------------------------------- 1 | public func increment(number: Int) -> Int { 2 | return number + 1 3 | } 4 | -------------------------------------------------------------------------------- /tests/numbers/integer-ranges.js: -------------------------------------------------------------------------------- 1 | export function rangeForUInt8() { 2 | return [0, 255]; 3 | } 4 | export function rangeForInt8() { 5 | return [-128, 127]; 6 | } 7 | export function rangeForUInt() { 8 | return [0, 4294967295]; 9 | } 10 | export function rangeForInt() { 11 | return [-2147483648, 2147483647]; 12 | } 13 | export function rangeForUInt64() { 14 | return [0, 9007199254740991]; 15 | } 16 | export function rangeForInt64() { 17 | return [-9007199254740991, 9007199254740991]; 18 | } -------------------------------------------------------------------------------- /tests/numbers/integer-ranges.swift: -------------------------------------------------------------------------------- 1 | public func rangeForUInt8() -> (min: UInt8, max: UInt8) { 2 | return (UInt8.min, UInt8.max) 3 | } 4 | 5 | public func rangeForInt8() -> (min: Int8, max: Int8) { 6 | return (Int8.min, Int8.max) 7 | } 8 | 9 | 10 | public func rangeForUInt() -> (min: UInt, max: UInt) { 11 | return (UInt.min, UInt.max) 12 | } 13 | 14 | public func rangeForInt() -> (min: Int, max: Int) { 15 | return (Int.min, Int.max) 16 | } 17 | 18 | 19 | public func rangeForUInt64() -> (min: UInt64, max: UInt64) { 20 | return (UInt64.min, UInt64.max) 21 | } 22 | 23 | public func rangeForInt64() -> (min: Int64, max: Int64) { 24 | return (Int64.min, Int64.max) 25 | } 26 | 27 | -------------------------------------------------------------------------------- /tests/numbers/more-silly-math.js: -------------------------------------------------------------------------------- 1 | export function more_silly_math$num$(num) { 2 | const result = [0]; 3 | 4 | if (num < 0) { 5 | result[0] = num; 6 | } else { 7 | result[0] = -num; 8 | } 9 | 10 | return result[0] * 1000 + 4; 11 | } -------------------------------------------------------------------------------- /tests/numbers/more-silly-math.swift: -------------------------------------------------------------------------------- 1 | public func more_silly_math(num: Int) -> Int { 2 | var result: Int 3 | if (num < 0) { 4 | result = num; 5 | } else { 6 | result = -num; 7 | } 8 | return result * 1000 + 4; 9 | } 10 | -------------------------------------------------------------------------------- /tests/numbers/negate.js: -------------------------------------------------------------------------------- 1 | export function negate$number$(number) { 2 | return -number; 3 | } -------------------------------------------------------------------------------- /tests/numbers/negate.swift: -------------------------------------------------------------------------------- 1 | public func negate(number: Int) -> Int { 2 | return -number 3 | } 4 | -------------------------------------------------------------------------------- /tests/numbers/repeat.js: -------------------------------------------------------------------------------- 1 | export function silly_math$num$(num) { 2 | const result = [num]; 3 | 4 | do { 5 | result[0] *= result[0]; 6 | } while (result[0] < 10000); 7 | 8 | return result[0]; 9 | } -------------------------------------------------------------------------------- /tests/numbers/repeat.swift: -------------------------------------------------------------------------------- 1 | public func silly_math(num: Int) -> Int { 2 | var result = num; 3 | repeat { 4 | result *= result; 5 | } while (result < 10000) 6 | return result; 7 | } 8 | -------------------------------------------------------------------------------- /tests/numbers/silly-math.js: -------------------------------------------------------------------------------- 1 | export function silly_math$num$(num) { 2 | const result = [num]; 3 | 4 | while (result[0] < 10000) { 5 | result[0] *= result[0]; 6 | } 7 | 8 | return result[0]; 9 | } -------------------------------------------------------------------------------- /tests/numbers/silly-math.swift: -------------------------------------------------------------------------------- 1 | public func silly_math(num: Int) -> Int { 2 | var result = num; 3 | while (result < 10000) { 4 | result *= result; 5 | } 6 | return result; 7 | } 8 | -------------------------------------------------------------------------------- /tests/numbers/wrapped.js: -------------------------------------------------------------------------------- 1 | export function factorial_recursive$number$(number) { 2 | if (number <= 1) { 3 | return 1; 4 | } 5 | 6 | return number * factorial_recursive$number$(number - 1 | 0) | 0; 7 | } -------------------------------------------------------------------------------- /tests/numbers/wrapped.swift: -------------------------------------------------------------------------------- 1 | public func factorial_recursive(number: Int) -> Int { 2 | if (number <= 1) { 3 | return 1 4 | } 5 | return number &* factorial_recursive(number: number &- 1) 6 | } 7 | -------------------------------------------------------------------------------- /tests/optionals/chaining.js: -------------------------------------------------------------------------------- 1 | export function length$of$(string) { 2 | return string !== null ? string.length : null; 3 | } -------------------------------------------------------------------------------- /tests/optionals/chaining.swift: -------------------------------------------------------------------------------- 1 | public func length(of string: String?) -> Int? { 2 | return string?.utf16.count 3 | } 4 | -------------------------------------------------------------------------------- /tests/optionals/coalesce.js: -------------------------------------------------------------------------------- 1 | export function coalesce$option$(option) { 2 | return option !== null ? option : false; 3 | } -------------------------------------------------------------------------------- /tests/optionals/coalesce.swift: -------------------------------------------------------------------------------- 1 | public func coalesce(option: Bool?) -> Bool { 2 | return option ?? false 3 | } 4 | -------------------------------------------------------------------------------- /tests/optionals/compare.js: -------------------------------------------------------------------------------- 1 | export function checkEqual$singleOptional$with$(singleOptional, other) { 2 | return singleOptional === other; 3 | } 4 | export function checkEqual$doubleOptional$with$(doubleOptional, other) { 5 | return doubleOptional.length === 0 ? other.length === 0 : other.length !== 0 && doubleOptional[0] === other[0]; 6 | } 7 | export function checkNotEqual$singleOptional$with$(singleOptional, other) { 8 | return singleOptional !== other; 9 | } 10 | export function checkNotEqual$doubleOptional$with$(doubleOptional, other) { 11 | return doubleOptional.length === 0 ? other.length !== 0 : other.length === 0 || doubleOptional[0] !== other[0]; 12 | } -------------------------------------------------------------------------------- /tests/optionals/compare.swift: -------------------------------------------------------------------------------- 1 | public func checkEqual(singleOptional: Bool?, with other:Bool?) -> Bool { 2 | return singleOptional == other 3 | } 4 | 5 | public func checkEqual(doubleOptional: Bool??, with other:Bool??) -> Bool { 6 | return doubleOptional == other 7 | } 8 | 9 | public func checkNotEqual(singleOptional: Bool?, with other:Bool?) -> Bool { 10 | return singleOptional != other 11 | } 12 | 13 | public func checkNotEqual(doubleOptional: Bool??, with other:Bool??) -> Bool { 14 | return doubleOptional != other 15 | } 16 | -------------------------------------------------------------------------------- /tests/optionals/double.js: -------------------------------------------------------------------------------- 1 | export function optional_from$num$(num) { 2 | if (num > 0) { 3 | return [true]; 4 | } else { 5 | if (num === 0) { 6 | return [null]; 7 | } 8 | } 9 | 10 | return []; 11 | } -------------------------------------------------------------------------------- /tests/optionals/double.swift: -------------------------------------------------------------------------------- 1 | public func optional_from(num: Int) -> Bool?? { 2 | if (num > 0) { 3 | return true 4 | } else if (num == 0) { 5 | return .some(.none) 6 | } 7 | return .none 8 | } 9 | -------------------------------------------------------------------------------- /tests/optionals/extract-double.js: -------------------------------------------------------------------------------- 1 | export function description_of_double$option$(option) { 2 | if (option.length !== 0) { 3 | const unwrapped = option[0]; 4 | 5 | if (unwrapped !== null) { 6 | const doubleUnwrapped = unwrapped; 7 | 8 | if (doubleUnwrapped) { 9 | return "True"; 10 | } 11 | 12 | return "False"; 13 | } 14 | 15 | return "Inner None"; 16 | } 17 | 18 | return "Outer None"; 19 | } -------------------------------------------------------------------------------- /tests/optionals/extract-double.swift: -------------------------------------------------------------------------------- 1 | public func description_of_double(option: Bool??) -> String { 2 | if let unwrapped = option { 3 | if let doubleUnwrapped = unwrapped { 4 | if doubleUnwrapped { 5 | return "True" 6 | } 7 | return "False" 8 | } 9 | return "Inner None" 10 | } 11 | return "Outer None" 12 | } 13 | -------------------------------------------------------------------------------- /tests/optionals/extract-single.js: -------------------------------------------------------------------------------- 1 | export function description_of$option$(option) { 2 | if (option !== null) { 3 | const unwrapped = option; 4 | 5 | if (unwrapped) { 6 | return "True"; 7 | } 8 | 9 | return "False"; 10 | } 11 | 12 | return "None"; 13 | } -------------------------------------------------------------------------------- /tests/optionals/extract-single.swift: -------------------------------------------------------------------------------- 1 | public func description_of(option: Bool?) -> String { 2 | if let unwrapped = option { 3 | if unwrapped { 4 | return "True" 5 | } 6 | return "False" 7 | } 8 | return "None" 9 | } 10 | -------------------------------------------------------------------------------- /tests/optionals/force-extract.js: -------------------------------------------------------------------------------- 1 | function $$forceUnwrapFailed() { 2 | throw new TypeError("Unexpectedly found nil while unwrapping an Optional value"); 3 | } 4 | 5 | export function force_unwrap$option$(option) { 6 | return option !== null ? option : $$forceUnwrapFailed(); 7 | } 8 | export function force_unwrap$doubleOption$(option) { 9 | return option.length !== 0 ? option[0] : $$forceUnwrapFailed(); 10 | } 11 | export function force_unwrap$allTheWay$(option) { 12 | const optional = option.length !== 0 ? option[0] : $$forceUnwrapFailed(); 13 | return optional !== null ? optional : $$forceUnwrapFailed(); 14 | } -------------------------------------------------------------------------------- /tests/optionals/force-extract.swift: -------------------------------------------------------------------------------- 1 | public func force_unwrap(option: Bool?) -> Bool { 2 | return option! 3 | } 4 | 5 | public func force_unwrap(doubleOption option: Bool??) -> Bool? { 6 | return option! 7 | } 8 | 9 | public func force_unwrap(allTheWay option: Bool??) -> Bool { 10 | return option!! 11 | } 12 | -------------------------------------------------------------------------------- /tests/optionals/from-int.js: -------------------------------------------------------------------------------- 1 | export function optional_from$num$(num) { 2 | if (num > 0) { 3 | return true; 4 | } else { 5 | if (num === 0) { 6 | return false; 7 | } 8 | } 9 | 10 | return null; 11 | } -------------------------------------------------------------------------------- /tests/optionals/from-int.swift: -------------------------------------------------------------------------------- 1 | public func optional_from(num: Int) -> Bool? { 2 | if (num > 0) { 3 | return true 4 | } else if (num == 0) { 5 | return false 6 | } 7 | return .none 8 | } 9 | -------------------------------------------------------------------------------- /tests/optionals/has-value.js: -------------------------------------------------------------------------------- 1 | export function has_value$option$(option) { 2 | return option !== null; 3 | } -------------------------------------------------------------------------------- /tests/optionals/has-value.swift: -------------------------------------------------------------------------------- 1 | public func has_value(option: Bool?) -> Bool { 2 | return option != nil 3 | } 4 | -------------------------------------------------------------------------------- /tests/optionals/nil-literals.js: -------------------------------------------------------------------------------- 1 | export function single_optional() { 2 | return null; 3 | } 4 | export function double_optional() { 5 | return []; 6 | } -------------------------------------------------------------------------------- /tests/optionals/nil-literals.swift: -------------------------------------------------------------------------------- 1 | public func single_optional() -> Bool? { 2 | return nil 3 | } 4 | 5 | public func double_optional() -> Bool?? { 6 | return nil 7 | } 8 | -------------------------------------------------------------------------------- /tests/strings/codepoint-length.js: -------------------------------------------------------------------------------- 1 | export function utf32_length$str$(str) { 2 | return Array.from(str).length; 3 | } -------------------------------------------------------------------------------- /tests/strings/codepoint-length.swift: -------------------------------------------------------------------------------- 1 | public func utf32_length(str: String) -> Int { 2 | return str.unicodeScalars.count; 3 | } 4 | -------------------------------------------------------------------------------- /tests/strings/concat.js: -------------------------------------------------------------------------------- 1 | export function concat$l$r$(l, r) { 2 | return l + r; 3 | } -------------------------------------------------------------------------------- /tests/strings/concat.swift: -------------------------------------------------------------------------------- 1 | public func concat(l: String, r: String) -> String { 2 | return l + r 3 | } 4 | -------------------------------------------------------------------------------- /tests/strings/encoded-length.js: -------------------------------------------------------------------------------- 1 | export function utf8_length$str$(str) { 2 | return new TextEncoder("utf-8").encode(str).length; 3 | } -------------------------------------------------------------------------------- /tests/strings/encoded-length.swift: -------------------------------------------------------------------------------- 1 | public func utf8_length(str: String) -> Int { 2 | return str.utf8.count; 3 | } 4 | -------------------------------------------------------------------------------- /tests/strings/hash.js: -------------------------------------------------------------------------------- 1 | export function hash$of$(str) { 2 | let hash = 0; 3 | 4 | for (let i = 0; i < str.length; i++) { 5 | hash = (hash << 5) + str.charCodeAt(i) - hash; 6 | } 7 | 8 | return hash | 0; 9 | } -------------------------------------------------------------------------------- /tests/strings/hash.swift: -------------------------------------------------------------------------------- 1 | public func hash(of str: String) -> Int { 2 | return str.hashValue; 3 | } 4 | -------------------------------------------------------------------------------- /tests/strings/interpolated.js: -------------------------------------------------------------------------------- 1 | export function interpolate$multiplier$(multiplier) { 2 | return `${multiplier} times 2.5 is ${multiplier * 2.5}`; 3 | } -------------------------------------------------------------------------------- /tests/strings/interpolated.swift: -------------------------------------------------------------------------------- 1 | public func interpolate(multiplier: Int) -> String { 2 | return "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)" 3 | } 4 | -------------------------------------------------------------------------------- /tests/strings/lowercase.js: -------------------------------------------------------------------------------- 1 | export function lowercase$ofString$(str) { 2 | return str.toLowerCase(); 3 | } -------------------------------------------------------------------------------- /tests/strings/lowercase.swift: -------------------------------------------------------------------------------- 1 | public func lowercase(ofString str: String) -> String { 2 | return str.lowercased() 3 | } 4 | -------------------------------------------------------------------------------- /tests/strings/parse-bool.js: -------------------------------------------------------------------------------- 1 | export function parseBoolean$fromString$(str) { 2 | return str === "True" || str !== "False" && null; 3 | } 4 | export function parseBooleanTrue() { 5 | return true; 6 | } 7 | export function parseBooleanFalse() { 8 | return false; 9 | } 10 | export function parseBooleanElse() { 11 | return null; 12 | } -------------------------------------------------------------------------------- /tests/strings/parse-bool.swift: -------------------------------------------------------------------------------- 1 | public func parseBoolean(fromString str: String) -> Bool? { 2 | return Bool(str) 3 | } 4 | 5 | public func parseBooleanTrue() -> Bool? { 6 | return Bool("True") 7 | } 8 | 9 | public func parseBooleanFalse() -> Bool? { 10 | return Bool("False") 11 | } 12 | 13 | public func parseBooleanElse() -> Bool? { 14 | return Bool("Else") 15 | } 16 | -------------------------------------------------------------------------------- /tests/strings/print.js: -------------------------------------------------------------------------------- 1 | console.log("hello world"); -------------------------------------------------------------------------------- /tests/strings/print.swift: -------------------------------------------------------------------------------- 1 | print("hello world") 2 | -------------------------------------------------------------------------------- /tests/strings/static-string.js: -------------------------------------------------------------------------------- 1 | export function hello_world() { 2 | return "Hello World!"; 3 | } -------------------------------------------------------------------------------- /tests/strings/static-string.swift: -------------------------------------------------------------------------------- 1 | public func hello_world() -> String { 2 | return "Hello World!" 3 | } 4 | -------------------------------------------------------------------------------- /tests/strings/string-length.js: -------------------------------------------------------------------------------- 1 | export function string_length$str$(str) { 2 | return str.length; 3 | } -------------------------------------------------------------------------------- /tests/strings/string-length.swift: -------------------------------------------------------------------------------- 1 | public func string_length(str: String) -> Int { 2 | return str.utf16.count; 3 | } 4 | -------------------------------------------------------------------------------- /tests/strings/unicode-literal.js: -------------------------------------------------------------------------------- 1 | export function unicode() { 2 | return "backslash: <\\>\ntab: <\t>\ncarriage return: <\r>\nnewline: <\n>\ndouble quote: <\">\nsingle quote: <'>\nnull: <\0>\nshrug: <\uD83E\uDD37\u200D>\ndel: <\x7F>\nack: <\x06>\ndegree: <\xB0>"; 3 | } -------------------------------------------------------------------------------- /tests/strings/unicode-literal.swift: -------------------------------------------------------------------------------- 1 | public func unicode() -> String { 2 | return """ 3 | backslash: <\\> 4 | tab: < > 5 | carriage return: <\r> 6 | newline: <\n> 7 | double quote: <\"> 8 | single quote: <'> 9 | null: <\0> 10 | shrug: <🤷‍> 11 | del: <\u{7F}> 12 | ack: <\u{06}> 13 | degree: <\u{00B0}> 14 | """ 15 | } 16 | -------------------------------------------------------------------------------- /tests/strings/uppercase.js: -------------------------------------------------------------------------------- 1 | export function uppercase$ofString$(str) { 2 | return str.toUpperCase(); 3 | } -------------------------------------------------------------------------------- /tests/strings/uppercase.swift: -------------------------------------------------------------------------------- 1 | public func uppercase(ofString str: String) -> String { 2 | return str.uppercased() 3 | } 4 | -------------------------------------------------------------------------------- /tests/struct/computed-getter.js: -------------------------------------------------------------------------------- 1 | export function isEmpty$size$(size) { 2 | return self.width === 0 && self.height === 0; 3 | } -------------------------------------------------------------------------------- /tests/struct/computed-getter.swift: -------------------------------------------------------------------------------- 1 | public struct Size { 2 | var width: Double 3 | var height: Double 4 | var isEmpty: Bool { 5 | return width == 0 && height == 0 6 | } 7 | } 8 | 9 | public func isEmpty(size: Size) -> Bool { 10 | return size.isEmpty 11 | } 12 | -------------------------------------------------------------------------------- /tests/struct/copy-and-edit.js: -------------------------------------------------------------------------------- 1 | export function pointOffsetFromOrigin$x$y$(x, y) { 2 | let result = { 3 | x: origin.x, 4 | y: origin.y 5 | }; 6 | result.x = result.x + x; 7 | result.y = result.y + y; 8 | return result; 9 | } 10 | let origin = { 11 | x: 0, 12 | y: 0 13 | }; -------------------------------------------------------------------------------- /tests/struct/copy-and-edit.swift: -------------------------------------------------------------------------------- 1 | public struct Point { 2 | var x: Double 3 | var y: Double 4 | } 5 | 6 | var origin = Point(x: 0, y: 0) 7 | 8 | public func pointOffsetFromOrigin(x: Double, y: Double) -> Point { 9 | var result = origin 10 | result.x += x 11 | result.y += y 12 | return result 13 | } 14 | -------------------------------------------------------------------------------- /tests/struct/distance.js: -------------------------------------------------------------------------------- 1 | export function distance$first$second$(first, second) { 2 | const _x = first.x - second.x; 3 | 4 | const _y = first.y - second.y; 5 | 6 | const delta = { 7 | x: _x, 8 | y: _y 9 | }; 10 | return Math.sqrt(delta.x * delta.x + delta.y * delta.y); 11 | } -------------------------------------------------------------------------------- /tests/struct/distance.swift: -------------------------------------------------------------------------------- 1 | public struct Point { 2 | var x: Double 3 | var y: Double 4 | public init() { 5 | x = 0 6 | y = 0 7 | } 8 | public init(x _x: Double, y _y: Double) { 9 | x = _x 10 | y = _y 11 | } 12 | var isOrigin: Bool { 13 | get { 14 | return x == 0 && y == 0 15 | } 16 | } 17 | } 18 | 19 | public func distance(first: Point, second: Point) -> Double { 20 | let delta = Point(x: first.x - second.x, y: first.y - second.y) 21 | return (delta.x * delta.x + delta.y * delta.y).squareRoot() 22 | } 23 | -------------------------------------------------------------------------------- /tests/struct/in-array.js: -------------------------------------------------------------------------------- 1 | export function makeSizes$w1$h1$w2$h2$(w1, h1, w2, h2) { 2 | return [{ 3 | width: w1, 4 | height: h1 5 | }, { 6 | width: w2, 7 | height: h2 8 | }]; 9 | } 10 | export function countSizes$sizes$(sizes) { 11 | return sizes.length; 12 | } 13 | export function copySizes$sizes$(sizes) { 14 | return sizes.map(function (value) { 15 | return { 16 | width: value.width, 17 | height: value.height 18 | }; 19 | }); 20 | } -------------------------------------------------------------------------------- /tests/struct/in-array.swift: -------------------------------------------------------------------------------- 1 | public struct Size { 2 | var width: Double 3 | var height: Double 4 | public init(width w: Double, height h: Double) { 5 | width = w 6 | height = h 7 | } 8 | } 9 | 10 | public func makeSizes(w1: Double, h1: Double, w2: Double, h2: Double) -> [Size] { 11 | return [Size(width: w1, height: h1), Size(width: w2, height: h2)] 12 | } 13 | 14 | public func countSizes(sizes: [Size]) -> Int { 15 | return sizes.count 16 | } 17 | 18 | public func copySizes(sizes: [Size]) -> [Size] { 19 | return sizes 20 | } 21 | -------------------------------------------------------------------------------- /tests/struct/in-tuple.js: -------------------------------------------------------------------------------- 1 | export function makeSizes$w1$h1$w2$h2$(w1, h1, w2, h2) { 2 | return [{ 3 | width: w1, 4 | height: h1 5 | }, { 6 | width: w2, 7 | height: h2 8 | }]; 9 | } 10 | export function sumSizes$sizes$(sizes) { 11 | const w = sizes[0].width + sizes[1].height; 12 | const h = sizes[0].height + sizes[1].height; 13 | return { 14 | width: w, 15 | height: h 16 | }; 17 | } 18 | export function copySizes$sizes$(sizes) { 19 | return [{ 20 | width: sizes[0].width, 21 | height: sizes[0].height 22 | }, { 23 | width: sizes[1].width, 24 | height: sizes[1].height 25 | }]; 26 | } -------------------------------------------------------------------------------- /tests/struct/in-tuple.swift: -------------------------------------------------------------------------------- 1 | public struct Size { 2 | var width: Double 3 | var height: Double 4 | public init(width w: Double, height h: Double) { 5 | width = w 6 | height = h 7 | } 8 | } 9 | 10 | public func makeSizes(w1: Double, h1: Double, w2: Double, h2: Double) -> (Size, Size) { 11 | return (Size(width: w1, height: h1), Size(width: w2, height: h2)) 12 | } 13 | 14 | public func sumSizes(sizes: (Size, Size)) -> Size { 15 | return Size(width: sizes.0.width + sizes.1.height, height: sizes.0.height + sizes.1.height) 16 | } 17 | 18 | public func copySizes(sizes: (Size, Size)) -> (Size, Size) { 19 | return sizes 20 | } 21 | -------------------------------------------------------------------------------- /tests/struct/make-struct.js: -------------------------------------------------------------------------------- 1 | export function makeSize$w$h$(w, h) { 2 | return { 3 | width: w, 4 | height: h 5 | }; 6 | } -------------------------------------------------------------------------------- /tests/struct/make-struct.swift: -------------------------------------------------------------------------------- 1 | public struct Size { 2 | var width: Double 3 | var height: Double 4 | public init(width w: Double, height h: Double) { 5 | width = w 6 | height = h 7 | } 8 | } 9 | 10 | public func makeSize(w: Double, h: Double) -> Size { 11 | return Size(width: w, height: h) 12 | } 13 | -------------------------------------------------------------------------------- /tests/struct/var.js: -------------------------------------------------------------------------------- 1 | export let origin = { 2 | x: 0, 3 | y: 0 4 | }; -------------------------------------------------------------------------------- /tests/struct/var.swift: -------------------------------------------------------------------------------- 1 | public struct Point { 2 | var x: Double 3 | var y: Double 4 | public init() { 5 | x = 0 6 | y = 0 7 | } 8 | } 9 | 10 | public var origin = Point() 11 | -------------------------------------------------------------------------------- /tests/tuples/binary-calculation.js: -------------------------------------------------------------------------------- 1 | export function distanceToZero$ofPoint$(point) { 2 | const x = point[0], 3 | y = point[1]; 4 | return Math.sqrt(x * x + y * y); 5 | } -------------------------------------------------------------------------------- /tests/tuples/binary-calculation.swift: -------------------------------------------------------------------------------- 1 | public func distanceToZero(ofPoint point:(Double, Double)) -> Double { 2 | let (x, y) = point 3 | return (x * x + y * y).squareRoot() 4 | } 5 | -------------------------------------------------------------------------------- /tests/tuples/binary-init.js: -------------------------------------------------------------------------------- 1 | export let tuplePoint = [0, 0]; -------------------------------------------------------------------------------- /tests/tuples/binary-init.swift: -------------------------------------------------------------------------------- 1 | public var tuplePoint: (Double, Double) = (0, 0) 2 | -------------------------------------------------------------------------------- /tests/tuples/binary-pattern.js: -------------------------------------------------------------------------------- 1 | export const x = 0; 2 | export const y = 0; -------------------------------------------------------------------------------- /tests/tuples/binary-pattern.swift: -------------------------------------------------------------------------------- 1 | public let (x, y): (Double, Double) = (0, 0) 2 | -------------------------------------------------------------------------------- /tests/tuples/empty-init.js: -------------------------------------------------------------------------------- 1 | export const emptyInit; -------------------------------------------------------------------------------- /tests/tuples/empty-init.swift: -------------------------------------------------------------------------------- 1 | public let emptyInit: () = () 2 | -------------------------------------------------------------------------------- /tests/tuples/make-binary.js: -------------------------------------------------------------------------------- 1 | export function makeTuplePoint$x$y$(x, y) { 2 | return [x, y]; 3 | } -------------------------------------------------------------------------------- /tests/tuples/make-binary.swift: -------------------------------------------------------------------------------- 1 | public func makeTuplePoint(x: Double, y: Double) -> (Double, Double) { 2 | return (x, y) 3 | } 4 | -------------------------------------------------------------------------------- /tests/tuples/make-unary.js: -------------------------------------------------------------------------------- 1 | export function makeUnary$of$(value) { 2 | return value; 3 | } -------------------------------------------------------------------------------- /tests/tuples/make-unary.swift: -------------------------------------------------------------------------------- 1 | public func makeUnary(of value: Double) -> (Double) { 2 | return (value) 3 | } 4 | -------------------------------------------------------------------------------- /tests/tuples/read-binary-first.js: -------------------------------------------------------------------------------- 1 | export function readX$fromTuplePoint$(point) { 2 | return point[0]; 3 | } -------------------------------------------------------------------------------- /tests/tuples/read-binary-first.swift: -------------------------------------------------------------------------------- 1 | public func readX(fromTuplePoint point:(Double, Double)) -> Double { 2 | return point.0 3 | } 4 | -------------------------------------------------------------------------------- /tests/tuples/read-binary-pattern.js: -------------------------------------------------------------------------------- 1 | export function readX$fromTuplePoint$(point) { 2 | const x = point[0]; 3 | return x; 4 | } -------------------------------------------------------------------------------- /tests/tuples/read-binary-pattern.swift: -------------------------------------------------------------------------------- 1 | public func readX(fromTuplePoint point:(Double, Double)) -> Double { 2 | let (x, _) = point 3 | return x 4 | } 5 | -------------------------------------------------------------------------------- /tests/tuples/read-binary-second.js: -------------------------------------------------------------------------------- 1 | export function readY$fromTuplePoint$(point) { 2 | return point[1]; 3 | } -------------------------------------------------------------------------------- /tests/tuples/read-binary-second.swift: -------------------------------------------------------------------------------- 1 | public func readY(fromTuplePoint point:(Double, Double)) -> Double { 2 | return point.1 3 | } 4 | -------------------------------------------------------------------------------- /tests/tuples/unary-init.js: -------------------------------------------------------------------------------- 1 | export const unaryInit = 0; -------------------------------------------------------------------------------- /tests/tuples/unary-init.swift: -------------------------------------------------------------------------------- 1 | public let unaryInit: (Double) = (0) 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "lib": ["ES2015"], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | "inlineSourceMap": true, 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "removeComments": true, /* Do not emit comments to output. */ 19 | // "noEmit": true, /* Do not emit outputs. */ 20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 22 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 23 | 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [], /* List of folders to include type definitions from. */ 45 | "types": ["node", "jest"], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": false /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | 50 | /* Source Map Options */ 51 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 59 | } 60 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "arrow-parens": true, 5 | "arrow-return-shorthand": true, 6 | "ban-types": [true, ["Object", "Use {} instead."], ["String"], ["Number"]], 7 | "curly": true, 8 | "forin": true, 9 | "indent": [true, "tabs", 4], 10 | "max-line-length": { 11 | "options": { 12 | "limit": 0, 13 | "ignore-pattern": "^import |^export {(.*?)}" 14 | } 15 | }, 16 | "new-parens": true, 17 | "no-angle-bracket-type-assertion": true, 18 | "no-boolean-literal-compare": true, 19 | "no-irregular-whitespace": true, 20 | "no-any": true, 21 | "no-arg": true, 22 | "no-bitwise": false, 23 | "no-conditional-assignment": true, 24 | "no-consecutive-blank-lines": false, 25 | "no-console": false, 26 | "no-default-export": true, 27 | "no-duplicate-imports": true, 28 | "no-for-in-array": true, 29 | "no-implicit-dependencies": true, 30 | "no-invalid-template-strings": true, 31 | "no-string-throw": true, 32 | "no-unsafe-finally": true, 33 | "no-unused-expression": true, 34 | "no-void-expression": true, 35 | "radix": true, 36 | "restrict-plus-operands": true, 37 | "object-literal-sort-keys": false, 38 | "one-line": [true, "check-catch", "check-finally", "check-else", "check-open-brace", "check-whitespace"], 39 | "one-variable-per-declaration": [true, "ignore-for-loop"], 40 | "ordered-imports": [true, {"named-imports-order":"lowercase-first"}], 41 | "prefer-object-spread": true, 42 | "prefer-const": true, 43 | "semicolon": true, 44 | "space-before-function-paren": [true, { "named": "never", "anonymous": "never", "method": "never", "constructor": "never", "asyncArrow": "always" }], 45 | "trailing-comma": [true, { "multiline": "always", "singleline": "never" }], 46 | "interface-name": [true, "never-prefix"], 47 | "quotemark": [true, "double", "avoid-escape"] 48 | }, 49 | "jsRules": { 50 | "max-line-length": { 51 | "options": [0] 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | export function parse(text: string): Type; 2 | 3 | export type Type = Function | Optional | MetaType | Generic | Dictionary | Array | Tuple | Modified | Name | Namespaced | Constrained; 4 | 5 | export interface Optional { 6 | kind: "optional"; 7 | type: Type; 8 | location?: Location; 9 | } 10 | 11 | export interface Generic { 12 | kind: "generic"; 13 | base: Type; 14 | arguments: Type[]; 15 | location?: Location; 16 | } 17 | 18 | export interface Function { 19 | kind: "function"; 20 | arguments: Tuple; 21 | return: Type; 22 | throws: boolean; 23 | rethrows: boolean; 24 | attributes: string[] 25 | location?: Location; 26 | } 27 | 28 | export interface Tuple { 29 | kind: "tuple"; 30 | types: Type[]; 31 | location?: Location; 32 | } 33 | 34 | export interface Array { 35 | kind: "array"; 36 | type: Type; 37 | location?: Location; 38 | } 39 | 40 | export interface Dictionary { 41 | kind: "dictionary"; 42 | keyType: Type; 43 | valueType: Type; 44 | location?: Location; 45 | } 46 | 47 | export interface MetaType { 48 | kind: "metatype"; 49 | base: Type; 50 | as: "Type" | "Protocol" 51 | location?: Location; 52 | } 53 | 54 | export interface Modified { 55 | kind: "modified"; 56 | modifier: string; 57 | type: Type; 58 | location?: Location; 59 | } 60 | 61 | export interface Name { 62 | kind: "name"; 63 | name: string; 64 | location?: Location; 65 | } 66 | 67 | export interface Namespaced { 68 | kind: "namespaced"; 69 | namespace: Generic | Name; 70 | type: Type; 71 | location?: Location; 72 | } 73 | 74 | export interface Constrained { 75 | kind: "constrained"; 76 | type: Type; 77 | constraint: Type; 78 | location?: Location; 79 | } 80 | 81 | 82 | export interface Location { 83 | start: Position; 84 | end: Position; 85 | } 86 | 87 | export interface Position { 88 | offset: number; 89 | line: number; 90 | column: number; 91 | } 92 | -------------------------------------------------------------------------------- /types.pegjs: -------------------------------------------------------------------------------- 1 | Top 2 | = _ type:Type _ { return type; } 3 | 4 | Type "type" 5 | = Constrained / GenericFunction / Function / NamespacedType / MetaType / Optional / Generic / Dictionary / Array / Tuple / Modified / Void / Name 6 | 7 | Types "type list" 8 | = head:Type tail:CommaType* { return [head].concat(tail) } 9 | CommaType 10 | = ',' _ value:Type { return value; } 11 | 12 | Optional "optional" 13 | = type:(Constrained / GenericFunction / Function / NamespacedType / Generic / Dictionary / Array / Tuple / Modified / Name) depth:[?!]+ { return depth.reduce(function (type) { return { kind: "optional", type: type, location: location() } }, type); } 14 | 15 | Generic "generic" 16 | = base:Name '<' typeArgs:TypesWithConstraints '>' { 17 | if (base.kind === "name") { 18 | if (base.name === "Array" && typeArgs.length === 1) { 19 | return { kind: "array", type: typeArgs[0], location: location() }; 20 | } 21 | if (base.name === "Dictionary" && typeArgs.length === 2) { 22 | return { kind: "dictionary", keyType: typeArgs[0], valueType: typeArgs[1], location: location() }; 23 | } 24 | } 25 | return { kind: "generic", base: base, arguments: typeArgs, location: location() }; 26 | } 27 | 28 | TypesWithConstraints 29 | = head:(TypeWithConstraint / Type) tail:CommaTypeWithConstraint* { return [head].concat(tail) } 30 | CommaTypeWithConstraint 31 | = ',' _ value:(TypeWithConstraint / Type) { return value; } 32 | 33 | TypeWithConstraint 34 | = type:Type ' ' _ ':' _ constraint:Type { 35 | return { kind: "constrained", type: type, constraint: constraint, location: location() }; 36 | } 37 | 38 | GenericFunction "generic function" 39 | = '<' typeArgs:Types '> ' fn:Function { 40 | return { kind: "generic", base: fn, arguments: typeArgs, location: location() } 41 | } 42 | 43 | Constrained "constrainted name" 44 | = type:Name ' ' _ 'where ' _ Name _ ':' _ constraint:Type { 45 | return { kind: "constrained", type: type, constraint: constraint, location: location() }; 46 | } 47 | 48 | Function "function" 49 | = attributes:FunctionAttribute* argTypes:Tuple _ throws:('throws' / "rethrows")? _ '->' _ returnType:Type { return { kind: "function", arguments: argTypes, return: returnType, throws: throws === "throws", rethrows: throws === "rethrows", attributes: attributes, location: location() }; } 50 | FunctionAttribute "attribute" 51 | = content:('@autoclosure' / ('@convention(' ('swift' / 'block' / 'c') ')') / '@escaping') _ { return content; } 52 | 53 | Tuple "tuple" 54 | = '(' types:(TupleContents / _) ')' { return { kind: "tuple", types: types, location: location() }; } 55 | TupleContents 56 | = _ (Name ':' _)? head:(Variadic / Type) tail:TupleTerm* _ { return typeof tail !== "undefined" ? [head].concat(tail) : [head]; } 57 | TupleTerm 58 | = _ ',' _ (Name ':' _)? type:(Variadic / Type) { return type; } 59 | 60 | Variadic "variadic" 61 | = type:Type '...' { return { kind: "array", type: type, location: location() } } 62 | 63 | Array "array" 64 | = '[' _ type:Type _ ']' { return { kind: "array", type: type, location: location() }; } 65 | 66 | Dictionary "dictionary" 67 | = '[' _ keyType:Type _ ':' _ valueType:Type _ ']' { return { kind: "dictionary", keyType: keyType, valueType: valueType, location: location() }; } 68 | 69 | MetaType "metatype" 70 | = base:(Generic / Optional / Name) '.' as:("Type" / "Protocol") { return { kind: "metatype", base: base, as: as, location: location() }; } 71 | 72 | NamespacedType "namespaced type" 73 | = namespace:(Generic / Name) '.' !('Type' / 'Protocol') type:Type { return { kind: "namespaced", namespace: namespace, type: type, location: location() }; } 74 | 75 | Modified "modifier" 76 | = modifier:("inout" / "@lvalue" / "__owned" / "__shared") " " _ type:Type { return { kind: "modified", modifier: modifier, type: type, location: location() }; } 77 | 78 | Name "name" 79 | = head:[a-zA-Z_] tail:[a-zA-Z0-9_\-]* { return { kind: "name", name: head + tail.join(""), location: location() }; } 80 | 81 | Void "void" 82 | = "Void" { return { kind: "tuple", types: [] } } 83 | 84 | _ "whitespace" 85 | = [ \t\n\r]* { return []; } 86 | -------------------------------------------------------------------------------- /utils.ts: -------------------------------------------------------------------------------- 1 | export function expectLength>(array: T, ...lengths: number[]) { 2 | for (const length of lengths) { 3 | if (array.length === length) { 4 | return; 5 | } 6 | } 7 | console.error(array); 8 | throw new Error(`Expected ${lengths.join(" or ")} items, but got ${array.length}`); 9 | } 10 | 11 | export function concat(first: T[], ...rest: T[][]): T[]; 12 | export function concat(first: ReadonlyArray, ...rest: Array>): ReadonlyArray; 13 | export function concat(first: ReadonlyArray, ...rest: Array>): ReadonlyArray { 14 | let result = first; 15 | for (const other of rest) { 16 | if (other.length !== 0) { 17 | result = result.length !== 0 ? result.concat(other) : other; 18 | } 19 | } 20 | return result; 21 | } 22 | 23 | export function lookupForMap(map: { readonly [key: string]: V }): (key: string) => V | undefined { 24 | return (key: string) => Object.hasOwnProperty.call(map, key) ? map[key] : undefined; 25 | } 26 | 27 | function toLowerCase(text: string): string { 28 | return text.toLowerCase(); 29 | } 30 | 31 | export function camelCase(text: string): string { 32 | return text.replace(/^[^a-z]/, toLowerCase); 33 | } 34 | --------------------------------------------------------------------------------