Repository: evanw/skew Branch: master Commit: df456e7be9cf Files: 92 Total size: 2.3 MB Directory structure: gitextract_luug_bnx/ ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.py ├── docs/ │ └── compiler.md ├── extras/ │ ├── Atom/ │ │ └── README.md │ └── Sublime Text/ │ ├── README.md │ └── Skew/ │ ├── Comments.tmPreferences │ ├── Skew.sublime-completions │ └── Skew.tmLanguage ├── npm/ │ ├── README │ └── package.json ├── package.json ├── skewc.js ├── src/ │ ├── backend/ │ │ ├── cplusplus.sk │ │ ├── csharp.sk │ │ ├── emitter.sk │ │ ├── javascript.sk │ │ ├── lisptree.sk │ │ ├── sourcemap.sk │ │ └── typescript.sk │ ├── core/ │ │ ├── content.sk │ │ ├── node.sk │ │ ├── operators.sk │ │ ├── support.sk │ │ └── symbol.sk │ ├── cpp/ │ │ ├── fast.cpp │ │ ├── skew.cpp │ │ ├── skew.h │ │ ├── support.cpp │ │ └── support.h │ ├── driver/ │ │ ├── jsapi.d.ts │ │ ├── jsapi.sk │ │ ├── options.sk │ │ ├── terminal.sk │ │ └── tests.sk │ ├── frontend/ │ │ ├── flex.l │ │ ├── flex.py │ │ ├── lexer.py │ │ ├── lexer.sk │ │ ├── log.sk │ │ ├── parser.sk │ │ ├── pratt.sk │ │ ├── range.sk │ │ ├── source.sk │ │ ├── token.sk │ │ └── version.sk │ ├── lib/ │ │ ├── build.sk │ │ ├── io.sk │ │ ├── terminal.sk │ │ ├── timestamp.sk │ │ └── unit.sk │ └── middle/ │ ├── callgraph.sk │ ├── compiler.sk │ ├── controlflow.sk │ ├── folding.sk │ ├── globalizing.sk │ ├── ide.sk │ ├── inlining.sk │ ├── interfaceremoval.sk │ ├── lambdaconversion.sk │ ├── library.sk │ ├── librarycpp.sk │ ├── librarycs.sk │ ├── libraryjs.sk │ ├── merging.sk │ ├── motion.sk │ ├── renaming.sk │ ├── resolving.sk │ ├── scope.sk │ ├── shaking.sk │ ├── type.sk │ ├── typecache.sk │ └── unicode.sk ├── tests/ │ ├── cplusplus.sk │ ├── csharp.sk │ ├── formatting.sk │ ├── ide.sk │ ├── javascript.mangle.sk │ ├── javascript.minify.sk │ ├── javascript.sk │ ├── library.sk │ ├── node.sk │ ├── other.sk │ ├── parsing.sk │ ├── simple.sk │ └── unicode.sk └── www/ ├── benchmark.html ├── index.html ├── index.js └── style.css ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store *.pyc /node_modules/ /out/ /npm/skew.js /npm/skewc /npm/skew.cpp /npm/skew.h /npm/skew.d.ts ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - stable ================================================ FILE: LICENSE ================================================ Copyright (c) 2015 Evan Wallace Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Skew Programming Language Visit https://evanw.github.io/skew-lang.org/ for more information and a live demo of the compiler. ================================================ FILE: build.py ================================================ #!/usr/bin/env python import os import sys import glob import gzip import json import time import pipes import base64 import shutil import subprocess SOURCES = ( glob.glob('src/backend/*.sk') + glob.glob('src/core/*.sk') + glob.glob('src/driver/*.sk') + glob.glob('src/frontend/*.sk') + glob.glob('src/lib/*.sk') + glob.glob('src/middle/*.sk') + glob.glob('tests/*.sk') ) PUBLIC_CPP_FILES = [ 'src/cpp/skew.cpp', 'src/cpp/skew.h', ] FLAGS = [ '--inline-functions', '--verbose', '--message-limit=0', ] CPP_FLAGS = [ '-std=c++11', '-Wall', '-Wextra', '-Wno-switch', '-Wno-unused-parameter', '-Wno-unused-variable', '-include', 'src/cpp/skew.h', '-include', 'src/cpp/support.h', ] CPP_DEBUG_FLAGS = [ 'src/cpp/skew.cpp', 'src/cpp/support.cpp', ] CPP_RELEASE_FLAGS = [ '-O3', '-DNDEBUG', '-fomit-frame-pointer', '-include', 'src/cpp/skew.cpp', '-include', 'src/cpp/support.cpp', '-include', 'src/cpp/fast.cpp', ] CPP_GC_FLAGS = [ '-DSKEW_GC_MARK_AND_SWEEP', ] node_binary = None jobs = {} ################################################################################ def job(fn): jobs[fn.__name__] = fn return fn def run(args, exit_on_failure=True, **kwargs): # Print the command for debugging so that it can be copied and pasted into a terminal directly print(' '.join(map(pipes.quote, args))) # Start the process process = subprocess.Popen(args, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr, **kwargs) try: process.wait() # Abort on failure if exit_on_failure and process.returncode: print('error: command exited with code %d' % process.returncode) sys.exit(1) return process.returncode # Ensure the process is terminated finally: try: process.terminate() except: pass def watch_folder(folder, callback): before = None while True: after = '' for path, name, files in os.walk(folder): for f in files: both = '%s/%s' % (path, f) try: mtime = os.stat(both).st_mtime except: mtime = -1 after += both + '%d,' % mtime if before != after: before = after callback() time.sleep(0.1) def load_version(): return json.load(open('npm/package.json'))['version'] def update_version(version): open('src/frontend/version.sk', 'w').write('namespace Skew {\n const VERSION = %s\n}\n' % json.dumps(version)) def check_same(a, b): run(['diff', a, b]) def mkdir(path): try: os.makedirs(path) except: pass def rmtree(path): try: shutil.rmtree(path) except: pass def run_js(source, args, exit_on_failure=True): global node_binary if node_binary is None: node_binary = 'nodejs' if run(['which', 'nodejs'], exit_on_failure=False) == 0 else 'node' run([node_binary, source] + args, exit_on_failure=exit_on_failure) def run_cs(source, args): run(['mono', '--debug', source] + args) def run_cpp(source, args): run([source] + args) def skewc_js(source, target, sources=SOURCES, build='SKEWC', release=False, exit_on_failure=True): run_js(source, sources + FLAGS + ['--output-file=' + target, '--define:BUILD=' + build] + (['--release'] if release else []), exit_on_failure=exit_on_failure) def skewc_cs(source, target, sources=SOURCES, build='SKEWC', release=False): run_cs(source, sources + FLAGS + ['--output-file=' + target, '--define:BUILD=' + build] + (['--release'] if release else [])) def skewc_cpp(source, target, sources=SOURCES, build='SKEWC', release=False): run_cpp(source, sources + FLAGS + ['--output-file=' + target, '--define:BUILD=' + build] + (['--release'] if release else [])) def compile_cs(sources, target): run(['mcs', '-debug'] + sources + ['-out:' + target]) def compile_cpp(source, target, release=False, gc=False): run(['c++', source, '-o', target] + CPP_FLAGS + (CPP_RELEASE_FLAGS if release else CPP_DEBUG_FLAGS) + (CPP_GC_FLAGS if gc else [])) ################################################################################ @job def default(): mkdir('out') skewc_js('skewc.js', 'out/skewc.min.js', build='SKEWC', release=True) skewc_js('skewc.js', 'out/skew-api.min.js', build='API', release=True) @job def clean(): rmtree('out') @job def replace(): mkdir('out') skewc_js('skewc.js', 'out/skewc.js', build='SKEWC') skewc_js('out/skewc.js', 'out/skewc2.js', build='SKEWC') skewc_js('out/skewc2.js', 'out/skewc3.js', build='SKEWC') check_same('out/skewc2.js', 'out/skewc3.js') os.remove('skewc.js') os.remove('out/skewc2.js') os.rename('out/skewc3.js', 'skewc.js') @job def check(): check_js() check_cs() check_cpp() @job def check_js(): mkdir('out') skewc_js('skewc.js', 'out/skewc.min.js', build='SKEWC', release=True) skewc_js('out/skewc.min.js', 'out/skewc2.min.js', build='SKEWC', release=True) skewc_js('out/skewc2.min.js', 'out/skewc3.min.js', build='SKEWC', release=True) check_same('out/skewc2.min.js', 'out/skewc3.min.js') @job def check_cs(): mkdir('out') skewc_js('skewc.js', 'out/skewc.cs', build='SKEWC') # Iteration 1 compile_cs(['out/skewc.cs'], 'out/skewc.exe') skewc_cs('out/skewc.exe', 'out/skewc2.cs', build='SKEWC') # Iteration 2 compile_cs(['out/skewc2.cs'], 'out/skewc2.exe') skewc_cs('out/skewc2.exe', 'out/skewc3.cs', build='SKEWC') check_same('out/skewc2.cs', 'out/skewc3.cs') @job def check_cpp(): mkdir('out') skewc_js('skewc.js', 'out/skewc.cpp', build='SKEWC', release=True) # Iteration 1: Debug compile_cpp('out/skewc.cpp', 'out/skewc') skewc_cpp('out/skewc', 'out/skewc2.cpp', build='SKEWC') # Iteration 2: Release compile_cpp('out/skewc2.cpp', 'out/skewc2', release=True) skewc_cpp('out/skewc2', 'out/skewc3.cpp', build='SKEWC') check_same('out/skewc2.cpp', 'out/skewc3.cpp') # Iteration 3: GC compile_cpp('out/skewc3.cpp', 'out/skewc3', gc=True) skewc_cpp('out/skewc3', 'out/skewc4.cpp', build='SKEWC') check_same('out/skewc3.cpp', 'out/skewc4.cpp') @job def check_determinism(): # Generate JavaScript debug and release builds mkdir('out') skewc_js('skewc.js', 'out/skewc.js.js', build='SKEWC') skewc_js('skewc.js', 'out/skewc.js.min.js', build='SKEWC', release=True) # Check C# skewc_js('skewc.js', 'out/skewc.cs', build='SKEWC') compile_cs(['out/skewc.cs'], 'out/skewc.exe') skewc_cs('out/skewc.exe', 'out/skewc.cs.js', build='SKEWC') check_same('out/skewc.js.js', 'out/skewc.cs.js') skewc_cs('out/skewc.exe', 'out/skewc.cs.min.js', build='SKEWC', release=True) check_same('out/skewc.js.min.js', 'out/skewc.cs.min.js') # Check C++ skewc_js('skewc.js', 'out/skewc.cpp', build='SKEWC') compile_cpp('out/skewc.cpp', 'out/skewc') skewc_cpp('out/skewc', 'out/skewc.cpp.js', build='SKEWC') check_same('out/skewc.js.js', 'out/skewc.cpp.js') skewc_cpp('out/skewc', 'out/skewc.cpp.min.js', build='SKEWC', release=True) check_same('out/skewc.js.min.js', 'out/skewc.cpp.min.js') @job def test(): test_js() test_cs() test_cpp() @job def test_js(): mkdir('out') skewc_js('skewc.js', 'out/skewc.js', build='SKEWC') # Debug skewc_js('out/skewc.js', 'out/test.js', build='TEST') run_js('out/test.js', []) # Release skewc_js('out/skewc.js', 'out/test.min.js', build='TEST', release=True) run_js('out/test.min.js', []) @job def test_cs(): mkdir('out') skewc_js('skewc.js', 'out/skewc.js', build='SKEWC') # Single file skewc_js('out/skewc.js', 'out/test.cs', build='TEST') compile_cs(['out/test.cs'], 'out/test.exe') run_cs('out/test.exe', []) # Multiple files rmtree('out/cs') mkdir('out/cs') run_js('out/skewc.js', SOURCES + ['--target=cs', '--output-dir=out/cs', '--define:BUILD=TEST']) compile_cs(glob.glob('out/cs/*.cs'), 'out/test.exe') run_cs('out/test.exe', []) @job def test_cpp(): mkdir('out') skewc_js('skewc.js', 'out/skewc.js', build='SKEWC') # Debug skewc_js('out/skewc.js', 'out/test.debug.cpp', build='TEST') compile_cpp('out/test.debug.cpp', 'out/test.debug') run_cpp('out/test.debug', []) # Release skewc_js('out/skewc.js', 'out/test.release.cpp', build='TEST', release=True) compile_cpp('out/test.release.cpp', 'out/test.release', release=True) run_cpp('out/test.release', []) # GC skewc_js('out/skewc.js', 'out/test.gc.cpp', build='TEST') compile_cpp('out/test.gc.cpp', 'out/test.gc', gc=True) run_cpp('out/test.gc', []) @job def benchmark(): mkdir('out') open('out/benchmark.sk', 'w').write('\n'.join(open(f).read() for f in SOURCES)) skewc_js('skewc.js', 'out/benchmark.js', sources=['out/benchmark.sk'], release=True) @job def watch(): mkdir('out') watch_folder('src', lambda: skewc_js('skewc.js', 'out/skew-api.js', build='API', exit_on_failure=False)) @job def flex(): run(['python', 'src/frontend/lexer.py']) @job def publish(): test() check() run(['npm', 'version', 'patch'], cwd='npm') version = load_version() update_version(version) replace() skewc_js('skewc.js', 'out/skewc.min.js', build='SKEWC', release=True) skewc_js('skewc.js', 'npm/skew.js', build='API', release=True) open('npm/skewc', 'w').write('#!/usr/bin/env node\n' + open('out/skewc.min.js').read()) run(['chmod', '+x', 'npm/skewc']) shutil.copyfile('src/driver/jsapi.d.ts', 'npm/skew.d.ts') for name in PUBLIC_CPP_FILES: shutil.copyfile(name, 'npm/' + os.path.basename(name)) run(['npm', 'publish'], cwd='npm') ################################################################################ def main(args): if not args: args = ['default'] for arg in args: if arg in jobs: jobs[arg]() else: sys.exit('error: unknown job name "%s"' % arg) main(sys.argv[1:]) ================================================ FILE: docs/compiler.md ================================================ # Compiler This documents the internals of the compiler. ## Development Development on the compiler itself is straightforward since the compiler compiles itself. The current build of the compiler in JavaScript is included in the repo as `skewc.js` and is used by `build.py`. Here are some useful commands (see `Makefile` for a complete list): * `./build.py`: Build the compiler into `out/browser.js` which can be tested using `www/index.html` * `./build.py check`: Run various sanity checks including compiling the compiler with itself a few times * `./build.py test`: Run all tests using all supported language targets * `./build.py replace`: Replace the top-level `skewc.js` file with a newer version of itself The core of the compiler is the `compile` method in `src/middle/compiler.sk` and is a good place to start reading for an overview of the compilation process. ## Lexing The lexer is split into two files, `src/frontend/token.sk` and `src/frontend/lexer.sk`. It started off as a hand-written lexer but now uses [flex](http://flex.sourceforge.net/) for speed. The `src/frontend/build.py` script takes `src/frontend/flex.l` and generates `lexer.sk` by running flex and extracting the embedded magic constants and lookup tables from its output. Use `./build.py flex` to do this from the root directory. The generated lexer source code is checked in because it changes infrequently and because it avoids requiring flex as a dependency. The output of flex is awful for a number of reasons but it's really fast. Lexing technically requires infinite lookahead due to the generic type syntax. Like C#, angle brackets are matched using syntactic structure alone without a symbol table. When a `<` token is encountered, it's only considered the start of a parameterization expression if there's a matching `>` ahead in the token stream and the tokens in between meet certain conditions. This lookahead may sound expensive, but it can be done efficiently without backtracking by storing information in a stack. This is done during the lexing pass in `token.sk` and means the lexer is still O(n). Using angle brackets for generics adds the additional complexity of needing to split tokens that start with a `>`. For example, the type `Foo>` should end with two `>` tokens, not one `>>` token. ## Parsing The hand-written parser uses recursive descent for statements and a Pratt parser for expressions. Pratt parsing support is in `src/frontend/pratt.sk` and the grammar implementation is in `src/frontend/parser.sk`. For a grammar overview, look at `createExpressionParser` for expressions and `parseStatement` for statements. ## Syntax Tree Unlike many object-oriented syntax trees, this syntax tree just uses a single `Node` object, defined in `src/core/node.sk`. This makes syntax trees much easier to traverse and optimize. Tree traversal involves a single recursive function instead of a massive visitor object. Structure invariants are maintained by convention and runtime asserts instead of the type system. Primitive literal nodes use `Content` objects to store their constant values, defined in `src/core/content.sk`. ## Preprocessing Before type checking begins, all parsed syntax trees are merged and preprocessed. Preprocessing is done using an order-independent, outside-in algorithm. A worklist is seeded with top-level preprocessor directives and preprocessing iterates until a fixed point is reached. Each iteration attempts to process top-level `if` directives and the constant variables they reference. Error reporting is delayed until the end since only then can unbound variables be classified as errors. Preprocessing should be pretty fast since it doesn't need to traverse into function definitions. ## Type Checking Type checking starts by creating a symbol for each declaration, creating a scope for each block that needs one, and inserting each symbol into its enclosing scope. Each scope can both reference symbols on a type and store local symbols. For example, two adjacent namespace declarations with the same name have two separate scopes that both reference the same type. Once all symbols and scopes are prepared, type checking is done using a single tree traversal. Type checking is made order-independent by applying it recursively. For example, resolving the body of a function may require resolving the type of a variable, which may require resolving its initializer, which may require resolving a constructor, which would then require resolving the enclosing type, which may require resolving base types, and so on: def foo { bar.baz } var bar = Bar.new class Bar : Baz {} class Baz { def baz {} } To prevent cycles, symbol resolution is separated into an initialization phase and a resolution phase. The initialization phase resolves just enough to know the type of the symbol while the resolution phase fully resolves the symbol's contents. Using a symbol requires initializing it but not resolving it. Cycles are detected by giving each symbol three states: uninitialized, initializing, and initialized. Encountering an initializing symbol is an error. These rules ensure that `class Foo { var foo Foo }` is valid but `class Foo : Foo {}` is an error. Limited type inference is performed using type context propagation. A type hint can optionally be provided during expression resolution and will be used to provide missing information if available. For example, type context from the variable type in `var foo List = [0]` ensures that the list literal contains doubles instead of ints. ================================================ FILE: extras/Atom/README.md ================================================ # Atom Syntax Definitions for Skew Run `apm install skew` to install syntax highlighting for the [Atom editor](https://atom.io). The package repository that it installs from is at https://github.com/evanw/skew-atom. ================================================ FILE: extras/Sublime Text/README.md ================================================ # Sublime Text Syntax Definitions for Skew ## Installing To install, copy the "Skew" folder into the appropriate location for the given platform and version listed below. [(more information about Sublime Text packages)](http://docs.sublimetext.info/en/latest/basic_concepts.html#the-packages-directory) ### OS X _Sublime Text 2_ ~/Library/Application Support/Sublime Text 2/Packages/ _Sublime Text 3_ ~/Library/Application Support/Sublime Text 3/Packages/ ### Windows _Sublime Text 2_ %APPDATA%\Sublime Text 2\Packages _Sublime Text 3_ %APPDATA%\Sublime Text 3\Packages ### Linux _Sublime Text 2_ ~/.Sublime Text 2/Packages _Sublime Text 3_ ~/.Sublime Text 3/Packages ================================================ FILE: extras/Sublime Text/Skew/Comments.tmPreferences ================================================ name Comments scope source.skew settings shellVariables name TM_COMMENT_START value # ================================================ FILE: extras/Sublime Text/Skew/Skew.sublime-completions ================================================ { "scope": "source.skew", "completions": [ { "trigger": "as", "contents": "as" }, { "trigger": "break", "contents": "break" }, { "trigger": "case", "contents": "case" }, { "trigger": "catch", "contents": "catch" }, { "trigger": "class", "contents": "class" }, { "trigger": "const", "contents": "const" }, { "trigger": "continue", "contents": "continue" }, { "trigger": "def", "contents": "def" }, { "trigger": "default", "contents": "default" }, { "trigger": "dynamic", "contents": "dynamic" }, { "trigger": "else", "contents": "else" }, { "trigger": "enum", "contents": "enum" }, { "trigger": "false", "contents": "false" }, { "trigger": "finally", "contents": "finally" }, { "trigger": "for", "contents": "for" }, { "trigger": "if", "contents": "if" }, { "trigger": "in", "contents": "in" }, { "trigger": "interface", "contents": "interface" }, { "trigger": "is", "contents": "is" }, { "trigger": "namespace", "contents": "namespace" }, { "trigger": "null", "contents": "null" }, { "trigger": "over", "contents": "over" }, { "trigger": "return", "contents": "return" }, { "trigger": "super", "contents": "super" }, { "trigger": "switch", "contents": "switch" }, { "trigger": "throw", "contents": "throw" }, { "trigger": "true", "contents": "true" }, { "trigger": "try", "contents": "try" }, { "trigger": "var", "contents": "var" }, { "trigger": "while", "contents": "while" } ] } ================================================ FILE: extras/Sublime Text/Skew/Skew.tmLanguage ================================================ fileTypes sk name Skew scopeName source.skew patterns match #.* name comment.skew match \b(?:as|break|case|continue|default|else|finally|if|in|is|return|super|switch|throw|try|while)\b name keyword.skew match \b(?:[A-Z_][A-Z0-9_]+|null|true|false|self|0b[01]+(?:\.[01]+)?|0o[0-7]+(?:\.[0-7]+)?|0x[0-9A-Fa-f]+(?:\.[0-9A-Fa-f]+)?|[0-9]+(?:\.[0-9]+)?(?:e[+-]?[0-9]+)?f?)\b name constant.numeric.skew match @[A-Za-z_][A-Za-z0-9_]*\b name keyword.skew match \b(?:bool|double|dynamic|fn|int|string|[A-Z][A-Za-z0-9_]*(?:\.[A-Z][A-Za-z0-9_]*[a-z][A-Za-z0-9_]*)*)\b(?:<.*?>(?!>))? name storage.type.skew match \bdef\b(?:\s+([A-Za-z0-9_\-\+\*/%!^&|~=><\[\]\{\}\.]+))? name keyword.skew captures 1 name entity.name.function.skew match \b(?:catch|const|for|var)\b(?:\s+([A-Za-z0-9_\.]+))? name keyword.skew captures 1 name entity.name.function.skew match ^\s*\b(?:class|def|enum|flags|interface|namespace|over|type)\b(?:\s+([A-Za-z0-9_\.]+))? name keyword.skew captures 1 name entity.name.function.skew begin ' end ' name string.quoted.single.skew patterns match \\. name constant.character.escape.skew begin " end " name string.quoted.double.skew patterns begin \\\( end \) name string.interpolated.skew match \\. name constant.character.escape.skew ================================================ FILE: npm/README ================================================ See [http://skew-lang.org](http://skew-lang.org) for more information. ================================================ FILE: npm/package.json ================================================ { "name": "skew", "version": "0.9.19", "author": "Evan Wallace", "description": "A compiler for the Skew programming language", "license": "MIT", "main": "skew.js", "bin": { "skewc": "skewc" }, "types": "./skew.d.ts", "repository": { "type": "git", "url": "https://github.com/evanw/skew" } } ================================================ FILE: package.json ================================================ { "scripts": { "test": "python build.py test_js" } } ================================================ FILE: skewc.js ================================================ (function() { var __create = Object.create ? Object.create : function(prototype) { return {'__proto__': prototype}; }; function __extends(derived, base) { derived.prototype = __create(base.prototype); derived.prototype.constructor = derived; } var __imul = Math.imul ? Math.imul : function(a, b) { return (a * (b >>> 16) << 16) + a * (b & 65535) | 0; }; function assert(truth) { if (!truth) { throw Error('Assertion failed'); } } var Target = { CSHARP: 2, JAVASCRIPT: 3 }; function StringBuilder() { this.buffer = ''; } function Box(value) { this.value = value; } var Unicode = {}; Unicode.codeUnitCountForCodePoints = function(codePoints, encoding) { var count = 0; switch (encoding) { case Unicode.Encoding.UTF8: { for (var i = 0, list = codePoints, count1 = list.length; i < count1; i = i + 1 | 0) { var codePoint = in_List.get(list, i); if (codePoint < 128) { count = count + 1 | 0; } else if (codePoint < 2048) { count = count + 2 | 0; } else if (codePoint < 65536) { count = count + 3 | 0; } else { count = count + 4 | 0; } } break; } case Unicode.Encoding.UTF16: { for (var i1 = 0, list1 = codePoints, count2 = list1.length; i1 < count2; i1 = i1 + 1 | 0) { var codePoint1 = in_List.get(list1, i1); if (codePoint1 < 65536) { count = count + 1 | 0; } else { count = count + 2 | 0; } } break; } case Unicode.Encoding.UTF32: { count = codePoints.length; break; } } return count; }; Unicode.Encoding = { UTF8: 0, UTF16: 1, UTF32: 2 }; Unicode.StringIterator = function() { this.value = ''; this.index = 0; this.stop = 0; }; Unicode.StringIterator.prototype.reset = function(text, start) { this.value = text; this.index = start; this.stop = text.length; return this; }; Unicode.StringIterator.prototype.nextCodePoint = function() { if (this.index >= this.stop) { return -1; } var a = in_string.get1(this.value, (this.index = this.index + 1 | 0) + -1 | 0); if ((a & 64512) != 55296) { return a; } if (this.index >= this.stop) { return -1; } var b = in_string.get1(this.value, (this.index = this.index + 1 | 0) + -1 | 0); return ((a << 10) + b | 0) + ((65536 - (55296 << 10) | 0) - 56320 | 0) | 0; }; ///////////////////////////////////////////////////////////////////////////////// // // This is a generated file, all edits will be lost! // ///////////////////////////////////////////////////////////////////////////////// var Skew = {}; Skew.quoteString = function(text, style, octal) { var count = text.length; // Use whichever quote character is less frequent if (style == Skew.QuoteStyle.SHORTEST) { var singleQuotes = 0; var doubleQuotes = 0; for (var i = 0, count1 = count; i < count1; i = i + 1 | 0) { var c = in_string.get1(text, i); if (c == 34) { doubleQuotes = doubleQuotes + 1 | 0; } else if (c == 39) { singleQuotes = singleQuotes + 1 | 0; } } style = singleQuotes <= doubleQuotes ? Skew.QuoteStyle.SINGLE : Skew.QuoteStyle.DOUBLE; } var builder = new StringBuilder(); var quoteString = style == Skew.QuoteStyle.SINGLE ? "'" : '"'; var quote = style == Skew.QuoteStyle.TYPESCRIPT_TEMPLATE ? 96 : style == Skew.QuoteStyle.SINGLE ? 39 : 34; var escaped = ''; // Append long runs of unescaped characters using a single slice for speed var start = 0; if (style != Skew.QuoteStyle.TYPESCRIPT_TEMPLATE) { builder.buffer += quoteString; } for (var i1 = 0, count2 = count; i1 < count2; i1 = i1 + 1 | 0) { var c1 = in_string.get1(text, i1); if (c1 == quote) { escaped = '\\' + quoteString; } else if (c1 == 10) { escaped = '\\n'; } else if (c1 == 13) { escaped = '\\r'; } else if (c1 == 9) { escaped = '\\t'; } else if (c1 == 0) { // Avoid issues around accidental octal encoding var next = (i1 + 1 | 0) < count ? in_string.get1(text, i1 + 1 | 0) : 0; escaped = octal == Skew.QuoteOctal.OCTAL_WORKAROUND && next >= 48 && next <= 57 ? '\\000' : '\\0'; } else if (c1 == 92) { escaped = '\\\\'; } else if (c1 == 36 && style == Skew.QuoteStyle.TYPESCRIPT_TEMPLATE) { escaped = '\\$'; } else if (c1 < 32) { escaped = '\\x' + in_string.get(Skew.HEX, c1 >> 4) + in_string.get(Skew.HEX, c1 & 15); } else { continue; } builder.buffer += in_string.slice2(text, start, i1); builder.buffer += escaped; start = i1 + 1 | 0; } builder.buffer += in_string.slice2(text, start, count); if (style != Skew.QuoteStyle.TYPESCRIPT_TEMPLATE) { builder.buffer += quoteString; } return builder.buffer; }; // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, // the next four bits are the actual value, and the 6th bit is the continuation // bit. The continuation bit tells us whether there are more digits in this // value following this digit. // // Continuation // | Sign // | | // V V // 101011 // Skew.encodeVLQ = function(value) { var vlq = value < 0 ? -value << 1 | 1 : value << 1; var encoded = ''; while (true) { var digit = vlq & 31; vlq >>= 5; // If there are still more digits in this value, we must make sure the // continuation bit is marked if (vlq != 0) { digit |= 32; } encoded += in_string.get(Skew.BASE64, digit); if (vlq == 0) { break; } } return encoded; }; Skew.hashCombine = function(left, right) { return left ^ ((right - 1640531527 | 0) + (left << 6) | 0) + (left >> 2); }; Skew.splitPath = function(path) { var slashIndex = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); return slashIndex == -1 ? new Skew.SplitPath('.', path) : new Skew.SplitPath(in_string.slice2(path, 0, slashIndex), in_string.slice1(path, slashIndex + 1 | 0)); }; Skew.withUppercaseFirstLetter = function(text) { return text == '' ? text : in_string.get(text, 0).toUpperCase() + in_string.slice1(text, 1); }; Skew.indentIndex = function(text) { var i = 0; while (i < text.length && (in_string.get1(text, i) == 32 || in_string.get1(text, i) == 9)) { i = i + 1 | 0; } return i; }; Skew.indentOfLine = function(line) { return in_string.slice2(line, 0, Skew.indentIndex(line)); }; Skew.lineWithoutIndent = function(line) { return in_string.slice1(line, Skew.indentIndex(line)); }; Skew.bytesToString = function(bytes) { var KB = 1 << 10; var MB = 1 << 20; var GB = 1 << 30; if (bytes == 1) { return '1 byte'; } if (bytes < KB) { return bytes.toString() + ' bytes'; } if (bytes < MB) { return (Math.round(bytes / KB * 10) / 10).toString() + 'kb'; } if (bytes < GB) { return (Math.round(bytes / MB * 10) / 10).toString() + 'mb'; } return (Math.round(bytes / GB * 10) / 10).toString() + 'gb'; }; Skew.doubleToStringWithDot = function(value) { // These cases are different for each language target and must be handled before this assert(isFinite(value)); var text = value.toString(); // The C# implementation of double.ToString() uses an uppercase "E" if (TARGET == Target.CSHARP) { text = text.toLowerCase(); } // "1" => "1.0" // "1.5" => "1.5" // "1e+100" => "1.0e+100" // "1.5e+100" => "1.5e+100" if (!(text.indexOf('.') != -1)) { var e = text.indexOf('e'); if (e != -1) { text = in_string.slice2(text, 0, e) + '.0' + in_string.slice1(text, e); } else { text += '.0'; } } return text; }; // The cost of changing the case of a letter is 0.5 instead of 1 Skew.caseAwareLevenshteinEditDistance = function(a, b) { var an = a.length; var bn = b.length; var v0 = []; var v1 = []; for (var i = 0, count = bn + 1 | 0; i < count; i = i + 1 | 0) { v0.push(i); v1.push(i); } for (var i1 = 0, count3 = an; i1 < count3; i1 = i1 + 1 | 0) { var ca = in_string.get1(a, i1); in_List.set(v1, 0, i1 + 1 | 0); for (var j = 0, count1 = bn; j < count1; j = j + 1 | 0) { var cb = in_string.get1(b, j); in_List.set(v1, j + 1 | 0, Math.min(in_List.get(v0, j) + (ca == cb ? 0 : Skew.toLowerCase(ca) == Skew.toLowerCase(cb) ? 0.5 : 1), Math.min(in_List.get(v1, j), in_List.get(v0, j + 1 | 0)) + 1)); } for (var j1 = 0, count2 = bn + 1 | 0; j1 < count2; j1 = j1 + 1 | 0) { in_List.set(v0, j1, in_List.get(v1, j1)); } } return in_List.get(v1, bn); }; Skew.toLowerCase = function(c) { return c >= 65 && c <= 90 ? (97 - 65 | 0) + c | 0 : c; }; Skew.replaceSingleQuotesWithDoubleQuotes = function(text) { assert(text.startsWith("'")); assert(text.endsWith("'")); var builder = new StringBuilder(); var start = 1; var limit = text.length - 1 | 0; builder.buffer += '"'; for (var i = start; i < limit; i = i + 1 | 0) { var c = in_string.get1(text, i); if (c == 34) { builder.buffer += in_string.slice2(text, start, i); builder.buffer += '\\"'; start = i + 1 | 0; } else if (c == 92) { if (in_string.get1(text, i + 1 | 0) == 39) { builder.buffer += in_string.slice2(text, start, i); builder.buffer += "'"; start = i + 2 | 0; } i = i + 1 | 0; } } builder.buffer += in_string.slice2(text, start, limit); builder.buffer += '"'; return builder.buffer; }; Skew.argumentCountForOperator = function(text) { if (Skew.validArgumentCounts == null) { Skew.validArgumentCounts = new Map(); for (var i = 0, list = Array.from(Skew.operatorInfo.values()), count = list.length; i < count; i = i + 1 | 0) { var value = in_List.get(list, i); in_StringMap.set(Skew.validArgumentCounts, value.text, value.validArgumentCounts); } in_StringMap.set(Skew.validArgumentCounts, '<>...', [1]); in_StringMap.set(Skew.validArgumentCounts, '[...]', [1]); in_StringMap.set(Skew.validArgumentCounts, '[new]', [0, 1]); in_StringMap.set(Skew.validArgumentCounts, '{...}', [2]); in_StringMap.set(Skew.validArgumentCounts, '{new}', [0, 2]); } return in_StringMap.get(Skew.validArgumentCounts, text, null); }; Skew.skewcMain = function($arguments) { var log = new Skew.Log(); var diagnosticLimit = 0; var printDiagnostic = function(diagnostic) { var terminalWidth = process.stdout.columns; if (diagnosticLimit > 0 && log.diagnostics.length >= diagnosticLimit) { return; } if (diagnostic.range != null) { Skew.printWithColor(Terminal.Color.BOLD, diagnostic.range.locationString() + ': '); } switch (diagnostic.kind) { case Skew.DiagnosticKind.WARNING: { Skew.printWarning(diagnostic.text); break; } case Skew.DiagnosticKind.ERROR: { Skew.printError(diagnostic.text); break; } } if (diagnostic.range != null) { var formatted = diagnostic.range.format(terminalWidth); process.stdout.write(formatted.line + '\n'); Skew.printWithColor(Terminal.Color.GREEN, formatted.range + '\n'); } if (diagnostic.noteRange != null) { var formatted1 = diagnostic.noteRange.format(terminalWidth); Skew.printWithColor(Terminal.Color.BOLD, diagnostic.noteRange.locationString() + ': '); Skew.printNote(diagnostic.noteText); process.stdout.write(formatted1.line + '\n'); Skew.printWithColor(Terminal.Color.GREEN, formatted1.range + '\n'); } }; // Print diagnostics immediately when generated to improve perceived speed log.appendCallback = printDiagnostic; // Translate frontend flags to compiler options var parser = new Skew.Options.Parser(); var options = Skew.parseOptions(log, parser, $arguments); diagnosticLimit = parser.intForOption(Skew.Option.MESSAGE_LIMIT, Skew.DEFAULT_MESSAGE_LIMIT); var fixAll = parser.boolForOption(Skew.Option.FIX_ALL, false); var fixCount = 0; // Optionally have the log transform warnings into errors if (options != null) { log.warningsAreErrors = options.warningsAreErrors; } // Suppress logging during fixes if (fixAll) { log = new Skew.Log(); } // Iterate until fixed point when applying fixes while (true) { var inputs = []; var inputRanges = []; Skew.readSources(log, parser.normalArguments, inputs, inputRanges); // Run the compilation if (!log.hasErrors() && options != null) { var result = Skew.compile(log, options, inputs); // Write all outputs if (!log.hasErrors()) { for (var i = 0, list = result.outputs, count1 = list.length; i < count1; i = i + 1 | 0) { var output = in_List.get(list, i); if (output.name != null && !IO.writeFile(output.name, output.contents)) { var outputFile = parser.rangeForOption(Skew.Option.OUTPUT_FILE); var outputDirectory = parser.rangeForOption(Skew.Option.OUTPUT_DIRECTORY); log.commandLineErrorUnwritableFile(outputFile != null ? outputFile : outputDirectory, output.name); break; } } // Print compilation statistics if (!log.hasErrors()) { Skew.printWithColor(Terminal.Color.GRAY, result.statistics(inputs, options.verbose ? Skew.StatisticsKind.LONG : Skew.StatisticsKind.SHORT) + '\n'); } } } if (!fixAll) { break; } // Attempt to automatically fix warnings and errors var applyLog = new Skew.Log(); var count = Skew.applyFixes(log, applyLog, function(source) { for (var i = 0, count2 = inputs.length; i < count2; i = i + 1 | 0) { if (source.name == in_List.get(inputs, i).name) { return in_List.get(inputRanges, i); } } return parser.rangeForOption(Skew.Option.FIX_ALL); }); fixCount = fixCount + count | 0; log = applyLog; if (count == 0 || applyLog.hasErrors()) { break; } } // Print diagnostics afterward when applying fixes since they aren't printed earlier if (fixAll) { for (var i1 = 0, list1 = log.diagnostics, count2 = list1.length; i1 < count2; i1 = i1 + 1 | 0) { var diagnostic = in_List.get(list1, i1); printDiagnostic(diagnostic); } process.stdout.write(fixCount.toString() + ' ' + (fixCount == 1 ? 'fix' : 'fixes') + ' applied' + '\n'); } // Print any errors and warnings Skew.printLogSummary(log, diagnosticLimit); // Optionally report a failure if any warnings were found if (options != null && options.warningsAreErrors) { return log.hasErrors() || log.hasWarnings() ? 1 : 0; } else { return log.hasErrors() ? 1 : 0; } }; Skew.printWithColor = function(color, text) { Terminal.setColor(color); process.stdout.write(text); Terminal.setColor(Terminal.Color.DEFAULT); }; Skew.printError = function(text) { Skew.printWithColor(Terminal.Color.RED, 'error: '); Skew.printWithColor(Terminal.Color.BOLD, text + '\n'); }; Skew.printNote = function(text) { Skew.printWithColor(Terminal.Color.GRAY, 'note: '); Skew.printWithColor(Terminal.Color.BOLD, text + '\n'); }; Skew.printWarning = function(text) { Skew.printWithColor(Terminal.Color.MAGENTA, 'warning: '); Skew.printWithColor(Terminal.Color.BOLD, text + '\n'); }; Skew.printUsage = function(parser) { Skew.printWithColor(Terminal.Color.GREEN, '\nusage: '); Skew.printWithColor(Terminal.Color.BOLD, 'skewc [flags] [inputs]\n'); process.stdout.write(parser.usageText(Math.min(process.stdout.columns, 80))); }; Skew.printLogSummary = function(log, diagnosticLimit) { var hasErrors = log.hasErrors(); var hasWarnings = log.hasWarnings(); var summary = ''; if (hasWarnings) { summary += Skew.PrettyPrint.plural1(log.warningCount(), 'warning'); if (hasErrors) { summary += ' and '; } } if (hasErrors) { summary += Skew.PrettyPrint.plural1(log.errorCount(), 'error'); } if (hasWarnings || hasErrors) { process.stdout.write(summary + ' generated'); if (log.wasWarningCount() > 0) { process.stdout.write(' (warnings are being treated as errors due to "--warnings-are-errors")'); } if (diagnosticLimit > 0 && log.diagnostics.length > diagnosticLimit) { Skew.printWithColor(Terminal.Color.GRAY, ' (only showing ' + Skew.PrettyPrint.plural1(diagnosticLimit, 'message') + ', use "--message-limit=0" to see all)'); } process.stdout.write('\n'); } }; Skew.readSources = function(log, normalArguments, inputs, inputRanges) { var visit = null; visit = function(range, path, isExplicit) { if (Skew.splitPath(path).entry.startsWith('.')) { return; } // Directories if (IO.isDirectory(path)) { var entries = IO.readDirectory(path); if (entries == null) { log.commandLineErrorUnreadableFile(range, path); } for (var i = 0, list = entries, count = list.length; i < count; i = i + 1 | 0) { var entry = in_List.get(list, i); if (!entry.startsWith('.')) { visit(range, path + '/' + entry, false); } } } // Files (ignore non-skew files that aren't explicitly specified) else if (isExplicit || path.endsWith('.sk')) { var contents = IO.readFile(path); if (contents == null) { log.commandLineErrorUnreadableFile(range, path); } else { inputs.push(new Skew.Source(path, contents)); inputRanges.push(range); } } }; // Recursively visit input directories for (var i = 0, list = normalArguments, count = list.length; i < count; i = i + 1 | 0) { var range = in_List.get(list, i); visit(range, range.toString(), true); } }; Skew.parseOptions = function(log, parser, $arguments) { // Configure the parser parser.define(Skew.Options.Type.BOOL, Skew.Option.HELP, '--help', 'Prints this message.').aliases(['-help', '?', '-?', '-h', '-H', '/?', '/h', '/H']); parser.define(Skew.Options.Type.STRING, Skew.Option.TARGET, '--target', 'Sets the target format. Valid targets are ' + Skew.joinKeys(Array.from(Skew.VALID_TARGETS.keys())) + '.'); parser.define(Skew.Options.Type.STRING, Skew.Option.OUTPUT_FILE, '--output-file', 'Combines all output into a single file. Mutually exclusive with --output-dir.'); parser.define(Skew.Options.Type.STRING, Skew.Option.OUTPUT_DIRECTORY, '--output-dir', 'Places all output files in the specified directory. Mutually exclusive with --output-file.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.NO_OUTPUT, '--no-output', 'Stops after the type checking pass and does not generate any output.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.RELEASE, '--release', 'Implies --js-mangle, --js-minify, --fold-constants, --inline-functions, --globalize-functions, and --define:RELEASE=true.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.VERBOSE, '--verbose', 'Prints out information about the compilation.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.VERSION, '--version', 'Prints the current compiler version (' + Skew.VERSION + ') and exits.'); parser.define(Skew.Options.Type.INT, Skew.Option.MESSAGE_LIMIT, '--message-limit', 'Sets the maximum number of messages to report. ' + ('Pass 0 to disable the message limit. The default is ' + Skew.DEFAULT_MESSAGE_LIMIT.toString() + '.')); parser.define(Skew.Options.Type.STRING_LIST, Skew.Option.DEFINE, '--define', 'Override variable values at compile time.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.JS_MANGLE, '--js-mangle', 'Transforms emitted JavaScript to be as small as possible. The "@export" annotation prevents renaming a symbol.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.JS_MINIFY, '--js-minify', 'Remove whitespace when compiling to JavaScript.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.JS_SOURCE_MAP, '--js-source-map', 'Generates a source map when targeting JavaScript. ' + 'The source map is saved with the ".map" extension in the same directory as the main output file.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.FOLD_CONSTANTS, '--fold-constants', 'Evaluates constants at compile time and removes dead code inside functions.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.INLINE_FUNCTIONS, '--inline-functions', 'Uses heuristics to automatically inline simple global functions.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.GLOBALIZE_FUNCTIONS, '--globalize-functions', 'Convert instance functions to global functions for better inlining.'); parser.define(Skew.Options.Type.BOOL, Skew.Option.FIX_ALL, '--fix-all', 'Attempt to automatically fix as many errors and warnings as possible. ' + "THIS WILL WRITE OVER YOUR SOURCE CODE. Make sure you know what you're doing."); parser.define(Skew.Options.Type.BOOL, Skew.Option.IGNORED_COMMENT_WARNING, '--ignored-comment-warning', "Warn when the compiler doesn't store a comment in the parse tree."); parser.define(Skew.Options.Type.BOOL, Skew.Option.WARNINGS_ARE_ERRORS, '--warnings-are-errors', 'Turns warnings into errors.'); // Parse the command line arguments parser.parse(log, $arguments); if (log.hasErrors()) { return null; } // Early-out when printing the usage text if (parser.boolForOption(Skew.Option.HELP, $arguments.length == 0)) { Skew.printUsage(parser); return null; } // Early-out when printing the version if (parser.boolForOption(Skew.Option.VERSION, false)) { process.stdout.write(Skew.VERSION + '\n'); return null; } // Set up the options for the compiler var options = new Skew.CompilerOptions(); var releaseFlag = parser.boolForOption(Skew.Option.RELEASE, false); options.foldAllConstants = parser.boolForOption(Skew.Option.FOLD_CONSTANTS, releaseFlag); options.globalizeAllFunctions = parser.boolForOption(Skew.Option.GLOBALIZE_FUNCTIONS, releaseFlag); options.inlineAllFunctions = parser.boolForOption(Skew.Option.INLINE_FUNCTIONS, releaseFlag); options.jsMangle = parser.boolForOption(Skew.Option.JS_MANGLE, releaseFlag); options.jsMinify = parser.boolForOption(Skew.Option.JS_MINIFY, releaseFlag); options.jsSourceMap = parser.boolForOption(Skew.Option.JS_SOURCE_MAP, false); options.stopAfterResolve = parser.boolForOption(Skew.Option.NO_OUTPUT, false); options.verbose = parser.boolForOption(Skew.Option.VERBOSE, false); options.warnAboutIgnoredComments = parser.boolForOption(Skew.Option.IGNORED_COMMENT_WARNING, false); options.warningsAreErrors = parser.boolForOption(Skew.Option.WARNINGS_ARE_ERRORS, false); // Prepare the defines if (releaseFlag) { options.define('RELEASE', 'true'); } for (var i = 0, list = parser.rangeListForOption(Skew.Option.DEFINE), count = list.length; i < count; i = i + 1 | 0) { var range = in_List.get(list, i); var name = range.toString(); var equals = name.indexOf('='); if (equals < 0) { log.commandLineErrorExpectedDefineValue(range, name); continue; } in_StringMap.set(options.defines, in_string.slice2(name, 0, equals), new Skew.Define(range.fromStart(equals), range.fromEnd((name.length - equals | 0) - 1 | 0))); } // There must be at least one source file var end = parser.source.contents.length; var trailingSpace = new Skew.Range(parser.source, end - 1 | 0, end); if (parser.normalArguments.length == 0 && !options.stopAfterResolve) { log.commandLineErrorNoInputFiles(trailingSpace); } // Parse the output location if (!options.stopAfterResolve) { var outputFile = parser.rangeForOption(Skew.Option.OUTPUT_FILE); var outputDirectory = parser.rangeForOption(Skew.Option.OUTPUT_DIRECTORY); if (outputFile == null && outputDirectory == null) { log.commandLineErrorMissingOutput(trailingSpace, '--output-file', '--output-dir'); } else if (outputFile != null && outputDirectory != null) { log.commandLineErrorDuplicateOutput(outputFile.start > outputDirectory.start ? outputFile : outputDirectory, '--output-file', '--output-dir'); } else if (outputFile != null) { options.outputFile = outputFile.toString(); } else { options.outputDirectory = outputDirectory.toString(); } } // Check the target format var target = parser.rangeForOption(Skew.Option.TARGET); if (target != null) { options.target = Skew.parseEnum(log, 'target', Skew.VALID_TARGETS, target, null); } else if (!options.createTargetFromExtension()) { log.commandLineErrorMissingTarget(trailingSpace); } return options; }; Skew.applyFixes = function(log, applyLog, rangeForSource) { var fixCount = 0; // Collect diagnostics by source file var map = new Map(); for (var i1 = 0, list = log.diagnostics, count = list.length; i1 < count; i1 = i1 + 1 | 0) { var diagnostic = in_List.get(list, i1); if (diagnostic.range != null && diagnostic.fixes != null && diagnostic.fixes.length == 1) { var name = diagnostic.range.source.name; var diagnostics = in_StringMap.get(map, name, null); if (diagnostics == null) { in_StringMap.set(map, name, diagnostics = []); } diagnostics.push(diagnostic); } } // Apply for each source file for (var i2 = 0, list1 = Array.from(map.values()), count2 = list1.length; i2 < count2; i2 = i2 + 1 | 0) { var diagnostics1 = in_List.get(list1, i2); var source = in_List.first(diagnostics1).range.source; var contents = source.contents; diagnostics1.sort(function(a, b) { return in_int.compare(b.range.start, a.range.start); }); // Apply fixes in reverse to avoid issues with changing offsets var last = contents.length; for (var i = 0, count1 = diagnostics1.length; i < count1; i = i + 1 | 0) { var fix = in_List.first(in_List.get(diagnostics1, i).fixes); // Typo correction isn't robust enough right now to fix automatically if (fix.kind == Skew.FixKind.SYMBOL_TYPO || fix.range.end > last) { continue; } contents = in_string.slice2(contents, 0, fix.range.start) + fix.replacement + in_string.slice1(contents, fix.range.end); last = fix.range.start; fixCount = fixCount + 1 | 0; } // Write over the source file in place if (!IO.writeFile(source.name, contents)) { applyLog.commandLineErrorUnwritableFile(rangeForSource(source), source.name); } } return fixCount; }; Skew.joinKeys = function(keys) { keys.sort(Skew.SORT_STRINGS); return Skew.PrettyPrint.joinQuoted(keys, 'and'); }; Skew.parseEnum = function(log, name, map, range, defaultValue) { if (range != null) { var key = range.toString(); if (map.has(key)) { return in_StringMap.get1(map, key); } var keys = Array.from(map.keys()); // Sort so the order is deterministic keys.sort(Skew.SORT_STRINGS); log.commandLineErrorInvalidEnum(range, name, key, keys); } return defaultValue; }; // This is the inner loop from "flex", an ancient lexer generator. The output // of flex is pretty bad (obfuscated variable names and the opposite of modular // code) but it's fast and somewhat standard for compiler design. The code below // replaces a simple hand-coded lexer and offers much better performance. Skew.tokenize = function(log, source) { var comments = null; var tokens = []; var text = source.contents; var count = text.length; var previousKind = Skew.TokenKind.NULL; var previousWasComment = false; var stack = []; // For backing up var yy_last_accepting_state = 0; var yy_last_accepting_cpos = 0; // The current character pointer var yy_cp = 0; while (yy_cp < count) { // Reset the NFA var yy_current_state = 1; // The pointer to the beginning of the token var yy_bp = yy_cp; var yy_act = Skew.TokenKind.ERROR; // Special-case string interpolation var c = in_string.get1(text, yy_cp); var isStringInterpolation = c == 34; if (c == 41) { for (var i = stack.length - 1 | 0; i >= 0; i = i - 1 | 0) { var kind = in_List.get(stack, i).kind; if (kind == Skew.TokenKind.STRING_INTERPOLATION_START) { isStringInterpolation = true; } else if (kind != Skew.TokenKind.LESS_THAN) { break; } } } if (isStringInterpolation) { var isExit = c == 41; yy_cp = yy_cp + 1 | 0; while (yy_cp < count) { c = in_string.get1(text, (yy_cp = yy_cp + 1 | 0) + -1 | 0); if (c == 34) { yy_act = isExit ? Skew.TokenKind.STRING_INTERPOLATION_END : Skew.TokenKind.STRING; break; } if (c == 92) { if (yy_cp == count) { break; } c = in_string.get1(text, (yy_cp = yy_cp + 1 | 0) + -1 | 0); if (c == 40) { yy_act = isExit ? Skew.TokenKind.STRING_INTERPOLATION_CONTINUE : Skew.TokenKind.STRING_INTERPOLATION_START; break; } } } } // Special-case XML literals else if (c == 62 && !(stack.length == 0) && in_List.last(stack).kind == Skew.TokenKind.XML_START) { yy_cp = yy_cp + 1 | 0; yy_act = Skew.TokenKind.XML_END; } // Search for a match else { while (yy_current_state != Skew.YY_JAM_STATE) { if (yy_cp >= count) { // This prevents syntax errors from causing infinite loops break; } c = in_string.get1(text, yy_cp); // All of the interesting characters are ASCII var index = c < 127 ? c : 127; var yy_c = in_List.get(Skew.yy_ec, index); if (in_List.get(Skew.yy_accept, yy_current_state) != Skew.TokenKind.YY_INVALID_ACTION) { yy_last_accepting_state = yy_current_state; yy_last_accepting_cpos = yy_cp; } while (in_List.get(Skew.yy_chk, in_List.get(Skew.yy_base, yy_current_state) + yy_c | 0) != yy_current_state) { yy_current_state = in_List.get(Skew.yy_def, yy_current_state); if (yy_current_state >= Skew.YY_ACCEPT_LENGTH) { yy_c = in_List.get(Skew.yy_meta, yy_c); } } yy_current_state = in_List.get(Skew.yy_nxt, in_List.get(Skew.yy_base, yy_current_state) + yy_c | 0); yy_cp = yy_cp + 1 | 0; } // Find the action yy_act = in_List.get(Skew.yy_accept, yy_current_state); while (yy_act == Skew.TokenKind.YY_INVALID_ACTION) { // Have to back up yy_cp = yy_last_accepting_cpos; yy_current_state = yy_last_accepting_state; yy_act = in_List.get(Skew.yy_accept, yy_current_state); } // Ignore whitespace if (yy_act == Skew.TokenKind.WHITESPACE) { continue; } // Stop at the end of the file if (yy_act == Skew.TokenKind.END_OF_FILE) { break; } } // Special-case XML literals if (yy_act == Skew.TokenKind.LESS_THAN && !Skew.FORBID_XML_AFTER.has(previousKind)) { yy_act = Skew.TokenKind.XML_START; } // This is the default action in flex, which is usually called ECHO else if (yy_act == Skew.TokenKind.ERROR) { var iterator = Unicode.StringIterator.INSTANCE.reset(text, yy_bp); iterator.nextCodePoint(); var range = new Skew.Range(source, yy_bp, iterator.index); log.syntaxErrorExtraData(range, range.toString()); break; } var token = new Skew.Token(new Skew.Range(source, yy_bp, yy_cp), yy_act, null); // Have a nice error message for certain tokens if (yy_act == Skew.TokenKind.COMMENT_ERROR) { log.syntaxErrorSlashComment(token.range); token.kind = Skew.TokenKind.COMMENT; } else if (yy_act == Skew.TokenKind.NOT_EQUAL_ERROR) { log.syntaxErrorOperatorTypo(token.range, '!='); token.kind = Skew.TokenKind.NOT_EQUAL; } else if (yy_act == Skew.TokenKind.EQUAL_ERROR) { log.syntaxErrorOperatorTypo(token.range, '=='); token.kind = Skew.TokenKind.EQUAL; } // Tokens that start with a greater than may need to be split, potentially multiple times var loop = true; while (loop) { var tokenStartsWithGreaterThan = in_string.get1(text, token.range.start) == 62; var tokenKind = token.kind; loop = false; // Remove tokens from the stack if they aren't working out while (!(stack.length == 0)) { var top = in_List.last(stack); var topKind = top.kind; // Stop parsing a type if we find a token that no type expression uses if (topKind == Skew.TokenKind.LESS_THAN && tokenKind != Skew.TokenKind.LESS_THAN && tokenKind != Skew.TokenKind.IDENTIFIER && tokenKind != Skew.TokenKind.COMMA && tokenKind != Skew.TokenKind.DYNAMIC && tokenKind != Skew.TokenKind.DOT && tokenKind != Skew.TokenKind.LEFT_PARENTHESIS && tokenKind != Skew.TokenKind.RIGHT_PARENTHESIS && !tokenStartsWithGreaterThan) { in_List.removeLast(stack); } else { break; } } // Group open if (tokenKind == Skew.TokenKind.LEFT_PARENTHESIS || tokenKind == Skew.TokenKind.LEFT_BRACE || tokenKind == Skew.TokenKind.LEFT_BRACKET || tokenKind == Skew.TokenKind.LESS_THAN || tokenKind == Skew.TokenKind.STRING_INTERPOLATION_START || tokenKind == Skew.TokenKind.XML_START) { stack.push(token); } // Group close else if (tokenKind == Skew.TokenKind.RIGHT_PARENTHESIS || tokenKind == Skew.TokenKind.RIGHT_BRACE || tokenKind == Skew.TokenKind.RIGHT_BRACKET || tokenKind == Skew.TokenKind.STRING_INTERPOLATION_END || tokenKind == Skew.TokenKind.XML_END || tokenStartsWithGreaterThan) { // Search for a matching opposite token while (!(stack.length == 0)) { var top1 = in_List.last(stack); var topKind1 = top1.kind; // Don't match ">" that don't work since they are just operators if (tokenStartsWithGreaterThan && topKind1 != Skew.TokenKind.LESS_THAN) { break; } // Consume the current token in_List.removeLast(stack); // Stop if it's a match if (tokenKind == Skew.TokenKind.RIGHT_PARENTHESIS && topKind1 == Skew.TokenKind.LEFT_PARENTHESIS || tokenKind == Skew.TokenKind.RIGHT_BRACKET && topKind1 == Skew.TokenKind.LEFT_BRACKET || tokenKind == Skew.TokenKind.RIGHT_BRACE && topKind1 == Skew.TokenKind.LEFT_BRACE || tokenKind == Skew.TokenKind.STRING_INTERPOLATION_END && topKind1 == Skew.TokenKind.STRING_INTERPOLATION_START) { break; } // Special-case angle brackets matches and ignore tentative matches that didn't work out if (topKind1 == Skew.TokenKind.LESS_THAN && tokenStartsWithGreaterThan) { // Break apart operators that start with a closing angle bracket if (tokenKind != Skew.TokenKind.GREATER_THAN) { var start = token.range.start; tokens.push(new Skew.Token(new Skew.Range(source, start, start + 1 | 0), Skew.TokenKind.PARAMETER_LIST_END, null)); token.range = new Skew.Range(source, start + 1 | 0, token.range.end); token.kind = tokenKind == Skew.TokenKind.SHIFT_RIGHT ? Skew.TokenKind.GREATER_THAN : tokenKind == Skew.TokenKind.UNSIGNED_SHIFT_RIGHT ? Skew.TokenKind.SHIFT_RIGHT : tokenKind == Skew.TokenKind.GREATER_THAN_OR_EQUAL ? Skew.TokenKind.ASSIGN : tokenKind == Skew.TokenKind.ASSIGN_SHIFT_RIGHT ? Skew.TokenKind.GREATER_THAN_OR_EQUAL : tokenKind == Skew.TokenKind.ASSIGN_UNSIGNED_SHIFT_RIGHT ? Skew.TokenKind.ASSIGN_SHIFT_RIGHT : Skew.TokenKind.NULL; assert(token.kind != Skew.TokenKind.NULL); // Split this token again loop = tokenKind != Skew.TokenKind.GREATER_THAN_OR_EQUAL; } else { token.kind = Skew.TokenKind.PARAMETER_LIST_END; } // Convert the "<" into a bound for type parameter lists top1.kind = Skew.TokenKind.PARAMETER_LIST_START; // Stop the search since we found a match break; } } } } // Remove newlines based on the previous token to enable line continuations. // Make sure to be conservative. We want to be like Python, not like // JavaScript ASI! Anything that is at all ambiguous should be disallowed. // // Examples: // - "var x = 0 \n .toString" // - "var x = 0 # comment \n .toString" // - "var x = 0 \n # comment \n .toString" // - "var x = 0 \n ### \n multi-line comment \n ### \n return 0" // if (previousKind == Skew.TokenKind.NEWLINE && token.kind == Skew.TokenKind.NEWLINE) { if (comments != null && !previousWasComment) { in_List.last(comments).hasGapBelow = true; } previousWasComment = false; continue; } else if (previousKind == Skew.TokenKind.NEWLINE && Skew.REMOVE_WHITESPACE_BEFORE.has(token.kind)) { var last = in_List.takeLast(tokens); if (last.comments != null) { if (comments == null) { comments = []; } in_List.append1(comments, last.comments); } } // Attach comments to tokens instead of having comments be tokens previousWasComment = token.kind == Skew.TokenKind.COMMENT; if (previousWasComment) { if (comments == null) { comments = []; } if (comments.length == 0 || in_List.last(comments).hasGapBelow) { comments.push(new Skew.Comment(token.range, [], false, false)); } var range1 = token.range; var line = in_string.slice2(source.contents, range1.start + 1 | 0, range1.end); var hashes = 0; for (var j = 0, count1 = line.length; j < count1; j = j + 1 | 0) { if (in_string.get1(line, j) != 35) { break; } hashes = hashes + 1 | 0; } if (hashes != 0) { line = in_string.repeat('/', hashes) + in_string.slice1(line, hashes); } in_List.last(comments).lines.push(line); continue; } previousKind = token.kind; if (previousKind != Skew.TokenKind.NEWLINE) { token.comments = comments; comments = null; } // Capture trailing comments if (!(tokens.length == 0) && comments != null && comments.length == 1 && in_List.first(comments).lines.length == 1 && !in_List.first(comments).hasGapBelow) { in_List.first(comments).isTrailing = true; token.comments = comments; comments = null; } // Accumulate the token for this iteration tokens.push(token); } // Every token stream ends in END_OF_FILE tokens.push(new Skew.Token(new Skew.Range(source, yy_cp, yy_cp), Skew.TokenKind.END_OF_FILE, comments)); // Also return preprocessor token presence so the preprocessor can be avoided return tokens; }; // Remove all code that isn't reachable from the entry point or from an // imported or exported symbol. This is called tree shaking here but is also // known as dead code elimination. Tree shaking is perhaps a better name // because this pass doesn't remove dead code inside functions. Skew.shakingPass = function(global, entryPoint, mode) { var graph = new Skew.UsageGraph(global, mode); var symbols = []; Skew.Shaking.collectExportedSymbols(global, symbols, entryPoint); var usages = graph.usagesForSymbols(symbols); if (usages != null) { Skew.Shaking.removeUnusedSymbols(global, usages); } }; Skew.compile = function(log, options, inputs) { inputs = inputs.slice(); options.target.includeSources(inputs); options.target.editOptions(options); inputs.unshift(new Skew.Source('', Skew.UNICODE_LIBRARY)); inputs.unshift(new Skew.Source('', Skew.NATIVE_LIBRARY)); var context = new Skew.PassContext(log, options, inputs); var passTimers = []; var totalTimer = new Skew.Timer(); totalTimer.start(); // Run all passes, stop compilation if there are errors after resolving (wait until then to make IDE mode better) for (var i = 0, list = options.passes, count = list.length; i < count; i = i + 1 | 0) { var pass = in_List.get(list, i); if (context.isResolvePassComplete && log.hasErrors()) { break; } if (pass.shouldRun()) { var passTimer = new Skew.PassTimer(pass.kind()); passTimers.push(passTimer); passTimer.timer.start(); pass.run(context); passTimer.timer.stop(); context.verify(); } } totalTimer.stop(); return new Skew.CompilerResult(context.cache, context.global, context.outputs, passTimers, totalTimer); }; Skew.SORT_STRINGS = function(a, b) { return in_string.compare(a, b); }; Skew.PassKind = { EMITTING: 0, LEXING: 1, PARSING: 2, RESOLVING: 3, LAMBDA_CONVERSION: 4, CALL_GRAPH: 5, INLINING: 6, FOLDING: 7, MOTION: 8, GLOBALIZING: 9, MERGING: 10, INTERFACE_REMOVAL: 11, RENAMING: 12 }; Skew.EmitMode = { ALWAYS_EMIT: 0, SKIP_IF_EMPTY: 1 }; Skew.Emitter = function() { this._sources = []; this._prefix = new StringBuilder(); this._code = new StringBuilder(); this._indentAmount = ' '; this._indent = ''; }; Skew.Emitter.prototype.sources = function() { return this._sources; }; Skew.Emitter.prototype._increaseIndent = function() { this._indent += this._indentAmount; }; Skew.Emitter.prototype._decreaseIndent = function() { this._indent = in_string.slice1(this._indent, this._indentAmount.length); }; Skew.Emitter.prototype._emit = function(text) { this._code.buffer += text; }; Skew.Emitter.prototype._emitPrefix = function(text) { this._prefix.buffer += text; }; Skew.Emitter.prototype._createSource = function(name, mode) { var code = this._code.buffer; if (mode == Skew.EmitMode.ALWAYS_EMIT || code != '') { this._prefix.buffer += code; this._sources.push(new Skew.Source(name, this._prefix.buffer)); } this._prefix = new StringBuilder(); this._code = new StringBuilder(); }; Skew.Emitter.prototype._collectObjects = function(global) { var objects = []; this._findObjects(objects, global); return objects; }; Skew.Emitter.prototype._sortedObjects = function(global) { var objects = this._collectObjects(global); // Sort by inheritance and containment for (var i = 0, count = objects.length; i < count; i = i + 1 | 0) { var j = i; // Select an object that comes before all other types while (j < objects.length) { var object = in_List.get(objects, j); var k = i; // Check to see if this comes before all other types while (k < objects.length) { if (j != k && Skew.Emitter._objectComesBefore(in_List.get(objects, k), object)) { break; } k = k + 1 | 0; } if (k == objects.length) { break; } j = j + 1 | 0; } // Swap the object into the correct order if (j < objects.length) { in_List.swap(objects, i, j); } } return objects; }; Skew.Emitter.prototype._markVirtualFunctions = function(symbol) { for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._markVirtualFunctions(object); } for (var i2 = 0, list2 = symbol.functions, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var $function = in_List.get(list2, i2); if ($function.overridden != null) { $function.overridden.flags |= Skew.SymbolFlags.IS_VIRTUAL; $function.flags |= Skew.SymbolFlags.IS_VIRTUAL; } if ($function.implementations != null) { for (var i1 = 0, list1 = $function.implementations, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var other = in_List.get(list1, i1); other.flags |= Skew.SymbolFlags.IS_VIRTUAL; $function.flags |= Skew.SymbolFlags.IS_VIRTUAL; } } } }; Skew.Emitter.prototype._findObjects = function(objects, object) { objects.push(object); for (var i = 0, list = object.objects, count = list.length; i < count; i = i + 1 | 0) { var o = in_List.get(list, i); this._findObjects(objects, o); } }; Skew.Emitter._isContainedBy = function(inner, outer) { if (inner.parent == null) { return false; } if (inner.parent == outer) { return true; } return Skew.Emitter._isContainedBy(inner.parent.asObjectSymbol(), outer); }; Skew.Emitter._objectComesBefore = function(before, after) { return after.hasBaseClass(before) || after.hasInterface(before) || Skew.Emitter._isContainedBy(after, before) || after.forwardTo == before; }; // These dump() functions are helpful for debugging syntax trees Skew.LispTreeEmitter = function(_options) { Skew.Emitter.call(this); this._options = _options; }; __extends(Skew.LispTreeEmitter, Skew.Emitter); Skew.LispTreeEmitter.prototype.visit = function(global) { this._visitObject(global); this._emit('\n'); this._createSource(this._options.outputDirectory != null ? this._options.outputDirectory + '/compiled.lisp' : this._options.outputFile, Skew.EmitMode.ALWAYS_EMIT); }; Skew.LispTreeEmitter.prototype._visitObject = function(symbol) { this._emit('(' + this._mangleKind(in_List.get(Skew.in_SymbolKind._strings, symbol.kind)) + ' ' + Skew.quoteString(symbol.name, Skew.QuoteStyle.DOUBLE, Skew.QuoteOctal.OCTAL_WORKAROUND)); this._increaseIndent(); for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._emit('\n' + this._indent); this._visitObject(object); } for (var i1 = 0, list1 = symbol.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); this._emit('\n' + this._indent); this._visitFunction($function); } for (var i2 = 0, list2 = symbol.variables, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); this._emit('\n' + this._indent); this._visitVariable(variable); } this._decreaseIndent(); this._emit(')'); }; Skew.LispTreeEmitter.prototype._visitFunction = function(symbol) { this._emit('(' + this._mangleKind(in_List.get(Skew.in_SymbolKind._strings, symbol.kind)) + ' ' + Skew.quoteString(symbol.name, Skew.QuoteStyle.DOUBLE, Skew.QuoteOctal.OCTAL_WORKAROUND)); this._increaseIndent(); for (var i = 0, list = symbol.$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); this._emit('\n' + this._indent); this._visitVariable(argument); } this._emit('\n' + this._indent); this._visitNode(symbol.returnType); this._emit('\n' + this._indent); this._visitNode(symbol.block); this._decreaseIndent(); this._emit(')'); }; Skew.LispTreeEmitter.prototype._visitVariable = function(symbol) { this._emit('(' + this._mangleKind(in_List.get(Skew.in_SymbolKind._strings, symbol.kind)) + ' ' + Skew.quoteString(symbol.name, Skew.QuoteStyle.DOUBLE, Skew.QuoteOctal.OCTAL_WORKAROUND) + ' '); this._visitNode(symbol.type); this._emit(' '); this._visitNode(symbol.value); this._emit(')'); }; Skew.LispTreeEmitter.prototype._visitNode = function(node) { if (node == null) { this._emit('nil'); return; } this._emit('(' + this._mangleKind(in_List.get(Skew.in_NodeKind._strings, node.kind))); var content = node.content; if (content != null) { switch (content.kind()) { case Skew.ContentKind.INT: { this._emit(' ' + Skew.in_Content.asInt(content).toString()); break; } case Skew.ContentKind.BOOL: { this._emit(' ' + Skew.in_Content.asBool(content).toString()); break; } case Skew.ContentKind.DOUBLE: { this._emit(' ' + Skew.in_Content.asDouble(content).toString()); break; } case Skew.ContentKind.STRING: { this._emit(' ' + Skew.quoteString(Skew.in_Content.asString(content), Skew.QuoteStyle.DOUBLE, Skew.QuoteOctal.OCTAL_WORKAROUND)); break; } } } if (node.kind == Skew.NodeKind.VARIABLE) { this._emit(' '); this._visitVariable(node.symbol.asVariableSymbol()); } else if (node.kind == Skew.NodeKind.LAMBDA) { this._emit(' '); this._visitFunction(node.symbol.asFunctionSymbol()); } else if (node.hasChildren()) { this._increaseIndent(); for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._emit('\n' + this._indent); this._visitNode(child); } this._decreaseIndent(); } this._emit(')'); }; Skew.LispTreeEmitter.prototype._mangleKind = function(kind) { return kind.toLowerCase().split('_').join('-'); }; Skew.CSharpEmitter = function(_options, _cache) { Skew.Emitter.call(this); this._options = _options; this._cache = _cache; this._previousNode = null; this._previousSymbol = null; this._namespaceStack = []; this._symbolsCheckedForUsing = new Map(); this._usingNames = new Map(); this._loopLabels = new Map(); this._enclosingFunction = null; }; __extends(Skew.CSharpEmitter, Skew.Emitter); Skew.CSharpEmitter.prototype.visit = function(global) { this._indentAmount = ' '; this._moveGlobalsIntoClasses(global); // Generate the entry point var entryPoint = this._cache.entryPointSymbol; if (entryPoint != null) { entryPoint.name = 'Main'; // The entry point in C# takes an array, not a list if (entryPoint.$arguments.length == 1) { var argument = in_List.first(entryPoint.$arguments); var array = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_ARGUMENT, argument.name); array.type = new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent('string[]')).withType(Skew.Type.DYNAMIC); array.resolvedType = Skew.Type.DYNAMIC; entryPoint.$arguments = [array]; entryPoint.resolvedType.argumentTypes = [array.resolvedType]; // Create the list from the array if (entryPoint.block != null) { array.name = entryPoint.scope.generateName(array.name); argument.kind = Skew.SymbolKind.VARIABLE_LOCAL; argument.value = Skew.Node.createCall(new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent('new')).appendChild(new Skew.Node(Skew.NodeKind.TYPE).withType(argument.resolvedType)).withType(Skew.Type.DYNAMIC)).withType(Skew.Type.DYNAMIC).appendChild(Skew.Node.createSymbolReference(array)); entryPoint.block.prependChild(new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(Skew.Node.createVariable(argument))); } } } // Avoid emitting unnecessary stuff Skew.shakingPass(global, entryPoint, Skew.ShakingMode.USE_TYPES); this._markVirtualFunctions(global); var emitIndividualFiles = this._options.outputDirectory != null; var objects = this._collectObjects(global); for (var i1 = 0, list1 = objects, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var object = in_List.get(list1, i1); // Convert "flags" types to wrapped types if (object.kind == Skew.SymbolKind.OBJECT_FLAGS) { object.kind = Skew.SymbolKind.OBJECT_WRAPPED; object.wrappedType = this._cache.intType; // Enum values become normal global variables for (var i = 0, list = object.variables, count = list.length; i < count; i = i + 1 | 0) { var variable = in_List.get(list, i); if (variable.kind == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS) { variable.kind = Skew.SymbolKind.VARIABLE_GLOBAL; variable.flags |= Skew.SymbolFlags.IS_CSHARP_CONST; } } } } // All code in C# is inside objects, so just emit objects recursively for (var i2 = 0, list2 = objects, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var object1 = in_List.get(list2, i2); // Nested objects will be emitted by their parent if (object1.parent != null && object1.parent.kind == Skew.SymbolKind.OBJECT_CLASS) { continue; } this._emitObject(object1); // Emit each object into its own file if requested if (emitIndividualFiles) { this._finalizeEmittedFile(); this._createSource(this._options.outputDirectory + '/' + Skew.CSharpEmitter._fullName(object1) + '.cs', Skew.EmitMode.SKIP_IF_EMPTY); } } // Emit a single file if requested if (!emitIndividualFiles) { this._finalizeEmittedFile(); this._createSource(this._options.outputFile, Skew.EmitMode.ALWAYS_EMIT); } }; Skew.CSharpEmitter.prototype._moveGlobalsIntoClasses = function(symbol) { if (!Skew.in_SymbolKind.isNamespaceOrGlobal(symbol.kind)) { return; } // Just change namespaces into classes if there aren't nested objects if (symbol.kind == Skew.SymbolKind.OBJECT_NAMESPACE && symbol.objects.length == 0 && (!(symbol.functions.length == 0) || !(symbol.variables.length == 0))) { symbol.kind = Skew.SymbolKind.OBJECT_CLASS; return; } var globals = null; var lazilyCreateGlobals = function() { if (globals == null) { globals = new Skew.ObjectSymbol(Skew.SymbolKind.OBJECT_CLASS, symbol.scope.generateName(symbol.kind == Skew.SymbolKind.OBJECT_NAMESPACE ? symbol.name + 'Globals' : 'Globals')); globals.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, globals); globals.state = Skew.SymbolState.INITIALIZED; globals.parent = symbol; symbol.objects.push(globals); } }; for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._moveGlobalsIntoClasses(object); } in_List.removeIf(symbol.functions, function($function) { if ($function.kind != Skew.SymbolKind.FUNCTION_ANNOTATION && !$function.isImported()) { lazilyCreateGlobals(); $function.parent = globals; globals.functions.push($function); return true; } return false; }); in_List.removeIf(symbol.variables, function(variable) { if (variable.kind == Skew.SymbolKind.VARIABLE_GLOBAL && !variable.isImported()) { lazilyCreateGlobals(); variable.parent = globals; globals.variables.push(variable); return true; } return false; }); }; Skew.CSharpEmitter.prototype._adjustNamespace = function(symbol) { // Get the namespace chain for this symbol var symbols = []; while (symbol != null && symbol.kind != Skew.SymbolKind.OBJECT_GLOBAL) { if (symbol.kind == Skew.SymbolKind.OBJECT_NAMESPACE) { symbols.unshift(symbol); } symbol = symbol.parent; } // Find the intersection var limit = Math.min(this._namespaceStack.length, symbols.length); var i = 0; while (i < limit) { if (in_List.get(this._namespaceStack, i) != in_List.get(symbols, i)) { break; } i = i + 1 | 0; } // Leave the old namespace while (this._namespaceStack.length > i) { var object = in_List.takeLast(this._namespaceStack); this._decreaseIndent(); this._emit(this._indent + '}\n'); this._emitNewlineAfterSymbol(object); } // Enter the new namespace while (this._namespaceStack.length < symbols.length) { var object1 = in_List.get(symbols, this._namespaceStack.length); this._emitNewlineBeforeSymbol(object1); this._emit(this._indent + 'namespace ' + Skew.CSharpEmitter._mangleName(object1) + '\n'); this._emit(this._indent + '{\n'); this._increaseIndent(); this._namespaceStack.push(object1); } }; Skew.CSharpEmitter.prototype._finalizeEmittedFile = function() { var usings = Array.from(this._usingNames.keys()); if (!(usings.length == 0)) { // Sort so the order is deterministic usings.sort(Skew.SORT_STRINGS); for (var i = 0, list = usings, count = list.length; i < count; i = i + 1 | 0) { var using = in_List.get(list, i); this._emitPrefix('using ' + using + ';\n'); } this._emitPrefix('\n'); } this._adjustNamespace(null); this._previousSymbol = null; this._symbolsCheckedForUsing = new Map(); this._usingNames = new Map(); }; Skew.CSharpEmitter.prototype._handleSymbol = function(symbol) { if (!Skew.in_SymbolKind.isLocal(symbol.kind) && !this._symbolsCheckedForUsing.has(symbol.id)) { in_IntMap.set(this._symbolsCheckedForUsing, symbol.id, 0); if (symbol.annotations != null) { for (var i = 0, list = symbol.annotations, count = list.length; i < count; i = i + 1 | 0) { var annotation = in_List.get(list, i); if (annotation.symbol != null && annotation.symbol.fullName() == 'using') { var value = annotation.annotationValue(); if (value.childCount() == 2) { in_StringMap.set(this._usingNames, value.lastChild().asString(), 0); } } } } if (symbol.parent != null) { this._handleSymbol(symbol.parent); } } }; Skew.CSharpEmitter.prototype._emitNewlineBeforeSymbol = function(symbol) { if (this._previousSymbol != null && (!Skew.in_SymbolKind.isVariable(this._previousSymbol.kind) || !Skew.in_SymbolKind.isVariable(symbol.kind) || symbol.comments != null)) { this._emit('\n'); } this._previousSymbol = null; }; Skew.CSharpEmitter.prototype._emitNewlineAfterSymbol = function(symbol) { this._previousSymbol = symbol; }; Skew.CSharpEmitter.prototype._emitNewlineBeforeStatement = function(node) { if (this._previousNode != null && (node.comments != null || !Skew.CSharpEmitter._isCompactNodeKind(this._previousNode.kind) || !Skew.CSharpEmitter._isCompactNodeKind(node.kind))) { this._emit('\n'); } this._previousNode = null; }; Skew.CSharpEmitter.prototype._emitNewlineAfterStatement = function(node) { this._previousNode = node; }; Skew.CSharpEmitter.prototype._emitComments = function(comments) { if (comments != null) { for (var i1 = 0, list1 = comments, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var comment = in_List.get(list1, i1); for (var i = 0, list = comment.lines, count = list.length; i < count; i = i + 1 | 0) { var line = in_List.get(list, i); this._emit(this._indent + '//' + line + '\n'); } if (comment.hasGapBelow) { this._emit('\n'); } } } }; Skew.CSharpEmitter.prototype._emitObject = function(symbol) { this._handleSymbol(symbol); if (symbol.isImported() || Skew.in_SymbolKind.isNamespaceOrGlobal(symbol.kind)) { return; } this._adjustNamespace(symbol); this._emitNewlineBeforeSymbol(symbol); this._emitComments(symbol.comments); this._emit(this._indent + 'public '); if (symbol.isAbstract()) { this._emit('abstract '); } switch (symbol.kind) { case Skew.SymbolKind.OBJECT_CLASS: { this._emit('class '); break; } case Skew.SymbolKind.OBJECT_ENUM: case Skew.SymbolKind.OBJECT_FLAGS: { this._emit('enum '); break; } case Skew.SymbolKind.OBJECT_INTERFACE: { this._emit('interface '); break; } case Skew.SymbolKind.OBJECT_WRAPPED: case Skew.SymbolKind.OBJECT_NAMESPACE: { this._emit('static class '); break; } default: { assert(false); break; } } this._emit(Skew.CSharpEmitter._mangleName(symbol)); this._emitTypeParameters(symbol.parameters); if ((symbol.$extends != null || symbol.$implements != null) && symbol.kind != Skew.SymbolKind.OBJECT_WRAPPED) { this._emit(' : '); if (symbol.$extends != null) { this._emitExpressionOrType(symbol.$extends, symbol.baseType); } if (symbol.$implements != null) { for (var i = 0, list = symbol.$implements, count = list.length; i < count; i = i + 1 | 0) { var node = in_List.get(list, i); if (node != in_List.first(symbol.$implements) || symbol.$extends != null) { this._emit(', '); } this._emitExpressionOrType(node, node.resolvedType); } } } this._emit('\n' + this._indent + '{\n'); this._increaseIndent(); for (var i1 = 0, list1 = symbol.objects, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var object = in_List.get(list1, i1); this._emitObject(object); } for (var i2 = 0, list2 = symbol.variables, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); this._emitVariable(variable); } for (var i3 = 0, list3 = symbol.functions, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var $function = in_List.get(list3, i3); this._emitFunction($function); } this._decreaseIndent(); this._emit(this._indent + '}\n'); this._emitNewlineAfterSymbol(symbol); }; Skew.CSharpEmitter.prototype._emitTypeParameters = function(parameters) { if (parameters != null) { this._emit('<'); for (var i = 0, list = parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); if (parameter != in_List.first(parameters)) { this._emit(', '); } this._emit(Skew.CSharpEmitter._mangleName(parameter)); } this._emit('>'); } }; Skew.CSharpEmitter.prototype._emitArgumentList = function(symbol) { this._emit('('); for (var i = 0, list = symbol.$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); if (argument != in_List.first(symbol.$arguments)) { this._emit(', '); } this._emitExpressionOrType(argument.type, argument.resolvedType); this._emit(' ' + Skew.CSharpEmitter._mangleName(argument)); } this._emit(')'); }; Skew.CSharpEmitter.prototype._emitVariable = function(symbol) { this._handleSymbol(symbol); if (symbol.isImported()) { return; } this._emitNewlineBeforeSymbol(symbol); this._emitComments(symbol.comments); if (symbol.kind == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS) { this._emit(this._indent + Skew.CSharpEmitter._mangleName(symbol)); if (symbol.value != null) { // Enum values are initialized with integers symbol.value.resolvedType = this._cache.intType; this._emit(' = '); this._emitExpression(symbol.value, Skew.Precedence.COMMA); } this._emit(',\n'); } else { this._emit(this._indent + 'public '); if (symbol.kind == Skew.SymbolKind.VARIABLE_GLOBAL) { this._emit(symbol.isCSharpConst() ? 'const ' : 'static '); } this._emitExpressionOrType(symbol.type, symbol.resolvedType); this._emit(' ' + Skew.CSharpEmitter._mangleName(symbol)); if (symbol.value != null) { this._emit(' = '); this._emitExpression(symbol.value, Skew.Precedence.COMMA); } this._emit(';\n'); } this._emitNewlineAfterSymbol(symbol); }; Skew.CSharpEmitter.prototype._emitFunction = function(symbol) { this._handleSymbol(symbol); if (symbol.isImported()) { return; } // C# has sane capture rules for "this" so no variable insertion is needed if (symbol.$this != null) { symbol.$this.name = 'this'; symbol.$this.flags |= Skew.SymbolFlags.IS_EXPORTED; } this._enclosingFunction = symbol; this._emitNewlineBeforeSymbol(symbol); this._emitComments(symbol.comments); this._emit(this._indent); if (symbol.parent.kind != Skew.SymbolKind.OBJECT_INTERFACE) { this._emit('public '); } if (symbol.kind == Skew.SymbolKind.FUNCTION_GLOBAL) { this._emit('static '); } if (symbol.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { if (symbol.parent.kind != Skew.SymbolKind.OBJECT_INTERFACE) { if (symbol.block == null) { this._emit('abstract '); } else if (symbol.overridden != null) { this._emit('override '); } else if (symbol.isVirtual()) { this._emit('virtual '); } } this._emitExpressionOrType(symbol.returnType, symbol.resolvedType.returnType); this._emit(' '); } this._emit(Skew.CSharpEmitter._mangleName(symbol)); this._emitTypeParameters(symbol.parameters); this._emitArgumentList(symbol); var block = symbol.block; if (block == null) { this._emit(';\n'); } else { // Move the super constructor call out of the function body if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR && block.hasChildren()) { var first = block.firstChild(); if (first.kind == Skew.NodeKind.EXPRESSION) { var call = first.expressionValue(); if (call.kind == Skew.NodeKind.CALL && call.callValue().kind == Skew.NodeKind.SUPER) { this._emit(' : '); first.remove(); this._emitExpression(call, Skew.Precedence.LOWEST); } } } this._emit('\n'); this._emitBlock(block); this._emit('\n'); } this._emitNewlineAfterSymbol(symbol); this._enclosingFunction = null; }; Skew.CSharpEmitter.prototype._emitType = function(type) { if (type == null) { this._emit('void'); return; } type = this._cache.unwrappedType(type); if (type == Skew.Type.DYNAMIC) { this._emit('dynamic'); } else if (type.kind == Skew.TypeKind.LAMBDA) { var argumentTypes = type.argumentTypes; var returnType = type.returnType; this._emit(returnType != null ? 'System.Func' : 'System.Action'); if (!(argumentTypes.length == 0) || returnType != null) { this._emit('<'); for (var i = 0, count = argumentTypes.length; i < count; i = i + 1 | 0) { if (i != 0) { this._emit(', '); } this._emitType(in_List.get(argumentTypes, i)); } if (returnType != null) { if (!(argumentTypes.length == 0)) { this._emit(', '); } this._emitType(returnType); } this._emit('>'); } } else { assert(type.kind == Skew.TypeKind.SYMBOL); this._handleSymbol(type.symbol); this._emit(Skew.CSharpEmitter._fullName(type.symbol)); if (type.isParameterized()) { this._emit('<'); if (this._cache.isIntMap(type) || this._cache.isStringMap(type)) { this._emit(this._cache.isIntMap(type) ? 'int' : 'string'); this._emit(', '); this._emitType(in_List.first(type.substitutions)); } else { for (var i1 = 0, count1 = type.substitutions.length; i1 < count1; i1 = i1 + 1 | 0) { if (i1 != 0) { this._emit(', '); } this._emitType(in_List.get(type.substitutions, i1)); } } this._emit('>'); } } }; Skew.CSharpEmitter.prototype._emitExpressionOrType = function(node, type) { if (node != null && (type == null || type == Skew.Type.DYNAMIC)) { this._emitExpression(node, Skew.Precedence.LOWEST); } else { this._emitType(type); } }; Skew.CSharpEmitter.prototype._emitStatements = function(node) { this._previousNode = null; for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._emitNewlineBeforeStatement(child); this._emitComments(child.comments); this._emitStatement(child); this._emitNewlineAfterStatement(child); } this._previousNode = null; }; Skew.CSharpEmitter.prototype._emitBlock = function(node) { assert(node.kind == Skew.NodeKind.BLOCK); this._emit(this._indent + '{\n'); this._increaseIndent(); this._emitStatements(node); this._decreaseIndent(); this._emit(this._indent + '}'); }; Skew.CSharpEmitter.prototype._emitIf = function(node) { this._emit('if ('); this._emitExpression(node.ifTest(), Skew.Precedence.LOWEST); this._emit(')\n'); this._emitBlock(node.ifTrue()); this._emit('\n'); var block = node.ifFalse(); if (block != null) { var singleIf = block.hasOneChild() && block.firstChild().kind == Skew.NodeKind.IF ? block.firstChild() : null; if (block.comments != null || singleIf != null && singleIf.comments != null) { this._emit('\n'); this._emitComments(block.comments); if (singleIf != null) { this._emitComments(singleIf.comments); } } this._emit(this._indent + 'else'); if (singleIf != null) { this._emit(' '); this._emitIf(singleIf); } else { this._emit('\n'); this._emitBlock(block); this._emit('\n'); } } }; Skew.CSharpEmitter.prototype._scanForSwitchBreak = function(node, loop) { if (node.kind == Skew.NodeKind.BREAK) { for (var parent = node.parent(); parent != loop; parent = parent.parent()) { if (parent.kind == Skew.NodeKind.SWITCH) { var label = in_IntMap.get(this._loopLabels, loop.id, null); if (label == null) { label = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, this._enclosingFunction.scope.generateName('label')); in_IntMap.set(this._loopLabels, loop.id, label); } in_IntMap.set(this._loopLabels, node.id, label); break; } } } // Stop at nested loops since those will be tested later else if (node == loop || !Skew.in_NodeKind.isLoop(node.kind)) { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._scanForSwitchBreak(child, loop); } } }; Skew.CSharpEmitter.prototype._emitStatement = function(node) { if (Skew.in_NodeKind.isLoop(node.kind)) { this._scanForSwitchBreak(node, node); } switch (node.kind) { case Skew.NodeKind.COMMENT_BLOCK: { break; } case Skew.NodeKind.VARIABLES: { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { var symbol = child.symbol.asVariableSymbol(); this._emit(this._indent); this._emitExpressionOrType(symbol.type, symbol.resolvedType); this._emit(' ' + Skew.CSharpEmitter._mangleName(symbol)); if (symbol.value != null) { this._emit(' = '); this._emitExpression(symbol.value, Skew.Precedence.ASSIGN); } this._emit(';\n'); } break; } case Skew.NodeKind.EXPRESSION: { this._emit(this._indent); this._emitExpression(node.expressionValue(), Skew.Precedence.LOWEST); this._emit(';\n'); break; } case Skew.NodeKind.BREAK: { var label = in_IntMap.get(this._loopLabels, node.id, null); if (label != null) { this._emit(this._indent + 'goto ' + Skew.CSharpEmitter._mangleName(label) + ';\n'); } else { this._emit(this._indent + 'break;\n'); } break; } case Skew.NodeKind.CONTINUE: { this._emit(this._indent + 'continue;\n'); break; } case Skew.NodeKind.IF: { this._emit(this._indent); this._emitIf(node); break; } case Skew.NodeKind.SWITCH: { var switchValue = node.switchValue(); this._emit(this._indent + 'switch ('); this._emitExpression(switchValue, Skew.Precedence.LOWEST); this._emit(')\n' + this._indent + '{\n'); this._increaseIndent(); for (var child1 = switchValue.nextSibling(); child1 != null; child1 = child1.nextSibling()) { var block = child1.caseBlock(); if (child1.previousSibling() != switchValue) { this._emit('\n'); } if (child1.hasOneChild()) { this._emit(this._indent + 'default:'); } else { for (var value = child1.firstChild(); value != block; value = value.nextSibling()) { if (value.previousSibling() != null) { this._emit('\n'); } this._emit(this._indent + 'case '); this._emitExpression(value, Skew.Precedence.LOWEST); this._emit(':'); } } this._emit('\n' + this._indent + '{\n'); this._increaseIndent(); this._emitStatements(block); if (block.hasControlFlowAtEnd()) { this._emit(this._indent + 'break;\n'); } this._decreaseIndent(); this._emit(this._indent + '}\n'); } this._decreaseIndent(); this._emit(this._indent + '}\n'); break; } case Skew.NodeKind.RETURN: { this._emit(this._indent + 'return'); var value1 = node.returnValue(); if (value1 != null) { this._emit(' '); this._emitExpression(value1, Skew.Precedence.LOWEST); } this._emit(';\n'); break; } case Skew.NodeKind.THROW: { this._emit(this._indent + 'throw '); this._emitExpression(node.throwValue(), Skew.Precedence.LOWEST); this._emit(';\n'); break; } case Skew.NodeKind.FOREACH: { this._emit(this._indent + 'foreach (var ' + Skew.CSharpEmitter._mangleName(node.symbol) + ' in '); this._emitExpression(node.foreachValue(), Skew.Precedence.LOWEST); this._emit(')\n'); this._emitBlock(node.foreachBlock()); this._emit('\n'); break; } case Skew.NodeKind.FOR: { var setup = node.forSetup(); var test = node.forTest(); var update = node.forUpdate(); this._emit(this._indent + 'for ('); if (!setup.isEmptySequence()) { if (setup.kind == Skew.NodeKind.VARIABLES) { var symbol1 = setup.firstChild().symbol.asVariableSymbol(); this._emitExpressionOrType(symbol1.type, symbol1.resolvedType); this._emit(' '); for (var child2 = setup.firstChild(); child2 != null; child2 = child2.nextSibling()) { symbol1 = child2.symbol.asVariableSymbol(); assert(child2.kind == Skew.NodeKind.VARIABLE); if (child2.previousSibling() != null) { this._emit(', '); } this._emit(Skew.CSharpEmitter._mangleName(symbol1) + ' = '); this._emitExpression(symbol1.value, Skew.Precedence.COMMA); } } else { this._emitExpression(setup, Skew.Precedence.LOWEST); } } this._emit('; '); if (!test.isEmptySequence()) { this._emitExpression(test, Skew.Precedence.LOWEST); } this._emit('; '); if (!update.isEmptySequence()) { this._emitExpression(update, Skew.Precedence.LOWEST); } this._emit(')\n'); this._emitBlock(node.forBlock()); this._emit('\n'); break; } case Skew.NodeKind.TRY: { var tryBlock = node.tryBlock(); var finallyBlock = node.finallyBlock(); this._emit(this._indent + 'try\n'); this._emitBlock(tryBlock); this._emit('\n'); for (var child3 = tryBlock.nextSibling(); child3 != finallyBlock; child3 = child3.nextSibling()) { if (child3.comments != null) { this._emit('\n'); this._emitComments(child3.comments); } this._emit(this._indent + 'catch'); if (child3.symbol != null) { this._emit(' ('); this._emitExpressionOrType(child3.symbol.asVariableSymbol().type, child3.symbol.resolvedType); this._emit(' ' + Skew.CSharpEmitter._mangleName(child3.symbol) + ')'); } this._emit('\n'); this._emitBlock(child3.catchBlock()); this._emit('\n'); } if (finallyBlock != null) { if (finallyBlock.comments != null) { this._emit('\n'); this._emitComments(finallyBlock.comments); } this._emit(this._indent + 'finally\n'); this._emitBlock(finallyBlock); this._emit('\n'); } break; } case Skew.NodeKind.WHILE: { this._emit(this._indent + 'while ('); this._emitExpression(node.whileTest(), Skew.Precedence.LOWEST); this._emit(')\n'); this._emitBlock(node.whileBlock()); this._emit('\n'); break; } default: { assert(false); break; } } if (Skew.in_NodeKind.isLoop(node.kind)) { var label1 = in_IntMap.get(this._loopLabels, node.id, null); if (label1 != null) { this._emit(this._indent + Skew.CSharpEmitter._mangleName(label1) + (node.nextSibling() != null ? ':\n' : ':;\n')); } } }; Skew.CSharpEmitter.prototype._emitContent = function(content) { switch (content.kind()) { case Skew.ContentKind.BOOL: { this._emit(Skew.in_Content.asBool(content).toString()); break; } case Skew.ContentKind.INT: { this._emit(Skew.in_Content.asInt(content).toString()); break; } case Skew.ContentKind.DOUBLE: { var value = Skew.in_Content.asDouble(content); if (!isFinite(value)) { in_StringMap.set(this._usingNames, 'System', 0); } this._emit(isNaN(value) ? 'Double.NaN' : value == 1 / 0 ? 'Double.PositiveInfinity' : value == -(1 / 0) ? 'Double.NegativeInfinity' : Skew.doubleToStringWithDot(value)); break; } case Skew.ContentKind.STRING: { this._emit(Skew.quoteString(Skew.in_Content.asString(content), Skew.QuoteStyle.DOUBLE, Skew.QuoteOctal.NORMAL)); break; } } }; Skew.CSharpEmitter.prototype._emitCommaSeparatedExpressions = function(from, to) { while (from != to) { this._emitExpression(from, Skew.Precedence.COMMA); from = from.nextSibling(); if (from != to) { this._emit(', '); } } }; Skew.CSharpEmitter.prototype._emitExpression = function(node, precedence) { var kind = node.kind; var symbol = node.symbol; if (symbol != null) { this._handleSymbol(symbol); } switch (kind) { case Skew.NodeKind.TYPE: case Skew.NodeKind.LAMBDA_TYPE: { this._emitType(node.resolvedType); break; } case Skew.NodeKind.NULL: { this._emit('null'); break; } case Skew.NodeKind.NAME: { this._emit(symbol != null ? Skew.CSharpEmitter._fullName(symbol) : node.asString()); break; } case Skew.NodeKind.DOT: { this._emitExpression(node.dotTarget(), Skew.Precedence.MEMBER); this._emit('.' + (symbol != null ? Skew.CSharpEmitter._mangleName(symbol) : node.asString())); break; } case Skew.NodeKind.CONSTANT: { var wrap = precedence == Skew.Precedence.MEMBER && node.isNumberLessThanZero() && (!node.isDouble() || isFinite(node.asDouble())); if (wrap) { this._emit('('); } if (node.resolvedType.isEnumOrFlags()) { this._emit('('); this._emitType(node.resolvedType); this._emit(')'); } this._emitContent(node.content); if (wrap) { this._emit(')'); } break; } case Skew.NodeKind.CALL: { var value = node.callValue(); var wrap1 = value.kind == Skew.NodeKind.LAMBDA; if (wrap1) { this._emit('new '); this._emitType(value.resolvedType); this._emit('('); } if (value.kind == Skew.NodeKind.SUPER) { this._emit('base'); if (symbol.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { this._emit('.'); this._emit(Skew.CSharpEmitter._mangleName(symbol)); } } else if (symbol != null && symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { this._emit('new '); this._emitType(node.resolvedType); } else if (value.kind == Skew.NodeKind.DOT && value.asString() == 'new') { this._emit('new '); this._emitExpression(value.dotTarget(), Skew.Precedence.MEMBER); } else { this._emitExpression(value, Skew.Precedence.UNARY_POSTFIX); } if (wrap1) { this._emit(')'); } this._emit('('); this._emitCommaSeparatedExpressions(value.nextSibling(), null); this._emit(')'); break; } case Skew.NodeKind.CAST: { var resolvedType = node.resolvedType; var type = node.castType(); var value1 = node.castValue(); if (type.kind == Skew.NodeKind.TYPE && type.resolvedType == Skew.Type.DYNAMIC) { this._emitExpression(value1, precedence); } // Automatically promote integer literals to doubles instead of using a cast else if (this._cache.isEquivalentToDouble(resolvedType) && value1.isInt()) { this._emitExpression(this._cache.createDouble(value1.asInt()), precedence); } // C# doesn't have a cast from bool to int else if (this._cache.isNumeric(resolvedType) && value1.resolvedType == this._cache.boolType) { this._emitExpression(Skew.Node.createHook(value1.remove(), this._cache.createInt(1), this._cache.createInt(0)).withType(this._cache.intType), precedence); } // C# doesn't have a cast from int to bool else if (resolvedType == this._cache.boolType && this._cache.isNumeric(value1.resolvedType)) { this._emitExpression(Skew.Node.createBinary(Skew.NodeKind.NOT_EQUAL, value1.remove(), this._cache.createInt(0)).withType(this._cache.boolType), precedence); } // Only emit a cast if the underlying types are different else if (this._cache.unwrappedType(value1.resolvedType) != this._cache.unwrappedType(type.resolvedType) || type.resolvedType == Skew.Type.DYNAMIC) { if (Skew.Precedence.UNARY_POSTFIX < precedence) { this._emit('('); } this._emit('('); this._emitExpressionOrType(type, type.resolvedType); this._emit(')'); this._emitExpression(value1, Skew.Precedence.UNARY_POSTFIX); if (Skew.Precedence.UNARY_POSTFIX < precedence) { this._emit(')'); } } // Otherwise, pretend the cast isn't there else { this._emitExpression(value1, precedence); } break; } case Skew.NodeKind.TYPE_CHECK: { var value2 = node.typeCheckValue(); var type1 = node.typeCheckType(); if (Skew.Precedence.COMPARE < precedence) { this._emit('('); } this._emitExpression(value2, Skew.Precedence.LOWEST); this._emit(' is '); this._emitExpressionOrType(type1, type1.resolvedType); if (Skew.Precedence.COMPARE < precedence) { this._emit(')'); } break; } case Skew.NodeKind.INITIALIZER_LIST: { this._emit('new '); this._emitType(node.resolvedType); if (node.hasChildren()) { this._emit(' { '); this._emitCommaSeparatedExpressions(node.firstChild(), null); this._emit(' }'); } else { this._emit('()'); } break; } case Skew.NodeKind.INDEX: { this._emitExpression(node.indexLeft(), Skew.Precedence.UNARY_POSTFIX); this._emit('['); this._emitExpression(node.indexRight(), Skew.Precedence.LOWEST); this._emit(']'); break; } case Skew.NodeKind.ASSIGN_INDEX: { if (Skew.Precedence.ASSIGN < precedence) { this._emit('('); } this._emitExpression(node.assignIndexLeft(), Skew.Precedence.UNARY_POSTFIX); this._emit('['); this._emitExpression(node.assignIndexCenter(), Skew.Precedence.LOWEST); this._emit('] = '); this._emitExpression(node.assignIndexRight(), Skew.Precedence.ASSIGN); if (Skew.Precedence.ASSIGN < precedence) { this._emit(')'); } break; } case Skew.NodeKind.PARAMETERIZE: { var value3 = node.parameterizeValue(); if (value3.isType()) { this._emitType(node.resolvedType); } else { this._emitExpression(value3, precedence); this._emit('<'); this._emitCommaSeparatedExpressions(value3.nextSibling(), null); this._emit('>'); } break; } case Skew.NodeKind.SEQUENCE: { if (Skew.Precedence.COMMA <= precedence) { this._emit('('); } this._emitCommaSeparatedExpressions(node.firstChild(), null); if (Skew.Precedence.COMMA <= precedence) { this._emit(')'); } break; } case Skew.NodeKind.HOOK: { if (Skew.Precedence.ASSIGN < precedence) { this._emit('('); } this._emitExpression(node.hookTest(), Skew.Precedence.LOGICAL_OR); this._emit(' ? '); this._emitExpression(node.hookTrue(), Skew.Precedence.ASSIGN); this._emit(' : '); this._emitExpression(node.hookFalse(), Skew.Precedence.ASSIGN); if (Skew.Precedence.ASSIGN < precedence) { this._emit(')'); } break; } case Skew.NodeKind.LAMBDA: { var oldEnclosingFunction = this._enclosingFunction; this._enclosingFunction = symbol.asFunctionSymbol(); this._emitArgumentList(symbol.asFunctionSymbol()); this._emit(' =>\n'); this._emitBlock(symbol.asFunctionSymbol().block); this._enclosingFunction = oldEnclosingFunction; break; } case Skew.NodeKind.COMPLEMENT: case Skew.NodeKind.NEGATIVE: case Skew.NodeKind.NOT: case Skew.NodeKind.POSITIVE: case Skew.NodeKind.POSTFIX_DECREMENT: case Skew.NodeKind.POSTFIX_INCREMENT: case Skew.NodeKind.PREFIX_DECREMENT: case Skew.NodeKind.PREFIX_INCREMENT: { var value4 = node.unaryValue(); var info = in_IntMap.get1(Skew.operatorInfo, kind); var sign = node.sign(); if (info.precedence < precedence) { this._emit('('); } if (!Skew.in_NodeKind.isUnaryPostfix(kind)) { this._emit(info.text); // Prevent "x - -1" from becoming "x--1" if (sign != Skew.NodeKind.NULL && sign == value4.sign()) { this._emit(' '); } } this._emitExpression(value4, info.precedence); if (Skew.in_NodeKind.isUnaryPostfix(kind)) { this._emit(info.text); } if (info.precedence < precedence) { this._emit(')'); } break; } default: { if (Skew.in_NodeKind.isBinary(kind)) { var left = node.binaryLeft(); var right = node.binaryRight(); // Some types stupidly don't implement operator "==" if ((kind == Skew.NodeKind.EQUAL || kind == Skew.NodeKind.NOT_EQUAL) && left.resolvedType.isParameter() && right.resolvedType.isParameter()) { if (kind == Skew.NodeKind.NOT_EQUAL) { this._emit('!'); } this._emit('EqualityComparer<'); this._emitType(left.resolvedType); this._emit('>.Default.Equals('); this._emitExpression(left, Skew.Precedence.COMMA); this._emit(', '); this._emitExpression(right, Skew.Precedence.COMMA); this._emit(')'); in_StringMap.set(this._usingNames, 'System.Collections.Generic', 0); } else { var info1 = in_IntMap.get1(Skew.operatorInfo, kind); if (info1.precedence < precedence) { this._emit('('); } this._emitExpression(left, info1.precedence + (info1.associativity == Skew.Associativity.RIGHT | 0) | 0); this._emit(' ' + info1.text + ' '); this._emitExpression(right, info1.precedence + (info1.associativity == Skew.Associativity.LEFT | 0) | 0); if (info1.precedence < precedence) { this._emit(')'); } } } else { assert(false); } break; } } }; Skew.CSharpEmitter._isCompactNodeKind = function(kind) { return kind == Skew.NodeKind.EXPRESSION || kind == Skew.NodeKind.VARIABLES || Skew.in_NodeKind.isJump(kind); }; Skew.CSharpEmitter._fullName = function(symbol) { var parent = symbol.parent; if (parent != null && parent.kind != Skew.SymbolKind.OBJECT_GLOBAL && !Skew.in_SymbolKind.isParameter(symbol.kind)) { var enclosingName = Skew.CSharpEmitter._fullName(parent); if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { return enclosingName; } return enclosingName + '.' + Skew.CSharpEmitter._mangleName(symbol); } return Skew.CSharpEmitter._mangleName(symbol); }; Skew.CSharpEmitter._mangleName = function(symbol) { symbol = symbol.forwarded(); if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { symbol = symbol.parent; } if (!symbol.isImportedOrExported() && Skew.CSharpEmitter._isKeyword.has(symbol.name)) { return '_' + symbol.name; } return symbol.name; }; Skew.JavaScriptEmitter = function(_context, _options, _cache) { Skew.Emitter.call(this); this._context = _context; this._options = _options; this._cache = _cache; this._isSpecialVariableNeeded = new Map(); this._loopLabels = new Map(); this._specialVariables = new Map(); this._global = null; this._symbolsToExport = []; this._allSpecialVariables = null; this._exportsNamespace = null; this._enclosingFunction = null; this._enclosingLoop = null; this._namespacePrefix = ''; this._previousNode = null; this._previousSymbol = null; this._parenthesizeInExpressions = 0; this._currentColumn = 0; this._currentLine = 0; this._generator = new Skew.SourceMapGenerator(); this._previousSource = null; this._previousStart = 0; this._sourceMap = false; this._allSymbols = []; this._localVariableUnionFind = new Skew.UnionFind(); this._namingGroupIndexForSymbol = new Map(); this._symbolCounts = new Map(); this._nextSymbolName = 0; this._mangle = false; this._minify = false; this._needsSemicolon = false; this._newline = '\n'; this._space = ' '; this._previousCodeUnit = 0; this._currentSelf = null; this._needsSelf = false; }; __extends(Skew.JavaScriptEmitter, Skew.Emitter); Skew.JavaScriptEmitter.prototype.visit = function(global) { this._mangle = this._options.jsMangle; this._minify = this._options.jsMinify; this._sourceMap = this._options.jsSourceMap; this._global = global; if (this._minify) { this._indentAmount = ''; this._newline = ''; this._space = ''; } // Load special-cased variables for (var i = 0, list = global.variables, count = list.length; i < count; i = i + 1 | 0) { var variable = in_List.get(list, i); var special = in_StringMap.get(Skew.JavaScriptEmitter._specialVariableMap, variable.name, Skew.JavaScriptEmitter.SpecialVariable.NONE); if (special != Skew.JavaScriptEmitter.SpecialVariable.NONE) { in_IntMap.set(this._specialVariables, special, variable); } } assert(this._specialVariables.has(Skew.JavaScriptEmitter.SpecialVariable.AS_STRING)); assert(this._specialVariables.has(Skew.JavaScriptEmitter.SpecialVariable.CREATE)); assert(this._specialVariables.has(Skew.JavaScriptEmitter.SpecialVariable.EXTENDS)); assert(this._specialVariables.has(Skew.JavaScriptEmitter.SpecialVariable.IS_BOOL)); assert(this._specialVariables.has(Skew.JavaScriptEmitter.SpecialVariable.IS_DOUBLE)); assert(this._specialVariables.has(Skew.JavaScriptEmitter.SpecialVariable.IS_INT)); assert(this._specialVariables.has(Skew.JavaScriptEmitter.SpecialVariable.IS_STRING)); assert(this._specialVariables.has(Skew.JavaScriptEmitter.SpecialVariable.MULTIPLY)); assert(this._specialVariables.has(Skew.JavaScriptEmitter.SpecialVariable.PROTOTYPE)); // These don't need to be initialized in_IntMap.get1(this._specialVariables, Skew.JavaScriptEmitter.SpecialVariable.PROTOTYPE).value = null; // Sort these so their order is deterministic this._allSpecialVariables = Array.from(this._specialVariables.values()); this._allSpecialVariables.sort(Skew.Symbol.SORT_VARIABLES_BY_ID); // Preprocess the code if (this._mangle) { this._liftGlobals1(global); } if (this._options.inlineAllFunctions) { this._maybeInlineFunctions(global); } Skew.shakingPass(global, this._cache.entryPointSymbol, Skew.ShakingMode.IGNORE_TYPES); this._prepareGlobal(global); this._convertLambdasToFunctions(global); var objects = this._sortedObjects(global); // Make sure the "__create" variable is inserted when used even if "__extends" isn't used var create = in_IntMap.get1(this._specialVariables, Skew.JavaScriptEmitter.SpecialVariable.CREATE); if (global.variables.indexOf(create) != -1) { in_IntMap.set(this._isSpecialVariableNeeded, create.id, 0); } // The entire body of code is wrapped in a closure for safety this._emit(this._indent + '(function(' + (this._exportsNamespace != null ? Skew.JavaScriptEmitter._mangleName(this._exportsNamespace) : '') + ')' + this._space + '{' + this._newline + ''); this._increaseIndent(); // Emit special-cased variables that must come first for (var i1 = 0, list1 = this._allSpecialVariables, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var variable1 = in_List.get(list1, i1); if (this._isSpecialVariableNeeded.has(variable1.id)) { if (variable1.value != null && variable1.value.kind == Skew.NodeKind.LAMBDA) { this._emitFunction(this._convertLambdaToFunction(variable1)); } else { this._emitVariable(variable1); } } } // Emit objects and functions for (var i2 = 0, list2 = objects, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var object = in_List.get(list2, i2); this._emitObject(object); } // Emit variables var statement = new Skew.Node(Skew.NodeKind.VARIABLES); for (var i4 = 0, list4 = objects, count4 = list4.length; i4 < count4; i4 = i4 + 1 | 0) { var object1 = in_List.get(list4, i4); this._namespacePrefix = ''; for (var o = object1; o.kind != Skew.SymbolKind.OBJECT_GLOBAL; o = o.parent.asObjectSymbol()) { this._namespacePrefix = Skew.JavaScriptEmitter._mangleName(o) + '.' + this._namespacePrefix; } for (var i3 = 0, list3 = object1.variables, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var variable2 = in_List.get(list3, i3); if (!(this._allSpecialVariables.indexOf(variable2) != -1)) { if (this._mangle && this._namespacePrefix == '' && !variable2.isImportedOrExported()) { statement.appendChild(Skew.Node.createVariable(variable2)); } else { this._emitVariable(variable2); } } } } this._namespacePrefix = ''; // Group adjacent variables into a single statement during mangling if (statement.hasChildren()) { this._emitNewlineBeforeSymbol(statement.firstChild().symbol); this._emitStatement(statement); this._emitNewlineAfterSymbol(statement.firstChild().symbol); for (var child = statement.firstChild(); child != null; child = child.nextSibling()) { child.removeChildren(); } } // Emit entry point var entryPointSymbol = this._cache.entryPointSymbol; if (entryPointSymbol != null) { var type = entryPointSymbol.resolvedType; var callText = Skew.JavaScriptEmitter._fullName(entryPointSymbol) + (type.argumentTypes.length == 0 ? '()' : '(process.argv.slice(2))'); this._emitSemicolonIfNeeded(); this._emit(this._newline + this._indent + (type.returnType == this._cache.intType ? 'process.exit(' + callText + ')' : callText)); this._emitSemicolonAfterStatement(); } // End the closure wrapping everything this._decreaseIndent(); this._emit(this._indent + '})(' + (this._exportsNamespace != null ? 'this' : '') + ');\n'); var codeName = this._options.outputDirectory != null ? this._options.outputDirectory + '/compiled.js' : this._options.outputFile; var mapName = codeName != null ? codeName + '.map' : null; // Obfuscate the sourceMappingURL so it's not incorrectly picked up as the // sourceMappingURL for the compiled JavaScript compiler file if (this._sourceMap) { this._emit('/'); this._emit('/# sourceMappingURL=' + Skew.splitPath(mapName).entry + '\n'); } this._createSource(codeName, Skew.EmitMode.ALWAYS_EMIT); // Create the source map if (this._sourceMap) { this._emit(this._generator.toString()); this._createSource(mapName, Skew.EmitMode.ALWAYS_EMIT); } }; Skew.JavaScriptEmitter.prototype._emit = function(text) { if (this._minify || this._sourceMap) { var n = text.length; for (var i = 0, count = n; i < count; i = i + 1 | 0) { if (in_string.get1(text, i) == 10) { this._currentColumn = 0; this._currentLine = this._currentLine + 1 | 0; } else { this._currentColumn = this._currentColumn + 1 | 0; } } if (n != 0) { this._previousCodeUnit = in_string.get1(text, n - 1 | 0); } } this._code.buffer += text; }; Skew.JavaScriptEmitter.prototype._liftGlobals1 = function(global) { var globalObjects = []; var globalFunctions = []; var globalVariables = []; this._liftGlobals2(global, globalObjects, globalFunctions, globalVariables); for (var i = 0, list = globalObjects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); object.parent = global; } for (var i1 = 0, list1 = globalFunctions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); $function.parent = global; } for (var i2 = 0, list2 = globalVariables, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); variable.parent = global; } in_List.append1(global.objects, globalObjects); in_List.append1(global.functions, globalFunctions); in_List.append1(global.variables, globalVariables); }; Skew.JavaScriptEmitter.prototype._liftGlobals2 = function(symbol, globalObjects, globalFunctions, globalVariables) { var self = this; var shouldLiftGlobals = symbol.parent != null; // Scan over child objects in_List.removeIf(symbol.objects, function(object) { self._liftGlobals2(object, globalObjects, globalFunctions, globalVariables); if (shouldLiftGlobals && !object.isImportedOrExported()) { globalObjects.push(object); return true; } return false; }); in_List.removeIf(symbol.functions, function($function) { if (shouldLiftGlobals && $function.kind == Skew.SymbolKind.FUNCTION_GLOBAL && !$function.isImportedOrExported()) { globalFunctions.push($function); return true; } return false; }); // Scan over child variables in_List.removeIf(symbol.variables, function(variable) { if (shouldLiftGlobals && variable.kind == Skew.SymbolKind.VARIABLE_GLOBAL && !variable.isImportedOrExported()) { globalVariables.push(variable); return true; } return false; }); }; Skew.JavaScriptEmitter.prototype._collectInlineableFunctions = function(symbol, listAppends, mapInserts) { for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._collectInlineableFunctions(object, listAppends, mapInserts); } for (var i3 = 0, list3 = symbol.functions, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var $function = in_List.get(list3, i3); if ($function.block == null || !$function.block.hasTwoChildren()) { continue; } var $arguments = $function.$arguments; // "foo([], 0)" => "[0]" where "foo" is "def foo(a, b) { a.push(b); return a }" if ($arguments.length == 2) { var first = $function.block.firstChild(); var second = $function.block.lastChild(); if (first.kind == Skew.NodeKind.EXPRESSION && first.expressionValue().kind == Skew.NodeKind.CALL && second.kind == Skew.NodeKind.RETURN && second.returnValue() != null) { var call = first.expressionValue(); var callValue = call.callValue(); if (call.hasTwoChildren() && callValue.kind == Skew.NodeKind.DOT && callValue.asString() == 'push' && Skew.JavaScriptEmitter._isReferenceTo(callValue.dotTarget(), in_List.get($arguments, 0)) && Skew.JavaScriptEmitter._isReferenceTo(call.lastChild(), in_List.get($arguments, 1)) && Skew.JavaScriptEmitter._isReferenceTo(second.returnValue(), in_List.get($arguments, 0))) { for (var i1 = 0, list1 = this._context.callGraph.callInfoForSymbol($function).callSites, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var callSite = in_List.get(list1, i1); if (callSite != null && callSite.callNode.kind == Skew.NodeKind.CALL) { assert(callSite.callNode.symbol == $function); listAppends.push(callSite.callNode); } } } } } // "foo({}, 0, 1)" => "{0: 1}" where "foo" is "def foo(a, b, c) { a[b] = c; return a }" else if ($arguments.length == 3) { var keyType = in_List.get($arguments, 1).resolvedType; var first1 = $function.block.firstChild(); var second1 = $function.block.lastChild(); if ((keyType == Skew.Type.DYNAMIC || this._cache.isEquivalentToInt(keyType) || this._cache.isEquivalentToString(keyType)) && first1.kind == Skew.NodeKind.EXPRESSION && first1.expressionValue().kind == Skew.NodeKind.ASSIGN_INDEX && second1.kind == Skew.NodeKind.RETURN && second1.returnValue() != null) { var assign = first1.expressionValue(); if (Skew.JavaScriptEmitter._isReferenceTo(assign.assignIndexLeft(), in_List.get($arguments, 0)) && Skew.JavaScriptEmitter._isReferenceTo(assign.assignIndexCenter(), in_List.get($arguments, 1)) && Skew.JavaScriptEmitter._isReferenceTo(assign.assignIndexRight(), in_List.get($arguments, 2)) && Skew.JavaScriptEmitter._isReferenceTo(second1.returnValue(), in_List.get($arguments, 0))) { for (var i2 = 0, list2 = this._context.callGraph.callInfoForSymbol($function).callSites, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var callSite1 = in_List.get(list2, i2); if (callSite1 != null && callSite1.callNode.kind == Skew.NodeKind.CALL) { assert(callSite1.callNode.symbol == $function); mapInserts.push(callSite1.callNode); } } } } } } }; // This uses iteration until fixed point to avoid dependence on inlining order Skew.JavaScriptEmitter.prototype._maybeInlineFunctions = function(global) { var listAppends = []; var mapInserts = []; this._collectInlineableFunctions(global, listAppends, mapInserts); // List append fixed point var changed = true; while (changed) { changed = false; for (var i = 0, count = listAppends.length; i < count; i = i + 1 | 0) { var node = in_List.get(listAppends, i); // This will be null if it was already inlined if (node == null) { continue; } var firstArgument = node.callValue().nextSibling(); var secondArgument = firstArgument.nextSibling(); // List expressions are sometimes casted if (firstArgument.kind == Skew.NodeKind.CAST) { firstArgument = firstArgument.castValue(); } // Only check when the inputs are constants if (firstArgument.kind == Skew.NodeKind.INITIALIZER_LIST) { node.become(firstArgument.remove().appendChild(secondArgument.remove())); in_List.set(listAppends, i, null); changed = true; } } } // Map insert fixed point changed = true; while (changed) { changed = false; for (var i1 = 0, count1 = mapInserts.length; i1 < count1; i1 = i1 + 1 | 0) { var node1 = in_List.get(mapInserts, i1); // This will be null if it was already inlined if (node1 == null) { continue; } var firstArgument1 = node1.callValue().nextSibling(); var secondArgument1 = firstArgument1.nextSibling(); var thirdArgument = secondArgument1.nextSibling(); // Map expressions are sometimes casted if (firstArgument1.kind == Skew.NodeKind.CAST) { firstArgument1 = firstArgument1.castValue(); } // Only check when the inputs are constants if (firstArgument1.kind == Skew.NodeKind.INITIALIZER_MAP && (secondArgument1.isInt() || secondArgument1.isString())) { node1.become(firstArgument1.remove().appendChild(Skew.Node.createPair(secondArgument1.remove(), thirdArgument.remove()).withType(Skew.Type.DYNAMIC))); in_List.set(mapInserts, i1, null); changed = true; } } } }; Skew.JavaScriptEmitter.prototype._prepareGlobal = function(global) { // Lower certain stuff into JavaScript (for example, "x as bool" becomes "!!x") this._patchObject(global); this._exportSymbols(); // Skip everything below if we aren't mangling if (!this._mangle) { return; } // These will be culled by tree shaking regardless of whether they are needed for (var i = 0, list = this._allSpecialVariables, count = list.length; i < count; i = i + 1 | 0) { var variable = in_List.get(list, i); if (this._isSpecialVariableNeeded.has(variable.id)) { this._allocateNamingGroupIndex(variable); this._patchNode(variable.value); } } // Rename symbols based on frequency for better compression this._renameSymbols(); }; Skew.JavaScriptEmitter.prototype._convertLambdaToFunction = function(variable) { var $function = variable.value.symbol.asFunctionSymbol(); $function.kind = Skew.SymbolKind.FUNCTION_GLOBAL; $function.parent = variable.parent; $function.name = variable.name; if ($function.block.parent() != null) { $function.block.remove(); } return $function; }; Skew.JavaScriptEmitter.prototype._convertLambdasToFunctions = function(symbol) { var self = this; for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); self._convertLambdasToFunctions(object); } in_List.removeIf(symbol.variables, function(variable) { if (variable.kind == Skew.SymbolKind.VARIABLE_GLOBAL && variable.isConst() && !variable.isExported() && variable.value != null && variable.value.kind == Skew.NodeKind.LAMBDA) { symbol.functions.push(self._convertLambdaToFunction(variable)); return true; } return false; }); }; Skew.JavaScriptEmitter.prototype._allocateNamingGroupIndex = function(symbol) { if (this._mangle && !this._namingGroupIndexForSymbol.has(symbol.id)) { var index = this._localVariableUnionFind.allocate1(); in_IntMap.set(this._namingGroupIndexForSymbol, symbol.id, index); this._allSymbols.push(symbol); // Explicitly add function arguments since they won't be reached by // normal tree traversal if (Skew.in_SymbolKind.isFunction(symbol.kind)) { var $this = symbol.asFunctionSymbol().$this; if ($this != null) { this._allocateNamingGroupIndex($this); } for (var i = 0, list = symbol.asFunctionSymbol().$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); this._allocateNamingGroupIndex(argument); } } } }; Skew.JavaScriptEmitter.prototype._renameSymbols = function() { // This holds the groups used for naming. Unioning two labels using // this object will cause both groups of symbols to have the same name. var namingGroupsUnionFind = new Skew.UnionFind().allocate2(this._allSymbols.length); // These are optional and only reduce the number of generated names var order = []; this._aliasLocalVariables(namingGroupsUnionFind, order); this._aliasUnrelatedProperties(namingGroupsUnionFind, order); // Ensure all overridden symbols have the same generated name. This is // manditory for correctness, otherwise virtual functions break. var namingGroupMap = new Map(); for (var i = 0, list = this._allSymbols, count1 = list.length; i < count1; i = i + 1 | 0) { var symbol = in_List.get(list, i); if (Skew.in_SymbolKind.isFunction(symbol.kind)) { var $function = symbol.asFunctionSymbol(); assert(this._namingGroupIndexForSymbol.has($function.id)); var id = in_IntMap.get(namingGroupMap, $function.namingGroup, -1); if (id == -1) { in_IntMap.set(namingGroupMap, $function.namingGroup, in_IntMap.get1(this._namingGroupIndexForSymbol, $function.id)); } else { namingGroupsUnionFind.union(id, in_IntMap.get1(this._namingGroupIndexForSymbol, $function.id)); } } } // Collect all reserved names together into one big set for querying var reservedNames = new Map(Skew.JavaScriptEmitter._isKeyword); for (var i1 = 0, list1 = this._allSymbols, count2 = list1.length; i1 < count2; i1 = i1 + 1 | 0) { var symbol1 = in_List.get(list1, i1); if (!Skew.JavaScriptEmitter._shouldRenameSymbol(symbol1)) { in_StringMap.set(reservedNames, symbol1.name, 0); } } // Everything that should have the same name is now grouped together. // Generate and assign names to all internal symbols, but use shorter // names for more frequently used symbols. var sortedGroups = []; for (var i3 = 0, list2 = this._extractGroups(namingGroupsUnionFind, Skew.JavaScriptEmitter.ExtractGroupsMode.ALL_SYMBOLS), count4 = list2.length; i3 < count4; i3 = i3 + 1 | 0) { var group = in_List.get(list2, i3); var count = 0; for (var i2 = 0, count3 = group.length; i2 < count3; i2 = i2 + 1 | 0) { var symbol2 = in_List.get(group, i2); if (Skew.JavaScriptEmitter._shouldRenameSymbol(symbol2)) { count = count + in_IntMap.get(this._symbolCounts, symbol2.id, 0) | 0; } } sortedGroups.push(new Skew.JavaScriptEmitter.SymbolGroup(group, count)); } // Create a total order to make builds deterministic when maps use hashing sortedGroups.sort(function(a, b) { var difference = in_int.compare(b.count, a.count); if (difference == 0) { difference = in_int.compare(b.symbols.length, a.symbols.length); for (var i = 0; difference == 0 && i < a.symbols.length; i = i + 1 | 0) { difference = in_int.compare(in_List.get(a.symbols, i).id, in_List.get(b.symbols, i).id); } } return difference; }); for (var i5 = 0, list4 = sortedGroups, count6 = list4.length; i5 < count6; i5 = i5 + 1 | 0) { var group1 = in_List.get(list4, i5); var name = ''; for (var i4 = 0, list3 = group1.symbols, count5 = list3.length; i4 < count5; i4 = i4 + 1 | 0) { var symbol3 = in_List.get(list3, i4); if (Skew.JavaScriptEmitter._shouldRenameSymbol(symbol3)) { if (name == '') { name = this._generateSymbolName(reservedNames); } symbol3.name = name; } } } }; // Merge local variables from different functions together in the order // they were declared. This will cause every argument list to use the same // variables in the same order, which should offer better gzip: // // function d(a, b) {} // function e(a, b, c) {} // Skew.JavaScriptEmitter.prototype._aliasLocalVariables = function(unionFind, order) { this._zipTogetherInOrder(unionFind, order, this._extractGroups(this._localVariableUnionFind, Skew.JavaScriptEmitter.ExtractGroupsMode.ONLY_LOCAL_VARIABLES)); }; // Merge all related types together into naming groups. This ensures names // will be unique within a subclass hierarchy allowing names to be // duplicated in separate subclass hierarchies. Skew.JavaScriptEmitter.prototype._aliasUnrelatedProperties = function(unionFind, order) { var relatedTypesUnionFind = new Skew.UnionFind().allocate2(this._allSymbols.length); for (var i = 0, count1 = this._allSymbols.length; i < count1; i = i + 1 | 0) { var symbol = in_List.get(this._allSymbols, i); if (symbol.kind == Skew.SymbolKind.OBJECT_CLASS) { var baseClass = symbol.asObjectSymbol().baseClass; if (baseClass != null) { relatedTypesUnionFind.union(i, in_IntMap.get1(this._namingGroupIndexForSymbol, baseClass.id)); } for (var i1 = 0, list = symbol.asObjectSymbol().variables, count = list.length; i1 < count; i1 = i1 + 1 | 0) { var variable = in_List.get(list, i1); relatedTypesUnionFind.union(i, in_IntMap.get1(this._namingGroupIndexForSymbol, variable.id)); } } } this._zipTogetherInOrder(unionFind, order, this._extractGroups(relatedTypesUnionFind, Skew.JavaScriptEmitter.ExtractGroupsMode.ONLY_INSTANCE_VARIABLES)); }; Skew.JavaScriptEmitter.prototype._zipTogetherInOrder = function(unionFind, order, groups) { for (var i1 = 0, list = groups, count1 = list.length; i1 < count1; i1 = i1 + 1 | 0) { var group = in_List.get(list, i1); for (var i = 0, count = group.length; i < count; i = i + 1 | 0) { var symbol = in_List.get(group, i); var index = in_IntMap.get1(this._namingGroupIndexForSymbol, symbol.id); if (i >= order.length) { order.push(index); } else { unionFind.union(index, in_List.get(order, i)); } } } }; Skew.JavaScriptEmitter.prototype._generateSymbolName = function(reservedNames) { while (true) { var name = Skew.JavaScriptEmitter._numberToName(this._nextSymbolName); this._nextSymbolName = this._nextSymbolName + 1 | 0; if (!reservedNames.has(name)) { return name; } } }; Skew.JavaScriptEmitter.prototype._extractGroups = function(unionFind, mode) { var labelToGroup = new Map(); for (var i = 0, list = this._allSymbols, count = list.length; i < count; i = i + 1 | 0) { var symbol = in_List.get(list, i); if (mode == Skew.JavaScriptEmitter.ExtractGroupsMode.ONLY_LOCAL_VARIABLES && !Skew.in_SymbolKind.isLocalOrArgumentVariable(symbol.kind) || mode == Skew.JavaScriptEmitter.ExtractGroupsMode.ONLY_INSTANCE_VARIABLES && symbol.kind != Skew.SymbolKind.VARIABLE_INSTANCE) { continue; } assert(this._namingGroupIndexForSymbol.has(symbol.id)); var label = unionFind.find(in_IntMap.get1(this._namingGroupIndexForSymbol, symbol.id)); var group = in_IntMap.get(labelToGroup, label, null); if (group == null) { group = []; in_IntMap.set(labelToGroup, label, group); } group.push(symbol); } // Sort each resulting group to make builds deterministic when maps use hashing var groups = Array.from(labelToGroup.values()); for (var i1 = 0, list1 = groups, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var group1 = in_List.get(list1, i1); group1.sort(Skew.Symbol.SORT_BY_ID); } return groups; }; Skew.JavaScriptEmitter.prototype._addMapping = function(range) { if (this._sourceMap && range != null) { var source = range.source; var start = range.start; if (this._previousSource != source || this._previousStart != start) { var location = source.indexToLineColumn(start); this._generator.addMapping(source, location.line, location.column, this._currentLine, this._currentColumn); this._previousStart = start; this._previousSource = source; } } }; Skew.JavaScriptEmitter.prototype._emitSemicolonAfterStatement = function() { if (!this._minify) { this._emit(';\n'); } else { this._needsSemicolon = true; } }; Skew.JavaScriptEmitter.prototype._emitSemicolonIfNeeded = function() { if (this._needsSemicolon) { this._emit(';'); this._needsSemicolon = false; } this._maybeEmitMinifedNewline(); }; // Lots of text editors choke up on long lines, so add a newline every now // and then for usability's sake Skew.JavaScriptEmitter.prototype._maybeEmitMinifedNewline = function() { if (this._minify && this._currentColumn > 1024) { this._emit('\n'); } }; Skew.JavaScriptEmitter.prototype._emitNewlineBeforeSymbol = function(symbol) { this._emitSemicolonIfNeeded(); if (!this._minify && this._previousSymbol != null && (!Skew.in_SymbolKind.isObject(this._previousSymbol.kind) || !Skew.in_SymbolKind.isObject(symbol.kind) || symbol.comments != null || Skew.in_SymbolKind.isEnumOrFlags(this._previousSymbol.kind) || Skew.in_SymbolKind.isEnumOrFlags(symbol.kind)) && (!Skew.in_SymbolKind.isVariable(this._previousSymbol.kind) || !Skew.in_SymbolKind.isVariable(symbol.kind) || symbol.comments != null)) { this._emit('\n'); } this._previousSymbol = null; this._addMapping(symbol.range); }; Skew.JavaScriptEmitter.prototype._emitNewlineAfterSymbol = function(symbol) { this._previousSymbol = symbol; }; Skew.JavaScriptEmitter.prototype._emitNewlineBeforeStatement = function(node) { if (!this._minify && this._previousNode != null && (node.comments != null || !Skew.JavaScriptEmitter._isCompactNodeKind(this._previousNode.kind) || !Skew.JavaScriptEmitter._isCompactNodeKind(node.kind))) { this._emit('\n'); } else { this._maybeEmitMinifedNewline(); } this._previousNode = null; }; Skew.JavaScriptEmitter.prototype._emitNewlineAfterStatement = function(node) { this._previousNode = node; }; Skew.JavaScriptEmitter.prototype._emitComments = function(comments) { if (comments != null && !this._minify) { for (var i1 = 0, list1 = comments, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var comment = in_List.get(list1, i1); for (var i = 0, list = comment.lines, count = list.length; i < count; i = i + 1 | 0) { var line = in_List.get(list, i); this._emit(this._indent + '//' + line + '\n'); } if (comment.hasGapBelow) { this._emit('\n'); } } } }; Skew.JavaScriptEmitter.prototype._emitObject = function(symbol) { if (symbol.isImported()) { return; } var foundPrimaryConstructor = false; this._namespacePrefix = symbol.parent != null ? Skew.JavaScriptEmitter._computeNamespacePrefix(symbol.parent.asObjectSymbol()) : ''; switch (symbol.kind) { case Skew.SymbolKind.OBJECT_NAMESPACE: case Skew.SymbolKind.OBJECT_INTERFACE: case Skew.SymbolKind.OBJECT_WRAPPED: { if (symbol.forwardTo == null && symbol != this._exportsNamespace) { this._emitNewlineBeforeSymbol(symbol); this._emitComments(symbol.comments); this._emit(this._indent + (this._namespacePrefix == '' ? 'var ' : this._namespacePrefix) + Skew.JavaScriptEmitter._mangleName(symbol) + this._space + '=' + this._space + '{}'); this._emitSemicolonAfterStatement(); this._emitNewlineAfterSymbol(symbol); } break; } case Skew.SymbolKind.OBJECT_ENUM: case Skew.SymbolKind.OBJECT_FLAGS: { this._emitNewlineBeforeSymbol(symbol); this._emitComments(symbol.comments); this._emit(this._indent + (this._namespacePrefix == '' ? 'var ' : this._namespacePrefix) + Skew.JavaScriptEmitter._mangleName(symbol) + this._space + '=' + this._space + '{'); this._increaseIndent(); var isFirst = true; for (var i = 0, list = symbol.variables, count = list.length; i < count; i = i + 1 | 0) { var variable = in_List.get(list, i); if (variable.kind == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS) { if (isFirst) { isFirst = false; } else { this._emit(','); } this._emit(this._newline); this._emitNewlineBeforeSymbol(variable); this._emitComments(variable.comments); this._emit(this._indent + Skew.JavaScriptEmitter._mangleName(variable) + ':' + this._space); this._emitExpression(variable.value, Skew.Precedence.COMMA); this._emitNewlineAfterSymbol(variable); } } this._decreaseIndent(); if (!isFirst && !this._minify) { this._emit('\n' + this._indent); } this._emit('}'); this._emitSemicolonAfterStatement(); this._emitNewlineAfterSymbol(symbol); break; } case Skew.SymbolKind.OBJECT_CLASS: { var variable1 = in_IntMap.get1(this._specialVariables, Skew.JavaScriptEmitter.SpecialVariable.EXTENDS); for (var i1 = 0, list1 = symbol.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); if ($function.isPrimaryConstructor()) { if ($function.comments == null && symbol.comments != null) { $function.comments = symbol.comments; } this._emitFunction($function); if (symbol.baseClass != null || symbol.kind == Skew.SymbolKind.OBJECT_CLASS && symbol.$extends != null) { if (!this._minify) { this._emit('\n' + this._indent); } this._emitSemicolonIfNeeded(); this._addMapping(variable1.range); this._emit(Skew.JavaScriptEmitter._mangleName(variable1) + '(' + Skew.JavaScriptEmitter._fullName(symbol) + ',' + this._space); if (symbol.baseClass != null) { this._emit(Skew.JavaScriptEmitter._fullName(symbol.baseClass)); } else { assert(symbol.kind == Skew.SymbolKind.OBJECT_CLASS && symbol.$extends != null); this._emitExpression(symbol.$extends, Skew.Precedence.LOWEST); } this._emit(')'); this._emitSemicolonAfterStatement(); } foundPrimaryConstructor = true; break; } } // Emit a namespace if the class is never constructed if (!foundPrimaryConstructor) { this._emitNewlineBeforeSymbol(symbol); this._emit(this._indent + (this._namespacePrefix == '' && !symbol.isExported() ? 'var ' : this._namespacePrefix) + Skew.JavaScriptEmitter._mangleName(symbol) + this._space + '=' + this._space + '{}'); this._emitSemicolonAfterStatement(); } break; } } if (symbol.kind != Skew.SymbolKind.OBJECT_GLOBAL) { this._namespacePrefix += Skew.JavaScriptEmitter._mangleName(symbol) + '.'; } if (symbol.usePrototypeCache()) { this._emitSemicolonIfNeeded(); this._emit(this._newline + this._indent + Skew.JavaScriptEmitter._mangleName(in_IntMap.get1(this._specialVariables, Skew.JavaScriptEmitter.SpecialVariable.PROTOTYPE)) + this._space + '=' + this._space + Skew.JavaScriptEmitter._fullName(symbol) + '.prototype'); this._emitSemicolonAfterStatement(); } // Ignore instance functions if the class is never constructed for (var i2 = 0, list2 = symbol.functions, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var function1 = in_List.get(list2, i2); if (foundPrimaryConstructor ? !function1.isPrimaryConstructor() : function1.kind == Skew.SymbolKind.FUNCTION_GLOBAL) { this._emitFunction(function1); } } }; Skew.JavaScriptEmitter.prototype._emitArgumentList = function($arguments) { for (var i = 0, list = $arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); if (argument != in_List.first($arguments)) { this._emit(',' + this._space); } this._addMapping(argument.range); this._emit(Skew.JavaScriptEmitter._mangleName(argument)); } }; Skew.JavaScriptEmitter.prototype._emitFunction = function(symbol) { if (symbol.block == null) { return; } this._emitNewlineBeforeSymbol(symbol); this._emitComments(symbol.comments); var isExpression = this._namespacePrefix != '' || symbol.isExported(); var name = Skew.JavaScriptEmitter._mangleName(symbol.isPrimaryConstructor() ? symbol.parent : symbol); if (isExpression) { this._emit(this._indent + (symbol.kind != Skew.SymbolKind.FUNCTION_INSTANCE ? this._namespacePrefix : symbol.parent.usePrototypeCache() ? Skew.JavaScriptEmitter._mangleName(in_IntMap.get1(this._specialVariables, Skew.JavaScriptEmitter.SpecialVariable.PROTOTYPE)) + '.' : this._namespacePrefix + 'prototype.') + name + this._space + '=' + this._space + 'function('); } else { this._emit(this._indent + 'function ' + name + '('); } this._emitArgumentList(symbol.$arguments); this._emit(')' + this._space + '{' + this._newline); this._increaseIndent(); this._enclosingFunction = symbol; this._emitStatements(symbol.block); this._enclosingFunction = null; this._decreaseIndent(); this._emit(this._indent + '}'); if (isExpression) { this._emitSemicolonAfterStatement(); } else { this._needsSemicolon = false; this._emit(this._newline); } this._emitNewlineAfterSymbol(symbol); // Secondary constructors need the same prototype as the primary constructor if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR && !symbol.isPrimaryConstructor()) { this._emitSemicolonIfNeeded(); this._emit(this._newline + this._indent + Skew.JavaScriptEmitter._fullName(symbol) + '.prototype' + this._space + '=' + this._space + (symbol.parent.usePrototypeCache() ? Skew.JavaScriptEmitter._mangleName(in_IntMap.get1(this._specialVariables, Skew.JavaScriptEmitter.SpecialVariable.PROTOTYPE)) : Skew.JavaScriptEmitter._fullName(symbol.parent) + '.prototype')); this._emitSemicolonAfterStatement(); } }; Skew.JavaScriptEmitter.prototype._emitVariable = function(symbol) { if (symbol.isImported()) { return; } if (symbol.kind != Skew.SymbolKind.VARIABLE_INSTANCE && symbol.kind != Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS && (symbol.value != null || this._namespacePrefix == '' || Skew.in_SymbolKind.isLocalOrArgumentVariable(symbol.kind))) { this._emitNewlineBeforeSymbol(symbol); this._emitComments(symbol.comments); this._emit(this._indent + (this._namespacePrefix == '' || Skew.in_SymbolKind.isLocalOrArgumentVariable(symbol.kind) ? 'var ' : this._namespacePrefix) + Skew.JavaScriptEmitter._mangleName(symbol)); if (symbol.value != null) { this._emit(this._space + '=' + this._space); this._emitExpression(symbol.value, Skew.Precedence.COMMA); } this._emitSemicolonAfterStatement(); this._emitNewlineAfterSymbol(symbol); } }; Skew.JavaScriptEmitter.prototype._emitStatements = function(node) { this._previousNode = null; for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._emitSemicolonIfNeeded(); this._emitNewlineBeforeStatement(child); this._addMapping(child.range); this._emitComments(child.comments); this._emitStatement(child); this._emitNewlineAfterStatement(child); } this._previousNode = null; }; Skew.JavaScriptEmitter.prototype._emitBlock = function(node, after, mode) { var shouldMinify = mode == Skew.JavaScriptEmitter.BracesMode.CAN_OMIT_BRACES && this._minify; this._addMapping(node.range); if (shouldMinify && !node.hasChildren()) { this._emit(';'); } else if (shouldMinify && node.hasOneChild() && node.firstChild().kind != Skew.NodeKind.COMMENT_BLOCK) { if (after == Skew.JavaScriptEmitter.AfterToken.AFTER_KEYWORD) { this._emit(' '); } this._emitStatement(node.firstChild()); } else { this._emit(this._space + '{' + this._newline); if (node.hasChildren()) { this._increaseIndent(); this._emitStatements(node); this._decreaseIndent(); } this._emit(this._indent + '}'); this._needsSemicolon = false; } }; Skew.JavaScriptEmitter.prototype._emitVariables = function(node) { this._emit('var '); for (var child = node.firstChild(); child != null; child = child.nextSibling()) { if (child.previousSibling() != null) { this._emit(',' + this._space); } var symbol = child.symbol.asVariableSymbol(); this._emit(Skew.JavaScriptEmitter._mangleName(symbol)); if (symbol.value != null) { this._emit(this._space + '=' + this._space); this._emitExpression(symbol.value, Skew.Precedence.COMMA); } } }; Skew.JavaScriptEmitter.prototype._canRemoveSpaceBeforeKeyword = function(node) { var kind = node.kind; return Skew.in_NodeKind.isUnary(kind) && !Skew.in_NodeKind.isUnaryPostfix(kind) || node.isString() || node.isNumberLessThanZero() || Skew.in_NodeKind.isInitializer(kind) || (kind == Skew.NodeKind.HOOK || kind == Skew.NodeKind.SEQUENCE) && this._canRemoveSpaceBeforeKeyword(node.firstChild()); }; Skew.JavaScriptEmitter.prototype._emitSpaceBeforeKeyword = function(node) { if (!this._minify || !this._canRemoveSpaceBeforeKeyword(node)) { this._emit(' '); } }; Skew.JavaScriptEmitter.prototype._emitStatement = function(node) { switch (node.kind) { case Skew.NodeKind.COMMENT_BLOCK: { break; } case Skew.NodeKind.VARIABLES: { this._emit(this._indent); this._emitVariables(node); this._emitSemicolonAfterStatement(); break; } case Skew.NodeKind.EXPRESSION: { this._emit(this._indent); this._emitExpression(node.expressionValue(), Skew.Precedence.LOWEST); this._emitSemicolonAfterStatement(); break; } case Skew.NodeKind.BREAK: { var label = in_IntMap.get(this._loopLabels, node.id, null); this._emit(this._indent + 'break'); if (label != null) { this._emit(' ' + Skew.JavaScriptEmitter._mangleName(label)); } this._emitSemicolonAfterStatement(); break; } case Skew.NodeKind.CONTINUE: { this._emit(this._indent + 'continue'); this._emitSemicolonAfterStatement(); break; } case Skew.NodeKind.RETURN: { this._emit(this._indent + 'return'); var value = node.returnValue(); if (value != null) { var comments = value.comments; if (!this._minify && comments != null) { // JavaScript needs parentheses here to avoid ASI issues this._emit(' (\n'); this._increaseIndent(); this._emitComments(comments); this._emit(this._indent); this._emitExpression(value, Skew.Precedence.LOWEST); this._decreaseIndent(); this._emit(')'); } else { this._emitSpaceBeforeKeyword(value); this._emitExpression(value, Skew.Precedence.LOWEST); } } this._emitSemicolonAfterStatement(); break; } case Skew.NodeKind.THROW: { var value1 = node.throwValue(); this._emit(this._indent + 'throw'); this._emitSpaceBeforeKeyword(value1); this._emitExpression(value1, Skew.Precedence.LOWEST); this._emitSemicolonAfterStatement(); break; } case Skew.NodeKind.FOR: { var setup = node.forSetup(); var test = node.forTest(); var update = node.forUpdate(); this._emit(this._indent); this._emitLoopLabel(node); this._emit('for' + this._space + '('); if (!setup.isEmptySequence()) { this._parenthesizeInExpressions = this._parenthesizeInExpressions + 1 | 0; if (setup.kind == Skew.NodeKind.VARIABLES) { this._emitVariables(setup); } else { this._emitExpression(setup, Skew.Precedence.LOWEST); } this._parenthesizeInExpressions = this._parenthesizeInExpressions - 1 | 0; } this._emit(';'); if (!test.isEmptySequence()) { this._emit(this._space); this._emitExpression(test, Skew.Precedence.LOWEST); } this._emit(';'); if (!update.isEmptySequence()) { this._emit(this._space); this._emitExpression(update, Skew.Precedence.LOWEST); } this._emit(')'); this._emitBlock(node.forBlock(), Skew.JavaScriptEmitter.AfterToken.AFTER_PARENTHESIS, Skew.JavaScriptEmitter.BracesMode.CAN_OMIT_BRACES); this._emit(this._newline); break; } case Skew.NodeKind.FOREACH: { this._emit(this._indent); this._emitLoopLabel(node); this._emit('for' + this._space + '(var ' + Skew.JavaScriptEmitter._mangleName(node.symbol) + ' in '); this._emitExpression(node.foreachValue(), Skew.Precedence.LOWEST); this._emit(')'); this._emitBlock(node.foreachBlock(), Skew.JavaScriptEmitter.AfterToken.AFTER_PARENTHESIS, Skew.JavaScriptEmitter.BracesMode.CAN_OMIT_BRACES); this._emit(this._newline); break; } case Skew.NodeKind.IF: { this._emit(this._indent); this._emitIf(node); this._emit(this._newline); break; } case Skew.NodeKind.SWITCH: { var switchValue = node.switchValue(); this._emit(this._indent + 'switch' + this._space + '('); this._emitExpression(switchValue, Skew.Precedence.LOWEST); this._emit(')' + this._space + '{' + this._newline); this._increaseIndent(); for (var child = switchValue.nextSibling(); child != null; child = child.nextSibling()) { var block = child.caseBlock(); this._emitSemicolonIfNeeded(); if (child.previousSibling() != switchValue) { this._emit(this._newline); } this._emitComments(child.comments); if (child.hasOneChild()) { this._emit(this._indent + 'default:'); } else { for (var value2 = child.firstChild(); value2 != block; value2 = value2.nextSibling()) { if (value2.previousSibling() != null) { this._emit(this._newline); } this._emitComments(value2.comments); this._emit(this._indent + 'case'); this._emitSpaceBeforeKeyword(value2); this._emitExpression(value2, Skew.Precedence.LOWEST); this._emit(':'); } } if (!this._minify) { this._emit(' {\n'); this._increaseIndent(); } this._emitStatements(block); if (block.hasControlFlowAtEnd()) { this._emitSemicolonIfNeeded(); this._emit(this._indent + 'break'); this._emitSemicolonAfterStatement(); } if (!this._minify) { this._decreaseIndent(); this._emit(this._indent + '}\n'); } } this._decreaseIndent(); this._emit(this._indent + '}' + this._newline); this._needsSemicolon = false; break; } case Skew.NodeKind.TRY: { var tryBlock = node.tryBlock(); var finallyBlock = node.finallyBlock(); this._emit(this._indent + 'try'); this._emitBlock(tryBlock, Skew.JavaScriptEmitter.AfterToken.AFTER_KEYWORD, Skew.JavaScriptEmitter.BracesMode.MUST_KEEP_BRACES); this._emit(this._newline); for (var child1 = tryBlock.nextSibling(); child1 != finallyBlock; child1 = child1.nextSibling()) { this._emit(this._newline); this._emitComments(child1.comments); this._emit(this._indent + 'catch' + this._space + '(' + Skew.JavaScriptEmitter._mangleName(child1.symbol) + ')'); this._emitBlock(child1.catchBlock(), Skew.JavaScriptEmitter.AfterToken.AFTER_KEYWORD, Skew.JavaScriptEmitter.BracesMode.MUST_KEEP_BRACES); this._emit(this._newline); } if (finallyBlock != null) { this._emit(this._newline); this._emitComments(finallyBlock.comments); this._emit(this._indent + 'finally'); this._emitBlock(finallyBlock, Skew.JavaScriptEmitter.AfterToken.AFTER_KEYWORD, Skew.JavaScriptEmitter.BracesMode.MUST_KEEP_BRACES); this._emit(this._newline); } break; } case Skew.NodeKind.WHILE: { this._emit(this._indent); this._emitLoopLabel(node); this._emit('while' + this._space + '('); this._emitExpression(node.whileTest(), Skew.Precedence.LOWEST); this._emit(')'); this._emitBlock(node.whileBlock(), Skew.JavaScriptEmitter.AfterToken.AFTER_PARENTHESIS, Skew.JavaScriptEmitter.BracesMode.CAN_OMIT_BRACES); this._emit(this._newline); break; } default: { assert(false); break; } } }; Skew.JavaScriptEmitter.prototype._emitLoopLabel = function(node) { var label = in_IntMap.get(this._loopLabels, node.id, null); if (label != null) { this._emit(Skew.JavaScriptEmitter._mangleName(label) + ':' + this._space); } }; Skew.JavaScriptEmitter.prototype._emitIf = function(node) { var trueBlock = node.ifTrue(); var falseBlock = node.ifFalse(); this._emit('if' + this._space + '('); this._emitExpression(node.ifTest(), Skew.Precedence.LOWEST); this._emit(')'); // Make sure to always keep braces to avoid the dangling "else" case // "if (a) if (b) c; else d; else e;" // "if (a) { if (b) if (c) d; else e; } else f;" // "if (a) { if (b) c; else if (d) e; } else f;" // "if (a) { while (true) if (b) break; } else c;" var braces = Skew.JavaScriptEmitter.BracesMode.CAN_OMIT_BRACES; if (falseBlock != null) { var statement = trueBlock.blockStatement(); if (statement != null && (statement.kind == Skew.NodeKind.IF || statement.kind == Skew.NodeKind.FOR && statement.forBlock().blockStatement() != null || statement.kind == Skew.NodeKind.FOREACH && statement.foreachBlock().blockStatement() != null || statement.kind == Skew.NodeKind.WHILE && statement.whileBlock().blockStatement() != null)) { braces = Skew.JavaScriptEmitter.BracesMode.MUST_KEEP_BRACES; } } this._emitBlock(node.ifTrue(), Skew.JavaScriptEmitter.AfterToken.AFTER_PARENTHESIS, braces); if (falseBlock != null) { var singleIf = Skew.JavaScriptEmitter._singleIf(falseBlock); this._emitSemicolonIfNeeded(); this._emit(this._newline + this._newline); this._emitComments(falseBlock.comments); if (singleIf != null) { this._emitComments(singleIf.comments); } this._emit(this._indent + 'else'); if (singleIf != null) { this._emit(' '); this._emitIf(singleIf); } else { this._emitBlock(falseBlock, Skew.JavaScriptEmitter.AfterToken.AFTER_KEYWORD, Skew.JavaScriptEmitter.BracesMode.CAN_OMIT_BRACES); } } }; Skew.JavaScriptEmitter.prototype._emitContent = function(content) { switch (content.kind()) { case Skew.ContentKind.BOOL: { this._emit(Skew.in_Content.asBool(content).toString()); break; } case Skew.ContentKind.INT: { this._emit(Skew.in_Content.asInt(content).toString()); break; } case Skew.ContentKind.DOUBLE: { var value = Skew.in_Content.asDouble(content); var text = isNaN(value) ? 'NaN' : value == 1 / 0 ? 'Infinity' : value == -(1 / 0) ? '-Infinity' : // The C# implementation of double.ToString() uses an uppercase "E" TARGET == Target.CSHARP ? value.toString().toLowerCase() : value.toString(); // "0.123" => ".123" // "-0.123" => "-.123" if (this._minify) { if (text.startsWith('0.') && text != '0.') { text = in_string.slice1(text, 1); } else if (text.startsWith('-0.') && text != '-0.') { text = '-' + in_string.slice1(text, 2); } } this._emit(text); break; } case Skew.ContentKind.STRING: { this._emit(Skew.quoteString(Skew.in_Content.asString(content), Skew.QuoteStyle.SHORTEST, Skew.QuoteOctal.OCTAL_WORKAROUND)); break; } } }; Skew.JavaScriptEmitter.prototype._emitCommaSeparatedExpressions = function(from, to) { while (from != to) { this._emitExpression(from, Skew.Precedence.COMMA); from = from.nextSibling(); if (from != to) { this._emit(',' + this._space); this._maybeEmitMinifedNewline(); } } }; // Calling a function in an expression that starts with something like "function(){}()" // must be wrapped in parentheses to avoid looking like a function statement Skew.JavaScriptEmitter.prototype._lambdaMayNeedParentheses = function(node) { var parent = node.parent(); if (parent == null) { // Expression statements always have parents return false; } switch (parent.kind) { case Skew.NodeKind.CALL: { return node == parent.callValue() && this._lambdaMayNeedParentheses(parent); } case Skew.NodeKind.DOT: { return this._lambdaMayNeedParentheses(parent); } case Skew.NodeKind.INDEX: { return node == parent.indexLeft() && this._lambdaMayNeedParentheses(parent); } case Skew.NodeKind.ASSIGN_INDEX: { return node == parent.assignIndexLeft() && this._lambdaMayNeedParentheses(parent); } default: { if (Skew.in_NodeKind.isBinary(parent.kind)) { return node == parent.binaryLeft() && this._lambdaMayNeedParentheses(parent); } // Not sure, wrap to be safe return true; } } }; // Returns true if the provided call node must be parenthesized due to being inside a dot expression Skew.JavaScriptEmitter.prototype._checkForDotParentOfCall = function(node) { assert(node.kind == Skew.NodeKind.CALL); var p = node.parent(); label: while (p != null) { switch (p.kind) { case Skew.NodeKind.CAST: case Skew.NodeKind.PARAMETERIZE: { p = p.parent(); break; } case Skew.NodeKind.DOT: { return true; } default: { break label; } } } return false; }; Skew.JavaScriptEmitter.prototype._emitExpression = function(node, precedence) { var kind = node.kind; this._addMapping(node.range); switch (kind) { case Skew.NodeKind.TYPE: { this._emit(Skew.JavaScriptEmitter._fullName(node.resolvedType.symbol)); break; } case Skew.NodeKind.NULL: { this._emit('null'); break; } case Skew.NodeKind.NAME: { var symbol = node.symbol; this._emit(symbol != null ? Skew.JavaScriptEmitter._fullName(symbol) : node.asString()); break; } case Skew.NodeKind.DOT: { this._emitExpression(node.dotTarget(), Skew.Precedence.MEMBER); this._emit('.' + (node.symbol != null ? Skew.JavaScriptEmitter._mangleName(node.symbol) : node.asString())); break; } case Skew.NodeKind.CONSTANT: { var wrap = precedence == Skew.Precedence.MEMBER && (node.isInt() || node.isDouble() && (isFinite(node.asDouble()) || node.asDouble() < 0)); if (wrap) { this._emit('('); } // Prevent "x - -1" from becoming "x--1" if (this._minify && node.isNumberLessThanZero() && this._previousCodeUnit == 45) { this._emit(' '); } this._emitContent(node.content); if (wrap) { this._emit(')'); } break; } case Skew.NodeKind.CALL: { var value = node.callValue(); var call = value.kind == Skew.NodeKind.SUPER; var isKeyword = value.kind == Skew.NodeKind.NAME && value.symbol == null && Skew.JavaScriptEmitter._keywordCallMap.has(value.asString()); var parenthesize = isKeyword && Skew.Precedence.UNARY_POSTFIX < precedence; var wrap1 = value.kind == Skew.NodeKind.LAMBDA && this._lambdaMayNeedParentheses(node); var isNew = false; if (parenthesize) { this._emit('('); } if (wrap1) { this._emit('('); } if (!call && node.symbol != null && node.symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { this._emit('new ' + Skew.JavaScriptEmitter._fullName(node.symbol)); isNew = true; } else if (!call && value.kind == Skew.NodeKind.DOT && value.asString() == 'new') { this._emit('new '); this._emitExpression(value.dotTarget(), Skew.Precedence.MEMBER); isNew = true; } else { this._emitExpression(value, Skew.Precedence.UNARY_POSTFIX); if (call) { this._emit('.call'); } } if (wrap1) { this._emit(')'); } // Omit parentheses during mangling when possible if (!isNew || !this._mangle || call || value.nextSibling() != null || this._checkForDotParentOfCall(node)) { this._emit(isKeyword ? ' ' : '('); if (call) { this._emit(Skew.JavaScriptEmitter._mangleName(this._enclosingFunction.$this)); } for (var child = value.nextSibling(); child != null; child = child.nextSibling()) { if (call || child.previousSibling() != value) { this._emit(',' + this._space); this._maybeEmitMinifedNewline(); } this._emitExpression(child, Skew.Precedence.COMMA); } if (!isKeyword) { this._emit(')'); } } if (parenthesize) { this._emit(')'); } break; } case Skew.NodeKind.INITIALIZER_LIST: case Skew.NodeKind.INITIALIZER_MAP: { var useBraces = kind == Skew.NodeKind.INITIALIZER_MAP; var isIndented = false; if (!this._minify) { for (var child1 = node.firstChild(); child1 != null; child1 = child1.nextSibling()) { if (child1.comments != null) { isIndented = true; break; } } } this._emit(useBraces ? '{' : '['); if (isIndented) { this._increaseIndent(); } for (var child2 = node.firstChild(); child2 != null; child2 = child2.nextSibling()) { if (child2.previousSibling() != null) { this._emit(',' + (isIndented ? '' : this._space)); this._maybeEmitMinifedNewline(); } if (isIndented) { this._emit('\n'); this._emitComments(child2.comments); this._emit(this._indent); } this._emitExpression(child2, Skew.Precedence.COMMA); } if (isIndented) { this._decreaseIndent(); this._emit('\n' + this._indent); } this._emit(useBraces ? '}' : ']'); break; } case Skew.NodeKind.PAIR: { this._emitExpression(node.firstValue(), Skew.Precedence.LOWEST); this._emit(':' + this._space); this._emitExpression(node.secondValue(), Skew.Precedence.LOWEST); break; } case Skew.NodeKind.INDEX: { this._emitExpression(node.indexLeft(), Skew.Precedence.UNARY_POSTFIX); this._emit('['); this._emitExpression(node.indexRight(), Skew.Precedence.LOWEST); this._emit(']'); break; } case Skew.NodeKind.ASSIGN_INDEX: { if (Skew.Precedence.ASSIGN < precedence) { this._emit('('); } this._emitExpression(node.assignIndexLeft(), Skew.Precedence.UNARY_POSTFIX); this._emit('['); this._emitExpression(node.assignIndexCenter(), Skew.Precedence.LOWEST); this._emit(']' + this._space + '=' + this._space + ''); this._emitExpression(node.assignIndexRight(), Skew.Precedence.ASSIGN); if (Skew.Precedence.ASSIGN < precedence) { this._emit(')'); } break; } case Skew.NodeKind.CAST: { this._emitExpression(node.castValue(), precedence); break; } case Skew.NodeKind.PARAMETERIZE: { this._emitExpression(node.parameterizeValue(), precedence); break; } case Skew.NodeKind.SEQUENCE: { if (Skew.Precedence.COMMA <= precedence) { this._emit('('); } this._emitCommaSeparatedExpressions(node.firstChild(), null); if (Skew.Precedence.COMMA <= precedence) { this._emit(')'); } break; } case Skew.NodeKind.SUPER: { this._emit(Skew.JavaScriptEmitter._fullName(node.symbol)); break; } case Skew.NodeKind.HOOK: { if (Skew.Precedence.ASSIGN < precedence) { this._emit('('); } this._emitExpression(node.hookTest(), Skew.Precedence.LOGICAL_OR); this._emit(this._space + '?'); var left = node.hookTrue(); if (left.comments != null) { this._emit(this._newline); this._increaseIndent(); this._emitComments(left.comments); this._emit(this._indent); this._emitExpression(left, Skew.Precedence.ASSIGN); this._decreaseIndent(); } else { this._emit(this._space); this._emitExpression(left, Skew.Precedence.ASSIGN); } this._emit(this._space + ':'); var right = node.hookFalse(); if (right.comments != null) { this._emit(this._newline); this._increaseIndent(); this._emitComments(right.comments); this._emit(this._indent); this._emitExpression(right, Skew.Precedence.ASSIGN); this._decreaseIndent(); } else { this._emit(this._space); this._emitExpression(right, Skew.Precedence.ASSIGN); } if (Skew.Precedence.ASSIGN < precedence) { this._emit(')'); } break; } case Skew.NodeKind.LAMBDA: { var symbol1 = node.symbol.asFunctionSymbol(); this._emit('function('); this._emitArgumentList(symbol1.$arguments); this._emit(')'); this._emitBlock(symbol1.block, Skew.JavaScriptEmitter.AfterToken.AFTER_PARENTHESIS, Skew.JavaScriptEmitter.BracesMode.MUST_KEEP_BRACES); break; } case Skew.NodeKind.COMPLEMENT: case Skew.NodeKind.NEGATIVE: case Skew.NodeKind.NOT: case Skew.NodeKind.POSITIVE: case Skew.NodeKind.POSTFIX_DECREMENT: case Skew.NodeKind.POSTFIX_INCREMENT: case Skew.NodeKind.PREFIX_DECREMENT: case Skew.NodeKind.PREFIX_INCREMENT: { var value1 = node.unaryValue(); var info = in_IntMap.get1(Skew.operatorInfo, kind); if (info.precedence < precedence) { this._emit('('); } if (Skew.in_NodeKind.isUnaryPostfix(kind)) { this._emitExpression(value1, info.precedence); this._emit(info.text); } else { // Prevent "x - -1" from becoming "x--1" if (this._minify && (kind == Skew.NodeKind.POSITIVE || kind == Skew.NodeKind.NEGATIVE || kind == Skew.NodeKind.PREFIX_INCREMENT || kind == Skew.NodeKind.PREFIX_DECREMENT) && in_string.get1(info.text, 0) == this._previousCodeUnit) { this._emit(' '); } this._emit(info.text); this._emitExpression(value1, info.precedence); } if (info.precedence < precedence) { this._emit(')'); } break; } case Skew.NodeKind.TYPE_CHECK: { var type = node.typeCheckType(); var resolvedType = type.resolvedType; if (resolvedType.isWrapped()) { resolvedType = this._cache.unwrappedType(resolvedType); } if (resolvedType.kind == Skew.TypeKind.SYMBOL || type.kind != Skew.NodeKind.TYPE) { if (Skew.Precedence.COMPARE < precedence) { this._emit('('); } this._emitExpression(node.typeCheckValue(), Skew.Precedence.COMPARE); this._emit(' instanceof '); if (resolvedType.kind == Skew.TypeKind.SYMBOL) { this._emit(Skew.JavaScriptEmitter._fullName(resolvedType.symbol)); } else { this._emitExpression(type, Skew.Precedence.COMPARE); } if (Skew.Precedence.COMPARE < precedence) { this._emit(')'); } } else { this._emitExpression(node.typeCheckValue(), precedence); } break; } default: { if (Skew.in_NodeKind.isBinary(kind)) { var info1 = in_IntMap.get1(Skew.operatorInfo, kind); var left1 = node.binaryLeft(); var right1 = node.binaryRight(); var extraEquals = left1.resolvedType == Skew.Type.DYNAMIC || right1.resolvedType == Skew.Type.DYNAMIC ? '=' : ''; var needsParentheses = info1.precedence < precedence || kind == Skew.NodeKind.IN && this._parenthesizeInExpressions != 0; if (needsParentheses) { this._emit('('); } this._emitExpression(node.binaryLeft(), info1.precedence + (info1.associativity == Skew.Associativity.RIGHT | 0) | 0); // Always emit spaces around keyword operators, even when minifying var comments = this._minify ? null : right1.comments; this._emit(kind == Skew.NodeKind.IN ? (left1.isString() ? this._space : ' ') + 'in' + (comments != null ? '' : ' ') : this._space + (kind == Skew.NodeKind.EQUAL ? '==' + extraEquals : kind == Skew.NodeKind.NOT_EQUAL ? '!=' + extraEquals : info1.text)); if (comments != null) { this._emit(this._newline); this._increaseIndent(); this._emitComments(comments); this._emit(this._indent); this._emitExpression(right1, info1.precedence + (info1.associativity == Skew.Associativity.LEFT | 0) | 0); this._decreaseIndent(); } else { if (kind != Skew.NodeKind.IN) { this._emit(this._space); } this._emitExpression(right1, info1.precedence + (info1.associativity == Skew.Associativity.LEFT | 0) | 0); } if (needsParentheses) { this._emit(')'); } } else { assert(false); } break; } } }; Skew.JavaScriptEmitter.prototype._patchObject = function(symbol) { this._allocateNamingGroupIndex(symbol); // Subclasses need the extension stub if (!symbol.isImported() && (symbol.baseClass != null || symbol.kind == Skew.SymbolKind.OBJECT_CLASS && symbol.$extends != null)) { this._specialVariable(Skew.JavaScriptEmitter.SpecialVariable.EXTENDS); this._specialVariable(Skew.JavaScriptEmitter.SpecialVariable.CREATE); } // Scan over child objects for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._patchObject(object); if (symbol == this._global && object.isExported()) { this._symbolsToExport.push(object); } } // Scan over child functions var isPrimaryConstructor = true; var prototypeCount = 0; for (var i2 = 0, list2 = symbol.functions, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var $function = in_List.get(list2, i2); var block = $function.block; var $this = $function.$this; this._allocateNamingGroupIndex($function); // Check to see if we need an explicit "self" parameter while patching the block this._needsSelf = false; this._currentSelf = $this; this._enclosingFunction = $function; this._patchNode(block); this._enclosingFunction = null; // Only insert the "self" variable if required to handle capture inside lambdas if (this._needsSelf) { this._unionVariableWithFunction($this, $function); if (block != null) { $this.kind = Skew.SymbolKind.VARIABLE_LOCAL; $this.value = new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent('this')).withType(Skew.Type.DYNAMIC); var variable = Skew.Node.createVariable($this); var merged = false; // When mangling, add the "self" variable to an existing variable statement if present if (this._mangle && block.hasChildren()) { var firstChild = block.firstChild(); if (firstChild.kind == Skew.NodeKind.VARIABLES) { firstChild.prependChild(variable); merged = true; } else if (firstChild.kind == Skew.NodeKind.FOR) { if (firstChild.forSetup().kind == Skew.NodeKind.VARIABLES) { firstChild.forSetup().prependChild(variable); merged = true; } else if (firstChild.forSetup().isEmptySequence()) { firstChild.forSetup().replaceWith(new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(variable)); merged = true; } } } if (!merged) { block.prependChild(new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(variable)); } } } else if ($this != null) { $this.name = 'this'; $this.flags |= Skew.SymbolFlags.IS_EXPORTED; } for (var i1 = 0, list1 = $function.$arguments, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var argument = in_List.get(list1, i1); this._allocateNamingGroupIndex(argument); this._unionVariableWithFunction(argument, $function); } // Rename extra constructors overloads so they don't conflict if ($function.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR && isPrimaryConstructor) { $function.flags |= Skew.SymbolFlags.IS_PRIMARY_CONSTRUCTOR; isPrimaryConstructor = false; } // Mark the prototype variable as needed when the prototype is used else if (this._mangle && ($function.kind == Skew.SymbolKind.FUNCTION_INSTANCE || $function.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR && !isPrimaryConstructor)) { if ((prototypeCount = prototypeCount + 1 | 0) == 2) { var variable1 = this._specialVariable(Skew.JavaScriptEmitter.SpecialVariable.PROTOTYPE); in_IntMap.set(this._symbolCounts, variable1.id, in_IntMap.get(this._symbolCounts, variable1.id, 0) + 1 | 0); symbol.flags |= Skew.SymbolFlags.USE_PROTOTYPE_CACHE; } } if (symbol == this._global && $function.isExported()) { this._symbolsToExport.push($function); } } // Scan over child variables for (var i3 = 0, list3 = symbol.variables, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var variable2 = in_List.get(list3, i3); this._allocateNamingGroupIndex(variable2); this._patchNode(variable2.value); if (symbol == this._global && variable2.isExported()) { this._symbolsToExport.push(variable2); } } }; Skew.JavaScriptEmitter.prototype._exportSymbols = function() { if (this._symbolsToExport.length == 0) { return; } this._exportsNamespace = new Skew.ObjectSymbol(Skew.SymbolKind.OBJECT_NAMESPACE, this._global.scope.generateName('exports')); this._exportsNamespace.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, this._exportsNamespace); this._exportsNamespace.state = Skew.SymbolState.INITIALIZED; this._exportsNamespace.scope = new Skew.ObjectScope(this._global.scope, this._exportsNamespace); this._exportsNamespace.parent = this._global; this._global.objects.push(this._exportsNamespace); this._allocateNamingGroupIndex(this._exportsNamespace); for (var i = 0, list = this._symbolsToExport, count = list.length; i < count; i = i + 1 | 0) { var symbol = in_List.get(list, i); assert(symbol.parent != null); assert(Skew.in_SymbolKind.isObject(symbol.parent.kind)); var oldParent = symbol.parent.asObjectSymbol(); symbol.parent = this._exportsNamespace; if (Skew.in_SymbolKind.isObject(symbol.kind)) { in_List.removeOne(oldParent.objects, symbol.asObjectSymbol()); this._exportsNamespace.objects.push(symbol.asObjectSymbol()); } else if (Skew.in_SymbolKind.isFunction(symbol.kind)) { in_List.removeOne(oldParent.functions, symbol.asFunctionSymbol()); this._exportsNamespace.functions.push(symbol.asFunctionSymbol()); } else if (Skew.in_SymbolKind.isVariable(symbol.kind)) { in_List.removeOne(oldParent.variables, symbol.asVariableSymbol()); this._exportsNamespace.variables.push(symbol.asVariableSymbol()); } else { assert(false); } } }; Skew.JavaScriptEmitter.prototype._createIntBinary = function(kind, left, right) { if (kind == Skew.NodeKind.MULTIPLY) { return Skew.Node.createSymbolCall(this._specialVariable(Skew.JavaScriptEmitter.SpecialVariable.MULTIPLY)).appendChild(left).appendChild(right); } return this._wrapWithIntCast(Skew.Node.createBinary(kind, left, right).withType(this._cache.intType)); }; Skew.JavaScriptEmitter.prototype._wrapWithNot = function(node) { return Skew.Node.createUnary(Skew.NodeKind.NOT, node).withType(this._cache.boolType).withRange(node.range); }; Skew.JavaScriptEmitter.prototype._wrapWithIntCast = function(node) { return Skew.Node.createBinary(Skew.NodeKind.BITWISE_OR, node, this._cache.createInt(0)).withType(this._cache.intType).withRange(node.range); }; Skew.JavaScriptEmitter.prototype._removeIntCast = function(node) { if (node.kind == Skew.NodeKind.BITWISE_OR && node.binaryRight().isInt() && node.binaryRight().asInt() == 0) { node.replaceWith(node.binaryLeft().remove()); } }; Skew.JavaScriptEmitter.prototype._patchUnaryArithmetic = function(node) { if (node.resolvedType == this._cache.intType && !Skew.JavaScriptEmitter._alwaysConvertsOperandsToInt(node.parent())) { var value = node.unaryValue(); if (value.resolvedType == this._cache.intType) { if (value.isInt()) { value.content = new Skew.IntContent(-value.asInt() | 0); node.become(value.remove()); } else { node.become(this._wrapWithIntCast(node.cloneAndStealChildren())); } } } }; Skew.JavaScriptEmitter.prototype._patchBinaryArithmetic = function(node) { // Make sure arithmetic integer operators don't emit doubles outside the // integer range. Allowing this causes JIT slowdowns due to extra checks // during compilation and potential deoptimizations during execution. // Special-case the integer "%" operator where the right operand may be // "0" since that generates "NaN" which is not representable as an int. if (node.resolvedType == this._cache.intType && !Skew.JavaScriptEmitter._alwaysConvertsOperandsToInt(node.parent()) && (node.kind != Skew.NodeKind.REMAINDER && node.kind != Skew.NodeKind.UNSIGNED_SHIFT_RIGHT || !node.binaryRight().isInt() || node.binaryRight().asInt() == 0)) { var left = node.binaryLeft(); var right = node.binaryRight(); if (left.resolvedType == this._cache.intType && right.resolvedType == this._cache.intType) { node.become(this._createIntBinary(node.kind, left.remove(), right.remove()).withRange(node.range)); } } }; Skew.JavaScriptEmitter.prototype._patchTypeCheck = function(node) { var value = node.typeCheckValue(); var type = this._cache.unwrappedType(node.typeCheckType().resolvedType); if (type == this._cache.boolType) { node.become(Skew.Node.createSymbolCall(this._specialVariable(Skew.JavaScriptEmitter.SpecialVariable.IS_BOOL)).appendChild(value.remove())); } else if (this._cache.isInteger(type)) { node.become(Skew.Node.createSymbolCall(this._specialVariable(Skew.JavaScriptEmitter.SpecialVariable.IS_INT)).appendChild(value.remove())); } else if (type == this._cache.doubleType) { node.become(Skew.Node.createSymbolCall(this._specialVariable(Skew.JavaScriptEmitter.SpecialVariable.IS_DOUBLE)).appendChild(value.remove())); } else if (type == this._cache.stringType) { node.become(Skew.Node.createSymbolCall(this._specialVariable(Skew.JavaScriptEmitter.SpecialVariable.IS_STRING)).appendChild(value.remove())); } else if (type.kind == Skew.TypeKind.LAMBDA) { node.typeCheckType().replaceWith(new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent('Function')).withType(Skew.Type.DYNAMIC)); } }; // Group each variable inside the function with the function itself so that // they can be renamed together and won't cause any collisions inside the // function Skew.JavaScriptEmitter.prototype._unionVariableWithFunction = function(symbol, $function) { if (this._mangle && $function != null) { assert(this._namingGroupIndexForSymbol.has(symbol.id)); assert(this._namingGroupIndexForSymbol.has($function.id)); this._localVariableUnionFind.union(in_IntMap.get1(this._namingGroupIndexForSymbol, symbol.id), in_IntMap.get1(this._namingGroupIndexForSymbol, $function.id)); } }; Skew.JavaScriptEmitter.prototype._patchNode = function(node) { if (node == null) { return; } var oldEnclosingFunction = this._enclosingFunction; var oldLoop = this._enclosingLoop; var symbol = node.symbol; var kind = node.kind; if (this._mangle && symbol != null) { this._allocateNamingGroupIndex(symbol); in_IntMap.set(this._symbolCounts, symbol.id, in_IntMap.get(this._symbolCounts, symbol.id, 0) + 1 | 0); } if (kind == Skew.NodeKind.LAMBDA) { this._enclosingFunction = symbol.asFunctionSymbol(); } else if (Skew.in_NodeKind.isLoop(kind)) { this._enclosingLoop = node; } if (kind == Skew.NodeKind.CAST) { this._patchNode(node.castValue()); } else { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._patchNode(child); } } if (kind == Skew.NodeKind.LAMBDA) { this._enclosingFunction = oldEnclosingFunction; } else if (Skew.in_NodeKind.isLoop(kind)) { this._enclosingLoop = oldLoop; } // Split this into a separate function because this function is hot and V8 doesn't // optimize it otherwise (it's optimized "too many times" whatever that means) this._patchNodeHelper(node); }; Skew.JavaScriptEmitter.prototype._patchNodeHelper = function(node) { switch (node.kind) { case Skew.NodeKind.ADD: case Skew.NodeKind.SUBTRACT: case Skew.NodeKind.MULTIPLY: case Skew.NodeKind.DIVIDE: case Skew.NodeKind.REMAINDER: case Skew.NodeKind.UNSIGNED_SHIFT_RIGHT: { this._patchBinaryArithmetic(node); break; } case Skew.NodeKind.BREAK: { this._patchBreak(node); break; } case Skew.NodeKind.CAST: { this._patchCast(node); break; } case Skew.NodeKind.FOREACH: { this._unionVariableWithFunction(node.symbol, this._enclosingFunction); break; } case Skew.NodeKind.LAMBDA: { this._patchLambda(node); break; } case Skew.NodeKind.NAME: { this._patchName(node); break; } case Skew.NodeKind.NEGATIVE: { this._patchUnaryArithmetic(node); break; } case Skew.NodeKind.TRY: { this._patchTry(node); break; } case Skew.NodeKind.TYPE_CHECK: { this._patchTypeCheck(node); break; } case Skew.NodeKind.VARIABLE: { this._unionVariableWithFunction(node.symbol, this._enclosingFunction); break; } } if (this._mangle) { switch (node.kind) { case Skew.NodeKind.ASSIGN_INDEX: { this._peepholeMangleAssignIndex(node); break; } case Skew.NodeKind.BLOCK: { this._peepholeMangleBlock(node); break; } case Skew.NodeKind.CALL: { this._peepholeMangleCall(node); break; } case Skew.NodeKind.CONSTANT: { this._peepholeMangleConstant(node); break; } case Skew.NodeKind.FOR: { this._peepholeMangleFor(node); break; } case Skew.NodeKind.HOOK: { this._peepholeMangleHook(node); break; } case Skew.NodeKind.IF: { this._peepholeMangleIf(node); break; } case Skew.NodeKind.INDEX: { this._peepholeMangleIndex(node); break; } case Skew.NodeKind.PAIR: { this._peepholeManglePair(node); break; } case Skew.NodeKind.WHILE: { this._peepholeMangleWhile(node); break; } default: { if (Skew.in_NodeKind.isBinary(node.kind)) { this._peepholeMangleBinary(node); } break; } } } }; Skew.JavaScriptEmitter.prototype._peepholeManglePair = function(node) { if (Skew.JavaScriptEmitter._isIdentifierString(node.firstValue())) { node.firstValue().kind = Skew.NodeKind.NAME; } }; Skew.JavaScriptEmitter.prototype._peepholeMangleConstant = function(node) { switch (node.content.kind()) { case Skew.ContentKind.BOOL: { node.become(this._wrapWithNot(this._cache.createInt(node.asBool() ? 0 : 1).withRange(node.range))); break; } case Skew.ContentKind.INT: { var value = node.asInt(); // "-2147483648" => "1 << 31" if (value != 0) { var count = value.toString().length; var shift = 0; // Count zero bits while ((value & 1) == 0) { value >>>= 1; shift = shift + 1 | 0; } // Do the substitution if it makes sense if (shift != 0 && ((value.toString().length + 2 | 0) + shift.toString().length | 0) < count) { node.become(Skew.Node.createBinary(Skew.NodeKind.SHIFT_LEFT, this._cache.createInt(value), this._cache.createInt(shift)).withType(this._cache.intType).withRange(node.range)); } } break; } case Skew.ContentKind.DOUBLE: { var value1 = node.asDouble(); var reciprocal = 1 / value1; // Shorten long reciprocals (don't replace multiplication with division // because that's not numerically identical). These should be constant- // folded by the JIT at compile-time. // // "x * 0.3333333333333333" => "x * (1 / 3)" // for (var i = 1; i < 10; i = i + 1 | 0) { if (reciprocal * i == (reciprocal * i | 0) && value1.toString().length >= 10) { node.become(Skew.Node.createBinary(Skew.NodeKind.DIVIDE, this._cache.createDouble(i), this._cache.createDouble(reciprocal * i)).withType(this._cache.doubleType).withRange(node.range)); break; } } break; } } }; Skew.JavaScriptEmitter.prototype._patchName = function(node) { if (node.symbol != null && node.symbol == this._currentSelf && this._enclosingFunction != null && this._enclosingFunction.kind == Skew.SymbolKind.FUNCTION_LOCAL) { this._needsSelf = true; } }; Skew.JavaScriptEmitter.prototype._peepholeMangleCall = function(node) { var value = node.callValue(); var parent = node.parent(); // "x + y.toString()" => "x + y" where "x" is a string // "x.toString() + ''" => "x + ''" if (value.nextSibling() == null && value.kind == Skew.NodeKind.DOT && value.asString() == 'toString' && value.symbol != null && value.symbol.isImportedOrExported() && parent.kind == Skew.NodeKind.ADD && (node == parent.binaryRight() && this._cache.isEquivalentToString(parent.binaryLeft().resolvedType) || parent.binaryRight().isString())) { node.become(value.dotTarget().remove()); } }; // The "break" statement inside a switch should break out of the enclosing // loop: // // while true { // switch x { // case 0 { // break // } // } // } // // becomes: // // label: while (true) { // switch (x) { // case 0: { // break label; // } // } // } // Skew.JavaScriptEmitter.prototype._patchBreak = function(node) { var loop = this._enclosingLoop; for (var parent = node.parent(); parent != loop; parent = parent.parent()) { if (parent.kind == Skew.NodeKind.SWITCH) { var label = in_IntMap.get(this._loopLabels, loop.id, null); if (label == null) { label = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, this._enclosingFunction.scope.generateName('label')); this._allocateNamingGroupIndex(label); this._unionVariableWithFunction(label, this._enclosingFunction); in_IntMap.set(this._loopLabels, loop.id, label); } in_IntMap.set(this._loopLabels, node.id, label); break; } } }; Skew.JavaScriptEmitter.prototype._patchLambda = function(node) { var $function = node.symbol.asFunctionSymbol(); for (var i = 0, list = $function.$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); this._allocateNamingGroupIndex(argument); this._unionVariableWithFunction(argument, $function); } this._unionVariableWithFunction($function, this._enclosingFunction); }; Skew.JavaScriptEmitter.prototype._recursiveSubstituteSymbol = function(node, old, $new) { if (node.symbol == old) { node.symbol = $new; } for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._recursiveSubstituteSymbol(child, old, $new); } }; Skew.JavaScriptEmitter.prototype._patchTry = function(node) { if (node.hasChildren() && !node.hasOneChild()) { var tryBlock = node.tryBlock(); var finallyBlock = node.finallyBlock(); var firstCatch = finallyBlock != null ? finallyBlock.previousSibling() : node.lastChild(); var variable = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, firstCatch.kind == Skew.NodeKind.CATCH && firstCatch.symbol != null ? firstCatch.symbol.name : this._enclosingFunction.scope.generateName('e')); variable.resolvedType = Skew.Type.DYNAMIC; var block = new Skew.Node(Skew.NodeKind.BLOCK).appendChild(Skew.Node.createThrow(Skew.Node.createSymbolReference(variable))); // Iterate backwards over the catch blocks for (var child = firstCatch, previous = child.previousSibling(); child != tryBlock; child = previous, previous = child.previousSibling()) { var catchBlock = child.remove().catchBlock().remove(); // Substitute the variable into the contents of the block if (child.symbol != null) { this._recursiveSubstituteSymbol(catchBlock, child.symbol, variable); } // Build up the chain of tests in reverse if (child.symbol != null && child.symbol.resolvedType != Skew.Type.DYNAMIC) { var test = Skew.Node.createTypeCheck(Skew.Node.createSymbolReference(variable), new Skew.Node(Skew.NodeKind.TYPE).withType(child.symbol.resolvedType)).withType(this._cache.boolType); block = new Skew.Node(Skew.NodeKind.BLOCK).appendChild(catchBlock.hasChildren() ? Skew.Node.createIf(test, catchBlock, block) : Skew.Node.createIf(this._wrapWithNot(test), block, null)); } else { block = catchBlock; } } node.insertChildAfter(tryBlock, Skew.Node.createCatch(variable, block)); // Make sure the new variable name is mangled this._allocateNamingGroupIndex(variable); this._unionVariableWithFunction(variable, this._enclosingFunction); } }; Skew.JavaScriptEmitter.prototype._peepholeMangleBinary = function(node) { var kind = node.kind; var left = node.binaryLeft(); var right = node.binaryRight(); // "(a, b) || c" => "a, b || c" // "(a, b) && c" => "a, b && c" if ((kind == Skew.NodeKind.LOGICAL_OR || kind == Skew.NodeKind.LOGICAL_AND) && left.kind == Skew.NodeKind.SEQUENCE) { var binary = Skew.Node.createBinary(kind, left.lastChild().cloneAndStealChildren(), right.remove()).withType(Skew.Type.DYNAMIC); this._peepholeMangleBinary(binary); left.lastChild().replaceWith(binary); node.become(left.remove()); } // "a + (b + c)" => "(a + b) + c" else if (Skew.in_NodeKind.isBinaryAssociative(kind) && right.kind == kind) { while (true) { node.rotateBinaryRightToLeft(); node = node.binaryLeft(); if (!Skew.in_NodeKind.isBinaryAssociative(node.kind) || node.binaryRight().kind != node.kind) { break; } } } else if ((kind == Skew.NodeKind.GREATER_THAN_OR_EQUAL || kind == Skew.NodeKind.LESS_THAN_OR_EQUAL) && this._cache.isEquivalentToInt(left.resolvedType) && this._cache.isEquivalentToInt(right.resolvedType)) { if (left.isInt()) { var value = left.asInt(); // "2 >= a" => "3 > a" if (node.kind == Skew.NodeKind.GREATER_THAN_OR_EQUAL && value < 2147483647) { left.content = new Skew.IntContent(value + 1 | 0); node.kind = Skew.NodeKind.GREATER_THAN; } // "2 <= a" => "1 < a" else if (node.kind == Skew.NodeKind.LESS_THAN_OR_EQUAL && value >= -2147483647) { left.content = new Skew.IntContent(value - 1 | 0); node.kind = Skew.NodeKind.LESS_THAN; } } else if (right.isInt()) { var value1 = right.asInt(); // "a >= 2" => "a > 1" if (node.kind == Skew.NodeKind.GREATER_THAN_OR_EQUAL && value1 >= -2147483647) { right.content = new Skew.IntContent(value1 - 1 | 0); node.kind = Skew.NodeKind.GREATER_THAN; } // "a <= 2" => "a < 3" else if (node.kind == Skew.NodeKind.LESS_THAN_OR_EQUAL && value1 < 2147483647) { right.content = new Skew.IntContent(value1 + 1 | 0); node.kind = Skew.NodeKind.LESS_THAN; } } } }; // Simplifies the node assuming it's used in a boolean context. Note that // this may replace the passed-in node, which will then need to be queried // again if it's needed for further stuff. Skew.JavaScriptEmitter.prototype._peepholeMangleBoolean = function(node, canSwap) { var kind = node.kind; if (kind == Skew.NodeKind.EQUAL || kind == Skew.NodeKind.NOT_EQUAL) { var left = node.binaryLeft(); var right = node.binaryRight(); var replacement = Skew.JavaScriptEmitter._isFalsy(right) ? left : Skew.JavaScriptEmitter._isFalsy(left) ? right : null; // "if (a != 0) b;" => "if (a) b;" if (replacement != null) { // This minification is not valid for strings and doubles because // they both have multiple falsy values (NaN and 0, null, and "") if (left.resolvedType != null && left.resolvedType != Skew.Type.DYNAMIC && !this._cache.isEquivalentToDouble(left.resolvedType) && !this._cache.isEquivalentToString(left.resolvedType) && right.resolvedType != null && right.resolvedType != Skew.Type.DYNAMIC && !this._cache.isEquivalentToDouble(right.resolvedType) && !this._cache.isEquivalentToString(right.resolvedType)) { replacement.remove(); node.become(kind == Skew.NodeKind.EQUAL ? this._wrapWithNot(replacement) : replacement); } } else if (this._cache.isInteger(left.resolvedType) && this._cache.isInteger(right.resolvedType) && (kind == Skew.NodeKind.NOT_EQUAL || kind == Skew.NodeKind.EQUAL && canSwap == Skew.JavaScriptEmitter.BooleanSwap.SWAP)) { // "if (a != -1) c;" => "if (~a) c;" // "if (a == -1) c; else d;" => "if (~a) d; else c;" if (right.isInt() && right.asInt() == -1) { node.become(Skew.Node.createUnary(Skew.NodeKind.COMPLEMENT, left.remove()).withType(this._cache.intType)); } // "if (-1 != b) c;" => "if (~b) c;" // "if (-1 == b) c; else d;" => "if (~b) d; else c;" else if (left.isInt() && left.asInt() == -1) { node.become(Skew.Node.createUnary(Skew.NodeKind.COMPLEMENT, right.remove()).withType(this._cache.intType)); } // "if (a != b) c;" => "if (a ^ b) c;" // "if (a == b) c; else d;" => "if (a ^ b) d; else c;" // "if ((a + b | 0) != (c + d | 0)) e;" => "if (a + b ^ c + d) e;" else { node.kind = Skew.NodeKind.BITWISE_XOR; this._removeIntCast(node.binaryLeft()); this._removeIntCast(node.binaryRight()); } return kind == Skew.NodeKind.EQUAL ? Skew.JavaScriptEmitter.BooleanSwap.SWAP : Skew.JavaScriptEmitter.BooleanSwap.NO_SWAP; } } // "if (a != 0 || b != 0) c;" => "if (a || b) c;" else if (kind == Skew.NodeKind.LOGICAL_AND || kind == Skew.NodeKind.LOGICAL_OR) { this._peepholeMangleBoolean(node.binaryLeft(), Skew.JavaScriptEmitter.BooleanSwap.NO_SWAP); this._peepholeMangleBoolean(node.binaryRight(), Skew.JavaScriptEmitter.BooleanSwap.NO_SWAP); } // "if (!a) b; else c;" => "if (a) c; else b;" // "a == 0 ? b : c;" => "a ? c : b;" // This is not an "else if" check since EQUAL may be turned into NOT above if (node.kind == Skew.NodeKind.NOT && canSwap == Skew.JavaScriptEmitter.BooleanSwap.SWAP) { node.become(node.unaryValue().remove()); return Skew.JavaScriptEmitter.BooleanSwap.SWAP; } // "if (a, !b) c; else d;" => "if (a, b) d; else c;" if (node.kind == Skew.NodeKind.SEQUENCE) { return this._peepholeMangleBoolean(node.lastChild(), canSwap); } return Skew.JavaScriptEmitter.BooleanSwap.NO_SWAP; }; Skew.JavaScriptEmitter.prototype._peepholeMangleIf = function(node) { var test = node.ifTest(); var trueBlock = node.ifTrue(); var falseBlock = node.ifFalse(); var trueStatement = trueBlock.blockStatement(); var swapped = this._peepholeMangleBoolean(test, falseBlock != null || trueStatement != null && trueStatement.kind == Skew.NodeKind.EXPRESSION ? Skew.JavaScriptEmitter.BooleanSwap.SWAP : Skew.JavaScriptEmitter.BooleanSwap.NO_SWAP); // "if (a) b; else ;" => "if (a) b;" if (falseBlock != null && !falseBlock.hasChildren()) { falseBlock.remove(); falseBlock = null; } if (falseBlock != null) { var falseStatement = falseBlock.blockStatement(); // "if (!a) b; else c;" => "if (a) c; else b;" if (swapped == Skew.JavaScriptEmitter.BooleanSwap.SWAP) { var block = trueBlock; trueBlock = falseBlock; falseBlock = block; var statement = trueStatement; trueStatement = falseStatement; falseStatement = statement; trueBlock.swapWith(falseBlock); } if (trueStatement != null && falseStatement != null) { // "if (a) b; else c;" => "a ? b : c;" if (trueStatement.kind == Skew.NodeKind.EXPRESSION && falseStatement.kind == Skew.NodeKind.EXPRESSION) { var hook = Skew.Node.createHook(test.remove(), trueStatement.expressionValue().remove(), falseStatement.expressionValue().remove()).withType(Skew.Type.DYNAMIC); this._peepholeMangleHook(hook); node.become(Skew.Node.createExpression(hook)); } // "if (a) return b; else return c;" => "return a ? b : c;" else if (trueStatement.kind == Skew.NodeKind.RETURN && falseStatement.kind == Skew.NodeKind.RETURN) { var trueValue = trueStatement.returnValue(); var falseValue = falseStatement.returnValue(); if (trueValue != null && falseValue != null) { var hook1 = Skew.Node.createHook(test.remove(), trueValue.remove(), falseValue.remove()).withType(Skew.Type.DYNAMIC); this._peepholeMangleHook(hook1); node.become(Skew.Node.createReturn(hook1)); } } } } // "if (a) b;" => "a && b;" // "if (!a) b;" => "a || b;" else if (trueStatement != null && trueStatement.kind == Skew.NodeKind.EXPRESSION) { var binary = Skew.Node.createBinary(swapped == Skew.JavaScriptEmitter.BooleanSwap.SWAP ? Skew.NodeKind.LOGICAL_OR : Skew.NodeKind.LOGICAL_AND, test.remove(), trueStatement.expressionValue().remove()).withType(Skew.Type.DYNAMIC); this._peepholeMangleBinary(binary); node.become(Skew.Node.createExpression(binary)); } // "if (a) if (b) c;" => "if (a && b) c;" else { var singleIf = Skew.JavaScriptEmitter._singleIf(trueBlock); if (singleIf != null && singleIf.ifFalse() == null) { var block1 = singleIf.ifTrue(); test.replaceWith(Skew.Node.createBinary(Skew.NodeKind.LOGICAL_AND, test.cloneAndStealChildren(), singleIf.ifTest().remove()).withType(Skew.Type.DYNAMIC)); trueBlock.replaceWith(block1.remove()); } } }; Skew.JavaScriptEmitter.prototype._peepholeMangleWhile = function(node) { var test = node.whileTest(); var block = node.whileBlock(); this._peepholeMangleBoolean(test.remove(), Skew.JavaScriptEmitter.BooleanSwap.NO_SWAP); // "while (a) {}" => "for (; a;) {}" var loop = Skew.Node.createFor(new Skew.Node(Skew.NodeKind.SEQUENCE).withType(Skew.Type.DYNAMIC), test, new Skew.Node(Skew.NodeKind.SEQUENCE).withType(Skew.Type.DYNAMIC), block.remove()).withRange(node.range); this._peepholeMangleFor(loop); node.become(loop); }; Skew.JavaScriptEmitter.prototype._peepholeMangleFor = function(node) { var test = node.forTest(); this._peepholeMangleBoolean(test, Skew.JavaScriptEmitter.BooleanSwap.NO_SWAP); // "for (; true;) {}" => "for (;;) {}" if (test.kind == Skew.NodeKind.NOT && test.unaryValue().isInt() && test.unaryValue().asInt() == 0) { var empty = new Skew.Node(Skew.NodeKind.SEQUENCE).withType(Skew.Type.DYNAMIC); test.replaceWith(empty); test = empty; } // "for (a;;) if (b) break;" => "for (a; b;) {}" if (node.forUpdate().isEmptySequence()) { var statement = node.forBlock().blockStatement(); if (statement != null && statement.kind == Skew.NodeKind.IF && statement.ifFalse() == null) { var branch = statement.ifTrue().blockStatement(); if (branch != null && branch.kind == Skew.NodeKind.BREAK) { var condition = statement.remove().ifTest().remove(); condition.invertBooleanCondition(this._cache); if (test.isEmptySequence()) { test.replaceWith(condition); } else { condition = Skew.Node.createBinary(Skew.NodeKind.LOGICAL_AND, test.cloneAndStealChildren(), condition).withType(Skew.Type.DYNAMIC); this._peepholeMangleBinary(condition); test.become(condition); } } } } }; Skew.JavaScriptEmitter.prototype._peepholeMangleHook = function(node) { var test = node.hookTest(); var trueValue = node.hookTrue(); var falseValue = node.hookFalse(); var swapped = this._peepholeMangleBoolean(test, Skew.JavaScriptEmitter.BooleanSwap.SWAP); // "!a ? b : c;" => "a ? c : b;" if (swapped == Skew.JavaScriptEmitter.BooleanSwap.SWAP) { var temp = trueValue; trueValue = falseValue; falseValue = temp; trueValue.swapWith(falseValue); } // "a.b ? c : null" => "a.b && c" if (falseValue.kind == Skew.NodeKind.CAST && falseValue.castValue().kind == Skew.NodeKind.NULL && test.resolvedType != null && test.resolvedType != Skew.Type.DYNAMIC && test.resolvedType.isReference()) { node.become(Skew.Node.createBinary(Skew.NodeKind.LOGICAL_AND, test.remove(), trueValue.remove()).withType(node.resolvedType)); return; } // "a ? a : b" => "a || b" // "a = b ? a : c" => "(a = b) || c" if (test.looksTheSameAs(trueValue) && test.hasNoSideEffects() || Skew.in_NodeKind.isBinaryAssign(test.kind) && test.binaryLeft().looksTheSameAs(trueValue) && test.binaryLeft().hasNoSideEffects()) { node.become(Skew.Node.createBinary(Skew.NodeKind.LOGICAL_OR, test.remove(), falseValue.remove()).withType(node.resolvedType)); return; } // "a ? b : a" => "a && b" if (test.looksTheSameAs(falseValue) && test.hasNoSideEffects()) { node.become(Skew.Node.createBinary(Skew.NodeKind.LOGICAL_AND, test.remove(), trueValue.remove()).withType(node.resolvedType)); return; } // "a ? b : b" => "a, b" if (trueValue.looksTheSameAs(falseValue)) { node.become(test.hasNoSideEffects() ? trueValue.remove() : Skew.Node.createSequence2(test.remove(), trueValue.remove())); return; } // Collapse partially-identical hook expressions if (falseValue.kind == Skew.NodeKind.HOOK) { var falseTest = falseValue.hookTest(); var falseTrueValue = falseValue.hookTrue(); var falseFalseValue = falseValue.hookFalse(); // "a ? b : c ? b : d" => "a || c ? b : d" // "a ? b : c || d ? b : e" => "a || c || d ? b : e" if (trueValue.looksTheSameAs(falseTrueValue)) { var both = Skew.Node.createBinary(Skew.NodeKind.LOGICAL_OR, test.cloneAndStealChildren(), falseTest.remove()).withType(Skew.Type.DYNAMIC); this._peepholeMangleBinary(both); test.replaceWith(both); falseValue.replaceWith(falseFalseValue.remove()); this._peepholeMangleHook(node); return; } } // Collapse partially-identical binary expressions if (trueValue.kind == falseValue.kind && Skew.in_NodeKind.isBinary(trueValue.kind)) { var trueLeft = trueValue.binaryLeft(); var trueRight = trueValue.binaryRight(); var falseLeft = falseValue.binaryLeft(); var falseRight = falseValue.binaryRight(); // "a ? b = c : b = d;" => "b = a ? c : d;" if (trueLeft.looksTheSameAs(falseLeft)) { var hook = Skew.Node.createHook(test.remove(), trueRight.remove(), falseRight.remove()).withType(Skew.Type.DYNAMIC); this._peepholeMangleHook(hook); node.become(Skew.Node.createBinary(trueValue.kind, trueLeft.remove(), hook).withType(node.resolvedType)); } // "a ? b + 100 : c + 100;" => "(a ? b + c) + 100;" else if (trueRight.looksTheSameAs(falseRight) && !Skew.in_NodeKind.isBinaryAssign(trueValue.kind)) { var hook1 = Skew.Node.createHook(test.remove(), trueLeft.remove(), falseLeft.remove()).withType(Skew.Type.DYNAMIC); this._peepholeMangleHook(hook1); node.become(Skew.Node.createBinary(trueValue.kind, hook1, trueRight.remove()).withType(node.resolvedType)); } } // "(a, b) ? c : d" => "a, b ? c : d" if (test.kind == Skew.NodeKind.SEQUENCE) { node.prependChild(test.remove().lastChild().remove()); test.appendChild(node.cloneAndStealChildren()); node.become(test); } }; Skew.JavaScriptEmitter.prototype._peepholeMangleAssignIndex = function(node) { var left = node.assignIndexLeft(); var center = node.assignIndexCenter(); var right = node.assignIndexRight(); if (Skew.JavaScriptEmitter._isIdentifierString(center)) { node.become(Skew.Node.createBinary(Skew.NodeKind.ASSIGN, new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(center.asString())).appendChild(left.remove()).withRange(Skew.Range.span(left.range, center.range)).withType(Skew.Type.DYNAMIC), right.remove()).withRange(node.range).withType(node.resolvedType)); } }; Skew.JavaScriptEmitter.prototype._peepholeMangleIndex = function(node) { var left = node.indexLeft(); var right = node.indexRight(); if (Skew.JavaScriptEmitter._isIdentifierString(right)) { node.become(new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(right.asString())).appendChild(left.remove()).withRange(node.range).withType(node.resolvedType)); } }; Skew.JavaScriptEmitter.prototype._peepholeMangleBlock = function(node) { for (var child = node.firstChild(), next = null; child != null; child = next) { var previous = child.previousSibling(); next = child.nextSibling(); switch (child.kind) { // Make sure we entirely remove blocks only containing comment blocks case Skew.NodeKind.COMMENT_BLOCK: { child.remove(); break; } // "var a; var b;" => "var a, b;" case Skew.NodeKind.VARIABLES: { if (previous != null && previous.kind == Skew.NodeKind.VARIABLES) { child.replaceWith(previous.remove().appendChildrenFrom(child)); } break; } // "a; b; c;" => "a, b, c;" case Skew.NodeKind.EXPRESSION: { if (child.expressionValue().hasNoSideEffects()) { child.remove(); } else if (previous != null && previous.kind == Skew.NodeKind.EXPRESSION) { var sequence = Skew.Node.createSequence2(previous.remove().expressionValue().remove(), child.expressionValue().remove()); child.become(Skew.Node.createExpression(sequence)); } break; } case Skew.NodeKind.RETURN: { while (previous != null) { // "if (a) return b; return c;" => "return a ? b : c;" if (child.returnValue() != null && previous.kind == Skew.NodeKind.IF && previous.ifFalse() == null) { var statement = previous.ifTrue().blockStatement(); if (statement != null && statement.kind == Skew.NodeKind.RETURN && statement.returnValue() != null) { var hook = Skew.Node.createHook(previous.remove().ifTest().remove(), statement.returnValue().remove(), child.returnValue().remove()).withType(Skew.Type.DYNAMIC); this._peepholeMangleHook(hook); child.become(Skew.Node.createReturn(hook)); } else { break; } } // "a; return b;" => "return a, b;" else if (child.returnValue() != null && previous.kind == Skew.NodeKind.EXPRESSION) { var sequence1 = Skew.Node.createSequence2(previous.remove().expressionValue().remove(), child.returnValue().remove()); child.become(Skew.Node.createReturn(sequence1)); } else { break; } previous = child.previousSibling(); } break; } case Skew.NodeKind.IF: { while (previous != null) { // "if (a) b; if (c) b;" => "if (a || c) b;" if (child.ifFalse() == null && previous.kind == Skew.NodeKind.IF && previous.ifFalse() == null && previous.ifTrue().looksTheSameAs(child.ifTrue())) { child.ifTest().replaceWith(Skew.Node.createBinary(Skew.NodeKind.LOGICAL_OR, previous.remove().ifTest().remove(), child.ifTest().cloneAndStealChildren()).withType(Skew.Type.DYNAMIC)); } // "a; if (b) c;" => "if (a, b) c;" else if (previous.kind == Skew.NodeKind.EXPRESSION) { var sequence2 = Skew.Node.createSequence2(previous.remove().expressionValue().remove(), child.ifTest().cloneAndStealChildren()); child.ifTest().replaceWith(sequence2); } else { break; } previous = child.previousSibling(); } // "void foo() { if (a) return; b(); c() }" => "void foo() { if (!a) { b(); c() } }" // "while (a) { if (b) continue; c(); d() }" => "while (a) { if (!b) { c(); d() } }" if (child.ifFalse() == null) { var trueBlock = child.ifTrue(); if (trueBlock.hasChildren()) { var statement1 = trueBlock.lastChild(); if ((statement1.kind == Skew.NodeKind.RETURN && statement1.returnValue() == null || statement1.kind == Skew.NodeKind.CONTINUE) && Skew.JavaScriptEmitter._isJumpImplied(node, statement1.kind)) { var block = null; // If the if statement block without the jump is empty, then flip // the condition of the if statement and reuse the block. Otherwise, // create an else branch for the if statement and use that block. statement1.remove(); if (!trueBlock.hasChildren()) { child.ifTest().invertBooleanCondition(this._cache); block = trueBlock; } else if (next != null) { block = new Skew.Node(Skew.NodeKind.BLOCK); child.appendChild(block); assert(block == child.ifFalse()); } else { // Returning here is fine because this is the last child return; } // Move the rest of this block into the block for the if statement while (child.nextSibling() != null) { block.appendChild(child.nextSibling().remove()); } this._peepholeMangleBlock(block); this._peepholeMangleIf(child); // "a(); if (b) return; c();" => "a(); if (!b) c();" => "a(); !b && c();" => "a(), !b && c();" if (child.kind == Skew.NodeKind.EXPRESSION && previous != null && previous.kind == Skew.NodeKind.EXPRESSION) { var sequence3 = Skew.Node.createSequence2(previous.remove().expressionValue().remove(), child.expressionValue().remove()); child.become(Skew.Node.createExpression(sequence3)); } return; } } } break; } case Skew.NodeKind.FOR: { var setup = child.forSetup(); // "var a; for (;;) {}" => "for (var a;;) {}" if (previous != null && setup.isEmptySequence() && previous.kind == Skew.NodeKind.VARIABLES) { setup.replaceWith(previous.remove().appendChildrenFrom(setup)); } // "var a; for (var b;;) {}" => "for (var a, b;;) {}" else if (previous != null && setup.kind == Skew.NodeKind.VARIABLES && previous.kind == Skew.NodeKind.VARIABLES) { setup.replaceWith(previous.remove().appendChildrenFrom(setup)); } // "a; for (b;;) {}" => "for (a, b;;) {}" else if (previous != null && Skew.in_NodeKind.isExpression(setup.kind) && previous.kind == Skew.NodeKind.EXPRESSION) { setup.replaceWith(Skew.Node.createSequence2(previous.remove().expressionValue().remove(), setup.cloneAndStealChildren())); } break; } case Skew.NodeKind.SWITCH: { var switchValue = child.switchValue(); var defaultCase = child.defaultCase(); if (defaultCase != null) { var hasFlowAtEnd = false; // See if any non-default case will flow past the end of the switch block for (var caseChild = switchValue.nextSibling(); caseChild != defaultCase; caseChild = caseChild.nextSibling()) { if (caseChild.caseBlock().hasControlFlowAtEnd()) { hasFlowAtEnd = true; } } // "switch (a) { case b: return; default: c; break; }" => "switch (a) { case b: return; } c;" if (!hasFlowAtEnd) { node.insertChildrenAfterFrom(defaultCase.caseBlock(), child); next = child.nextSibling(); defaultCase.remove(); defaultCase = null; } } // "switch (a) {}" => "a;" if (child.hasOneChild()) { next = Skew.Node.createExpression(switchValue.remove()); child.replaceWith(next); continue; } // "switch (a) { case b: c; break; }" => "if (a == b) c;" else if (child.hasTwoChildren()) { var singleCase = child.lastChild(); if (singleCase.hasTwoChildren()) { var value = singleCase.firstChild(); next = Skew.Node.createIf(Skew.Node.createBinary(Skew.NodeKind.EQUAL, switchValue.remove(), value.remove()).withType(this._cache.boolType), singleCase.caseBlock().remove(), null); this._peepholeMangleIf(next); child.replaceWith(next); continue; } } // "switch (a) { case b: c; break; default: d; break; }" => "if (a == b) c; else d;" else if (child.hasThreeChildren()) { var firstCase = switchValue.nextSibling(); var secondCase = child.lastChild(); if (firstCase.hasTwoChildren() && secondCase.hasOneChild()) { var value1 = firstCase.firstChild(); next = Skew.Node.createIf(Skew.Node.createBinary(Skew.NodeKind.EQUAL, switchValue.remove(), value1.remove()).withType(this._cache.boolType), firstCase.caseBlock().remove(), secondCase.caseBlock().remove()); this._peepholeMangleIf(next); child.replaceWith(next); continue; } } // Optimize specific patterns of switch statements if (switchValue.kind == Skew.NodeKind.NAME && defaultCase == null) { this._peepholeMangleSwitchCases(child); } break; } } } }; // "switch (a) { case 0: return 0; case 1: return 1; case 2: return 2; }" => "if (a >= 0 && a <= 2) return a" // "switch (a) { case 0: return 1; case 1: return 2; case 2: return 3; }" => "if (a >= 0 && a <= 2) return a + 1" Skew.JavaScriptEmitter.prototype._peepholeMangleSwitchCases = function(node) { var switchValue = node.switchValue(); var firstCase = switchValue.nextSibling(); if (!this._cache.isEquivalentToInt(switchValue.resolvedType)) { return; } var sharedDelta = 0; var count = 0; var min = 0; var max = 0; for (var child = firstCase; child != null; child = child.nextSibling()) { var singleStatement = child.caseBlock().blockStatement(); if (!child.hasTwoChildren() || singleStatement == null || singleStatement.kind != Skew.NodeKind.RETURN) { return; } var caseValue = child.firstChild(); var returnValue = singleStatement.returnValue(); if (!caseValue.isInt() || returnValue == null || !returnValue.isInt()) { return; } var caseInt = caseValue.asInt(); var returnInt = returnValue.asInt(); var delta = returnInt - caseInt | 0; if (count == 0) { sharedDelta = delta; min = caseInt; max = caseInt; } else if (delta != sharedDelta) { return; } else { min = Math.min(min, caseInt); max = Math.max(max, caseInt); } count = count + 1 | 0; } // Make sure the pattern is matched if (count == 0) { return; } var block = new Skew.Node(Skew.NodeKind.BLOCK).appendChild(Skew.Node.createReturn(sharedDelta > 0 ? this._createIntBinary(Skew.NodeKind.ADD, switchValue.remove(), this._cache.createInt(sharedDelta)) : sharedDelta < 0 ? this._createIntBinary(Skew.NodeKind.SUBTRACT, switchValue.remove(), this._cache.createInt(-sharedDelta | 0)) : switchValue.remove())); // Replace the large "switch" statement with a smaller "if" statement if the entire range is covered if ((max - min | 0) == (count - 1 | 0)) { var lower = Skew.Node.createBinary(Skew.NodeKind.GREATER_THAN_OR_EQUAL, switchValue.clone(), this._cache.createInt(min)).withType(this._cache.boolType); var upper = Skew.Node.createBinary(Skew.NodeKind.LESS_THAN_OR_EQUAL, switchValue.clone(), this._cache.createInt(max)).withType(this._cache.boolType); // Convert ">=" and "<=" to ">" and "<" where possible this._peepholeMangleBinary(lower); this._peepholeMangleBinary(upper); node.replaceWith(Skew.Node.createIf(Skew.Node.createBinary(Skew.NodeKind.LOGICAL_AND, lower, upper).withType(this._cache.boolType), block, null)); } // Just combine everything into one case else { var combined = new Skew.Node(Skew.NodeKind.CASE); for (var child1 = firstCase; child1 != null; child1 = child1.nextSibling()) { combined.appendChild(child1.firstChild().remove()); } node.replaceWith(Skew.Node.createSwitch(switchValue.clone()).appendChild(combined.appendChild(block))); } }; Skew.JavaScriptEmitter.prototype._patchCast = function(node) { var value = node.castValue(); var type = node.resolvedType; var valueType = value.resolvedType; // Wrapping should be transparent in the emitted code if (type.isWrapped() || valueType.isWrapped()) { return; } // Cast to bool if (type == this._cache.boolType) { if (valueType != this._cache.boolType) { node.become(this._wrapWithNot(this._wrapWithNot(value.remove()))); } } // Cast to int else if (this._cache.isInteger(type)) { if (!this._cache.isInteger(valueType) && !Skew.JavaScriptEmitter._alwaysConvertsOperandsToInt(node.parent())) { node.become(this._wrapWithIntCast(value.remove())); } else if (value.isInt()) { node.become(value.remove().withType(node.resolvedType)); } } // Cast to double else if (type == this._cache.doubleType) { if (!this._cache.isNumeric(valueType)) { node.become(Skew.Node.createUnary(Skew.NodeKind.POSITIVE, value.remove()).withRange(node.range).withType(this._cache.doubleType)); } } // Cast to string else if (type == this._cache.stringType) { if (valueType != this._cache.stringType && valueType != Skew.Type.NULL) { node.become(Skew.Node.createSymbolCall(this._specialVariable(Skew.JavaScriptEmitter.SpecialVariable.AS_STRING)).appendChild(value.remove())); } } }; Skew.JavaScriptEmitter.prototype._specialVariable = function(name) { assert(this._specialVariables.has(name)); var variable = in_IntMap.get1(this._specialVariables, name); in_IntMap.set(this._isSpecialVariableNeeded, variable.id, 0); return variable; }; Skew.JavaScriptEmitter._isReferenceTo = function(node, symbol) { if (node.kind == Skew.NodeKind.CAST) { node = node.castValue(); } return node.kind == Skew.NodeKind.NAME && node.symbol == symbol; }; Skew.JavaScriptEmitter._isJumpImplied = function(node, kind) { assert(node.kind == Skew.NodeKind.BLOCK); assert(kind == Skew.NodeKind.RETURN || kind == Skew.NodeKind.CONTINUE); var parent = node.parent(); if (kind == Skew.NodeKind.RETURN && (parent == null || parent.kind == Skew.NodeKind.LAMBDA) || kind == Skew.NodeKind.CONTINUE && parent != null && Skew.in_NodeKind.isLoop(parent.kind)) { return true; } if (parent != null && parent.kind == Skew.NodeKind.IF && parent.nextSibling() == null) { return Skew.JavaScriptEmitter._isJumpImplied(parent.parent(), kind); } return false; }; Skew.JavaScriptEmitter._isIdentifierString = function(node) { if (node.isString()) { var value = node.asString(); for (var i = 0, count = value.length; i < count; i = i + 1 | 0) { var c = in_string.get1(value, i); if ((c < 65 || c > 90) && (c < 97 || c > 122) && c != 95 && c != 36 && (i == 0 || c < 48 || c > 57)) { return false; } } return value != '' && !Skew.JavaScriptEmitter._isKeyword.has(value); } return false; }; Skew.JavaScriptEmitter._singleIf = function(block) { if (block == null) { return null; } var statement = block.blockStatement(); if (statement != null && statement.kind == Skew.NodeKind.IF) { return statement; } return null; }; Skew.JavaScriptEmitter._numberToName = function(number) { var name = in_string.get(Skew.JavaScriptEmitter._first, number % Skew.JavaScriptEmitter._first.length | 0); number = number / Skew.JavaScriptEmitter._first.length | 0; while (number > 0) { number = number - 1 | 0; name += in_string.get(Skew.JavaScriptEmitter._rest, number % Skew.JavaScriptEmitter._rest.length | 0); number = number / Skew.JavaScriptEmitter._rest.length | 0; } return name; }; Skew.JavaScriptEmitter._isCompactNodeKind = function(kind) { return kind == Skew.NodeKind.EXPRESSION || kind == Skew.NodeKind.VARIABLES || Skew.in_NodeKind.isJump(kind); }; Skew.JavaScriptEmitter._isFalsy = function(node) { switch (node.kind) { case Skew.NodeKind.NULL: { return true; } case Skew.NodeKind.CAST: { return Skew.JavaScriptEmitter._isFalsy(node.castValue()); } case Skew.NodeKind.CONSTANT: { var content = node.content; switch (content.kind()) { case Skew.ContentKind.INT: { return Skew.in_Content.asInt(content) == 0; } case Skew.ContentKind.DOUBLE: { return Skew.in_Content.asDouble(content) == 0 || isNaN(Skew.in_Content.asDouble(content)); } case Skew.ContentKind.STRING: { return Skew.in_Content.asString(content) == ''; } } break; } } return false; }; Skew.JavaScriptEmitter._fullName = function(symbol) { var parent = symbol.parent; if (parent != null && parent.kind != Skew.SymbolKind.OBJECT_GLOBAL) { var enclosingName = Skew.JavaScriptEmitter._fullName(parent); if (symbol.isPrimaryConstructor() || symbol.isImported() && symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { return enclosingName; } assert(symbol.kind != Skew.SymbolKind.OVERLOADED_INSTANCE); if (symbol.kind == Skew.SymbolKind.FUNCTION_INSTANCE) { enclosingName += '.prototype'; } return enclosingName + '.' + Skew.JavaScriptEmitter._mangleName(symbol); } return Skew.JavaScriptEmitter._mangleName(symbol); }; Skew.JavaScriptEmitter._shouldRenameSymbol = function(symbol) { // Don't rename annotations since "@rename" is used for renaming and is identified by name return !symbol.isImportedOrExported() && !symbol.isRenamed() && !symbol.isPrimaryConstructor() && symbol.kind != Skew.SymbolKind.FUNCTION_ANNOTATION && symbol.kind != Skew.SymbolKind.OBJECT_GLOBAL && symbol.kind != Skew.SymbolKind.FUNCTION_LOCAL; }; Skew.JavaScriptEmitter._mangleName = function(symbol) { symbol = symbol.forwarded(); if (symbol.isPrimaryConstructor()) { symbol = symbol.parent; } if (!symbol.isImportedOrExported() && (Skew.JavaScriptEmitter._isKeyword.has(symbol.name) || symbol.parent != null && symbol.parent.kind == Skew.SymbolKind.OBJECT_CLASS && !Skew.in_SymbolKind.isOnInstances(symbol.kind) && Skew.JavaScriptEmitter._isFunctionProperty.has(symbol.name))) { return '$' + symbol.name; } return symbol.name; }; Skew.JavaScriptEmitter._computeNamespacePrefix = function(symbol) { assert(Skew.in_SymbolKind.isObject(symbol.kind)); return symbol.kind == Skew.SymbolKind.OBJECT_GLOBAL ? '' : Skew.JavaScriptEmitter._computeNamespacePrefix(symbol.parent.asObjectSymbol()) + Skew.JavaScriptEmitter._mangleName(symbol) + '.'; }; Skew.JavaScriptEmitter._alwaysConvertsOperandsToInt = function(node) { if (node != null) { switch (node.kind) { case Skew.NodeKind.ASSIGN_BITWISE_AND: case Skew.NodeKind.ASSIGN_BITWISE_OR: case Skew.NodeKind.ASSIGN_BITWISE_XOR: case Skew.NodeKind.ASSIGN_SHIFT_LEFT: case Skew.NodeKind.ASSIGN_SHIFT_RIGHT: case Skew.NodeKind.BITWISE_AND: case Skew.NodeKind.BITWISE_OR: case Skew.NodeKind.BITWISE_XOR: case Skew.NodeKind.COMPLEMENT: case Skew.NodeKind.SHIFT_LEFT: case Skew.NodeKind.SHIFT_RIGHT: { return true; } } } return false; }; Skew.JavaScriptEmitter.BooleanSwap = { SWAP: 0, NO_SWAP: 1 }; Skew.JavaScriptEmitter.ExtractGroupsMode = { ALL_SYMBOLS: 0, ONLY_LOCAL_VARIABLES: 1, ONLY_INSTANCE_VARIABLES: 2 }; Skew.JavaScriptEmitter.SymbolGroup = function(symbols, count) { this.symbols = symbols; this.count = count; }; Skew.JavaScriptEmitter.AfterToken = { AFTER_KEYWORD: 0, AFTER_PARENTHESIS: 1 }; Skew.JavaScriptEmitter.BracesMode = { MUST_KEEP_BRACES: 0, CAN_OMIT_BRACES: 1 }; Skew.JavaScriptEmitter.SpecialVariable = { NONE: 0, AS_STRING: 1, CREATE: 2, EXTENDS: 3, IS_BOOL: 4, IS_DOUBLE: 5, IS_INT: 6, IS_STRING: 7, MULTIPLY: 8, PROTOTYPE: 9 }; Skew.TypeScriptEmitter = function(_log, _options, _cache) { Skew.Emitter.call(this); this._log = _log; this._options = _options; this._cache = _cache; this._specialVariables = new Map(); this._ctors = new Map(); this._enclosingNamespaces = []; this._emittedComments = []; this._previousNode = null; this._previousSymbol = null; this._symbolsCheckedForImport = new Map(); this._importedFiles = new Map(); this._loopLabels = new Map(); this._enclosingFunction = null; this._expectedNextEnumValue = 0; this._currentFile = ''; }; __extends(Skew.TypeScriptEmitter, Skew.Emitter); Skew.TypeScriptEmitter.prototype.visit = function(global) { var self = this; self._indentAmount = ' '; // Generate the entry point var entryPoint = self._cache.entryPointSymbol; if (entryPoint != null) { entryPoint.name = 'main'; } // Load special-cased variables for (var i = 0, list = global.variables, count = list.length; i < count; i = i + 1 | 0) { var variable = in_List.get(list, i); var special = in_StringMap.get(Skew.TypeScriptEmitter._specialVariableMap, variable.name, Skew.TypeScriptEmitter.SpecialVariable.NONE); if (special != Skew.TypeScriptEmitter.SpecialVariable.NONE) { in_IntMap.set(self._specialVariables, special, variable); variable.flags |= Skew.SymbolFlags.IS_EXPORTED; } } assert(self._specialVariables.has(Skew.TypeScriptEmitter.SpecialVariable.AS_STRING)); assert(self._specialVariables.has(Skew.TypeScriptEmitter.SpecialVariable.IS_INT)); // Avoid emitting unnecessary stuff Skew.shakingPass(global, entryPoint, Skew.ShakingMode.USE_TYPES); self._markVirtualFunctions(global); var emitIndividualFiles = self._options.outputDirectory != null; var symbolsByFile = new Map(); // Bucket things by the source file they came from var collisions = new Map(); var add = function(s) { var name = ''; if (s.range != null) { name = s.range.source.name; } if (!symbolsByFile.has(name)) { in_StringMap.set(symbolsByFile, name, []); } in_StringMap.get1(symbolsByFile, name).push(s); // Track collisions if (!s.isImported()) { var list = in_StringMap.get(collisions, s.name, []); list.push(s); in_StringMap.set(collisions, s.name, list); } }; // There can only be one constructor in TypeScript var fixAllMultipleCtors = null; fixAllMultipleCtors = function(p) { for (var i1 = 0, list1 = p.objects, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var s = in_List.get(list1, i1); fixAllMultipleCtors(s); if (s.kind == Skew.SymbolKind.OBJECT_CLASS && !s.isImported()) { var ctors = s.functions.filter(function(f) { return f.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR; }); if (ctors.length > 1) { s.functions = s.functions.filter(function(f) { return f.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR; }); var canUseArgumentCount = ctors.every(function(c1) { return ctors.filter(function(c2) { return c1.$arguments.length == c2.$arguments.length; }).length == 1; }); in_IntMap.set(self._ctors, s.id, new Skew.TypeScriptEmitter.MultipleCtors(ctors, canUseArgumentCount)); } } } }; fixAllMultipleCtors(global); var addAll = null; addAll = function(p) { // If this namespace has comments, move its comments to // the first child of this namespace in the same file if (p.comments != null) { var all = []; for (var i1 = 0, list1 = p.variables, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var s = in_List.get(list1, i1); all.push(s); } for (var i2 = 0, list2 = p.functions, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var s1 = in_List.get(list2, i2); all.push(s1); } for (var i3 = 0, list3 = p.objects, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var s2 = in_List.get(list3, i3); all.push(s2); } // Iterate over the comments in reverse because we are prefixing for (var i = 0, count5 = p.comments.length; i < count5; i = i + 1 | 0) { var c = in_List.get(p.comments, (p.comments.length - i | 0) - 1 | 0); var best = null; for (var i4 = 0, list4 = all, count4 = list4.length; i4 < count4; i4 = i4 + 1 | 0) { var s3 = in_List.get(list4, i4); if (s3.range.source == c.range.source) { if (best == null || best.range.start > s3.range.start) { best = s3; } } } if (best != null) { best.comments = Skew.Comment.concat([c], best.comments); } else if (self._options.warnAboutIgnoredComments) { self._log.syntaxWarningIgnoredCommentInEmitter(c.range); } } } for (var i5 = 0, list5 = p.variables, count6 = list5.length; i5 < count6; i5 = i5 + 1 | 0) { var s4 = in_List.get(list5, i5); add(s4); } for (var i6 = 0, list6 = p.functions, count7 = list6.length; i6 < count7; i6 = i6 + 1 | 0) { var s5 = in_List.get(list6, i6); add(s5); } for (var i7 = 0, list7 = p.objects, count8 = list7.length; i7 < count8; i7 = i7 + 1 | 0) { var s6 = in_List.get(list7, i7); if (Skew.TypeScriptEmitter._shouldFlattenNamespace(s6)) { addAll(s6); } else { add(s6); } } }; addAll(global); // Rename all collisions in_StringMap.each(collisions, function(name, list) { if (list.length > 1) { for (var i1 = 0, list1 = list, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var s = in_List.get(list1, i1); var i = 1; while (true) { var rename = name + i.toString(); if (!collisions.has(rename)) { in_StringMap.set(collisions, rename, []); s.name = rename; break; } i = i + 1 | 0; } } } }); // Emit each global object into a separate file in_StringMap.each(symbolsByFile, function(file, symbols) { self._currentFile = file; for (var i1 = 0, list1 = symbols, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var s = in_List.get(list1, i1); if (Skew.in_SymbolKind.isObject(s.kind)) { self._emitObject(s); } } for (var i2 = 0, list2 = symbols, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var s1 = in_List.get(list2, i2); if (Skew.in_SymbolKind.isFunction(s1.kind)) { self._emitFunction(s1); } } for (var i3 = 0, list3 = symbols, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var s2 = in_List.get(list3, i3); if (Skew.in_SymbolKind.isVariable(s2.kind)) { self._emitVariable(s2); } } // Emit each object into its own file if requested if (emitIndividualFiles) { self._finalizeEmittedFile(); self._createSource(self._options.outputDirectory + '/' + self._tsFileName(file), Skew.EmitMode.SKIP_IF_EMPTY); } }); // Emit a single file if requested if (!emitIndividualFiles) { self._finalizeEmittedFile(); self._createSource(self._options.outputFile, Skew.EmitMode.ALWAYS_EMIT); } }; Skew.TypeScriptEmitter.prototype._specialVariable = function(name) { assert(this._specialVariables.has(name)); var variable = in_IntMap.get1(this._specialVariables, name); this._handleSymbol(variable); return variable; }; Skew.TypeScriptEmitter.prototype._tsFileName = function(skewFile) { skewFile = skewFile.split('<').join(''); skewFile = skewFile.split('>').join(''); if (skewFile.endsWith('.sk')) { skewFile = in_string.slice2(skewFile, 0, skewFile.length - '.sk'.length | 0); } return skewFile + '.ts'; }; Skew.TypeScriptEmitter.prototype._relativeImport = function(file) { var currentParts = this._currentFile.split('/'); var fileParts = file.split('/'); in_List.removeLast(currentParts); while (!(currentParts.length == 0) && !(fileParts.length == 0) && in_List.first(currentParts) == in_List.first(fileParts)) { in_List.removeFirst(currentParts); in_List.removeFirst(fileParts); } if (currentParts.length == 0) { fileParts.unshift('.'); } else { for (var i = 0, list = currentParts, count = list.length; i < count; i = i + 1 | 0) { var _ = in_List.get(list, i); fileParts.unshift('..'); } } return fileParts.join('/'); }; Skew.TypeScriptEmitter.prototype._finalizeEmittedFile = function() { var importedFiles = Array.from(this._importedFiles.keys()); if (!(importedFiles.length == 0)) { // Sort so the order is deterministic importedFiles.sort(Skew.SORT_STRINGS); for (var i = 0, list = importedFiles, count = list.length; i < count; i = i + 1 | 0) { var file = in_List.get(list, i); var importedNames = Array.from(in_StringMap.get1(this._importedFiles, file).keys()); // Sort so the order is deterministic importedNames.sort(Skew.SORT_STRINGS); var where = Skew.quoteString(this._relativeImport(file), Skew.QuoteStyle.DOUBLE, Skew.QuoteOctal.NORMAL); this._emitPrefix('import { ' + importedNames.join(', ') + ' } from ' + where + ';\n'); } this._emitPrefix('\n'); } this._previousSymbol = null; this._symbolsCheckedForImport = new Map(); this._importedFiles = new Map(); }; Skew.TypeScriptEmitter.prototype._handleSymbol = function(symbol) { if (!Skew.in_SymbolKind.isLocal(symbol.kind) && !this._symbolsCheckedForImport.has(symbol.id)) { in_IntMap.set(this._symbolsCheckedForImport, symbol.id, 0); var parent = symbol.parent; if (parent != null && (symbol.kind == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS || parent.kind == Skew.SymbolKind.OBJECT_WRAPPED || parent.kind == Skew.SymbolKind.OBJECT_NAMESPACE && !Skew.TypeScriptEmitter._shouldFlattenNamespace(parent) || Skew.in_SymbolKind.isObject(symbol.kind) && parent.kind == Skew.SymbolKind.OBJECT_CLASS || symbol.kind == Skew.SymbolKind.FUNCTION_GLOBAL && parent.kind == Skew.SymbolKind.OBJECT_CLASS || symbol.kind == Skew.SymbolKind.VARIABLE_GLOBAL && parent.kind == Skew.SymbolKind.OBJECT_CLASS)) { this._handleSymbol(parent); } else if (!symbol.isImported() && symbol.range != null && (symbol.kind == Skew.SymbolKind.OBJECT_CLASS || symbol.kind == Skew.SymbolKind.OBJECT_ENUM || symbol.kind == Skew.SymbolKind.OBJECT_FLAGS || symbol.kind == Skew.SymbolKind.OBJECT_WRAPPED || symbol.kind == Skew.SymbolKind.OBJECT_INTERFACE || symbol.kind == Skew.SymbolKind.OBJECT_NAMESPACE || symbol.kind == Skew.SymbolKind.FUNCTION_GLOBAL && parent.kind != Skew.SymbolKind.OBJECT_CLASS || symbol.kind == Skew.SymbolKind.VARIABLE_GLOBAL && parent.kind != Skew.SymbolKind.OBJECT_CLASS)) { var file = symbol.range.source.name; if (this._currentFile != file) { file = this._tsFileName(file); file = in_string.slice2(file, 0, file.length - '.ts'.length | 0); if (!this._importedFiles.has(file)) { in_StringMap.set(this._importedFiles, file, new Map()); } in_StringMap.set(in_StringMap.get1(this._importedFiles, file), Skew.TypeScriptEmitter._mangleName(symbol), 0); } } } }; Skew.TypeScriptEmitter.prototype._emitNewlineBeforeSymbol = function(symbol) { if (this._previousSymbol != null && (symbol.comments != null || (!Skew.in_SymbolKind.isVariable(this._previousSymbol.kind) || !Skew.in_SymbolKind.isVariable(symbol.kind)) && (!Skew.in_SymbolKind.isFunction(this._previousSymbol.kind) || this._previousSymbol.asFunctionSymbol().block != null || !Skew.in_SymbolKind.isFunction(symbol.kind) || symbol.asFunctionSymbol().block != null))) { this._emit('\n'); } this._previousSymbol = null; }; Skew.TypeScriptEmitter.prototype._emitNewlineAfterSymbol = function(symbol) { this._previousSymbol = symbol; }; Skew.TypeScriptEmitter.prototype._emitNewlineBeforeStatement = function(node) { if (this._previousNode != null && (node.comments != null || !Skew.TypeScriptEmitter._isCompactNodeKind(this._previousNode.kind) || !Skew.TypeScriptEmitter._isCompactNodeKind(node.kind))) { this._emit('\n'); } this._previousNode = null; }; Skew.TypeScriptEmitter.prototype._emitNewlineAfterStatement = function(node) { this._previousNode = node; }; Skew.TypeScriptEmitter.prototype._emitComments = function(comments) { if (comments != null) { for (var i1 = 0, list1 = comments, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var comment = in_List.get(list1, i1); for (var i = 0, list = comment.lines, count = list.length; i < count; i = i + 1 | 0) { var line = in_List.get(list, i); this._emit(this._indent + '//' + line + '\n'); } if (comment.hasGapBelow) { this._emit('\n'); } if (this._options.warnAboutIgnoredComments) { this._emittedComments.push(comment); } } } }; Skew.TypeScriptEmitter.prototype._emitTrailingComment = function(comment) { if (comment != null) { assert(comment.lines.length == 1); this._emit(' //' + in_List.first(comment.lines)); if (this._options.warnAboutIgnoredComments) { this._emittedComments.push(comment); } } }; Skew.TypeScriptEmitter.prototype._emitObject = function(symbol) { this._handleSymbol(symbol); if (symbol.isImported() || symbol.kind == Skew.SymbolKind.OBJECT_GLOBAL) { return; } this._emitNewlineBeforeSymbol(symbol); this._emitComments(symbol.comments); this._emit(this._indent); this._emit('export '); if (symbol.isAbstract()) { this._emit('abstract '); } switch (symbol.kind) { case Skew.SymbolKind.OBJECT_CLASS: { this._emit('class '); break; } case Skew.SymbolKind.OBJECT_ENUM: case Skew.SymbolKind.OBJECT_FLAGS: { var toString = in_StringMap.get(symbol.members, 'toString', null); if (toString != null && Skew.in_SymbolKind.isFunction(toString.kind) && (Skew.SymbolFlags.IS_INLINING_FORCED & toString.flags) != 0 && (Skew.SymbolFlags.IS_AUTOMATICALLY_GENERATED & toString.flags) != 0 && toString.inlinedCount == 0) { this._emit('const '); } this._emit('enum '); break; } case Skew.SymbolKind.OBJECT_INTERFACE: { this._emit('interface '); break; } case Skew.SymbolKind.OBJECT_WRAPPED: { this._emit('type '); break; } case Skew.SymbolKind.OBJECT_NAMESPACE: { this._emit('namespace '); break; } default: { assert(false); break; } } this._emit(Skew.TypeScriptEmitter._mangleName(symbol)); this._emitTypeParameters(symbol.parameters); if (symbol.kind == Skew.SymbolKind.OBJECT_WRAPPED) { this._emit(' = '); this._emitExpressionOrType(symbol.$extends, symbol.wrappedType); this._emit('\n'); this._emitNewlineAfterSymbol(symbol); } else { if (symbol.$extends != null || symbol.$implements != null) { if (symbol.$extends != null) { this._emit(' extends '); this._emitExpressionOrType(symbol.$extends, symbol.baseType); } if (symbol.$implements != null) { this._emit(' implements '); for (var i = 0, list = symbol.$implements, count = list.length; i < count; i = i + 1 | 0) { var node = in_List.get(list, i); if (node != in_List.first(symbol.$implements)) { this._emit(', '); } this._emitExpressionOrType(node, node.resolvedType); } } } this._emit(' {\n'); this._increaseIndent(); this._expectedNextEnumValue = 0; if (symbol.kind == Skew.SymbolKind.OBJECT_NAMESPACE) { this._enclosingNamespaces.push(symbol); } var variablesComeFirst = symbol.kind == Skew.SymbolKind.OBJECT_CLASS; if (variablesComeFirst) { for (var i1 = 0, list1 = symbol.variables, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var variable = in_List.get(list1, i1); this._emitVariable(variable); } } var multiple = in_IntMap.get(this._ctors, symbol.id, null); if (multiple != null) { this._emitConstructor(symbol, multiple.ctors, multiple.canUseArgumentCount); } for (var i2 = 0, list2 = symbol.functions, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var $function = in_List.get(list2, i2); this._emitFunction($function); } if (!variablesComeFirst) { for (var i3 = 0, list3 = symbol.variables, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var variable1 = in_List.get(list3, i3); this._emitVariable(variable1); } } if (symbol.kind == Skew.SymbolKind.OBJECT_NAMESPACE) { in_List.removeLast(this._enclosingNamespaces); } this._emitComments(symbol.commentsInsideEndOfBlock); this._decreaseIndent(); this._emit(this._indent + '}\n'); this._emitNewlineAfterSymbol(symbol); } if (symbol.objects.length > 0 || symbol.kind == Skew.SymbolKind.OBJECT_WRAPPED && (symbol.variables.length > 0 || symbol.functions.length > 0)) { this._emitNewlineBeforeSymbol(symbol); this._emit(this._indent + 'export namespace '); this._emit(Skew.TypeScriptEmitter._mangleName(symbol)); this._emit(' {\n'); this._increaseIndent(); if (symbol.kind == Skew.SymbolKind.OBJECT_WRAPPED) { this._enclosingNamespaces.push(symbol); } for (var i4 = 0, list4 = symbol.objects, count4 = list4.length; i4 < count4; i4 = i4 + 1 | 0) { var object = in_List.get(list4, i4); this._emitObject(object); } if (symbol.kind == Skew.SymbolKind.OBJECT_WRAPPED) { for (var i5 = 0, list5 = symbol.functions, count5 = list5.length; i5 < count5; i5 = i5 + 1 | 0) { var function1 = in_List.get(list5, i5); this._emitFunction(function1); } for (var i6 = 0, list6 = symbol.variables, count6 = list6.length; i6 < count6; i6 = i6 + 1 | 0) { var variable2 = in_List.get(list6, i6); this._emitVariable(variable2); } } if (symbol.kind == Skew.SymbolKind.OBJECT_WRAPPED) { in_List.removeLast(this._enclosingNamespaces); } this._decreaseIndent(); this._emit(this._indent + '}\n'); } }; Skew.TypeScriptEmitter.prototype._emitConstructor = function(object, ctors, canUseArgumentCount) { // Optimize for standard TypeScript idioms if we can if (canUseArgumentCount) { // Forward-declare the function signatures this._emitNewlineBeforeSymbol(in_List.first(ctors)); for (var i = 0, list = ctors, count = list.length; i < count; i = i + 1 | 0) { var ctor = in_List.get(list, i); this._emitComments(ctor.comments); this._emit(this._indent); this._emit('constructor'); this._emitTypeParameters(ctor.parameters); this._emitArgumentList(ctor); this._emit(';\n'); } this._emitNewlineAfterSymbol(in_List.first(ctors)); // Define the implementation this._emitNewlineBeforeSymbol(in_List.first(ctors)); this._emit(this._indent); this._emit('constructor() {\n'); this._increaseIndent(); for (var i2 = 0, list2 = ctors, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var ctor1 = in_List.get(list2, i2); var block = ctor1.block; var prefix = ctor1 == in_List.first(ctors) ? this._indent : '\n' + this._indent + 'else '; assert(block != null); assert(block.kind == Skew.NodeKind.BLOCK); // JavaScript arrow functions have sane capture rules for "this" so no variable insertion is needed if (ctor1.$this != null) { ctor1.$this.name = 'this'; ctor1.$this.flags |= Skew.SymbolFlags.IS_EXPORTED; } this._enclosingFunction = ctor1; this._emit(prefix + ('if (arguments.length == ' + ctor1.$arguments.length.toString() + ') {\n')); this._increaseIndent(); if (!(ctor1.$arguments.length == 0)) { this._emit(this._indent + ('let [' + ctor1.$arguments.map(function(arg) { return Skew.TypeScriptEmitter._mangleName(arg); }).join(', ') + ']: [')); for (var i1 = 0, list1 = ctor1.$arguments, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var arg = in_List.get(list1, i1); if (arg != in_List.first(ctor1.$arguments)) { this._emit(', '); } this._emitExpressionOrType(arg.type, arg.resolvedType); } this._emit('] = arguments as any;\n'); } this._emitStatements(block); this._decreaseIndent(); this._emit(this._indent + '}\n'); this._enclosingFunction = null; } this._decreaseIndent(); this._emit(this._indent); this._emit('}\n'); this._emitNewlineAfterSymbol(in_List.first(ctors)); } // Otherwise, fall back to something that is still correct: disambiguating with an index else { // Forward-declare the function signatures this._emitNewlineBeforeSymbol(in_List.first(ctors)); for (var i4 = 0, list4 = ctors, count4 = list4.length; i4 < count4; i4 = i4 + 1 | 0) { var ctor2 = in_List.get(list4, i4); this._emitComments(ctor2.comments); this._emit(this._indent); this._emit('constructor'); this._emitTypeParameters(ctor2.parameters); this._emit('(_: ' + ctors.indexOf(ctor2).toString()); for (var i3 = 0, list3 = ctor2.$arguments, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var arg1 = in_List.get(list3, i3); this._emit(', ' + Skew.TypeScriptEmitter._mangleName(arg1) + ': '); this._emitExpressionOrType(arg1.type, arg1.resolvedType); } this._emit(');\n'); } this._emitNewlineAfterSymbol(in_List.first(ctors)); // Define the implementation this._emitNewlineBeforeSymbol(in_List.first(ctors)); this._emit(this._indent); this._emit('constructor() {\n'); this._increaseIndent(); for (var i6 = 0, list6 = ctors, count6 = list6.length; i6 < count6; i6 = i6 + 1 | 0) { var ctor3 = in_List.get(list6, i6); var block1 = ctor3.block; var prefix1 = ctor3 == in_List.first(ctors) ? this._indent : '\n' + this._indent + 'else '; assert(block1 != null); assert(block1.kind == Skew.NodeKind.BLOCK); // JavaScript arrow functions have sane capture rules for "this" so no variable insertion is needed if (ctor3.$this != null) { ctor3.$this.name = 'this'; ctor3.$this.flags |= Skew.SymbolFlags.IS_EXPORTED; } this._enclosingFunction = ctor3; this._emit(prefix1 + ('if (arguments[0] == ' + ctors.indexOf(ctor3).toString() + ') {\n')); this._increaseIndent(); if (!(ctor3.$arguments.length == 0)) { this._emit(this._indent + ('let [' + ctor3.$arguments.map(function(arg) { return ', ' + Skew.TypeScriptEmitter._mangleName(arg); }).join('') + ']: [number')); for (var i5 = 0, list5 = ctor3.$arguments, count5 = list5.length; i5 < count5; i5 = i5 + 1 | 0) { var arg2 = in_List.get(list5, i5); this._emit(', '); this._emitExpressionOrType(arg2.type, arg2.resolvedType); } this._emit('] = arguments as any;\n'); } this._emitStatements(block1); this._decreaseIndent(); this._emit(this._indent + '}\n'); this._enclosingFunction = null; } this._decreaseIndent(); this._emit(this._indent); this._emit('}\n'); this._emitNewlineAfterSymbol(in_List.first(ctors)); } }; Skew.TypeScriptEmitter.prototype._emitTypeParameters = function(parameters) { if (parameters != null) { this._emit('<'); for (var i = 0, list = parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); if (parameter != in_List.first(parameters)) { this._emit(', '); } this._emit(Skew.TypeScriptEmitter._mangleName(parameter)); } this._emit('>'); } }; Skew.TypeScriptEmitter.prototype._emitArgumentList = function(symbol) { this._emit('('); for (var i = 0, list = symbol.$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); if (argument != in_List.first(symbol.$arguments)) { this._emit(', '); } this._emit(Skew.TypeScriptEmitter._mangleName(argument) + ': '); this._emitExpressionOrType(argument.type, argument.resolvedType); } this._emit(')'); }; Skew.TypeScriptEmitter.prototype._emitVariable = function(symbol) { this._handleSymbol(symbol); if (symbol.isImported()) { return; } var trailing = Skew.Comment.lastTrailingComment(symbol.comments); var notTrailing = Skew.Comment.withoutLastTrailingComment(symbol.comments); this._emitNewlineBeforeSymbol(symbol); this._emitComments(notTrailing); this._emit(this._indent); if (symbol.kind == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS) { this._emit(Skew.TypeScriptEmitter._mangleName(symbol)); if (symbol.value != null) { // Enum values are initialized with integers symbol.value.resolvedType = this._cache.intType; if (symbol.value.asInt() != this._expectedNextEnumValue) { this._emit(' = '); this._emitExpression(symbol.value, Skew.Precedence.COMMA); } this._expectedNextEnumValue = symbol.value.asInt() + 1 | 0; } this._emit(','); } else { if (symbol.kind == Skew.SymbolKind.VARIABLE_GLOBAL && symbol.parent != null && symbol.parent.kind == Skew.SymbolKind.OBJECT_CLASS) { this._emit('static '); } else if (symbol.kind != Skew.SymbolKind.VARIABLE_INSTANCE) { this._emit('export '); this._emit('let '); } var emitValue = symbol.value != null && symbol.kind != Skew.SymbolKind.VARIABLE_INSTANCE; if (emitValue && this._canOmitTypeAnnotation(symbol.value)) { this._emit(Skew.TypeScriptEmitter._mangleName(symbol)); } else { this._emit(Skew.TypeScriptEmitter._mangleName(symbol) + ': '); this._emitExpressionOrType(symbol.type, symbol.resolvedType); } if (emitValue) { this._emit(' = '); this._emitExpression(symbol.value, Skew.Precedence.COMMA); } this._emit(';'); } this._emitTrailingComment(trailing); this._emit('\n'); this._emitNewlineAfterSymbol(symbol); }; // Various heuristics to make nicer-looking code without introducing too many type errors Skew.TypeScriptEmitter.prototype._canOmitTypeAnnotation = function(value) { var type = this._cache.unwrappedType(value.resolvedType); if (type == Skew.Type.DYNAMIC) { return false; } if (value.kind == Skew.NodeKind.CALL || value.kind == Skew.NodeKind.DOT) { return true; } if (value.kind == Skew.NodeKind.NAME) { return this._enclosingFunction == null || value.symbol != this._enclosingFunction.$this; } if (type == this._cache.boolType || this._cache.isNumeric(type)) { return true; } if (type == this._cache.stringType && value.kind != Skew.NodeKind.NULL && (value.kind != Skew.NodeKind.CAST || value.castValue().kind != Skew.NodeKind.NULL)) { return true; } return false; }; Skew.TypeScriptEmitter.prototype._emitFunction = function(symbol) { this._handleSymbol(symbol); if (symbol.isImported()) { return; } // JavaScript arrow functions have sane capture rules for "this" so no variable insertion is needed if (symbol.$this != null) { symbol.$this.name = 'this'; symbol.$this.flags |= Skew.SymbolFlags.IS_EXPORTED; } this._enclosingFunction = symbol; this._emitNewlineBeforeSymbol(symbol); this._emitComments(symbol.comments); this._emit(this._indent); var block = symbol.block; if (block == null && symbol.parent != null && symbol.parent.kind == Skew.SymbolKind.OBJECT_CLASS) { this._emit('abstract '); } if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { this._emit('constructor'); } else { if (symbol.kind == Skew.SymbolKind.FUNCTION_GLOBAL && symbol.parent != null && symbol.parent.kind == Skew.SymbolKind.OBJECT_CLASS) { this._emit('static '); } else if (symbol.kind != Skew.SymbolKind.FUNCTION_INSTANCE) { this._emit('export function '); } this._emit(Skew.TypeScriptEmitter._mangleName(symbol)); } this._emitTypeParameters(symbol.parameters); this._emitArgumentList(symbol); if (symbol.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { this._emit(': '); this._emitExpressionOrType(symbol.returnType, symbol.resolvedType.returnType); } if (block == null) { this._emit(';\n'); } else { var comments = []; if (this._options.warnAboutIgnoredComments) { this._scanForComments(block, comments); this._emittedComments = []; } this._emitBlock(block); if (this._options.warnAboutIgnoredComments) { for (var i = 0, list = comments, count = list.length; i < count; i = i + 1 | 0) { var c = in_List.get(list, i); if (!(this._emittedComments.indexOf(c) != -1)) { this._log.syntaxWarningIgnoredCommentInEmitter(c.range); } } } this._emit('\n'); } this._emitNewlineAfterSymbol(symbol); this._enclosingFunction = null; }; Skew.TypeScriptEmitter.prototype._scanForComments = function(node, comments) { if (node.comments != null) { in_List.append1(comments, node.comments); } if (node.innerComments != null) { in_List.append1(comments, node.innerComments); } for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._scanForComments(child, comments); } }; Skew.TypeScriptEmitter.prototype._emitType = function(type) { if (type == null) { this._emit('void'); return; } if (type == Skew.Type.DYNAMIC) { this._emit('any'); } else if (type.kind == Skew.TypeKind.LAMBDA) { var argumentTypes = type.argumentTypes; var returnType = type.returnType; this._emit('('); for (var i = 0, count = argumentTypes.length; i < count; i = i + 1 | 0) { if (i != 0) { this._emit(', '); } this._emit('v' + i.toString() + ': '); this._emitType(in_List.get(argumentTypes, i)); } this._emit(') => '); if (returnType != null) { this._emitType(returnType); } else { this._emit('void'); } } else if (this._cache.isIntMap(type) || this._cache.isStringMap(type)) { this._emit('Map<'); this._emit(this._cache.isIntMap(type) ? 'number' : 'string'); this._emit(', '); this._emitType(in_List.first(type.substitutions)); this._emit('>'); } else { assert(type.kind == Skew.TypeKind.SYMBOL); this._handleSymbol(type.symbol); this._emit(this._fullName(type.symbol)); if (type.isParameterized()) { this._emit('<'); for (var i1 = 0, count1 = type.substitutions.length; i1 < count1; i1 = i1 + 1 | 0) { if (i1 != 0) { this._emit(', '); } this._emitType(in_List.get(type.substitutions, i1)); } this._emit('>'); } } }; Skew.TypeScriptEmitter.prototype._emitExpressionOrType = function(node, type) { if (node != null && (type == null || type == Skew.Type.DYNAMIC)) { // Treat the type "dynamic.Object" as an alias for "dynamic" instead of what it actually means if (type == Skew.Type.DYNAMIC && node.kind == Skew.NodeKind.NAME && node.asString() == 'Object') { this._emitType(Skew.Type.DYNAMIC); } else { this._emitExpression(node, Skew.Precedence.LOWEST); } } else { this._emitType(type); } }; Skew.TypeScriptEmitter.prototype._emitStatements = function(node) { this._previousNode = null; for (var child = node.firstChild(); child != null; child = child.nextSibling()) { var trailing = Skew.Comment.lastTrailingComment(child.comments); var notTrailing = Skew.Comment.withoutLastTrailingComment(child.comments); this._emitNewlineBeforeStatement(child); this._emitComments(notTrailing); this._emitStatement(child, trailing); this._emitNewlineAfterStatement(child); } this._previousNode = null; }; Skew.TypeScriptEmitter.prototype._emitBlock = function(node) { assert(node.kind == Skew.NodeKind.BLOCK); this._emit(' {\n'); this._increaseIndent(); this._emitStatements(node); this._decreaseIndent(); this._emit(this._indent + '}'); }; Skew.TypeScriptEmitter.prototype._emitIf = function(node) { this._emit('if ('); this._emitExpression(node.ifTest(), Skew.Precedence.LOWEST); this._emit(')'); var then = node.ifTrue(); var thenComments = then.comments; // Some people put comments before blocks in if statements if (thenComments != null) { this._emit('\n'); this._emitComments(thenComments); this._emit(this._indent + '{\n'); this._increaseIndent(); this._emitStatements(then); this._decreaseIndent(); this._emit(this._indent + '}'); } else { this._emitBlock(then); } var block = node.ifFalse(); if (block != null) { var singleIf = block.hasOneChild() && block.firstChild().kind == Skew.NodeKind.IF ? block.firstChild() : null; if (block.comments != null || singleIf != null && singleIf.comments != null) { this._emit('\n'); this._emit('\n'); this._emitComments(block.comments); if (singleIf != null) { this._emitComments(singleIf.comments); } this._emit(this._indent + 'else'); } else { this._emit(' else'); } if (singleIf != null) { this._emit(' '); this._emitIf(singleIf); } else { this._emitBlock(block); this._emit('\n'); } } else { this._emit('\n'); } }; Skew.TypeScriptEmitter.prototype._scanForSwitchBreak = function(node, loop) { if (node.kind == Skew.NodeKind.BREAK) { for (var parent = node.parent(); parent != loop; parent = parent.parent()) { if (parent.kind == Skew.NodeKind.SWITCH) { var label = in_IntMap.get(this._loopLabels, loop.id, null); if (label == null) { label = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, this._enclosingFunction.scope.generateName('label')); in_IntMap.set(this._loopLabels, loop.id, label); } in_IntMap.set(this._loopLabels, node.id, label); break; } } } // Stop at nested loops since those will be tested later else if (node == loop || !Skew.in_NodeKind.isLoop(node.kind)) { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._scanForSwitchBreak(child, loop); } } }; Skew.TypeScriptEmitter.prototype._emitStatement = function(node, trailing) { if (Skew.in_NodeKind.isLoop(node.kind)) { this._scanForSwitchBreak(node, node); var label = in_IntMap.get(this._loopLabels, node.id, null); if (label != null) { this._emit(this._indent + Skew.TypeScriptEmitter._mangleName(label) + (node.nextSibling() != null ? ':\n' : ':;\n')); } } switch (node.kind) { case Skew.NodeKind.COMMENT_BLOCK: { break; } case Skew.NodeKind.VARIABLES: { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { var symbol = child.symbol.asVariableSymbol(); var value = symbol.value; this._emit(this._indent + 'let '); if (value != null && this._canOmitTypeAnnotation(value)) { this._emit(Skew.TypeScriptEmitter._mangleName(symbol)); } else { this._emit(Skew.TypeScriptEmitter._mangleName(symbol) + ': '); this._emitExpressionOrType(symbol.type, symbol.resolvedType); } if (value != null) { var comments = this._commentsFromExpression(value); if (comments != null) { this._emit(' =\n'); this._increaseIndent(); this._emitComments(comments); this._emit(this._indent); this._emitExpression(value, Skew.Precedence.ASSIGN); this._decreaseIndent(); } else { this._emit(' = '); this._emitExpression(value, Skew.Precedence.ASSIGN); } } this._emit(';'); this._emitTrailingComment(trailing); this._emit('\n'); } break; } case Skew.NodeKind.EXPRESSION: { this._emit(this._indent); this._emitExpression(node.expressionValue(), Skew.Precedence.LOWEST); this._emit(';'); this._emitTrailingComment(trailing); this._emit('\n'); break; } case Skew.NodeKind.BREAK: { var label1 = in_IntMap.get(this._loopLabels, node.id, null); if (label1 != null) { this._emit(this._indent + 'break ' + Skew.TypeScriptEmitter._mangleName(label1) + ';'); } else { this._emit(this._indent + 'break;'); } this._emitTrailingComment(trailing); this._emit('\n'); break; } case Skew.NodeKind.CONTINUE: { this._emit(this._indent + 'continue;'); this._emitTrailingComment(trailing); this._emit('\n'); break; } case Skew.NodeKind.IF: { this._emit(this._indent); if (trailing != null) { this._emitComments([trailing]); } this._emitIf(node); break; } case Skew.NodeKind.SWITCH: { var switchValue = node.switchValue(); this._emit(this._indent + 'switch ('); this._emitExpression(switchValue, Skew.Precedence.LOWEST); this._emit(') {\n'); this._increaseIndent(); for (var child1 = switchValue.nextSibling(); child1 != null; child1 = child1.nextSibling()) { var block = child1.caseBlock(); var blockComments = block.comments; if (child1.previousSibling() != switchValue) { this._emit('\n'); } this._emitComments(child1.comments); if (child1.hasOneChild()) { this._emit(this._indent + 'default:'); } else { for (var value1 = child1.firstChild(); value1 != block; value1 = value1.nextSibling()) { if (value1.previousSibling() != null) { this._emit('\n'); } this._emitComments(this._commentsFromExpression(value1)); this._emit(this._indent + 'case '); this._emitExpression(value1, Skew.Precedence.LOWEST); this._emit(':'); } } // Some people put comments before blocks in case statements if (blockComments != null) { this._emit('\n'); this._emitComments(blockComments); this._emit(this._indent + '{\n'); } else { this._emit(' {\n'); } this._increaseIndent(); this._emitStatements(block); if (block.hasControlFlowAtEnd()) { this._emit(this._indent + 'break;\n'); } this._decreaseIndent(); this._emit(this._indent + '}\n'); } this._decreaseIndent(); this._emit(this._indent + '}'); this._emitTrailingComment(trailing); this._emit('\n'); break; } case Skew.NodeKind.RETURN: { this._emit(this._indent + 'return'); var value2 = node.returnValue(); if (value2 != null) { var comments1 = value2.comments; if (comments1 != null) { // JavaScript needs parentheses here to avoid ASI issues this._emit(' (\n'); this._increaseIndent(); this._emitComments(comments1); this._emit(this._indent); this._emitExpression(value2, Skew.Precedence.LOWEST); this._decreaseIndent(); this._emit(')'); } else { this._emit(' '); this._emitExpression(value2, Skew.Precedence.LOWEST); } } this._emit(';'); this._emitTrailingComment(trailing); this._emit('\n'); break; } case Skew.NodeKind.THROW: { this._emit(this._indent + 'throw '); this._emitExpression(node.throwValue(), Skew.Precedence.LOWEST); this._emit(';'); this._emitTrailingComment(trailing); this._emit('\n'); break; } case Skew.NodeKind.FOREACH: { var value3 = node.foreachValue(); this._emit(this._indent + 'for (const ' + Skew.TypeScriptEmitter._mangleName(node.symbol)); this._emit(this._cache.isList(value3.resolvedType) ? ' of ' : ' in '); this._emitExpression(value3, Skew.Precedence.LOWEST); this._emit(')'); this._emitBlock(node.foreachBlock()); this._emitTrailingComment(trailing); this._emit('\n'); break; } case Skew.NodeKind.FOR: { var setup = node.forSetup(); var test = node.forTest(); var update = node.forUpdate(); this._emit(this._indent + 'for ('); if (!setup.isEmptySequence()) { if (setup.kind == Skew.NodeKind.VARIABLES) { var symbol1 = setup.firstChild().symbol.asVariableSymbol(); this._emit('let '); for (var child2 = setup.firstChild(); child2 != null; child2 = child2.nextSibling()) { symbol1 = child2.symbol.asVariableSymbol(); assert(child2.kind == Skew.NodeKind.VARIABLE); if (child2.previousSibling() != null) { this._emit(', '); } if (this._canOmitTypeAnnotation(symbol1.value)) { this._emit(Skew.TypeScriptEmitter._mangleName(symbol1)); } else { this._emit(Skew.TypeScriptEmitter._mangleName(symbol1) + ': '); this._emitExpressionOrType(symbol1.type, symbol1.resolvedType); } this._emit(' = '); this._emitExpression(symbol1.value, Skew.Precedence.COMMA); } } else { this._emitExpression(setup, Skew.Precedence.LOWEST); } } this._emit('; '); if (!test.isEmptySequence()) { this._emitExpression(test, Skew.Precedence.LOWEST); } this._emit('; '); if (!update.isEmptySequence()) { this._emitExpression(update, Skew.Precedence.LOWEST); } this._emit(')'); this._emitBlock(node.forBlock()); this._emitTrailingComment(trailing); this._emit('\n'); break; } case Skew.NodeKind.TRY: { var tryBlock = node.tryBlock(); var finallyBlock = node.finallyBlock(); if (trailing != null) { this._emitComments([trailing]); } this._emit(this._indent + 'try'); this._emitBlock(tryBlock); this._emit('\n'); for (var child3 = tryBlock.nextSibling(); child3 != finallyBlock; child3 = child3.nextSibling()) { if (child3.comments != null) { this._emit('\n'); this._emitComments(child3.comments); } this._emit(this._indent + 'catch'); if (child3.symbol != null) { this._emit(' (' + Skew.TypeScriptEmitter._mangleName(child3.symbol) + ')'); } this._emitBlock(child3.catchBlock()); this._emit('\n'); } if (finallyBlock != null) { if (finallyBlock.comments != null) { this._emit('\n'); this._emitComments(finallyBlock.comments); } this._emit(this._indent + 'finally'); this._emitBlock(finallyBlock); this._emit('\n'); } break; } case Skew.NodeKind.WHILE: { this._emit(this._indent + 'while ('); this._emitExpression(node.whileTest(), Skew.Precedence.LOWEST); this._emit(')'); this._emitBlock(node.whileBlock()); this._emitTrailingComment(trailing); this._emit('\n'); break; } default: { assert(false); break; } } }; Skew.TypeScriptEmitter.prototype._emitContent = function(content) { switch (content.kind()) { case Skew.ContentKind.BOOL: { this._emit(Skew.in_Content.asBool(content).toString()); break; } case Skew.ContentKind.INT: { this._emit(Skew.in_Content.asInt(content).toString()); break; } case Skew.ContentKind.DOUBLE: { var value = Skew.in_Content.asDouble(content); this._emit(isNaN(value) ? 'NaN' : value == 1 / 0 ? 'Infinity' : value == -(1 / 0) ? '-Infinity' : value.toString()); break; } case Skew.ContentKind.STRING: { this._emit(Skew.quoteString(Skew.in_Content.asString(content), Skew.QuoteStyle.SHORTEST, Skew.QuoteOctal.NORMAL)); break; } } }; Skew.TypeScriptEmitter.prototype._commentsFromExpression = function(node) { var comments = node.comments; switch (node.kind) { case Skew.NodeKind.CAST: { return Skew.Comment.concat(comments, node.castValue().comments); } case Skew.NodeKind.CALL: { return Skew.Comment.concat(comments, node.callValue().comments); } } return comments; }; Skew.TypeScriptEmitter.prototype._emitCommaSeparatedExpressions = function(from, to) { var isIndented = false; for (var child = from; child != to; child = child.nextSibling()) { if (this._commentsFromExpression(child) != null) { isIndented = true; break; } } if (isIndented) { this._increaseIndent(); } while (from != to) { var comments = this._commentsFromExpression(from); var trailing = Skew.Comment.lastTrailingComment(comments); var notTrailing = Skew.Comment.withoutLastTrailingComment(comments); if (isIndented) { this._emit('\n'); this._emitComments(notTrailing); this._emit(this._indent); } this._emitExpression(from, Skew.Precedence.COMMA); from = from.nextSibling(); if (from != to) { this._emit(isIndented ? ',' : ', '); } this._emitTrailingComment(trailing); } if (isIndented) { this._decreaseIndent(); this._emit('\n'); this._emit(this._indent); } }; Skew.TypeScriptEmitter.prototype._emitExpression = function(node, precedence) { var kind = node.kind; var symbol = node.symbol; if (symbol != null) { this._handleSymbol(symbol); } switch (kind) { case Skew.NodeKind.TYPE: case Skew.NodeKind.LAMBDA_TYPE: { this._emitType(node.resolvedType); break; } case Skew.NodeKind.NULL: { this._emit('null'); break; } case Skew.NodeKind.NAME: { this._emit(symbol != null ? this._fullName(symbol) : node.asString()); break; } case Skew.NodeKind.DOT: { var innerComments = node.innerComments; this._emitExpression(node.dotTarget(), Skew.Precedence.MEMBER); if (innerComments != null) { this._increaseIndent(); this._emit('\n'); this._emitComments(innerComments); this._emit(this._indent); this._decreaseIndent(); } this._emit('.' + (symbol != null ? Skew.TypeScriptEmitter._mangleName(symbol) : node.asString())); break; } case Skew.NodeKind.STRING_INTERPOLATION: { this._emit('`'); var isString = true; for (var child = node.firstChild(); child != null; child = child.nextSibling()) { if (isString) { this._emit(Skew.quoteString(child.asString(), Skew.QuoteStyle.TYPESCRIPT_TEMPLATE, Skew.QuoteOctal.NORMAL)); } else { this._emit('${'); var value = child; // Omit implied ".toString()" calls on interpolated values if (value.kind == Skew.NodeKind.CALL) { var target = value.callValue(); if (target.nextSibling() == null && target.kind == Skew.NodeKind.DOT && target.asString() == 'toString') { value = target.dotTarget(); } } this._emitExpression(value, Skew.Precedence.LOWEST); this._emit('}'); } isString = !isString; } this._emit('`'); break; } case Skew.NodeKind.CONSTANT: { var wrap = precedence == Skew.Precedence.MEMBER && node.isNumberLessThanZero() && (!node.isDouble() || isFinite(node.asDouble())); if (wrap) { this._emit('('); } this._emitContent(node.content); if (node.resolvedType.isEnumOrFlags()) { this._emit(' as '); this._emitType(node.resolvedType); } if (wrap) { this._emit(')'); } break; } case Skew.NodeKind.CALL: { var value1 = node.callValue(); var wrap1 = value1.kind == Skew.NodeKind.LAMBDA; // Turn "new Object" into "{}" if (value1.kind == Skew.NodeKind.DOT && value1.asString() == 'new' && value1.nextSibling() == null) { var target1 = value1.dotTarget(); if (target1.kind == Skew.NodeKind.NAME && target1.asString() == 'Object') { this._emit('{}'); return; } } if (wrap1) { this._emit('('); } if (value1.kind == Skew.NodeKind.SUPER) { this._emit('super'); if (symbol.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { this._emit('.'); this._emit(Skew.TypeScriptEmitter._mangleName(symbol)); } } else if (symbol != null && symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { this._emit('new '); this._emitType(node.resolvedType); } else if (value1.kind == Skew.NodeKind.DOT && value1.asString() == 'new') { this._emit('new '); this._emitExpression(value1.dotTarget(), Skew.Precedence.MEMBER); } else { this._emitExpression(value1, Skew.Precedence.UNARY_POSTFIX); } if (wrap1) { this._emit(')'); } this._emit('('); if (symbol != null && symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { var multiple = in_IntMap.get(this._ctors, symbol.parent.id, null); if (multiple != null && !multiple.canUseArgumentCount) { this._emit(multiple.ctors.indexOf(symbol).toString()); if (value1.nextSibling() != null) { this._emit(', '); } } } this._emitCommaSeparatedExpressions(value1.nextSibling(), null); this._emit(')'); break; } case Skew.NodeKind.CAST: { var type = node.castType(); var value2 = node.castValue(); var unwrappedSource = this._cache.unwrappedType(value2.resolvedType); var unwrappedTarget = this._cache.unwrappedType(type.resolvedType); // Skip the cast in certain cases if (type.kind == Skew.NodeKind.TYPE && (type.resolvedType == Skew.Type.DYNAMIC || value2.kind == Skew.NodeKind.NULL)) { this._emitExpression(value2, precedence); } // Conversion from integer to any numeric type can be ignored else if (this._cache.isInteger(unwrappedSource) && this._cache.isNumeric(unwrappedTarget)) { this._emitExpression(value2, precedence); } // Cast from bool to a number else if (this._cache.isNumeric(unwrappedTarget) && value2.resolvedType == this._cache.boolType) { this._emitExpression(Skew.Node.createHook(value2.remove(), this._cache.createInt(1), this._cache.createInt(0)).withType(this._cache.intType), precedence); } // Cast to bool else if (unwrappedTarget == this._cache.boolType && unwrappedSource != this._cache.boolType) { this._emitExpression(Skew.Node.createUnary(Skew.NodeKind.NOT, Skew.Node.createUnary(Skew.NodeKind.NOT, value2.remove()).withType(this._cache.boolType)).withType(this._cache.boolType), precedence); } // Cast to int else if (this._cache.isInteger(unwrappedTarget) && !this._cache.isInteger(unwrappedSource)) { this._emitExpression(Skew.Node.createBinary(Skew.NodeKind.BITWISE_OR, value2.remove(), new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.IntContent(0)).withType(this._cache.intType)).withType(this._cache.intType), precedence); } // Cast to double else if (unwrappedTarget == this._cache.doubleType && unwrappedSource != this._cache.doubleType) { this._emitExpression(Skew.Node.createUnary(Skew.NodeKind.POSITIVE, value2.remove()).withType(this._cache.doubleType), precedence); } // Cast to string else if (unwrappedTarget == this._cache.stringType && unwrappedSource != this._cache.stringType) { this._emitExpression(Skew.Node.createSymbolCall(this._specialVariable(Skew.TypeScriptEmitter.SpecialVariable.AS_STRING)).appendChild(value2.remove()).withType(this._cache.stringType), precedence); } // Only emit a cast if the underlying types are different else if (unwrappedSource != unwrappedTarget || type.resolvedType == Skew.Type.DYNAMIC) { if (Skew.Precedence.ASSIGN < precedence) { this._emit('('); } this._emitExpression(value2, Skew.Precedence.ASSIGN); this._emit(' as '); this._emitExpressionOrType(type, type.resolvedType); if (Skew.Precedence.ASSIGN < precedence) { this._emit(')'); } } // Otherwise, pretend the cast isn't there else { this._emitExpression(value2, precedence); } break; } case Skew.NodeKind.TYPE_CHECK: { var value3 = node.typeCheckValue(); var type1 = node.typeCheckType(); var targetType = this._cache.unwrappedType(type1.resolvedType); if (this._cache.isInteger(targetType)) { this._emitExpression(Skew.Node.createSymbolCall(this._specialVariable(Skew.TypeScriptEmitter.SpecialVariable.IS_INT)).appendChild(value3.remove()).withType(this._cache.boolType), precedence); return; } if (Skew.Precedence.COMPARE < precedence) { this._emit('('); } if (targetType == this._cache.doubleType) { this._emit('typeof '); this._emitExpression(value3, Skew.Precedence.UNARY_PREFIX); this._emit(" === 'number'"); } else if (targetType == this._cache.stringType) { this._emit('typeof '); this._emitExpression(value3, Skew.Precedence.UNARY_PREFIX); this._emit(" === 'string'"); } else if (targetType == this._cache.boolType) { this._emit('typeof '); this._emitExpression(value3, Skew.Precedence.UNARY_PREFIX); this._emit(" === 'boolean'"); } else { this._emitExpression(value3, Skew.Precedence.LOWEST); this._emit(' instanceof '); if (type1.resolvedType == Skew.Type.DYNAMIC) { this._emitExpression(type1, Skew.Precedence.LOWEST); } else { this._emitExpressionOrType(type1, type1.resolvedType); } } if (Skew.Precedence.COMPARE < precedence) { this._emit(')'); } break; } case Skew.NodeKind.INITIALIZER_LIST: { this._emit('['); this._emitCommaSeparatedExpressions(node.firstChild(), null); this._emit(']'); break; } case Skew.NodeKind.INITIALIZER_MAP: { if (!node.hasChildren()) { this._emit('{}'); } else { this._emit('{\n'); this._increaseIndent(); for (var child1 = node.firstChild(); child1 != null; child1 = child1.nextSibling()) { this._emitComments(child1.comments); this._emit(this._indent); this._emitExpression(child1.firstValue(), Skew.Precedence.COMMA); this._emit(': '); this._emitExpression(child1.secondValue(), Skew.Precedence.COMMA); this._emit(',\n'); } this._decreaseIndent(); this._emit(this._indent + '}'); } break; } case Skew.NodeKind.INDEX: { this._emitExpression(node.indexLeft(), Skew.Precedence.UNARY_POSTFIX); this._emit('['); this._emitExpression(node.indexRight(), Skew.Precedence.LOWEST); this._emit(']'); break; } case Skew.NodeKind.ASSIGN_INDEX: { if (Skew.Precedence.ASSIGN < precedence) { this._emit('('); } this._emitExpression(node.assignIndexLeft(), Skew.Precedence.UNARY_POSTFIX); this._emit('['); this._emitExpression(node.assignIndexCenter(), Skew.Precedence.LOWEST); this._emit('] = '); this._emitExpression(node.assignIndexRight(), Skew.Precedence.ASSIGN); if (Skew.Precedence.ASSIGN < precedence) { this._emit(')'); } break; } case Skew.NodeKind.PARAMETERIZE: { var value4 = node.parameterizeValue(); if (value4.isType()) { this._emitType(node.resolvedType); } else { this._emitExpression(value4, precedence); this._emit('<'); this._emitCommaSeparatedExpressions(value4.nextSibling(), null); this._emit('>'); } break; } case Skew.NodeKind.SEQUENCE: { if (Skew.Precedence.COMMA <= precedence) { this._emit('('); } this._emitCommaSeparatedExpressions(node.firstChild(), null); if (Skew.Precedence.COMMA <= precedence) { this._emit(')'); } break; } case Skew.NodeKind.HOOK: { if (Skew.Precedence.ASSIGN < precedence) { this._emit('('); } this._emitExpression(node.hookTest(), Skew.Precedence.LOGICAL_OR); this._emit(' ?'); var left = node.hookTrue(); var leftComments = this._commentsFromExpression(left); if (leftComments != null) { this._emit('\n'); this._increaseIndent(); this._emitComments(leftComments); this._emit(this._indent); this._emitExpression(left, Skew.Precedence.ASSIGN); this._decreaseIndent(); } else { this._emit(' '); this._emitExpression(left, Skew.Precedence.ASSIGN); } this._emit(' :'); var right = node.hookFalse(); var rightComments = this._commentsFromExpression(right); if (rightComments != null) { this._emit('\n'); this._increaseIndent(); this._emitComments(rightComments); this._emit(this._indent); this._emitExpression(right, Skew.Precedence.ASSIGN); this._decreaseIndent(); } else { this._emit(' '); this._emitExpression(right, Skew.Precedence.ASSIGN); } if (Skew.Precedence.ASSIGN < precedence) { this._emit(')'); } break; } case Skew.NodeKind.LAMBDA: { var oldEnclosingFunction = this._enclosingFunction; this._enclosingFunction = symbol.asFunctionSymbol(); this._emitArgumentList(symbol.asFunctionSymbol()); this._emit(' =>'); this._emitBlock(symbol.asFunctionSymbol().block); this._enclosingFunction = oldEnclosingFunction; break; } case Skew.NodeKind.COMPLEMENT: case Skew.NodeKind.NEGATIVE: case Skew.NodeKind.NOT: case Skew.NodeKind.POSITIVE: case Skew.NodeKind.POSTFIX_DECREMENT: case Skew.NodeKind.POSTFIX_INCREMENT: case Skew.NodeKind.PREFIX_DECREMENT: case Skew.NodeKind.PREFIX_INCREMENT: { var value5 = node.unaryValue(); var info = in_IntMap.get1(Skew.operatorInfo, kind); var sign = node.sign(); if (info.precedence < precedence) { this._emit('('); } if (!Skew.in_NodeKind.isUnaryPostfix(kind)) { this._emit(info.text); // Prevent "x - -1" from becoming "x--1" if (sign != Skew.NodeKind.NULL && sign == value5.sign()) { this._emit(' '); } } this._emitExpression(value5, info.precedence); if (Skew.in_NodeKind.isUnaryPostfix(kind)) { this._emit(info.text); } if (info.precedence < precedence) { this._emit(')'); } break; } default: { if (Skew.in_NodeKind.isBinary(kind)) { var left1 = node.binaryLeft(); var right1 = node.binaryRight(); // Handle truncating integer division if (node.resolvedType == this._cache.intType && kind == Skew.NodeKind.DIVIDE && node.parent() != null && node.parent().kind != Skew.NodeKind.BITWISE_OR) { var divide = Skew.Node.createBinary(Skew.NodeKind.DIVIDE, left1.remove(), right1.remove()).withType(this._cache.intType); var zero = new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.IntContent(0)).withType(this._cache.intType); this._emitExpression(Skew.Node.createBinary(Skew.NodeKind.BITWISE_OR, divide, zero).withType(this._cache.intType), precedence); return; } var info1 = in_IntMap.get1(Skew.operatorInfo, kind); if (info1.precedence < precedence) { this._emit('('); } this._emitExpression(left1, info1.precedence + (info1.associativity == Skew.Associativity.RIGHT | 0) | 0); this._emit(' ' + info1.text + (kind == Skew.NodeKind.EQUAL || kind == Skew.NodeKind.NOT_EQUAL ? '=' : '')); var comments = this._commentsFromExpression(right1); if (comments != null) { var leading = Skew.Comment.firstTrailingComment(comments); var notLeading = Skew.Comment.withoutFirstTrailingComment(comments); this._emitTrailingComment(leading); this._emit('\n'); this._increaseIndent(); this._emitComments(notLeading); this._emit(this._indent); this._emitExpression(right1, info1.precedence + (info1.associativity == Skew.Associativity.LEFT | 0) | 0); this._decreaseIndent(); } else { this._emit(' '); this._emitExpression(right1, info1.precedence + (info1.associativity == Skew.Associativity.LEFT | 0) | 0); } if (info1.precedence < precedence) { this._emit(')'); } } else { assert(false); } break; } } }; Skew.TypeScriptEmitter.prototype._fullName = function(symbol) { var parent = symbol.parent; if (parent != null && parent.kind != Skew.SymbolKind.OBJECT_GLOBAL && (!Skew.TypeScriptEmitter._shouldFlattenNamespace(parent) || parent.isImported()) && !Skew.in_SymbolKind.isParameter(symbol.kind) && !(this._enclosingNamespaces.indexOf(parent) != -1)) { var enclosingName = this._fullName(parent); if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { return enclosingName; } return enclosingName + '.' + Skew.TypeScriptEmitter._mangleName(symbol); } return Skew.TypeScriptEmitter._mangleName(symbol); }; Skew.TypeScriptEmitter._isInsideNamespaceOrGlobal = function(symbol) { var parent = symbol.parent; return parent != null && (parent.kind == Skew.SymbolKind.OBJECT_GLOBAL || parent.kind == Skew.SymbolKind.OBJECT_NAMESPACE && Skew.TypeScriptEmitter._isInsideNamespaceOrGlobal(parent)); }; Skew.TypeScriptEmitter._shouldFlattenNamespace = function(symbol) { return symbol.kind == Skew.SymbolKind.OBJECT_NAMESPACE && Skew.TypeScriptEmitter._isInsideNamespaceOrGlobal(symbol); }; Skew.TypeScriptEmitter._isCompactNodeKind = function(kind) { return kind == Skew.NodeKind.EXPRESSION || kind == Skew.NodeKind.VARIABLES || Skew.in_NodeKind.isJump(kind); }; Skew.TypeScriptEmitter._mangleName = function(symbol) { symbol = symbol.forwarded(); if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { symbol = symbol.parent; } if (!symbol.isImportedOrExported() && Skew.TypeScriptEmitter._isKeyword.has(symbol.name)) { return '_' + symbol.name; } var parent = symbol.parent; if (parent != null && parent.kind == Skew.SymbolKind.OBJECT_NAMESPACE && parent.name.startsWith('in_')) { var prefix = Skew.TypeScriptEmitter._mangleName(parent); if (prefix.startsWith('in_')) { prefix = in_string.slice1(prefix, 3); } return prefix + '_' + symbol.name; } return symbol.name; }; Skew.TypeScriptEmitter.MultipleCtors = function(ctors, canUseArgumentCount) { this.ctors = ctors; this.canUseArgumentCount = canUseArgumentCount; }; Skew.TypeScriptEmitter.SpecialVariable = { NONE: 0, AS_STRING: 1, IS_INT: 2 }; Skew.QuoteStyle = { DOUBLE: 0, SINGLE: 1, SHORTEST: 2, TYPESCRIPT_TEMPLATE: 3 }; Skew.QuoteOctal = { NORMAL: 0, OCTAL_WORKAROUND: 1 }; Skew.Associativity = { NONE: 0, LEFT: 1, RIGHT: 2 }; // The same operator precedence as C for the most part Skew.Precedence = { LOWEST: 0, COMMA: 1, ASSIGN: 2, NULL_JOIN: 3, LOGICAL_OR: 4, LOGICAL_AND: 5, BITWISE_OR: 6, BITWISE_XOR: 7, BITWISE_AND: 8, EQUAL: 9, COMPARE: 10, SHIFT: 11, ADD: 12, MULTIPLY: 13, UNARY_PREFIX: 14, UNARY_POSTFIX: 15, MEMBER: 16 }; Skew.CPlusPlusEmitter = function(_options, _cache) { Skew.Emitter.call(this); this._options = _options; this._cache = _cache; this._previousNode = null; this._previousSymbol = null; this._namespaceStack = []; this._symbolsCheckedForInclude = new Map(); this._includeNames = new Map(); this._loopLabels = new Map(); this._enclosingFunction = null; this._dummyFunction = new Skew.FunctionSymbol(Skew.SymbolKind.FUNCTION_INSTANCE, ''); }; __extends(Skew.CPlusPlusEmitter, Skew.Emitter); Skew.CPlusPlusEmitter.prototype.visit = function(global) { // Generate the entry point var entryPoint = this._cache.entryPointSymbol; if (entryPoint != null) { entryPoint.name = 'main'; // The entry point must not be in a namespace if (entryPoint.parent != global) { in_List.removeOne(entryPoint.parent.asObjectSymbol().functions, entryPoint); entryPoint.parent = global; global.functions.push(entryPoint); } // The entry point in C++ takes an array, not a list if (entryPoint.$arguments.length == 1) { var argument = in_List.first(entryPoint.$arguments); var argc = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_ARGUMENT, entryPoint.scope.generateName('argc')); var argv = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_ARGUMENT, entryPoint.scope.generateName('argv')); argc.initializeWithType(this._cache.intType); argv.type = new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent('char**')).withType(Skew.Type.DYNAMIC); argv.resolvedType = Skew.Type.DYNAMIC; argv.state = Skew.SymbolState.INITIALIZED; entryPoint.$arguments = [argc, argv]; entryPoint.resolvedType.argumentTypes = [argc.resolvedType, argv.resolvedType]; // Create the list from the array if (entryPoint.block != null) { var advance = new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent('*' + argv.name + '++')).withType(Skew.Type.DYNAMIC); var block = new Skew.Node(Skew.NodeKind.BLOCK).appendChild(Skew.Node.createExpression(Skew.Node.createCall(new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent('append')).appendChild(Skew.Node.createSymbolReference(argument)).withType(Skew.Type.DYNAMIC)).withType(Skew.Type.DYNAMIC).appendChild(advance))); var check = Skew.Node.createIf(advance.clone(), new Skew.Node(Skew.NodeKind.BLOCK).appendChild(new Skew.Node(Skew.NodeKind.WHILE).appendChild(new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent('*' + argv.name)).withType(Skew.Type.DYNAMIC)).appendChild(block)), null); argument.kind = Skew.SymbolKind.VARIABLE_LOCAL; argument.value = Skew.Node.createCall(new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent('new')).appendChild(new Skew.Node(Skew.NodeKind.TYPE).withType(argument.resolvedType)).withType(Skew.Type.DYNAMIC)).withType(Skew.Type.DYNAMIC); entryPoint.block.prependChild(check); entryPoint.block.prependChild(new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(Skew.Node.createVariable(argument))); } } } // Make sure strings reference the right namespace. This has to be set // here instead of in the library code to avoid a cyclic definition. this._cache.stringType.symbol.name = 'Skew::string'; // Avoid emitting unnecessary stuff Skew.shakingPass(global, this._cache.entryPointSymbol, Skew.ShakingMode.USE_TYPES); this._markVirtualFunctions(global); // Nested types in C++ can't be forward declared var sorted = this._sortedObjects(global); for (var i = 0, list = sorted, count = list.length; i < count; i = i + 1 | 0) { var symbol = in_List.get(list, i); this._moveNestedObjectToEnclosingNamespace(symbol); } // Emit code in passes to deal with C++'s forward declarations for (var i1 = 0, list1 = sorted, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var symbol1 = in_List.get(list1, i1); this._declareObject(symbol1); } for (var i2 = 0, list2 = sorted, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var symbol2 = in_List.get(list2, i2); this._defineObject(symbol2); } this._adjustNamespace(null); for (var i4 = 0, list4 = sorted, count4 = list4.length; i4 < count4; i4 = i4 + 1 | 0) { var symbol3 = in_List.get(list4, i4); if (!symbol3.isImported()) { for (var i3 = 0, list3 = symbol3.variables, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var variable = in_List.get(list3, i3); if (variable.kind == Skew.SymbolKind.VARIABLE_GLOBAL) { this._emitVariable(variable, Skew.CPlusPlusEmitter.CodeMode.IMPLEMENT); } } } } this._adjustNamespace(null); for (var i6 = 0, list6 = sorted, count6 = list6.length; i6 < count6; i6 = i6 + 1 | 0) { var symbol4 = in_List.get(list6, i6); if (!symbol4.isImported()) { for (var i5 = 0, list5 = symbol4.functions, count5 = list5.length; i5 < count5; i5 = i5 + 1 | 0) { var $function = in_List.get(list5, i5); this._emitFunction($function, Skew.CPlusPlusEmitter.CodeMode.IMPLEMENT); } this._emitMarkFunction(symbol4, Skew.CPlusPlusEmitter.CodeMode.IMPLEMENT); } } this._finalizeEmittedFile(); this._createSource(this._options.outputFile, Skew.EmitMode.ALWAYS_EMIT); }; Skew.CPlusPlusEmitter.prototype._emitNewlineBeforeSymbol = function(symbol, mode) { if (this._previousSymbol != null && (!Skew.in_SymbolKind.isVariable(this._previousSymbol.kind) || !Skew.in_SymbolKind.isVariable(symbol.kind) || symbol.comments != null) && (mode != Skew.CPlusPlusEmitter.CodeMode.DEFINE || !Skew.in_SymbolKind.isFunction(this._previousSymbol.kind) || !Skew.in_SymbolKind.isFunction(symbol.kind) || symbol.comments != null) && (mode != Skew.CPlusPlusEmitter.CodeMode.DECLARE || this._previousSymbol.kind != Skew.SymbolKind.OBJECT_CLASS || symbol.kind != Skew.SymbolKind.OBJECT_CLASS)) { this._emit('\n'); } this._previousSymbol = null; }; Skew.CPlusPlusEmitter.prototype._emitNewlineAfterSymbol = function(symbol) { this._previousSymbol = symbol; }; Skew.CPlusPlusEmitter.prototype._emitNewlineBeforeStatement = function(node) { if (this._previousNode != null && (node.comments != null || !Skew.CPlusPlusEmitter._isCompactNodeKind(this._previousNode.kind) || !Skew.CPlusPlusEmitter._isCompactNodeKind(node.kind))) { this._emit('\n'); } this._previousNode = null; }; Skew.CPlusPlusEmitter.prototype._emitNewlineAfterStatement = function(node) { this._previousNode = node; }; Skew.CPlusPlusEmitter.prototype._adjustNamespace = function(symbol) { // Get the namespace chain for this symbol var symbols = []; while (symbol != null && symbol.kind != Skew.SymbolKind.OBJECT_GLOBAL) { if (symbol.kind == Skew.SymbolKind.OBJECT_NAMESPACE || symbol.kind == Skew.SymbolKind.OBJECT_WRAPPED) { symbols.unshift(symbol); } symbol = symbol.parent; } // Find the intersection var limit = Math.min(this._namespaceStack.length, symbols.length); var i = 0; while (i < limit) { if (in_List.get(this._namespaceStack, i) != in_List.get(symbols, i)) { break; } i = i + 1 | 0; } // Leave the old namespace while (this._namespaceStack.length > i) { var object = in_List.takeLast(this._namespaceStack); this._decreaseIndent(); this._emit(this._indent + '}\n'); this._emitNewlineAfterSymbol(object); } // Enter the new namespace while (this._namespaceStack.length < symbols.length) { var object1 = in_List.get(symbols, this._namespaceStack.length); this._emitNewlineBeforeSymbol(object1, Skew.CPlusPlusEmitter.CodeMode.DEFINE); this._emit(this._indent + 'namespace ' + Skew.CPlusPlusEmitter._mangleName(object1) + ' {\n'); this._increaseIndent(); this._namespaceStack.push(object1); } }; Skew.CPlusPlusEmitter.prototype._emitComments = function(comments) { if (comments != null) { for (var i1 = 0, list1 = comments, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var comment = in_List.get(list1, i1); for (var i = 0, list = comment.lines, count = list.length; i < count; i = i + 1 | 0) { var line = in_List.get(list, i); this._emit(this._indent + '//' + line + '\n'); } if (comment.hasGapBelow) { this._emit('\n'); } } } }; Skew.CPlusPlusEmitter.prototype._moveNestedObjectToEnclosingNamespace = function(symbol) { var parent = symbol.parent; while (parent != null && parent.kind == Skew.SymbolKind.OBJECT_CLASS) { parent = parent.parent; } if (symbol.parent != parent) { in_List.removeOne(symbol.parent.asObjectSymbol().objects, symbol); symbol.parent = parent; parent.asObjectSymbol().objects.push(symbol); } }; Skew.CPlusPlusEmitter.prototype._declareObject = function(symbol) { if (symbol.isImported()) { return; } switch (symbol.kind) { case Skew.SymbolKind.OBJECT_ENUM: case Skew.SymbolKind.OBJECT_FLAGS: { this._adjustNamespace(symbol); this._emitNewlineBeforeSymbol(symbol, Skew.CPlusPlusEmitter.CodeMode.DECLARE); if (symbol.kind == Skew.SymbolKind.OBJECT_FLAGS) { this._emit(this._indent + 'namespace ' + Skew.CPlusPlusEmitter._mangleName(symbol) + ' {\n'); this._increaseIndent(); if (!(symbol.variables.length == 0)) { this._emit(this._indent + 'enum {\n'); } } else { this._emit(this._indent + 'enum struct ' + Skew.CPlusPlusEmitter._mangleName(symbol) + ' {\n'); } this._increaseIndent(); for (var i = 0, list = symbol.variables, count = list.length; i < count; i = i + 1 | 0) { var variable = in_List.get(list, i); this._emitVariable(variable, Skew.CPlusPlusEmitter.CodeMode.DECLARE); } this._decreaseIndent(); if (symbol.kind == Skew.SymbolKind.OBJECT_FLAGS) { if (!(symbol.variables.length == 0)) { this._emit(this._indent + '};\n'); } this._decreaseIndent(); this._emit(this._indent + '}\n'); } else { this._emit(this._indent + '};\n'); } this._emitNewlineAfterSymbol(symbol); break; } case Skew.SymbolKind.OBJECT_CLASS: case Skew.SymbolKind.OBJECT_INTERFACE: { this._adjustNamespace(symbol); this._emitNewlineBeforeSymbol(symbol, Skew.CPlusPlusEmitter.CodeMode.DECLARE); this._emitTypeParameters(symbol.parameters); this._emit(this._indent + 'struct ' + Skew.CPlusPlusEmitter._mangleName(symbol) + ';\n'); this._emitNewlineAfterSymbol(symbol); break; } } }; Skew.CPlusPlusEmitter.prototype._defineObject = function(symbol) { if (symbol.isImported()) { return; } switch (symbol.kind) { case Skew.SymbolKind.OBJECT_CLASS: case Skew.SymbolKind.OBJECT_INTERFACE: { this._adjustNamespace(symbol); this._emitNewlineBeforeSymbol(symbol, Skew.CPlusPlusEmitter.CodeMode.DEFINE); this._emitComments(symbol.comments); this._emitTypeParameters(symbol.parameters); this._emit(this._indent + 'struct ' + Skew.CPlusPlusEmitter._mangleName(symbol)); if (symbol.$extends != null || symbol.$implements != null) { this._emit(' : '); if (symbol.$extends != null) { this._emitExpressionOrType(symbol.$extends, symbol.baseType, Skew.CPlusPlusEmitter.CppEmitMode.BARE); } if (symbol.$implements != null) { for (var i = 0, list = symbol.$implements, count = list.length; i < count; i = i + 1 | 0) { var node = in_List.get(list, i); if (node != in_List.first(symbol.$implements) || symbol.$extends != null) { this._emit(', '); } this._emitExpressionOrType(node, node.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.BARE); } } } else { this._emit(' : virtual Skew::Object'); } this._emit(' {\n'); this._increaseIndent(); for (var i1 = 0, list1 = symbol.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); this._emitFunction($function, Skew.CPlusPlusEmitter.CodeMode.DEFINE); } for (var i2 = 0, list2 = symbol.variables, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); this._emitVariable(variable, Skew.CPlusPlusEmitter.CodeMode.DEFINE); } this._emitMarkFunction(symbol, Skew.CPlusPlusEmitter.CodeMode.DEFINE); this._decreaseIndent(); this._emit(this._indent + '};\n'); this._emitNewlineAfterSymbol(symbol); break; } case Skew.SymbolKind.OBJECT_NAMESPACE: case Skew.SymbolKind.OBJECT_WRAPPED: { this._adjustNamespace(symbol); for (var i3 = 0, list3 = symbol.functions, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var function1 = in_List.get(list3, i3); this._emitFunction(function1, Skew.CPlusPlusEmitter.CodeMode.DEFINE); } for (var i4 = 0, list4 = symbol.variables, count4 = list4.length; i4 < count4; i4 = i4 + 1 | 0) { var variable1 = in_List.get(list4, i4); this._emitVariable(variable1, Skew.CPlusPlusEmitter.CodeMode.DEFINE); } break; } } }; Skew.CPlusPlusEmitter.prototype._emitEnclosingSymbolPrefix = function(parent) { this._emit(Skew.CPlusPlusEmitter._fullName(parent)); if (parent.parameters != null) { this._emit('<'); for (var i = 0, list = parent.parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); if (parameter != in_List.first(parent.parameters)) { this._emit(', '); } this._emit(Skew.CPlusPlusEmitter._mangleName(parameter)); } this._emit('>'); } this._emit('::'); }; Skew.CPlusPlusEmitter.prototype._emitFunction = function(symbol, mode) { var parent = symbol.parent.asObjectSymbol(); var block = symbol.block; if (symbol.isImported() || mode == Skew.CPlusPlusEmitter.CodeMode.IMPLEMENT && block == null) { return; } // We can't use lambdas in C++ since they don't have the right semantics so no variable insertion is needed if (symbol.$this != null) { symbol.$this.name = 'this'; symbol.$this.flags |= Skew.SymbolFlags.IS_EXPORTED; } this._enclosingFunction = symbol; this._emitNewlineBeforeSymbol(symbol, mode); this._emitComments(symbol.comments); if (mode == Skew.CPlusPlusEmitter.CodeMode.IMPLEMENT) { // TODO: Merge these with the ones on the symbol when symbols have both this._emitTypeParameters(parent.parameters); } this._emitTypeParameters(symbol.parameters); this._emit(this._indent); if (mode == Skew.CPlusPlusEmitter.CodeMode.DEFINE && symbol.kind == Skew.SymbolKind.FUNCTION_GLOBAL && symbol.parent.kind == Skew.SymbolKind.OBJECT_CLASS) { this._emit('static '); } if (symbol.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { if (mode == Skew.CPlusPlusEmitter.CodeMode.DEFINE && (symbol.isVirtual() || block == null)) { this._emit('virtual '); } this._emitExpressionOrType(symbol.returnType, symbol.resolvedType.returnType, Skew.CPlusPlusEmitter.CppEmitMode.DECLARATION); } if (mode == Skew.CPlusPlusEmitter.CodeMode.IMPLEMENT && parent.kind != Skew.SymbolKind.OBJECT_GLOBAL) { this._emitEnclosingSymbolPrefix(parent); } this._emit(Skew.CPlusPlusEmitter._mangleName(symbol)); this._emitArgumentList(symbol); if (mode == Skew.CPlusPlusEmitter.CodeMode.IMPLEMENT) { // Move the super constructor call out of the function body if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR && block.hasChildren()) { var first = block.firstChild(); if (first.kind == Skew.NodeKind.EXPRESSION) { var call = first.expressionValue(); if (call.kind == Skew.NodeKind.CALL && call.callValue().kind == Skew.NodeKind.SUPER) { this._emit(' : '); first.remove(); this._emitExpression(call, Skew.Precedence.LOWEST); } } } this._emitBlock(block); this._emit('\n'); } else { if (symbol.overridden != null && symbol.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { this._emit(' override'); } if (block == null) { this._emit(' = 0'); } this._emit(';\n'); } this._emitNewlineAfterSymbol(symbol); this._enclosingFunction = null; }; Skew.CPlusPlusEmitter.prototype._emitMarkFunction = function(symbol, mode) { if (symbol.kind == Skew.SymbolKind.OBJECT_CLASS) { if (mode == Skew.CPlusPlusEmitter.CodeMode.DEFINE) { this._emit('\n' + this._indent + '#ifdef SKEW_GC_MARK_AND_SWEEP\n'); this._increaseIndent(); this._emit(this._indent + 'virtual void __gc_mark() override;\n'); this._decreaseIndent(); this._emit(this._indent + '#endif\n'); } else if (mode == Skew.CPlusPlusEmitter.CodeMode.IMPLEMENT) { this._emitNewlineBeforeSymbol(this._dummyFunction, mode); this._emit(this._indent + '#ifdef SKEW_GC_MARK_AND_SWEEP\n'); this._increaseIndent(); this._emitTypeParameters(symbol.parameters); this._emit(this._indent + 'void '); this._emitEnclosingSymbolPrefix(symbol); this._emit('__gc_mark() {\n'); this._increaseIndent(); if (symbol.baseClass != null) { this._emit(this._indent + Skew.CPlusPlusEmitter._fullName(symbol.baseClass) + '::__gc_mark();\n'); } for (var i = 0, list = symbol.variables, count = list.length; i < count; i = i + 1 | 0) { var variable = in_List.get(list, i); if (variable.kind == Skew.SymbolKind.VARIABLE_INSTANCE && variable.parent == symbol && this._isReferenceType(variable.resolvedType)) { this._emit(this._indent + 'Skew::GC::mark(' + Skew.CPlusPlusEmitter._mangleName(variable) + ');\n'); } } this._decreaseIndent(); this._emit(this._indent + '}\n'); this._decreaseIndent(); this._emit(this._indent + '#endif\n'); this._emitNewlineAfterSymbol(this._dummyFunction); } } }; Skew.CPlusPlusEmitter.prototype._emitTypeParameters = function(parameters) { if (parameters != null) { this._emit(this._indent + 'template <'); for (var i = 0, list = parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); if (parameter != in_List.first(parameters)) { this._emit(', '); } this._emit('typename ' + Skew.CPlusPlusEmitter._mangleName(parameter)); } this._emit('>\n'); } }; Skew.CPlusPlusEmitter.prototype._emitArgumentList = function(symbol) { this._emit('('); for (var i = 0, list = symbol.$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); if (argument != in_List.first(symbol.$arguments)) { this._emit(', '); } this._emitExpressionOrType(argument.type, argument.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.DECLARATION); this._emit(Skew.CPlusPlusEmitter._mangleName(argument)); } this._emit(')'); }; Skew.CPlusPlusEmitter.prototype._emitVariable = function(symbol, mode) { if (symbol.isImported()) { return; } var avoidFullName = symbol.kind == Skew.SymbolKind.VARIABLE_GLOBAL && symbol.parent.kind != Skew.SymbolKind.OBJECT_CLASS; if (mode == Skew.CPlusPlusEmitter.CodeMode.IMPLEMENT && symbol.kind != Skew.SymbolKind.VARIABLE_LOCAL) { this._adjustNamespace(avoidFullName ? symbol.parent : null); } this._emitNewlineBeforeSymbol(symbol, mode); this._emitComments(symbol.comments); if (symbol.kind == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS) { // Enum values are initialized with integers, so avoid any casts symbol.value.resolvedType = this._cache.intType; this._emit(this._indent + Skew.CPlusPlusEmitter._mangleName(symbol) + ' = '); this._emitExpression(symbol.value, Skew.Precedence.COMMA); this._emit(',\n'); } else { this._emit(this._indent); if (mode == Skew.CPlusPlusEmitter.CodeMode.DEFINE && symbol.kind == Skew.SymbolKind.VARIABLE_GLOBAL) { this._emit(symbol.parent.kind == Skew.SymbolKind.OBJECT_CLASS ? 'static ' : 'extern '); } // Global variables must be stored in roots to avoid accidental garbage collection if (symbol.kind == Skew.SymbolKind.VARIABLE_GLOBAL && this._isReferenceType(symbol.resolvedType)) { this._emit('Skew::Root<'); this._emitType(symbol.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.BARE); this._emit('> '); } else { this._emitType(symbol.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.DECLARATION); } if (mode == Skew.CPlusPlusEmitter.CodeMode.DEFINE) { this._emit(Skew.CPlusPlusEmitter._mangleName(symbol)); } else { this._emit(avoidFullName ? Skew.CPlusPlusEmitter._mangleName(symbol) : Skew.CPlusPlusEmitter._fullName(symbol)); if (symbol.value != null) { this._emit(' = '); this._emitExpression(symbol.value, Skew.Precedence.ASSIGN); } } this._emit(';\n'); } this._emitNewlineAfterSymbol(symbol); }; Skew.CPlusPlusEmitter.prototype._emitStatements = function(node) { this._previousNode = null; for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._emitNewlineBeforeStatement(child); this._emitComments(child.comments); this._emitStatement(child); this._emitNewlineAfterStatement(child); } this._previousNode = null; }; Skew.CPlusPlusEmitter.prototype._emitBlock = function(node) { this._emit(' {\n'); this._increaseIndent(); this._emitStatements(node); this._decreaseIndent(); this._emit(this._indent + '}'); }; Skew.CPlusPlusEmitter.prototype._scanForSwitchBreak = function(node, loop) { if (node.kind == Skew.NodeKind.BREAK) { for (var parent = node.parent(); parent != loop; parent = parent.parent()) { if (parent.kind == Skew.NodeKind.SWITCH) { var label = in_IntMap.get(this._loopLabels, loop.id, null); if (label == null) { label = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, this._enclosingFunction.scope.generateName('label')); in_IntMap.set(this._loopLabels, loop.id, label); } in_IntMap.set(this._loopLabels, node.id, label); break; } } } // Stop at nested loops since those will be tested later else if (node == loop || !Skew.in_NodeKind.isLoop(node.kind)) { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._scanForSwitchBreak(child, loop); } } }; Skew.CPlusPlusEmitter.prototype._emitStatement = function(node) { if (Skew.in_NodeKind.isLoop(node.kind)) { this._scanForSwitchBreak(node, node); } switch (node.kind) { case Skew.NodeKind.COMMENT_BLOCK: { break; } case Skew.NodeKind.VARIABLES: { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._emitVariable(child.symbol.asVariableSymbol(), Skew.CPlusPlusEmitter.CodeMode.IMPLEMENT); } break; } case Skew.NodeKind.EXPRESSION: { this._emit(this._indent); this._emitExpression(node.expressionValue(), Skew.Precedence.LOWEST); this._emit(';\n'); break; } case Skew.NodeKind.BREAK: { var label = in_IntMap.get(this._loopLabels, node.id, null); if (label != null) { this._emit(this._indent + 'goto ' + Skew.CPlusPlusEmitter._mangleName(label) + ';\n'); } else { this._emit(this._indent + 'break;\n'); } break; } case Skew.NodeKind.CONTINUE: { this._emit(this._indent + 'continue;\n'); break; } case Skew.NodeKind.IF: { this._emit(this._indent); this._emitIf(node); this._emit('\n'); break; } case Skew.NodeKind.SWITCH: { var switchValue = node.switchValue(); this._emit(this._indent + 'switch ('); this._emitExpression(switchValue, Skew.Precedence.LOWEST); this._emit(') {\n'); this._increaseIndent(); for (var child1 = switchValue.nextSibling(); child1 != null; child1 = child1.nextSibling()) { var block = child1.caseBlock(); if (child1.previousSibling() != switchValue) { this._emit('\n'); } if (child1.hasOneChild()) { this._emit(this._indent + 'default:'); } else { for (var value = child1.firstChild(); value != block; value = value.nextSibling()) { if (value.previousSibling() != null) { this._emit('\n'); } this._emit(this._indent + 'case '); this._emitExpression(value, Skew.Precedence.LOWEST); this._emit(':'); } } this._emit(' {\n'); this._increaseIndent(); this._emitStatements(block); if (block.hasControlFlowAtEnd()) { this._emit(this._indent + 'break;\n'); } this._decreaseIndent(); this._emit(this._indent + '}\n'); } this._decreaseIndent(); this._emit(this._indent + '}\n'); break; } case Skew.NodeKind.RETURN: { this._emit(this._indent + 'return'); var value1 = node.returnValue(); if (value1 != null) { this._emit(' '); this._emitExpression(value1, Skew.Precedence.LOWEST); } this._emit(';\n'); break; } case Skew.NodeKind.THROW: { this._emit(this._indent + 'throw '); this._emitExpression(node.throwValue(), Skew.Precedence.LOWEST); this._emit(';\n'); break; } case Skew.NodeKind.FOR: { var setup = node.forSetup(); var test = node.forTest(); var update = node.forUpdate(); this._emit(this._indent + 'for ('); if (!setup.isEmptySequence()) { if (setup.kind == Skew.NodeKind.VARIABLES) { this._emitType(setup.firstChild().symbol.asVariableSymbol().resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.DECLARATION); for (var child2 = setup.firstChild(); child2 != null; child2 = child2.nextSibling()) { var symbol = child2.symbol.asVariableSymbol(); assert(child2.kind == Skew.NodeKind.VARIABLE); if (child2.previousSibling() != null) { this._emit(', '); if (this._isReferenceType(symbol.resolvedType)) { this._emit('*'); } } this._emit(Skew.CPlusPlusEmitter._mangleName(symbol) + ' = '); this._emitExpression(symbol.value, Skew.Precedence.COMMA); } } else { this._emitExpression(setup, Skew.Precedence.LOWEST); } } this._emit('; '); if (!test.isEmptySequence()) { this._emitExpression(test, Skew.Precedence.LOWEST); } this._emit('; '); if (!update.isEmptySequence()) { this._emitExpression(update, Skew.Precedence.LOWEST); } this._emit(')'); this._emitBlock(node.forBlock()); this._emit('\n'); break; } case Skew.NodeKind.TRY: { var tryBlock = node.tryBlock(); var finallyBlock = node.finallyBlock(); this._emit(this._indent + 'try'); this._emitBlock(tryBlock); this._emit('\n'); for (var child3 = tryBlock.nextSibling(); child3 != finallyBlock; child3 = child3.nextSibling()) { if (child3.comments != null) { this._emit('\n'); this._emitComments(child3.comments); } this._emit(this._indent + 'catch'); if (child3.symbol != null) { this._emit(' ('); this._emitType(child3.symbol.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.DECLARATION); this._emit(Skew.CPlusPlusEmitter._mangleName(child3.symbol) + ')'); } else { this._emit(' (...)'); } this._emitBlock(child3.catchBlock()); this._emit('\n'); } if (finallyBlock != null) { if (finallyBlock.comments != null) { this._emit('\n'); this._emitComments(finallyBlock.comments); } this._emit(this._indent + 'finally'); this._emitBlock(finallyBlock); this._emit('\n'); } break; } case Skew.NodeKind.WHILE: { this._emit(this._indent + 'while ('); this._emitExpression(node.whileTest(), Skew.Precedence.LOWEST); this._emit(')'); this._emitBlock(node.whileBlock()); this._emit('\n'); break; } case Skew.NodeKind.FOREACH: { var symbol1 = node.symbol.asVariableSymbol(); var value2 = node.foreachValue(); this._emit(this._indent + 'for ('); this._emitType(symbol1.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.DECLARATION); this._emit(Skew.CPlusPlusEmitter._mangleName(symbol1) + ' : '); if (this._isReferenceType(value2.resolvedType)) { this._emit('*'); this._emitExpression(value2, Skew.Precedence.UNARY_PREFIX); } else { this._emitExpression(value2, Skew.Precedence.LOWEST); } this._emit(')'); this._emitBlock(node.foreachBlock()); this._emit('\n'); break; } default: { assert(false); break; } } if (Skew.in_NodeKind.isLoop(node.kind)) { var label1 = in_IntMap.get(this._loopLabels, node.id, null); if (label1 != null) { this._emit(this._indent + Skew.CPlusPlusEmitter._mangleName(label1) + (node.nextSibling() != null ? ':\n' : ':;\n')); } } }; Skew.CPlusPlusEmitter.prototype._emitIf = function(node) { this._emit('if ('); this._emitExpression(node.ifTest(), Skew.Precedence.LOWEST); this._emit(')'); this._emitBlock(node.ifTrue()); var block = node.ifFalse(); if (block != null) { var singleIf = block.hasOneChild() && block.firstChild().kind == Skew.NodeKind.IF ? block.firstChild() : null; this._emit('\n\n'); this._emitComments(block.comments); if (singleIf != null) { this._emitComments(singleIf.comments); } this._emit(this._indent + 'else'); if (singleIf != null) { this._emit(' '); this._emitIf(singleIf); } else { this._emitBlock(block); } } }; Skew.CPlusPlusEmitter.prototype._emitContent = function(content) { switch (content.kind()) { case Skew.ContentKind.BOOL: { this._emit(Skew.in_Content.asBool(content).toString()); break; } case Skew.ContentKind.INT: { this._emit(Skew.in_Content.asInt(content).toString()); break; } case Skew.ContentKind.DOUBLE: { var value = Skew.in_Content.asDouble(content); if (!isFinite(value)) { in_StringMap.set(this._includeNames, '', 0); } this._emit(isNaN(value) ? 'NAN' : value == 1 / 0 ? 'INFINITY' : value == -(1 / 0) ? '-INFINITY' : Skew.doubleToStringWithDot(value)); break; } case Skew.ContentKind.STRING: { var text = Skew.in_Content.asString(content); var quoted = Skew.quoteString(text, Skew.QuoteStyle.DOUBLE, Skew.QuoteOctal.OCTAL_WORKAROUND); // TODO: This needs to work with UTF8 this._emit(text.indexOf('\0') != -1 ? 'Skew::string(' + quoted + ', ' + text.length.toString() + ')' : quoted + '_s'); break; } } }; Skew.CPlusPlusEmitter.prototype._emitCommaSeparatedExpressions = function(from, to) { while (from != to) { this._emitExpression(from, Skew.Precedence.COMMA); from = from.nextSibling(); if (from != to) { this._emit(', '); } } }; Skew.CPlusPlusEmitter.prototype._emitExpression = function(node, precedence) { var kind = node.kind; var symbol = node.symbol; if (symbol != null) { this._handleSymbol(symbol); } switch (kind) { case Skew.NodeKind.TYPE: case Skew.NodeKind.LAMBDA_TYPE: { this._emitType(node.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.BARE); break; } case Skew.NodeKind.NULL: { this._emit('nullptr'); break; } case Skew.NodeKind.NAME: { this._emit(symbol != null ? Skew.CPlusPlusEmitter._fullName(symbol) : node.asString()); // Need to unwrap GC roots using ".get()" when global variables are referenced if (symbol != null && symbol.kind == Skew.SymbolKind.VARIABLE_GLOBAL && this._isReferenceType(symbol.resolvedType) && (node.parent() == null || node.parent().kind != Skew.NodeKind.DOT && (node.parent().kind != Skew.NodeKind.ASSIGN || node != node.parent().binaryLeft()))) { this._emit('.get()'); } break; } case Skew.NodeKind.DOT: { var target = node.dotTarget(); var type = target.resolvedType; this._emitExpression(target, Skew.Precedence.MEMBER); this._emit((type != null && this._isReferenceType(type) ? '->' : '.') + (symbol != null ? Skew.CPlusPlusEmitter._mangleName(symbol) : node.asString())); break; } case Skew.NodeKind.CONSTANT: { if (node.resolvedType.isEnumOrFlags()) { this._emit('('); this._emitType(node.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.NORMAL); this._emit(')'); } this._emitContent(node.content); break; } case Skew.NodeKind.CALL: { var value = node.callValue(); var wrap = false; if (value.kind == Skew.NodeKind.SUPER) { this._emit(Skew.CPlusPlusEmitter._fullName(symbol)); } else if (symbol != null && symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { wrap = precedence == Skew.Precedence.MEMBER; if (wrap) { this._emit('('); } this._emit('new '); this._emitType(node.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.BARE); } else if (value.kind == Skew.NodeKind.DOT && value.asString() == 'new') { wrap = precedence == Skew.Precedence.MEMBER; if (wrap) { this._emit('('); } this._emit('new '); this._emitExpression(value.dotTarget(), Skew.Precedence.MEMBER); } else { this._emitExpression(value, Skew.Precedence.UNARY_POSTFIX); } this._emit('('); this._emitCommaSeparatedExpressions(node.firstChild().nextSibling(), null); this._emit(')'); if (wrap) { this._emit(')'); } break; } case Skew.NodeKind.CAST: { var resolvedType = node.resolvedType; var type1 = node.castType(); var value1 = node.castValue(); if (value1.kind == Skew.NodeKind.NULL && node.resolvedType == this._cache.stringType) { this._emitType(this._cache.stringType, Skew.CPlusPlusEmitter.CppEmitMode.BARE); this._emit('()'); } else if (type1.kind == Skew.NodeKind.TYPE && type1.resolvedType == Skew.Type.DYNAMIC) { this._emitExpression(value1, precedence); } // Automatically promote integer literals to doubles instead of using a cast else if (this._cache.isEquivalentToDouble(resolvedType) && value1.isInt()) { this._emitExpression(this._cache.createDouble(value1.asInt()), precedence); } // Only emit a cast if the underlying types are different else if (this._unwrappedType(value1.resolvedType) != this._unwrappedType(type1.resolvedType) || type1.resolvedType == Skew.Type.DYNAMIC) { if (Skew.Precedence.UNARY_POSTFIX < precedence) { this._emit('('); } this._emit('('); this._emitExpressionOrType(type1, resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.NORMAL); this._emit(')'); this._emitExpression(value1, Skew.Precedence.UNARY_POSTFIX); if (Skew.Precedence.UNARY_POSTFIX < precedence) { this._emit(')'); } } // Otherwise, pretend the cast isn't there else { this._emitExpression(value1, precedence); } break; } case Skew.NodeKind.TYPE_CHECK: { var value2 = node.typeCheckValue(); var type2 = node.typeCheckType(); if (Skew.Precedence.EQUAL < precedence) { this._emit('('); } this._emit('dynamic_cast<'); this._emitExpressionOrType(type2, type2.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.NORMAL); this._emit('>('); this._emitExpression(value2, Skew.Precedence.LOWEST); this._emit(') != nullptr'); if (Skew.Precedence.EQUAL < precedence) { this._emit(')'); } break; } case Skew.NodeKind.INDEX: { var left = node.indexLeft(); if (this._isReferenceType(left.resolvedType)) { this._emit('(*'); this._emitExpression(left, Skew.Precedence.UNARY_PREFIX); this._emit(')'); } else { this._emitExpression(left, Skew.Precedence.UNARY_POSTFIX); } this._emit('['); this._emitExpression(node.indexRight(), Skew.Precedence.LOWEST); this._emit(']'); break; } case Skew.NodeKind.ASSIGN_INDEX: { var left1 = node.assignIndexLeft(); if (Skew.Precedence.ASSIGN < precedence) { this._emit('('); } if (this._isReferenceType(left1.resolvedType)) { this._emit('(*'); this._emitExpression(left1, Skew.Precedence.UNARY_PREFIX); this._emit(')'); } else { this._emitExpression(left1, Skew.Precedence.UNARY_POSTFIX); } this._emit('['); this._emitExpression(node.assignIndexCenter(), Skew.Precedence.LOWEST); this._emit('] = '); this._emitExpression(node.assignIndexRight(), Skew.Precedence.ASSIGN); if (Skew.Precedence.ASSIGN < precedence) { this._emit(')'); } break; } case Skew.NodeKind.PARAMETERIZE: { var value3 = node.parameterizeValue(); if (value3.isType()) { this._emitType(node.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.NORMAL); } else { this._emitExpression(value3, precedence); this._emit('<'); for (var child = value3.nextSibling(); child != null; child = child.nextSibling()) { if (child.previousSibling() != value3) { this._emit(', '); } this._emitExpressionOrType(child, child.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.NORMAL); } this._emit('>'); } break; } case Skew.NodeKind.SEQUENCE: { if (Skew.Precedence.COMMA <= precedence) { this._emit('('); } this._emitCommaSeparatedExpressions(node.firstChild(), null); if (Skew.Precedence.COMMA <= precedence) { this._emit(')'); } break; } case Skew.NodeKind.HOOK: { if (Skew.Precedence.ASSIGN < precedence) { this._emit('('); } this._emitExpression(node.hookTest(), Skew.Precedence.LOGICAL_OR); this._emit(' ? '); this._emitExpression(node.hookTrue(), Skew.Precedence.ASSIGN); this._emit(' : '); this._emitExpression(node.hookFalse(), Skew.Precedence.ASSIGN); if (Skew.Precedence.ASSIGN < precedence) { this._emit(')'); } break; } case Skew.NodeKind.INITIALIZER_LIST: case Skew.NodeKind.INITIALIZER_MAP: { var wrap1 = precedence == Skew.Precedence.MEMBER; if (wrap1) { this._emit('('); } this._emit('new '); this._emitType(node.resolvedType, Skew.CPlusPlusEmitter.CppEmitMode.BARE); if (node.hasChildren()) { this._emit('({'); this._emitCommaSeparatedExpressions(node.firstChild(), null); this._emit('})'); } else { this._emit('()'); } if (wrap1) { this._emit(')'); } break; } case Skew.NodeKind.PAIR: { in_StringMap.set(this._includeNames, '', 0); this._emit('std::make_pair('); this._emitCommaSeparatedExpressions(node.firstChild(), null); this._emit(')'); break; } case Skew.NodeKind.COMPLEMENT: case Skew.NodeKind.NEGATIVE: case Skew.NodeKind.NOT: case Skew.NodeKind.POSITIVE: case Skew.NodeKind.POSTFIX_DECREMENT: case Skew.NodeKind.POSTFIX_INCREMENT: case Skew.NodeKind.PREFIX_DECREMENT: case Skew.NodeKind.PREFIX_INCREMENT: { var value4 = node.unaryValue(); var info = in_IntMap.get1(Skew.operatorInfo, kind); var sign = node.sign(); if (info.precedence < precedence) { this._emit('('); } if (!Skew.in_NodeKind.isUnaryPostfix(kind)) { this._emit(info.text); // Prevent "x - -1" from becoming "x--1" if (sign != Skew.NodeKind.NULL && sign == value4.sign()) { this._emit(' '); } } this._emitExpression(value4, info.precedence); if (Skew.in_NodeKind.isUnaryPostfix(kind)) { this._emit(info.text); } if (info.precedence < precedence) { this._emit(')'); } break; } default: { if (Skew.in_NodeKind.isBinary(kind)) { var parent = node.parent(); if (parent != null && // Clang warns about "&&" inside "||" or "&" inside "|" without parentheses (parent.kind == Skew.NodeKind.LOGICAL_OR && kind == Skew.NodeKind.LOGICAL_AND || parent.kind == Skew.NodeKind.BITWISE_OR && kind == Skew.NodeKind.BITWISE_AND || // Clang and GCC also warn about add/subtract inside bitwise operations and shifts without parentheses (parent.kind == Skew.NodeKind.BITWISE_AND || parent.kind == Skew.NodeKind.BITWISE_OR || parent.kind == Skew.NodeKind.BITWISE_XOR || Skew.in_NodeKind.isShift(parent.kind)) && (kind == Skew.NodeKind.ADD || kind == Skew.NodeKind.SUBTRACT))) { precedence = Skew.Precedence.MEMBER; } var info1 = in_IntMap.get1(Skew.operatorInfo, kind); if (info1.precedence < precedence) { this._emit('('); } this._emitExpression(node.binaryLeft(), info1.precedence + (info1.associativity == Skew.Associativity.RIGHT | 0) | 0); this._emit(' ' + info1.text + ' '); this._emitExpression(node.binaryRight(), info1.precedence + (info1.associativity == Skew.Associativity.LEFT | 0) | 0); if (info1.precedence < precedence) { this._emit(')'); } } else { assert(false); } break; } } }; Skew.CPlusPlusEmitter.prototype._emitExpressionOrType = function(node, type, mode) { if (node != null && (type == null || type == Skew.Type.DYNAMIC)) { this._emitExpression(node, Skew.Precedence.LOWEST); if (mode == Skew.CPlusPlusEmitter.CppEmitMode.DECLARATION) { this._emit(' '); } } else { this._emitType(type, mode); } }; Skew.CPlusPlusEmitter.prototype._emitType = function(type, mode) { if (type == null) { this._emit('void'); } else { type = this._unwrappedType(type); if (type == Skew.Type.DYNAMIC) { this._emit('void'); } else if (type.kind == Skew.TypeKind.LAMBDA) { var hasReturnType = type.returnType != null; var argumentCount = type.argumentTypes.length; this._emit((hasReturnType ? 'Skew::Fn' : 'Skew::FnVoid') + argumentCount.toString()); if (hasReturnType || argumentCount != 0) { this._emit('<'); if (hasReturnType) { this._emitType(type.returnType, Skew.CPlusPlusEmitter.CppEmitMode.NORMAL); } for (var i = 0, count = argumentCount; i < count; i = i + 1 | 0) { if (i != 0 || hasReturnType) { this._emit(', '); } this._emitType(in_List.get(type.argumentTypes, i), Skew.CPlusPlusEmitter.CppEmitMode.NORMAL); } this._emit('>'); } } else { assert(type.kind == Skew.TypeKind.SYMBOL); this._handleSymbol(type.symbol); this._emit(Skew.CPlusPlusEmitter._fullName(type.symbol)); if (type.isParameterized()) { this._emit('<'); for (var i1 = 0, count1 = type.substitutions.length; i1 < count1; i1 = i1 + 1 | 0) { if (i1 != 0) { this._emit(', '); } this._emitType(in_List.get(type.substitutions, i1), Skew.CPlusPlusEmitter.CppEmitMode.NORMAL); } this._emit('>'); } } } if (type != null && this._isReferenceType(type) && mode != Skew.CPlusPlusEmitter.CppEmitMode.BARE) { this._emit(' *'); } else if (mode == Skew.CPlusPlusEmitter.CppEmitMode.DECLARATION) { this._emit(' '); } }; Skew.CPlusPlusEmitter.prototype._unwrappedType = function(type) { return type.isFlags() ? this._cache.intType : this._cache.unwrappedType(type); }; Skew.CPlusPlusEmitter.prototype._isReferenceType = function(type) { return type.isReference() && type != this._cache.stringType; }; Skew.CPlusPlusEmitter.prototype._finalizeEmittedFile = function() { var includes = Array.from(this._includeNames.keys()); if (!(includes.length == 0)) { // Sort so the order is deterministic includes.sort(Skew.SORT_STRINGS); for (var i = 0, list = includes, count = list.length; i < count; i = i + 1 | 0) { var include = in_List.get(list, i); this._emitPrefix('#include ' + (include.startsWith('<') && include.endsWith('>') ? include : '"' + include + '"') + '\n'); } this._emitPrefix('\n'); } this._adjustNamespace(null); this._previousSymbol = null; this._symbolsCheckedForInclude = new Map(); this._includeNames = new Map(); }; Skew.CPlusPlusEmitter.prototype._handleSymbol = function(symbol) { if (!Skew.in_SymbolKind.isLocal(symbol.kind) && !this._symbolsCheckedForInclude.has(symbol.id)) { in_IntMap.set(this._symbolsCheckedForInclude, symbol.id, 0); if (symbol.annotations != null) { for (var i = 0, list = symbol.annotations, count = list.length; i < count; i = i + 1 | 0) { var annotation = in_List.get(list, i); if (annotation.symbol != null && annotation.symbol.fullName() == 'include') { var value = annotation.annotationValue(); if (value.childCount() == 2) { in_StringMap.set(this._includeNames, value.lastChild().asString(), 0); } } } } if (symbol.parent != null) { this._handleSymbol(symbol.parent); } } }; Skew.CPlusPlusEmitter._isCompactNodeKind = function(kind) { return kind == Skew.NodeKind.EXPRESSION || kind == Skew.NodeKind.VARIABLES || Skew.in_NodeKind.isJump(kind); }; Skew.CPlusPlusEmitter._fullName = function(symbol) { var parent = symbol.parent; if (parent != null && parent.kind != Skew.SymbolKind.OBJECT_GLOBAL && !Skew.in_SymbolKind.isParameter(symbol.kind)) { return Skew.CPlusPlusEmitter._fullName(parent) + '::' + Skew.CPlusPlusEmitter._mangleName(symbol); } return Skew.CPlusPlusEmitter._mangleName(symbol); }; Skew.CPlusPlusEmitter._mangleName = function(symbol) { symbol = symbol.forwarded(); if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { return Skew.CPlusPlusEmitter._mangleName(symbol.parent); } if (!symbol.isImportedOrExported() && Skew.CPlusPlusEmitter._isNative.has(symbol.name) || Skew.CPlusPlusEmitter._isKeyword.has(symbol.name)) { return '_' + symbol.name; } return symbol.name; }; Skew.CPlusPlusEmitter.CodeMode = { DECLARE: 0, DEFINE: 1, IMPLEMENT: 2 }; Skew.CPlusPlusEmitter.CppEmitMode = { BARE: 0, NORMAL: 1, DECLARATION: 2 }; Skew.SourceMapping = function(sourceIndex, originalLine, originalColumn, generatedLine, generatedColumn) { this.sourceIndex = sourceIndex; this.originalLine = originalLine; this.originalColumn = originalColumn; this.generatedLine = generatedLine; this.generatedColumn = generatedColumn; }; // Based on https://github.com/mozilla/source-map Skew.SourceMapGenerator = function() { this._mappings = []; this._sources = []; }; Skew.SourceMapGenerator.prototype.addMapping = function(source, originalLine, originalColumn, generatedLine, generatedColumn) { var sourceIndex = this._sources.indexOf(source); if (sourceIndex == -1) { sourceIndex = this._sources.length; this._sources.push(source); } this._mappings.push(new Skew.SourceMapping(sourceIndex, originalLine, originalColumn, generatedLine, generatedColumn)); }; Skew.SourceMapGenerator.prototype.toString = function() { var sourceNames = []; var sourceContents = []; for (var i = 0, list = this._sources, count = list.length; i < count; i = i + 1 | 0) { var source = in_List.get(list, i); sourceNames.push(Skew.quoteString(source.name, Skew.QuoteStyle.DOUBLE, Skew.QuoteOctal.OCTAL_WORKAROUND)); sourceContents.push(Skew.quoteString(source.contents, Skew.QuoteStyle.DOUBLE, Skew.QuoteOctal.OCTAL_WORKAROUND)); } var builder = new StringBuilder(); builder.buffer += '{"version":3,"sources":['; builder.buffer += sourceNames.join(','); builder.buffer += '],"sourcesContent":['; builder.buffer += sourceContents.join(','); builder.buffer += '],"names":[],"mappings":"'; // Sort the mappings in increasing order by generated location this._mappings.sort(function(a, b) { var delta = in_int.compare(a.generatedLine, b.generatedLine); return delta != 0 ? delta : in_int.compare(a.generatedColumn, b.generatedColumn); }); var previousGeneratedColumn = 0; var previousGeneratedLine = 0; var previousOriginalColumn = 0; var previousOriginalLine = 0; var previousSourceIndex = 0; // Generate the base64 VLQ encoded mappings for (var i1 = 0, list1 = this._mappings, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var mapping = in_List.get(list1, i1); var generatedLine = mapping.generatedLine; // Insert ',' for the same line and ';' for a line if (previousGeneratedLine == generatedLine) { if (previousGeneratedColumn == mapping.generatedColumn && (previousGeneratedLine != 0 || previousGeneratedColumn != 0)) { continue; } builder.buffer += ','; } else { previousGeneratedColumn = 0; while (previousGeneratedLine < generatedLine) { builder.buffer += ';'; previousGeneratedLine = previousGeneratedLine + 1 | 0; } } // Record the generated column (the line is recorded using ';' above) builder.buffer += Skew.encodeVLQ(mapping.generatedColumn - previousGeneratedColumn | 0); previousGeneratedColumn = mapping.generatedColumn; // Record the generated source builder.buffer += Skew.encodeVLQ(mapping.sourceIndex - previousSourceIndex | 0); previousSourceIndex = mapping.sourceIndex; // Record the original line builder.buffer += Skew.encodeVLQ(mapping.originalLine - previousOriginalLine | 0); previousOriginalLine = mapping.originalLine; // Record the original column builder.buffer += Skew.encodeVLQ(mapping.originalColumn - previousOriginalColumn | 0); previousOriginalColumn = mapping.originalColumn; } builder.buffer += '"}\n'; return builder.buffer; }; Skew.UnionFind = function() { this.parents = []; }; Skew.UnionFind.prototype.allocate1 = function() { var index = this.parents.length; this.parents.push(index); return index; }; Skew.UnionFind.prototype.allocate2 = function(count) { for (var i = 0, count1 = count; i < count1; i = i + 1 | 0) { this.parents.push(this.parents.length); } return this; }; Skew.UnionFind.prototype.union = function(left, right) { in_List.set(this.parents, this.find(left), this.find(right)); }; Skew.UnionFind.prototype.find = function(index) { assert(index >= 0 && index < this.parents.length); var parent = in_List.get(this.parents, index); if (parent != index) { parent = this.find(parent); in_List.set(this.parents, index, parent); } return parent; }; Skew.SplitPath = function(directory, entry) { this.directory = directory; this.entry = entry; }; Skew.PrettyPrint = {}; Skew.PrettyPrint.plural1 = function(value, word) { return value.toString() + ' ' + word + (value == 1 ? '' : 's'); }; Skew.PrettyPrint.joinQuoted = function(parts, trailing) { return Skew.PrettyPrint.join(parts.map(function(part) { return '"' + part + '"'; }), trailing); }; Skew.PrettyPrint.join = function(parts, trailing) { if (parts.length < 3) { return parts.join(' ' + trailing + ' '); } var text = ''; for (var i = 0, count = parts.length; i < count; i = i + 1 | 0) { if (i != 0) { text += ', '; if ((i + 1 | 0) == parts.length) { text += trailing + ' '; } } text += in_List.get(parts, i); } return text; }; Skew.PrettyPrint.wrapWords = function(text, width) { // An invalid length means wrapping is disabled if (width < 1) { return [text]; } var words = text.split(' '); var lines = []; var line = ''; // Run the word wrapping algorithm var i = 0; while (i < words.length) { var word = in_List.get(words, i); var lineLength = line.length; var wordLength = word.length; var estimatedLength = (lineLength + 1 | 0) + wordLength | 0; i = i + 1 | 0; // Collapse adjacent spaces if (word == '') { continue; } // Start the line if (line == '') { while (word.length > width) { lines.push(in_string.slice2(word, 0, width)); word = in_string.slice2(word, width, word.length); } line = word; } // Continue line else if (estimatedLength < width) { line += ' ' + word; } // Continue and wrap else if (estimatedLength == width) { lines.push(line + ' ' + word); line = ''; } // Wrap and try again else { lines.push(line); line = ''; i = i - 1 | 0; } } // Don't add an empty trailing line unless there are no other lines if (line != '' || lines.length == 0) { lines.push(line); } return lines; }; Skew.SymbolKind = { PARAMETER_FUNCTION: 0, PARAMETER_OBJECT: 1, OBJECT_CLASS: 2, OBJECT_ENUM: 3, OBJECT_FLAGS: 4, OBJECT_GLOBAL: 5, OBJECT_INTERFACE: 6, OBJECT_NAMESPACE: 7, OBJECT_WRAPPED: 8, FUNCTION_ANNOTATION: 9, FUNCTION_CONSTRUCTOR: 10, FUNCTION_GLOBAL: 11, FUNCTION_INSTANCE: 12, FUNCTION_LOCAL: 13, OVERLOADED_ANNOTATION: 14, OVERLOADED_GLOBAL: 15, OVERLOADED_INSTANCE: 16, VARIABLE_ARGUMENT: 17, VARIABLE_ENUM_OR_FLAGS: 18, VARIABLE_GLOBAL: 19, VARIABLE_INSTANCE: 20, VARIABLE_LOCAL: 21 }; Skew.SymbolState = { UNINITIALIZED: 0, INITIALIZING: 1, INITIALIZED: 2 }; Skew.SymbolFlags = { // Internal IS_AUTOMATICALLY_GENERATED: 1, IS_CONST: 2, IS_GETTER: 4, IS_LOOP_VARIABLE: 8, IS_OVER: 16, IS_SETTER: 32, IS_VALUE_TYPE: 64, SHOULD_INFER_RETURN_TYPE: 128, // Modifiers IS_DEPRECATED: 256, IS_ENTRY_POINT: 512, IS_EXPORTED: 1024, IS_IMPORTED: 2048, IS_INLINING_FORCED: 4096, IS_INLINING_PREVENTED: 8192, IS_PREFERRED: 16384, IS_PROTECTED: 32768, IS_RENAMED: 65536, IS_SKIPPED: 131072, SHOULD_SPREAD: 262144, // Pass-specific IS_CSHARP_CONST: 524288, IS_DYNAMIC_LAMBDA: 1048576, IS_GUARD_CONDITIONAL: 2097152, IS_OBSOLETE: 4194304, IS_PRIMARY_CONSTRUCTOR: 8388608, IS_VIRTUAL: 16777216, USE_PROTOTYPE_CACHE: 33554432 }; Skew.Symbol = function(kind, name) { this.id = Skew.Symbol._nextID = Skew.Symbol._nextID + 1 | 0; this.kind = kind; this.name = name; this.rename = null; this.range = null; this.parent = null; this.resolvedType = null; this.scope = null; this.state = Skew.SymbolState.UNINITIALIZED; this.annotations = null; this.comments = null; this.forwardTo = null; this.flags = 0; this.nextMergedSymbol = null; }; Skew.Symbol.prototype._cloneFrom = function(symbol) { this.rename = symbol.rename; this.range = symbol.range; this.scope = symbol.scope; this.state = symbol.state; this.flags = symbol.flags; }; // Flags Skew.Symbol.prototype.isAutomaticallyGenerated = function() { return (Skew.SymbolFlags.IS_AUTOMATICALLY_GENERATED & this.flags) != 0; }; Skew.Symbol.prototype.isConst = function() { return (Skew.SymbolFlags.IS_CONST & this.flags) != 0; }; Skew.Symbol.prototype.isGetter = function() { return (Skew.SymbolFlags.IS_GETTER & this.flags) != 0; }; Skew.Symbol.prototype.isLoopVariable = function() { return (Skew.SymbolFlags.IS_LOOP_VARIABLE & this.flags) != 0; }; Skew.Symbol.prototype.isOver = function() { return (Skew.SymbolFlags.IS_OVER & this.flags) != 0; }; Skew.Symbol.prototype.isSetter = function() { return (Skew.SymbolFlags.IS_SETTER & this.flags) != 0; }; Skew.Symbol.prototype.isValueType = function() { return (Skew.SymbolFlags.IS_VALUE_TYPE & this.flags) != 0; }; Skew.Symbol.prototype.shouldInferReturnType = function() { return (Skew.SymbolFlags.SHOULD_INFER_RETURN_TYPE & this.flags) != 0; }; // Modifiers Skew.Symbol.prototype.isDeprecated = function() { return (Skew.SymbolFlags.IS_DEPRECATED & this.flags) != 0; }; Skew.Symbol.prototype.isEntryPoint = function() { return (Skew.SymbolFlags.IS_ENTRY_POINT & this.flags) != 0; }; Skew.Symbol.prototype.isExported = function() { return (Skew.SymbolFlags.IS_EXPORTED & this.flags) != 0; }; Skew.Symbol.prototype.isImported = function() { return (Skew.SymbolFlags.IS_IMPORTED & this.flags) != 0; }; Skew.Symbol.prototype.isInliningForced = function() { return (Skew.SymbolFlags.IS_INLINING_FORCED & this.flags) != 0; }; Skew.Symbol.prototype.isInliningPrevented = function() { return (Skew.SymbolFlags.IS_INLINING_PREVENTED & this.flags) != 0; }; Skew.Symbol.prototype.isPreferred = function() { return (Skew.SymbolFlags.IS_PREFERRED & this.flags) != 0; }; Skew.Symbol.prototype.isProtected = function() { return (Skew.SymbolFlags.IS_PROTECTED & this.flags) != 0; }; Skew.Symbol.prototype.isRenamed = function() { return (Skew.SymbolFlags.IS_RENAMED & this.flags) != 0; }; Skew.Symbol.prototype.isSkipped = function() { return (Skew.SymbolFlags.IS_SKIPPED & this.flags) != 0; }; Skew.Symbol.prototype.shouldSpread = function() { return (Skew.SymbolFlags.SHOULD_SPREAD & this.flags) != 0; }; // Pass-specific flags Skew.Symbol.prototype.isCSharpConst = function() { return (Skew.SymbolFlags.IS_CSHARP_CONST & this.flags) != 0; }; Skew.Symbol.prototype.isDynamicLambda = function() { return (Skew.SymbolFlags.IS_DYNAMIC_LAMBDA & this.flags) != 0; }; Skew.Symbol.prototype.isGuardConditional = function() { return (Skew.SymbolFlags.IS_GUARD_CONDITIONAL & this.flags) != 0; }; Skew.Symbol.prototype.isObsolete = function() { return (Skew.SymbolFlags.IS_OBSOLETE & this.flags) != 0; }; Skew.Symbol.prototype.isPrimaryConstructor = function() { return (Skew.SymbolFlags.IS_PRIMARY_CONSTRUCTOR & this.flags) != 0; }; Skew.Symbol.prototype.isVirtual = function() { return (Skew.SymbolFlags.IS_VIRTUAL & this.flags) != 0; }; Skew.Symbol.prototype.usePrototypeCache = function() { return (Skew.SymbolFlags.USE_PROTOTYPE_CACHE & this.flags) != 0; }; // Combinations Skew.Symbol.prototype.isImportedOrExported = function() { return ((Skew.SymbolFlags.IS_IMPORTED | Skew.SymbolFlags.IS_EXPORTED) & this.flags) != 0; }; Skew.Symbol.prototype.asParameterSymbol = function() { assert(Skew.in_SymbolKind.isParameter(this.kind)); return this; }; Skew.Symbol.prototype.asObjectSymbol = function() { assert(Skew.in_SymbolKind.isObject(this.kind)); return this; }; Skew.Symbol.prototype.asFunctionSymbol = function() { assert(Skew.in_SymbolKind.isFunction(this.kind)); return this; }; Skew.Symbol.prototype.asOverloadedFunctionSymbol = function() { assert(Skew.in_SymbolKind.isOverloadedFunction(this.kind)); return this; }; Skew.Symbol.prototype.asVariableSymbol = function() { assert(Skew.in_SymbolKind.isVariable(this.kind)); return this; }; Skew.Symbol.prototype.fullName = function() { if (this.parent != null && this.parent.kind != Skew.SymbolKind.OBJECT_GLOBAL && !Skew.in_SymbolKind.isParameter(this.kind)) { return this.parent.fullName() + '.' + this.name; } return this.name; }; Skew.Symbol.prototype.forwarded = function() { var symbol = this; while (symbol.forwardTo != null) { symbol = symbol.forwardTo; } return symbol; }; Skew.Symbol.prototype.spreadingAnnotations = function() { var result = null; if (this.annotations != null) { for (var i = 0, list = this.annotations, count = list.length; i < count; i = i + 1 | 0) { var annotation = in_List.get(list, i); if (annotation.symbol != null && annotation.symbol.shouldSpread()) { if (result == null) { result = []; } result.push(annotation); } } } return result; }; Skew.Symbol.prototype.mergeInformationFrom = function(symbol) { // Link merged symbols together var link = this; while (link.nextMergedSymbol != null) { link = link.nextMergedSymbol; } link.nextMergedSymbol = symbol; // Combine annotations if (this.annotations == null) { this.annotations = symbol.annotations; } else if (symbol.annotations != null) { in_List.append1(this.annotations, symbol.annotations); } // Combine comments if (this.comments == null) { this.comments = symbol.comments; } else if (symbol.comments != null) { in_List.append1(this.comments, symbol.comments); } if (this.rename == null) { this.rename = symbol.rename; } }; Skew.Symbol._substituteSymbols = function(node, symbols) { if (node.symbol != null) { node.symbol = in_IntMap.get(symbols, node.symbol.id, node.symbol); } for (var child = node.firstChild(); child != null; child = child.nextSibling()) { Skew.Symbol._substituteSymbols(child, symbols); } }; Skew.Symbol.SORT_BY_ID = function(a, b) { return in_int.compare(a.id, b.id); }; Skew.Symbol.SORT_OBJECTS_BY_ID = function(a, b) { return in_int.compare(a.id, b.id); }; Skew.Symbol.SORT_VARIABLES_BY_ID = function(a, b) { return in_int.compare(a.id, b.id); }; Skew.ParameterSymbol = function(kind, name) { Skew.Symbol.call(this, kind, name); }; __extends(Skew.ParameterSymbol, Skew.Symbol); Skew.ParameterSymbol.prototype.clone = function() { var clone = new Skew.ParameterSymbol(this.kind, this.name); clone._cloneFrom(this); clone.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, clone); return clone; }; Skew.Guard = function(parent, test, contents, elseGuard) { this.parent = parent; this.test = test; this.contents = contents; this.elseGuard = elseGuard; }; Skew.ObjectSymbol = function(kind, name) { Skew.Symbol.call(this, kind, name); this.$extends = null; this.$implements = null; this.baseType = null; this.baseClass = null; this.interfaceTypes = null; this.wrappedType = null; this.members = new Map(); this.objects = []; this.functions = []; this.variables = []; this.parameters = null; this.guards = null; this.hasCheckedInterfacesAndAbstractStatus = false; this.isAbstractBecauseOf = null; this.commentsInsideEndOfBlock = null; }; __extends(Skew.ObjectSymbol, Skew.Symbol); Skew.ObjectSymbol.prototype.isAbstract = function() { return this.isAbstractBecauseOf != null; }; Skew.ObjectSymbol.prototype.hasBaseClass = function(symbol) { return this.baseClass != null && (this.baseClass == symbol || this.baseClass.hasBaseClass(symbol)); }; Skew.ObjectSymbol.prototype.hasInterface = function(symbol) { return this.interfaceTypes != null && this.interfaceTypes.some(function(type) { return type.symbol == symbol; }); }; Skew.ObjectSymbol.prototype.isSameOrHasBaseClass = function(symbol) { return this == symbol || this.hasBaseClass(symbol); }; Skew.FunctionSymbol = function(kind, name) { Skew.Symbol.call(this, kind, name); this.overridden = null; this.overloaded = null; this.implementations = null; this.parameters = null; this.$arguments = []; this.$this = null; this.argumentOnlyType = null; this.returnType = null; this.block = null; this.namingGroup = -1; this.inlinedCount = 0; }; __extends(Skew.FunctionSymbol, Skew.Symbol); Skew.FunctionSymbol.prototype.clone = function() { var clone = new Skew.FunctionSymbol(this.kind, this.name); var symbols = new Map(); clone._cloneFrom(this); if (this.state == Skew.SymbolState.INITIALIZED) { clone.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, clone); clone.resolvedType.returnType = this.resolvedType.returnType; clone.resolvedType.argumentTypes = this.resolvedType.argumentTypes.slice(); clone.argumentOnlyType = this.argumentOnlyType; } if (this.parameters != null) { clone.parameters = []; for (var i = 0, list = this.parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); var cloned = parameter.clone(); in_IntMap.set(symbols, parameter.id, cloned); clone.parameters.push(cloned); } } for (var i1 = 0, list1 = this.$arguments, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var argument = in_List.get(list1, i1); var cloned1 = argument.clone(); in_IntMap.set(symbols, argument.id, cloned1); clone.$arguments.push(cloned1); } if (this.returnType != null) { clone.returnType = this.returnType.clone(); } if (this.block != null) { clone.block = this.block.clone(); Skew.Symbol._substituteSymbols(clone.block, symbols); } return clone; }; Skew.VariableSymbol = function(kind, name) { Skew.Symbol.call(this, kind, name); this.type = null; this.value = null; }; __extends(Skew.VariableSymbol, Skew.Symbol); Skew.VariableSymbol.prototype.clone = function() { var clone = new Skew.VariableSymbol(this.kind, this.name); clone._cloneFrom(this); clone.resolvedType = this.resolvedType; if (this.type != null) { clone.type = this.type.clone(); } if (this.value != null) { clone.value = this.value.clone(); Skew.Symbol._substituteSymbols(clone.value, in_IntMap.insert(new Map(), this.id, clone)); } return clone; }; Skew.VariableSymbol.prototype.initializeWithType = function(target) { assert(this.state == Skew.SymbolState.UNINITIALIZED); assert(this.type == null); assert(this.resolvedType == null); this.state = Skew.SymbolState.INITIALIZED; this.resolvedType = target; this.type = new Skew.Node(Skew.NodeKind.TYPE).withType(target); }; Skew.OverloadedFunctionSymbol = function(kind, name, symbols) { Skew.Symbol.call(this, kind, name); this.symbols = symbols; }; __extends(Skew.OverloadedFunctionSymbol, Skew.Symbol); Skew.FuzzySymbolKind = { EVERYTHING: 0, TYPE_ONLY: 1, GLOBAL_ONLY: 2, INSTANCE_ONLY: 3 }; Skew.FuzzySymbolMatcher = function(name, kind) { this._name = null; this._kind = 0; this._bestScore = 0; this._bestMatch = null; this._name = name; this._kind = kind; this._bestScore = name.length * 0.5; this._bestMatch = null; }; Skew.FuzzySymbolMatcher.prototype._isBetterScore = function(score, match) { if (score < this._bestScore) { return true; } // Do tie-breaking using a consistent ordering so that language targets // with unordered maps (C++ for example) can iterate over symbols in an // unspecified order for speed and still deterministically arrive at the // same result. if (score == this._bestScore && (this._bestMatch == null || match.id < this._bestMatch.id)) { return true; } return false; }; Skew.FuzzySymbolMatcher.prototype.include = function(match) { if (this._kind == Skew.FuzzySymbolKind.INSTANCE_ONLY && !Skew.in_SymbolKind.isOnInstances(match.kind) || this._kind == Skew.FuzzySymbolKind.GLOBAL_ONLY && Skew.in_SymbolKind.isOnInstances(match.kind) || this._kind == Skew.FuzzySymbolKind.TYPE_ONLY && !Skew.in_SymbolKind.isType(match.kind) || match.state == Skew.SymbolState.INITIALIZING) { return; } var score = Skew.caseAwareLevenshteinEditDistance(this._name, match.name); if (score <= match.name.length * 0.5 && this._isBetterScore(score, match)) { this._bestScore = score; this._bestMatch = match; } }; Skew.FuzzySymbolMatcher.prototype.bestSoFar = function() { return this._bestMatch; }; Skew.ContentKind = { BOOL: 0, INT: 1, DOUBLE: 2, STRING: 3 }; Skew.BoolContent = function(value) { this.value = value; }; Skew.BoolContent.prototype.kind = function() { return Skew.ContentKind.BOOL; }; Skew.IntContent = function(value) { this.value = value; }; Skew.IntContent.prototype.kind = function() { return Skew.ContentKind.INT; }; Skew.DoubleContent = function(value) { this.value = value; }; Skew.DoubleContent.prototype.kind = function() { return Skew.ContentKind.DOUBLE; }; Skew.StringContent = function(value) { this.value = value; }; Skew.StringContent.prototype.kind = function() { return Skew.ContentKind.STRING; }; Skew.OperatorInfo = function(text, precedence, associativity, kind, validArgumentCounts, assignKind) { this.text = text; this.precedence = precedence; this.associativity = associativity; this.kind = kind; this.validArgumentCounts = validArgumentCounts; this.assignKind = assignKind; }; Skew.OperatorKind = { FIXED: 0, OVERRIDABLE: 1 }; Skew.NodeKind = { // Other ANNOTATION: 0, BLOCK: 1, CASE: 2, CATCH: 3, VARIABLE: 4, // Statements BREAK: 5, COMMENT_BLOCK: 6, CONTINUE: 7, EXPRESSION: 8, FOR: 9, FOREACH: 10, IF: 11, RETURN: 12, SWITCH: 13, THROW: 14, TRY: 15, VARIABLES: 16, WHILE: 17, // Expressions ASSIGN_INDEX: 18, CALL: 19, CAST: 20, CONSTANT: 21, DOT: 22, HOOK: 23, INDEX: 24, INITIALIZER_LIST: 25, INITIALIZER_MAP: 26, LAMBDA: 27, LAMBDA_TYPE: 28, NAME: 29, NULL: 30, NULL_DOT: 31, PAIR: 32, PARAMETERIZE: 33, PARSE_ERROR: 34, SEQUENCE: 35, STRING_INTERPOLATION: 36, SUPER: 37, TYPE: 38, TYPE_CHECK: 39, XML: 40, // Unary operators COMPLEMENT: 41, NEGATIVE: 42, NOT: 43, POSITIVE: 44, POSTFIX_DECREMENT: 45, POSTFIX_INCREMENT: 46, PREFIX_DECREMENT: 47, PREFIX_INCREMENT: 48, // Binary operators ADD: 49, BITWISE_AND: 50, BITWISE_OR: 51, BITWISE_XOR: 52, COMPARE: 53, DIVIDE: 54, EQUAL: 55, IN: 56, LOGICAL_AND: 57, LOGICAL_OR: 58, MODULUS: 59, MULTIPLY: 60, NOT_EQUAL: 61, NULL_JOIN: 62, POWER: 63, REMAINDER: 64, SHIFT_LEFT: 65, SHIFT_RIGHT: 66, SUBTRACT: 67, UNSIGNED_SHIFT_RIGHT: 68, // Binary comparison operators GREATER_THAN: 69, GREATER_THAN_OR_EQUAL: 70, LESS_THAN: 71, LESS_THAN_OR_EQUAL: 72, // Binary assigment operators ASSIGN: 73, ASSIGN_ADD: 74, ASSIGN_BITWISE_AND: 75, ASSIGN_BITWISE_OR: 76, ASSIGN_BITWISE_XOR: 77, ASSIGN_DIVIDE: 78, ASSIGN_MODULUS: 79, ASSIGN_MULTIPLY: 80, ASSIGN_NULL: 81, ASSIGN_POWER: 82, ASSIGN_REMAINDER: 83, ASSIGN_SHIFT_LEFT: 84, ASSIGN_SHIFT_RIGHT: 85, ASSIGN_SUBTRACT: 86, ASSIGN_UNSIGNED_SHIFT_RIGHT: 87 }; Skew.NodeFlags = { // This flag is only for blocks. A simple control flow analysis is run // during code resolution and blocks where control flow reaches the end of // the block have this flag set. HAS_CONTROL_FLOW_AT_END: 1, // Use this flag to tell the IDE support code to ignore this node. This is // useful for compiler-generated nodes that are used for lowering and that // need marked ranges for error reporting but that should not show up in // tooltips. IS_IGNORED_BY_IDE: 2, // An implicit return is a return statement inside an expression lambda. For // example, the lambda "x => x" is compiled into "x => { return x }" where // the return statement has this flag set. IS_IMPLICIT_RETURN: 4, // This flag marks list nodes that help implement initializer expressions. IS_INITIALIZER_EXPANSION: 8, // This flag marks nodes that were wrapped in parentheses in the original // source code. It's used for warnings about C-style syntax in conditional // statements and to call a lambda returned from a getter. IS_INSIDE_PARENTHESES: 16, // This flag is set on nodes that are expected to be types. SHOULD_EXPECT_TYPE: 32, // This flag marks nodes that were converted from ASSIGN_NULL to ASSIGN nodes. WAS_ASSIGN_NULL: 64, // This flag marks nodes that were converted from NULL_JOIN to HOOK nodes. WAS_NULL_JOIN: 128 }; // Nodes represent executable code (variable initializers and function bodies) // Node-specific queries // Factory functions // Getters, most of which should be inlineable when asserts are skipped in release Skew.Node = function(kind) { this.id = Skew.Node._nextID = Skew.Node._nextID + 1 | 0; this.kind = kind; this.flags = 0; this.range = null; this.internalRange = null; this.symbol = null; this.content = null; this.resolvedType = null; this.comments = null; this.innerComments = null; this._parent = null; this._firstChild = null; this._lastChild = null; this._previousSibling = null; this._nextSibling = null; }; Skew.Node.prototype._cloneWithoutChildren = function() { var ref1; var ref; var clone = new Skew.Node(this.kind); clone.flags = this.flags; clone.range = this.range; clone.internalRange = this.internalRange; clone.symbol = this.symbol; clone.content = this.content; clone.resolvedType = this.resolvedType; clone.comments = (ref = this.comments) != null ? ref.slice() : null; clone.innerComments = (ref1 = this.innerComments) != null ? ref1.slice() : null; return clone; }; // When used with become(), this provides a convenient way to wrap a node in // an operation without the caller needing to be aware of replaceWith(): // // node.become(Node.createUnary(.NOT, node.cloneAndStealChildren)) // Skew.Node.prototype.cloneAndStealChildren = function() { var clone = this._cloneWithoutChildren(); while (this.hasChildren()) { clone.appendChild(this._firstChild.remove()); } return clone; }; Skew.Node.prototype.clone = function() { var clone = this._cloneWithoutChildren(); if (this.kind == Skew.NodeKind.LAMBDA) { clone.symbol = this.symbol.asFunctionSymbol().clone(); clone.appendChild(clone.symbol.asFunctionSymbol().block); } else if (this.kind == Skew.NodeKind.VARIABLE) { clone.symbol = this.symbol.asVariableSymbol().clone(); clone.appendChild(clone.symbol.asVariableSymbol().value); } else { for (var child = this._firstChild; child != null; child = child._nextSibling) { clone.appendChild(child.clone()); } } return clone; }; // Change self node in place to become the provided node. The parent node is // not changed, so become() can be called within a nested method and does not // need to report the updated node reference to the caller since the reference // does not change. Skew.Node.prototype.become = function(node) { if (node == this) { return; } assert(node._parent == null); this.kind = node.kind; this.flags = node.flags; this.range = node.range; this.internalRange = node.internalRange; this.symbol = node.symbol; this.content = node.content; this.resolvedType = node.resolvedType; this.comments = node.comments; this.removeChildren(); this.appendChildrenFrom(node); }; Skew.Node.prototype.parent = function() { return this._parent; }; Skew.Node.prototype.firstChild = function() { return this._firstChild; }; Skew.Node.prototype.lastChild = function() { return this._lastChild; }; Skew.Node.prototype.previousSibling = function() { return this._previousSibling; }; Skew.Node.prototype.nextSibling = function() { return this._nextSibling; }; Skew.Node.prototype.hasControlFlowAtEnd = function() { return (Skew.NodeFlags.HAS_CONTROL_FLOW_AT_END & this.flags) != 0; }; Skew.Node.prototype.isImplicitReturn = function() { return (Skew.NodeFlags.IS_IMPLICIT_RETURN & this.flags) != 0; }; Skew.Node.prototype.isInitializerExpansion = function() { return (Skew.NodeFlags.IS_INITIALIZER_EXPANSION & this.flags) != 0; }; Skew.Node.prototype.isInsideParentheses = function() { return (Skew.NodeFlags.IS_INSIDE_PARENTHESES & this.flags) != 0; }; Skew.Node.prototype.wasAssignNull = function() { return (Skew.NodeFlags.WAS_ASSIGN_NULL & this.flags) != 0; }; Skew.Node.prototype.wasNullJoin = function() { return (Skew.NodeFlags.WAS_NULL_JOIN & this.flags) != 0; }; Skew.Node.prototype.shouldExpectType = function() { for (var node = this; node != null; node = node.parent()) { if ((Skew.NodeFlags.SHOULD_EXPECT_TYPE & node.flags) != 0) { return true; } } return false; }; // This is cheaper than childCount == 0 Skew.Node.prototype.hasChildren = function() { return this._firstChild != null; }; // This is cheaper than childCount == 1 Skew.Node.prototype.hasOneChild = function() { return this.hasChildren() && this._firstChild == this._lastChild; }; // This is cheaper than childCount == 2 Skew.Node.prototype.hasTwoChildren = function() { return this.hasChildren() && this._firstChild.nextSibling() == this._lastChild; }; // This is cheaper than childCount == 3 Skew.Node.prototype.hasThreeChildren = function() { return this.hasChildren() && this._firstChild.nextSibling() == this._lastChild.previousSibling(); }; Skew.Node.prototype.childCount = function() { var count = 0; for (var child = this._firstChild; child != null; child = child._nextSibling) { count = count + 1 | 0; } return count; }; Skew.Node.prototype.withFlags = function(value) { this.flags = value; return this; }; Skew.Node.prototype.withType = function(value) { this.resolvedType = value; return this; }; Skew.Node.prototype.withSymbol = function(value) { this.symbol = value; return this; }; Skew.Node.prototype.withContent = function(value) { this.content = value; return this; }; Skew.Node.prototype.withRange = function(value) { this.range = value; return this; }; Skew.Node.prototype.withInternalRange = function(value) { this.internalRange = value; return this; }; Skew.Node.prototype.withComments = function(value) { assert(this.comments == null); this.comments = value; return this; }; Skew.Node.prototype.withInnerComments = function(value) { assert(this.innerComments == null); this.innerComments = value; return this; }; Skew.Node.prototype.internalRangeOrRange = function() { return this.internalRange != null ? this.internalRange : this.range; }; Skew.Node.prototype.prependChild = function(node) { if (node == null) { return this; } assert(node != this); assert(node._parent == null); assert(node._previousSibling == null); assert(node._nextSibling == null); node._parent = this; if (this.hasChildren()) { node._nextSibling = this._firstChild; this._firstChild._previousSibling = node; this._firstChild = node; } else { this._lastChild = this._firstChild = node; } return this; }; Skew.Node.prototype.appendChild = function(node) { if (node == null) { return this; } assert(node != this); assert(node._parent == null); assert(node._previousSibling == null); assert(node._nextSibling == null); node._parent = this; if (this.hasChildren()) { node._previousSibling = this._lastChild; this._lastChild._nextSibling = node; this._lastChild = node; } else { this._lastChild = this._firstChild = node; } return this; }; Skew.Node.prototype.appendChildrenFrom = function(node) { assert(node != this); while (node.hasChildren()) { this.appendChild(node._firstChild.remove()); } return this; }; Skew.Node.prototype.insertChildBefore = function(after, before) { if (before == null) { return this; } assert(before != after); assert(before._parent == null); assert(before._previousSibling == null); assert(before._nextSibling == null); assert(after == null || after._parent == this); if (after == null) { return this.appendChild(before); } before._parent = this; before._previousSibling = after._previousSibling; before._nextSibling = after; if (after._previousSibling != null) { assert(after == after._previousSibling._nextSibling); after._previousSibling._nextSibling = before; } else { assert(after == this._firstChild); this._firstChild = before; } after._previousSibling = before; return this; }; Skew.Node.prototype.insertChildAfter = function(before, after) { if (after == null) { return this; } assert(before != after); assert(after._parent == null); assert(after._previousSibling == null); assert(after._nextSibling == null); assert(before == null || before._parent == this); if (before == null) { return this.prependChild(after); } after._parent = this; after._previousSibling = before; after._nextSibling = before._nextSibling; if (before._nextSibling != null) { assert(before == before._nextSibling._previousSibling); before._nextSibling._previousSibling = after; } else { assert(before == this._lastChild); this._lastChild = after; } before._nextSibling = after; return this; }; Skew.Node.prototype.insertChildrenAfterFrom = function(from, after) { while (from.hasChildren()) { this.insertChildAfter(after, from.lastChild().remove()); } }; Skew.Node.prototype.remove = function() { assert(this._parent != null); if (this._previousSibling != null) { assert(this._previousSibling._nextSibling == this); this._previousSibling._nextSibling = this._nextSibling; } else { assert(this._parent._firstChild == this); this._parent._firstChild = this._nextSibling; } if (this._nextSibling != null) { assert(this._nextSibling._previousSibling == this); this._nextSibling._previousSibling = this._previousSibling; } else { assert(this._parent._lastChild == this); this._parent._lastChild = this._previousSibling; } this._parent = null; this._previousSibling = null; this._nextSibling = null; return this; }; Skew.Node.prototype.removeChildren = function() { while (this.hasChildren()) { this._firstChild.remove(); } }; Skew.Node.prototype.replaceWith = function(node) { assert(node != this); assert(this._parent != null); assert(node._parent == null); assert(node._previousSibling == null); assert(node._nextSibling == null); node._parent = this._parent; node._previousSibling = this._previousSibling; node._nextSibling = this._nextSibling; if (this._previousSibling != null) { assert(this._previousSibling._nextSibling == this); this._previousSibling._nextSibling = node; } else { assert(this._parent._firstChild == this); this._parent._firstChild = node; } if (this._nextSibling != null) { assert(this._nextSibling._previousSibling == this); this._nextSibling._previousSibling = node; } else { assert(this._parent._lastChild == this); this._parent._lastChild = node; } if (this._parent.kind == Skew.NodeKind.LAMBDA) { assert(this == this._parent.symbol.asFunctionSymbol().block); this._parent.symbol.asFunctionSymbol().block = node; } else if (this._parent.kind == Skew.NodeKind.VARIABLE) { assert(this == this._parent.symbol.asVariableSymbol().value); this._parent.symbol.asVariableSymbol().value = node; } this._parent = null; this._previousSibling = null; this._nextSibling = null; return this; }; Skew.Node.prototype.replaceWithChildrenFrom = function(node) { assert(node != this); var parent = this._parent; while (node.hasChildren()) { parent.insertChildBefore(this, node._firstChild.remove()); } return this.remove(); }; Skew.Node.prototype.swapWith = function(node) { assert(node != this); assert(this._parent != null && this._parent == node._parent); var parent = this._parent; var nextSibling = this._nextSibling; if (node == this._previousSibling) { parent.insertChildBefore(node, this.remove()); } else if (node == nextSibling) { parent.insertChildAfter(node, this.remove()); } else { parent.insertChildBefore(node, this.remove()); parent.insertChildBefore(nextSibling, node.remove()); } }; Skew.Node._symbolsOrStringsLookTheSame = function(left, right) { return left.symbol != null && left.symbol == right.symbol || left.symbol == null && right.symbol == null && left.asString() == right.asString(); }; Skew.Node._childrenLookTheSame = function(left, right) { var leftChild = left.firstChild(); var rightChild = right.firstChild(); while (leftChild != null && rightChild != null) { if (!Skew.Node._looksTheSame(leftChild, rightChild)) { return false; } leftChild = leftChild.nextSibling(); rightChild = rightChild.nextSibling(); } return leftChild == null && rightChild == null; }; Skew.Node._looksTheSame = function(left, right) { if (left.kind == right.kind) { switch (left.kind) { case Skew.NodeKind.NULL: { return true; } case Skew.NodeKind.NAME: { return Skew.Node._symbolsOrStringsLookTheSame(left, right); } case Skew.NodeKind.DOT: { return Skew.Node._symbolsOrStringsLookTheSame(left, right) && Skew.Node._looksTheSame(left.dotTarget(), right.dotTarget()); } case Skew.NodeKind.CONSTANT: { switch (left.content.kind()) { case Skew.ContentKind.INT: { return right.isInt() && left.asInt() == right.asInt(); } case Skew.ContentKind.BOOL: { return right.isBool() && left.asBool() == right.asBool(); } case Skew.ContentKind.DOUBLE: { return right.isDouble() && left.asDouble() == right.asDouble(); } case Skew.ContentKind.STRING: { return right.isString() && left.asString() == right.asString(); } } break; } case Skew.NodeKind.BLOCK: case Skew.NodeKind.BREAK: case Skew.NodeKind.CONTINUE: case Skew.NodeKind.EXPRESSION: case Skew.NodeKind.IF: case Skew.NodeKind.RETURN: case Skew.NodeKind.THROW: case Skew.NodeKind.WHILE: case Skew.NodeKind.ASSIGN_INDEX: case Skew.NodeKind.CALL: case Skew.NodeKind.HOOK: case Skew.NodeKind.INDEX: case Skew.NodeKind.INITIALIZER_LIST: case Skew.NodeKind.INITIALIZER_MAP: case Skew.NodeKind.PAIR: case Skew.NodeKind.SEQUENCE: case Skew.NodeKind.COMPLEMENT: case Skew.NodeKind.NEGATIVE: case Skew.NodeKind.NOT: case Skew.NodeKind.POSITIVE: case Skew.NodeKind.POSTFIX_DECREMENT: case Skew.NodeKind.POSTFIX_INCREMENT: case Skew.NodeKind.PREFIX_DECREMENT: case Skew.NodeKind.PREFIX_INCREMENT: { return Skew.Node._childrenLookTheSame(left, right); } default: { if (Skew.in_NodeKind.isBinary(left.kind)) { return Skew.Node._childrenLookTheSame(left, right); } break; } } } // Null literals are always implicitly casted, so unwrap implicit casts if (left.kind == Skew.NodeKind.CAST) { return Skew.Node._looksTheSame(left.castValue(), right); } if (right.kind == Skew.NodeKind.CAST) { return Skew.Node._looksTheSame(left, right.castValue()); } return false; }; Skew.Node.prototype.isSuperCallStatement = function() { return this.kind == Skew.NodeKind.EXPRESSION && (this.expressionValue().kind == Skew.NodeKind.SUPER || this.expressionValue().kind == Skew.NodeKind.CALL && this.expressionValue().callValue().kind == Skew.NodeKind.SUPER); }; Skew.Node.prototype.isEmptySequence = function() { return this.kind == Skew.NodeKind.SEQUENCE && !this.hasChildren(); }; Skew.Node.prototype.isTrue = function() { return this.kind == Skew.NodeKind.CONSTANT && this.content.kind() == Skew.ContentKind.BOOL && Skew.in_Content.asBool(this.content); }; Skew.Node.prototype.isFalse = function() { return this.kind == Skew.NodeKind.CONSTANT && this.content.kind() == Skew.ContentKind.BOOL && !Skew.in_Content.asBool(this.content); }; Skew.Node.prototype.isType = function() { return this.kind == Skew.NodeKind.TYPE || this.kind == Skew.NodeKind.LAMBDA_TYPE || (this.kind == Skew.NodeKind.NAME || this.kind == Skew.NodeKind.DOT || this.kind == Skew.NodeKind.PARAMETERIZE) && this.symbol != null && Skew.in_SymbolKind.isType(this.symbol.kind); }; Skew.Node.prototype.isAssignTarget = function() { return this._parent != null && (Skew.in_NodeKind.isUnaryAssign(this._parent.kind) || Skew.in_NodeKind.isBinaryAssign(this._parent.kind) && this == this._parent.binaryLeft()); }; Skew.Node.prototype.isZero = function() { return this.isInt() && this.asInt() == 0 || this.isDouble() && this.asDouble() == 0; }; Skew.Node.prototype.isNumberLessThanZero = function() { return this.isInt() && this.asInt() < 0 || this.isDouble() && this.asDouble() < 0; }; Skew.Node.prototype.hasNoSideEffects = function() { assert(Skew.in_NodeKind.isExpression(this.kind)); switch (this.kind) { case Skew.NodeKind.CONSTANT: case Skew.NodeKind.NAME: case Skew.NodeKind.NULL: case Skew.NodeKind.TYPE: { return true; } case Skew.NodeKind.CAST: { return this.castValue().hasNoSideEffects(); } case Skew.NodeKind.HOOK: { return this.hookTest().hasNoSideEffects() && this.hookTrue().hasNoSideEffects() && this.hookFalse().hasNoSideEffects(); } case Skew.NodeKind.DOT: { return this.dotTarget().hasNoSideEffects(); } case Skew.NodeKind.COMPLEMENT: case Skew.NodeKind.NEGATIVE: case Skew.NodeKind.NOT: case Skew.NodeKind.POSITIVE: case Skew.NodeKind.POSTFIX_DECREMENT: case Skew.NodeKind.POSTFIX_INCREMENT: case Skew.NodeKind.PREFIX_DECREMENT: case Skew.NodeKind.PREFIX_INCREMENT: { return !Skew.in_NodeKind.isUnaryAssign(this.kind) && this.unaryValue().hasNoSideEffects(); } default: { if (Skew.in_NodeKind.isBinary(this.kind)) { return !Skew.in_NodeKind.isBinaryAssign(this.kind) && this.binaryLeft().hasNoSideEffects() && this.binaryRight().hasNoSideEffects(); } break; } } return false; }; Skew.Node.prototype.looksTheSameAs = function(node) { return Skew.Node._looksTheSame(this, node); }; Skew.Node.prototype.invertBooleanCondition = function(cache) { assert(Skew.in_NodeKind.isExpression(this.kind)); switch (this.kind) { case Skew.NodeKind.CONSTANT: { if (this.content.kind() == Skew.ContentKind.BOOL) { this.content = new Skew.BoolContent(!Skew.in_Content.asBool(this.content)); } return; } case Skew.NodeKind.NOT: { this.become(this.unaryValue().remove()); return; } case Skew.NodeKind.EQUAL: { this.kind = Skew.NodeKind.NOT_EQUAL; return; } case Skew.NodeKind.NOT_EQUAL: { this.kind = Skew.NodeKind.EQUAL; return; } case Skew.NodeKind.LOGICAL_OR: { this.kind = Skew.NodeKind.LOGICAL_AND; this.binaryLeft().invertBooleanCondition(cache); this.binaryRight().invertBooleanCondition(cache); return; } case Skew.NodeKind.LOGICAL_AND: { this.kind = Skew.NodeKind.LOGICAL_OR; this.binaryLeft().invertBooleanCondition(cache); this.binaryRight().invertBooleanCondition(cache); return; } // Non-equality comparison operators involving floating-point numbers // can't be inverted because one or both of those values may be NAN. // Equality comparisons still work fine because inverting the test // inverts the result as expected: // // Test | Result // --------------+---------- // 0 == NAN | false // 0 != NAN | true // 0 < NAN | false // 0 > NAN | false // 0 <= NAN | false // 0 >= NAN | false // NAN == NAN | false // NAN != NAN | true // NAN < NAN | false // NAN > NAN | false // NAN <= NAN | false // NAN >= NAN | false // case Skew.NodeKind.LESS_THAN: case Skew.NodeKind.GREATER_THAN: case Skew.NodeKind.LESS_THAN_OR_EQUAL: case Skew.NodeKind.GREATER_THAN_OR_EQUAL: { var commonType = cache.commonImplicitType(this.binaryLeft().resolvedType, this.binaryRight().resolvedType); if (commonType != null && commonType != cache.doubleType) { switch (this.kind) { case Skew.NodeKind.LESS_THAN: { this.kind = Skew.NodeKind.GREATER_THAN_OR_EQUAL; break; } case Skew.NodeKind.GREATER_THAN: { this.kind = Skew.NodeKind.LESS_THAN_OR_EQUAL; break; } case Skew.NodeKind.LESS_THAN_OR_EQUAL: { this.kind = Skew.NodeKind.GREATER_THAN; break; } case Skew.NodeKind.GREATER_THAN_OR_EQUAL: { this.kind = Skew.NodeKind.LESS_THAN; break; } } return; } break; } case Skew.NodeKind.SEQUENCE: { this._lastChild.invertBooleanCondition(cache); return; } } this.become(Skew.Node.createUnary(Skew.NodeKind.NOT, this.cloneAndStealChildren()).withType(cache.boolType)); }; // "a + (b + c)" => "(a + b) + c" Skew.Node.prototype.rotateBinaryRightToLeft = function() { assert(this.kind == this.binaryRight().kind); var left = this.binaryLeft(); var right = this.binaryRight(); var rightLeft = right.binaryLeft(); var rightRight = right.binaryRight(); // "a + (b + c)" => "(b + c) + a" left.swapWith(right); // "a + (b + c)" => "(c + b) + a" rightLeft.swapWith(rightRight); // "a + (b + c)" => "(a + c + b)" right.prependChild(left.remove()); // "a + (b + c)" => "(a + b) + c" this.appendChild(rightRight.remove()); }; // If a variable is inside a variable cluster, break up the variable cluster // into separate clusters so that variable is in a cluster all by itself. That // way the variable can easily be replaced by something else (an assigment, // for example. This does not handle variables inside loop headers. // // "var a, b, c, d, e" => c.extractVariableFromVariables => "var a, b; var c; var d, e" // Skew.Node.prototype.extractVariableFromVariables = function() { assert(this.kind == Skew.NodeKind.VARIABLE); assert(this.parent() != null && this.parent().kind == Skew.NodeKind.VARIABLES); assert(this.parent().parent() != null && this.parent().parent().kind == Skew.NodeKind.BLOCK); // Split off variables before this one if (this.previousSibling() != null) { var variables = new Skew.Node(Skew.NodeKind.VARIABLES); while (this.previousSibling() != null) { variables.prependChild(this.previousSibling().remove()); } this.parent().parent().insertChildBefore(this.parent(), variables); } // Split off variables after this one if (this.nextSibling() != null) { var variables1 = new Skew.Node(Skew.NodeKind.VARIABLES); while (this.nextSibling() != null) { variables1.appendChild(this.nextSibling().remove()); } this.parent().parent().insertChildAfter(this.parent(), variables1); } }; Skew.Node.prototype.sign = function() { if (this.kind == Skew.NodeKind.NEGATIVE || this.kind == Skew.NodeKind.PREFIX_DECREMENT || this.isNumberLessThanZero()) { return Skew.NodeKind.NEGATIVE; } if (this.kind == Skew.NodeKind.POSITIVE || this.kind == Skew.NodeKind.PREFIX_INCREMENT) { return Skew.NodeKind.POSITIVE; } return Skew.NodeKind.NULL; }; Skew.Node.createAnnotation = function(value, test) { assert(Skew.in_NodeKind.isExpression(value.kind)); assert(test == null || Skew.in_NodeKind.isExpression(test.kind)); return new Skew.Node(Skew.NodeKind.ANNOTATION).appendChild(value).appendChild(test); }; Skew.Node.createCatch = function(symbol, block) { assert(block.kind == Skew.NodeKind.BLOCK); return new Skew.Node(Skew.NodeKind.CATCH).appendChild(block).withSymbol(symbol); }; // This adds the initializer expression to the tree for ease of traversal Skew.Node.createVariable = function(symbol) { return new Skew.Node(Skew.NodeKind.VARIABLE).appendChild(symbol.value).withSymbol(symbol); }; Skew.Node.createExpression = function(value) { assert(Skew.in_NodeKind.isExpression(value.kind)); return new Skew.Node(Skew.NodeKind.EXPRESSION).appendChild(value); }; Skew.Node.createFor = function(setup, test, update, block) { assert(Skew.in_NodeKind.isExpression(setup.kind) || setup.kind == Skew.NodeKind.VARIABLES); assert(Skew.in_NodeKind.isExpression(test.kind)); assert(Skew.in_NodeKind.isExpression(update.kind)); assert(block.kind == Skew.NodeKind.BLOCK); return new Skew.Node(Skew.NodeKind.FOR).appendChild(setup).appendChild(test).appendChild(update).appendChild(block); }; Skew.Node.createForeach = function(symbol, value, block) { assert(Skew.in_NodeKind.isExpression(value.kind)); assert(block.kind == Skew.NodeKind.BLOCK); return new Skew.Node(Skew.NodeKind.FOREACH).withSymbol(symbol).appendChild(value).appendChild(block); }; Skew.Node.createIf = function(test, trueBlock, falseBlock) { assert(Skew.in_NodeKind.isExpression(test.kind)); assert(trueBlock.kind == Skew.NodeKind.BLOCK); assert(falseBlock == null || falseBlock.kind == Skew.NodeKind.BLOCK); return new Skew.Node(Skew.NodeKind.IF).appendChild(test).appendChild(trueBlock).appendChild(falseBlock); }; Skew.Node.createReturn = function(value) { assert(value == null || Skew.in_NodeKind.isExpression(value.kind)); return new Skew.Node(Skew.NodeKind.RETURN).appendChild(value); }; Skew.Node.createSwitch = function(value) { assert(Skew.in_NodeKind.isExpression(value.kind)); return new Skew.Node(Skew.NodeKind.SWITCH).appendChild(value); }; Skew.Node.createThrow = function(value) { assert(Skew.in_NodeKind.isExpression(value.kind)); return new Skew.Node(Skew.NodeKind.THROW).appendChild(value); }; Skew.Node.createTry = function(tryBlock) { assert(tryBlock.kind == Skew.NodeKind.BLOCK); return new Skew.Node(Skew.NodeKind.TRY).appendChild(tryBlock); }; Skew.Node.createIndex = function(left, right) { assert(Skew.in_NodeKind.isExpression(left.kind)); assert(Skew.in_NodeKind.isExpression(right.kind)); return new Skew.Node(Skew.NodeKind.INDEX).appendChild(left).appendChild(right); }; Skew.Node.createCall = function(target) { assert(Skew.in_NodeKind.isExpression(target.kind)); return new Skew.Node(Skew.NodeKind.CALL).appendChild(target); }; Skew.Node.createCast = function(value, type) { assert(Skew.in_NodeKind.isExpression(value.kind)); assert(Skew.in_NodeKind.isExpression(type.kind)); return new Skew.Node(Skew.NodeKind.CAST).appendChild(value).appendChild(type); }; Skew.Node.createHook = function(test, trueValue, falseValue) { assert(Skew.in_NodeKind.isExpression(test.kind)); assert(Skew.in_NodeKind.isExpression(trueValue.kind)); assert(Skew.in_NodeKind.isExpression(falseValue.kind)); return new Skew.Node(Skew.NodeKind.HOOK).appendChild(test).appendChild(trueValue).appendChild(falseValue); }; Skew.Node.createInitializer = function(kind) { assert(Skew.in_NodeKind.isInitializer(kind)); return new Skew.Node(kind); }; // This adds the block to the tree for ease of traversal Skew.Node.createLambda = function(symbol) { return new Skew.Node(Skew.NodeKind.LAMBDA).appendChild(symbol.block).withSymbol(symbol); }; Skew.Node.createPair = function(first, second) { assert(Skew.in_NodeKind.isExpression(first.kind)); assert(Skew.in_NodeKind.isExpression(second.kind)); return new Skew.Node(Skew.NodeKind.PAIR).appendChild(first).appendChild(second); }; Skew.Node.createParameterize = function(value) { assert(Skew.in_NodeKind.isExpression(value.kind)); return new Skew.Node(Skew.NodeKind.PARAMETERIZE).appendChild(value); }; Skew.Node.createSequence2 = function(before, after) { assert(Skew.in_NodeKind.isExpression(before.kind)); assert(Skew.in_NodeKind.isExpression(after.kind)); assert(before.parent() == null); assert(after.parent() == null); if (before.kind == Skew.NodeKind.SEQUENCE) { if (after.kind == Skew.NodeKind.SEQUENCE) { return before.withType(after.resolvedType).appendChildrenFrom(after); } return before.withType(after.resolvedType).appendChild(after); } if (after.kind == Skew.NodeKind.SEQUENCE) { return after.prependChild(before); } return new Skew.Node(Skew.NodeKind.SEQUENCE).withType(after.resolvedType).appendChild(before).appendChild(after); }; Skew.Node.createTypeCheck = function(value, type) { assert(Skew.in_NodeKind.isExpression(value.kind)); assert(Skew.in_NodeKind.isExpression(type.kind)); return new Skew.Node(Skew.NodeKind.TYPE_CHECK).appendChild(value).appendChild(type); }; Skew.Node.createXML = function(tag, attributes, children, closingTag) { assert(Skew.in_NodeKind.isExpression(tag.kind)); assert(attributes.kind == Skew.NodeKind.SEQUENCE); assert(children.kind == Skew.NodeKind.BLOCK); assert(closingTag == null || Skew.in_NodeKind.isExpression(closingTag.kind)); return new Skew.Node(Skew.NodeKind.XML).appendChild(tag).appendChild(attributes).appendChild(children).appendChild(closingTag); }; Skew.Node.createUnary = function(kind, value) { assert(Skew.in_NodeKind.isUnary(kind)); assert(Skew.in_NodeKind.isExpression(value.kind)); return new Skew.Node(kind).appendChild(value); }; Skew.Node.createBinary = function(kind, left, right) { assert(Skew.in_NodeKind.isBinary(kind)); assert(Skew.in_NodeKind.isExpression(left.kind)); assert(Skew.in_NodeKind.isExpression(right.kind)); return new Skew.Node(kind).appendChild(left).appendChild(right); }; Skew.Node.createSymbolReference = function(symbol) { return new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent(symbol.name)).withSymbol(symbol).withType(symbol.resolvedType); }; Skew.Node.createMemberReference = function(target, member) { return new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(member.name)).appendChild(target).withSymbol(member).withType(member.resolvedType); }; Skew.Node.createSymbolCall = function(symbol) { return Skew.Node.createCall(Skew.Node.createSymbolReference(symbol)).withSymbol(symbol).withType(symbol.resolvedType.returnType); }; Skew.Node.prototype.isInt = function() { return this.kind == Skew.NodeKind.CONSTANT && this.content.kind() == Skew.ContentKind.INT; }; Skew.Node.prototype.isBool = function() { return this.kind == Skew.NodeKind.CONSTANT && this.content.kind() == Skew.ContentKind.BOOL; }; Skew.Node.prototype.isDouble = function() { return this.kind == Skew.NodeKind.CONSTANT && this.content.kind() == Skew.ContentKind.DOUBLE; }; Skew.Node.prototype.isString = function() { return this.kind == Skew.NodeKind.CONSTANT && this.content.kind() == Skew.ContentKind.STRING; }; Skew.Node.prototype.asInt = function() { assert(this.kind == Skew.NodeKind.CONSTANT); return Skew.in_Content.asInt(this.content); }; Skew.Node.prototype.asBool = function() { assert(this.kind == Skew.NodeKind.CONSTANT); return Skew.in_Content.asBool(this.content); }; Skew.Node.prototype.asDouble = function() { assert(this.kind == Skew.NodeKind.CONSTANT); return Skew.in_Content.asDouble(this.content); }; Skew.Node.prototype.asString = function() { assert(this.kind == Skew.NodeKind.NAME || this.kind == Skew.NodeKind.DOT || this.kind == Skew.NodeKind.CONSTANT || this.kind == Skew.NodeKind.NULL_DOT); return Skew.in_Content.asString(this.content); }; Skew.Node.prototype.blockStatement = function() { assert(this.kind == Skew.NodeKind.BLOCK); return this.hasOneChild() ? this._firstChild : null; }; Skew.Node.prototype.firstValue = function() { assert(this.kind == Skew.NodeKind.PAIR); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.secondValue = function() { assert(this.kind == Skew.NodeKind.PAIR); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._lastChild.kind)); return this._lastChild; }; Skew.Node.prototype.dotTarget = function() { assert(this.kind == Skew.NodeKind.DOT || this.kind == Skew.NodeKind.NULL_DOT); assert(this.childCount() <= 1); assert(this._firstChild == null || Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.annotationValue = function() { assert(this.kind == Skew.NodeKind.ANNOTATION); assert(this.childCount() == 1 || this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.annotationTest = function() { assert(this.kind == Skew.NodeKind.ANNOTATION); assert(this.childCount() == 1 || this.childCount() == 2); assert(this._firstChild._nextSibling == null || Skew.in_NodeKind.isExpression(this._firstChild._nextSibling.kind)); return this._firstChild._nextSibling; }; Skew.Node.prototype.caseBlock = function() { assert(this.kind == Skew.NodeKind.CASE); assert(this.childCount() >= 1); assert(this._lastChild.kind == Skew.NodeKind.BLOCK); return this._lastChild; }; Skew.Node.prototype.catchBlock = function() { assert(this.kind == Skew.NodeKind.CATCH); assert(this.childCount() == 1); assert(this._firstChild.kind == Skew.NodeKind.BLOCK); return this._firstChild; }; Skew.Node.prototype.variableValue = function() { assert(this.kind == Skew.NodeKind.VARIABLE); assert(this.childCount() <= 1); assert(this._firstChild == null || Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.expressionValue = function() { assert(this.kind == Skew.NodeKind.EXPRESSION); assert(this.childCount() == 1); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.returnValue = function() { assert(this.kind == Skew.NodeKind.RETURN); assert(this.childCount() <= 1); assert(this._firstChild == null || Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.switchValue = function() { assert(this.kind == Skew.NodeKind.SWITCH); assert(this.childCount() >= 1); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.defaultCase = function() { assert(this.kind == Skew.NodeKind.SWITCH); assert(this.childCount() >= 1); // The default case is always the last one return !this.hasOneChild() && this._lastChild.hasOneChild() ? this._lastChild : null; }; Skew.Node.prototype.parameterizeValue = function() { assert(this.kind == Skew.NodeKind.PARAMETERIZE); assert(this.childCount() >= 1); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.callValue = function() { assert(this.kind == Skew.NodeKind.CALL); assert(this.childCount() >= 1); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.castValue = function() { assert(this.kind == Skew.NodeKind.CAST); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.castType = function() { assert(this.kind == Skew.NodeKind.CAST); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._lastChild.kind)); return this._lastChild; }; Skew.Node.prototype.typeCheckValue = function() { assert(this.kind == Skew.NodeKind.TYPE_CHECK); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.typeCheckType = function() { assert(this.kind == Skew.NodeKind.TYPE_CHECK); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._lastChild.kind)); return this._lastChild; }; Skew.Node.prototype.xmlTag = function() { assert(this.kind == Skew.NodeKind.XML); assert(this.childCount() == 3 || this.childCount() == 4); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.xmlAttributes = function() { assert(this.kind == Skew.NodeKind.XML); assert(this.childCount() == 3 || this.childCount() == 4); assert(this._firstChild._nextSibling.kind == Skew.NodeKind.SEQUENCE); return this._firstChild._nextSibling; }; Skew.Node.prototype.xmlChildren = function() { assert(this.kind == Skew.NodeKind.XML); assert(this.childCount() == 3 || this.childCount() == 4); assert(this._firstChild._nextSibling._nextSibling.kind == Skew.NodeKind.BLOCK); return this._firstChild._nextSibling._nextSibling; }; Skew.Node.prototype.xmlClosingTag = function() { assert(this.kind == Skew.NodeKind.XML); assert(this.childCount() == 3 || this.childCount() == 4); assert(this._firstChild._nextSibling._nextSibling._nextSibling == null || Skew.in_NodeKind.isExpression(this._firstChild._nextSibling._nextSibling._nextSibling.kind)); return this._firstChild._nextSibling._nextSibling._nextSibling; }; Skew.Node.prototype.unaryValue = function() { assert(Skew.in_NodeKind.isUnary(this.kind)); assert(this.childCount() == 1); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.binaryLeft = function() { assert(Skew.in_NodeKind.isBinary(this.kind)); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.binaryRight = function() { assert(Skew.in_NodeKind.isBinary(this.kind)); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._lastChild.kind)); return this._lastChild; }; Skew.Node.prototype.throwValue = function() { assert(this.childCount() == 1); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.tryBlock = function() { assert(this.kind == Skew.NodeKind.TRY); assert(this.childCount() >= 1); assert(this._firstChild.kind == Skew.NodeKind.BLOCK); return this._firstChild; }; Skew.Node.prototype.finallyBlock = function() { assert(this.kind == Skew.NodeKind.TRY); assert(this.childCount() >= 1); var finallyBlock = this._lastChild; return finallyBlock != this.tryBlock() && finallyBlock.kind == Skew.NodeKind.BLOCK ? finallyBlock : null; }; Skew.Node.prototype.whileTest = function() { assert(this.kind == Skew.NodeKind.WHILE); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.whileBlock = function() { assert(this.kind == Skew.NodeKind.WHILE); assert(this.childCount() == 2); assert(this._lastChild.kind == Skew.NodeKind.BLOCK); return this._lastChild; }; Skew.Node.prototype.forSetup = function() { assert(this.kind == Skew.NodeKind.FOR); assert(this.childCount() == 4); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind) || this._firstChild.kind == Skew.NodeKind.VARIABLES); return this._firstChild; }; Skew.Node.prototype.forTest = function() { assert(this.kind == Skew.NodeKind.FOR); assert(this.childCount() == 4); assert(Skew.in_NodeKind.isExpression(this._firstChild._nextSibling.kind)); return this._firstChild._nextSibling; }; Skew.Node.prototype.forUpdate = function() { assert(this.kind == Skew.NodeKind.FOR); assert(this.childCount() == 4); assert(Skew.in_NodeKind.isExpression(this._lastChild._previousSibling.kind)); return this._lastChild._previousSibling; }; Skew.Node.prototype.forBlock = function() { assert(this.kind == Skew.NodeKind.FOR); assert(this.childCount() == 4); assert(this._lastChild.kind == Skew.NodeKind.BLOCK); return this._lastChild; }; Skew.Node.prototype.foreachValue = function() { assert(this.kind == Skew.NodeKind.FOREACH); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.foreachBlock = function() { assert(this.kind == Skew.NodeKind.FOREACH); assert(this.childCount() == 2); assert(this._lastChild.kind == Skew.NodeKind.BLOCK); return this._lastChild; }; Skew.Node.prototype.ifTest = function() { assert(this.kind == Skew.NodeKind.IF); assert(this.childCount() == 2 || this.childCount() == 3); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.ifTrue = function() { assert(this.kind == Skew.NodeKind.IF); assert(this.childCount() == 2 || this.childCount() == 3); assert(this._firstChild._nextSibling.kind == Skew.NodeKind.BLOCK); return this._firstChild._nextSibling; }; Skew.Node.prototype.ifFalse = function() { assert(this.kind == Skew.NodeKind.IF); assert(this.childCount() == 2 || this.childCount() == 3); assert(this._firstChild._nextSibling._nextSibling == null || this._firstChild._nextSibling._nextSibling.kind == Skew.NodeKind.BLOCK); return this._firstChild._nextSibling._nextSibling; }; Skew.Node.prototype.hookTest = function() { assert(this.kind == Skew.NodeKind.HOOK); assert(this.childCount() == 3); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.hookTrue = function() { assert(this.kind == Skew.NodeKind.HOOK); assert(this.childCount() == 3); assert(Skew.in_NodeKind.isExpression(this._firstChild._nextSibling.kind)); return this._firstChild._nextSibling; }; Skew.Node.prototype.hookFalse = function() { assert(this.kind == Skew.NodeKind.HOOK); assert(this.childCount() == 3); assert(Skew.in_NodeKind.isExpression(this._lastChild.kind)); return this._lastChild; }; Skew.Node.prototype.indexLeft = function() { assert(this.kind == Skew.NodeKind.INDEX); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.indexRight = function() { assert(this.kind == Skew.NodeKind.INDEX); assert(this.childCount() == 2); assert(Skew.in_NodeKind.isExpression(this._firstChild._nextSibling.kind)); return this._firstChild._nextSibling; }; Skew.Node.prototype.assignIndexLeft = function() { assert(this.kind == Skew.NodeKind.ASSIGN_INDEX); assert(this.childCount() == 3); assert(Skew.in_NodeKind.isExpression(this._firstChild.kind)); return this._firstChild; }; Skew.Node.prototype.assignIndexCenter = function() { assert(this.kind == Skew.NodeKind.ASSIGN_INDEX); assert(this.childCount() == 3); assert(Skew.in_NodeKind.isExpression(this._firstChild._nextSibling.kind)); return this._firstChild._nextSibling; }; Skew.Node.prototype.assignIndexRight = function() { assert(this.kind == Skew.NodeKind.ASSIGN_INDEX); assert(this.childCount() == 3); assert(Skew.in_NodeKind.isExpression(this._lastChild.kind)); return this._lastChild; }; Skew.Node.prototype.lambdaBlock = function() { assert(this.kind == Skew.NodeKind.LAMBDA); assert(this.childCount() == 1); assert(this._firstChild.kind == Skew.NodeKind.BLOCK); return this._firstChild; }; Skew.Node.prototype.lambdaReturnType = function() { assert(this.kind == Skew.NodeKind.LAMBDA_TYPE); assert(this.childCount() >= 1); assert(Skew.in_NodeKind.isExpression(this._lastChild.kind)); return this._lastChild; }; // Syntax warnings can be thought of as linting Skew.Log = function() { this.diagnostics = []; this.appendCallback = null; this.warningsAreErrors = false; this._warningCount = 0; this._errorCount = 0; this._wasWarningCount = 0; }; Skew.Log.prototype.commandLineWarningDuplicateFlagValue = function(range, name, previous) { this.append(this.newWarning(range, 'Multiple values are specified for "' + name + '", using the later value').withNote(previous, 'Ignoring the previous value')); }; Skew.Log.prototype.commandLineErrorBadFlag = function(range, name) { this.append(this.newError(range, 'Unknown command line flag "' + name + '"')); }; Skew.Log.prototype.commandLineErrorMissingValue = function(range, text) { this.append(this.newError(range, 'Use "' + text + '" to provide a value')); }; Skew.Log.prototype.commandLineErrorExpectedToken = function(range, expected, found, text) { this.append(this.newError(range, 'Expected "' + expected + '" but found "' + found + '" in "' + text + '"')); }; Skew.Log.prototype.commandLineErrorNonBooleanValue = function(range, value, text) { this.append(this.newError(range, 'Expected "true" or "false" but found "' + value + '" in "' + text + '"')); }; Skew.Log.prototype.commandLineErrorNonIntegerValue = function(range, value, text) { this.append(this.newError(range, 'Expected integer constant but found "' + value + '" in "' + text + '"')); }; Skew.Log.prototype.commandLineErrorExpectedDefineValue = function(range, name) { this.append(this.newError(range, 'Use "--define:' + name + '=___" to provide a value')); }; Skew.Log.prototype.commandLineErrorMissingOutput = function(range, first, second) { this.append(this.newError(range, 'Specify the output location using either "' + first + '" or "' + second + '"')); }; Skew.Log.prototype.commandLineErrorDuplicateOutput = function(range, first, second) { this.append(this.newError(range, 'Cannot specify both "' + first + '" and "' + second + '"')); }; Skew.Log.prototype.commandLineErrorUnreadableFile = function(range, name) { this.append(this.newError(range, 'Could not read from "' + name + '"')); }; Skew.Log.prototype.commandLineErrorUnwritableFile = function(range, name) { this.append(this.newError(range, 'Could not write to "' + name + '"')); }; Skew.Log.prototype.commandLineErrorNoInputFiles = function(range) { this.append(this.newError(range, 'Missing input files')); }; Skew.Log.prototype.commandLineErrorMissingTarget = function(range) { this.append(this.newError(range, 'Specify the target format using "--target"')); }; Skew.Log.prototype.commandLineErrorInvalidEnum = function(range, name, found, expected) { this.append(this.newError(range, 'Invalid ' + name + ' "' + found + '", must be either ' + Skew.PrettyPrint.joinQuoted(expected, 'or'))); }; Skew.Log.prototype.hasErrors = function() { return this._errorCount != 0; }; Skew.Log.prototype.hasWarnings = function() { return this._warningCount != 0; }; Skew.Log.prototype.warningCount = function() { return this._warningCount; }; Skew.Log.prototype.errorCount = function() { return this._errorCount; }; Skew.Log.prototype.wasWarningCount = function() { return this._wasWarningCount; }; Skew.Log.prototype.newError = function(range, text) { return new Skew.Diagnostic(Skew.DiagnosticKind.ERROR, range, text, false); }; Skew.Log.prototype.newWarning = function(range, text) { return new Skew.Diagnostic(this.warningsAreErrors ? Skew.DiagnosticKind.ERROR : Skew.DiagnosticKind.WARNING, range, text, this.warningsAreErrors); }; Skew.Log.prototype.append = function(diagnostic) { this.diagnostics.push(diagnostic); if (diagnostic.kind == Skew.DiagnosticKind.ERROR) { this._errorCount = this._errorCount + 1 | 0; } else { this._warningCount = this._warningCount + 1 | 0; } if (diagnostic.wasWarning) { this._wasWarningCount = this._wasWarningCount + 1 | 0; } if (this.appendCallback != null) { this.appendCallback(diagnostic); } }; Skew.Log.prototype.syntaxWarningIgnoredCommentInParser = function(range) { this.append(this.newWarning(range, 'This comment was ignored by the parser')); }; Skew.Log.prototype.syntaxWarningIgnoredCommentInEmitter = function(range) { this.append(this.newWarning(range, 'This comment was ignored by the emitter')); }; Skew.Log.prototype.syntaxWarningOctal = function(range) { var text = range.toString(); while (text.startsWith('0')) { text = in_string.slice1(text, 1); } this.append(this.newWarning(range, 'Number interpreted as decimal (use the prefix "0o" for octal numbers)').withFix(Skew.FixKind.OCTAL_REMOVE_ZEROS, range, 'Remove the leading zeros to avoid confusion', text).withFix(Skew.FixKind.OCTAL_ADD_PREFIX, range, 'Add the prefix "0o" to interpret the number as octal', '0o' + text)); }; Skew.Log.prototype.syntaxWarningExtraParentheses = function(range) { var leftSpace = range.rangeIncludingLeftWhitespace().start == range.start ? ' ' : ''; var rightSpace = range.rangeIncludingRightWhitespace().end == range.end ? ' ' : ''; var text = range.toString(); this.append(this.newWarning(range, 'Unnecessary parentheses').withFix(Skew.FixKind.UNNECESSARY_PARENTHESES, range, 'Remove parentheses', leftSpace + in_string.slice2(text, 1, text.length - 1 | 0) + rightSpace)); }; Skew.Log.prototype.syntaxWarningExtraComma = function(range) { this.append(this.newWarning(range, 'Unnecessary comma').withFix(Skew.FixKind.EXTRA_COMMA, range, 'Remove comma', '')); }; Skew.Log.prototype.syntaxErrorInvalidEscapeSequence = function(range) { this.append(this.newError(range, 'Invalid escape sequence')); }; Skew.Log.prototype.syntaxErrorIntegerLiteralTooLarge = function(range) { this.append(this.newError(range, 'Integer literal is too big to fit in 32 bits')); }; Skew.Log.prototype.syntaxErrorInvalidCharacter = function(range) { this.append(this.newError(range, 'Use double quotes for strings (single quotes are for character literals)').withFix(Skew.FixKind.SINGLE_QUOTES, range, 'Replace single quotes with double quotes', Skew.replaceSingleQuotesWithDoubleQuotes(range.toString()))); }; Skew.Log.prototype.syntaxErrorExtraData = function(range, text) { this.append(this.newError(range, 'Syntax error "' + (text == '"' ? '\\"' : text) + '"')); }; Skew.Log.prototype.syntaxErrorExtraColonBeforeType = function(range) { this.append(this.newError(range, 'Do not use a colon before a type expression').withFix(Skew.FixKind.EXTRA_COLON, range, 'Remove the colon', '')); }; Skew.Log.prototype.syntaxErrorNewOperator = function(range, correction) { this.append(this.newError(range, 'There is no "new" operator, use "' + correction + '" instead').withFix(Skew.FixKind.NEW_OPERATOR, range, 'Replace with "' + correction + '"', correction)); }; Skew.Log.prototype.syntaxErrorExtendsKeyword = function(range) { this.append(this.newError(range, 'Use ":" instead of "extends" to indicate a base class').withFix(Skew.FixKind.EXTENDS_IMPLEMENTS, range, 'Replace "extends" with ":"', ':')); }; Skew.Log.prototype.syntaxErrorImplementsKeyword = function(range) { this.append(this.newError(range, 'Use "::" instead of "implements" to indicate implemented interfaces').withFix(Skew.FixKind.EXTENDS_IMPLEMENTS, range, 'Replace "implements" with "::"', '::')); }; Skew.Log.prototype.syntaxErrorMissingVar = function(range) { this.append(this.newError(range, 'Use "var" before variable declarations').withFix(Skew.FixKind.MISSING_VAR, range, 'Insert "var"', 'var ' + range.toString())); }; Skew.Log.prototype.syntaxErrorMissingDef = function(range) { this.append(this.newError(range, 'Use "def" before function declarations').withFix(Skew.FixKind.MISSING_DEF, range, 'Insert "def"', 'def ' + range.toString())); }; Skew.Log.prototype.syntaxErrorStaticKeyword = function(range, parentKind, parentName) { this.append(this.newError(range, parentKind == Skew.SymbolKind.OBJECT_GLOBAL || parentKind == Skew.SymbolKind.OBJECT_NAMESPACE ? 'There is no "static" keyword' : 'There is no "static" keyword (declare this symbol in a namespace called "' + parentName + '" instead)')); }; Skew.Log.prototype.syntaxErrorPublicKeyword = function(range) { this.append(this.newError(range, 'There is no "public" keyword')); }; Skew.Log.prototype.syntaxErrorPrivateOrProtected = function(range) { this.append(this.newError(range, 'There is no "' + range.toString() + '" keyword (to give something protected access, use a name starting with "_" instead)')); }; Skew.Log.prototype.syntaxErrorExpectedCommaBetweenCases = function(range) { this.append(this.newError(range, 'Use a comma between multiple values in a case statement (example: "case 1, 2, 3 { ... }")').withFix(Skew.FixKind.CASE_COMMA, range, 'Replace this with a comma', ',')); }; Skew.Log.prototype.syntaxErrorColonAfterCaseOrDefault = function(range, breakRange) { var diagnostic = this.newError(range, 'Surround the body of case and default statements with "{" and "}" instead of ":" and "break"'); if (breakRange != null) { assert(range.source == breakRange.source); var start = range.source.indexToLineColumn(range.end); var end = range.source.indexToLineColumn(breakRange.start); var text = in_string.slice2(range.source.contents, range.end, breakRange.start); // Use the indentation of the case statement for the "}" if (start.line < end.line) { text = in_string.slice2(text, 0, text.length - end.column | 0) + Skew.indentOfLine(range.source.contentsOfLine(start.line)) + Skew.lineWithoutIndent(in_string.slice2(range.source.contentsOfLine(end.line), 0, end.column)); } diagnostic.withFix(Skew.FixKind.CASE_BRACES, Skew.Range.span(range.rangeIncludingLeftWhitespace(), breakRange), 'Replace ":" and "break" with "{" and "}"', ' {' + text + '}'); } this.append(diagnostic); }; Skew.Log.prototype.syntaxErrorOperatorTypo = function(range, correction) { this.append(this.newError(range, 'Use the "' + correction + '" operator instead').withFix(Skew.FixKind.OPERATOR_TYPO, range, 'Replace with "' + correction + '"', correction)); }; Skew.Log.prototype.syntaxErrorExtraVarInForLoop = function(range) { this.append(this.newError(range, 'The "var" keyword is unnecessary here since for loops automatically declare their variables').withFix(Skew.FixKind.FOR_LOOP_VAR, range != null ? range.rangeIncludingRightWhitespace() : null, 'Remove "var"', '')); }; Skew.Log.prototype.syntaxErrorWrongListSyntax = function(range, correction) { this.append(this.newError(range, 'The array type is "List"').withFix(Skew.FixKind.ARRAY_SYNTAX, range, 'Replace with "' + correction + '"', correction)); }; Skew.Log.prototype.syntaxErrorSlashComment = function(range) { var text = range.toString(); var last = text.length - 1 | 0; assert(text.startsWith('//')); if (in_string.get1(text, last) == 10) { text = in_string.slice2(text, 0, last); range = range.fromStart(last); } // Change a run of "////" into "####" var replacement = ''; for (var i = 1, count = text.length; i < count; i = i + 1 | 0) { if (in_string.get1(text, i) == 47) { replacement += '#'; } else { replacement += in_string.slice1(text, i); break; } } this.append(this.newError(range, 'Comments start with "#" instead of "//"').withFix(Skew.FixKind.SLASH_COMMENT, range, 'Replace "//" with "#"', replacement)); }; Skew.Log.prototype.syntaxErrorUnexpectedToken = function(token) { this.append(this.newError(token.range, 'Unexpected ' + Skew.in_TokenKind.toString(token.kind))); }; Skew.Log.prototype.syntaxErrorExpectedToken = function(range, found, expected) { var diagnostic = this.newError(range, 'Expected ' + Skew.in_TokenKind.toString(expected) + ' but found ' + Skew.in_TokenKind.toString(found)); if (found == Skew.TokenKind.SEMICOLON && expected == Skew.TokenKind.NEWLINE) { diagnostic.withFix(Skew.FixKind.EXTRA_SEMICOLON, range, 'Remove ";"', ''); } this.append(diagnostic); }; Skew.Log.prototype.syntaxErrorEmptyFunctionParentheses = function(range) { this.append(this.newError(range, 'Functions without arguments do not use parentheses').withFix(Skew.FixKind.EXTRA_DEF_PARENTHESES, range, 'Remove parentheses', '')); }; Skew.Log.prototype.syntaxErrorBadDeclarationInsideType = function(range) { this.append(this.newError(range, 'Cannot use this declaration here')); }; Skew.Log.prototype.syntaxErrorBadOperatorCustomization = function(range, kind, why) { this.append(this.newError(range, 'The ' + Skew.in_TokenKind.toString(kind) + ' operator is not customizable because ' + why)); }; Skew.Log.prototype.syntaxErrorVariableDeclarationNeedsVar = function(range, name) { this.append(this.newError(range, 'Declare variables using "var" and put the type after the variable name').withFix(Skew.FixKind.NEED_VAR, Skew.Range.span(range, name), 'Declare "' + name.toString() + '" correctly', 'var ' + name.toString() + ' ' + range.toString())); }; Skew.Log.prototype.syntaxErrorXMLClosingTagMismatch = function(range, found, expected, openingRange) { this.append(this.newError(range, 'Expected "' + expected + '" but found "' + found + '" in XML literal').withNote(openingRange, 'Attempted to match opening tag here')); }; Skew.Log.prototype.syntaxErrorOptionalArgument = function(range) { this.append(this.newError(range, "Optional arguments aren't supported yet")); }; Skew.Log._expectedCountText = function(singular, expected, found) { return 'Expected ' + Skew.PrettyPrint.plural1(expected, singular) + ' but found ' + Skew.PrettyPrint.plural1(found, singular); }; Skew.Log._formatArgumentTypes = function(types) { if (types == null) { return ''; } var names = []; for (var i = 0, list = types, count = list.length; i < count; i = i + 1 | 0) { var type = in_List.get(list, i); names.push(type.toString()); } return ' of type' + (types.length == 1 ? '' : 's') + ' ' + Skew.PrettyPrint.join(names, 'and'); }; Skew.Log.prototype.semanticWarningInliningFailed = function(range, name) { this.append(this.newWarning(range, 'Cannot inline function "' + name + '"')); }; Skew.Log.prototype.semanticWarningIdenticalOperands = function(range, operator) { this.append(this.newWarning(range, 'Both sides of "' + operator + '" are identical, is this a bug?')); }; Skew.Log.prototype.semanticWarningSuspiciousAssignmentLocation = function(range) { this.append(this.newWarning(range, 'Use of "=" here looks like a bug, did you mean to use "=="?')); }; Skew.Log.prototype.semanticWarningShiftByZero = function(range) { this.append(this.newWarning(range, "Shifting an integer by zero doesn't do anything, is this a bug?")); }; Skew.Log.prototype.semanticWarningUnusedExpression = function(range) { this.append(this.newWarning(range, 'Unused expression')); }; Skew.Log.prototype.semanticErrorXMLMissingAppend = function(range, type) { this.append(this.newError(range, 'Implement a function called "<>..." on type "' + type.toString() + '" to add support for child elements')); }; Skew.Log.prototype.semanticErrorComparisonOperatorNotInt = function(range) { this.append(this.newError(range, 'The comparison operator must have a return type of "int"')); }; Skew.Log.prototype.semanticErrorDuplicateSymbol = function(range, name, previous) { this.append(this.newError(range, '"' + name + '" is already declared').withNote(previous, 'The previous declaration is here')); }; Skew.Log.prototype.semanticErrorShadowedSymbol = function(range, name, previous) { this.append(this.newError(range, '"' + name + '" shadows a previous declaration').withNote(previous, 'The previous declaration is here')); }; Skew.Log.prototype.semanticErrorDuplicateTypeParameters = function(range, name, previous) { this.append(this.newError(range, '"' + name + '" already has type parameters').withNote(previous, 'Type parameters were previously declared here')); }; Skew.Log.prototype.semanticErrorDuplicateBaseType = function(range, name, previous) { this.append(this.newError(range, '"' + name + '" already has a base type').withNote(previous, 'The previous base type is here')); }; Skew.Log.prototype.semanticErrorCyclicDeclaration = function(range, name) { this.append(this.newError(range, 'Cyclic declaration of "' + name + '"')); }; Skew.Log.prototype.semanticErrorUndeclaredSymbol = function(range, name, correction, correctionRange) { var diagnostic = this.newError(range, '"' + name + '" is not declared' + (correction != null ? ', did you mean "' + correction + '"?' : '')); if (correction != null && correctionRange != null) { diagnostic.withNote(correctionRange, '"' + correction + '" is defined here').withFix(Skew.FixKind.SYMBOL_TYPO, range, 'Replace with "' + correction + '"', correction); } this.append(diagnostic); }; Skew.Log.prototype.semanticErrorUndeclaredSelfSymbol = function(range, name) { this.append(this.newError(range, '"' + name + '" is not declared (use "self" to refer to the object instance)').withFix(Skew.FixKind.SELF_VS_THIS, range, 'Replace "' + name + '" with "self"', 'self')); }; Skew.Log.prototype.semanticErrorUnknownMemberSymbol = function(range, name, type, correction, correctionRange) { var diagnostic = this.newError(range, '"' + name + '" is not declared on type "' + type.toString() + '"' + (correction != null ? ', did you mean "' + correction + '"?' : '')); if (correction != null && correctionRange != null) { diagnostic.withNote(correctionRange, '"' + correction + '" is defined here').withFix(Skew.FixKind.SYMBOL_TYPO, range, 'Replace with "' + correction + '"', correction); } this.append(diagnostic); }; Skew.Log.prototype.semanticErrorVarMissingType = function(range, name) { this.append(this.newError(range, 'Unable to determine the type of "' + name + '"')); }; Skew.Log.prototype.semanticErrorVarMissingValue = function(range, name) { this.append(this.newError(range, 'The implicitly typed variable "' + name + '" must be initialized')); }; Skew.Log.prototype.semanticErrorConstMissingValue = function(range, name) { this.append(this.newError(range, 'The constant "' + name + '" must be initialized')); }; Skew.Log.prototype.semanticErrorInvalidCall = function(range, type) { this.append(this.newError(range, 'Cannot call value of type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorCannotParameterize = function(range, type) { this.append(this.newError(range, 'Cannot parameterize "' + type.toString() + '"' + (type.isParameterized() ? ' because it is already parameterized' : ' because it has no type parameters'))); }; Skew.Log.prototype.semanticErrorParameterCount = function(range, expected, found) { this.append(this.newError(range, Skew.Log._expectedCountText('type parameter', expected, found))); }; Skew.Log.prototype.semanticErrorArgumentCount = function(range, expected, found, name, $function) { this.append(this.newError(range, Skew.Log._expectedCountText('argument', expected, found) + (name != null ? ' when calling "' + name + '"' : '')).withNote($function, 'The function declaration is here')); }; Skew.Log.prototype.semanticErrorGetterRequiresWrap = function(range, name, $function) { this.append(this.newError(range, 'Wrap calls to the function "' + name + '" in parentheses to call the returned lambda').withNote($function, 'The function declaration is here')); }; Skew.Log.prototype.semanticErrorGetterCalledTwice = function(range, name, $function) { var diagnostic = this.newError(range, 'Cannot call the value returned from the function "' + name + '" (this function was called automatically because it takes no arguments)').withNote($function, 'The function declaration is here'); if (range.toString() == '()') { diagnostic.withFix(Skew.FixKind.EXTRA_CALL_PARENTHESES, range, 'Remove the unnecessary "()"', ''); } this.append(diagnostic); }; Skew.Log.prototype.semanticErrorUseOfVoidFunction = function(range, name, $function) { this.append(this.newError(range, 'The function "' + name + '" does not return a value').withNote($function, 'The function declaration is here')); }; Skew.Log.prototype.semanticErrorUseOfVoidLambda = function(range) { this.append(this.newError(range, 'This call does not return a value')); }; Skew.Log.prototype.semanticErrorBadImplicitVariableType = function(range, type) { this.append(this.newError(range, 'Implicitly typed variables cannot be of type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorNoDefaultValue = function(range, type) { this.append(this.newError(range, 'Cannot construct a default value of type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorMemberUnexpectedGlobal = function(range, name) { this.append(this.newError(range, 'Cannot access global member "' + name + '" from an instance context')); }; Skew.Log.prototype.semanticErrorMemberUnexpectedInstance = function(range, name) { this.append(this.newError(range, 'Cannot access instance member "' + name + '" from a global context')); }; Skew.Log.prototype.semanticErrorMemberUnexpectedTypeParameter = function(range, name) { this.append(this.newError(range, 'Cannot access type parameter "' + name + '" here')); }; Skew.Log.prototype.semanticErrorConstructorReturnType = function(range) { this.append(this.newError(range, 'Constructors cannot have a return type').withFix(Skew.FixKind.NEW_RETURN_TYPE, range != null ? range.rangeIncludingLeftWhitespace() : null, 'Remove the return type', '')); }; Skew.Log.prototype.semanticErrorNoMatchingOverload = function(range, name, count, types) { this.append(this.newError(range, 'No overload of "' + name + '" was found that takes ' + Skew.PrettyPrint.plural1(count, 'argument') + Skew.Log._formatArgumentTypes(types))); }; Skew.Log.prototype.semanticErrorAmbiguousOverload = function(range, name, count, types) { this.append(this.newError(range, 'Multiple matching overloads of "' + name + '" were found that can take ' + Skew.PrettyPrint.plural1(count, 'argument') + Skew.Log._formatArgumentTypes(types))); }; Skew.Log.prototype.semanticErrorUnexpectedExpression = function(range, type) { this.append(this.newError(range, 'Unexpected expression of type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorUnexpectedType = function(range, type) { this.append(this.newError(range, 'Unexpected type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorIncompatibleTypes = function(range, from, to, isCastAllowed) { this.append(this.newError(range, 'Cannot convert from type "' + from.toString() + '" to type "' + to.toString() + '"' + (isCastAllowed ? ' without a cast' : ''))); }; Skew.Log.prototype.semanticErrorInvalidDefine1 = function(range, value, type, name) { this.append(this.newError(range, 'Cannot convert "' + value + '" to type "' + type.toString() + '" for variable "' + name + '"')); }; Skew.Log.prototype.semanticWarningExtraCast = function(range, from, to) { this.append(this.newWarning(range, 'Unnecessary cast from type "' + from.toString() + '" to type "' + to.toString() + '"').withFix(Skew.FixKind.EXTRA_CAST, range != null ? range.rangeIncludingLeftWhitespace() : null, 'Remove the cast', '')); }; Skew.Log.prototype.semanticWarningExtraTypeCheck = function(range, from, to) { this.append(this.newWarning(range, 'Unnecessary type check, type "' + from.toString() + '" is always type "' + to.toString() + '"')); }; Skew.Log.prototype.semanticWarningBadTypeCheck = function(range, type) { this.append(this.newError(range, 'Cannot check against interface type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorWrongArgumentCount = function(range, name, count) { this.append(this.newError(range, 'Expected "' + name + '" to take ' + Skew.PrettyPrint.plural1(count, 'argument'))); }; Skew.Log.prototype.semanticErrorWrongArgumentCountRange = function(range, name, values) { assert(!(values.length == 0)); var first = in_List.first(values); var count = values.length; if (count == 1) { this.semanticErrorWrongArgumentCount(range, name, first); } else { var counts = []; var min = first; var max = first; var text = null; for (var i = 0, list = values, count1 = list.length; i < count1; i = i + 1 | 0) { var value = in_List.get(list, i); min = Math.min(min, value); max = Math.max(max, value); counts.push(value.toString()); } // Assuming values are unique, this means all values form a continuous range if (((max - min | 0) + 1 | 0) == count) { if (min == 0) { text = 'Expected "' + name + '" to take at most ' + Skew.PrettyPrint.plural1(max, 'argument'); } else { text = 'Expected "' + name + '" to take between ' + min.toString() + ' and ' + max.toString() + ' arguments'; } } // Otherwise, the values are disjoint else { text = 'Expected "' + name + '" to take either ' + Skew.PrettyPrint.join(counts, 'or') + ' arguments'; } this.append(this.newError(range, text)); } }; Skew.Log.prototype.semanticErrorExpectedList = function(range, name, type) { this.append(this.newError(range, 'Expected argument "' + name + '" to be of type "List" instead of type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorUnexpectedReturnValue = function(range) { this.append(this.newError(range, 'Cannot return a value inside a function without a return type')); }; Skew.Log.prototype.semanticErrorBadReturnType = function(range, type) { this.append(this.newError(range, 'Cannot create a function with a return type of "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorVoidReturnType = function(range) { this.append(this.newError(range, 'There is no explicit "void" return type (to indicate that there\'s nothing to return, just don\'t put a return type)').withFix(Skew.FixKind.VOID_RETURN, range != null ? range.rangeIncludingLeftWhitespace() : null, 'Remove "void"', '')); }; Skew.Log.prototype.semanticErrorExpectedReturnValue = function(range, type) { this.append(this.newError(range, 'Must return a value of type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorMissingReturn = function(range, name, type) { this.append(this.newError(range, 'All control paths for "' + name + '" must return a value of type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorBadStorage = function(range) { this.append(this.newError(range, 'Cannot store to this location')); }; Skew.Log.prototype.semanticErrorStorageToConstSymbol = function(range, name) { this.append(this.newError(range, 'Cannot store to constant symbol "' + name + '"')); }; Skew.Log.prototype.semanticErrorAccessViolation = function(range, name) { this.append(this.newError(range, 'Cannot access protected symbol "' + name + '" here')); }; Skew.Log.prototype.semanticWarningDeprecatedUsage = function(range, name) { this.append(this.newWarning(range, 'Use of deprecated symbol "' + name + '"')); }; Skew.Log.prototype.semanticErrorUnparameterizedType = function(range, type) { this.append(this.newError(range, 'Cannot use unparameterized type "' + type.toString() + '" here')); }; Skew.Log.prototype.semanticErrorParameterizedType = function(range, type) { this.append(this.newError(range, 'Cannot use parameterized type "' + type.toString() + '" here')); }; Skew.Log.prototype.semanticErrorNoCommonType = function(range, left, right) { this.append(this.newError(range, 'No common type for "' + left.toString() + '" and "' + right.toString() + '"')); }; Skew.Log.prototype.semanticErrorInvalidAnnotation = function(range, annotation, name) { this.append(this.newError(range, 'Cannot use the annotation "' + annotation + '" on "' + name + '"')); }; Skew.Log.prototype.semanticWarningDuplicateAnnotation = function(range, annotation, name) { this.append(this.newWarning(range, 'Duplicate annotation "' + annotation + '" on "' + name + '"')); }; Skew.Log.prototype.semanticWarningRedundantAnnotation = function(range, annotation, name, parent) { this.append(this.newWarning(range, 'Redundant annotation "' + annotation + '" on "' + name + '" is already inherited from type "' + parent + '"')); }; Skew.Log.prototype.semanticErrorBadForValue = function(range, type) { this.append(this.newError(range, 'Cannot iterate over type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticWarningEmptyRange = function(range) { this.append(this.newWarning(range, 'This range is empty')); }; Skew.Log.prototype.semanticErrorMissingDotContext = function(range, name) { this.append(this.newError(range, 'Cannot access "' + name + '" without type context')); }; Skew.Log.prototype.semanticErrorInitializerTypeInferenceFailed = function(range) { this.append(this.newError(range, 'Cannot infer a type for this literal')); }; Skew.Log.prototype.semanticErrorInitializerRecursiveExpansion = function(range, newRange) { this.append(this.newError(range, 'Attempting to resolve this literal led to recursive expansion').withNote(newRange, 'The constructor that was called recursively is here')); }; Skew.Log.prototype.semanticErrorXMLCannotConstruct = function(range, type) { this.append(this.newError(range, 'Cannot construct type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorDuplicateOverload = function(range, name, previous) { this.append(this.newError(range, 'Duplicate overloaded function "' + name + '"').withNote(previous, 'The previous declaration is here')); }; Skew.Log.prototype.semanticErrorInvalidExtends = function(range, type) { this.append(this.newError(range, 'Cannot extend type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorInvalidImplements = function(range, type) { this.append(this.newError(range, 'Cannot implement type "' + type.toString() + '"')); }; Skew.Log.prototype.semanticErrorDuplicateImplements = function(range, type, previous) { this.append(this.newError(range, 'Duplicate implemented type "' + type.toString() + '"').withNote(previous, 'The first occurrence is here')); }; Skew.Log.prototype.semanticErrorBadInterfaceImplementation = function(range, classType, interfaceType, name, reason) { this.append(this.newError(range, 'Type "' + classType.toString() + '" is missing an implementation of function "' + name + '" from interface "' + interfaceType.toString() + '"').withNote(reason, 'The function declaration is here')); }; Skew.Log.prototype.semanticErrorBadInterfaceImplementationReturnType = function(range, name, found, expected, interfaceType, reason) { this.append(this.newError(range, found != null && expected != null ? 'Function "' + name + '" has unexpected return type "' + found.toString() + '", expected return type "' + expected.toString() + '" ' + ('to match the function with the same name and argument types from interface "' + interfaceType.toString() + '"') : 'Expected the return type of function "' + name + '" to match the function with the same name and argument types from interface "' + interfaceType.toString() + '"').withNote(reason, 'The function declaration is here')); }; Skew.Log.prototype.semanticErrorBadOverride = function(range, name, base, overridden) { this.append(this.newError(range, '"' + name + '" overrides another declaration with the same name in base type "' + base.toString() + '"').withNote(overridden, 'The overridden declaration is here')); }; Skew.Log.prototype.semanticErrorBadOverrideReturnType = function(range, name, base, overridden) { this.append(this.newError(range, '"' + name + '" overrides another function with the same name and argument types but a different return type in base type "' + base.toString() + '"').withNote(overridden, 'The overridden function is here')); }; Skew.Log.prototype.semanticErrorModifierMissingOverride = function(range, name, overridden) { this.append(this.newError(range, '"' + name + '" overrides another symbol with the same name but is declared using "def" instead of "over"').withNote(overridden, 'The overridden declaration is here')); }; Skew.Log.prototype.semanticErrorModifierUnusedOverride = function(range, name) { this.append(this.newError(range, '"' + name + '" is declared using "over" instead of "def" but does not override anything')); }; Skew.Log.prototype.semanticErrorBadSuper = function(range) { this.append(this.newError(range, 'Cannot use "super" here')); }; Skew.Log.prototype.semanticErrorBadJump = function(range, name) { this.append(this.newError(range, 'Cannot use "' + name + '" outside a loop')); }; Skew.Log.prototype.semanticErrorMustCallFunction = function(range, name, lower, upper) { this.append(this.newError(range, lower == upper ? 'The function "' + name + '" takes ' + Skew.PrettyPrint.plural1(lower, 'argument') + ' and must be called' : 'The function "' + name + '" takes between ' + lower.toString() + ' and ' + upper.toString() + ' arguments and must be called')); }; Skew.Log.prototype.semanticErrorDuplicateEntryPoint = function(range, previous) { this.append(this.newError(range, 'Multiple entry points are declared').withNote(previous, 'The first entry point is here')); }; Skew.Log.prototype.semanticErrorInvalidEntryPointArguments = function(range, name) { this.append(this.newError(range, 'Entry point "' + name + '" must take either no arguments or one argument of type "List"')); }; Skew.Log.prototype.semanticErrorInvalidEntryPointReturnType = function(range, name) { this.append(this.newError(range, 'Entry point "' + name + '" must return either nothing or a value of type "int"')); }; Skew.Log.prototype.semanticErrorInvalidDefine2 = function(range, name) { this.append(this.newError(range, 'Could not find a variable named "' + name + '" to override')); }; Skew.Log.prototype.semanticErrorExpectedConstant = function(range) { this.append(this.newError(range, 'This value must be a compile-time constant')); }; Skew.Log.prototype.semanticWarningUnreadLocalVariable = function(range, name) { this.append(this.newWarning(range, 'Local variable "' + name + '" is never read')); }; Skew.Log.prototype.semanticErrorAbstractNew = function(range, type, reason, name) { this.append(this.newError(range, 'Cannot construct abstract type "' + type.toString() + '"').withNote(reason, 'The type "' + type.toString() + '" is abstract due to member "' + name + '"')); }; Skew.Log.prototype.semanticErrorUnimplementedFunction = function(range, name) { this.append(this.newError(range, 'Non-imported function "' + name + '" is missing an implementation (use the "@import" annotation if it\'s implemented externally)')); }; Skew.Log.prototype.semanticErrorDefaultCaseNotLast = function(range) { this.append(this.newError(range, 'The default case in a switch statement must come last')); }; Skew.Log.prototype.semanticErrorForLoopDifferentType = function(range, name, found, expected) { this.append(this.newError(range, 'Expected loop variable "' + name + '" to be of type "' + expected.toString() + '" instead of type "' + found.toString() + '"')); }; Skew.Log.prototype.semanticErrorDuplicateCase = function(range, previous) { this.append(this.newError(range, 'Duplicate case value').withNote(previous, 'The first occurrence is here')); }; Skew.Log.prototype.semanticErrorMissingWrappedType = function(range, name) { this.append(this.newError(range, 'Missing base type for wrapped type "' + name + '"')); }; Skew.Log.prototype.semanticErrorDuplicateRename = function(range, name, optionA, optionB) { this.append(this.newError(range, 'Cannot rename "' + name + '" to both "' + optionA + '" and "' + optionB + '"')); }; Skew.Log.prototype.semanticErrorMissingSuper = function(range) { this.append(this.newError(range, 'Constructors for derived types must start with a call to "super"')); }; Skew.Log.prototype.semanticErrorTooManyFlags = function(range, name) { this.append(this.newError(range, 'The type "' + name + '" cannot have more than 32 flags')); }; Skew.Options = {}; Skew.Options.Type = { BOOL: 0, INT: 1, STRING: 2, STRING_LIST: 3 }; Skew.Options.Data = function(parser, type, option, name, description) { this.parser = parser; this.type = type; this.option = option; this.name = name; this.description = description; }; Skew.Options.Data.prototype.nameText = function() { return this.name + (this.type == Skew.Options.Type.BOOL ? '' : this.type == Skew.Options.Type.STRING_LIST ? ':___' : '=___'); }; Skew.Options.Data.prototype.aliases = function(names) { for (var i = 0, list = names, count = list.length; i < count; i = i + 1 | 0) { var name = in_List.get(list, i); in_StringMap.set(this.parser.map, name, this); } return this; }; Skew.Options.Parser = function() { this.options = []; this.map = new Map(); this.optionalArguments = new Map(); this.normalArguments = []; this.source = null; }; Skew.Options.Parser.prototype.define = function(type, option, name, description) { var data = new Skew.Options.Data(this, type, option, name, description); in_StringMap.set(this.map, name, data); this.options.push(data); return data; }; Skew.Options.Parser.prototype.nodeForOption = function(option) { return in_IntMap.get(this.optionalArguments, option, null); }; Skew.Options.Parser.prototype.boolForOption = function(option, defaultValue) { var node = this.nodeForOption(option); return node != null ? Skew.in_Content.asBool(node.content) : defaultValue; }; Skew.Options.Parser.prototype.intForOption = function(option, defaultValue) { var node = this.nodeForOption(option); return node != null ? Skew.in_Content.asInt(node.content) : defaultValue; }; Skew.Options.Parser.prototype.rangeForOption = function(option) { var node = this.nodeForOption(option); return node != null ? node.range : null; }; Skew.Options.Parser.prototype.rangeListForOption = function(option) { var node = this.nodeForOption(option); var ranges = []; if (node != null) { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { ranges.push(child.range); } } return ranges; }; Skew.Options.Parser.prototype.parse = function(log, $arguments) { this.source = new Skew.Source('', ''); var ranges = []; // Create a source for the arguments to work with the log system. The // trailing space is needed to be able to point to the character after // the last argument without wrapping onto the next line. for (var i1 = 0, list = $arguments, count = list.length; i1 < count; i1 = i1 + 1 | 0) { var argument = in_List.get(list, i1); var needsQuotes = argument.indexOf(' ') != -1; var start = this.source.contents.length + (needsQuotes | 0) | 0; ranges.push(new Skew.Range(this.source, start, start + argument.length | 0)); this.source.contents += needsQuotes ? "'" + argument + "' " : argument + ' '; } // Parse each argument for (var i = 0, count1 = $arguments.length; i < count1; i = i + 1 | 0) { var argument1 = in_List.get($arguments, i); var range = in_List.get(ranges, i); // Track all normal arguments separately if (argument1 == '' || in_string.get1(argument1, 0) != 45 && !this.map.has(argument1)) { this.normalArguments.push(range); continue; } // Parse a flag var equals = argument1.indexOf('='); var colon = argument1.indexOf(':'); var separator = equals >= 0 && (colon < 0 || equals < colon) ? equals : colon; var name = separator >= 0 ? in_string.slice2(argument1, 0, separator) : argument1; var data = in_StringMap.get(this.map, name, null); // Check that the flag exists if (data == null) { log.commandLineErrorBadFlag(range.fromStart(name.length), name); continue; } // Validate the flag data var text = in_string.slice1(argument1, separator + 1 | 0); var separatorRange = separator < 0 ? null : range.slice(separator, separator + 1 | 0); var textRange = range.fromEnd(text.length); switch (data.type) { // Parse a single boolean value case Skew.Options.Type.BOOL: { if (separator < 0) { text = 'true'; } else if (in_string.get1(argument1, separator) != 61) { log.commandLineErrorExpectedToken(separatorRange, '=', in_string.get(argument1, separator), argument1); continue; } else if (text != 'true' && text != 'false') { log.commandLineErrorNonBooleanValue(textRange, text, argument1); continue; } if (this.optionalArguments.has(data.option)) { log.commandLineWarningDuplicateFlagValue(textRange, name, in_IntMap.get1(this.optionalArguments, data.option).range); } in_IntMap.set(this.optionalArguments, data.option, new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.BoolContent(text == 'true')).withRange(textRange)); break; } // Parse a single int value case Skew.Options.Type.INT: { if (separator < 0) { log.commandLineErrorMissingValue(textRange, data.nameText()); } else if (in_string.get1(argument1, separator) != 61) { log.commandLineErrorExpectedToken(separatorRange, '=', in_string.get(argument1, separator), argument1); } else { var box = Skew.Parsing.parseIntLiteral(log, textRange); if (box == null) { log.commandLineErrorNonIntegerValue(textRange, text, argument1); } else { if (this.optionalArguments.has(data.option)) { log.commandLineWarningDuplicateFlagValue(textRange, name, in_IntMap.get1(this.optionalArguments, data.option).range); } in_IntMap.set(this.optionalArguments, data.option, new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.IntContent(box.value)).withRange(textRange)); } } break; } // Parse a single string value case Skew.Options.Type.STRING: { if (separator < 0) { log.commandLineErrorMissingValue(textRange, data.nameText()); } else if (in_string.get1(argument1, separator) != 61) { log.commandLineErrorExpectedToken(separatorRange, '=', in_string.get(argument1, separator), argument1); } else { if (this.optionalArguments.has(data.option)) { log.commandLineWarningDuplicateFlagValue(textRange, name, in_IntMap.get1(this.optionalArguments, data.option).range); } in_IntMap.set(this.optionalArguments, data.option, new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.StringContent(text)).withRange(textRange)); } break; } // Parse an item in a list of string values case Skew.Options.Type.STRING_LIST: { if (separator < 0) { log.commandLineErrorMissingValue(textRange, data.nameText()); } else if (in_string.get1(argument1, separator) != 58) { log.commandLineErrorExpectedToken(separatorRange, ':', in_string.get(argument1, separator), argument1); } else { var node = null; if (this.optionalArguments.has(data.option)) { node = in_IntMap.get1(this.optionalArguments, data.option); } else { node = Skew.Node.createInitializer(Skew.NodeKind.INITIALIZER_LIST); in_IntMap.set(this.optionalArguments, data.option, node); } node.appendChild(new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.StringContent(text)).withRange(textRange)); } break; } } } }; Skew.Options.Parser.prototype.usageText = function(wrapWidth) { var text = ''; var columnWidth = 0; // Figure out the column width for (var i = 0, list = this.options, count = list.length; i < count; i = i + 1 | 0) { var option = in_List.get(list, i); var width = option.nameText().length + 4 | 0; if (columnWidth < width) { columnWidth = width; } } // Format the options var columnText = in_string.repeat(' ', columnWidth); for (var i2 = 0, list2 = this.options, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var option1 = in_List.get(list2, i2); var nameText = option1.nameText(); var isFirst = true; text += '\n ' + nameText + in_string.repeat(' ', (columnWidth - nameText.length | 0) - 2 | 0); for (var i1 = 0, list1 = Skew.PrettyPrint.wrapWords(option1.description, wrapWidth - columnWidth | 0), count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var line = in_List.get(list1, i1); text += (isFirst ? '' : columnText) + line + '\n'; isFirst = false; } } return text + '\n'; }; Skew.Option = { DEFINE: 0, FIX_ALL: 1, FOLD_CONSTANTS: 2, GLOBALIZE_FUNCTIONS: 4, HELP: 5, IGNORED_COMMENT_WARNING: 6, INLINE_FUNCTIONS: 7, JS_MANGLE: 8, JS_MINIFY: 9, JS_SOURCE_MAP: 10, MESSAGE_LIMIT: 11, NO_OUTPUT: 12, OUTPUT_DIRECTORY: 13, OUTPUT_FILE: 14, RELEASE: 15, TARGET: 16, VERBOSE: 17, VERSION: 18, WARNINGS_ARE_ERRORS: 19 }; Skew.DiagnosticKind = { ERROR: 0, WARNING: 1 }; Skew.Fix = function(kind, range, description, replacement) { this.kind = kind; this.range = range; this.description = description; this.replacement = replacement; }; Skew.FixKind = { ARRAY_SYNTAX: 0, CASE_BRACES: 1, CASE_COMMA: 2, EXTENDS_IMPLEMENTS: 3, EXTRA_CALL_PARENTHESES: 4, EXTRA_CAST: 5, EXTRA_COLON: 6, EXTRA_COMMA: 7, EXTRA_DEF_PARENTHESES: 8, EXTRA_SEMICOLON: 9, FOR_LOOP_VAR: 10, MISSING_DEF: 11, MISSING_VAR: 12, NEED_VAR: 13, NEW_OPERATOR: 14, NEW_RETURN_TYPE: 15, OCTAL_ADD_PREFIX: 16, OCTAL_REMOVE_ZEROS: 17, OPERATOR_TYPO: 18, SELF_VS_THIS: 19, SINGLE_QUOTES: 20, SLASH_COMMENT: 21, SYMBOL_TYPO: 22, UNNECESSARY_PARENTHESES: 23, VOID_RETURN: 24 }; Skew.Diagnostic = function(kind, range, text, wasWarning) { this.kind = kind; this.range = range; this.text = text; this.wasWarning = wasWarning; this.noteRange = null; this.noteText = ''; this.fixes = null; }; Skew.Diagnostic.prototype.withFix = function(kind, range, description, replacement) { if (range != null && replacement != null) { (this.fixes != null ? this.fixes : this.fixes = []).push(new Skew.Fix(kind, range, description, replacement)); } return this; }; Skew.Diagnostic.prototype.withNote = function(range, text) { if (range != null) { this.noteRange = range; this.noteText = text; } return this; }; Skew.Comment = function(range, lines, hasGapBelow, isTrailing) { this.range = range; this.lines = lines; this.hasGapBelow = hasGapBelow; this.isTrailing = isTrailing; }; Skew.Comment.concat = function(left, right) { if (left == null) { return right; } if (right == null) { return left; } in_List.append1(left, right); return left; }; Skew.Comment.lastTrailingComment = function(comments) { if (comments != null) { var last = in_List.last(comments); if (last.isTrailing && last.lines.length == 1) { return last; } } return null; }; Skew.Comment.withoutLastTrailingComment = function(comments) { if (comments != null) { var last = in_List.last(comments); if (last.isTrailing && last.lines.length == 1) { return in_List.slice2(comments, 0, comments.length - 1 | 0); } } return comments; }; Skew.Comment.firstTrailingComment = function(comments) { if (comments != null) { var first = in_List.first(comments); if (first.isTrailing && first.lines.length == 1) { return first; } } return null; }; Skew.Comment.withoutFirstTrailingComment = function(comments) { if (comments != null) { var first = in_List.first(comments); if (first.isTrailing && first.lines.length == 1) { return in_List.slice1(comments, 1); } } return comments; }; Skew.Token = function(range, kind, comments) { this.range = range; this.kind = kind; this.comments = comments; }; Skew.TokenKind = { // Type parameters are surrounded by "<" and ">" PARAMETER_LIST_END: 0, PARAMETER_LIST_START: 1, // XML entities are surrounded by "<" and ">" (or "" but those are defined by flex) XML_END: 2, XML_START: 3, // String interpolation looks like "start\( 1 )continue( 2 )end" STRING_INTERPOLATION_CONTINUE: 4, STRING_INTERPOLATION_END: 5, STRING_INTERPOLATION_START: 6, ANNOTATION: 7, ARROW: 8, AS: 9, ASSIGN: 10, ASSIGN_BITWISE_AND: 11, ASSIGN_BITWISE_OR: 12, ASSIGN_BITWISE_XOR: 13, ASSIGN_DIVIDE: 14, ASSIGN_INDEX: 15, ASSIGN_MINUS: 16, ASSIGN_MODULUS: 17, ASSIGN_MULTIPLY: 18, ASSIGN_NULL: 19, ASSIGN_PLUS: 20, ASSIGN_POWER: 21, ASSIGN_REMAINDER: 22, ASSIGN_SHIFT_LEFT: 23, ASSIGN_SHIFT_RIGHT: 24, ASSIGN_UNSIGNED_SHIFT_RIGHT: 25, BITWISE_AND: 26, BITWISE_OR: 27, BITWISE_XOR: 28, BREAK: 29, CASE: 30, CATCH: 31, CHARACTER: 32, COLON: 33, COMMA: 34, COMMENT: 35, COMMENT_ERROR: 36, COMPARE: 37, CONST: 38, CONTINUE: 39, DECREMENT: 40, DEFAULT: 41, DIVIDE: 42, DOT: 43, DOT_DOT: 44, DOUBLE: 45, DOUBLE_COLON: 46, DYNAMIC: 47, ELSE: 48, END_OF_FILE: 49, EQUAL: 50, EQUAL_ERROR: 51, ERROR: 52, FALSE: 53, FINALLY: 54, FOR: 55, GREATER_THAN: 56, GREATER_THAN_OR_EQUAL: 57, IDENTIFIER: 58, IF: 59, IN: 60, INCREMENT: 61, INDEX: 62, INT: 63, INT_BINARY: 64, INT_HEX: 65, INT_OCTAL: 66, IS: 67, LEFT_BRACE: 68, LEFT_BRACKET: 69, LEFT_PARENTHESIS: 70, LESS_THAN: 71, LESS_THAN_OR_EQUAL: 72, LIST: 73, LIST_NEW: 74, LOGICAL_AND: 75, LOGICAL_OR: 76, MINUS: 77, MODULUS: 78, MULTIPLY: 79, NEWLINE: 80, NOT: 81, NOT_EQUAL: 82, NOT_EQUAL_ERROR: 83, NULL: 84, NULL_DOT: 85, NULL_JOIN: 86, PLUS: 87, POWER: 88, QUESTION_MARK: 89, REMAINDER: 90, RETURN: 91, RIGHT_BRACE: 92, RIGHT_BRACKET: 93, RIGHT_PARENTHESIS: 94, SEMICOLON: 95, SET: 96, SET_NEW: 97, SHIFT_LEFT: 98, SHIFT_RIGHT: 99, STRING: 100, SUPER: 101, SWITCH: 102, THROW: 103, TILDE: 104, TRUE: 105, TRY: 106, UNSIGNED_SHIFT_RIGHT: 107, VAR: 108, WHILE: 109, WHITESPACE: 110, XML_CHILD: 111, XML_END_EMPTY: 112, XML_START_CLOSE: 113, YY_INVALID_ACTION: 114 }; Skew.FormattedRange = function(line, range) { this.line = line; this.range = range; }; Skew.Range = function(source, start, end) { this.source = source; this.start = start; this.end = end; }; Skew.Range.prototype.toString = function() { return in_string.slice2(this.source.contents, this.start, this.end); }; Skew.Range.prototype.locationString = function() { var location = this.source.indexToLineColumn(this.start); return this.source.name + ':' + (location.line + 1 | 0).toString() + ':' + (location.column + 1 | 0).toString(); }; Skew.Range.prototype.touches = function(index) { return this.start <= index && index <= this.end; }; Skew.Range.prototype.format = function(maxLength) { assert(this.source != null); var start = this.source.indexToLineColumn(this.start); var end = this.source.indexToLineColumn(this.end); var line = this.source.contentsOfLine(start.line); var startColumn = start.column; var endColumn = end.line == start.line ? end.column : line.length; // Use a unicode iterator to count the actual code points so they don't get sliced through the middle var iterator = Unicode.StringIterator.INSTANCE.reset(line, 0); var codePoints = []; var a = 0; var b = 0; // Expand tabs into spaces while (true) { if (iterator.index == startColumn) { a = codePoints.length; } if (iterator.index == endColumn) { b = codePoints.length; } var codePoint = iterator.nextCodePoint(); if (codePoint < 0) { break; } if (codePoint == 9) { for (var space = 0, count1 = 8 - codePoints.length % 8 | 0; space < count1; space = space + 1 | 0) { codePoints.push(32); } } else { codePoints.push(codePoint); } } // Ensure the line length doesn't exceed maxLength var count = codePoints.length; if (maxLength > 0 && count > maxLength) { var centeredWidth = Math.min(b - a | 0, maxLength / 2 | 0); var centeredStart = Math.max((maxLength - centeredWidth | 0) / 2 | 0, 3); // Left aligned if (a < centeredStart) { line = in_string.fromCodePoints(in_List.slice2(codePoints, 0, maxLength - 3 | 0)) + '...'; if (b > (maxLength - 3 | 0)) { b = maxLength - 3 | 0; } } // Right aligned else if ((count - a | 0) < (maxLength - centeredStart | 0)) { var offset = count - maxLength | 0; line = '...' + in_string.fromCodePoints(in_List.slice2(codePoints, offset + 3 | 0, count)); a = a - offset | 0; b = b - offset | 0; } // Center aligned else { var offset1 = a - centeredStart | 0; line = '...' + in_string.fromCodePoints(in_List.slice2(codePoints, offset1 + 3 | 0, (offset1 + maxLength | 0) - 3 | 0)) + '...'; a = a - offset1 | 0; b = b - offset1 | 0; if (b > (maxLength - 3 | 0)) { b = maxLength - 3 | 0; } } } else { line = in_string.fromCodePoints(codePoints); } return new Skew.FormattedRange(line, in_string.repeat(' ', a) + ((b - a | 0) < 2 ? '^' : in_string.repeat('~', b - a | 0))); }; Skew.Range.prototype.fromStart = function(count) { assert(count >= 0 && count <= (this.end - this.start | 0)); return new Skew.Range(this.source, this.start, this.start + count | 0); }; Skew.Range.prototype.fromEnd = function(count) { assert(count >= 0 && count <= (this.end - this.start | 0)); return new Skew.Range(this.source, this.end - count | 0, this.end); }; Skew.Range.prototype.slice = function(offsetStart, offsetEnd) { assert(offsetStart >= 0 && offsetStart <= offsetEnd && offsetEnd <= (this.end - this.start | 0)); return new Skew.Range(this.source, this.start + offsetStart | 0, this.start + offsetEnd | 0); }; Skew.Range.prototype.rangeIncludingLeftWhitespace = function() { var index = this.start; var contents = this.source.contents; while (index > 0) { var c = in_string.get1(contents, index - 1 | 0); if (c != 32 && c != 9) { break; } index = index - 1 | 0; } return new Skew.Range(this.source, index, this.end); }; Skew.Range.prototype.rangeIncludingRightWhitespace = function() { var index = this.end; var contents = this.source.contents; while (index < contents.length) { var c = in_string.get1(contents, index); if (c != 32 && c != 9) { break; } index = index + 1 | 0; } return new Skew.Range(this.source, this.start, index); }; Skew.Range.span = function(start, end) { assert(start.source == end.source); assert(start.start <= end.end); return new Skew.Range(start.source, start.start, end.end); }; Skew.Parsing = {}; // Parser recovery is done by skipping to the next closing token after an error Skew.Parsing.scanForToken = function(context, kind) { if (context.expect(kind)) { return; } // Scan forward for the token while (!context.peek1(Skew.TokenKind.END_OF_FILE)) { if (context.eat(kind)) { return; } switch (context.current().kind) { // Stop at the next closing token case Skew.TokenKind.RIGHT_PARENTHESIS: case Skew.TokenKind.RIGHT_BRACKET: case Skew.TokenKind.RIGHT_BRACE: { return; } // Optionally recover parsing before the next statement if it's unambiguous case Skew.TokenKind.BREAK: case Skew.TokenKind.CATCH: case Skew.TokenKind.CONST: case Skew.TokenKind.CONTINUE: case Skew.TokenKind.ELSE: case Skew.TokenKind.FINALLY: case Skew.TokenKind.FOR: case Skew.TokenKind.IF: case Skew.TokenKind.RETURN: case Skew.TokenKind.TRY: case Skew.TokenKind.VAR: case Skew.TokenKind.WHILE: { return; } } context.next(); } }; Skew.Parsing.parseIntLiteral = function(log, range) { var text = range.toString(); // Parse negative signs for use with the "--define" flag var isNegative = text.startsWith('-'); var start = isNegative | 0; var count = text.length; var doubleValue = 0; var intValue = 0; var base = 10; // Parse the base if ((start + 2 | 0) < count && in_string.get1(text, start) == 48) { var c = in_string.get1(text, start + 1 | 0); if (c == 98) { base = 2; start = start + 2 | 0; } else if (c == 111) { base = 8; start = start + 2 | 0; } else if (c == 120) { base = 16; start = start + 2 | 0; } } // There must be numbers after the base if (start == count) { return null; } // Special-case hexadecimal since it's more complex if (base == 16) { for (var i = start, count1 = count; i < count1; i = i + 1 | 0) { var c1 = in_string.get1(text, i); if ((c1 < 48 || c1 > 57) && (c1 < 65 || c1 > 70) && (c1 < 97 || c1 > 102)) { return null; } var delta = c1 - (c1 <= 57 ? 48 : c1 <= 70 ? 65 - 10 | 0 : 97 - 10 | 0) | 0; doubleValue = doubleValue * 16 + delta; intValue = __imul(intValue, 16) + delta | 0; } } // All other bases are zero-relative else { for (var i1 = start, count2 = count; i1 < count2; i1 = i1 + 1 | 0) { var delta1 = in_string.get1(text, i1) - 48 | 0; if (delta1 < 0 || delta1 >= base) { return null; } doubleValue = doubleValue * base + delta1; intValue = __imul(intValue, base) + delta1 | 0; } } // Integer literals are only an error if they are outside both the signed and // unsigned 32-bit integer ranges. Integers here are 32-bit signed integers // but it can be convenient to write literals using unsigned notation and // have the compiler do the wrapping (for example, all Mach-O files use a // magic number of 0xFEEDFACE). if (doubleValue < -2147483648 || doubleValue > 4294967295) { log.syntaxErrorIntegerLiteralTooLarge(range); return new Box(intValue); } // Warn about decimal integers that start with "0" because other languages // strangely treat these numbers as octal instead of decimal if (base == 10 && intValue != 0 && in_string.get1(text, 0) == 48) { log.syntaxWarningOctal(range); } return new Box(isNegative ? -intValue | 0 : intValue); }; Skew.Parsing.checkExtraParentheses = function(context, node) { if (node.isInsideParentheses()) { context.log.syntaxWarningExtraParentheses(node.range); } }; Skew.Parsing._warnAboutIgnoredComments = function(context, comments) { if (comments != null && context.warnAboutIgnoredComments) { for (var i = 0, list = comments, count = list.length; i < count; i = i + 1 | 0) { var comment = in_List.get(list, i); context.log.syntaxWarningIgnoredCommentInParser(comment.range); } } }; Skew.Parsing.parseTrailingComment = function(context) { switch (context.current().kind) { case Skew.TokenKind.NEWLINE: case Skew.TokenKind.END_OF_FILE: case Skew.TokenKind.RIGHT_PARENTHESIS: case Skew.TokenKind.RIGHT_BRACE: case Skew.TokenKind.RIGHT_BRACKET: { return context.stealComments(); } } return null; }; Skew.Parsing.parseAnnotations = function(context, annotations) { while (context.peek1(Skew.TokenKind.ANNOTATION)) { var range = context.next().range; var value = new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent(range.toString())).withRange(range); // Change "@foo.bar.baz" into "foo.bar.@baz" if (context.peek1(Skew.TokenKind.DOT)) { var root = value.asString(); value.content = new Skew.StringContent(in_string.slice1(root, 1)); while (context.eat(Skew.TokenKind.DOT)) { var name = context.current().range; if (!context.expect(Skew.TokenKind.IDENTIFIER)) { break; } value = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(name.toString())).appendChild(value).withRange(context.spanSince(range)).withInternalRange(name); } value.content = new Skew.StringContent('@' + value.asString()); } // Parse parentheses if present var token = context.current(); if (context.eat(Skew.TokenKind.LEFT_PARENTHESIS)) { var call = Skew.Node.createCall(value); Skew.Parsing.parseCommaSeparatedList(context, call, Skew.TokenKind.RIGHT_PARENTHESIS); value = call.withRange(context.spanSince(range)).withInternalRange(context.spanSince(token.range)); } // Parse a trailing if condition var test = null; if (context.eat(Skew.TokenKind.IF)) { test = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); } // All annotations must end in a newline to avoid confusion with the trailing if if (!context.peek1(Skew.TokenKind.LEFT_BRACE) && !context.expect(Skew.TokenKind.NEWLINE)) { Skew.Parsing.scanForToken(context, Skew.TokenKind.NEWLINE); } annotations.push(Skew.Node.createAnnotation(value, test).withRange(context.spanSince(range))); } return annotations; }; // When the type is present, this parses something like "int x = 0" Skew.Parsing.parseVariables = function(context, type) { var variables = new Skew.Node(Skew.NodeKind.VARIABLES); var token = context.current(); // Skip "var" or "const" if present if (type == null) { context.next(); } while (true) { var range = context.current().range; if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return null; } var symbol = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, range.toString()); symbol.range = range; if (token.kind == Skew.TokenKind.CONST) { symbol.flags |= Skew.SymbolFlags.IS_CONST; } if (type != null) { symbol.type = type.clone(); } else { if (context.peek1(Skew.TokenKind.COLON)) { context.log.syntaxErrorExtraColonBeforeType(context.next().range); } if (Skew.Parsing.peekType(context)) { symbol.type = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); } } if (context.eat(Skew.TokenKind.ASSIGN)) { symbol.value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); } variables.appendChild(Skew.Node.createVariable(symbol).withRange(context.spanSince(range))); if (!context.eat(Skew.TokenKind.COMMA)) { break; } } return variables.withRange(context.spanSince(type != null ? type.range : token.range)); }; Skew.Parsing.parseJump = function(context) { var token = context.next(); return (token.kind == Skew.TokenKind.BREAK ? new Skew.Node(Skew.NodeKind.BREAK) : new Skew.Node(Skew.NodeKind.CONTINUE)).withRange(token.range); }; Skew.Parsing.parseReturn = function(context) { var token = context.next(); var value = null; // Check for "return;" explicitly for a better error message if (!context.skipSemicolon() && !context.peek1(Skew.TokenKind.NEWLINE) && !context.peek1(Skew.TokenKind.COMMENT) && !context.peek1(Skew.TokenKind.RIGHT_BRACE)) { value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); Skew.Parsing.checkExtraParentheses(context, value); } return Skew.Node.createReturn(value).withRange(context.spanSince(token.range)); }; Skew.Parsing.parseSwitch = function(context) { var token = context.next(); var value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); Skew.Parsing.checkExtraParentheses(context, value); var node = Skew.Node.createSwitch(value); context.skipWhitespace(); if (!context.expect(Skew.TokenKind.LEFT_BRACE)) { return null; } context.eat(Skew.TokenKind.NEWLINE); while (!context.peek1(Skew.TokenKind.RIGHT_BRACE)) { var comments = context.stealComments(); // Ignore trailing comments if (context.peek1(Skew.TokenKind.RIGHT_BRACE) || context.peek1(Skew.TokenKind.END_OF_FILE)) { Skew.Parsing._warnAboutIgnoredComments(context, comments); break; } // Parse a new case var child = new Skew.Node(Skew.NodeKind.CASE); var start = context.current(); var colon = null; if (context.eat(Skew.TokenKind.CASE)) { while (true) { var constantComments = context.stealComments(); var constant = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); Skew.Parsing.checkExtraParentheses(context, constant); child.appendChild(constant); constant.comments = Skew.Comment.concat(constantComments, Skew.Parsing.parseTrailingComment(context)); // A colon isn't valid syntax here, but try to have a nice error message if (context.peek1(Skew.TokenKind.COLON)) { colon = context.next().range; context.skipWhitespace(); if (context.eat(Skew.TokenKind.CASE)) { context.log.syntaxErrorExpectedCommaBetweenCases(context.spanSince(colon)); colon = null; } else { break; } } // Commas separate multiple values else if (!context.eat(Skew.TokenKind.COMMA)) { break; } constant.comments = Skew.Comment.concat(constant.comments, Skew.Parsing.parseTrailingComment(context)); } } // Default cases have no values else { if (!context.eat(Skew.TokenKind.DEFAULT)) { context.expect(Skew.TokenKind.CASE); return null; } // A colon isn't valid syntax here, but try to have a nice error message if (context.peek1(Skew.TokenKind.COLON)) { colon = context.next().range; } } var block = null; // Use a block instead of requiring "break" at the end if (colon == null) { block = Skew.Parsing.parseBlock(context); if (block == null) { return null; } } // If there was a syntax error, try to parse a C-style block else { var range = context.current().range; block = new Skew.Node(Skew.NodeKind.BLOCK); if (!Skew.Parsing.parseStatements(context, block, Skew.Parsing.StatementsMode.C_STYLE_SWITCH)) { return null; } var breakRange = null; if (context.peek1(Skew.TokenKind.BREAK)) { breakRange = context.next().range; if (context.skipSemicolon()) { breakRange = context.spanSince(breakRange); } } block.withRange(context.spanSince(range)); context.log.syntaxErrorColonAfterCaseOrDefault(colon, breakRange); context.eat(Skew.TokenKind.NEWLINE); } // Create the case node.appendChild(child.appendChild(block).withRange(context.spanSince(start.range))); // Parse trailing comments and/or newline child.comments = Skew.Comment.concat(comments, Skew.Parsing.parseTrailingComment(context)); if (context.peek1(Skew.TokenKind.RIGHT_BRACE) || colon == null && !context.expect(Skew.TokenKind.NEWLINE)) { break; } } if (!context.expect(Skew.TokenKind.RIGHT_BRACE)) { return null; } return node.withRange(context.spanSince(token.range)); }; Skew.Parsing.parseFor = function(context) { var token = context.next(); var parentheses = context.peek1(Skew.TokenKind.LEFT_PARENTHESIS) ? context.next() : null; // Doing "for var i = ..." is an error if (context.peek1(Skew.TokenKind.VAR)) { context.log.syntaxErrorExtraVarInForLoop(context.next().range); } var initialNameRange = context.current().range; if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return null; } // for a in b {} if (context.eat(Skew.TokenKind.IN)) { var symbol = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, initialNameRange.toString()); symbol.range = initialNameRange; var value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); if (context.eat(Skew.TokenKind.DOT_DOT)) { var second = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); value = Skew.Node.createPair(value, second).withRange(Skew.Range.span(value.range, second.range)); } Skew.Parsing.checkExtraParentheses(context, value); // Allow parentheses around the loop header if (parentheses != null) { if (!context.expect(Skew.TokenKind.RIGHT_PARENTHESIS)) { return null; } context.log.syntaxWarningExtraParentheses(context.spanSince(parentheses.range)); } var block = Skew.Parsing.parseBlock(context); if (block == null) { return null; } return Skew.Node.createForeach(symbol, value, block).withRange(context.spanSince(token.range)); } // for a = 0; a < 10; a++ {} var setup = new Skew.Node(Skew.NodeKind.VARIABLES); context.undo(); while (true) { var nameRange = context.current().range; if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return null; } var symbol1 = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, nameRange.toString()); symbol1.range = nameRange; if (Skew.Parsing.peekType(context)) { symbol1.type = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); } if (context.eat(Skew.TokenKind.ASSIGN)) { symbol1.value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); Skew.Parsing.checkExtraParentheses(context, symbol1.value); } setup.appendChild(Skew.Node.createVariable(symbol1).withRange(context.spanSince(nameRange))); if (!context.eat(Skew.TokenKind.COMMA)) { break; } } setup.range = context.spanSince(initialNameRange); if (!context.expect(Skew.TokenKind.SEMICOLON)) { return null; } var test = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); if (!context.expect(Skew.TokenKind.SEMICOLON)) { return null; } var update = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); // This is the one place in the grammar that sequence expressions are allowed if (context.eat(Skew.TokenKind.COMMA)) { update = new Skew.Node(Skew.NodeKind.SEQUENCE).appendChild(update); while (true) { var value1 = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); update.appendChild(value1); if (!context.eat(Skew.TokenKind.COMMA)) { break; } } } // Allow parentheses around the loop header if (parentheses != null) { if (!context.expect(Skew.TokenKind.RIGHT_PARENTHESIS)) { return null; } context.log.syntaxWarningExtraParentheses(context.spanSince(parentheses.range)); } var block1 = Skew.Parsing.parseBlock(context); if (block1 == null) { return null; } return Skew.Node.createFor(setup, test, update, block1).withRange(context.spanSince(token.range)); }; Skew.Parsing.parseIf = function(context) { var token = context.next(); var test = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); Skew.Parsing.checkExtraParentheses(context, test); var trueBlock = Skew.Parsing.parseBlock(context); if (trueBlock == null) { return null; } return Skew.Node.createIf(test, trueBlock, null).withRange(context.spanSince(token.range)); }; Skew.Parsing.parseThrow = function(context) { var token = context.next(); var value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); Skew.Parsing.checkExtraParentheses(context, value); return Skew.Node.createThrow(value).withRange(context.spanSince(token.range)); }; Skew.Parsing.parseTry = function(context) { var token = context.next(); var tryBlock = Skew.Parsing.parseBlock(context); if (tryBlock == null) { return null; } return Skew.Node.createTry(tryBlock).withRange(context.spanSince(token.range)); }; Skew.Parsing.parseWhile = function(context) { var token = context.next(); var test = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); Skew.Parsing.checkExtraParentheses(context, test); var block = Skew.Parsing.parseBlock(context); if (block == null) { return null; } return new Skew.Node(Skew.NodeKind.WHILE).appendChild(test).appendChild(block).withRange(context.spanSince(token.range)); }; Skew.Parsing.parseStatement = function(context) { var token = context.current(); switch (token.kind) { case Skew.TokenKind.BREAK: case Skew.TokenKind.CONTINUE: { return Skew.Parsing.parseJump(context); } case Skew.TokenKind.CONST: case Skew.TokenKind.VAR: { return Skew.Parsing.parseVariables(context, null); } case Skew.TokenKind.FOR: { return Skew.Parsing.parseFor(context); } case Skew.TokenKind.IF: { return Skew.Parsing.parseIf(context); } case Skew.TokenKind.RETURN: { return Skew.Parsing.parseReturn(context); } case Skew.TokenKind.SWITCH: { return Skew.Parsing.parseSwitch(context); } case Skew.TokenKind.THROW: { return Skew.Parsing.parseThrow(context); } case Skew.TokenKind.TRY: { return Skew.Parsing.parseTry(context); } case Skew.TokenKind.WHILE: { return Skew.Parsing.parseWhile(context); } } var value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); Skew.Parsing.checkExtraParentheses(context, value); // A special case for better errors when users try to use C-style variable declarations if (!value.isInsideParentheses() && Skew.Parsing.looksLikeType(value) && context.peek1(Skew.TokenKind.IDENTIFIER) && context.canReportSyntaxError()) { context.log.syntaxErrorVariableDeclarationNeedsVar(value.range, context.current().range); return Skew.Parsing.parseVariables(context, value); } var node = Skew.Node.createExpression(value).withRange(value.range); return node; }; Skew.Parsing.looksLikeType = function(node) { var kind = node.kind; return kind == Skew.NodeKind.NAME || kind == Skew.NodeKind.TYPE || kind == Skew.NodeKind.LAMBDA_TYPE || kind == Skew.NodeKind.DOT && node.dotTarget() != null && Skew.Parsing.looksLikeType(node.dotTarget()) || kind == Skew.NodeKind.PARAMETERIZE && Skew.Parsing.looksLikeType(node.parameterizeValue()); }; Skew.Parsing.parseStatements = function(context, parent, mode) { var previous = null; var leading = Skew.Parsing.parseTrailingComment(context); context.eat(Skew.TokenKind.NEWLINE); while (!context.peek1(Skew.TokenKind.RIGHT_BRACE) && !context.peek1(Skew.TokenKind.XML_START_CLOSE) && !context.peek1(Skew.TokenKind.END_OF_FILE)) { // The block may start with a trailing comment var comments = Skew.Comment.concat(leading, context.stealComments()); leading = null; // When parsing a C-style switch, stop if it looks like the end of the case if (mode == Skew.Parsing.StatementsMode.C_STYLE_SWITCH && (context.peek1(Skew.TokenKind.CASE) || context.peek1(Skew.TokenKind.DEFAULT) || context.peek1(Skew.TokenKind.BREAK))) { Skew.Parsing._warnAboutIgnoredComments(context, comments); break; } // Merge "else" statements with the previous "if" if (context.peek1(Skew.TokenKind.ELSE)) { var isValid = previous != null && previous.kind == Skew.NodeKind.IF && previous.ifFalse() == null; if (!isValid) { context.unexpectedToken(); } context.next(); // Match "else if" if (context.peek1(Skew.TokenKind.IF)) { var statement = Skew.Parsing.parseIf(context); if (statement == null) { return false; } // Append to the if statement var falseBlock = new Skew.Node(Skew.NodeKind.BLOCK).withRange(statement.range).appendChild(statement); falseBlock.comments = comments; if (isValid) { previous.appendChild(falseBlock); previous = statement; } else { previous = null; } } // Match "else" else { var falseBlock1 = Skew.Parsing.parseBlock(context); if (falseBlock1 == null) { return false; } // Append to the if statement falseBlock1.comments = comments; if (isValid) { previous.appendChild(falseBlock1); previous = falseBlock1; } else { previous = null; } } } // Merge "catch" statements with the previous "try" else if (context.peek1(Skew.TokenKind.CATCH)) { var isValid1 = previous != null && previous.kind == Skew.NodeKind.TRY && previous.finallyBlock() == null; if (!isValid1) { context.unexpectedToken(); } var catchToken = context.next(); var symbol = null; var nameRange = context.current().range; // Optional typed variable if (context.eat(Skew.TokenKind.IDENTIFIER)) { symbol = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, nameRange.toString()); symbol.range = nameRange; symbol.type = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); } // Parse the block var catchBlock = Skew.Parsing.parseBlock(context); if (catchBlock == null) { return false; } // Append to the try statement var child = Skew.Node.createCatch(symbol, catchBlock).withRange(context.spanSince(catchToken.range)); child.comments = comments; if (isValid1) { previous.appendChild(child); } else { previous = null; } } // Merge "finally" statements with the previous "try" else if (context.peek1(Skew.TokenKind.FINALLY)) { var isValid2 = previous != null && previous.kind == Skew.NodeKind.TRY && previous.finallyBlock() == null; if (!isValid2) { context.unexpectedToken(); } context.next(); // Parse the block var finallyBlock = Skew.Parsing.parseBlock(context); if (finallyBlock == null) { return false; } // Append to the try statement finallyBlock.comments = comments; if (isValid2) { previous.appendChild(finallyBlock); } else { previous = null; } } // Parse a new statement else { var current = context.current(); var statement1 = Skew.Parsing.parseStatement(context); if (statement1 == null) { Skew.Parsing.scanForToken(context, Skew.TokenKind.NEWLINE); continue; } // Prevent an infinite loop due to a syntax error at the start of an expression if (context.current() == current) { context.next(); } previous = statement1; statement1.comments = comments; parent.appendChild(statement1); } // Special-case semicolons before comments for better parser recovery var semicolon = context.skipSemicolon(); // Parse trailing comments and/or newline var trailing = Skew.Parsing.parseTrailingComment(context); if (trailing != null) { if (previous != null) { previous.comments = Skew.Comment.concat(previous.comments, trailing); } else { Skew.Parsing._warnAboutIgnoredComments(context, trailing); } } if (context.peek1(Skew.TokenKind.RIGHT_BRACE) || context.peek1(Skew.TokenKind.XML_START_CLOSE)) { break; } else if (!context.peek1(Skew.TokenKind.ELSE) && !context.peek1(Skew.TokenKind.CATCH) && !context.peek1(Skew.TokenKind.FINALLY)) { if (semicolon) { context.eat(Skew.TokenKind.NEWLINE); } else { context.expect(Skew.TokenKind.NEWLINE); } } } // Convert trailing comments to blocks var comments1 = Skew.Comment.concat(leading, context.stealComments()); if (comments1 != null) { parent.appendChild(new Skew.Node(Skew.NodeKind.COMMENT_BLOCK).withComments(comments1)); } return true; }; Skew.Parsing.parseBlock = function(context) { context.skipWhitespace(); var comments = context.stealComments(); var token = context.current(); if (!context.expect(Skew.TokenKind.LEFT_BRACE)) { // Return a block to try to recover and continue parsing return new Skew.Node(Skew.NodeKind.BLOCK); } var block = new Skew.Node(Skew.NodeKind.BLOCK); if (!Skew.Parsing.parseStatements(context, block, Skew.Parsing.StatementsMode.NORMAL) || !context.expect(Skew.TokenKind.RIGHT_BRACE)) { return null; } return block.withRange(context.spanSince(token.range)).withComments(comments); }; Skew.Parsing.peekType = function(context) { return context.peek1(Skew.TokenKind.IDENTIFIER) || context.peek1(Skew.TokenKind.DYNAMIC); }; Skew.Parsing.parseFunctionBlock = function(context, symbol) { // "=> x" is the same as "{ return x }" if (symbol.kind == Skew.SymbolKind.FUNCTION_LOCAL) { if (!context.expect(Skew.TokenKind.ARROW)) { return false; } if (context.peek1(Skew.TokenKind.LEFT_BRACE)) { symbol.block = Skew.Parsing.parseBlock(context); if (symbol.block == null) { return false; } } else { var value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); symbol.block = new Skew.Node(Skew.NodeKind.BLOCK).withRange(value.range).appendChild(Skew.Node.createReturn(value).withRange(value.range).withFlags(Skew.NodeFlags.IS_IMPLICIT_RETURN)); } } // Parse function body if present else if (context.peek1(Skew.TokenKind.LEFT_BRACE)) { symbol.block = Skew.Parsing.parseBlock(context); if (symbol.block == null) { return false; } } return true; }; Skew.Parsing.parseFunctionArguments = function(context, symbol) { var leading = Skew.Parsing.parseTrailingComment(context); var usingTypes = false; while (!context.eat(Skew.TokenKind.RIGHT_PARENTHESIS)) { context.skipWhitespace(); var comments = Skew.Comment.concat(leading, context.stealComments()); leading = null; var range = context.current().range; if (!context.expect(Skew.TokenKind.IDENTIFIER)) { Skew.Parsing.scanForToken(context, Skew.TokenKind.RIGHT_PARENTHESIS); break; } var arg = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_ARGUMENT, range.toString()); arg.range = range; // Parse argument type if (symbol.kind != Skew.SymbolKind.FUNCTION_LOCAL || (symbol.$arguments.length == 0 ? Skew.Parsing.peekType(context) || context.peek1(Skew.TokenKind.COLON) : usingTypes)) { if (context.peek1(Skew.TokenKind.COLON)) { context.log.syntaxErrorExtraColonBeforeType(context.next().range); } arg.type = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); usingTypes = true; } // Optional arguments aren't supported yet var assign = context.current().range; if (context.eat(Skew.TokenKind.ASSIGN)) { Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); context.log.syntaxErrorOptionalArgument(context.spanSince(assign)); } symbol.$arguments.push(arg); if (!context.peek1(Skew.TokenKind.RIGHT_PARENTHESIS)) { if (!context.expect(Skew.TokenKind.COMMA)) { Skew.Parsing.scanForToken(context, Skew.TokenKind.RIGHT_PARENTHESIS); break; } } arg.comments = Skew.Comment.concat(comments, Skew.Parsing.parseTrailingComment(context)); } return true; }; Skew.Parsing.parseFunctionReturnTypeAndBlock = function(context, symbol) { if (context.peek1(Skew.TokenKind.COLON)) { context.log.syntaxErrorExtraColonBeforeType(context.next().range); } if (Skew.Parsing.peekType(context)) { symbol.returnType = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); } if (context.peek1(Skew.TokenKind.NEWLINE) && context.peek2(Skew.TokenKind.LEFT_BRACE, 1)) { context.next(); } return Skew.Parsing.parseFunctionBlock(context, symbol); }; Skew.Parsing.parseTypeParameters = function(context, kind) { var parameters = []; while (true) { var range = context.current().range; var name = range.toString(); if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return null; } var symbol = new Skew.ParameterSymbol(kind, name); symbol.range = range; parameters.push(symbol); if (!context.eat(Skew.TokenKind.COMMA)) { break; } } if (!context.expect(Skew.TokenKind.PARAMETER_LIST_END)) { return null; } return parameters; }; Skew.Parsing.parseAfterBlock = function(context) { // Check for ";" explicitly for a better error message context.skipSemicolon(); return context.peek1(Skew.TokenKind.END_OF_FILE) || context.peek1(Skew.TokenKind.RIGHT_BRACE) || context.expect(Skew.TokenKind.NEWLINE); }; Skew.Parsing.recursiveParseGuard = function(context, parent, annotations) { var test = null; if (context.eat(Skew.TokenKind.IF)) { test = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); } if (!context.expect(Skew.TokenKind.LEFT_BRACE)) { return null; } var contents = new Skew.ObjectSymbol(parent.kind, ''); contents.flags |= Skew.SymbolFlags.IS_GUARD_CONDITIONAL; contents.parent = parent; Skew.Parsing.parseSymbols(context, contents, annotations); if (!context.expect(Skew.TokenKind.RIGHT_BRACE) || !context.peek1(Skew.TokenKind.ELSE) && !Skew.Parsing.parseAfterBlock(context)) { return null; } var elseGuard = null; if (context.eat(Skew.TokenKind.ELSE)) { elseGuard = Skew.Parsing.recursiveParseGuard(context, parent, annotations); if (elseGuard == null) { return null; } } return new Skew.Guard(parent, test, contents, elseGuard); }; Skew.Parsing.parseSymbol = function(context, parent, annotations) { // Parse comments before the symbol declaration var comments = context.stealComments(); // Ignore trailing comments if (context.peek1(Skew.TokenKind.RIGHT_BRACE) || context.peek1(Skew.TokenKind.END_OF_FILE)) { Skew.Parsing._warnAboutIgnoredComments(context, comments); return false; } // Parse a compile-time if statement if (context.peek1(Skew.TokenKind.IF)) { Skew.Parsing._warnAboutIgnoredComments(context, comments); var guard = Skew.Parsing.recursiveParseGuard(context, parent, annotations); if (guard == null) { return false; } if (parent.guards == null) { parent.guards = []; } parent.guards.push(guard); return true; } // Parse annotations before the symbol declaration if (context.peek1(Skew.TokenKind.ANNOTATION)) { annotations = Skew.Parsing.parseAnnotations(context, annotations != null ? annotations.slice() : []); // Parse an annotation block if (context.eat(Skew.TokenKind.LEFT_BRACE)) { Skew.Parsing._warnAboutIgnoredComments(context, comments); Skew.Parsing.parseSymbols(context, parent, annotations); return context.expect(Skew.TokenKind.RIGHT_BRACE) && Skew.Parsing.parseAfterBlock(context); } } var token = context.current(); var tokenKind = token.kind; var text = token.range.toString(); var wasCorrected = false; var symbol = null; var kind = in_StringMap.get(Skew.Parsing.identifierToSymbolKind, text, Skew.SymbolKind.OBJECT_GLOBAL); // Skip extra identifiers such as "public" in "public var x = 0" while (tokenKind == Skew.TokenKind.IDENTIFIER && kind == Skew.SymbolKind.OBJECT_GLOBAL && (context.peek2(Skew.TokenKind.IDENTIFIER, 1) || context.peek2(Skew.TokenKind.VAR, 1) || context.peek2(Skew.TokenKind.CONST, 1))) { switch (text) { case 'static': { context.log.syntaxErrorStaticKeyword(token.range, parent.kind, parent.name); break; } case 'public': { context.log.syntaxErrorPublicKeyword(token.range); break; } case 'protected': case 'private': { context.log.syntaxErrorPrivateOrProtected(token.range); break; } default: { context.unexpectedToken(); break; } } context.next(); token = context.current(); tokenKind = token.kind; text = token.range.toString(); kind = in_StringMap.get(Skew.Parsing.identifierToSymbolKind, text, Skew.SymbolKind.OBJECT_GLOBAL); } // Special-case a top-level "x = 0" and "x: int = 0" but avoid trying to make "def =() {}" into a variable if (tokenKind == Skew.TokenKind.IDENTIFIER && (context.peek2(Skew.TokenKind.ASSIGN, 1) && (!context.peek2(Skew.TokenKind.LEFT_PARENTHESIS, 2) || text != 'def') || context.peek2(Skew.TokenKind.COLON, 1) && context.peek2(Skew.TokenKind.IDENTIFIER, 2))) { context.log.syntaxErrorMissingVar(token.range); tokenKind = Skew.TokenKind.VAR; wasCorrected = true; } // Special-case a top-level "x() {}" else if (tokenKind == Skew.TokenKind.IDENTIFIER && context.peek2(Skew.TokenKind.LEFT_PARENTHESIS, 1)) { context.log.syntaxErrorMissingDef(token.range); tokenKind = Skew.TokenKind.IDENTIFIER; kind = Skew.SymbolKind.FUNCTION_GLOBAL; text = 'def'; wasCorrected = true; } // Special-case enum and flags symbols if (Skew.in_SymbolKind.isEnumOrFlags(parent.kind) && tokenKind == Skew.TokenKind.IDENTIFIER && kind == Skew.SymbolKind.OBJECT_GLOBAL) { while (true) { if (Skew.Parsing.identifierToSymbolKind.has(text) || !context.expect(Skew.TokenKind.IDENTIFIER)) { break; } var variable = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS, text); variable.range = token.range; variable.parent = parent; variable.flags |= Skew.SymbolFlags.IS_CONST; parent.variables.push(variable); symbol = variable; var comma = context.current(); if (!context.eat(Skew.TokenKind.COMMA)) { break; } variable.annotations = annotations != null ? annotations.slice() : null; variable.comments = comments; token = context.current(); text = token.range.toString(); if (context.peek1(Skew.TokenKind.NEWLINE) || context.peek1(Skew.TokenKind.RIGHT_BRACE)) { context.log.syntaxWarningExtraComma(comma.range); break; } } } else { // Parse the symbol kind switch (tokenKind) { case Skew.TokenKind.CONST: case Skew.TokenKind.VAR: { kind = Skew.in_SymbolKind.hasInstances(parent.kind) ? Skew.SymbolKind.VARIABLE_INSTANCE : Skew.SymbolKind.VARIABLE_GLOBAL; break; } // For symbol kinds that can't live inside functions, use contextual // keywords instead of special keyword tokens. This means these names // are still available to be used as regular symbols: // // class div { // var class = "" // } // // var example =
// case Skew.TokenKind.IDENTIFIER: { if (kind == Skew.SymbolKind.OBJECT_GLOBAL) { context.unexpectedToken(); return false; } if (kind == Skew.SymbolKind.FUNCTION_GLOBAL && Skew.in_SymbolKind.hasInstances(parent.kind)) { kind = Skew.SymbolKind.FUNCTION_INSTANCE; } break; } default: { context.unexpectedToken(); return false; } } if (!wasCorrected) { context.next(); } var nameToken = context.current(); var range = nameToken.range; var name = range.toString(); var isOperator = false; // Only check for custom operators for instance functions if (kind == Skew.SymbolKind.FUNCTION_INSTANCE) { if (Skew.Parsing.customOperators.has(nameToken.kind)) { isOperator = true; } else if (Skew.Parsing.forbiddenCustomOperators.has(nameToken.kind)) { context.log.syntaxErrorBadOperatorCustomization(range, nameToken.kind, in_IntMap.get1(Skew.Parsing.forbiddenGroupDescription, in_IntMap.get1(Skew.Parsing.forbiddenCustomOperators, nameToken.kind))); isOperator = true; } } // Parse the symbol name if (isOperator) { context.next(); } else if (kind == Skew.SymbolKind.FUNCTION_GLOBAL && context.eat(Skew.TokenKind.ANNOTATION)) { kind = Skew.SymbolKind.FUNCTION_ANNOTATION; } else if (context.eat(Skew.TokenKind.LIST_NEW) || context.eat(Skew.TokenKind.SET_NEW)) { if (kind == Skew.SymbolKind.FUNCTION_INSTANCE) { kind = Skew.SymbolKind.FUNCTION_CONSTRUCTOR; } } else { if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return false; } if (kind == Skew.SymbolKind.FUNCTION_INSTANCE && name == 'new') { kind = Skew.SymbolKind.FUNCTION_CONSTRUCTOR; } } // Parse shorthand nested namespace declarations if (Skew.in_SymbolKind.isObject(kind)) { while (context.eat(Skew.TokenKind.DOT)) { var nextToken = context.current(); if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return false; } // Wrap this declaration in a namespace var nextParent = new Skew.ObjectSymbol(Skew.SymbolKind.OBJECT_NAMESPACE, name); nextParent.range = range; nextParent.parent = parent; parent.objects.push(nextParent); parent = nextParent; // Update the declaration token nameToken = nextToken; range = nextToken.range; name = range.toString(); } } // Parse the symbol body switch (kind) { case Skew.SymbolKind.VARIABLE_GLOBAL: case Skew.SymbolKind.VARIABLE_INSTANCE: { while (true) { var variable1 = new Skew.VariableSymbol(kind, name); variable1.range = range; variable1.parent = parent; if (tokenKind == Skew.TokenKind.CONST) { variable1.flags |= Skew.SymbolFlags.IS_CONST; } if (context.peek1(Skew.TokenKind.COLON)) { context.log.syntaxErrorExtraColonBeforeType(context.next().range); } if (Skew.Parsing.peekType(context)) { variable1.type = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); } if (context.eat(Skew.TokenKind.ASSIGN)) { variable1.value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); Skew.Parsing.checkExtraParentheses(context, variable1.value); } parent.variables.push(variable1); if (!context.eat(Skew.TokenKind.COMMA)) { symbol = variable1; break; } variable1.annotations = annotations != null ? annotations.slice() : null; variable1.comments = comments; nameToken = context.current(); range = nameToken.range; name = range.toString(); if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return false; } } break; } case Skew.SymbolKind.FUNCTION_ANNOTATION: case Skew.SymbolKind.FUNCTION_CONSTRUCTOR: case Skew.SymbolKind.FUNCTION_GLOBAL: case Skew.SymbolKind.FUNCTION_INSTANCE: { var $function = new Skew.FunctionSymbol(kind, name); $function.range = range; $function.parent = parent; if (text == 'over') { $function.flags |= Skew.SymbolFlags.IS_OVER; } // Check for setters like "def foo=(x int) {}" but don't allow a space // between the name and the assignment operator if (kind != Skew.SymbolKind.FUNCTION_ANNOTATION && nameToken.kind == Skew.TokenKind.IDENTIFIER && context.peek1(Skew.TokenKind.ASSIGN) && context.current().range.start == nameToken.range.end) { $function.range = Skew.Range.span($function.range, context.next().range); $function.flags |= Skew.SymbolFlags.IS_SETTER; $function.name += '='; } // Parse type parameters if (context.eat(Skew.TokenKind.PARAMETER_LIST_START)) { $function.parameters = Skew.Parsing.parseTypeParameters(context, Skew.SymbolKind.PARAMETER_FUNCTION); if ($function.parameters == null) { return false; } } // Parse function arguments var before = context.current(); if (context.eat(Skew.TokenKind.LEFT_PARENTHESIS)) { if (!Skew.Parsing.parseFunctionArguments(context, $function)) { return false; } // Functions without arguments are "getters" and don't use parentheses if ($function.$arguments.length == 0) { context.log.syntaxErrorEmptyFunctionParentheses(context.spanSince(before.range)); } } if (kind != Skew.SymbolKind.FUNCTION_ANNOTATION && !Skew.Parsing.parseFunctionReturnTypeAndBlock(context, $function)) { return false; } // Don't mark operators as getters to avoid confusion with unary operators and compiler-generated call expressions if (!isOperator && $function.$arguments.length == 0) { $function.flags |= Skew.SymbolFlags.IS_GETTER; } parent.functions.push($function); symbol = $function; break; } case Skew.SymbolKind.OBJECT_CLASS: case Skew.SymbolKind.OBJECT_ENUM: case Skew.SymbolKind.OBJECT_FLAGS: case Skew.SymbolKind.OBJECT_INTERFACE: case Skew.SymbolKind.OBJECT_NAMESPACE: case Skew.SymbolKind.OBJECT_WRAPPED: { var object = new Skew.ObjectSymbol(kind, name); object.range = range; object.parent = parent; if (kind != Skew.SymbolKind.OBJECT_NAMESPACE && context.eat(Skew.TokenKind.PARAMETER_LIST_START)) { object.parameters = Skew.Parsing.parseTypeParameters(context, Skew.SymbolKind.PARAMETER_OBJECT); if (object.parameters == null) { return false; } } // Allow "type Foo = int" if (kind == Skew.SymbolKind.OBJECT_WRAPPED && context.eat(Skew.TokenKind.ASSIGN)) { object.$extends = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); if (object.$extends == null) { return false; } } // Regular block structure "type Foo : int {}" else { // Base class var isExtends = context.peek1(Skew.TokenKind.IDENTIFIER) && context.current().range.toString() == 'extends'; if (isExtends) { context.log.syntaxErrorExtendsKeyword(context.next().range); } if (isExtends || context.eat(Skew.TokenKind.COLON)) { object.$extends = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); if (object.$extends == null) { return false; } } // Interfaces var isImplements = context.peek1(Skew.TokenKind.IDENTIFIER) && context.current().range.toString() == 'implements'; if (isImplements) { context.log.syntaxErrorImplementsKeyword(context.next().range); } if (isImplements || context.eat(Skew.TokenKind.DOUBLE_COLON)) { object.$implements = []; while (true) { var type = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); object.$implements.push(type); if (!context.eat(Skew.TokenKind.COMMA)) { break; } } } context.skipWhitespace(); if (!context.expect(Skew.TokenKind.LEFT_BRACE)) { Skew.Parsing.scanForToken(context, Skew.TokenKind.LEFT_BRACE); } Skew.Parsing.parseSymbols(context, object, null); object.commentsInsideEndOfBlock = context.stealComments(); if (!context.expect(Skew.TokenKind.RIGHT_BRACE)) { return false; } } parent.objects.push(object); symbol = object; break; } default: { assert(false); break; } } // Forbid certain kinds of symbols inside certain object types if ((Skew.in_SymbolKind.isEnumOrFlags(parent.kind) || parent.kind == Skew.SymbolKind.OBJECT_WRAPPED || parent.kind == Skew.SymbolKind.OBJECT_INTERFACE) && (kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR || kind == Skew.SymbolKind.VARIABLE_INSTANCE)) { context.log.syntaxErrorBadDeclarationInsideType(context.spanSince(token.range)); } } symbol.annotations = annotations != null ? annotations.slice() : null; symbol.comments = Skew.Comment.concat(comments, Skew.Parsing.parseTrailingComment(context)); if (!Skew.Parsing.parseAfterBlock(context)) { return false; } return true; }; Skew.Parsing.parseSymbols = function(context, parent, annotations) { context.eat(Skew.TokenKind.NEWLINE); while (!context.peek1(Skew.TokenKind.END_OF_FILE) && !context.peek1(Skew.TokenKind.RIGHT_BRACE)) { if (!Skew.Parsing.parseSymbol(context, parent, annotations)) { break; } } }; Skew.Parsing.parseCommaSeparatedList = function(context, parent, stop) { var isFirst = true; var leading = Skew.Parsing.parseTrailingComment(context); context.skipWhitespace(); while (true) { var comments = Skew.Comment.concat(leading, context.stealComments()); leading = null; if (isFirst && context.eat(stop)) { Skew.Parsing._warnAboutIgnoredComments(context, comments); break; } var value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); value.comments = Skew.Comment.concat(comments, Skew.Parsing.parseTrailingComment(context)); context.skipWhitespace(); parent.appendChild(value); if (context.eat(stop)) { break; } if (!context.expect(Skew.TokenKind.COMMA)) { Skew.Parsing.scanForToken(context, stop); break; } value.comments = Skew.Comment.concat(value.comments, Skew.Parsing.parseTrailingComment(context)); context.skipWhitespace(); isFirst = false; } }; Skew.Parsing.parseHexCharacter = function(c) { if (c >= 48 && c <= 57) { return c - 48 | 0; } if (c >= 65 && c <= 70) { return (c - 65 | 0) + 10 | 0; } if (c >= 97 && c <= 102) { return (c - 97 | 0) + 10 | 0; } return -1; }; Skew.Parsing.createStringNode = function(log, range) { return new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.StringContent(Skew.Parsing.parseStringLiteral(log, range))).withRange(range); }; Skew.Parsing.parseStringLiteral = function(log, range) { var text = range.toString(); var count = text.length; assert(count >= 2); assert(in_string.get1(text, 0) == 34 || in_string.get1(text, 0) == 39 || in_string.get1(text, 0) == 41); assert(in_string.get1(text, count - 1 | 0) == 34 || in_string.get1(text, count - 1 | 0) == 39 || in_string.get1(text, count - 1 | 0) == 40); var builder = new StringBuilder(); var start = 1; var stop = count - (in_string.get1(text, count - 1 | 0) == 40 ? 2 : 1) | 0; var i = start; while (i < stop) { var c = in_string.get1(text, (i = i + 1 | 0) + -1 | 0); if (c == 92) { var escape = i - 1 | 0; builder.buffer += in_string.slice2(text, start, escape); if (i < stop) { c = in_string.get1(text, (i = i + 1 | 0) + -1 | 0); if (c == 110) { builder.buffer += '\n'; start = i; } else if (c == 114) { builder.buffer += '\r'; start = i; } else if (c == 116) { builder.buffer += '\t'; start = i; } else if (c == 48) { builder.buffer += '\0'; start = i; } else if (c == 92 || c == 34 || c == 39) { builder.buffer += String.fromCharCode(c); start = i; } else if (c == 120) { if (i < stop) { var c0 = Skew.Parsing.parseHexCharacter(in_string.get1(text, (i = i + 1 | 0) + -1 | 0)); if (i < stop) { var c1 = Skew.Parsing.parseHexCharacter(in_string.get1(text, (i = i + 1 | 0) + -1 | 0)); if (c0 != -1 && c1 != -1) { builder.buffer += String.fromCharCode(c0 << 4 | c1); start = i; } } } } } if (start < i) { log.syntaxErrorInvalidEscapeSequence(new Skew.Range(range.source, range.start + escape | 0, range.start + i | 0)); } } } builder.buffer += in_string.slice2(text, start, i); return builder.buffer; }; Skew.Parsing.boolLiteral = function(value) { return function(context, token) { return new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.BoolContent(value)).withRange(token.range); }; }; Skew.Parsing.tokenLiteral = function(kind) { return function(context, token) { return new Skew.Node(kind).withRange(token.range); }; }; Skew.Parsing.unaryPrefix = function(kind) { return function(context, token, value) { return Skew.Node.createUnary(kind, value).withRange(Skew.Range.span(token.range, value.range)).withInternalRange(token.range); }; }; Skew.Parsing.unaryPostfix = function(kind) { return function(context, value, token) { return Skew.Node.createUnary(kind, value).withRange(Skew.Range.span(value.range, token.range)).withInternalRange(token.range); }; }; Skew.Parsing.binaryInfix = function(kind) { return function(context, left, token, right) { if (kind == Skew.NodeKind.ASSIGN && left.kind == Skew.NodeKind.INDEX) { left.appendChild(right); left.kind = Skew.NodeKind.ASSIGN_INDEX; return left.withRange(Skew.Range.span(left.range, right.range)).withInternalRange(Skew.Range.span(left.internalRange, right.range)); } return Skew.Node.createBinary(kind, left, right).withRange(Skew.Range.span(left.range, right.range)).withInternalRange(token.range); }; }; Skew.Parsing.createExpressionParser = function() { var pratt = new Skew.Pratt(); ///////////////////////////////////////// // Literals ///////////////////////////////////////// pratt.literal(Skew.TokenKind.DOUBLE, function(context, token) { return new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.DoubleContent(+token.range.toString())).withRange(token.range); }); pratt.literal(Skew.TokenKind.FALSE, Skew.Parsing.boolLiteral(false)); pratt.literal(Skew.TokenKind.INT, Skew.Parsing.intLiteral); pratt.literal(Skew.TokenKind.INT_BINARY, Skew.Parsing.intLiteral); pratt.literal(Skew.TokenKind.INT_HEX, Skew.Parsing.intLiteral); pratt.literal(Skew.TokenKind.INT_OCTAL, Skew.Parsing.intLiteral); pratt.literal(Skew.TokenKind.NULL, Skew.Parsing.tokenLiteral(Skew.NodeKind.NULL)); pratt.literal(Skew.TokenKind.STRING, Skew.Parsing.stringLiteral); pratt.literal(Skew.TokenKind.SUPER, Skew.Parsing.tokenLiteral(Skew.NodeKind.SUPER)); pratt.literal(Skew.TokenKind.TRUE, Skew.Parsing.boolLiteral(true)); pratt.literal(Skew.TokenKind.CHARACTER, function(context, token) { var result = Skew.Parsing.parseStringLiteral(context.log, token.range); var codePoint = 0; // There must be exactly one unicode code point var iterator = Unicode.StringIterator.INSTANCE.reset(result, 0); codePoint = iterator.nextCodePoint(); if (codePoint == -1 || iterator.nextCodePoint() != -1) { context.log.syntaxErrorInvalidCharacter(token.range); } // Don't return null when there's an error because that // error won't affect the rest of the compilation return new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.IntContent(codePoint)).withRange(token.range); }); ///////////////////////////////////////// // Unary expressions ///////////////////////////////////////// pratt.prefix(Skew.TokenKind.MINUS, Skew.Precedence.UNARY_PREFIX, Skew.Parsing.unaryPrefix(Skew.NodeKind.NEGATIVE)); pratt.prefix(Skew.TokenKind.NOT, Skew.Precedence.UNARY_PREFIX, Skew.Parsing.unaryPrefix(Skew.NodeKind.NOT)); pratt.prefix(Skew.TokenKind.PLUS, Skew.Precedence.UNARY_PREFIX, Skew.Parsing.unaryPrefix(Skew.NodeKind.POSITIVE)); pratt.prefix(Skew.TokenKind.TILDE, Skew.Precedence.UNARY_PREFIX, Skew.Parsing.unaryPrefix(Skew.NodeKind.COMPLEMENT)); pratt.prefix(Skew.TokenKind.INCREMENT, Skew.Precedence.UNARY_PREFIX, Skew.Parsing.unaryPrefix(Skew.NodeKind.PREFIX_INCREMENT)); pratt.prefix(Skew.TokenKind.DECREMENT, Skew.Precedence.UNARY_PREFIX, Skew.Parsing.unaryPrefix(Skew.NodeKind.PREFIX_DECREMENT)); pratt.postfix(Skew.TokenKind.INCREMENT, Skew.Precedence.UNARY_PREFIX, Skew.Parsing.unaryPostfix(Skew.NodeKind.POSTFIX_INCREMENT)); pratt.postfix(Skew.TokenKind.DECREMENT, Skew.Precedence.UNARY_PREFIX, Skew.Parsing.unaryPostfix(Skew.NodeKind.POSTFIX_DECREMENT)); ///////////////////////////////////////// // Binary expressions ///////////////////////////////////////// pratt.infix(Skew.TokenKind.BITWISE_AND, Skew.Precedence.BITWISE_AND, Skew.Parsing.binaryInfix(Skew.NodeKind.BITWISE_AND)); pratt.infix(Skew.TokenKind.BITWISE_OR, Skew.Precedence.BITWISE_OR, Skew.Parsing.binaryInfix(Skew.NodeKind.BITWISE_OR)); pratt.infix(Skew.TokenKind.BITWISE_XOR, Skew.Precedence.BITWISE_XOR, Skew.Parsing.binaryInfix(Skew.NodeKind.BITWISE_XOR)); pratt.infix(Skew.TokenKind.COMPARE, Skew.Precedence.COMPARE, Skew.Parsing.binaryInfix(Skew.NodeKind.COMPARE)); pratt.infix(Skew.TokenKind.DIVIDE, Skew.Precedence.MULTIPLY, Skew.Parsing.binaryInfix(Skew.NodeKind.DIVIDE)); pratt.infix(Skew.TokenKind.EQUAL, Skew.Precedence.EQUAL, Skew.Parsing.binaryInfix(Skew.NodeKind.EQUAL)); pratt.infix(Skew.TokenKind.GREATER_THAN, Skew.Precedence.COMPARE, Skew.Parsing.binaryInfix(Skew.NodeKind.GREATER_THAN)); pratt.infix(Skew.TokenKind.GREATER_THAN_OR_EQUAL, Skew.Precedence.COMPARE, Skew.Parsing.binaryInfix(Skew.NodeKind.GREATER_THAN_OR_EQUAL)); pratt.infix(Skew.TokenKind.IN, Skew.Precedence.COMPARE, Skew.Parsing.binaryInfix(Skew.NodeKind.IN)); pratt.infix(Skew.TokenKind.LESS_THAN, Skew.Precedence.COMPARE, Skew.Parsing.binaryInfix(Skew.NodeKind.LESS_THAN)); pratt.infix(Skew.TokenKind.LESS_THAN_OR_EQUAL, Skew.Precedence.COMPARE, Skew.Parsing.binaryInfix(Skew.NodeKind.LESS_THAN_OR_EQUAL)); pratt.infix(Skew.TokenKind.LOGICAL_AND, Skew.Precedence.LOGICAL_AND, Skew.Parsing.binaryInfix(Skew.NodeKind.LOGICAL_AND)); pratt.infix(Skew.TokenKind.LOGICAL_OR, Skew.Precedence.LOGICAL_OR, Skew.Parsing.binaryInfix(Skew.NodeKind.LOGICAL_OR)); pratt.infix(Skew.TokenKind.MINUS, Skew.Precedence.ADD, Skew.Parsing.binaryInfix(Skew.NodeKind.SUBTRACT)); pratt.infix(Skew.TokenKind.MODULUS, Skew.Precedence.MULTIPLY, Skew.Parsing.binaryInfix(Skew.NodeKind.MODULUS)); pratt.infix(Skew.TokenKind.MULTIPLY, Skew.Precedence.MULTIPLY, Skew.Parsing.binaryInfix(Skew.NodeKind.MULTIPLY)); pratt.infix(Skew.TokenKind.NOT_EQUAL, Skew.Precedence.EQUAL, Skew.Parsing.binaryInfix(Skew.NodeKind.NOT_EQUAL)); pratt.infix(Skew.TokenKind.PLUS, Skew.Precedence.ADD, Skew.Parsing.binaryInfix(Skew.NodeKind.ADD)); pratt.infix(Skew.TokenKind.POWER, Skew.Precedence.UNARY_PREFIX, Skew.Parsing.binaryInfix(Skew.NodeKind.POWER)); pratt.infix(Skew.TokenKind.REMAINDER, Skew.Precedence.MULTIPLY, Skew.Parsing.binaryInfix(Skew.NodeKind.REMAINDER)); pratt.infix(Skew.TokenKind.SHIFT_LEFT, Skew.Precedence.SHIFT, Skew.Parsing.binaryInfix(Skew.NodeKind.SHIFT_LEFT)); pratt.infix(Skew.TokenKind.SHIFT_RIGHT, Skew.Precedence.SHIFT, Skew.Parsing.binaryInfix(Skew.NodeKind.SHIFT_RIGHT)); pratt.infix(Skew.TokenKind.UNSIGNED_SHIFT_RIGHT, Skew.Precedence.SHIFT, Skew.Parsing.binaryInfix(Skew.NodeKind.UNSIGNED_SHIFT_RIGHT)); pratt.infixRight(Skew.TokenKind.ASSIGN, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN)); pratt.infixRight(Skew.TokenKind.ASSIGN_BITWISE_AND, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_BITWISE_AND)); pratt.infixRight(Skew.TokenKind.ASSIGN_BITWISE_OR, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_BITWISE_OR)); pratt.infixRight(Skew.TokenKind.ASSIGN_BITWISE_XOR, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_BITWISE_XOR)); pratt.infixRight(Skew.TokenKind.ASSIGN_DIVIDE, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_DIVIDE)); pratt.infixRight(Skew.TokenKind.ASSIGN_MINUS, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_SUBTRACT)); pratt.infixRight(Skew.TokenKind.ASSIGN_MODULUS, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_MODULUS)); pratt.infixRight(Skew.TokenKind.ASSIGN_MULTIPLY, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_MULTIPLY)); pratt.infixRight(Skew.TokenKind.ASSIGN_NULL, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_NULL)); pratt.infixRight(Skew.TokenKind.ASSIGN_PLUS, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_ADD)); pratt.infixRight(Skew.TokenKind.ASSIGN_POWER, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_POWER)); pratt.infixRight(Skew.TokenKind.ASSIGN_REMAINDER, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_REMAINDER)); pratt.infixRight(Skew.TokenKind.ASSIGN_SHIFT_LEFT, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_SHIFT_LEFT)); pratt.infixRight(Skew.TokenKind.ASSIGN_SHIFT_RIGHT, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_SHIFT_RIGHT)); pratt.infixRight(Skew.TokenKind.ASSIGN_UNSIGNED_SHIFT_RIGHT, Skew.Precedence.ASSIGN, Skew.Parsing.binaryInfix(Skew.NodeKind.ASSIGN_UNSIGNED_SHIFT_RIGHT)); pratt.infixRight(Skew.TokenKind.NULL_JOIN, Skew.Precedence.NULL_JOIN, Skew.Parsing.binaryInfix(Skew.NodeKind.NULL_JOIN)); ///////////////////////////////////////// // Other expressions ///////////////////////////////////////// pratt.parselet(Skew.TokenKind.DOT, Skew.Precedence.MEMBER).infix = Skew.Parsing.dotInfixParselet; pratt.parselet(Skew.TokenKind.INDEX, Skew.Precedence.LOWEST).prefix = Skew.Parsing.initializerParselet; pratt.parselet(Skew.TokenKind.LEFT_BRACE, Skew.Precedence.LOWEST).prefix = Skew.Parsing.initializerParselet; pratt.parselet(Skew.TokenKind.LEFT_BRACKET, Skew.Precedence.LOWEST).prefix = Skew.Parsing.initializerParselet; pratt.parselet(Skew.TokenKind.LIST_NEW, Skew.Precedence.LOWEST).prefix = Skew.Parsing.initializerParselet; pratt.parselet(Skew.TokenKind.SET_NEW, Skew.Precedence.LOWEST).prefix = Skew.Parsing.initializerParselet; pratt.parselet(Skew.TokenKind.PARAMETER_LIST_START, Skew.Precedence.MEMBER).infix = Skew.Parsing.parameterizedParselet; // String interpolation pratt.parselet(Skew.TokenKind.STRING_INTERPOLATION_START, Skew.Precedence.LOWEST).prefix = function(context) { var token = context.next(); var node = new Skew.Node(Skew.NodeKind.STRING_INTERPOLATION).appendChild(Skew.Parsing.createStringNode(context.log, token.range)); while (true) { var value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); node.appendChild(value); context.skipWhitespace(); token = context.current(); if (!context.eat(Skew.TokenKind.STRING_INTERPOLATION_CONTINUE) && !context.expect(Skew.TokenKind.STRING_INTERPOLATION_END)) { return context.createParseError(); } node.appendChild(Skew.Parsing.createStringNode(context.log, token.range)); if (token.kind == Skew.TokenKind.STRING_INTERPOLATION_END) { break; } } return node.withRange(context.spanSince(node.firstChild().range)); }; // "x?.y" pratt.parselet(Skew.TokenKind.NULL_DOT, Skew.Precedence.MEMBER).infix = function(context, left) { context.next(); var range = context.current().range; if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return context.createParseError(); } return new Skew.Node(Skew.NodeKind.NULL_DOT).withContent(new Skew.StringContent(range.toString())).appendChild(left).withRange(context.spanSince(left.range)).withInternalRange(range); }; // Lambda expressions like "=> x" pratt.parselet(Skew.TokenKind.ARROW, Skew.Precedence.LOWEST).prefix = function(context) { var token = context.current(); var symbol = new Skew.FunctionSymbol(Skew.SymbolKind.FUNCTION_LOCAL, ''); if (!Skew.Parsing.parseFunctionBlock(context, symbol)) { return context.createParseError(); } symbol.range = context.spanSince(token.range); return Skew.Node.createLambda(symbol).withRange(symbol.range); }; // Cast expressions pratt.parselet(Skew.TokenKind.AS, Skew.Precedence.UNARY_PREFIX).infix = function(context, left) { var token = context.next(); var type = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); return Skew.Node.createCast(left, type).withRange(context.spanSince(left.range)).withInternalRange(token.range); }; // Using "." as a unary prefix operator accesses members off the inferred type pratt.parselet(Skew.TokenKind.DOT, Skew.Precedence.MEMBER).prefix = function(context) { var token = context.next(); var range = context.current().range; if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return context.createParseError(); } return new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(range.toString())).appendChild(null).withRange(context.spanSince(token.range)).withInternalRange(range); }; // Access members off of "dynamic" for untyped globals pratt.parselet(Skew.TokenKind.DYNAMIC, Skew.Precedence.LOWEST).prefix = function(context) { var token = context.next(); if (!context.expect(Skew.TokenKind.DOT)) { return context.createParseError(); } var range = context.current().range; if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return context.createParseError(); } return new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(range.toString())).appendChild(new Skew.Node(Skew.NodeKind.TYPE).withType(Skew.Type.DYNAMIC)).withRange(context.spanSince(token.range)).withInternalRange(range); }; // Name expressions and lambda expressions like "x => x * x" pratt.parselet(Skew.TokenKind.IDENTIFIER, Skew.Precedence.LOWEST).prefix = function(context) { var range = context.next().range; var name = range.toString(); // Lambda expression if (context.peek1(Skew.TokenKind.ARROW)) { var symbol = new Skew.FunctionSymbol(Skew.SymbolKind.FUNCTION_LOCAL, ''); var argument = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_ARGUMENT, name); argument.range = range; symbol.$arguments.push(argument); if (!Skew.Parsing.parseFunctionBlock(context, symbol)) { return context.createParseError(); } symbol.range = context.spanSince(range); return Skew.Node.createLambda(symbol).withRange(symbol.range); } // New expression (an error, but still parsed for a better error message) if (context.peek1(Skew.TokenKind.IDENTIFIER) && name == 'new') { var type = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); context.log.syntaxErrorNewOperator(context.spanSince(range), type.range.toString() + '.new'); return new Skew.Node(Skew.NodeKind.PARSE_ERROR).withRange(context.spanSince(range)); } return new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent(name)).withRange(range); }; // Type check expressions pratt.parselet(Skew.TokenKind.IS, Skew.Precedence.COMPARE).infix = function(context, left) { var token = context.next(); var type = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); return Skew.Node.createTypeCheck(left, type).withRange(context.spanSince(left.range)).withInternalRange(token.range); }; // Index expressions pratt.parselet(Skew.TokenKind.LEFT_BRACKET, Skew.Precedence.MEMBER).infix = function(context, left) { var token = context.next(); var right = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); Skew.Parsing.scanForToken(context, Skew.TokenKind.RIGHT_BRACKET); return Skew.Node.createIndex(left, right).withRange(context.spanSince(left.range)).withInternalRange(context.spanSince(token.range)); }; // Parenthetic groups and lambda expressions like "() => x" pratt.parselet(Skew.TokenKind.LEFT_PARENTHESIS, Skew.Precedence.LOWEST).prefix = function(context) { var token = context.next(); // Try to parse a group if (!context.peek1(Skew.TokenKind.RIGHT_PARENTHESIS)) { var value = pratt.parse(context, Skew.Precedence.LOWEST); if ((value.kind != Skew.NodeKind.NAME || !Skew.Parsing.peekType(context)) && context.eat(Skew.TokenKind.RIGHT_PARENTHESIS)) { if (value.kind != Skew.NodeKind.NAME || !context.peek1(Skew.TokenKind.ARROW)) { return value.withRange(context.spanSince(token.range)).withFlags(Skew.NodeFlags.IS_INSIDE_PARENTHESES); } context.undo(); } context.undo(); } // Parse a lambda instead var symbol = new Skew.FunctionSymbol(Skew.SymbolKind.FUNCTION_LOCAL, ''); if (!Skew.Parsing.parseFunctionArguments(context, symbol) || !Skew.Parsing.parseFunctionReturnTypeAndBlock(context, symbol)) { return context.createParseError(); } symbol.range = context.spanSince(token.range); return Skew.Node.createLambda(symbol).withRange(symbol.range); }; // Call expressions pratt.parselet(Skew.TokenKind.LEFT_PARENTHESIS, Skew.Precedence.UNARY_POSTFIX).infix = function(context, left) { var node = Skew.Node.createCall(left); var token = context.next(); Skew.Parsing.parseCommaSeparatedList(context, node, Skew.TokenKind.RIGHT_PARENTHESIS); return node.withRange(context.spanSince(left.range)).withInternalRange(context.spanSince(token.range)); }; // Hook expressions pratt.parselet(Skew.TokenKind.QUESTION_MARK, Skew.Precedence.ASSIGN).infix = function(context, left) { context.next(); var middle = pratt.parse(context, Skew.Precedence.ASSIGN - 1 | 0); if (!context.expect(Skew.TokenKind.COLON)) { return context.createParseError(); } var right = pratt.parse(context, Skew.Precedence.ASSIGN - 1 | 0); return Skew.Node.createHook(left, middle, right).withRange(context.spanSince(left.range)); }; // XML literals pratt.parselet(Skew.TokenKind.XML_START, Skew.Precedence.LOWEST).prefix = function(context) { var token = context.next(); var tag = Skew.Parsing.parseDotChain(context); if (tag == null) { Skew.Parsing.scanForToken(context, Skew.TokenKind.XML_END); return context.createParseError(); } var attributes = new Skew.Node(Skew.NodeKind.SEQUENCE); // Parse attributes context.skipWhitespace(); while (context.peek1(Skew.TokenKind.IDENTIFIER)) { var name = Skew.Parsing.parseDotChain(context); var assignment = context.current(); var kind = in_IntMap.get(Skew.Parsing.assignmentOperators, assignment.kind, Skew.NodeKind.NULL); if (kind == Skew.NodeKind.NULL) { context.expect(Skew.TokenKind.ASSIGN); Skew.Parsing.scanForToken(context, Skew.TokenKind.XML_END); return context.createParseError(); } context.next(); var value = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.UNARY_PREFIX - 1 | 0); attributes.appendChild(Skew.Node.createBinary(kind, name, value).withRange(context.spanSince(name.range)).withInternalRange(assignment.range)); context.skipWhitespace(); } // Parse end of tag var block = new Skew.Node(Skew.NodeKind.BLOCK); var closingTag = null; context.skipWhitespace(); // Check for children if (!context.eat(Skew.TokenKind.XML_END_EMPTY)) { if (!context.expect(Skew.TokenKind.XML_END)) { Skew.Parsing.scanForToken(context, Skew.TokenKind.XML_END); return context.createParseError(); } // Parse children context.skipWhitespace(); if (!Skew.Parsing.parseStatements(context, block, Skew.Parsing.StatementsMode.NORMAL) || !context.expect(Skew.TokenKind.XML_START_CLOSE)) { return context.createParseError(); } block.withRange(context.spanSince(token.range)); // Parse closing tag closingTag = Skew.Parsing.parseDotChain(context); if (closingTag == null) { Skew.Parsing.scanForToken(context, Skew.TokenKind.XML_END); return context.createParseError(); } context.skipWhitespace(); if (!context.expect(Skew.TokenKind.XML_END)) { Skew.Parsing.scanForToken(context, Skew.TokenKind.XML_END); return context.createParseError(); } // Validate closing tag (not a fatal error) if (!tag.looksTheSameAs(closingTag)) { context.log.syntaxErrorXMLClosingTagMismatch(closingTag.range, Skew.Parsing.dotChainToString(closingTag), Skew.Parsing.dotChainToString(tag), tag.range); } } return Skew.Node.createXML(tag, attributes, block, closingTag).withRange(context.spanSince(token.range)); }; return pratt; }; Skew.Parsing.dotChainToString = function(node) { assert(node.kind == Skew.NodeKind.NAME || node.kind == Skew.NodeKind.DOT); if (node.kind == Skew.NodeKind.NAME) { return node.asString(); } return Skew.Parsing.dotChainToString(node.dotTarget()) + '.' + node.asString(); }; Skew.Parsing.parseDotChain = function(context) { var current = context.current(); if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return null; } var chain = new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent(current.range.toString())).withRange(current.range); while (context.eat(Skew.TokenKind.DOT)) { current = context.current(); if (!context.expect(Skew.TokenKind.IDENTIFIER)) { return null; } chain = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(current.range.toString())).appendChild(chain).withRange(context.spanSince(chain.range)).withInternalRange(current.range); } return chain; }; Skew.Parsing.createTypeParser = function() { var pratt = new Skew.Pratt(); pratt.literal(Skew.TokenKind.DYNAMIC, function(context, token) { return new Skew.Node(Skew.NodeKind.TYPE).withType(Skew.Type.DYNAMIC).withRange(token.range); }); pratt.parselet(Skew.TokenKind.DOT, Skew.Precedence.MEMBER).infix = Skew.Parsing.dotInfixParselet; pratt.parselet(Skew.TokenKind.PARAMETER_LIST_START, Skew.Precedence.MEMBER).infix = Skew.Parsing.parameterizedParselet; // Name expressions or lambda type expressions like "fn(int) int" pratt.parselet(Skew.TokenKind.IDENTIFIER, Skew.Precedence.LOWEST).prefix = function(context) { var node = new Skew.Node(Skew.NodeKind.LAMBDA_TYPE).appendChild(new Skew.Node(Skew.NodeKind.TYPE).withType(Skew.Type.NULL)); var returnType = node.lambdaReturnType(); var token = context.next(); var name = token.range.toString(); var isFirst = true; if (name != 'fn' || !context.eat(Skew.TokenKind.LEFT_PARENTHESIS)) { return new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent(name)).withRange(token.range); } // Parse argument types while (!context.eat(Skew.TokenKind.RIGHT_PARENTHESIS)) { if (!isFirst && !context.expect(Skew.TokenKind.COMMA)) { Skew.Parsing.scanForToken(context, Skew.TokenKind.RIGHT_PARENTHESIS); return context.createParseError(); } node.insertChildBefore(returnType, Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST)); isFirst = false; } // Parse return type if present if (Skew.Parsing.peekType(context)) { returnType.replaceWith(Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST)); } return node.withRange(context.spanSince(token.range)); }; // The "Foo[]" syntax is an error but parse it anyway pratt.parselet(Skew.TokenKind.INDEX, Skew.Precedence.MEMBER).infix = function(context, left) { context.next(); var range = context.spanSince(left.range); context.log.syntaxErrorWrongListSyntax(range, 'List<' + left.range.toString() + '>'); return new Skew.Node(Skew.NodeKind.PARSE_ERROR).withRange(range); }; return pratt; }; Skew.Parsing.parseFile = function(log, tokens, global, warnAboutIgnoredComments) { if (Skew.Parsing.expressionParser == null) { Skew.Parsing.expressionParser = Skew.Parsing.createExpressionParser(); } if (Skew.Parsing.typeParser == null) { Skew.Parsing.typeParser = Skew.Parsing.createTypeParser(); } var context = new Skew.ParserContext(log, tokens, warnAboutIgnoredComments); Skew.Parsing.parseSymbols(context, global, null); context.expect(Skew.TokenKind.END_OF_FILE); }; Skew.Parsing.intLiteral = function(context, token) { return new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.IntContent(Skew.Parsing.parseIntLiteral(context.log, token.range).value)).withRange(token.range); }; Skew.Parsing.stringLiteral = function(context, token) { return Skew.Parsing.createStringNode(context.log, token.range); }; Skew.Parsing.StatementsMode = { NORMAL: 0, C_STYLE_SWITCH: 1 }; Skew.Parsing.ForbiddenGroup = { ASSIGN: 0, COMPARE: 1, EQUAL: 2, LOGICAL: 3 }; Skew.ParserContext = function(log, _tokens, warnAboutIgnoredComments) { this.log = log; this.inNonVoidFunction = false; this._tokens = _tokens; this._index = 0; this.warnAboutIgnoredComments = warnAboutIgnoredComments; this._previousSyntaxError = -1; }; Skew.ParserContext.prototype.current = function() { return in_List.get(this._tokens, this._index); }; Skew.ParserContext.prototype.stealComments = function() { var token = this.current(); var comments = token.comments; token.comments = null; return comments; }; Skew.ParserContext.prototype.next = function() { var token = this.current(); if (this.warnAboutIgnoredComments && token.comments != null) { this.log.syntaxWarningIgnoredCommentInParser(in_List.first(token.comments).range); } if ((this._index + 1 | 0) < this._tokens.length) { this._index = this._index + 1 | 0; } return token; }; Skew.ParserContext.prototype.spanSince = function(range) { var previous = in_List.get(this._tokens, this._index > 0 ? this._index - 1 | 0 : 0); return previous.range.end < range.start ? range : Skew.Range.span(range, previous.range); }; Skew.ParserContext.prototype.peek1 = function(kind) { return this.current().kind == kind; }; Skew.ParserContext.prototype.peek2 = function(kind, skip) { assert(skip >= 1); return in_List.get(this._tokens, Math.min(this._index + skip | 0, this._tokens.length - 1 | 0)).kind == kind; }; Skew.ParserContext.prototype.eat = function(kind) { if (this.peek1(kind)) { this.next(); return true; } return false; }; Skew.ParserContext.prototype.skipWhitespace = function() { this.eat(Skew.TokenKind.NEWLINE); }; Skew.ParserContext.prototype.skipSemicolon = function() { if (this.peek1(Skew.TokenKind.SEMICOLON)) { this.expect(Skew.TokenKind.NEWLINE); this.next(); return true; } return false; }; Skew.ParserContext.prototype.undo = function() { assert(this._index > 0); this._index = this._index - 1 | 0; }; Skew.ParserContext.prototype.expect = function(kind) { if (!this.eat(kind)) { if (this.canReportSyntaxError()) { this.log.syntaxErrorExpectedToken(this.current().range, this.current().kind, kind); } return false; } return true; }; Skew.ParserContext.prototype.unexpectedToken = function() { if (this.canReportSyntaxError()) { this.log.syntaxErrorUnexpectedToken(this.current()); } }; Skew.ParserContext.prototype.createParseError = function() { return new Skew.Node(Skew.NodeKind.PARSE_ERROR).withRange(this.current().range); }; Skew.ParserContext.prototype.canReportSyntaxError = function() { if (this._previousSyntaxError != this._index) { this._previousSyntaxError = this._index; return true; } return false; }; Skew.Parselet = function(precedence) { this.precedence = precedence; this.prefix = null; this.infix = null; }; // A Pratt parser is a parser that associates up to two operations per token, // each with its own precedence. Pratt parsers excel at parsing expression // trees with deeply nested precedence levels. For an excellent writeup, see: // // http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ // Skew.Pratt = function() { this._table = new Map(); }; Skew.Pratt.prototype.parselet = function(kind, precedence) { var parselet = in_IntMap.get(this._table, kind, null); if (parselet == null) { var created = new Skew.Parselet(precedence); parselet = created; in_IntMap.set(this._table, kind, created); } else if (precedence > parselet.precedence) { parselet.precedence = precedence; } return parselet; }; Skew.Pratt.prototype.parse = function(context, precedence) { context.skipWhitespace(); var comments = context.stealComments(); var token = context.current(); var parselet = in_IntMap.get(this._table, token.kind, null); if (parselet == null || parselet.prefix == null) { context.unexpectedToken(); return context.createParseError(); } var node = this.resume(context, precedence, parselet.prefix(context)); // Parselets must set the range of every node assert(node != null && node.range != null); node.comments = Skew.Comment.concat(comments, node.comments); return node; }; Skew.Pratt.prototype.resume = function(context, precedence, left) { while (true) { var kind = context.current().kind; var parselet = in_IntMap.get(this._table, kind, null); if (parselet == null || parselet.infix == null || parselet.precedence <= precedence) { break; } left = parselet.infix(context, left); // Parselets must set the range of every node assert(left != null && left.range != null); } return left; }; Skew.Pratt.prototype.literal = function(kind, callback) { this.parselet(kind, Skew.Precedence.LOWEST).prefix = function(context) { return callback(context, context.next()); }; }; Skew.Pratt.prototype.prefix = function(kind, precedence, callback) { var self = this; self.parselet(kind, Skew.Precedence.LOWEST).prefix = function(context) { var token = context.next(); return callback(context, token, self.parse(context, precedence)); }; }; Skew.Pratt.prototype.postfix = function(kind, precedence, callback) { this.parselet(kind, precedence).infix = function(context, left) { return callback(context, left, context.next()); }; }; Skew.Pratt.prototype.infix = function(kind, precedence, callback) { var self = this; self.parselet(kind, precedence).infix = function(context, left) { var token = context.next(); var comments = context.stealComments(); var right = self.parse(context, precedence); right.comments = Skew.Comment.concat(comments, right.comments); return callback(context, left, token, right); }; }; Skew.Pratt.prototype.infixRight = function(kind, precedence, callback) { var self = this; self.parselet(kind, precedence).infix = function(context, left) { var token = context.next(); // Subtract 1 for right-associativity return callback(context, left, token, self.parse(context, precedence - 1 | 0)); }; }; Skew.LineColumn = function(line, column) { this.line = line; this.column = column; }; Skew.Source = function(name, contents) { this.name = name; this.contents = contents; this._lineOffsets = null; }; Skew.Source.prototype.entireRange = function() { return new Skew.Range(this, 0, this.contents.length); }; Skew.Source.prototype.lineCount = function() { this._computeLineOffsets(); // Ignore the line offset at 0 return this._lineOffsets.length - 1 | 0; }; Skew.Source.prototype.contentsOfLine = function(line) { this._computeLineOffsets(); if (line < 0 || line >= this._lineOffsets.length) { return ''; } var start = in_List.get(this._lineOffsets, line); var end = (line + 1 | 0) < this._lineOffsets.length ? in_List.get(this._lineOffsets, line + 1 | 0) - 1 | 0 : this.contents.length; return in_string.slice2(this.contents, start, end); }; Skew.Source.prototype.indexToLineColumn = function(index) { this._computeLineOffsets(); // Binary search to find the line var count = this._lineOffsets.length; var line = 0; while (count > 0) { var step = count / 2 | 0; var i = line + step | 0; if (in_List.get(this._lineOffsets, i) <= index) { line = i + 1 | 0; count = (count - step | 0) - 1 | 0; } else { count = step; } } // Use the line to compute the column var column = line > 0 ? index - in_List.get(this._lineOffsets, line - 1 | 0) | 0 : index; return new Skew.LineColumn(line - 1 | 0, column); }; Skew.Source.prototype._computeLineOffsets = function() { if (this._lineOffsets == null) { this._lineOffsets = [0]; for (var i = 0, count = this.contents.length; i < count; i = i + 1 | 0) { if (in_string.get1(this.contents, i) == 10) { this._lineOffsets.push(i + 1 | 0); } } } }; Skew.ShakingMode = { USE_TYPES: 0, IGNORE_TYPES: 1 }; // This stores a mapping from every symbol to its immediate dependencies and // uses that to provide a mapping from a subset of symbols to their complete // dependencies. This is useful for dead code elimination. Skew.UsageGraph = function(global, mode) { this._mode = 0; this.context = null; this._currentUsages = null; this._overridesForSymbol = new Map(); this._usages = new Map(); this._allSymbols = new Map(); this._mode = mode; this._visitObject(global); this._changeContext(null); }; Skew.UsageGraph.prototype.usagesForSymbols = function(symbols) { var overridesToCheck = new Map(); var combinedUsages = new Map(); var stack = []; in_List.append1(stack, symbols); // Iterate until a fixed point is reached while (!(stack.length == 0)) { var symbol = in_List.takeLast(stack); if (!combinedUsages.has(symbol.id)) { in_IntMap.set(combinedUsages, symbol.id, symbol); var symbolUsages = in_IntMap.get(this._usages, symbol.id, null); if (symbolUsages != null) { in_List.append1(stack, symbolUsages); } // Handle function overrides if (Skew.in_SymbolKind.isFunction(symbol.kind)) { var overridden = symbol.asFunctionSymbol().overridden; var symbolOverrides = in_IntMap.get(this._overridesForSymbol, symbol.id, null); // Automatically include all overridden functions in case the use // of this type is polymorphic, which is a conservative estimate if (overridden != null) { stack.push(overridden); } // Check function overrides too if (symbolOverrides != null) { for (var i = 0, list = symbolOverrides, count = list.length; i < count; i = i + 1 | 0) { var override = in_List.get(list, i); var key = override.parent.id; // Queue this override immediately if the parent type is used if (combinedUsages.has(key)) { stack.push(override); } // Otherwise, remember this override for later if the parent type ends up being used else { var overrides = in_IntMap.get(overridesToCheck, key, null); if (overrides == null) { overrides = []; in_IntMap.set(overridesToCheck, key, overrides); } overrides.push(override); } } } } // Handle overrides dependent on this type else if (Skew.in_SymbolKind.isType(symbol.kind)) { var overrides1 = in_IntMap.get(overridesToCheck, symbol.id, null); if (overrides1 != null) { in_List.append1(stack, overrides1); } } } } return combinedUsages; }; Skew.UsageGraph.prototype._changeContext = function(symbol) { if (this.context != null) { var values = Array.from(this._currentUsages.values()); // Sort so the order is deterministic values.sort(Skew.Symbol.SORT_BY_ID); in_IntMap.set(this._usages, this.context.id, values); } this._currentUsages = new Map(); if (symbol != null) { this._includeSymbol(symbol); in_IntMap.set(this._currentUsages, symbol.id, symbol); } this.context = symbol; }; Skew.UsageGraph.prototype._recordOverride = function(base, derived) { var overrides = in_IntMap.get(this._overridesForSymbol, base.id, null); if (overrides == null) { overrides = []; in_IntMap.set(this._overridesForSymbol, base.id, overrides); } overrides.push(derived); }; Skew.UsageGraph.prototype._recordUsage = function(symbol) { this._includeSymbol(symbol); if (!Skew.in_SymbolKind.isLocal(symbol.kind)) { in_IntMap.set(this._currentUsages, symbol.id, symbol); } }; Skew.UsageGraph.prototype._visitObject = function(symbol) { for (var i3 = 0, list3 = symbol.objects, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var object = in_List.get(list3, i3); this._changeContext(object); this._recordUsage(symbol); // Always pull the base class in if (object.baseClass != null) { this._recordUsage(object.baseClass); } // Only pull interfaces in for typed targets (interfaces disappear entirely for untyped targets) if (this._mode != Skew.ShakingMode.IGNORE_TYPES && object.interfaceTypes != null) { for (var i = 0, list = object.interfaceTypes, count = list.length; i < count; i = i + 1 | 0) { var type = in_List.get(list, i); if (type.symbol != null) { this._recordUsage(type.symbol); } } } // If an imported type is used, automatically assume all functions and // variables for that type are used too if (object.isImported()) { for (var i1 = 0, list1 = object.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); this._recordUsage($function); } for (var i2 = 0, list2 = object.functions, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); this._recordUsage(variable); } } this._visitObject(object); } for (var i4 = 0, list4 = symbol.functions, count4 = list4.length; i4 < count4; i4 = i4 + 1 | 0) { var function1 = in_List.get(list4, i4); this._changeContext(function1); // Instance functions shouldn't cause their instance type to be emitted for dynamically-typed targets if (this._mode != Skew.ShakingMode.IGNORE_TYPES || function1.kind != Skew.SymbolKind.FUNCTION_INSTANCE) { this._recordUsage(symbol); } this._visitFunction(function1); } for (var i5 = 0, list5 = symbol.variables, count5 = list5.length; i5 < count5; i5 = i5 + 1 | 0) { var variable1 = in_List.get(list5, i5); this._changeContext(variable1); // Instance variables shouldn't require the class to be present because // accessing an instance variable already requires a constructed instance if (variable1.kind != Skew.SymbolKind.VARIABLE_INSTANCE) { this._recordUsage(symbol); } this._visitVariable(variable1); } }; Skew.UsageGraph.prototype._visitFunction = function(symbol) { for (var i = 0, list = symbol.$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); this._visitVariable(argument); } this._visitType(symbol.resolvedType.returnType); this._visitNode(symbol.block); // Remember which functions are overridden for later if (symbol.overridden != null) { this._recordOverride(symbol.overridden, symbol); } // Remember which functions are overridden for later if (symbol.implementations != null) { for (var i1 = 0, list1 = symbol.implementations, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); this._recordOverride(symbol, $function); this._recordOverride($function, symbol); } } }; Skew.UsageGraph.prototype._visitVariable = function(symbol) { this._visitType(symbol.resolvedType); this._visitNode(symbol.value); }; Skew.UsageGraph.prototype._visitNode = function(node) { if (node == null) { return; } if (node.kind == Skew.NodeKind.CAST) { this._visitNode(node.castValue()); this._visitType(node.castType().resolvedType); } // This is necessary to preserve the types of constant-folded enums in typed languages else if (node.kind == Skew.NodeKind.CONSTANT && this._mode == Skew.ShakingMode.USE_TYPES) { this._visitType(node.resolvedType); } else { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._visitNode(child); } } if (node.symbol != null) { this._recordUsage(node.symbol); } switch (node.kind) { // The function block is a child node and has already been visited case Skew.NodeKind.LAMBDA: { var $function = node.symbol.asFunctionSymbol(); for (var i = 0, list = $function.$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); this._visitVariable(argument); } this._visitType($function.resolvedType.returnType); break; } // The variable value is a child node and has already been visited case Skew.NodeKind.VARIABLE: { this._visitType(node.symbol.asVariableSymbol().resolvedType); break; } } }; Skew.UsageGraph.prototype._visitType = function(type) { if (this._mode == Skew.ShakingMode.USE_TYPES && type != null && type.symbol != null) { this._recordUsage(type.symbol); // This should be a tree too, so infinite loops should not happen if (type.isParameterized()) { for (var i = 0, list = type.substitutions, count = list.length; i < count; i = i + 1 | 0) { var substitution = in_List.get(list, i); this._visitType(substitution); } } } }; Skew.UsageGraph.prototype._includeSymbol = function(symbol) { in_IntMap.set(this._allSymbols, symbol.id, symbol); }; Skew.Shaking = {}; Skew.Shaking.collectExportedSymbols = function(symbol, symbols, entryPoint) { for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); Skew.Shaking.collectExportedSymbols(object, symbols, entryPoint); if (object.isExported()) { symbols.push(object); } } for (var i1 = 0, list1 = symbol.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); if ($function.isExported() || $function == entryPoint) { symbols.push($function); } } for (var i2 = 0, list2 = symbol.variables, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); if (variable.isExported()) { symbols.push(variable); } } }; Skew.Shaking.removeUnusedSymbols = function(symbol, usages) { in_List.removeIf(symbol.objects, function(object) { return !usages.has(object.id); }); in_List.removeIf(symbol.functions, function($function) { return !usages.has($function.id); }); in_List.removeIf(symbol.variables, function(variable) { return !usages.has(variable.id); }); for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); Skew.Shaking.removeUnusedSymbols(object, usages); } }; Skew.Resolving = {}; Skew.Resolving.ConversionKind = { IMPLICIT: 0, EXPLICIT: 1 }; Skew.Resolving.SymbolStatistic = { READ: 0, WRITE: 1 }; Skew.Resolving.LocalVariableStatistics = function(symbol) { this.symbol = symbol; this.readCount = 0; this.writeCount = 0; }; Skew.Resolving.LocalVariableStatistics.SORT_BY_ID = function(a, b) { return in_int.compare(a.symbol.id, b.symbol.id); }; Skew.Resolving.Resolver = function(_global, _options, _defines, _cache, _log) { this._global = _global; this._options = _options; this._defines = _defines; this._cache = _cache; this._log = _log; this._foreachLoops = []; this._localVariableStatistics = new Map(); this._controlFlow = new Skew.ControlFlowAnalyzer(); this._generatedGlobalVariables = []; this._constantFolder = null; this._isMergingGuards = true; }; Skew.Resolving.Resolver.prototype.resolve = function() { var self = this; self._constantFolder = new Skew.Folding.ConstantFolder(self._cache, self._options, function(symbol) { self._initializeSymbol(symbol); }); self._initializeGlobals(); self._iterativelyMergeGuards(); self._resolveGlobal(); self._removeObsoleteFunctions(self._global); in_List.insert1(self._global.variables, 0, self._generatedGlobalVariables); }; // Put the guts of the function inside another function because V8 doesn't // optimize functions with try-catch statements Skew.Resolving.Resolver.prototype._initializeSymbolSwitch = function(symbol) { switch (symbol.kind) { case Skew.SymbolKind.OBJECT_CLASS: case Skew.SymbolKind.OBJECT_ENUM: case Skew.SymbolKind.OBJECT_FLAGS: case Skew.SymbolKind.OBJECT_GLOBAL: case Skew.SymbolKind.OBJECT_INTERFACE: case Skew.SymbolKind.OBJECT_NAMESPACE: case Skew.SymbolKind.OBJECT_WRAPPED: { this._initializeObject(symbol.asObjectSymbol()); break; } case Skew.SymbolKind.FUNCTION_ANNOTATION: case Skew.SymbolKind.FUNCTION_CONSTRUCTOR: case Skew.SymbolKind.FUNCTION_GLOBAL: case Skew.SymbolKind.FUNCTION_INSTANCE: case Skew.SymbolKind.FUNCTION_LOCAL: { this._initializeFunction(symbol.asFunctionSymbol()); break; } case Skew.SymbolKind.VARIABLE_ARGUMENT: case Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS: case Skew.SymbolKind.VARIABLE_GLOBAL: case Skew.SymbolKind.VARIABLE_INSTANCE: case Skew.SymbolKind.VARIABLE_LOCAL: { this._initializeVariable(symbol.asVariableSymbol()); break; } case Skew.SymbolKind.PARAMETER_FUNCTION: case Skew.SymbolKind.PARAMETER_OBJECT: { this._initializeParameter(symbol.asParameterSymbol()); break; } case Skew.SymbolKind.OVERLOADED_ANNOTATION: case Skew.SymbolKind.OVERLOADED_GLOBAL: case Skew.SymbolKind.OVERLOADED_INSTANCE: { this._initializeOverloadedFunction(symbol.asOverloadedFunctionSymbol()); break; } default: { assert(false); break; } } }; Skew.Resolving.Resolver.prototype._initializeSymbol = function(symbol) { // The scope should have been set by the merging pass (or by this pass for local variables) assert(symbol.scope != null); // Only initialize the symbol once if (symbol.state == Skew.SymbolState.UNINITIALIZED) { symbol.state = Skew.SymbolState.INITIALIZING; try { this._initializeSymbolSwitch(symbol); } catch (failure) { if (failure instanceof Skew.Resolving.Resolver.GuardMergingFailure) { symbol.state = Skew.SymbolState.UNINITIALIZED; throw failure; } else { throw failure; } } assert(symbol.resolvedType != null); symbol.state = Skew.SymbolState.INITIALIZED; if (Skew.in_SymbolKind.isFunction(symbol.kind)) { var $function = symbol.asFunctionSymbol(); var overloaded = $function.overloaded; // After initializing a function symbol, ensure the entire overload set is initialized if (overloaded != null && overloaded.state == Skew.SymbolState.UNINITIALIZED) { this._initializeSymbol(overloaded); } } } // Detect cyclic symbol references such as "foo foo;" else if (symbol.state == Skew.SymbolState.INITIALIZING) { this._log.semanticErrorCyclicDeclaration(symbol.range, symbol.name); symbol.resolvedType = Skew.Type.DYNAMIC; } }; Skew.Resolving.Resolver.prototype._validateEntryPoint = function(symbol) { // Detect duplicate entry points if (this._cache.entryPointSymbol != null) { this._log.semanticErrorDuplicateEntryPoint(symbol.range, this._cache.entryPointSymbol.range); return; } this._cache.entryPointSymbol = symbol; // Only recognize a few entry point types var type = symbol.resolvedType; if (type != Skew.Type.DYNAMIC) { var argumentTypes = type.argumentTypes; // The argument list must be empty or one argument of type "List" if (argumentTypes.length > 1 || argumentTypes.length == 1 && in_List.first(argumentTypes) != this._cache.createListType(this._cache.stringType)) { this._log.semanticErrorInvalidEntryPointArguments(Skew.Range.span(in_List.first(symbol.$arguments).range, in_List.last(symbol.$arguments).type.range), symbol.name); } // The return type must be nothing or "int" else if (type.returnType != null && type.returnType != this._cache.intType) { this._log.semanticErrorInvalidEntryPointReturnType(symbol.returnType.range, symbol.name); } } }; Skew.Resolving.Resolver.prototype._resolveDefines = function(symbol) { var key = symbol.fullName(); var define = in_StringMap.get(this._defines, key, null); if (define == null) { return; } // Remove the define so we can tell what defines weren't used later on this._defines.delete(key); var type = symbol.resolvedType; var range = define.value; var value = range.toString(); var node = null; // Special-case booleans if (type == this._cache.boolType) { if (value == 'true' || value == 'false') { node = new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.BoolContent(value == 'true')); } } // Special-case doubles else if (type == this._cache.doubleType) { var number = +value; if (!isNaN(number)) { node = new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.DoubleContent(number)); } } // Special-case strings else if (type == this._cache.stringType) { node = new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.StringContent(value)); } // Special-case enums else if (type.isEnumOrFlags()) { node = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(value)).appendChild(null); } // Integers can also apply to doubles if (node == null && this._cache.isNumeric(type)) { var box = Skew.Parsing.parseIntLiteral(this._log, range); if (box != null) { node = new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.IntContent(box.value)); } } // Stop if anything failed above if (node == null) { this._log.semanticErrorInvalidDefine1(range, value, type, key); return; } this._resolveAsParameterizedExpressionWithConversion(node.withRange(range), this._global.scope, type); symbol.value = node; }; Skew.Resolving.Resolver.prototype._resolveAnnotations = function(symbol) { var self = this; var parent = symbol.parent; var annotations = symbol.annotations; // The import/export annotations are inherited, except import isn't inherited for implemented functions if (parent != null) { symbol.flags |= parent.flags & (Skew.in_SymbolKind.isFunction(symbol.kind) && symbol.asFunctionSymbol().block != null ? Skew.SymbolFlags.IS_EXPORTED : Skew.SymbolFlags.IS_IMPORTED | Skew.SymbolFlags.IS_EXPORTED); } // Resolve annotations on this symbol after annotation inheritance. Don't // use removeIf() since this annotation list may be shared elsewhere. if (annotations != null) { symbol.annotations = annotations.filter(function(annotation) { return self._resolveAnnotation(annotation, symbol); }); } // Protected access used to be an annotation. It's now indicated with just // a leading underscore. if (symbol.name.startsWith('_') && !Skew.in_SymbolKind.isLocal(symbol.kind)) { symbol.flags |= Skew.SymbolFlags.IS_PROTECTED; } }; Skew.Resolving.Resolver.prototype._resolveParameters = function(parameters) { if (parameters != null) { for (var i = 0, list = parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); this._resolveParameter(parameter); } } }; Skew.Resolving.Resolver.prototype._initializeParameter = function(symbol) { if (symbol.resolvedType == null) { symbol.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, symbol); } this._resolveAnnotations(symbol); }; Skew.Resolving.Resolver.prototype._resolveParameter = function(symbol) { this._initializeSymbol(symbol); }; Skew.Resolving.Resolver.prototype._initializeObject = function(symbol) { var ref; var kind = symbol.kind; var $extends = symbol.$extends; var $implements = symbol.$implements; if (symbol.resolvedType == null) { symbol.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, symbol); } this._resolveParameters(symbol.parameters); // Resolve the base type (only for classes and wrapped types) if ($extends != null) { this._resolveAsParameterizedType($extends, symbol.scope); var baseType = $extends.resolvedType; if (kind == Skew.SymbolKind.OBJECT_WRAPPED) { symbol.wrappedType = baseType; // Don't lose the type parameters from the base type symbol.resolvedType.environment = baseType.environment; } else if (kind != Skew.SymbolKind.OBJECT_CLASS || baseType != Skew.Type.DYNAMIC && (!baseType.isClass() || baseType.symbol.isValueType())) { this._log.semanticErrorInvalidExtends($extends.range, baseType); } else if (baseType != Skew.Type.DYNAMIC) { symbol.baseType = baseType; symbol.baseClass = baseType.symbol.asObjectSymbol(); // Don't lose the type parameters from the base type symbol.resolvedType.environment = baseType.environment; // Copy members from the base type var functions = []; var members = Array.from(symbol.baseClass.members.values()); // Sort so the order is deterministic members.sort(Skew.Symbol.SORT_BY_ID); for (var i2 = 0, list1 = members, count1 = list1.length; i2 < count1; i2 = i2 + 1 | 0) { var member = in_List.get(list1, i2); var memberKind = member.kind; // Separate out functions if (Skew.in_SymbolKind.isFunction(memberKind)) { if (memberKind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { functions.push(member.asFunctionSymbol()); } } // Include overloaded functions individually else if (Skew.in_SymbolKind.isOverloadedFunction(memberKind)) { for (var i1 = 0, list = member.asOverloadedFunctionSymbol().symbols, count = list.length; i1 < count; i1 = i1 + 1 | 0) { var $function = in_List.get(list, i1); if ($function.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { functions.push($function); } } } // Other kinds else if (!Skew.in_SymbolKind.isParameter(memberKind)) { var other = in_StringMap.get(symbol.members, member.name, null); if (other != null) { this._log.semanticErrorBadOverride(other.range, other.name, baseType, member.range); } else { in_StringMap.set(symbol.members, member.name, member); } } } Skew.Merging.mergeFunctions(this._log, symbol, functions, Skew.Merging.MergeBehavior.INTO_DERIVED_CLASS); } } // Wrapped types without something to wrap don't make sense else if (kind == Skew.SymbolKind.OBJECT_WRAPPED) { this._log.semanticErrorMissingWrappedType(symbol.range, symbol.fullName()); // Make sure to fill out the wrapped type anyway so code that tries to // access it doesn't crash. The dynamic type should ignore further errors. symbol.wrappedType = Skew.Type.DYNAMIC; } // Resolve the base interface types if ($implements != null) { symbol.interfaceTypes = []; for (var i = 0, count2 = $implements.length; i < count2; i = i + 1 | 0) { var type = in_List.get($implements, i); this._resolveAsParameterizedType(type, symbol.scope); // Ignore the dynamic type, which will be from errors and dynamic expressions used for exports var interfaceType = type.resolvedType; if (interfaceType == Skew.Type.DYNAMIC) { continue; } // Only classes can derive from interfaces if (kind != Skew.SymbolKind.OBJECT_CLASS || !interfaceType.isInterface()) { this._log.semanticErrorInvalidImplements(type.range, interfaceType); continue; } // An interface can only be implemented once for (var j = 0; j < i; j = j + 1 | 0) { var other1 = in_List.get($implements, j); if (other1.resolvedType == interfaceType) { this._log.semanticErrorDuplicateImplements(type.range, interfaceType, other1.range); break; } } symbol.interfaceTypes.push(interfaceType); } } // Assign values for all enums and flags before they are initialized if (Skew.in_SymbolKind.isEnumOrFlags(kind)) { var nextValue = 0; for (var i3 = 0, list2 = symbol.variables, count3 = list2.length; i3 < count3; i3 = i3 + 1 | 0) { var variable = in_List.get(list2, i3); if (variable.kind == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS) { if (nextValue >= 32 && kind == Skew.SymbolKind.OBJECT_FLAGS) { this._log.semanticErrorTooManyFlags(variable.range, symbol.name); } variable.value = new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.IntContent(kind == Skew.SymbolKind.OBJECT_FLAGS ? 1 << nextValue : nextValue)).withType(symbol.resolvedType).withRange(variable.range); nextValue = nextValue + 1 | 0; } } symbol.flags |= Skew.SymbolFlags.IS_VALUE_TYPE; } this._resolveAnnotations(symbol); // Create a default constructor if one doesn't exist var $constructor = in_StringMap.get(symbol.members, 'new', null); if (kind == Skew.SymbolKind.OBJECT_CLASS && !symbol.isImported() && $constructor == null) { var baseConstructor = (ref = symbol.baseClass) != null ? in_StringMap.get(ref.members, 'new', null) : null; // Unwrap the overload group if present if (baseConstructor != null && baseConstructor.kind == Skew.SymbolKind.OVERLOADED_GLOBAL) { var overloaded = baseConstructor.asOverloadedFunctionSymbol(); for (var i4 = 0, list3 = overloaded.symbols, count4 = list3.length; i4 < count4; i4 = i4 + 1 | 0) { var overload = in_List.get(list3, i4); if (overload.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { if (baseConstructor.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { // Signal that there isn't a single base constructor baseConstructor = null; break; } baseConstructor = overload; } } } // A default constructor can only be created if the base class has a single constructor if (symbol.baseClass == null || baseConstructor != null && baseConstructor.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { var generated = new Skew.FunctionSymbol(Skew.SymbolKind.FUNCTION_CONSTRUCTOR, 'new'); generated.scope = new Skew.FunctionScope(symbol.scope, generated); generated.flags |= Skew.SymbolFlags.IS_AUTOMATICALLY_GENERATED; generated.parent = symbol; generated.range = symbol.range; generated.overridden = baseConstructor != null ? baseConstructor.asFunctionSymbol() : null; symbol.functions.push(generated); in_StringMap.set(symbol.members, generated.name, generated); } } // Create a default toString if one doesn't exist if (kind == Skew.SymbolKind.OBJECT_ENUM && !symbol.isImported() && !symbol.members.has('toString')) { var generated1 = new Skew.FunctionSymbol(Skew.SymbolKind.FUNCTION_INSTANCE, 'toString'); generated1.scope = new Skew.FunctionScope(symbol.scope, generated1); generated1.flags |= Skew.SymbolFlags.IS_AUTOMATICALLY_GENERATED | Skew.SymbolFlags.IS_INLINING_FORCED; this._options.isAlwaysInlinePresent = true; generated1.parent = symbol; generated1.range = symbol.range; symbol.functions.push(generated1); in_StringMap.set(symbol.members, generated1.name, generated1); } }; Skew.Resolving.Resolver.prototype._checkInterfacesAndAbstractStatus1 = function(object, $function) { assert($function.kind == Skew.SymbolKind.FUNCTION_INSTANCE); assert($function.state == Skew.SymbolState.INITIALIZED); if (!object.isAbstract() && !$function.isImported() && !$function.isObsolete() && $function.block == null) { object.isAbstractBecauseOf = $function; } }; Skew.Resolving.Resolver.prototype._checkInterfacesAndAbstractStatus2 = function(symbol) { assert(symbol.state == Skew.SymbolState.INITIALIZED); if (symbol.hasCheckedInterfacesAndAbstractStatus || symbol.kind != Skew.SymbolKind.OBJECT_CLASS) { return; } symbol.hasCheckedInterfacesAndAbstractStatus = true; // Check to see if this class is abstract (has unimplemented members) var members = Array.from(symbol.members.values()); // Sort so the order is deterministic members.sort(Skew.Symbol.SORT_BY_ID); for (var i1 = 0, list1 = members, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var member = in_List.get(list1, i1); if (member.kind == Skew.SymbolKind.OVERLOADED_INSTANCE) { this._initializeSymbol(member); for (var i = 0, list = member.asOverloadedFunctionSymbol().symbols, count = list.length; i < count; i = i + 1 | 0) { var $function = in_List.get(list, i); this._checkInterfacesAndAbstractStatus1(symbol, $function); } } else if (member.kind == Skew.SymbolKind.FUNCTION_INSTANCE) { this._initializeSymbol(member); this._checkInterfacesAndAbstractStatus1(symbol, member.asFunctionSymbol()); } if (symbol.isAbstract()) { break; } } // Check interfaces for missing implementations if (symbol.interfaceTypes != null) { for (var i4 = 0, list4 = symbol.interfaceTypes, count4 = list4.length; i4 < count4; i4 = i4 + 1 | 0) { var interfaceType = in_List.get(list4, i4); for (var i3 = 0, list3 = interfaceType.symbol.asObjectSymbol().functions, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var function1 = in_List.get(list3, i3); if (function1.kind != Skew.SymbolKind.FUNCTION_INSTANCE || function1.block != null) { continue; } this._initializeSymbol(function1); var member1 = in_StringMap.get(symbol.members, function1.name, null); var match = null; var equivalence = Skew.TypeCache.Equivalence.NOT_EQUIVALENT; // Search for a matching function if (member1 != null) { this._initializeSymbol(member1); if (member1.kind == Skew.SymbolKind.OVERLOADED_INSTANCE) { for (var i2 = 0, list2 = member1.asOverloadedFunctionSymbol().symbols, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var other = in_List.get(list2, i2); equivalence = this._cache.areFunctionSymbolsEquivalent(function1, interfaceType.environment, other, null); if (equivalence != Skew.TypeCache.Equivalence.NOT_EQUIVALENT) { match = other; break; } } } else if (member1.kind == Skew.SymbolKind.FUNCTION_INSTANCE) { equivalence = this._cache.areFunctionSymbolsEquivalent(function1, interfaceType.environment, member1.asFunctionSymbol(), null); if (equivalence != Skew.TypeCache.Equivalence.NOT_EQUIVALENT) { match = member1.asFunctionSymbol(); } } } // Validate use of the interface if (match == null) { this._log.semanticErrorBadInterfaceImplementation(symbol.range, symbol.resolvedType, interfaceType, function1.name, function1.range); } else if (equivalence == Skew.TypeCache.Equivalence.EQUIVALENT_EXCEPT_RETURN_TYPE) { var returnType = function1.resolvedType.returnType; if (returnType != null) { returnType = this._cache.substitute(returnType, interfaceType.environment); } this._log.semanticErrorBadInterfaceImplementationReturnType(match.range, match.name, match.resolvedType.returnType, this._cache.substituteFunctionParameters(returnType, match, function1), interfaceType, function1.range); } else { if (function1.implementations == null) { function1.implementations = []; } function1.implementations.push(match); } } } } }; Skew.Resolving.Resolver.prototype._initializeGlobals = function() { this._initializeSymbol(this._cache.boolType.symbol); this._initializeSymbol(this._cache.doubleType.symbol); this._initializeSymbol(this._cache.intMapType.symbol); this._initializeSymbol(this._cache.intType.symbol); this._initializeSymbol(this._cache.listType.symbol); this._initializeSymbol(this._cache.stringMapType.symbol); this._initializeSymbol(this._cache.stringType.symbol); }; Skew.Resolving.Resolver.prototype._resolveGlobal = function() { this._resolveObject(this._global); this._scanLocalVariables(); this._convertForeachLoops(); this._discardUnusedDefines(); }; // An obsolete function is one without an implementation that was dropped in // favor of one with an implementation: // // namespace Foo { // def foo {} // // # This will be marked as obsolete // def foo // } // Skew.Resolving.Resolver.prototype._removeObsoleteFunctions = function(symbol) { for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._removeObsoleteFunctions(object); } in_List.removeIf(symbol.functions, function($function) { return $function.isObsolete(); }); }; Skew.Resolving.Resolver.prototype._iterativelyMergeGuards = function() { var guards = null; // Iterate until a fixed point is reached while (true) { guards = []; this._scanForGuards(this._global, guards); if (guards.length == 0) { break; } // Each iteration must remove at least one guard to continue if (!this._processGuards(guards)) { break; } } this._isMergingGuards = false; // All remaining guards are errors for (var i = 0, list = guards, count1 = list.length; i < count1; i = i + 1 | 0) { var guard = in_List.get(list, i); var count = this._log.errorCount(); this._resolveAsParameterizedExpressionWithConversion(guard.test, guard.parent.scope, this._cache.boolType); if (this._log.errorCount() == count) { this._log.semanticErrorExpectedConstant(guard.test.range); } } }; Skew.Resolving.Resolver.prototype._scanForGuards = function(symbol, guards) { if (symbol.guards != null) { in_List.append1(guards, symbol.guards); } for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._scanForGuards(object, guards); } }; Skew.Resolving.Resolver.prototype._reportGuardMergingFailure = function(node) { if (this._isMergingGuards) { throw new Skew.Resolving.Resolver.GuardMergingFailure(); } }; Skew.Resolving.Resolver.prototype._attemptToResolveGuardConstant = function(node, scope) { assert(scope != null); try { this._resolveAsParameterizedExpressionWithConversion(node, scope, this._cache.boolType); this._constantFolder.foldConstants(node); return true; } catch (failure) { if (!(failure instanceof Skew.Resolving.Resolver.GuardMergingFailure)) { throw failure; } } return false; }; Skew.Resolving.Resolver.prototype._processGuards = function(guards) { var wasGuardRemoved = false; for (var i = 0, list = guards, count = list.length; i < count; i = i + 1 | 0) { var guard = in_List.get(list, i); var test = guard.test; var parent = guard.parent; // If it's not a constant, we'll just try again in the next iteration if (!this._attemptToResolveGuardConstant(test, parent.scope)) { continue; } if (test.isBool()) { in_List.removeOne(parent.guards, guard); wasGuardRemoved = true; if (test.isTrue()) { this._mergeGuardIntoObject(guard, parent); } else { var elseGuard = guard.elseGuard; if (elseGuard != null) { if (elseGuard.test != null) { elseGuard.parent = parent; parent.guards.push(elseGuard); } else { this._mergeGuardIntoObject(elseGuard, parent); } } } } } return wasGuardRemoved; }; Skew.Resolving.Resolver.prototype._mergeGuardIntoObject = function(guard, object) { var symbol = guard.contents; Skew.Merging.mergeObjects(this._log, object, symbol.objects); Skew.Merging.mergeFunctions(this._log, object, symbol.functions, Skew.Merging.MergeBehavior.NORMAL); Skew.Merging.mergeVariables(this._log, object, symbol.variables); in_List.append1(object.objects, symbol.objects); in_List.append1(object.functions, symbol.functions); in_List.append1(object.variables, symbol.variables); // Handle nested guard clauses like this: // // if true { // if true { // var foo = 0 // } // } // if (symbol.guards != null) { for (var i = 0, list = symbol.guards, count = list.length; i < count; i = i + 1 | 0) { var nested = in_List.get(list, i); object.guards.push(nested); for (var g = nested; g != null; g = g.elseGuard) { g.parent = object; g.contents.parent = object; } } } }; // Foreach loops are converted to for loops after everything is resolved // because that process needs to generate symbol names and it's much easier // to generate non-conflicting symbol names after all local variables have // been defined. Skew.Resolving.Resolver.prototype._convertForeachLoops = function() { for (var i = 0, list1 = this._foreachLoops, count2 = list1.length; i < count2; i = i + 1 | 0) { var node = in_List.get(list1, i); var symbol = node.symbol.asVariableSymbol(); // Generate names at the function level to avoid conflicts with local scopes var scope = symbol.scope.findEnclosingFunctionOrLambda(); var value = node.foreachValue(); var block = node.foreachBlock(); // Handle "for i in 0..10" if (value.kind == Skew.NodeKind.PAIR) { var first = value.firstValue(); var second = value.secondValue(); symbol.flags &= ~Skew.SymbolFlags.IS_CONST; symbol.value = first.remove(); var setup = new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(Skew.Node.createVariable(symbol)); var symbolName = Skew.Node.createSymbolReference(symbol); var update = Skew.Node.createUnary(Skew.NodeKind.PREFIX_INCREMENT, symbolName); var test = null; // Special-case constant iteration limits to generate simpler code if (second.kind == Skew.NodeKind.CONSTANT || second.kind == Skew.NodeKind.NAME && second.symbol != null && second.symbol.isConst()) { test = Skew.Node.createBinary(Skew.NodeKind.LESS_THAN, symbolName.clone(), second.remove()); } // Otherwise, save the iteration limit in case it changes during iteration else { var count = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, scope.generateName('count')); count.initializeWithType(this._cache.intType); count.value = second.remove(); setup.appendChild(Skew.Node.createVariable(count)); test = Skew.Node.createBinary(Skew.NodeKind.LESS_THAN, symbolName.clone(), Skew.Node.createSymbolReference(count)); } // Use a C-style for loop to implement this foreach loop node.become(Skew.Node.createFor(setup, test, update, block.remove()).withComments(node.comments).withRange(node.range)); // Make sure the new expressions are resolved this._resolveNode(test, symbol.scope, null); this._resolveNode(update, symbol.scope, null); } else if (this._cache.isList(value.resolvedType) && !this._options.target.supportsListForeach()) { // Create the index variable var index = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, scope.generateName('i')); index.initializeWithType(this._cache.intType); index.value = this._cache.createInt(0); var setup1 = new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(Skew.Node.createVariable(index)); var indexName = Skew.Node.createSymbolReference(index); // Create the list variable var list = null; if (value.kind == Skew.NodeKind.NAME && value.symbol != null && Skew.in_SymbolKind.isVariable(value.symbol.kind) && value.symbol.isConst()) { list = value.symbol.asVariableSymbol(); } else { list = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, scope.generateName('list')); list.initializeWithType(value.resolvedType); list.value = value.remove(); setup1.appendChild(Skew.Node.createVariable(list)); } var listName = Skew.Node.createSymbolReference(list); // Create the count variable var count1 = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, scope.generateName('count')); count1.initializeWithType(this._cache.intType); count1.value = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent('count')).appendChild(listName); setup1.appendChild(Skew.Node.createVariable(count1)); var countName = Skew.Node.createSymbolReference(count1); // Move the loop variable into the loop body symbol.value = Skew.Node.createIndex(listName.clone(), indexName); block.prependChild(new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(Skew.Node.createVariable(symbol))); // Use a C-style for loop to implement this foreach loop var test1 = Skew.Node.createBinary(Skew.NodeKind.LESS_THAN, indexName.clone(), countName); var update1 = Skew.Node.createUnary(Skew.NodeKind.PREFIX_INCREMENT, indexName.clone()); node.become(Skew.Node.createFor(setup1, test1, update1, block.remove()).withComments(node.comments).withRange(node.range)); // Make sure the new expressions are resolved this._resolveNode(symbol.value, symbol.scope, null); this._resolveNode(count1.value, symbol.scope, null); this._resolveNode(test1, symbol.scope, null); this._resolveNode(update1, symbol.scope, null); } } }; Skew.Resolving.Resolver.prototype._scanLocalVariables = function() { var values = Array.from(this._localVariableStatistics.values()); // Sort so the order is deterministic values.sort(Skew.Resolving.LocalVariableStatistics.SORT_BY_ID); for (var i = 0, list = values, count = list.length; i < count; i = i + 1 | 0) { var info = in_List.get(list, i); var symbol = info.symbol; // Variables that are never re-assigned can safely be considered constants for constant folding if (info.writeCount == 0 && this._options.foldAllConstants) { symbol.flags |= Skew.SymbolFlags.IS_CONST; } // Unused local variables can safely be removed, but don't warn about "for i in 0..10 {}" if (info.readCount == 0 && !symbol.isLoopVariable() && symbol.kind == Skew.SymbolKind.VARIABLE_LOCAL) { this._log.semanticWarningUnreadLocalVariable(symbol.range, symbol.name); } // Rename local variables that conflict var scope = symbol.scope; while (scope.kind() == Skew.ScopeKind.LOCAL) { scope = scope.parent; } if (scope.used != null && in_StringMap.get(scope.used, symbol.name, null) != symbol) { symbol.name = scope.generateName(symbol.name); } } }; Skew.Resolving.Resolver.prototype._discardUnusedDefines = function() { var keys = Array.from(this._defines.keys()); // Sort so the order is deterministic keys.sort(function(a, b) { return in_string.compare(a, b); }); for (var i = 0, list = keys, count = list.length; i < count; i = i + 1 | 0) { var key = in_List.get(list, i); this._log.semanticErrorInvalidDefine2(in_StringMap.get1(this._defines, key).name, key); } }; Skew.Resolving.Resolver.prototype._resolveObject = function(symbol) { this._initializeSymbol(symbol); for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._resolveObject(object); } for (var i1 = 0, list1 = symbol.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); this._resolveFunction($function); } for (var i2 = 0, list2 = symbol.variables, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); this._resolveVariable1(variable); } this._checkInterfacesAndAbstractStatus2(symbol); }; Skew.Resolving.Resolver.prototype._initializeFunction = function(symbol) { if (symbol.resolvedType == null) { symbol.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, symbol); } // Referencing a normal variable instead of a special node kind for "this" // makes many things much easier including lambda capture and devirtualization if (symbol.kind == Skew.SymbolKind.FUNCTION_INSTANCE || symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { var $this = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_ARGUMENT, 'self'); $this.initializeWithType(this._cache.parameterize(symbol.parent.resolvedType)); $this.flags |= Skew.SymbolFlags.IS_CONST; symbol.$this = $this; } // Lazily-initialize automatically generated functions if (symbol.isAutomaticallyGenerated()) { if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { assert(symbol.name == 'new'); this._automaticallyGenerateClassConstructor(symbol); } else if (symbol.kind == Skew.SymbolKind.FUNCTION_INSTANCE) { assert(symbol.name == 'toString'); this._automaticallyGenerateEnumToString(symbol); } } this._resolveParameters(symbol.parameters); // Resolve the argument variables symbol.resolvedType.argumentTypes = []; for (var i = 0, list = symbol.$arguments, count1 = list.length; i < count1; i = i + 1 | 0) { var argument = in_List.get(list, i); argument.scope = symbol.scope; this._resolveVariable1(argument); symbol.resolvedType.argumentTypes.push(argument.resolvedType); } symbol.argumentOnlyType = this._cache.createLambdaType(symbol.resolvedType.argumentTypes, null); // Resolve the return type if present (no return type means "void") var returnType = null; if (symbol.returnType != null) { if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { this._log.semanticErrorConstructorReturnType(symbol.returnType.range); } // Explicitly handle a "void" return type for better error messages else if (symbol.returnType.kind == Skew.NodeKind.NAME && symbol.returnType.asString() == 'void' && symbol.scope.find('void', Skew.ScopeSearch.NORMAL) == null) { this._log.semanticErrorVoidReturnType(symbol.returnType.range); } else { this._resolveAsParameterizedType(symbol.returnType, symbol.scope); returnType = symbol.returnType.resolvedType; } } // Constructors always return the type they construct if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { returnType = this._cache.parameterize(symbol.parent.resolvedType); symbol.returnType = new Skew.Node(Skew.NodeKind.TYPE).withType(returnType); } // The "<=>" operator must return a numeric value for comparison with zero var count = symbol.$arguments.length; if (symbol.name == '<=>') { if (returnType == null || returnType != this._cache.intType) { this._log.semanticErrorComparisonOperatorNotInt(symbol.returnType != null ? symbol.returnType.range : symbol.range); returnType = Skew.Type.DYNAMIC; } else if (count != 1) { this._log.semanticErrorWrongArgumentCount(symbol.range, symbol.name, 1); } } // Setters must have one argument else if (symbol.isSetter() && count != 1) { this._log.semanticErrorWrongArgumentCount(symbol.range, symbol.name, 1); symbol.flags &= ~Skew.SymbolFlags.IS_SETTER; } // Validate argument count else { var argumentCount = Skew.argumentCountForOperator(symbol.name); if (argumentCount != null && !(argumentCount.indexOf(count) != -1)) { this._log.semanticErrorWrongArgumentCountRange(symbol.range, symbol.name, argumentCount); } // Enforce that the initializer constructor operators take lists of // values to avoid confusing error messages inside the code generated // for initializer expressions else if (symbol.name == '{new}' || symbol.name == '[new]') { for (var i1 = 0, list1 = symbol.$arguments, count2 = list1.length; i1 < count2; i1 = i1 + 1 | 0) { var argument1 = in_List.get(list1, i1); if (argument1.resolvedType != Skew.Type.DYNAMIC && !this._cache.isList(argument1.resolvedType)) { this._log.semanticErrorExpectedList(argument1.range, argument1.name, argument1.resolvedType); } } } } symbol.resolvedType.returnType = returnType; this._resolveAnnotations(symbol); // Validate the entry point after this symbol has a type if (symbol.isEntryPoint()) { this._validateEntryPoint(symbol); } }; Skew.Resolving.Resolver.prototype._automaticallyGenerateClassConstructor = function(symbol) { // Create the function body var block = new Skew.Node(Skew.NodeKind.BLOCK); symbol.block = block; // Mirror the base constructor's arguments if (symbol.overridden != null) { this._initializeSymbol(symbol.overridden); var $arguments = symbol.overridden.$arguments; var base = new Skew.Node(Skew.NodeKind.SUPER).withRange(symbol.overridden.range); if ($arguments.length == 0) { block.appendChild(Skew.Node.createExpression(base)); } else { var call = Skew.Node.createCall(base); for (var i = 0, list = $arguments, count = list.length; i < count; i = i + 1 | 0) { var variable = in_List.get(list, i); var argument = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_ARGUMENT, variable.name); argument.range = variable.range; argument.initializeWithType(variable.resolvedType); symbol.$arguments.push(argument); call.appendChild(Skew.Node.createSymbolReference(argument)); } block.prependChild(Skew.Node.createExpression(call)); } } // Add an argument for every uninitialized variable var parent = symbol.parent.asObjectSymbol(); this._initializeSymbol(parent); for (var i1 = 0, list1 = parent.variables, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var variable1 = in_List.get(list1, i1); if (variable1.kind == Skew.SymbolKind.VARIABLE_INSTANCE) { this._initializeSymbol(variable1); if (variable1.value == null) { var argument1 = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_ARGUMENT, variable1.name); argument1.initializeWithType(variable1.resolvedType); argument1.range = variable1.range; symbol.$arguments.push(argument1); block.appendChild(Skew.Node.createExpression(Skew.Node.createBinary(Skew.NodeKind.ASSIGN, Skew.Node.createMemberReference(Skew.Node.createSymbolReference(symbol.$this), variable1), Skew.Node.createSymbolReference(argument1)).withRange(variable1.range))); } else { block.appendChild(Skew.Node.createExpression(Skew.Node.createBinary(Skew.NodeKind.ASSIGN, Skew.Node.createMemberReference(Skew.Node.createSymbolReference(symbol.$this), variable1), variable1.value.clone()).withRange(variable1.range))); } } } // Make constructors without arguments into getters if (symbol.$arguments.length == 0) { symbol.flags |= Skew.SymbolFlags.IS_GETTER; } }; Skew.Resolving.Resolver.prototype._automaticallyGenerateEnumToString = function(symbol) { var parent = symbol.parent.asObjectSymbol(); var names = new Skew.Node(Skew.NodeKind.INITIALIZER_LIST); this._initializeSymbol(parent); symbol.returnType = new Skew.Node(Skew.NodeKind.TYPE).withType(this._cache.stringType); symbol.flags |= Skew.SymbolFlags.IS_GETTER; // TypeScript has special enum-to-string support that we can use instead if (this._options.target instanceof Skew.TypeScriptTarget) { var target = new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent(parent.name)).withSymbol(parent).withType(Skew.Type.DYNAMIC); symbol.block = new Skew.Node(Skew.NodeKind.BLOCK).appendChild(Skew.Node.createReturn(Skew.Node.createIndex(target, new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent('self'))))); return; } for (var i = 0, list = parent.variables, count = list.length; i < count; i = i + 1 | 0) { var variable = in_List.get(list, i); if (variable.kind == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS) { assert(variable.value.asInt() == names.childCount()); names.appendChild(new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.StringContent(variable.name))); } } var strings = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_GLOBAL, parent.scope.generateName('_strings')); strings.range = parent.range; strings.initializeWithType(this._cache.createListType(this._cache.stringType)); strings.value = names; strings.flags |= Skew.SymbolFlags.IS_PROTECTED | Skew.SymbolFlags.IS_CONST | Skew.SymbolFlags.IS_AUTOMATICALLY_GENERATED; strings.parent = parent; strings.scope = parent.scope; parent.variables.push(strings); this._resolveAsParameterizedExpressionWithConversion(strings.value, strings.scope, strings.resolvedType); symbol.block = new Skew.Node(Skew.NodeKind.BLOCK).appendChild(Skew.Node.createReturn(Skew.Node.createIndex(Skew.Node.createSymbolReference(strings), new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent('self'))))); }; Skew.Resolving.Resolver.prototype._resolveFunction = function(symbol) { this._initializeSymbol(symbol); // Validate use of "def" vs "over" if (!symbol.isObsolete()) { if (symbol.overridden != null && symbol.kind == Skew.SymbolKind.FUNCTION_INSTANCE) { if (!symbol.isOver()) { this._log.semanticErrorModifierMissingOverride(symbol.range, symbol.name, symbol.overridden.range); } } else if (symbol.isOver()) { this._log.semanticErrorModifierUnusedOverride(symbol.range, symbol.name); } } var scope = new Skew.LocalScope(symbol.scope, Skew.LocalType.NORMAL); if (symbol.$this != null) { scope.define(symbol.$this, this._log); } // Default values for argument variables aren't resolved with this local // scope since they are evaluated at the call site, not inside the // function body, and shouldn't have access to other arguments for (var i = 0, list = symbol.$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); scope.define(argument, this._log); in_IntMap.set(this._localVariableStatistics, argument.id, new Skew.Resolving.LocalVariableStatistics(argument)); } // The function is considered abstract if the body is missing var block = symbol.block; if (block != null) { var firstStatement = block.firstChild(); if (firstStatement != null && firstStatement.isSuperCallStatement()) { firstStatement = firstStatement.nextSibling(); } // User-specified constructors have variable initializers automatically inserted if (symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR && !symbol.isAutomaticallyGenerated()) { for (var i1 = 0, list1 = symbol.parent.asObjectSymbol().variables, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var variable = in_List.get(list1, i1); if (variable.kind == Skew.SymbolKind.VARIABLE_INSTANCE) { this._resolveVariable1(variable); // Attempt to create a default value if absent. Right now this // avoids the problem of initializing type parameters: // // class Foo { // var foo T // def new {} // def use T { return foo } // } // // This should be fixed at some point. if (variable.value == null && !variable.resolvedType.isParameter()) { variable.value = this._createDefaultValueForType(variable.resolvedType, variable.range); } if (variable.value != null) { block.insertChildBefore(firstStatement, Skew.Node.createExpression(Skew.Node.createBinary(Skew.NodeKind.ASSIGN, Skew.Node.createMemberReference(Skew.Node.createSymbolReference(symbol.$this), variable), variable.value.clone()))); } } } } // Skip resolving irrelevant function bodies to speed up code completion var context = this._options.completionContext; if (context != null && block.range != null && block.range.source != context.source) { return; } this._resolveNode(block, scope, null); // Missing a return statement is an error if (symbol.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR) { var returnType = symbol.resolvedType.returnType; if (returnType != null && !symbol.isDynamicLambda() && block.hasControlFlowAtEnd()) { this._log.semanticErrorMissingReturn(symbol.range, symbol.name, returnType); } } // Derived class constructors must start with a call to "super" else if (symbol.parent.asObjectSymbol().baseClass != null) { var first = block.firstChild(); if (first == null || !first.isSuperCallStatement()) { this._log.semanticErrorMissingSuper(firstStatement == null ? symbol.range : firstStatement.range); } } } // Global functions and functions on non-virtual types can't be abstract else if (!symbol.isImported() && !symbol.isObsolete() && (symbol.kind == Skew.SymbolKind.FUNCTION_GLOBAL || symbol.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR || symbol.kind == Skew.SymbolKind.FUNCTION_INSTANCE && symbol.parent.kind != Skew.SymbolKind.OBJECT_CLASS && symbol.parent.kind != Skew.SymbolKind.OBJECT_INTERFACE)) { this._log.semanticErrorUnimplementedFunction(symbol.range, symbol.name); } }; Skew.Resolving.Resolver.prototype._recordStatistic = function(symbol, statistic) { if (symbol != null && (symbol.kind == Skew.SymbolKind.VARIABLE_LOCAL || symbol.kind == Skew.SymbolKind.VARIABLE_ARGUMENT)) { var info = in_IntMap.get(this._localVariableStatistics, symbol.id, null); if (info != null) { switch (statistic) { case Skew.Resolving.SymbolStatistic.READ: { info.readCount = info.readCount + 1 | 0; break; } case Skew.Resolving.SymbolStatistic.WRITE: { info.writeCount = info.writeCount + 1 | 0; break; } } } } }; Skew.Resolving.Resolver.prototype._initializeVariable = function(symbol) { var value = symbol.value; // Normal variables may omit the initializer if the type is present if (symbol.type != null) { this._resolveAsParameterizedType(symbol.type, symbol.scope); symbol.resolvedType = symbol.type.resolvedType; symbol.state = Skew.SymbolState.INITIALIZED; // Resolve the constant now so initialized constants always have a value if (symbol.isConst() && value != null) { this._resolveAsParameterizedExpressionWithConversion(value, symbol.scope, symbol.resolvedType); } } // Enums take their type from their parent else if (symbol.kind == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS) { symbol.resolvedType = symbol.parent.resolvedType; } // Implicitly-typed variables take their type from their initializer else if (value != null) { this._resolveAsParameterizedExpression(value, symbol.scope); var type = value.resolvedType; symbol.resolvedType = type; // Forbid certain types if (!Skew.Resolving.Resolver._isValidVariableType(type)) { this._log.semanticErrorBadImplicitVariableType(symbol.range, type); symbol.resolvedType = Skew.Type.DYNAMIC; } } // Use a different error for constants which must have a type and lambda arguments which cannot have an initializer else if (symbol.isConst() || symbol.scope.kind() == Skew.ScopeKind.FUNCTION && symbol.scope.asFunctionScope().symbol.kind == Skew.SymbolKind.FUNCTION_LOCAL) { this._log.semanticErrorVarMissingType(symbol.range, symbol.name); symbol.resolvedType = Skew.Type.DYNAMIC; } // Variables without a type are an error else { this._log.semanticErrorVarMissingValue(symbol.range, symbol.name); symbol.resolvedType = Skew.Type.DYNAMIC; } // Make sure the symbol has a type node if (symbol.type == null) { symbol.type = new Skew.Node(Skew.NodeKind.TYPE).withType(symbol.resolvedType); } this._resolveDefines(symbol); this._resolveAnnotations(symbol); // Run post-annotation checks if (symbol.resolvedType != Skew.Type.DYNAMIC && symbol.isConst() && !symbol.isImported() && value == null && symbol.kind != Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS && symbol.kind != Skew.SymbolKind.VARIABLE_INSTANCE) { this._log.semanticErrorConstMissingValue(symbol.range, symbol.name); } }; Skew.Resolving.Resolver.prototype._resolveVariable1 = function(symbol) { this._initializeSymbol(symbol); if (symbol.value != null) { this._resolveAsParameterizedExpressionWithConversion(symbol.value, symbol.scope, symbol.resolvedType); } // Default-initialize variables else if (symbol.kind != Skew.SymbolKind.VARIABLE_ARGUMENT && symbol.kind != Skew.SymbolKind.VARIABLE_INSTANCE && symbol.kind != Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS) { symbol.value = this._createDefaultValueForType(symbol.resolvedType, symbol.range); } }; Skew.Resolving.Resolver.prototype._createDefaultValueForType = function(type, range) { var unwrapped = this._cache.unwrappedType(type); if (unwrapped == this._cache.intType) { return new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.IntContent(0)).withType(type); } if (unwrapped == this._cache.doubleType) { return new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.DoubleContent(0)).withType(type); } if (unwrapped == this._cache.boolType) { return new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.BoolContent(false)).withType(type); } if (unwrapped.isEnumOrFlags()) { return Skew.Node.createCast(this._cache.createInt(0), new Skew.Node(Skew.NodeKind.TYPE).withType(type)).withType(type); } if (unwrapped.isParameter()) { this._log.semanticErrorNoDefaultValue(range, type); return null; } assert(unwrapped.isReference()); return new Skew.Node(Skew.NodeKind.NULL).withType(type); }; Skew.Resolving.Resolver.prototype._initializeOverloadedFunction = function(symbol) { var symbols = symbol.symbols; if (symbol.resolvedType == null) { symbol.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, symbol); } // Ensure no two overloads have the same argument types var i = 0; while (i < symbols.length) { var $function = in_List.get(symbols, i); this._initializeSymbol($function); symbol.flags |= $function.flags & Skew.SymbolFlags.IS_SETTER; var equivalence = Skew.TypeCache.Equivalence.NOT_EQUIVALENT; var index = -1; for (var j = 0, count = i; j < count; j = j + 1 | 0) { equivalence = this._cache.areFunctionSymbolsEquivalent($function, null, in_List.get(symbols, j), null); if (equivalence != Skew.TypeCache.Equivalence.NOT_EQUIVALENT) { index = j; break; } } if (index == -1) { i = i + 1 | 0; continue; } var other = in_List.get(symbols, index); var parent = symbol.parent.asObjectSymbol(); var isFromSameObject = $function.parent == other.parent; var areReturnTypesDifferent = equivalence == Skew.TypeCache.Equivalence.EQUIVALENT_EXCEPT_RETURN_TYPE && (isFromSameObject || symbol.kind == Skew.SymbolKind.OVERLOADED_INSTANCE); // Symbols should be in the base type chain assert(parent.isSameOrHasBaseClass($function.parent)); assert(parent.isSameOrHasBaseClass(other.parent)); // Forbid overloading by return type if (!isFromSameObject && areReturnTypesDifferent) { var derived = $function.parent == parent ? $function : other; var base = derived == $function ? other : $function; this._log.semanticErrorBadOverrideReturnType(derived.range, derived.name, parent.baseType, base.range); if (isFromSameObject) { $function.flags |= Skew.SymbolFlags.IS_OBSOLETE; } } // Allow duplicate function declarations with the same type to merge // as long as there are not two declarations that provide implementations. // Mark the obsolete function as obsolete instead of removing it so it // doesn't potentially mess up iteration in a parent call stack. else if (areReturnTypesDifferent || isFromSameObject && $function.block != null && other.block != null) { this._log.semanticErrorDuplicateOverload($function.range, symbol.name, other.range); if (isFromSameObject) { $function.flags |= Skew.SymbolFlags.IS_OBSOLETE; } } // Keep "function" else if (isFromSameObject ? $function.block != null : $function.parent.asObjectSymbol().hasBaseClass(other.parent)) { if ($function.parent == parent && other.parent == parent) { $function.mergeInformationFrom(other); $function.flags |= $function.block != null ? other.flags & ~Skew.SymbolFlags.IS_IMPORTED : other.flags; other.flags |= Skew.SymbolFlags.IS_OBSOLETE; } else if (!isFromSameObject) { $function.overridden = other; } in_List.set(symbols, index, $function); } // Keep "other" else if ($function.parent == parent && other.parent == parent) { other.flags |= other.block != null ? $function.flags & ~Skew.SymbolFlags.IS_IMPORTED : $function.flags; other.mergeInformationFrom($function); $function.flags |= Skew.SymbolFlags.IS_OBSOLETE; } else if (!isFromSameObject) { other.overridden = $function; } // Remove the symbol after the merge in_List.removeAt(symbols, i); } }; // Put the guts of the function inside another function because V8 doesn't // optimize functions with try-catch statements Skew.Resolving.Resolver.prototype._resolveNodeSwitch = function(node, scope, context) { switch (node.kind) { case Skew.NodeKind.BLOCK: { this._resolveBlock(node, scope); break; } case Skew.NodeKind.PAIR: { this._resolvePair(node, scope, context); break; } // Statements case Skew.NodeKind.BREAK: case Skew.NodeKind.CONTINUE: { this._resolveJump(node, scope); break; } case Skew.NodeKind.COMMENT_BLOCK: { break; } case Skew.NodeKind.EXPRESSION: { this._resolveExpression(node, scope); break; } case Skew.NodeKind.FOR: { this._resolveFor(node, scope); break; } case Skew.NodeKind.FOREACH: { this._resolveForeach(node, scope); break; } case Skew.NodeKind.IF: { this._resolveIf(node, scope); break; } case Skew.NodeKind.RETURN: { this._resolveReturn(node, scope); break; } case Skew.NodeKind.SWITCH: { this._resolveSwitch(node, scope); break; } case Skew.NodeKind.THROW: { this._resolveThrow(node, scope); break; } case Skew.NodeKind.TRY: { this._resolveTry(node, scope); break; } case Skew.NodeKind.VARIABLE: { this._resolveVariable2(node, scope); break; } case Skew.NodeKind.VARIABLES: { this._resolveVariables(node, scope); break; } case Skew.NodeKind.WHILE: { this._resolveWhile(node, scope); break; } // Expressions case Skew.NodeKind.ASSIGN_INDEX: { this._resolveOperatorOverload(node, scope, context); break; } case Skew.NodeKind.CALL: { this._resolveCall(node, scope, context); break; } case Skew.NodeKind.CAST: { this._resolveCast(node, scope, context); break; } case Skew.NodeKind.COMPLEMENT: case Skew.NodeKind.NEGATIVE: case Skew.NodeKind.NOT: case Skew.NodeKind.POSITIVE: case Skew.NodeKind.POSTFIX_DECREMENT: case Skew.NodeKind.POSTFIX_INCREMENT: case Skew.NodeKind.PREFIX_DECREMENT: case Skew.NodeKind.PREFIX_INCREMENT: { this._resolveOperatorOverload(node, scope, context); break; } case Skew.NodeKind.CONSTANT: { this._resolveConstant(node, scope, context); break; } case Skew.NodeKind.DOT: { this._resolveDot(node, scope, context); break; } case Skew.NodeKind.HOOK: { this._resolveHook(node, scope, context); break; } case Skew.NodeKind.INDEX: { this._resolveOperatorOverload(node, scope, context); break; } case Skew.NodeKind.INITIALIZER_LIST: case Skew.NodeKind.INITIALIZER_MAP: { this._resolveInitializer(node, scope, context); break; } case Skew.NodeKind.LAMBDA: { this._resolveLambda(node, scope, context); break; } case Skew.NodeKind.LAMBDA_TYPE: { this._resolveLambdaType(node, scope); break; } case Skew.NodeKind.NAME: { this._resolveName(node, scope); break; } case Skew.NodeKind.NULL: { node.resolvedType = Skew.Type.NULL; break; } case Skew.NodeKind.NULL_DOT: { this._resolveNullDot(node, scope); break; } case Skew.NodeKind.PARAMETERIZE: { this._resolveParameterize(node, scope); break; } case Skew.NodeKind.PARSE_ERROR: { node.resolvedType = Skew.Type.DYNAMIC; break; } case Skew.NodeKind.SEQUENCE: { this._resolveSequence(node, scope, context); break; } case Skew.NodeKind.STRING_INTERPOLATION: { this._resolveStringInterpolation(node, scope); break; } case Skew.NodeKind.SUPER: { this._resolveSuper(node, scope); break; } case Skew.NodeKind.TYPE: { break; } case Skew.NodeKind.TYPE_CHECK: { this._resolveTypeCheck(node, scope); break; } case Skew.NodeKind.XML: { this._resolveXML(node, scope); break; } default: { if (Skew.in_NodeKind.isBinary(node.kind)) { this._resolveBinary(node, scope, context); } else { assert(false); } break; } } }; Skew.Resolving.Resolver.prototype._resolveNode = function(node, scope, context) { if (node.resolvedType != null) { // Only resolve once return; } node.resolvedType = Skew.Type.DYNAMIC; try { this._resolveNodeSwitch(node, scope, context); } catch (failure) { if (failure instanceof Skew.Resolving.Resolver.GuardMergingFailure) { node.resolvedType = null; throw failure; } else { throw failure; } } assert(node.resolvedType != null); }; Skew.Resolving.Resolver.prototype._resolveAsParameterizedType = function(node, scope) { assert(Skew.in_NodeKind.isExpression(node.kind)); node.flags |= Skew.NodeFlags.SHOULD_EXPECT_TYPE; this._resolveNode(node, scope, null); this._checkIsType(node); this._checkIsParameterized(node); }; Skew.Resolving.Resolver.prototype._resolveAsParameterizedExpression = function(node, scope) { assert(Skew.in_NodeKind.isExpression(node.kind)); this._resolveNode(node, scope, null); this._checkIsInstance(node); this._checkIsParameterized(node); }; Skew.Resolving.Resolver.prototype._resolveAsParameterizedExpressionWithTypeContext = function(node, scope, type) { assert(Skew.in_NodeKind.isExpression(node.kind)); this._resolveNode(node, scope, type); this._checkIsInstance(node); this._checkIsParameterized(node); }; Skew.Resolving.Resolver.prototype._resolveAsParameterizedExpressionWithConversion = function(node, scope, type) { this._resolveAsParameterizedExpressionWithTypeContext(node, scope, type); this._checkConversion(node, type, Skew.Resolving.ConversionKind.IMPLICIT); }; Skew.Resolving.Resolver.prototype._resolveChildrenAsParameterizedExpressions = function(node, scope) { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._resolveAsParameterizedExpression(child, scope); } }; Skew.Resolving.Resolver.prototype._resolveChildrenAsParameterizedExpressionsWithDynamicTypeContext = function(node, scope) { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._resolveAsParameterizedExpressionWithTypeContext(child, scope, Skew.Type.DYNAMIC); } }; Skew.Resolving.Resolver.prototype._checkUnusedExpression = function(node) { var kind = node.kind; if (kind == Skew.NodeKind.HOOK) { this._checkUnusedExpression(node.hookTrue()); this._checkUnusedExpression(node.hookFalse()); } else if (node.range != null && node.resolvedType != Skew.Type.DYNAMIC && kind != Skew.NodeKind.CALL && !Skew.in_NodeKind.isAssign(kind)) { this._log.semanticWarningUnusedExpression(node.range); } }; Skew.Resolving.Resolver.prototype._checkIsInstance = function(node) { if (node.resolvedType != Skew.Type.DYNAMIC && node.isType()) { this._log.semanticErrorUnexpectedType(node.range, node.resolvedType); node.resolvedType = Skew.Type.DYNAMIC; } }; Skew.Resolving.Resolver.prototype._checkIsType = function(node) { if (node.resolvedType != Skew.Type.DYNAMIC && !node.isType()) { this._log.semanticErrorUnexpectedExpression(node.range, node.resolvedType); node.resolvedType = Skew.Type.DYNAMIC; } }; Skew.Resolving.Resolver.prototype._checkIsParameterized = function(node) { if (node.resolvedType.parameters() != null && !node.resolvedType.isParameterized()) { this._log.semanticErrorUnparameterizedType(node.range, node.resolvedType); node.resolvedType = Skew.Type.DYNAMIC; } }; Skew.Resolving.Resolver.prototype._checkStorage = function(node, scope) { var symbol = node.symbol; // Only allow storage to variables if (node.kind != Skew.NodeKind.NAME && node.kind != Skew.NodeKind.DOT && (node.kind != Skew.NodeKind.INDEX || node.resolvedType != Skew.Type.DYNAMIC) || symbol != null && !Skew.in_SymbolKind.isVariable(symbol.kind)) { this._log.semanticErrorBadStorage(node.range); } // Forbid storage to constants else if (symbol != null && symbol.isConst()) { var $function = scope.findEnclosingFunction(); // Allow assignments to constants inside constructors if ($function == null || $function.symbol.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR || $function.symbol.parent != symbol.parent || symbol.kind != Skew.SymbolKind.VARIABLE_INSTANCE) { this._log.semanticErrorStorageToConstSymbol(node.internalRangeOrRange(), symbol.name); } } }; Skew.Resolving.Resolver.prototype._checkAccess = function(node, range, scope) { var symbol = node.symbol; if (symbol == null) { return; } // Check access control if (symbol.isProtected()) { while (scope != null) { if (scope.kind() == Skew.ScopeKind.OBJECT) { var object = scope.asObjectScope().symbol; if (object.isSameOrHasBaseClass(symbol.parent)) { return; } } scope = scope.parent; } this._log.semanticErrorAccessViolation(range, symbol.name); } // Deprecation annotations optionally provide a warning message if (symbol.isDeprecated()) { for (var i = 0, list = symbol.annotations, count = list.length; i < count; i = i + 1 | 0) { var annotation = in_List.get(list, i); if (annotation.symbol != null && annotation.symbol.fullName() == '@deprecated') { var value = annotation.annotationValue(); if (value.kind == Skew.NodeKind.CALL && value.hasTwoChildren()) { var last = value.lastChild(); if (last.kind == Skew.NodeKind.CONSTANT && last.content.kind() == Skew.ContentKind.STRING) { this._log.append(this._log.newWarning(range, Skew.in_Content.asString(last.content))); return; } } } } this._log.semanticWarningDeprecatedUsage(range, symbol.name); } }; Skew.Resolving.Resolver.prototype._checkConversion = function(node, to, kind) { var from = node.resolvedType; assert(from != null); assert(to != null); // The "dynamic" type is a hole in the type system if (from == Skew.Type.DYNAMIC || to == Skew.Type.DYNAMIC) { return; } // No conversion is needed for identical types if (from == to) { return; } // The implicit conversion must be valid if (kind == Skew.Resolving.ConversionKind.IMPLICIT && !this._cache.canImplicitlyConvert(from, to) || kind == Skew.Resolving.ConversionKind.EXPLICIT && !this._cache.canExplicitlyConvert(from, to)) { this._log.semanticErrorIncompatibleTypes(node.range, from, to, this._cache.canExplicitlyConvert(from, to)); node.resolvedType = Skew.Type.DYNAMIC; return; } // Make the implicit conversion explicit for convenience later on if (kind == Skew.Resolving.ConversionKind.IMPLICIT) { node.become(Skew.Node.createCast(node.cloneAndStealChildren(), new Skew.Node(Skew.NodeKind.TYPE).withType(to)).withType(to).withRange(node.range)); } }; Skew.Resolving.Resolver.prototype._resolveAnnotation = function(node, symbol) { var value = node.annotationValue(); var test = node.annotationTest(); this._resolveNode(value, symbol.scope, null); if (test != null) { this._resolveAsParameterizedExpressionWithConversion(test, symbol.scope, this._cache.boolType); } // Terminate early when there were errors if (value.symbol == null) { return false; } // Make sure annotations have the arguments they need if (value.kind != Skew.NodeKind.CALL) { this._log.semanticErrorArgumentCount(value.range, value.symbol.resolvedType.argumentTypes.length, 0, value.symbol.name, value.symbol.range); return false; } // Ensure all arguments are constants var isValid = true; for (var child = value.callValue().nextSibling(); child != null; child = child.nextSibling()) { isValid = isValid && this._recursivelyResolveAsConstant(child); } if (!isValid) { return false; } // Only store symbols for annotations with the correct arguments for ease of use node.symbol = value.symbol; // Apply built-in annotation logic var flag = in_StringMap.get(Skew.Resolving.Resolver._annotationSymbolFlags, value.symbol.fullName(), 0); if (flag != 0) { switch (flag) { case Skew.SymbolFlags.IS_DEPRECATED: { break; } case Skew.SymbolFlags.IS_ENTRY_POINT: { isValid = symbol.kind == Skew.SymbolKind.FUNCTION_GLOBAL; break; } case Skew.SymbolFlags.IS_EXPORTED: { isValid = !symbol.isImported(); break; } case Skew.SymbolFlags.IS_IMPORTED: { isValid = !symbol.isExported() && (!Skew.in_SymbolKind.isFunction(symbol.kind) || symbol.asFunctionSymbol().block == null); break; } case Skew.SymbolFlags.IS_INLINING_FORCED: case Skew.SymbolFlags.IS_INLINING_PREVENTED: case Skew.SymbolFlags.IS_PREFERRED: { isValid = Skew.in_SymbolKind.isFunction(symbol.kind); break; } case Skew.SymbolFlags.IS_RENAMED: { break; } case Skew.SymbolFlags.IS_SKIPPED: { isValid = Skew.in_SymbolKind.isFunction(symbol.kind) && symbol.resolvedType.returnType == null; break; } case Skew.SymbolFlags.SHOULD_SPREAD: { isValid = symbol.kind == Skew.SymbolKind.FUNCTION_ANNOTATION; break; } } if (flag == Skew.SymbolFlags.IS_INLINING_FORCED) { this._options.isAlwaysInlinePresent = true; } if (!isValid) { this._log.semanticErrorInvalidAnnotation(value.range, value.symbol.name, symbol.name); return false; } // Don't add an annotation when the test expression is false if (test != null && this._recursivelyResolveAsConstant(test) && test.isFalse()) { return false; } // Only warn about duplicate annotations after checking the test expression if ((symbol.flags & flag) != 0) { if ((symbol.parent.flags & flag & (Skew.SymbolFlags.IS_IMPORTED | Skew.SymbolFlags.IS_EXPORTED)) != 0) { this._log.semanticWarningRedundantAnnotation(value.range, value.symbol.name, symbol.name, symbol.parent.name); } else { this._log.semanticWarningDuplicateAnnotation(value.range, value.symbol.name, symbol.name); } } symbol.flags |= flag; // Store the new name for later if (flag == Skew.SymbolFlags.IS_RENAMED && value.hasTwoChildren()) { symbol.rename = value.lastChild().asString(); } } return true; }; Skew.Resolving.Resolver.prototype._recursivelyResolveAsConstant = function(node) { this._constantFolder.foldConstants(node); if (node.kind != Skew.NodeKind.CONSTANT) { this._log.semanticErrorExpectedConstant(node.range); return false; } return true; }; Skew.Resolving.Resolver.prototype._resolveBlock = function(node, scope) { assert(node.kind == Skew.NodeKind.BLOCK); this._controlFlow.pushBlock(node); for (var child = node.firstChild(), next = null; child != null; child = next) { var prev = child.previousSibling(); next = child.nextSibling(); // There is a well-known ambiguity in languages like JavaScript where // a return statement followed by a newline and a value can either be // parsed as a single return statement with a value or as two // statements, a return statement without a value and an expression // statement. Luckily, we're better off than JavaScript since we know // the type of the function. Parse a single statement in a non-void // function but two statements in a void function. if (child.kind == Skew.NodeKind.RETURN && next != null && child.returnValue() == null && next.kind == Skew.NodeKind.EXPRESSION) { var $function = scope.findEnclosingFunctionOrLambda().symbol; if ($function.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR && $function.resolvedType.returnType != null) { var statement = next.remove(); var value = statement.expressionValue().remove(); child.appendChild(value); var trailing = Skew.Comment.lastTrailingComment(statement.comments); var notTrailing = Skew.Comment.withoutLastTrailingComment(statement.comments); if (trailing != null) { child.comments = Skew.Comment.concat(child.comments, [trailing]); } value.comments = Skew.Comment.concat(notTrailing, value.comments); next = child.nextSibling(); assert(child.returnValue() != null); } } this._resolveNode(child, scope, null); // Visit control flow from the previous node to the next node, which // should handle the case where this node was replaced with something for (var n = prev != null ? prev.nextSibling() : node.firstChild(); n != next; n = n.nextSibling()) { this._controlFlow.visitStatementInPostOrder(n); } // Stop now if the child was removed if (child.parent() == null) { continue; } // The "@skip" annotation removes function calls after type checking if (child.kind == Skew.NodeKind.EXPRESSION) { var value1 = child.expressionValue(); if (value1.kind == Skew.NodeKind.CALL && value1.symbol != null && value1.symbol.isSkipped()) { child.remove(); } } } this._controlFlow.popBlock(node); }; Skew.Resolving.Resolver.prototype._resolvePair = function(node, scope, context) { // Allow resolving a pair with a type context of "dynamic" to // deliberately silence errors around needing type context if (context == Skew.Type.DYNAMIC) { this._resolveAsParameterizedExpressionWithConversion(node.firstValue(), scope, context); this._resolveAsParameterizedExpressionWithConversion(node.secondValue(), scope, context); return; } this._resolveAsParameterizedExpression(node.firstValue(), scope); this._resolveAsParameterizedExpression(node.secondValue(), scope); }; Skew.Resolving.Resolver.prototype._resolveJump = function(node, scope) { if (scope.findEnclosingLoop() == null) { this._log.semanticErrorBadJump(node.range, node.kind == Skew.NodeKind.BREAK ? 'break' : 'continue'); } }; Skew.Resolving.Resolver.prototype._resolveExpressionOrImplicitReturn = function(node, value, scope) { var hook = this._sinkNullDotIntoHook(value, scope, null); // Turn top-level "?." expressions into if statements if (hook != null) { var test = hook.hookTest(); var yes = hook.hookTrue(); var block = new Skew.Node(Skew.NodeKind.BLOCK).appendChild(Skew.Node.createExpression(yes.remove()).withRange(yes.range)).withRange(yes.range); node.become(Skew.Node.createIf(test.remove(), block, null).withRange(node.range).withComments(node.comments)); this._resolveNode(node, scope, null); } // Turn top-level "?=" expressions into if statements else if (value.kind == Skew.NodeKind.ASSIGN_NULL) { var left = value.binaryLeft(); var right = value.binaryRight(); this._resolveAsParameterizedExpressionWithTypeContext(left, scope, null); this._checkStorage(left, scope); var test1 = Skew.Node.createBinary(Skew.NodeKind.EQUAL, this._extractExpressionForAssignment(left, scope), new Skew.Node(Skew.NodeKind.NULL)).withRange(left.range); var assign = Skew.Node.createBinary(Skew.NodeKind.ASSIGN, left.remove(), right.remove()).withRange(node.range).withFlags(Skew.NodeFlags.WAS_ASSIGN_NULL); var block1 = new Skew.Node(Skew.NodeKind.BLOCK).appendChild(Skew.Node.createExpression(assign).withRange(node.range)).withRange(node.range); node.become(Skew.Node.createIf(test1, block1, null).withRange(node.range).withComments(node.comments)); this._resolveNode(node, scope, null); } // Normal expression statement else { this._resolveAsParameterizedExpression(value, scope); } }; Skew.Resolving.Resolver.prototype._resolveExpression = function(node, scope) { var value = node.expressionValue(); this._resolveExpressionOrImplicitReturn(node, value, scope); // Only continue this didn't get turned into an if statement due to a top-level "?." or "?=" expression if (node.kind == Skew.NodeKind.EXPRESSION) { this._checkUnusedExpression(value); } }; Skew.Resolving.Resolver.prototype._resolveFor = function(node, scope) { var setup = node.forSetup(); var update = node.forUpdate(); scope = new Skew.LocalScope(scope, Skew.LocalType.LOOP); if (setup.kind == Skew.NodeKind.VARIABLES) { this._resolveNode(setup, scope, null); // All for loop variables must have the same type. This is a requirement // for one-to-one code emission in the languages we want to target. var type = setup.firstChild().symbol.resolvedType; for (var child = setup.firstChild().nextSibling(); child != null; child = child.nextSibling()) { var symbol = child.symbol; if (symbol.resolvedType != type) { this._log.semanticErrorForLoopDifferentType(symbol.range, symbol.name, symbol.resolvedType, type); break; } } } else { this._resolveAsParameterizedExpression(setup, scope); } this._resolveAsParameterizedExpressionWithConversion(node.forTest(), scope, this._cache.boolType); this._resolveAsParameterizedExpression(update, scope); if (update.kind == Skew.NodeKind.SEQUENCE) { for (var child1 = update.firstChild(); child1 != null; child1 = child1.nextSibling()) { this._checkUnusedExpression(child1); } } this._resolveBlock(node.forBlock(), scope); }; Skew.Resolving.Resolver.prototype._resolveForeach = function(node, scope) { var type = Skew.Type.DYNAMIC; scope = new Skew.LocalScope(scope, Skew.LocalType.LOOP); var value = node.foreachValue(); this._resolveAsParameterizedExpression(value, scope); // Support "for i in 0..10" if (value.kind == Skew.NodeKind.PAIR) { var first = value.firstValue(); var second = value.secondValue(); type = this._cache.intType; this._checkConversion(first, this._cache.intType, Skew.Resolving.ConversionKind.IMPLICIT); this._checkConversion(second, this._cache.intType, Skew.Resolving.ConversionKind.IMPLICIT); // The ".." syntax only counts up, unlike CoffeeScript if (first.isInt() && second.isInt() && first.asInt() >= second.asInt()) { this._log.semanticWarningEmptyRange(value.range); } } // Support "for i in [1, 2, 3]" else if (this._cache.isList(value.resolvedType)) { type = in_List.get(value.resolvedType.substitutions, 0); } // Anything else is an error else if (value.resolvedType != Skew.Type.DYNAMIC) { this._log.semanticErrorBadForValue(value.range, value.resolvedType); } // Special-case symbol initialization with the type var symbol = node.symbol.asVariableSymbol(); scope.asLocalScope().define(symbol, this._log); in_IntMap.set(this._localVariableStatistics, symbol.id, new Skew.Resolving.LocalVariableStatistics(symbol)); symbol.initializeWithType(type); symbol.flags |= Skew.SymbolFlags.IS_CONST | Skew.SymbolFlags.IS_LOOP_VARIABLE; this._resolveBlock(node.foreachBlock(), scope); // Collect foreach loops and convert them in another pass this._foreachLoops.push(node); }; Skew.Resolving.Resolver.prototype._resolveIf = function(node, scope) { var test = node.ifTest(); var ifFalse = node.ifFalse(); this._resolveAsParameterizedExpressionWithConversion(test, scope, this._cache.boolType); this._resolveBlock(node.ifTrue(), new Skew.LocalScope(scope, Skew.LocalType.NORMAL)); if (ifFalse != null) { this._resolveBlock(ifFalse, new Skew.LocalScope(scope, Skew.LocalType.NORMAL)); } }; Skew.Resolving.Resolver.prototype._resolveReturn = function(node, scope) { var value = node.returnValue(); var $function = scope.findEnclosingFunctionOrLambda().symbol; var returnType = $function.kind != Skew.SymbolKind.FUNCTION_CONSTRUCTOR ? $function.resolvedType.returnType : null; // Check for a returned value if (value == null) { if (returnType != null && !$function.isDynamicLambda()) { this._log.semanticErrorExpectedReturnValue(node.range, returnType); } return; } // Check the type of the returned value if (returnType != null) { this._resolveAsParameterizedExpressionWithConversion(value, scope, returnType); if ($function.shouldInferReturnType() && Skew.Resolving.Resolver._isCallReturningVoid(value)) { node.kind = Skew.NodeKind.EXPRESSION; } return; } // If there's no return type, still check for other errors if (node.isImplicitReturn()) { this._resolveExpressionOrImplicitReturn(node, value, scope); // Stop now if this got turned into an if statement due to a top-level "?." or "?=" expression if (node.kind != Skew.NodeKind.RETURN) { return; } } else { this._resolveAsParameterizedExpression(value, scope); } // Lambdas without a return type or an explicit "return" statement get special treatment if (!node.isImplicitReturn()) { this._log.semanticErrorUnexpectedReturnValue(value.range); return; } // Check for a return value of type "void" if (!$function.shouldInferReturnType() || Skew.Resolving.Resolver._isCallReturningVoid(value)) { this._checkUnusedExpression(value); node.kind = Skew.NodeKind.EXPRESSION; return; } // Check for an invalid return type var type = value.resolvedType; if (!Skew.Resolving.Resolver._isValidVariableType(type)) { this._log.semanticErrorBadReturnType(value.range, type); node.kind = Skew.NodeKind.EXPRESSION; return; } // Mutate the return type to the type from the returned value $function.returnType = new Skew.Node(Skew.NodeKind.TYPE).withType(type); $function.resolvedType.returnType = type; }; Skew.Resolving.Resolver.prototype._resolveSwitch = function(node, scope) { var duplicateCases = new Map(); var mustEnsureConstantIntegers = this._options.target.requiresIntegerSwitchStatements(); var allValuesAreIntegers = true; var value = node.switchValue(); this._resolveAsParameterizedExpression(value, scope); for (var child = value.nextSibling(); child != null; child = child.nextSibling()) { var block = child.caseBlock(); // Resolve all case values for (var caseValue = child.firstChild(); caseValue != block; caseValue = caseValue.nextSibling()) { this._resolveAsParameterizedExpressionWithConversion(caseValue, scope, value.resolvedType); var symbol = caseValue.symbol; var integer = 0; // Check for a constant variable, which may just be read-only with a // value determined at runtime if (symbol != null && (mustEnsureConstantIntegers ? symbol.kind == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS : Skew.in_SymbolKind.isVariable(symbol.kind) && symbol.isConst())) { var constant = this._constantFolder.constantForSymbol(symbol.asVariableSymbol()); if (constant == null || constant.kind() != Skew.ContentKind.INT) { allValuesAreIntegers = false; continue; } integer = Skew.in_Content.asInt(constant); } // Fall back to the constant folder only as a last resort because it // mutates the syntax tree and harms readability else { this._constantFolder.foldConstants(caseValue); if (!caseValue.isInt()) { allValuesAreIntegers = false; continue; } integer = caseValue.asInt(); } // Duplicate case detection var previous = in_IntMap.get(duplicateCases, integer, null); if (previous != null) { this._log.semanticErrorDuplicateCase(caseValue.range, previous); } else { in_IntMap.set(duplicateCases, integer, caseValue.range); } } // The default case must be last, makes changing into an if chain easier later if (child.hasOneChild() && child.nextSibling() != null) { this._log.semanticErrorDefaultCaseNotLast(child.range); } this._resolveBlock(block, new Skew.LocalScope(scope, Skew.LocalType.NORMAL)); } // Fall back to an if statement if the case values aren't compile-time // integer constants, which is requried by many language targets if (!allValuesAreIntegers && mustEnsureConstantIntegers) { this._convertSwitchToIfChain(node, scope); } }; Skew.Resolving.Resolver.prototype._resolveThrow = function(node, scope) { var value = node.throwValue(); this._resolveAsParameterizedExpression(value, scope); }; Skew.Resolving.Resolver.prototype._resolveVariable2 = function(node, scope) { var symbol = node.symbol.asVariableSymbol(); scope.asLocalScope().define(symbol, this._log); in_IntMap.set(this._localVariableStatistics, symbol.id, new Skew.Resolving.LocalVariableStatistics(symbol)); this._resolveVariable1(symbol); // Make sure to parent any created values under the variable node if (!node.hasChildren() && symbol.value != null) { node.appendChild(symbol.value); } }; Skew.Resolving.Resolver.prototype._resolveVariables = function(node, scope) { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._resolveVariable2(child, scope); } }; Skew.Resolving.Resolver.prototype._resolveTry = function(node, scope) { var tryBlock = node.tryBlock(); var finallyBlock = node.finallyBlock(); this._resolveBlock(tryBlock, new Skew.LocalScope(scope, Skew.LocalType.NORMAL)); // Bare try statements catch all thrown values if (node.hasOneChild()) { node.appendChild(Skew.Node.createCatch(null, new Skew.Node(Skew.NodeKind.BLOCK))); } // Check catch statements for (var child = tryBlock.nextSibling(); child != finallyBlock; child = child.nextSibling()) { var childScope = new Skew.LocalScope(scope, Skew.LocalType.NORMAL); if (child.symbol != null) { var symbol = child.symbol.asVariableSymbol(); childScope.define(symbol, this._log); this._resolveVariable1(symbol); } this._resolveBlock(child.catchBlock(), childScope); } // Check finally block if (finallyBlock != null) { this._resolveBlock(finallyBlock, new Skew.LocalScope(scope, Skew.LocalType.NORMAL)); } }; Skew.Resolving.Resolver.prototype._resolveWhile = function(node, scope) { this._resolveAsParameterizedExpressionWithConversion(node.whileTest(), scope, this._cache.boolType); this._resolveBlock(node.whileBlock(), new Skew.LocalScope(scope, Skew.LocalType.LOOP)); }; Skew.Resolving.Resolver.prototype._resolveCall = function(node, scope, context) { var hook = this._sinkNullDotIntoHook(node, scope, context); if (hook != null) { node.become(hook); this._resolveAsParameterizedExpressionWithTypeContext(node, scope, context); return; } var value = node.callValue(); // Take argument types from call argument values for immediately-invoked // function expressions: // // var foo = ((a, b) => a + b)(1, 2) // var bar int = ((a, b) => { return a + b })(1, 2) // if (value.kind == Skew.NodeKind.LAMBDA) { var symbol = value.symbol.asFunctionSymbol(); var $arguments = symbol.$arguments; if (node.childCount() == ($arguments.length + 1 | 0)) { var child = value.nextSibling(); for (var i = 0, count = $arguments.length; i < count; i = i + 1 | 0) { var argument = in_List.get($arguments, i); if (argument.type == null) { this._resolveAsParameterizedExpression(child, scope); argument.type = new Skew.Node(Skew.NodeKind.TYPE).withType(child.resolvedType); } child = child.nextSibling(); } if (context != null && symbol.returnType == null) { symbol.returnType = new Skew.Node(Skew.NodeKind.TYPE).withType(context); } } } this._resolveAsParameterizedExpression(value, scope); var type = value.resolvedType; switch (type.kind) { // Each function has its own type for simplicity case Skew.TypeKind.SYMBOL: { if (this._resolveSymbolCall(node, scope, type)) { return; } break; } // Lambda types look like "fn(int, int) int" case Skew.TypeKind.LAMBDA: { if (this._resolveFunctionCall(node, scope, type)) { return; } break; } // Can't call other types (the null type, for example) default: { if (type != Skew.Type.DYNAMIC) { this._log.semanticErrorInvalidCall(node.internalRangeOrRange(), value.resolvedType); } break; } } // If there was an error, resolve the arguments to check for further // errors but use a dynamic type context to avoid introducing errors for (var child1 = value.nextSibling(); child1 != null; child1 = child1.nextSibling()) { this._resolveAsParameterizedExpressionWithConversion(child1, scope, Skew.Type.DYNAMIC); } }; Skew.Resolving.Resolver.prototype._resolveSymbolCall = function(node, scope, type) { var symbol = type.symbol; // Getters are called implicitly, so explicitly calling one is an error. // This error prevents a getter returning a lambda which is then called. // To overcome this, wrap the call in parentheses: // // def foo fn() // // def bar { // foo() # Error // (foo)() # Correct // } // if (symbol.isGetter() && Skew.Resolving.Resolver._isCallValue(node) && !node.callValue().isInsideParentheses()) { if (symbol.resolvedType.returnType != null && symbol.resolvedType.returnType.kind == Skew.TypeKind.LAMBDA) { this._log.semanticErrorGetterRequiresWrap(node.range, symbol.name, symbol.range); } else { this._log.semanticErrorGetterCalledTwice(node.parent().internalRangeOrRange(), symbol.name, symbol.range); } this._resolveChildrenAsParameterizedExpressionsWithDynamicTypeContext(node, scope); return false; } // Check for calling a function directly if (Skew.in_SymbolKind.isFunction(symbol.kind)) { return this._resolveFunctionCall(node, scope, type); } // Check for calling a set of functions, must not be ambiguous if (Skew.in_SymbolKind.isOverloadedFunction(symbol.kind)) { return this._resolveOverloadedFunctionCall(node, scope, type); } // Can't call other symbols this._log.semanticErrorInvalidCall(node.internalRangeOrRange(), node.callValue().resolvedType); return false; }; Skew.Resolving.Resolver.prototype._resolveFunctionCall = function(node, scope, type) { var ref; var $function = (ref = type.symbol) != null ? ref.asFunctionSymbol() : null; var expected = type.argumentTypes.length; var count = node.childCount() - 1 | 0; node.symbol = $function; // Use the return type even if there were errors if (type.returnType != null) { node.resolvedType = type.returnType; } // There is no "void" type, so make sure this return value isn't used else if (Skew.Resolving.Resolver._isExpressionUsed(node)) { if ($function != null) { this._log.semanticErrorUseOfVoidFunction(node.range, $function.name, $function.range); } else { this._log.semanticErrorUseOfVoidLambda(node.range); } } // Check argument count if (expected != count) { this._log.semanticErrorArgumentCount(node.internalRangeOrRange(), expected, count, $function != null ? $function.name : null, $function != null ? $function.range : null); this._resolveChildrenAsParameterizedExpressionsWithDynamicTypeContext(node, scope); return false; } // Check argument types var value = node.firstChild(); var child = value.nextSibling(); for (var i = 0, list = type.argumentTypes, count1 = list.length; i < count1; i = i + 1 | 0) { var argumentType = in_List.get(list, i); this._resolveAsParameterizedExpressionWithConversion(child, scope, argumentType); child = child.nextSibling(); } // Forbid constructing an abstract type if ($function != null && $function.kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR && value.kind != Skew.NodeKind.SUPER) { this._checkInterfacesAndAbstractStatus2($function.parent.asObjectSymbol()); var reason = $function.parent.asObjectSymbol().isAbstractBecauseOf; if (reason != null) { this._log.semanticErrorAbstractNew(node.internalRangeOrRange(), $function.parent.resolvedType, reason.range, reason.name); } } // Replace overloaded symbols with the chosen overload if (value.kind == Skew.NodeKind.PARAMETERIZE) { value = value.parameterizeValue(); } if ($function != null && value.symbol != null && Skew.in_SymbolKind.isOverloadedFunction(value.symbol.kind) && value.symbol.asOverloadedFunctionSymbol().symbols.indexOf($function) != -1) { value.symbol = $function; value.resolvedType = type; } return true; }; Skew.Resolving.Resolver.prototype._resolveOverloadedFunction = function(range, node, scope, symbolType) { var overloaded = symbolType.symbol.asOverloadedFunctionSymbol(); var firstArgument = node.firstChild().nextSibling(); var count = node.childCount() - 1 | 0; var candidates = []; // Filter by argument length and substitute using the current type environment for (var i1 = 0, list = overloaded.symbols, count1 = list.length; i1 < count1; i1 = i1 + 1 | 0) { var symbol = in_List.get(list, i1); if (symbol.$arguments.length == count || overloaded.symbols.length == 1) { candidates.push(this._cache.substitute(symbol.resolvedType, symbolType.environment)); } } // Check for matches if (candidates.length == 0) { this._log.semanticErrorNoMatchingOverload(range, overloaded.name, count, null); return null; } // Check for an unambiguous match if (candidates.length == 1) { return in_List.get(candidates, 0); } // First filter by syntactic structure impossibilities. This helps break // the chicken-and-egg problem of needing to resolve argument types to // get a match and needing a match to resolve argument types. For example, // a list literal needs type context to resolve correctly. var index = 0; while (index < candidates.length) { var child = firstArgument; for (var i2 = 0, list1 = in_List.get(candidates, index).argumentTypes, count2 = list1.length; i2 < count2; i2 = i2 + 1 | 0) { var type = in_List.get(list1, i2); var kind = child.kind; if (kind == Skew.NodeKind.NULL && !type.isReference() || kind == Skew.NodeKind.INITIALIZER_LIST && this._findMember(type, '[new]') == null && this._findMember(type, '[...]') == null || kind == Skew.NodeKind.INITIALIZER_MAP && this._findMember(type, '{new}') == null && this._findMember(type, '{...}') == null || kind == Skew.NodeKind.LAMBDA && (type.kind != Skew.TypeKind.LAMBDA || type.argumentTypes.length != child.symbol.asFunctionSymbol().$arguments.length)) { in_List.removeAt(candidates, index); index = index - 1 | 0; break; } child = child.nextSibling(); } index = index + 1 | 0; } // Check for an unambiguous match if (candidates.length == 1) { return in_List.get(candidates, 0); } // If that still didn't work, resolve the arguments without type context for (var child1 = firstArgument; child1 != null; child1 = child1.nextSibling()) { this._resolveAsParameterizedExpression(child1, scope); } // Try again, this time discarding all implicit conversion failures index = 0; while (index < candidates.length) { var child2 = firstArgument; for (var i3 = 0, list2 = in_List.get(candidates, index).argumentTypes, count3 = list2.length; i3 < count3; i3 = i3 + 1 | 0) { var type1 = in_List.get(list2, i3); if (!this._cache.canImplicitlyConvert(child2.resolvedType, type1)) { in_List.removeAt(candidates, index); index = index - 1 | 0; break; } child2 = child2.nextSibling(); } index = index + 1 | 0; } // Check for an unambiguous match if (candidates.length == 1) { return in_List.get(candidates, 0); } // Extract argument types for an error if there is one var childTypes = []; for (var child3 = firstArgument; child3 != null; child3 = child3.nextSibling()) { childTypes.push(child3.resolvedType); } // Give up without a match if (candidates.length == 0) { this._log.semanticErrorNoMatchingOverload(range, overloaded.name, count, childTypes); return null; } // If that still didn't work, try type equality for (var i4 = 0, list3 = candidates, count5 = list3.length; i4 < count5; i4 = i4 + 1 | 0) { var type2 = in_List.get(list3, i4); var isMatch = true; for (var i = 0, count4 = count; i < count4; i = i + 1 | 0) { if (in_List.get(childTypes, i) != in_List.get(type2.argumentTypes, i)) { isMatch = false; break; } } if (isMatch) { return type2; } } // If that still didn't work, try picking the preferred overload var firstPreferred = null; var secondPreferred = null; for (var i5 = 0, list4 = candidates, count6 = list4.length; i5 < count6; i5 = i5 + 1 | 0) { var type3 = in_List.get(list4, i5); if (type3.symbol.isPreferred()) { secondPreferred = firstPreferred; firstPreferred = type3; } } // Check for a single preferred overload if (firstPreferred != null && secondPreferred == null) { return firstPreferred; } // Give up since the overload is ambiguous this._log.semanticErrorAmbiguousOverload(range, overloaded.name, count, childTypes); return null; }; Skew.Resolving.Resolver.prototype._resolveOverloadedFunctionCall = function(node, scope, type) { var match = this._resolveOverloadedFunction(node.callValue().range, node, scope, type); if (match != null && this._resolveFunctionCall(node, scope, match)) { this._checkAccess(node, node.callValue().internalRangeOrRange(), scope); return true; } return false; }; Skew.Resolving.Resolver.prototype._resolveCast = function(node, scope, context) { var value = node.castValue(); var type = node.castType(); var neededTypeContext = Skew.Resolving.Resolver._needsTypeContext(value); this._resolveAsParameterizedType(type, scope); this._resolveAsParameterizedExpressionWithTypeContext(value, scope, type.resolvedType); this._checkConversion(value, type.resolvedType, Skew.Resolving.ConversionKind.EXPLICIT); node.resolvedType = type.resolvedType; // Warn about unnecessary casts var range = node.internalRangeOrRange(); if (range != null && type.resolvedType != Skew.Type.DYNAMIC && value.resolvedType != Skew.Type.DYNAMIC && !neededTypeContext && (value.resolvedType == type.resolvedType || context == type.resolvedType && this._cache.canImplicitlyConvert(value.resolvedType, type.resolvedType))) { this._log.semanticWarningExtraCast(Skew.Range.span(range, type.range), value.resolvedType, type.resolvedType); } }; Skew.Resolving.Resolver.prototype._resolveConstant = function(node, scope, context) { switch (node.content.kind()) { case Skew.ContentKind.BOOL: { node.resolvedType = this._cache.boolType; break; } case Skew.ContentKind.DOUBLE: { node.resolvedType = this._cache.doubleType; break; } case Skew.ContentKind.STRING: { node.resolvedType = this._cache.stringType; break; } // The literal "0" represents the empty set for "flags" types case Skew.ContentKind.INT: { node.resolvedType = context != null && context.isFlags() && node.asInt() == 0 ? context : this._cache.intType; break; } default: { assert(false); break; } } }; Skew.Resolving.Resolver.prototype._findMember = function(type, name) { if (type.kind == Skew.TypeKind.SYMBOL) { var symbol = type.symbol; if (Skew.in_SymbolKind.isObject(symbol.kind)) { var member = in_StringMap.get(symbol.asObjectSymbol().members, name, null); if (member != null) { this._initializeSymbol(member); return member; } } } return null; }; Skew.Resolving.Resolver.prototype._sinkNullDotIntoHook = function(node, scope, context) { var nullDot = node; // Search down the chain of dot accesses and calls for "?." expression while (true) { if (nullDot.kind == Skew.NodeKind.DOT && nullDot.dotTarget() != null) { nullDot = nullDot.dotTarget(); } else if (nullDot.kind == Skew.NodeKind.CALL) { nullDot = nullDot.callValue(); } else { break; } } // Stop if this isn't a "?." expression after all if (nullDot.kind != Skew.NodeKind.NULL_DOT) { return null; } // Wrap everything in a null check var target = nullDot.dotTarget().remove(); this._resolveAsParameterizedExpression(target, scope); var test = Skew.Node.createBinary(Skew.NodeKind.NOT_EQUAL, this._extractExpression(target, scope), new Skew.Node(Skew.NodeKind.NULL).withRange(nullDot.internalRange)).withRange(target.range); var dot = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(nullDot.asString())).appendChild(target).withRange(nullDot.range).withInternalRange(nullDot.internalRange); var hook = Skew.Node.createHook(test, dot, new Skew.Node(Skew.NodeKind.NULL).withRange(nullDot.internalRangeOrRange())).withRange(nullDot.range); nullDot.become(hook.hookTrue().clone()); // This is necessary to trigger the resolve below node.resolvedType = null; hook.hookTrue().become(node.cloneAndStealChildren()); return hook; }; Skew.Resolving.Resolver.prototype._checkForMemberCompletions = function(type, range, name, check) { assert(type != null); var completionContext = this._options.completionContext; if (completionContext != null && range != null && range.source == completionContext.source && range.touches(completionContext.index) && type.kind == Skew.TypeKind.SYMBOL && Skew.in_SymbolKind.isObject(type.symbol.kind)) { var prefix = in_string.slice2(name, 0, completionContext.index - range.start | 0); var object = type.symbol.asObjectSymbol(); this._initializeSymbol(object); completionContext.range = range; for (var i = 0, list = Array.from(object.members.values()), count = list.length; i < count; i = i + 1 | 0) { var member = in_List.get(list, i); var isOnInstances = Skew.in_SymbolKind.isOnInstances(member.kind); if ((check == Skew.Resolving.Resolver.CompletionCheck.INSTANCE_ONLY ? isOnInstances : check == Skew.Resolving.Resolver.CompletionCheck.GLOBAL_ONLY ? !isOnInstances : true) && Skew.Resolving.Resolver._matchCompletion(member, prefix)) { this._initializeSymbol(member); completionContext.addCompletion(member); } } } }; Skew.Resolving.Resolver.prototype._checkForScopeCompletions = function(scope, range, name, thisObject) { var completionContext = this._options.completionContext; if (completionContext != null && range != null && range.source == completionContext.source && range.touches(completionContext.index)) { var prefix = in_string.slice2(name, 0, completionContext.index - range.start | 0); completionContext.range = range; while (scope != null) { switch (scope.kind()) { case Skew.ScopeKind.OBJECT: { var object = scope.asObjectScope().symbol; for (var i = 0, list = Array.from(object.members.values()), count = list.length; i < count; i = i + 1 | 0) { var symbol = in_List.get(list, i); if (Skew.Resolving.Resolver._matchCompletion(symbol, prefix) && (!Skew.in_SymbolKind.isOnInstances(symbol.kind) || object == thisObject)) { this._initializeSymbol(symbol); completionContext.addCompletion(symbol); } } break; } case Skew.ScopeKind.FUNCTION: { for (var i1 = 0, list1 = Array.from(scope.asFunctionScope().parameters.values()), count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var symbol1 = in_List.get(list1, i1); if (Skew.Resolving.Resolver._matchCompletion(symbol1, prefix)) { this._initializeSymbol(symbol1); completionContext.addCompletion(symbol1); } } break; } case Skew.ScopeKind.LOCAL: { for (var i2 = 0, list2 = Array.from(scope.asLocalScope().locals.values()), count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var symbol2 = in_List.get(list2, i2); if (Skew.Resolving.Resolver._matchCompletion(symbol2, prefix)) { this._initializeSymbol(symbol2); completionContext.addCompletion(symbol2); } } break; } } scope = scope.parent; } } }; Skew.Resolving.Resolver.prototype._resolveDot = function(node, scope, context) { var hook = this._sinkNullDotIntoHook(node, scope, context); if (hook != null) { node.become(hook); this._resolveAsParameterizedExpressionWithTypeContext(node, scope, context); return; } var target = node.dotTarget(); var name = node.asString(); // Resolve the target if present if (target != null) { this._resolveNode(target, scope, null); // Support IDE code completion this._checkForMemberCompletions(target.resolvedType, node.internalRange, name, target.isType() ? Skew.Resolving.Resolver.CompletionCheck.GLOBAL_ONLY : Skew.Resolving.Resolver.CompletionCheck.INSTANCE_ONLY); } // Ignore parse errors (the syntax tree is kept around for code completion) if (name == '') { return; } // Infer the target from the type context if it's omitted if (target == null) { if (context == null) { this._log.semanticErrorMissingDotContext(node.range, name); return; } target = new Skew.Node(Skew.NodeKind.TYPE).withType(context); node.appendChild(target); assert(node.dotTarget() == target); // Support IDE code completion this._checkForMemberCompletions(target.resolvedType, node.internalRange, name, target.isType() ? Skew.Resolving.Resolver.CompletionCheck.GLOBAL_ONLY : Skew.Resolving.Resolver.CompletionCheck.INSTANCE_ONLY); } // Search for a setter first, then search for a normal member var symbol = null; if (Skew.Resolving.Resolver._shouldCheckForSetter(node)) { symbol = this._findMember(target.resolvedType, name + '='); } if (symbol == null) { symbol = this._findMember(target.resolvedType, name); if (symbol == null) { // Symbol lookup failure if (target.resolvedType != Skew.Type.DYNAMIC) { var type = target.resolvedType; var correction = type.kind != Skew.TypeKind.SYMBOL || !Skew.in_SymbolKind.isObject(type.symbol.kind) ? null : type.symbol.asObjectSymbol().scope.findWithFuzzyMatching(name, target.isType() ? Skew.FuzzySymbolKind.GLOBAL_ONLY : Skew.FuzzySymbolKind.INSTANCE_ONLY, Skew.FuzzyScopeSearch.SELF_ONLY); this._reportGuardMergingFailure(node); this._log.semanticErrorUnknownMemberSymbol(node.internalRangeOrRange(), name, target.resolvedType, correction != null ? correction.name : null, correction != null ? correction.range : null); } // Dynamic symbol access else { // Make sure to warn when accessing a symbol at statement level without using it if (node.parent() != null && node.parent().kind == Skew.NodeKind.EXPRESSION) { this._log.semanticWarningUnusedExpression(node.range); } // "dynamic.foo" => "foo" if (target.kind == Skew.NodeKind.TYPE) { node.kind = Skew.NodeKind.NAME; node.removeChildren(); } // "Foo.new" => "Foo.new()" // "Foo.new()" => "Foo.new()" else if (name == 'new' && !Skew.Resolving.Resolver._isCallValue(node)) { node.become(Skew.Node.createCall(node.cloneAndStealChildren()).withType(Skew.Type.DYNAMIC).withRange(node.range)); } } return; } } // Forbid referencing a base class global or constructor function from a derived class if (Skew.Resolving.Resolver._isBaseGlobalReference(target.resolvedType.symbol, symbol)) { this._log.semanticErrorUnknownMemberSymbol(node.range, name, target.resolvedType, symbol.fullName(), symbol.range); return; } var isType = target.isType(); var needsType = !Skew.in_SymbolKind.isOnInstances(symbol.kind); // Make sure the global/instance context matches the intended usage if (isType) { if (!needsType) { this._log.semanticErrorMemberUnexpectedInstance(node.internalRangeOrRange(), symbol.name); } else if (Skew.in_SymbolKind.isFunctionOrOverloadedFunction(symbol.kind)) { this._checkIsParameterized(target); } else if (target.resolvedType.isParameterized()) { this._log.semanticErrorParameterizedType(target.range, target.resolvedType); } } else if (needsType) { this._log.semanticErrorMemberUnexpectedGlobal(node.internalRangeOrRange(), symbol.name); } // Always access referenced globals directly if (!this._options.stopAfterResolve && Skew.in_SymbolKind.isGlobalReference(symbol.kind)) { node.kind = Skew.NodeKind.NAME; node.removeChildren(); } node.symbol = symbol; node.resolvedType = this._cache.substitute(symbol.resolvedType, target.resolvedType.environment); this._automaticallyCallGetter(node, scope); }; Skew.Resolving.Resolver.prototype._resolveHook = function(node, scope, context) { this._resolveAsParameterizedExpressionWithConversion(node.hookTest(), scope, this._cache.boolType); var trueValue = node.hookTrue(); var falseValue = node.hookFalse(); // Use the type context from the parent if (context != null) { this._resolveAsParameterizedExpressionWithConversion(trueValue, scope, context); this._resolveAsParameterizedExpressionWithConversion(falseValue, scope, context); node.resolvedType = context; } // Find the common type from both branches else { this._resolveAsParameterizedExpression(trueValue, scope); this._resolveAsParameterizedExpression(falseValue, scope); var commonType = this._cache.commonImplicitType(trueValue.resolvedType, falseValue.resolvedType); // Insert casts if needed since some targets can't perform this type inference if (commonType != null) { this._checkConversion(trueValue, commonType, Skew.Resolving.ConversionKind.IMPLICIT); this._checkConversion(falseValue, commonType, Skew.Resolving.ConversionKind.IMPLICIT); node.resolvedType = commonType; } else { this._log.semanticErrorNoCommonType(Skew.Range.span(trueValue.range, falseValue.range), trueValue.resolvedType, falseValue.resolvedType); } } // Check for likely bugs where both branches look the same if (trueValue.looksTheSameAs(falseValue)) { this._log.semanticWarningIdenticalOperands(Skew.Range.span(trueValue.range, falseValue.range), node.wasNullJoin() ? '??' : ':'); } }; Skew.Resolving.Resolver.prototype._resolveInitializer = function(node, scope, context) { // Make sure to resolve the children even if the initializer is invalid if (context != null) { if (context == Skew.Type.DYNAMIC || !this._resolveInitializerWithContext(node, scope, context)) { this._resolveChildrenAsParameterizedExpressionsWithDynamicTypeContext(node, scope); } return; } // First pass: only children with type context, second pass: all children for (var pass = 0; pass < 2; pass = pass + 1 | 0) { switch (node.kind) { case Skew.NodeKind.INITIALIZER_LIST: { var type = null; // Resolve all children for this pass for (var child = node.firstChild(); child != null; child = child.nextSibling()) { if (pass != 0 || !Skew.Resolving.Resolver._needsTypeContext(child)) { this._resolveAsParameterizedExpression(child, scope); type = this._mergeCommonType(type, child); } } // Resolve remaining children using the type context if valid if (type != null && Skew.Resolving.Resolver._isValidVariableType(type)) { this._resolveInitializerWithContext(node, scope, this._cache.createListType(type)); return; } break; } case Skew.NodeKind.INITIALIZER_MAP: { var keyType = null; var valueType = null; // Resolve all children for this pass for (var child1 = node.firstChild(); child1 != null; child1 = child1.nextSibling()) { var key = child1.firstValue(); var value = child1.secondValue(); if (pass != 0 || !Skew.Resolving.Resolver._needsTypeContext(key)) { this._resolveAsParameterizedExpression(key, scope); keyType = this._mergeCommonType(keyType, key); } if (pass != 0 || !Skew.Resolving.Resolver._needsTypeContext(value)) { this._resolveAsParameterizedExpression(value, scope); valueType = this._mergeCommonType(valueType, value); } } // Resolve remaining children using the type context if valid if (keyType != null && valueType != null && Skew.Resolving.Resolver._isValidVariableType(valueType)) { assert(!this._cache.isEquivalentToInt(keyType) || !this._cache.isEquivalentToString(keyType)); if (this._cache.isEquivalentToInt(keyType)) { this._resolveInitializerWithContext(node, scope, this._cache.createIntMapType(valueType)); return; } if (this._cache.isEquivalentToString(keyType)) { this._resolveInitializerWithContext(node, scope, this._cache.createStringMapType(valueType)); return; } } break; } } } this._log.semanticErrorInitializerTypeInferenceFailed(node.range); this._resolveChildrenAsParameterizedExpressionsWithDynamicTypeContext(node, scope); }; Skew.Resolving.Resolver.prototype._resolveInitializerWithContext = function(node, scope, context) { var isList = node.kind == Skew.NodeKind.INITIALIZER_LIST; var $new = this._findMember(context, isList ? '[new]' : '{new}'); var add = this._findMember(context, isList ? '[...]' : '{...}'); // Special-case imported literals to prevent an infinite loop for list literals if (add != null && add.isImported()) { var $function = add.asFunctionSymbol(); if ($function.$arguments.length == (isList ? 1 : 2)) { var functionType = this._cache.substitute($function.resolvedType, context.environment); for (var child = node.firstChild(); child != null; child = child.nextSibling()) { if (child.kind == Skew.NodeKind.PAIR) { this._resolveAsParameterizedExpressionWithConversion(child.firstValue(), scope, in_List.get(functionType.argumentTypes, 0)); this._resolveAsParameterizedExpressionWithConversion(child.secondValue(), scope, in_List.get(functionType.argumentTypes, 1)); child.resolvedType = Skew.Type.DYNAMIC; } else { this._resolveAsParameterizedExpressionWithConversion(child, scope, in_List.get(functionType.argumentTypes, 0)); } } node.resolvedType = context; return true; } } // Use simple call chaining when there's an add operator present if (add != null) { var type = new Skew.Node(Skew.NodeKind.TYPE).withType(context).withRange(node.range).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE); var chain = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent($new != null ? $new.name : 'new')).appendChild(type).withRange(node.range).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE); while (node.hasChildren()) { var child1 = node.firstChild().remove(); var dot = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(add.name)).appendChild(chain).withRange(child1.range).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE); chain = Skew.Node.createCall(dot).withRange(child1.range).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE); if (child1.kind == Skew.NodeKind.PAIR) { chain.appendChildrenFrom(child1); } else { chain.appendChild(child1); } } node.become(chain); this._resolveAsParameterizedExpressionWithConversion(node, scope, context); return true; } // Make sure there's a constructor to call if ($new == null) { // Avoid emitting an extra error when the constructor doesn't have the right type: // // def main Foo { // return [] // } // // class Foo { // def [new](x int) {} // } // if (!node.isInitializerExpansion()) { this._log.semanticErrorInitializerTypeInferenceFailed(node.range); } return false; } // Avoid infinite expansion if (node.isInitializerExpansion()) { this._log.semanticErrorInitializerRecursiveExpansion(node.range, $new.range); return false; } var dot1 = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent($new.name)).appendChild(new Skew.Node(Skew.NodeKind.TYPE).withType(context).withRange(node.range)).withRange(node.range); // Call the initializer constructor if (node.kind == Skew.NodeKind.INITIALIZER_MAP) { var firstValues = new Skew.Node(Skew.NodeKind.INITIALIZER_LIST).withFlags(Skew.NodeFlags.IS_INITIALIZER_EXPANSION).withRange(node.range); var secondValues = new Skew.Node(Skew.NodeKind.INITIALIZER_LIST).withFlags(Skew.NodeFlags.IS_INITIALIZER_EXPANSION).withRange(node.range); for (var child2 = node.firstChild(); child2 != null; child2 = child2.nextSibling()) { var first = child2.firstValue(); var second = child2.secondValue(); firstValues.appendChild(first.remove()); secondValues.appendChild(second.remove()); } node.become(Skew.Node.createCall(dot1).withRange(node.range).appendChild(firstValues).appendChild(secondValues)); } else { var values = new Skew.Node(Skew.NodeKind.INITIALIZER_LIST).withFlags(Skew.NodeFlags.IS_INITIALIZER_EXPANSION).withRange(node.range); node.become(Skew.Node.createCall(dot1).withRange(node.range).appendChild(values.appendChildrenFrom(node))); } this._resolveAsParameterizedExpressionWithConversion(node, scope, context); return true; }; Skew.Resolving.Resolver.prototype._mergeCommonType = function(commonType, child) { if (commonType == null || child.resolvedType == Skew.Type.DYNAMIC) { return child.resolvedType; } var result = this._cache.commonImplicitType(commonType, child.resolvedType); if (result != null) { return result; } this._log.semanticErrorNoCommonType(child.range, commonType, child.resolvedType); return Skew.Type.DYNAMIC; }; Skew.Resolving.Resolver.prototype._resolveLambda = function(node, scope, context) { var ref; var symbol = node.symbol.asFunctionSymbol(); symbol.scope = new Skew.FunctionScope(scope, symbol); // Use type context to implicitly set missing types if (context != null && context.kind == Skew.TypeKind.LAMBDA) { // Copy over the argument types if they line up if (context.argumentTypes.length == symbol.$arguments.length) { for (var i = 0, count = symbol.$arguments.length; i < count; i = i + 1 | 0) { if ((ref = in_List.get(symbol.$arguments, i)).type == null) { ref.type = new Skew.Node(Skew.NodeKind.TYPE).withType(in_List.get(context.argumentTypes, i)); } } } // Copy over the return type if (symbol.returnType == null && context.returnType != null) { symbol.returnType = new Skew.Node(Skew.NodeKind.TYPE).withType(context.returnType); } } else { // Only infer non-void return types if there's no type context if (symbol.returnType == null) { symbol.flags |= Skew.SymbolFlags.SHOULD_INFER_RETURN_TYPE; } // If there's dynamic type context, treat all arguments as dynamic if (context == Skew.Type.DYNAMIC) { for (var i1 = 0, list = symbol.$arguments, count1 = list.length; i1 < count1; i1 = i1 + 1 | 0) { var argument = in_List.get(list, i1); if (argument.type == null) { argument.type = new Skew.Node(Skew.NodeKind.TYPE).withType(Skew.Type.DYNAMIC); } } if (symbol.returnType == null) { symbol.returnType = new Skew.Node(Skew.NodeKind.TYPE).withType(Skew.Type.DYNAMIC); } symbol.flags |= Skew.SymbolFlags.IS_DYNAMIC_LAMBDA; } } this._resolveFunction(symbol); // Use a LambdaType instead of a SymbolType for the node var argumentTypes = []; var returnType = symbol.returnType; for (var i2 = 0, list1 = symbol.$arguments, count2 = list1.length; i2 < count2; i2 = i2 + 1 | 0) { var argument1 = in_List.get(list1, i2); argumentTypes.push(argument1.resolvedType); } node.resolvedType = this._cache.createLambdaType(argumentTypes, returnType != null ? returnType.resolvedType : null); }; Skew.Resolving.Resolver.prototype._resolveLambdaType = function(node, scope) { var lambdaReturnType = node.lambdaReturnType(); var argumentTypes = []; var returnType = null; for (var child = node.firstChild(); child != lambdaReturnType; child = child.nextSibling()) { this._resolveAsParameterizedType(child, scope); argumentTypes.push(child.resolvedType); } // An empty return type is signaled by the type "null" if (lambdaReturnType.kind != Skew.NodeKind.TYPE || lambdaReturnType.resolvedType != Skew.Type.NULL) { this._resolveAsParameterizedType(lambdaReturnType, scope); returnType = lambdaReturnType.resolvedType; } node.resolvedType = this._cache.createLambdaType(argumentTypes, returnType); }; Skew.Resolving.Resolver.prototype._resolveName = function(node, scope) { var enclosingFunction = scope.findEnclosingFunction(); var thisVariable = enclosingFunction != null ? enclosingFunction.symbol.$this : null; var name = node.asString(); // Support IDE code completion this._checkForScopeCompletions(scope, node.range, name, thisVariable != null ? enclosingFunction.symbol.parent.asObjectSymbol() : null); if (thisVariable != null) { this._checkForMemberCompletions(thisVariable.resolvedType, node.range, name, Skew.Resolving.Resolver.CompletionCheck.NORMAL); } var symbol = scope.find(name, Skew.Resolving.Resolver._shouldCheckForSetter(node) ? Skew.ScopeSearch.ALSO_CHECK_FOR_SETTER : Skew.ScopeSearch.NORMAL); if (symbol == null) { this._reportGuardMergingFailure(node); if (name == 'this' && thisVariable != null) { this._log.semanticErrorUndeclaredSelfSymbol(node.range, name); } else { var correction = scope.findWithFuzzyMatching(name, node.shouldExpectType() ? Skew.FuzzySymbolKind.TYPE_ONLY : thisVariable != null ? Skew.FuzzySymbolKind.EVERYTHING : Skew.FuzzySymbolKind.GLOBAL_ONLY, Skew.FuzzyScopeSearch.SELF_AND_PARENTS); this._log.semanticErrorUndeclaredSymbol(node.range, name, correction == null ? null : enclosingFunction != null && Skew.Resolving.Resolver._isBaseGlobalReference(enclosingFunction.symbol.parent, correction) ? correction.fullName() : correction.name, correction != null ? correction.range : null); } return; } this._initializeSymbol(symbol); // Track reads and writes of local variables for later use if (node.isAssignTarget()) { this._recordStatistic(symbol, Skew.Resolving.SymbolStatistic.WRITE); // Also track reads for assignments if (Skew.Resolving.Resolver._isExpressionUsed(node.parent())) { this._recordStatistic(symbol, Skew.Resolving.SymbolStatistic.READ); } } else { this._recordStatistic(symbol, Skew.Resolving.SymbolStatistic.READ); } // Forbid referencing a base class global or constructor function from a derived class if (enclosingFunction != null && Skew.Resolving.Resolver._isBaseGlobalReference(enclosingFunction.symbol.parent, symbol)) { this._log.semanticErrorUndeclaredSymbol(node.range, name, symbol.fullName(), symbol.range); return; } // Automatically insert "self." before instance symbols var resolvedType = symbol.resolvedType; if (Skew.in_SymbolKind.isOnInstances(symbol.kind)) { if (thisVariable != null && enclosingFunction.symbol.parent.asObjectSymbol().isSameOrHasBaseClass(symbol.parent)) { node.become(new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(name)).appendChild(Skew.Node.createSymbolReference(thisVariable)).withRange(node.range).withInternalRange(node.range)); resolvedType = this._cache.substitute(resolvedType, thisVariable.resolvedType.environment); } else { this._log.semanticErrorMemberUnexpectedInstance(node.range, symbol.name); } } // Type parameters for objects may only be used in certain circumstances else if (symbol.kind == Skew.SymbolKind.PARAMETER_OBJECT) { var parent = scope; var isValid = false; label: while (parent != null) { switch (parent.kind()) { case Skew.ScopeKind.OBJECT: { isValid = parent.asObjectScope().symbol == symbol.parent; break label; } case Skew.ScopeKind.FUNCTION: { var $function = parent.asFunctionScope().symbol; if ($function.kind != Skew.SymbolKind.FUNCTION_LOCAL) { isValid = $function.parent == symbol.parent; break label; } break; } case Skew.ScopeKind.VARIABLE: { var variable = parent.asVariableScope().symbol; isValid = variable.kind == Skew.SymbolKind.VARIABLE_INSTANCE && variable.parent == symbol.parent; break label; } } parent = parent.parent; } if (!isValid) { this._log.semanticErrorMemberUnexpectedTypeParameter(node.range, symbol.name); } } node.symbol = symbol; node.resolvedType = resolvedType; this._automaticallyCallGetter(node, scope); }; Skew.Resolving.Resolver.prototype._resolveNullDot = function(node, scope) { node.become(this._sinkNullDotIntoHook(node, scope, null)); this._resolveAsParameterizedExpression(node, scope); }; Skew.Resolving.Resolver.prototype._resolveParameterize = function(node, scope) { var value = node.parameterizeValue(); this._resolveNode(value, scope, null); // Resolve parameter types var substitutions = []; var count = 0; for (var child = value.nextSibling(); child != null; child = child.nextSibling()) { this._resolveAsParameterizedType(child, scope); substitutions.push(child.resolvedType); count = count + 1 | 0; } var type = value.resolvedType; var parameters = type.parameters(); // If this is an overloaded symbol, try to pick an overload just using the parameter count if (parameters == null && type.kind == Skew.TypeKind.SYMBOL && Skew.in_SymbolKind.isOverloadedFunction(type.symbol.kind)) { var match = null; for (var i = 0, list = type.symbol.asOverloadedFunctionSymbol().symbols, count1 = list.length; i < count1; i = i + 1 | 0) { var candidate = in_List.get(list, i); if (candidate.parameters != null && candidate.parameters.length == count) { if (match != null) { match = null; break; } match = candidate; } } if (match != null) { type = this._cache.substitute(match.resolvedType, type.environment); parameters = type.parameters(); } } // Check for type parameters if (parameters == null || type.isParameterized()) { if (type != Skew.Type.DYNAMIC) { this._log.semanticErrorCannotParameterize(node.range, type); } value.resolvedType = Skew.Type.DYNAMIC; return; } // Check parameter count var expected = parameters.length; if (count != expected) { this._log.semanticErrorParameterCount(node.internalRangeOrRange(), expected, count); value.resolvedType = Skew.Type.DYNAMIC; return; } // Make sure all parameters have types for (var i1 = 0, list1 = parameters, count2 = list1.length; i1 < count2; i1 = i1 + 1 | 0) { var parameter = in_List.get(list1, i1); this._initializeSymbol(parameter); } // Include the symbol for use with Node.isType node.resolvedType = this._cache.substitute(type, this._cache.mergeEnvironments(type.environment, this._cache.createEnvironment(parameters, substitutions), null)); node.symbol = value.symbol; }; Skew.Resolving.Resolver.prototype._resolveSequence = function(node, scope, context) { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._resolveAsParameterizedExpressionWithTypeContext(child, scope, child.nextSibling() == null ? context : null); } if (node.hasChildren()) { node.resolvedType = node.lastChild().resolvedType; } }; Skew.Resolving.Resolver.prototype._resolveStringInterpolation = function(node, scope) { assert(node.childCount() % 2 == 1); this._resolveChildrenAsParameterizedExpressions(node, scope); // TypeScript supports string interpolation natively if (this._options.target instanceof Skew.TypeScriptTarget) { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { if (child.resolvedType != Skew.Type.DYNAMIC && child.resolvedType != this._cache.stringType) { var temp = new Skew.Node(Skew.NodeKind.NULL); child.replaceWith(temp); child = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent('toString')).appendChild(child).withRange(child.range).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE); temp.replaceWith(child); } this._resolveAsParameterizedExpressionWithConversion(child, scope, this._cache.stringType); } node.resolvedType = this._cache.stringType; return; } // Convert the string interpolation into a series of string concatenations var joined = null; while (node.hasChildren()) { var child1 = node.firstChild().remove(); if (child1.isString() && child1.asString() == '') { continue; } else if (child1.resolvedType != Skew.Type.DYNAMIC && child1.resolvedType != this._cache.stringType) { child1 = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent('toString')).appendChild(child1).withRange(child1.range).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE); this._resolveAsParameterizedExpressionWithConversion(child1, scope, this._cache.stringType); } joined = joined != null ? Skew.Node.createBinary(Skew.NodeKind.ADD, joined, child1).withRange(Skew.Range.span(joined.range, child1.range)).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE) : child1; this._resolveAsParameterizedExpressionWithConversion(joined, scope, this._cache.stringType); } node.become(joined != null ? joined : new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.StringContent(''))); this._resolveAsParameterizedExpressionWithConversion(node, scope, this._cache.stringType); }; Skew.Resolving.Resolver.prototype._resolveSuper = function(node, scope) { var $function = scope.findEnclosingFunction(); var symbol = $function != null ? $function.symbol : null; var baseType = symbol != null ? symbol.parent.asObjectSymbol().baseType : null; var overridden = baseType == null ? null : this._findMember(baseType, symbol.name); if (overridden == null) { this._log.semanticErrorBadSuper(node.range); return; } // Calling a static method doesn't need special handling if (overridden.kind == Skew.SymbolKind.FUNCTION_GLOBAL) { node.kind = Skew.NodeKind.NAME; } node.resolvedType = overridden.resolvedType; node.symbol = overridden; this._automaticallyCallGetter(node, scope); }; Skew.Resolving.Resolver.prototype._resolveTypeCheck = function(node, scope) { var value = node.typeCheckValue(); var type = node.typeCheckType(); this._resolveAsParameterizedExpression(value, scope); this._resolveAsParameterizedType(type, scope); this._checkConversion(value, type.resolvedType, Skew.Resolving.ConversionKind.EXPLICIT); node.resolvedType = this._cache.boolType; // Type checks don't work against interfaces if (type.resolvedType.isInterface()) { this._log.semanticWarningBadTypeCheck(type.range, type.resolvedType); } // Warn about unnecessary type checks else if (value.resolvedType != Skew.Type.DYNAMIC && this._cache.canImplicitlyConvert(value.resolvedType, type.resolvedType) && (type.resolvedType != Skew.Type.DYNAMIC || type.kind == Skew.NodeKind.TYPE)) { this._log.semanticWarningExtraTypeCheck(node.range, value.resolvedType, type.resolvedType); } }; Skew.Resolving.Resolver.prototype._resolveXML = function(node, scope) { var ref; var tag = node.xmlTag(); var attributes = node.xmlAttributes(); var children = node.xmlChildren(); var closingTag = (ref = node.xmlClosingTag()) != null ? ref.remove() : null; var initialErrorCount = this._log.errorCount(); this._resolveAsParameterizedType(tag, scope); // Make sure there's a constructor to call if (this._findMember(tag.resolvedType, 'new') == null) { attributes.removeChildren(); children.removeChildren(); attributes.resolvedType = Skew.Type.DYNAMIC; // Only report an error if there isn't one already if (this._log.errorCount() == initialErrorCount) { this._log.semanticErrorXMLCannotConstruct(node.range, tag.resolvedType); } return; } // Call the constructor var value = new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent('new')).appendChild(tag.clone()).withRange(node.range).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE); var needsSequence = attributes.hasChildren() || children.hasChildren(); var result = value; this._resolveAsParameterizedExpression(value, scope); if (needsSequence) { result = new Skew.Node(Skew.NodeKind.SEQUENCE).withRange(node.range).appendChild(this._extractExpression(value, scope).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE)); } // Assign to attributes if necessary while (attributes.hasChildren()) { var child = attributes.firstChild().remove(); var name = child.binaryLeft(); while (name.kind == Skew.NodeKind.DOT) { name = name.dotTarget(); } assert(name.kind == Skew.NodeKind.NAME); name.replaceWith(new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(name.asString())).appendChild(value.clone()).withRange(name.range)); result.appendChild(child); } // Make sure there's an append function to call if needed if (children.hasChildren() && this._findMember(tag.resolvedType, '<>...') == null) { this._log.semanticErrorXMLMissingAppend(children.firstChild().range, tag.resolvedType); children.removeChildren(); } // Append children else { // Don't need a closure if all children are expressions var isJustExpressions = true; for (var child1 = children.firstChild(); child1 != null; child1 = child1.nextSibling()) { if (child1.kind != Skew.NodeKind.EXPRESSION) { isJustExpressions = false; break; } } // All expression statements get passed as arguments to "<>..." this._recursivelyReplaceExpressionsInXML(children, value); // Add to the sequence if (isJustExpressions) { for (var child2 = children.firstChild(); child2 != null; child2 = child2.nextSibling()) { result.appendChild(child2.expressionValue().remove()); } } // Wrap in a closure else { var symbol = new Skew.FunctionSymbol(Skew.SymbolKind.FUNCTION_LOCAL, ''); symbol.range = children.range; symbol.block = children.remove(); result.appendChild(Skew.Node.createCall(Skew.Node.createLambda(symbol).withRange(symbol.range)).withRange(symbol.range)); } } // Resolve the closing tag for IDE tooltips if (closingTag != null) { this._resolveAsParameterizedType(closingTag, scope); value = Skew.Node.createCast(value, closingTag); } // Resolve the value node.become(needsSequence ? result.appendChild(value) : value); this._resolveAsParameterizedExpression(node, scope); }; Skew.Resolving.Resolver.prototype._recursivelyReplaceExpressionsInXML = function(node, reference) { assert(node.kind == Skew.NodeKind.BLOCK); for (var child = node.firstChild(); child != null; child = child.nextSibling()) { switch (child.kind) { case Skew.NodeKind.EXPRESSION: { child.appendChild(Skew.Node.createCall(new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent('<>...')).appendChild(reference.clone()).withRange(child.range).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE)).appendChild(child.expressionValue().remove()).withRange(child.range)); break; } case Skew.NodeKind.FOR: { this._recursivelyReplaceExpressionsInXML(child.forBlock(), reference); break; } case Skew.NodeKind.FOREACH: { this._recursivelyReplaceExpressionsInXML(child.foreachBlock(), reference); break; } case Skew.NodeKind.IF: { this._recursivelyReplaceExpressionsInXML(child.ifTrue(), reference); if (child.ifFalse() != null) { this._recursivelyReplaceExpressionsInXML(child.ifFalse(), reference); } break; } case Skew.NodeKind.SWITCH: { for (var nested = child.switchValue().nextSibling(); nested != null; nested = nested.nextSibling()) { this._recursivelyReplaceExpressionsInXML(nested.caseBlock(), reference); } break; } case Skew.NodeKind.TRY: { var tryBlock = child.tryBlock(); var finallyBlock = child.finallyBlock(); this._recursivelyReplaceExpressionsInXML(tryBlock, reference); for (var nested1 = tryBlock.nextSibling(); nested1 != finallyBlock; nested1 = nested1.nextSibling()) { this._recursivelyReplaceExpressionsInXML(nested1.catchBlock(), reference); } if (finallyBlock != null) { this._recursivelyReplaceExpressionsInXML(finallyBlock, reference); } break; } case Skew.NodeKind.WHILE: { this._recursivelyReplaceExpressionsInXML(child.whileBlock(), reference); break; } } } }; Skew.Resolving.Resolver.prototype._resolveBinary = function(node, scope, context) { var kind = node.kind; var left = node.binaryLeft(); var right = node.binaryRight(); // Special-case the "??" operator if (kind == Skew.NodeKind.NULL_JOIN) { this._resolveAsParameterizedExpressionWithTypeContext(left, scope, context); this._resolveAsParameterizedExpressionWithTypeContext(right, scope, context != null ? context : left.resolvedType); var test = Skew.Node.createBinary(Skew.NodeKind.NOT_EQUAL, this._extractExpressionForAssignment(left, scope), new Skew.Node(Skew.NodeKind.NULL)).withRange(left.range); node.become(Skew.Node.createHook(test, left.remove(), right.remove()).withRange(node.range).withFlags(Skew.NodeFlags.WAS_NULL_JOIN)); this._resolveAsParameterizedExpressionWithTypeContext(node, scope, context); return; } // Special-case the "?=" operator if (kind == Skew.NodeKind.ASSIGN_NULL) { this._resolveAsParameterizedExpressionWithTypeContext(left, scope, context); this._checkStorage(left, scope); var test1 = Skew.Node.createBinary(Skew.NodeKind.NOT_EQUAL, this._extractExpressionForAssignment(left, scope), new Skew.Node(Skew.NodeKind.NULL)).withRange(left.range); var assign = Skew.Node.createBinary(Skew.NodeKind.ASSIGN, left.remove(), right.remove()).withRange(node.range).withFlags(Skew.NodeFlags.WAS_ASSIGN_NULL); node.become(Skew.Node.createHook(test1, left.clone(), assign).withRange(node.range)); this._resolveAsParameterizedExpressionWithTypeContext(node, scope, context); return; } // Special-case the equality operators if (kind == Skew.NodeKind.EQUAL || kind == Skew.NodeKind.NOT_EQUAL) { if (Skew.Resolving.Resolver._needsTypeContext(left)) { this._resolveAsParameterizedExpression(right, scope); this._resolveAsParameterizedExpressionWithTypeContext(left, scope, right.resolvedType); } else if (Skew.Resolving.Resolver._needsTypeContext(right)) { this._resolveAsParameterizedExpression(left, scope); this._resolveAsParameterizedExpressionWithTypeContext(right, scope, left.resolvedType); } else { this._resolveAsParameterizedExpression(left, scope); this._resolveAsParameterizedExpression(right, scope); } // Check for likely bugs "x == x" or "x != x", except when this is used to test for NaN if (left.looksTheSameAs(right) && left.hasNoSideEffects() && right.hasNoSideEffects() && !this._cache.isEquivalentToDouble(left.resolvedType) && left.resolvedType != Skew.Type.DYNAMIC) { this._log.semanticWarningIdenticalOperands(node.range, kind == Skew.NodeKind.EQUAL ? '==' : '!='); } // The two types must be compatible var commonType = this._cache.commonImplicitType(left.resolvedType, right.resolvedType); if (commonType == null) { this._log.semanticErrorNoCommonType(node.range, left.resolvedType, right.resolvedType); } else { node.resolvedType = this._cache.boolType; // Make sure type casts are inserted this._checkConversion(left, commonType, Skew.Resolving.ConversionKind.IMPLICIT); this._checkConversion(right, commonType, Skew.Resolving.ConversionKind.IMPLICIT); } return; } // Special-case assignment since it's not overridable if (kind == Skew.NodeKind.ASSIGN) { this._resolveAsParameterizedExpression(left, scope); // Automatically call setters if (left.symbol != null && left.symbol.isSetter()) { node.become(Skew.Node.createCall(left.remove()).withRange(node.range).withInternalRange(right.range).appendChild(right.remove())); this._resolveAsParameterizedExpression(node, scope); } // Resolve the right side using type context from the left side else { this._resolveAsParameterizedExpressionWithConversion(right, scope, left.resolvedType); node.resolvedType = left.resolvedType; this._checkStorage(left, scope); // Check for likely bugs "x = x" if (left.looksTheSameAs(right) && left.hasNoSideEffects() && right.hasNoSideEffects()) { this._log.semanticWarningIdenticalOperands(node.range, node.wasAssignNull() ? '?=' : '='); } // Check for likely bugs "x = y" instead of "x == y" based on type context else if (node.internalRange != null && context != null && context != Skew.Type.DYNAMIC && left.resolvedType == Skew.Type.DYNAMIC && right.resolvedType != Skew.Type.DYNAMIC && !this._cache.canImplicitlyConvert(right.resolvedType, context)) { this._log.semanticWarningSuspiciousAssignmentLocation(node.internalRangeOrRange()); } // Check for likely bugs "x = y" instead of "x == y" based on expression context else if (node.internalRange != null && node.parent() != null) { var parent = node.parent(); var parentKind = parent.kind; if (parentKind == Skew.NodeKind.IF || parentKind == Skew.NodeKind.WHILE || parentKind == Skew.NodeKind.LOGICAL_AND || parentKind == Skew.NodeKind.LOGICAL_OR || parentKind == Skew.NodeKind.NOT || parentKind == Skew.NodeKind.RETURN && !parent.isImplicitReturn() || parentKind == Skew.NodeKind.HOOK && node == parent.hookTest()) { this._log.semanticWarningSuspiciousAssignmentLocation(node.internalRangeOrRange()); } } } return; } // Special-case short-circuit logical operators since they aren't overridable if (kind == Skew.NodeKind.LOGICAL_AND || kind == Skew.NodeKind.LOGICAL_OR) { this._resolveAsParameterizedExpressionWithConversion(left, scope, this._cache.boolType); this._resolveAsParameterizedExpressionWithConversion(right, scope, this._cache.boolType); node.resolvedType = this._cache.boolType; // Check for likely bugs "x && x" or "x || x" if (left.looksTheSameAs(right) && left.hasNoSideEffects() && right.hasNoSideEffects() && (!left.isBool() || !right.isBool())) { this._log.semanticWarningIdenticalOperands(node.range, kind == Skew.NodeKind.LOGICAL_AND ? '&&' : '||'); } return; } this._resolveOperatorOverload(node, scope, context); }; Skew.Resolving.Resolver.prototype._generateReference = function(scope, type) { var enclosingFunction = scope.findEnclosingFunctionOrLambda(); var symbol = null; // Add a local variable if (enclosingFunction != null) { var block = enclosingFunction.symbol.block; // Make sure the call to "super" is still the first statement var after = block.firstChild(); if (after.isSuperCallStatement()) { after = after.nextSibling(); } // Add the new variable to the top of the function symbol = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, enclosingFunction.generateName('ref')); block.insertChildBefore(after, new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(Skew.Node.createVariable(symbol))); } // Otherwise, add a global variable else { symbol = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_GLOBAL, this._global.scope.generateName('ref')); symbol.parent = this._global; this._generatedGlobalVariables.push(symbol); } // Force-initialize the symbol symbol.initializeWithType(type); return Skew.Node.createSymbolReference(symbol); }; Skew.Resolving.Resolver.prototype._extractExpression = function(node, scope) { assert(node.resolvedType != null); if (node.kind == Skew.NodeKind.NAME || node.kind == Skew.NodeKind.CONSTANT) { return node.clone(); } // Replace the original expression with a reference var reference = this._generateReference(scope, node.resolvedType).withRange(node.range).withFlags(Skew.NodeFlags.IS_IGNORED_BY_IDE); var setup = node.cloneAndStealChildren(); node.become(reference); return Skew.Node.createBinary(Skew.NodeKind.ASSIGN, reference, setup).withType(node.resolvedType).withRange(node.range); }; // Expressions with side effects must be stored to temporary variables // if they need to be duplicated in an expression. This does the variable // allocation and storage and returns a partial assigment. // // Examples: // // "a" stays "a" and returns "a" // "a.b" stays "a.b" and returns "a.b" // "a[0]" stays "a[0]" and returns "a[0]" // "a().b" becomes "ref.b" and returns "(ref = a()).b" // "a()[0]" becomes "ref[0]" and returns "(ref = a())[0]" // "a()[b()]" becomes "ref[ref2]" and returns "(ref = a())[ref2 = b()]" // Skew.Resolving.Resolver.prototype._extractExpressionForAssignment = function(node, scope) { assert(node.resolvedType != null); // Handle dot expressions if (node.kind == Skew.NodeKind.DOT && node.symbol != null) { return new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(node.asString())).appendChild(this._extractExpression(node.dotTarget(), scope)).withSymbol(node.symbol).withType(node.resolvedType).withRange(node.range).withInternalRange(node.internalRange); } // Handle index expressions if (node.kind == Skew.NodeKind.INDEX) { var left = this._extractExpression(node.indexLeft(), scope); return Skew.Node.createIndex(left, this._extractExpression(node.indexRight(), scope)).withRange(node.range); } // Handle name expressions if (node.kind == Skew.NodeKind.NAME) { return node.clone(); } // Handle everything else return this._extractExpression(node, scope); }; Skew.Resolving.Resolver.prototype._resolveOperatorOverload = function(node, scope, context) { // The order of operands are reversed for the "in" operator var kind = node.kind; var reverseBinaryOrder = kind == Skew.NodeKind.IN; var first = node.firstChild(); var second = first.nextSibling(); var target = reverseBinaryOrder ? second : first; var other = Skew.in_NodeKind.isBinary(kind) ? reverseBinaryOrder ? first : second : null; var isBitOperation = Skew.in_NodeKind.isBitOperation(kind); var bitContext = isBitOperation && context != null && context.isFlags() ? context : null; // Allow "foo in [.FOO, .BAR]" if (kind == Skew.NodeKind.IN && target.kind == Skew.NodeKind.INITIALIZER_LIST && !Skew.Resolving.Resolver._needsTypeContext(other)) { this._resolveAsParameterizedExpression(other, scope); this._resolveAsParameterizedExpressionWithTypeContext(target, scope, other.resolvedType != Skew.Type.DYNAMIC ? this._cache.createListType(other.resolvedType) : null); } // Resolve just the target since the other arguments may need type context from overload resolution else { this._resolveAsParameterizedExpressionWithTypeContext(target, scope, bitContext); } // Warn about shifting by 0 in the original source code, since that doesn't // do anything when the arguments are integers and so is likely a mistake if (Skew.in_NodeKind.isShift(kind) && this._cache.isEquivalentToInt(target.resolvedType) && other.isInt() && other.asInt() == 0) { this._log.semanticWarningShiftByZero(node.range); } // Can't do overload resolution on the dynamic type var type = target.resolvedType; if (type == Skew.Type.DYNAMIC) { if (Skew.in_NodeKind.isAssign(kind) && kind != Skew.NodeKind.ASSIGN_INDEX) { this._checkStorage(target, scope); } this._resolveChildrenAsParameterizedExpressions(node, scope); return; } // Check if the operator can be overridden at all var info = in_IntMap.get1(Skew.operatorInfo, kind); if (info.kind != Skew.OperatorKind.OVERRIDABLE) { this._log.semanticErrorUnknownMemberSymbol(node.internalRangeOrRange(), info.text, type, null, null); this._resolveChildrenAsParameterizedExpressions(node, scope); return; } // Numeric conversions var enumFlagsType = null; // Binary operations if (other != null) { // Assignment operations aren't symmetric if (!Skew.in_NodeKind.isBinaryAssign(kind)) { if (type == this._cache.intType) { this._resolveAsParameterizedExpression(other, scope); // Auto-convert doubles to ints if (other.resolvedType == this._cache.doubleType) { this._checkConversion(target, this._cache.doubleType, Skew.Resolving.ConversionKind.IMPLICIT); type = this._cache.doubleType; } } // Check if the target is an enum else if (type.isEnumOrFlags()) { this._resolveAsParameterizedExpressionWithTypeContext(other, scope, bitContext != null ? bitContext : (isBitOperation || kind == Skew.NodeKind.IN) && type.isFlags() ? type : null); // Auto-convert enums to ints when both operands can be converted if (this._cache.isNumeric(other.resolvedType)) { type = this._cache.commonImplicitType(type, other.resolvedType); assert(type != null); if (type.isEnumOrFlags()) { if (type.isFlags()) { enumFlagsType = type; } type = this._cache.intType; } this._checkConversion(target, type, Skew.Resolving.ConversionKind.IMPLICIT); this._checkConversion(other, type, Skew.Resolving.ConversionKind.IMPLICIT); } } } // Allow certain operations on "flags" types else if (isBitOperation && type.isFlags()) { this._resolveAsParameterizedExpressionWithTypeContext(other, scope, type); enumFlagsType = type; type = this._cache.intType; this._checkConversion(other, type, Skew.Resolving.ConversionKind.IMPLICIT); } } // Allow "~x" on "flags" types else if (kind == Skew.NodeKind.COMPLEMENT && type.isEnumOrFlags()) { if (type.isFlags()) { enumFlagsType = type; } type = this._cache.intType; this._checkConversion(target, type, Skew.Resolving.ConversionKind.IMPLICIT); } // Find the operator method var isComparison = Skew.in_NodeKind.isBinaryComparison(kind); var name = isComparison ? '<=>' : info.text; var symbol = this._findMember(type, name); var extracted = null; var wasUnaryPostfix = false; // Convert operators like "+=" to a "+" inside a "=" if (symbol == null && info.assignKind != Skew.NodeKind.NULL) { symbol = this._findMember(type, in_IntMap.get1(Skew.operatorInfo, info.assignKind).text); if (symbol != null) { extracted = this._extractExpressionForAssignment(target, scope); if (kind == Skew.NodeKind.PREFIX_INCREMENT || kind == Skew.NodeKind.PREFIX_DECREMENT || kind == Skew.NodeKind.POSTFIX_INCREMENT || kind == Skew.NodeKind.POSTFIX_DECREMENT) { node.appendChild(this._cache.createInt(1).withRange(node.internalRangeOrRange())); // This no longer makes sense node.internalRange = null; } wasUnaryPostfix = Skew.in_NodeKind.isUnaryPostfix(kind) && Skew.Resolving.Resolver._isExpressionUsed(node); kind = info.assignKind; node.kind = kind; } } // Special-case the "in" operator on "flags" types if (symbol == null && kind == Skew.NodeKind.IN && enumFlagsType != null) { node.become(Skew.Node.createBinary(Skew.NodeKind.NOT_EQUAL, Skew.Node.createBinary(Skew.NodeKind.BITWISE_AND, other.remove(), target.remove()).withRange(node.range), this._cache.createInt(0)).withRange(node.range)); this._resolveAsParameterizedExpression(node, scope); return; } // Fail if the operator wasn't found if (symbol == null) { this._log.semanticErrorUnknownMemberSymbol(node.internalRangeOrRange(), name, type, null, null); this._resolveChildrenAsParameterizedExpressions(node, scope); return; } var symbolType = this._cache.substitute(symbol.resolvedType, type.environment); // Resolve the overload now so the symbol's properties can be inspected if (Skew.in_SymbolKind.isOverloadedFunction(symbol.kind)) { if (reverseBinaryOrder) { first.swapWith(second); } symbolType = this._resolveOverloadedFunction(node.internalRangeOrRange(), node, scope, symbolType); if (reverseBinaryOrder) { first.swapWith(second); } if (symbolType == null) { this._resolveChildrenAsParameterizedExpressions(node, scope); return; } symbol = symbolType.symbol; } var isRawImport = symbol.isImported() && !symbol.isRenamed(); node.symbol = symbol; this._checkAccess(node, node.internalRangeOrRange(), scope); // Check for a valid storage location for imported operators if (Skew.in_NodeKind.isAssign(kind) && kind != Skew.NodeKind.ASSIGN_INDEX && symbol.isImported() && extracted == null) { this._checkStorage(target, scope); } // "<", ">", "<=", or ">=" if (isComparison && (isRawImport || type == this._cache.intType || type == this._cache.doubleType)) { this._resolveChildrenAsParameterizedExpressions(node, scope); node.resolvedType = this._cache.boolType; node.symbol = null; } // Don't replace the operator with a call if it's just used for type checking else if (isRawImport) { if (reverseBinaryOrder) { first.swapWith(second); } if (!this._resolveFunctionCall(node, scope, symbolType)) { this._resolveChildrenAsParameterizedExpressions(node, scope); } if (reverseBinaryOrder) { first.swapWith(second); } // Handle "flags" types if (isBitOperation && enumFlagsType != null) { node.resolvedType = enumFlagsType; } } else { // Resolve the method call if (reverseBinaryOrder) { first.swapWith(second); } node.prependChild(Skew.Node.createMemberReference(target.remove(), symbol).withRange(node.internalRangeOrRange())); // Implement the logic for the "<=>" operator if (isComparison) { var call = new Skew.Node(Skew.NodeKind.CALL).appendChildrenFrom(node).withRange(node.range); node.appendChild(call); node.appendChild(this._cache.createInt(0)); node.resolvedType = this._cache.boolType; this._resolveFunctionCall(call, scope, symbolType); } // All other operators are just normal method calls else { node.kind = Skew.NodeKind.CALL; this._resolveFunctionCall(node, scope, symbolType); } } if (extracted != null) { // The expression used to initialize the assignment must return a value if (symbolType.returnType == null) { this._log.semanticErrorUseOfVoidFunction(node.range, symbol.name, symbol.range); } // Wrap everything in an assignment if the assignment target was extracted this._promoteToAssignment(node, extracted); this._resolveAsParameterizedExpression(node, scope); // Handle custom unary postfix operators if (wasUnaryPostfix) { node.become(Skew.Node.createBinary(kind, node.cloneAndStealChildren(), this._cache.createInt(-1).withRange(node.internalRangeOrRange())).withRange(node.range)); this._resolveAsParameterizedExpression(node, scope); } } // Handle custom unary assignment operators else if (Skew.in_NodeKind.isUnaryAssign(kind) && !isRawImport) { // "foo(x++)" => "foo((ref = x, x = ref.increment(), ref))" if (Skew.in_NodeKind.isUnaryPostfix(kind) && Skew.Resolving.Resolver._isExpressionUsed(node)) { var reference = this._generateReference(scope, target.resolvedType).withRange(target.range); var original = this._extractExpressionForAssignment(target, scope); target.replaceWith(reference); this._promoteToAssignment(node, target); node.become(new Skew.Node(Skew.NodeKind.SEQUENCE).appendChild(Skew.Node.createBinary(Skew.NodeKind.ASSIGN, reference.clone(), original).withRange(node.range)).appendChild(node.cloneAndStealChildren()).appendChild(reference.clone()).withRange(node.range)); this._resolveAsParameterizedExpression(node, scope); } // "foo(++x)" => "foo(x = x.increment())" else { this._promoteToAssignment(node, this._extractExpressionForAssignment(target, scope)); this._resolveAsParameterizedExpression(node, scope); } } }; Skew.Resolving.Resolver.prototype._promoteToAssignment = function(node, extracted) { assert(extracted.parent() == null); if (extracted.kind == Skew.NodeKind.INDEX) { extracted.kind = Skew.NodeKind.ASSIGN_INDEX; extracted.appendChild(node.cloneAndStealChildren()); node.become(extracted); } else { node.become(Skew.Node.createBinary(Skew.NodeKind.ASSIGN, extracted, node.cloneAndStealChildren()).withRange(node.range)); } }; Skew.Resolving.Resolver.prototype._automaticallyCallGetter = function(node, scope) { var symbol = node.symbol; if (symbol == null) { return false; } var kind = symbol.kind; var parent = node.parent(); // Never call a getter if type parameters are present if (parent != null && parent.kind == Skew.NodeKind.PARAMETERIZE && Skew.Resolving.Resolver._isCallValue(parent)) { return false; } // The check for getters is complicated by overloaded functions if (!symbol.isGetter() && Skew.in_SymbolKind.isOverloadedFunction(kind) && (!Skew.Resolving.Resolver._isCallValue(node) || parent.hasOneChild())) { var overloaded = symbol.asOverloadedFunctionSymbol(); for (var i = 0, list = overloaded.symbols, count1 = list.length; i < count1; i = i + 1 | 0) { var getter = in_List.get(list, i); // Just return the first getter assuming errors for duplicate getters // were already logged when the overloaded symbol was initialized if (getter.isGetter()) { node.resolvedType = this._cache.substitute(getter.resolvedType, node.resolvedType.environment); node.symbol = getter; symbol = getter; break; } } } this._checkAccess(node, node.internalRangeOrRange(), scope); // Automatically wrap the getter in a call expression if (symbol.isGetter()) { node.become(Skew.Node.createCall(node.cloneAndStealChildren()).withRange(node.range)); this._resolveAsParameterizedExpression(node, scope); return true; } // Forbid bare function references if (!symbol.isSetter() && node.resolvedType != Skew.Type.DYNAMIC && Skew.in_SymbolKind.isFunctionOrOverloadedFunction(kind) && kind != Skew.SymbolKind.FUNCTION_ANNOTATION && !Skew.Resolving.Resolver._isCallValue(node) && (parent == null || parent.kind != Skew.NodeKind.PARAMETERIZE || !Skew.Resolving.Resolver._isCallValue(parent))) { var lower = 2147483647; var upper = -1; if (Skew.in_SymbolKind.isFunction(kind)) { lower = upper = symbol.asFunctionSymbol().$arguments.length; } else { for (var i1 = 0, list1 = symbol.asOverloadedFunctionSymbol().symbols, count2 = list1.length; i1 < count2; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); var count = $function.$arguments.length; if (count < lower) { lower = count; } if (count > upper) { upper = count; } } } this._log.semanticErrorMustCallFunction(node.internalRangeOrRange(), symbol.name, lower, upper); node.resolvedType = Skew.Type.DYNAMIC; } return false; }; Skew.Resolving.Resolver.prototype._convertSwitchToIfChain = function(node, scope) { var variable = new Skew.VariableSymbol(Skew.SymbolKind.VARIABLE_LOCAL, scope.generateName('value')); var value = node.switchValue().remove(); var block = null; // Stash the variable being switched over so it's only evaluated once variable.initializeWithType(value.resolvedType); variable.value = value; node.parent().insertChildBefore(node, new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(Skew.Node.createVariable(variable))); // Build the chain in reverse starting with the last case for (var child = node.lastChild(); child != null; child = child.previousSibling()) { var caseBlock = child.caseBlock().remove(); var test = null; // Combine adjacent cases in a "||" chain while (child.hasChildren()) { var caseValue = Skew.Node.createBinary(Skew.NodeKind.EQUAL, Skew.Node.createSymbolReference(variable), child.firstChild().remove()).withType(this._cache.boolType); test = test != null ? Skew.Node.createBinary(Skew.NodeKind.LOGICAL_OR, test, caseValue).withType(this._cache.boolType) : caseValue; } // Chain if-else statements together block = test != null ? new Skew.Node(Skew.NodeKind.BLOCK).appendChild(Skew.Node.createIf(test, caseBlock, block)) : caseBlock; } // Replace the switch statement with the if chain if (block != null) { node.replaceWithChildrenFrom(block); } else { node.remove(); } }; Skew.Resolving.Resolver._shouldCheckForSetter = function(node) { return node.parent() != null && node.parent().kind == Skew.NodeKind.ASSIGN && node == node.parent().binaryLeft(); }; Skew.Resolving.Resolver._isExpressionUsed = function(node) { // Check for a null parent to handle variable initializers var parent = node.parent(); return parent == null || parent.kind != Skew.NodeKind.EXPRESSION && !parent.isImplicitReturn() && (parent.kind != Skew.NodeKind.ANNOTATION || node != parent.annotationValue()) && (parent.kind != Skew.NodeKind.FOR || node != parent.forUpdate()) && parent.kind != Skew.NodeKind.SEQUENCE; }; Skew.Resolving.Resolver._isValidVariableType = function(type) { return type != Skew.Type.NULL && (type.kind != Skew.TypeKind.SYMBOL || !Skew.in_SymbolKind.isFunctionOrOverloadedFunction(type.symbol.kind)); }; Skew.Resolving.Resolver._isBaseGlobalReference = function(parent, member) { return parent != null && parent.kind == Skew.SymbolKind.OBJECT_CLASS && Skew.in_SymbolKind.isGlobalReference(member.kind) && member.parent != parent && member.parent.kind == Skew.SymbolKind.OBJECT_CLASS && parent.asObjectSymbol().hasBaseClass(member.parent); }; Skew.Resolving.Resolver._isCallValue = function(node) { var parent = node.parent(); return parent != null && parent.kind == Skew.NodeKind.CALL && node == parent.callValue(); }; Skew.Resolving.Resolver._isCallReturningVoid = function(node) { return node.kind == Skew.NodeKind.CALL && (node.symbol != null && node.symbol.resolvedType.returnType == null || node.callValue().resolvedType.kind == Skew.TypeKind.LAMBDA && node.callValue().resolvedType.returnType == null); }; Skew.Resolving.Resolver._needsTypeContext = function(node) { return node.kind == Skew.NodeKind.DOT && node.dotTarget() == null || node.kind == Skew.NodeKind.HOOK && Skew.Resolving.Resolver._needsTypeContext(node.hookTrue()) && Skew.Resolving.Resolver._needsTypeContext(node.hookFalse()) || Skew.in_NodeKind.isInitializer(node.kind); }; Skew.Resolving.Resolver._matchCompletion = function(symbol, prefix) { if (symbol.state == Skew.SymbolState.INITIALIZING) { return false; } var name = symbol.name.toLowerCase(); if (in_string.get1(name, 0) == 95 && in_string.get1(prefix, 0) != 95) { name = in_string.slice1(name, 1); } return name.startsWith(prefix.toLowerCase()); }; Skew.Resolving.Resolver.CompletionCheck = { NORMAL: 0, INSTANCE_ONLY: 1, GLOBAL_ONLY: 2 }; Skew.Resolving.Resolver.GuardMergingFailure = function() { }; Skew.LambdaConversion = {}; Skew.LambdaConversion.CaptureKind = { FUNCTION: 0, LAMBDA: 1, LOOP: 2 }; Skew.LambdaConversion.Definition = function(symbol, node, scope) { this.symbol = symbol; this.node = node; this.scope = scope; this.isCaptured = false; this.member = null; }; Skew.LambdaConversion.Use = function(definition, node) { this.definition = definition; this.node = node; }; Skew.LambdaConversion.Copy = function(scope) { this.scope = scope; this.member = null; }; Skew.LambdaConversion.Scope = function(kind, node, enclosingFunction, parent) { this.id = Skew.LambdaConversion.Scope._nextID = Skew.LambdaConversion.Scope._nextID + 1 | 0; this.kind = kind; this.node = node; this.enclosingFunction = enclosingFunction; this.parent = parent; this.hasCapturedDefinitions = false; this.hasCapturingUses = false; this.environmentObject = null; this.environmentVariable = null; this.environmentConstructor = null; this.environmentConstructorCall = null; this.definitions = []; this.uses = []; this.copies = []; this.definitionLookup = new Map(); this.copyLookup = new Map(); }; Skew.LambdaConversion.Scope.prototype.recordDefinition = function(symbol, node) { assert(!this.definitionLookup.has(symbol.id)); var definition = new Skew.LambdaConversion.Definition(symbol, node, this); this.definitions.push(definition); in_IntMap.set(this.definitionLookup, symbol.id, definition); }; Skew.LambdaConversion.Scope.prototype.recordUse = function(symbol, node) { var isCaptured = false; // Walk up the scope chain for (var scope = this; scope != null; scope = scope.parent) { var definition = in_IntMap.get(scope.definitionLookup, symbol.id, null); // Stop once the definition is found if (definition != null) { this.uses.push(new Skew.LambdaConversion.Use(definition, node)); if (isCaptured) { definition.isCaptured = true; scope.hasCapturedDefinitions = true; this.hasCapturingUses = true; } break; } // Variables are captured if a lambda is in the scope chain if (scope.kind == Skew.LambdaConversion.CaptureKind.LAMBDA) { isCaptured = true; } } }; Skew.LambdaConversion.Scope.prototype.createReferenceToScope = function(scope) { // Skip to the enclosing scope with an environment var target = this; while (target.environmentObject == null) { assert(!target.hasCapturedDefinitions && target.kind != Skew.LambdaConversion.CaptureKind.LAMBDA); target = target.parent; } // Reference this scope if (scope == target) { return Skew.Node.createSymbolReference(target.environmentVariable); } // Reference a parent scope var copy = in_IntMap.get1(target.copyLookup, scope.id); return Skew.Node.createMemberReference(Skew.Node.createSymbolReference(target.environmentVariable), copy.member); }; Skew.LambdaConversion.Converter = function(_global, _cache) { this._global = _global; this._cache = _cache; this._scopes = []; this._stack = []; this._interfaces = new Map(); this._calls = []; this._skewNamespace = null; this._enclosingFunction = null; }; Skew.LambdaConversion.Converter.prototype.run = function() { this._visitObject(this._global); this._convertCalls(); this._convertLambdas(); }; Skew.LambdaConversion.Converter.prototype._convertCalls = function() { var swap = new Skew.Node(Skew.NodeKind.NULL); for (var i = 0, list = this._calls, count = list.length; i < count; i = i + 1 | 0) { var node = in_List.get(list, i); var value = node.callValue(); var resolvedType = value.resolvedType; if (resolvedType.kind == Skew.TypeKind.LAMBDA) { var interfaceType = this._interfaceTypeForLambdaType(resolvedType); var interfaceRun = in_List.first(interfaceType.symbol.asObjectSymbol().functions); assert(interfaceRun.name == 'run'); value.replaceWith(swap); swap.replaceWith(Skew.Node.createMemberReference(value, interfaceRun)); } } }; Skew.LambdaConversion.Converter.prototype._convertLambdas = function() { // Propagate required environment copies up the scope chain for (var i1 = 0, list1 = this._scopes, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var scope = in_List.get(list1, i1); if (scope.hasCapturingUses) { for (var i = 0, list = scope.uses, count = list.length; i < count; i = i + 1 | 0) { var use = in_List.get(list, i); if (use.definition.isCaptured) { var definingScope = use.definition.scope; for (var s = scope; s != definingScope; s = s.parent) { if (!s.copyLookup.has(definingScope.id)) { var copy = new Skew.LambdaConversion.Copy(definingScope); s.copies.push(copy); in_IntMap.set(s.copyLookup, definingScope.id, copy); } } } } } } for (var i5 = 0, list5 = this._scopes, count5 = list5.length; i5 < count5; i5 = i5 + 1 | 0) { var scope1 = in_List.get(list5, i5); if (scope1.hasCapturedDefinitions || scope1.kind == Skew.LambdaConversion.CaptureKind.LAMBDA) { // Create an object to store the environment var object = this._createObject(Skew.SymbolKind.OBJECT_CLASS, Skew.LambdaConversion.Converter._generateEnvironmentName(scope1), this._global); var $constructor = Skew.LambdaConversion.Converter._createConstructor(object); var constructorCall = Skew.Node.createCall(Skew.Node.createMemberReference(Skew.Node.createSymbolReference(object), $constructor)).withType(object.resolvedType); // The environment must store all captured variables for (var i2 = 0, list2 = scope1.definitions, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var definition = in_List.get(list2, i2); if (definition.isCaptured) { definition.member = Skew.LambdaConversion.Converter._createInstanceVariable(object.scope.generateName(definition.symbol.name), definition.symbol.resolvedType, object); } } // Insert the constructor call declaration switch (scope1.kind) { case Skew.LambdaConversion.CaptureKind.FUNCTION: { // Store the environment instance in a variable var variable = Skew.LambdaConversion.Converter._createVariable(Skew.SymbolKind.VARIABLE_LOCAL, scope1.enclosingFunction.scope.generateName('env'), object.resolvedType); variable.value = constructorCall; scope1.environmentVariable = variable; // Define the variable at the top of the function body var variables = new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(Skew.Node.createVariable(variable)); // TODO: Insert this after the call to "super" scope1.node.prependChild(variables); // Assign captured arguments and "self" to the environment // TODO: Remove the extra indirection to "self", copy it directly into environments instead var previous = variables; for (var i3 = 0, list3 = scope1.definitions, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var definition1 = in_List.get(list3, i3); if (definition1.isCaptured && (definition1.symbol.kind == Skew.SymbolKind.VARIABLE_ARGUMENT || definition1.symbol == scope1.enclosingFunction.$this)) { var assignment = Skew.LambdaConversion.Converter._createAssignment(variable, definition1.member, definition1.symbol); scope1.node.insertChildAfter(previous, assignment); previous = assignment; } } break; } case Skew.LambdaConversion.CaptureKind.LAMBDA: { var $function = scope1.node.symbol.asFunctionSymbol(); $function.kind = Skew.SymbolKind.FUNCTION_INSTANCE; $function.name = 'run'; $function.$this = Skew.LambdaConversion.Converter._createVariable(Skew.SymbolKind.VARIABLE_LOCAL, 'self', object.resolvedType); $function.parent = object; object.functions.push($function); scope1.node.become(constructorCall); scope1.environmentVariable = $function.$this; constructorCall = scope1.node; // Lambdas introduce two scopes instead of one. All captured // definitions for that lambda have to be in the nested scope, // even lambda function arguments, because that nested scope // needs to be different for each invocation of the lambda. assert(!scope1.hasCapturedDefinitions); // Implement the lambda interface with the right type parameters var interfaceType = this._interfaceTypeForLambdaType($function.resolvedType); var interfaceFunction = in_List.first(interfaceType.symbol.asObjectSymbol().functions); assert(interfaceFunction.name == 'run'); object.$implements = [new Skew.Node(Skew.NodeKind.TYPE).withType(interfaceType)]; object.interfaceTypes = [interfaceType]; if (interfaceFunction.implementations == null) { interfaceFunction.implementations = []; } interfaceFunction.implementations.push($function); break; } case Skew.LambdaConversion.CaptureKind.LOOP: { // Store the environment instance in a variable var variable1 = Skew.LambdaConversion.Converter._createVariable(Skew.SymbolKind.VARIABLE_LOCAL, scope1.enclosingFunction.scope.generateName('env'), object.resolvedType); variable1.value = constructorCall; scope1.environmentVariable = variable1; // Define the variable at the top of the function body var variables1 = new Skew.Node(Skew.NodeKind.VARIABLES).appendChild(Skew.Node.createVariable(variable1)); var node = scope1.node; var block = node.kind == Skew.NodeKind.FOR ? node.forBlock() : node.kind == Skew.NodeKind.FOREACH ? node.foreachBlock() : node.kind == Skew.NodeKind.WHILE ? node.whileBlock() : null; block.prependChild(variables1); // Assign captured loop variables var previous1 = variables1; for (var i4 = 0, list4 = scope1.definitions, count4 = list4.length; i4 < count4; i4 = i4 + 1 | 0) { var definition2 = in_List.get(list4, i4); if (definition2.isCaptured && definition2.symbol.isLoopVariable()) { var assignment1 = Skew.LambdaConversion.Converter._createAssignment(variable1, definition2.member, definition2.symbol); block.insertChildAfter(previous1, assignment1); previous1 = assignment1; } } break; } default: { assert(false); break; } } // These will be referenced later scope1.environmentObject = object; scope1.environmentConstructor = $constructor; scope1.environmentConstructorCall = constructorCall; } // Mutate the parent scope pointer to skip past irrelevant scopes // (those without environments). This means everything necessary to // access captured symbols can be found on the environment associated // with the parent scope without needing to look at grandparent scopes. // // All parent scopes that need environments should already have them // because scopes are iterated over using a pre-order traversal. while (scope1.parent != null && scope1.parent.environmentObject == null) { assert(!scope1.parent.hasCapturedDefinitions && scope1.parent.kind != Skew.LambdaConversion.CaptureKind.LAMBDA); scope1.parent = scope1.parent.parent; } } // Make sure each environment has a copy of each parent environment that it or its children needs for (var i7 = 0, list7 = this._scopes, count7 = list7.length; i7 < count7; i7 = i7 + 1 | 0) { var scope2 = in_List.get(list7, i7); var object1 = scope2.environmentObject; var constructor1 = scope2.environmentConstructor; var constructorCall1 = scope2.environmentConstructorCall; if (object1 != null) { for (var i6 = 0, list6 = scope2.copies, count6 = list6.length; i6 < count6; i6 = i6 + 1 | 0) { var copy1 = in_List.get(list6, i6); var name = object1.scope.generateName(copy1.scope.kind == Skew.LambdaConversion.CaptureKind.LAMBDA ? 'lambda' : 'env'); var member = Skew.LambdaConversion.Converter._createInstanceVariable(name, copy1.scope.environmentObject.resolvedType, object1); var argument = Skew.LambdaConversion.Converter._createVariable(Skew.SymbolKind.VARIABLE_ARGUMENT, name, member.resolvedType); copy1.member = member; constructor1.$arguments.push(argument); constructor1.resolvedType.argumentTypes.push(argument.resolvedType); constructor1.block.appendChild(Skew.LambdaConversion.Converter._createAssignment(constructor1.$this, member, argument)); constructorCall1.appendChild(scope2.parent.createReferenceToScope(copy1.scope)); } } } for (var i10 = 0, list10 = this._scopes, count10 = list10.length; i10 < count10; i10 = i10 + 1 | 0) { var scope3 = in_List.get(list10, i10); // Replace variable definitions of captured symbols with assignments to their environment if (scope3.hasCapturedDefinitions) { for (var i8 = 0, list8 = scope3.definitions, count8 = list8.length; i8 < count8; i8 = i8 + 1 | 0) { var definition3 = in_List.get(list8, i8); if (definition3.isCaptured && definition3.node != null) { assert(definition3.node.kind == Skew.NodeKind.VARIABLE); assert(definition3.node.parent().kind == Skew.NodeKind.VARIABLES); definition3.node.extractVariableFromVariables(); definition3.node.parent().replaceWith(Skew.Node.createExpression(Skew.Node.createBinary(Skew.NodeKind.ASSIGN, Skew.Node.createMemberReference(Skew.Node.createSymbolReference(scope3.environmentVariable), definition3.member), definition3.symbol.value.remove()).withType(definition3.member.resolvedType))); } } } // Replace all references to captured variables with a member access from the appropriate environment for (var i9 = 0, list9 = scope3.uses, count9 = list9.length; i9 < count9; i9 = i9 + 1 | 0) { var use1 = in_List.get(list9, i9); if (use1.definition.isCaptured) { use1.node.become(Skew.Node.createMemberReference(scope3.createReferenceToScope(use1.definition.scope), use1.definition.member)); } } } }; Skew.LambdaConversion.Converter.prototype._visitObject = function(symbol) { for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._visitObject(object); } for (var i1 = 0, list1 = symbol.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); this._visitFunction($function); } for (var i2 = 0, list2 = symbol.variables, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); this._visitVariable(variable); } }; Skew.LambdaConversion.Converter.prototype._visitFunction = function(symbol) { if (symbol.block != null) { this._enclosingFunction = symbol; var scope = this._pushScope(Skew.LambdaConversion.CaptureKind.FUNCTION, symbol.block, null); if (symbol.$this != null) { scope.recordDefinition(symbol.$this, null); } for (var i = 0, list = symbol.$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); scope.recordDefinition(argument, null); } this._visit(symbol.block); in_List.removeLast(this._stack); this._enclosingFunction = null; } }; Skew.LambdaConversion.Converter.prototype._visitVariable = function(symbol) { if (symbol.value != null) { this._visit(symbol.value); } }; Skew.LambdaConversion.Converter.prototype._visit = function(node) { var kind = node.kind; var symbol = node.symbol; var oldEnclosingFunction = this._enclosingFunction; if (kind == Skew.NodeKind.LAMBDA) { this._enclosingFunction = symbol.asFunctionSymbol(); var lambdaScope = this._pushScope(Skew.LambdaConversion.CaptureKind.LAMBDA, node, this._stack.length == 0 ? null : in_List.last(this._stack)); var scope = this._pushScope(Skew.LambdaConversion.CaptureKind.FUNCTION, node.lambdaBlock(), lambdaScope); for (var i = 0, list = symbol.asFunctionSymbol().$arguments, count = list.length; i < count; i = i + 1 | 0) { var argument = in_List.get(list, i); scope.recordDefinition(argument, null); } } else if (kind == Skew.NodeKind.FOREACH) { // Visit loop header this._visit(node.foreachValue()); // Visit loop body var scope1 = this._pushScope(Skew.LambdaConversion.CaptureKind.LOOP, node, in_List.last(this._stack)); scope1.recordDefinition(symbol.asVariableSymbol(), null); this._visit(node.foreachBlock()); in_List.removeLast(this._stack); return; } else if (kind == Skew.NodeKind.FOR || kind == Skew.NodeKind.WHILE) { this._pushScope(Skew.LambdaConversion.CaptureKind.LOOP, node, in_List.last(this._stack)); } else if (kind == Skew.NodeKind.VARIABLE) { in_List.last(this._stack).recordDefinition(symbol.asVariableSymbol(), node); } else if (kind == Skew.NodeKind.CATCH) { // TODO } else if (kind == Skew.NodeKind.CALL) { this._calls.push(node); } else if (kind == Skew.NodeKind.NAME && symbol != null && (symbol.kind == Skew.SymbolKind.VARIABLE_ARGUMENT || symbol.kind == Skew.SymbolKind.VARIABLE_LOCAL)) { in_List.last(this._stack).recordUse(symbol.asVariableSymbol(), node); } for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._visit(child); } if (kind == Skew.NodeKind.LAMBDA) { in_List.removeLast(this._stack); in_List.removeLast(this._stack); this._enclosingFunction = oldEnclosingFunction; } else if (Skew.in_NodeKind.isLoop(kind)) { in_List.removeLast(this._stack); } }; Skew.LambdaConversion.Converter.prototype._pushScope = function(kind, node, parent) { var scope = new Skew.LambdaConversion.Scope(kind, node, this._enclosingFunction, parent); this._scopes.push(scope); this._stack.push(scope); return scope; }; Skew.LambdaConversion.Converter.prototype._createObject = function(kind, name, parent) { var object = new Skew.ObjectSymbol(kind, parent.scope.generateName(name)); object.scope = new Skew.ObjectScope(parent.scope, object); object.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, object); object.state = Skew.SymbolState.INITIALIZED; object.parent = parent; parent.objects.push(object); return object; }; Skew.LambdaConversion.Converter.prototype._ensureSkewNamespaceExists = function() { if (this._skewNamespace == null) { var symbol = this._global.scope.find('Skew', Skew.ScopeSearch.NORMAL); // Did the user's code define the namespace? if (symbol != null && Skew.in_SymbolKind.isObject(symbol.kind)) { this._skewNamespace = symbol.asObjectSymbol(); } // It's missing or there's a conflict, define one ourselves else { this._skewNamespace = this._createObject(Skew.SymbolKind.OBJECT_NAMESPACE, 'Skew', this._global); this._skewNamespace.flags |= Skew.SymbolFlags.IS_IMPORTED; } } }; Skew.LambdaConversion.Converter.prototype._createInterface = function(count, hasReturnType) { var key = count << 1 | hasReturnType; var object = in_IntMap.get(this._interfaces, key, null); if (object == null) { this._ensureSkewNamespaceExists(); object = this._createObject(Skew.SymbolKind.OBJECT_INTERFACE, (hasReturnType ? 'Fn' : 'FnVoid') + count.toString(), this._skewNamespace); object.flags |= Skew.SymbolFlags.IS_IMPORTED; in_IntMap.set(this._interfaces, key, object); var $function = Skew.LambdaConversion.Converter._createFunction(object, Skew.SymbolKind.FUNCTION_INSTANCE, 'run', Skew.LambdaConversion.Converter.Body.ABSTRACT); $function.flags |= Skew.SymbolFlags.IS_IMPORTED; $function.resolvedType.argumentTypes = []; if (hasReturnType) { var returnType = Skew.LambdaConversion.Converter._createParameter(object, 'R').resolvedType; $function.resolvedType.returnType = returnType; $function.returnType = new Skew.Node(Skew.NodeKind.TYPE).withType(returnType); } for (var i = 0, count1 = count; i < count1; i = i + 1 | 0) { var parameter = Skew.LambdaConversion.Converter._createParameter(object, 'A' + (i + 1 | 0).toString()); $function.$arguments.push(Skew.LambdaConversion.Converter._createVariable(Skew.SymbolKind.VARIABLE_ARGUMENT, 'a' + (i + 1 | 0).toString(), parameter.resolvedType)); $function.resolvedType.argumentTypes.push(parameter.resolvedType); } } return object; }; Skew.LambdaConversion.Converter.prototype._interfaceTypeForLambdaType = function(lambdaType) { var $interface = this._createInterface(lambdaType.argumentTypes.length, lambdaType.returnType != null); var interfaceType = $interface.resolvedType; var substitutions = []; if (lambdaType.returnType != null) { substitutions.push(lambdaType.returnType); } in_List.append1(substitutions, lambdaType.argumentTypes); if (!(substitutions.length == 0)) { interfaceType = this._cache.substitute(interfaceType, this._cache.createEnvironment($interface.parameters, substitutions)); } return interfaceType; }; Skew.LambdaConversion.Converter._generateEnvironmentName = function(scope) { var name = ''; var root = scope; while (root.parent != null) { root = root.parent; } for (var symbol = root.enclosingFunction; symbol != null && symbol.kind != Skew.SymbolKind.OBJECT_GLOBAL; symbol = symbol.parent) { if (symbol.kind != Skew.SymbolKind.OBJECT_GLOBAL && !Skew.Renaming.isInvalidIdentifier(symbol.name)) { name = Skew.withUppercaseFirstLetter(symbol.name) + name; } } name += scope.kind == Skew.LambdaConversion.CaptureKind.LAMBDA ? 'Lambda' : 'Env'; return name; }; Skew.LambdaConversion.Converter._createConstructor = function(object) { var $function = Skew.LambdaConversion.Converter._createFunction(object, Skew.SymbolKind.FUNCTION_CONSTRUCTOR, 'new', Skew.LambdaConversion.Converter.Body.IMPLEMENTED); $function.resolvedType.returnType = object.resolvedType; $function.returnType = new Skew.Node(Skew.NodeKind.TYPE).withType(object.resolvedType); return $function; }; Skew.LambdaConversion.Converter._createFunction = function(object, kind, name, body) { var $function = new Skew.FunctionSymbol(kind, name); $function.scope = new Skew.FunctionScope(object.scope, $function); $function.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, $function); $function.resolvedType.argumentTypes = []; $function.state = Skew.SymbolState.INITIALIZED; $function.parent = object; if (body == Skew.LambdaConversion.Converter.Body.IMPLEMENTED) { $function.block = new Skew.Node(Skew.NodeKind.BLOCK); $function.$this = Skew.LambdaConversion.Converter._createVariable(Skew.SymbolKind.VARIABLE_LOCAL, 'self', object.resolvedType); } object.functions.push($function); return $function; }; Skew.LambdaConversion.Converter._createInstanceVariable = function(name, type, object) { var variable = Skew.LambdaConversion.Converter._createVariable(Skew.SymbolKind.VARIABLE_INSTANCE, name, type); variable.parent = object; object.variables.push(variable); return variable; }; Skew.LambdaConversion.Converter._createVariable = function(kind, name, type) { var variable = new Skew.VariableSymbol(kind, name); variable.initializeWithType(type); return variable; }; Skew.LambdaConversion.Converter._createParameter = function(parent, name) { var parameter = new Skew.ParameterSymbol(Skew.SymbolKind.PARAMETER_OBJECT, name); parameter.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, parameter); parameter.state = Skew.SymbolState.INITIALIZED; if (parent.parameters == null) { parent.parameters = []; } parent.parameters.push(parameter); return parameter; }; Skew.LambdaConversion.Converter._createAssignment = function(object, member, variable) { return Skew.Node.createExpression(Skew.Node.createBinary(Skew.NodeKind.ASSIGN, Skew.Node.createMemberReference(Skew.Node.createSymbolReference(object), member), Skew.Node.createSymbolReference(variable)).withType(member.resolvedType)); }; Skew.LambdaConversion.Converter.Body = { ABSTRACT: 0, IMPLEMENTED: 1 }; Skew.CompilerTarget = function() { }; Skew.CompilerTarget.prototype.stopAfterResolve = function() { return true; }; Skew.CompilerTarget.prototype.requiresIntegerSwitchStatements = function() { return false; }; Skew.CompilerTarget.prototype.supportsListForeach = function() { return false; }; Skew.CompilerTarget.prototype.supportsNestedTypes = function() { return false; }; Skew.CompilerTarget.prototype.needsLambdaLifting = function() { return false; }; Skew.CompilerTarget.prototype.removeSingletonInterfaces = function() { return false; }; Skew.CompilerTarget.prototype.stringEncoding = function() { return Unicode.Encoding.UTF32; }; Skew.CompilerTarget.prototype.editOptions = function(options) { }; Skew.CompilerTarget.prototype.includeSources = function(sources) { }; Skew.CompilerTarget.prototype.createEmitter = function(context) { return null; }; Skew.JavaScriptTarget = function() { Skew.CompilerTarget.call(this); }; __extends(Skew.JavaScriptTarget, Skew.CompilerTarget); Skew.JavaScriptTarget.prototype.stopAfterResolve = function() { return false; }; Skew.JavaScriptTarget.prototype.supportsNestedTypes = function() { return true; }; Skew.JavaScriptTarget.prototype.removeSingletonInterfaces = function() { return true; }; Skew.JavaScriptTarget.prototype.stringEncoding = function() { return Unicode.Encoding.UTF16; }; Skew.JavaScriptTarget.prototype.editOptions = function(options) { options.define('TARGET', 'JAVASCRIPT'); }; Skew.JavaScriptTarget.prototype.includeSources = function(sources) { sources.unshift(new Skew.Source('', Skew.NATIVE_LIBRARY_JS)); }; Skew.JavaScriptTarget.prototype.createEmitter = function(context) { return new Skew.JavaScriptEmitter(context, context.options, context.cache); }; Skew.CPlusPlusTarget = function() { Skew.CompilerTarget.call(this); }; __extends(Skew.CPlusPlusTarget, Skew.CompilerTarget); Skew.CPlusPlusTarget.prototype.stopAfterResolve = function() { return false; }; Skew.CPlusPlusTarget.prototype.requiresIntegerSwitchStatements = function() { return true; }; Skew.CPlusPlusTarget.prototype.supportsListForeach = function() { return true; }; Skew.CPlusPlusTarget.prototype.needsLambdaLifting = function() { return true; }; Skew.CPlusPlusTarget.prototype.stringEncoding = function() { return Unicode.Encoding.UTF8; }; Skew.CPlusPlusTarget.prototype.editOptions = function(options) { options.define('TARGET', 'CPLUSPLUS'); }; Skew.CPlusPlusTarget.prototype.includeSources = function(sources) { sources.unshift(new Skew.Source('', Skew.NATIVE_LIBRARY_CPP)); }; Skew.CPlusPlusTarget.prototype.createEmitter = function(context) { return new Skew.CPlusPlusEmitter(context.options, context.cache); }; Skew.CSharpTarget = function() { Skew.CompilerTarget.call(this); }; __extends(Skew.CSharpTarget, Skew.CompilerTarget); Skew.CSharpTarget.prototype.stopAfterResolve = function() { return false; }; Skew.CSharpTarget.prototype.requiresIntegerSwitchStatements = function() { return true; }; Skew.CSharpTarget.prototype.supportsListForeach = function() { return true; }; Skew.CSharpTarget.prototype.supportsNestedTypes = function() { return true; }; Skew.CSharpTarget.prototype.stringEncoding = function() { return Unicode.Encoding.UTF16; }; Skew.CSharpTarget.prototype.editOptions = function(options) { options.define('TARGET', 'CSHARP'); }; Skew.CSharpTarget.prototype.includeSources = function(sources) { sources.unshift(new Skew.Source('', Skew.NATIVE_LIBRARY_CS)); }; Skew.CSharpTarget.prototype.createEmitter = function(context) { return new Skew.CSharpEmitter(context.options, context.cache); }; Skew.LispTreeTarget = function() { Skew.CompilerTarget.call(this); }; __extends(Skew.LispTreeTarget, Skew.CompilerTarget); Skew.LispTreeTarget.prototype.createEmitter = function(context) { return new Skew.LispTreeEmitter(context.options); }; Skew.TypeScriptTarget = function() { Skew.CompilerTarget.call(this); }; __extends(Skew.TypeScriptTarget, Skew.CompilerTarget); Skew.TypeScriptTarget.prototype.stopAfterResolve = function() { return false; }; Skew.TypeScriptTarget.prototype.requiresIntegerSwitchStatements = function() { return true; }; Skew.TypeScriptTarget.prototype.supportsListForeach = function() { return true; }; Skew.TypeScriptTarget.prototype.supportsNestedTypes = function() { return true; }; Skew.TypeScriptTarget.prototype.removeSingletonInterfaces = function() { return true; }; Skew.TypeScriptTarget.prototype.stringEncoding = function() { return Unicode.Encoding.UTF16; }; Skew.TypeScriptTarget.prototype.editOptions = function(options) { options.define('TARGET', 'JAVASCRIPT'); }; Skew.TypeScriptTarget.prototype.includeSources = function(sources) { sources.unshift(new Skew.Source('', Skew.NATIVE_LIBRARY_JS)); }; Skew.TypeScriptTarget.prototype.createEmitter = function(context) { return new Skew.TypeScriptEmitter(context.log, context.options, context.cache); }; Skew.Define = function(name, value) { this.name = name; this.value = value; }; Skew.CompilerOptions = function() { var self = this; self.completionContext = null; self.defines = new Map(); self.foldAllConstants = false; self.globalizeAllFunctions = false; self.inlineAllFunctions = false; self.isAlwaysInlinePresent = false; self.jsMangle = false; self.jsMinify = false; self.jsSourceMap = false; self.outputDirectory = null; self.outputFile = null; self.passes = null; self.stopAfterResolve = false; self.target = new Skew.CompilerTarget(); self.verbose = false; self.warnAboutIgnoredComments = false; self.warningsAreErrors = false; self.passes = [ new Skew.LexingPass(), new Skew.ParsingPass(), new Skew.MergingPass(), new Skew.ResolvingPass(), new Skew.LambdaConversionPass().onlyRunWhen(function() { return self._continueAfterResolve() && self.target.needsLambdaLifting(); }), new Skew.InterfaceRemovalPass().onlyRunWhen(function() { return self._continueAfterResolve() && self.target.removeSingletonInterfaces() && self.globalizeAllFunctions; }), // The call graph is used as a shortcut so the tree only needs to be scanned once for all call-based optimizations new Skew.CallGraphPass().onlyRunWhen(function() { return self._continueAfterResolve(); }), new Skew.GlobalizingPass().onlyRunWhen(function() { return self._continueAfterResolve(); }), new Skew.MotionPass().onlyRunWhen(function() { return self._continueAfterResolve(); }), new Skew.RenamingPass().onlyRunWhen(function() { return self._continueAfterResolve(); }), new Skew.FoldingPass().onlyRunWhen(function() { return self._continueAfterResolve() && self.foldAllConstants; }), new Skew.InliningPass().onlyRunWhen(function() { return self._continueAfterResolve() && (self.inlineAllFunctions || self.isAlwaysInlinePresent); }), new Skew.FoldingPass().onlyRunWhen(function() { return self._continueAfterResolve() && (self.inlineAllFunctions || self.isAlwaysInlinePresent) && self.foldAllConstants; }), new Skew.EmittingPass().onlyRunWhen(function() { return !self.stopAfterResolve; }) ]; }; Skew.CompilerOptions.prototype.define = function(name, value) { var range = new Skew.Source('', '--define:' + name + '=' + value).entireRange(); in_StringMap.set(this.defines, name, new Skew.Define(range.slice(9, 9 + name.length | 0), range.fromEnd(value.length))); }; Skew.CompilerOptions.prototype._continueAfterResolve = function() { return !this.stopAfterResolve && !this.target.stopAfterResolve(); }; Skew.CompilerOptions.prototype.createTargetFromExtension = function() { if (this.outputFile != null) { var dot = this.outputFile.lastIndexOf('.'); if (dot != -1) { switch (in_string.slice1(this.outputFile, dot + 1 | 0)) { case 'cpp': case 'cxx': case 'cc': { this.target = new Skew.CPlusPlusTarget(); break; } case 'cs': { this.target = new Skew.CSharpTarget(); break; } case 'ts': { this.target = new Skew.TypeScriptTarget(); break; } case 'js': { this.target = new Skew.JavaScriptTarget(); break; } default: { return false; } } return true; } } return false; }; Skew.Timer = function() { this._isStarted = false; this._startTime = 0; this._totalSeconds = 0; }; Skew.Timer.prototype.start = function() { assert(!this._isStarted); this._isStarted = true; this._startTime = (typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now()) / 1000; }; Skew.Timer.prototype.stop = function() { assert(this._isStarted); this._isStarted = false; this._totalSeconds += (typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now()) / 1000 - this._startTime; }; Skew.Timer.prototype.elapsedSeconds = function() { return this._totalSeconds; }; Skew.Timer.prototype.elapsedMilliseconds = function() { return (Math.round(this._totalSeconds * 1000 * 10) / 10).toString() + 'ms'; }; Skew.PassContext = function(log, options, inputs) { this.log = log; this.options = options; this.inputs = inputs; this.cache = new Skew.TypeCache(); this.global = new Skew.ObjectSymbol(Skew.SymbolKind.OBJECT_GLOBAL, ''); this.callGraph = null; this.tokens = []; this.outputs = []; this.isResolvePassComplete = false; }; Skew.PassContext.prototype.verify = function() { this._verifyHierarchy1(this.global); }; Skew.PassContext.prototype._verifySymbol = function(symbol) { var ref; if (!this.isResolvePassComplete) { return; } // Special-case nested guards that aren't initialized when the outer guard has errors if (symbol.state != Skew.SymbolState.INITIALIZED) { assert(Skew.in_SymbolKind.isObject(symbol.kind)); assert(symbol.isGuardConditional()); assert(this.log.errorCount() > 0); return; } assert(symbol.state == Skew.SymbolState.INITIALIZED); assert(symbol.resolvedType != null); if (Skew.in_SymbolKind.isObject(symbol.kind) || Skew.in_SymbolKind.isFunction(symbol.kind) || Skew.in_SymbolKind.isParameter(symbol.kind)) { if (symbol.resolvedType == Skew.Type.DYNAMIC) { // Ignore errors due to cyclic declarations assert(this.log.errorCount() > 0); } else { assert(symbol.resolvedType.kind == Skew.TypeKind.SYMBOL); assert(symbol.resolvedType.symbol == symbol); } } if (Skew.in_SymbolKind.isFunction(symbol.kind) && symbol.resolvedType.kind == Skew.TypeKind.SYMBOL) { var $function = symbol.asFunctionSymbol(); assert(symbol.resolvedType.returnType == ((ref = $function.returnType) != null ? ref.resolvedType : null)); assert(symbol.resolvedType.argumentTypes.length == $function.$arguments.length); for (var i = 0, count = $function.$arguments.length; i < count; i = i + 1 | 0) { assert(in_List.get(symbol.resolvedType.argumentTypes, i) == in_List.get($function.$arguments, i).resolvedType); } } if (Skew.in_SymbolKind.isVariable(symbol.kind)) { assert(symbol.resolvedType == symbol.asVariableSymbol().type.resolvedType); } }; Skew.PassContext.prototype._verifyHierarchy1 = function(symbol) { this._verifySymbol(symbol); for (var i1 = 0, list1 = symbol.objects, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var object = in_List.get(list1, i1); assert(object.parent == symbol); this._verifyHierarchy1(object); if (object.$extends != null) { this._verifyHierarchy2(object.$extends, null); } if (object.$implements != null) { for (var i = 0, list = object.$implements, count = list.length; i < count; i = i + 1 | 0) { var node = in_List.get(list, i); this._verifyHierarchy2(node, null); } } } for (var i2 = 0, list2 = symbol.functions, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var $function = in_List.get(list2, i2); assert($function.parent == symbol); this._verifySymbol($function); if ($function.block != null) { this._verifyHierarchy2($function.block, null); } } for (var i3 = 0, list3 = symbol.variables, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var variable = in_List.get(list3, i3); assert(variable.parent == symbol); this._verifySymbol(variable); assert(variable.state != Skew.SymbolState.INITIALIZED || variable.type != null); if (variable.type != null) { this._verifyHierarchy2(variable.type, null); } if (variable.value != null) { this._verifyHierarchy2(variable.value, null); } } if (symbol.guards != null) { for (var i4 = 0, list4 = symbol.guards, count4 = list4.length; i4 < count4; i4 = i4 + 1 | 0) { var guard = in_List.get(list4, i4); this._verifyHierarchy3(guard, symbol); } } }; Skew.PassContext.prototype._verifyHierarchy2 = function(node, parent) { assert(node.parent() == parent); // All expressions must have a type after the type resolution pass if (this.isResolvePassComplete && Skew.in_NodeKind.isExpression(node.kind)) { assert(node.resolvedType != null); } if (node.kind == Skew.NodeKind.VARIABLE) { assert(node.symbol != null); assert(node.symbol.kind == Skew.SymbolKind.VARIABLE_LOCAL); var variable = node.symbol.asVariableSymbol(); assert(variable.value == node.variableValue()); this._verifySymbol(variable); assert(variable.state != Skew.SymbolState.INITIALIZED || variable.type != null); if (variable.type != null) { this._verifyHierarchy2(variable.type, null); } } else if (node.kind == Skew.NodeKind.LAMBDA) { assert(node.symbol != null); assert(node.symbol.kind == Skew.SymbolKind.FUNCTION_LOCAL); assert(node.symbol.asFunctionSymbol().block == node.lambdaBlock()); this._verifySymbol(node.symbol); } for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._verifyHierarchy2(child, node); } }; Skew.PassContext.prototype._verifyHierarchy3 = function(guard, parent) { assert(guard.parent == parent); assert(guard.contents.parent == parent); if (guard.test != null) { this._verifyHierarchy2(guard.test, null); } this._verifyHierarchy1(guard.contents); if (guard.elseGuard != null) { this._verifyHierarchy3(guard.elseGuard, parent); } }; Skew.Pass = function() { this._shouldRun = null; }; Skew.Pass.prototype.shouldRun = function() { return this._shouldRun != null ? this._shouldRun() : true; }; Skew.Pass.prototype.onlyRunWhen = function(callback) { this._shouldRun = callback; return this; }; Skew.LambdaConversionPass = function() { Skew.Pass.call(this); }; __extends(Skew.LambdaConversionPass, Skew.Pass); Skew.LambdaConversionPass.prototype.kind = function() { return Skew.PassKind.LAMBDA_CONVERSION; }; Skew.LambdaConversionPass.prototype.run = function(context) { new Skew.LambdaConversion.Converter(context.global, context.cache).run(); }; Skew.ResolvingPass = function() { Skew.Pass.call(this); }; __extends(Skew.ResolvingPass, Skew.Pass); Skew.ResolvingPass.prototype.kind = function() { return Skew.PassKind.RESOLVING; }; Skew.ResolvingPass.prototype.run = function(context) { context.cache.loadGlobals(context.log, context.global); new Skew.Resolving.Resolver(context.global, context.options, new Map(context.options.defines), context.cache, context.log).resolve(); // The tree isn't fully resolved for speed reasons if code completion is requested if (context.options.completionContext == null) { context.isResolvePassComplete = true; } }; Skew.ParsingPass = function() { Skew.Pass.call(this); }; __extends(Skew.ParsingPass, Skew.Pass); Skew.ParsingPass.prototype.kind = function() { return Skew.PassKind.PARSING; }; Skew.ParsingPass.prototype.run = function(context) { for (var i = 0, list = context.tokens, count = list.length; i < count; i = i + 1 | 0) { var tokens = in_List.get(list, i); Skew.Parsing.parseFile(context.log, tokens, context.global, context.options.warnAboutIgnoredComments); } }; Skew.EmittingPass = function() { Skew.Pass.call(this); }; __extends(Skew.EmittingPass, Skew.Pass); Skew.EmittingPass.prototype.kind = function() { return Skew.PassKind.EMITTING; }; Skew.EmittingPass.prototype.run = function(context) { var emitter = context.options.target.createEmitter(context); if (emitter != null) { emitter.visit(context.global); context.outputs = emitter.sources(); } }; Skew.LexingPass = function() { Skew.Pass.call(this); }; __extends(Skew.LexingPass, Skew.Pass); Skew.LexingPass.prototype.kind = function() { return Skew.PassKind.LEXING; }; Skew.LexingPass.prototype.run = function(context) { for (var i = 0, list = context.inputs, count = list.length; i < count; i = i + 1 | 0) { var source = in_List.get(list, i); context.tokens.push(Skew.tokenize(context.log, source)); } }; Skew.PassTimer = function(kind) { this.kind = kind; this.timer = new Skew.Timer(); }; Skew.StatisticsKind = { SHORT: 0, LONG: 1 }; Skew.CompilerResult = function(cache, global, outputs, passTimers, totalTimer) { this.cache = cache; this.global = global; this.outputs = outputs; this.passTimers = passTimers; this.totalTimer = totalTimer; }; Skew.CompilerResult.prototype.statistics = function(inputs, kind) { var builder = new StringBuilder(); var totalTime = this.totalTimer.elapsedSeconds(); var sourceStatistics = function(name, sources) { var totalBytes = 0; var totalLines = 0; for (var i = 0, list = sources, count = list.length; i < count; i = i + 1 | 0) { var source = in_List.get(list, i); totalBytes = totalBytes + source.contents.length | 0; if (kind == Skew.StatisticsKind.LONG) { totalLines = totalLines + source.lineCount() | 0; } } builder.buffer += name + (sources.length == 1 ? '' : 's') + ': '; builder.buffer += sources.length == 1 ? in_List.first(sources).name : sources.length.toString() + ' files'; builder.buffer += ' (' + Skew.bytesToString(totalBytes); builder.buffer += ', ' + Skew.bytesToString(Math.round(totalBytes / totalTime) | 0) + '/s'; if (kind == Skew.StatisticsKind.LONG) { builder.buffer += ', ' + Skew.PrettyPrint.plural1(totalLines, 'line'); builder.buffer += ', ' + Skew.PrettyPrint.plural1(Math.round(totalLines / totalTime) | 0, 'line') + '/s'; } builder.buffer += ')\n'; }; // Sources sourceStatistics('input', inputs); sourceStatistics('output', this.outputs); // Compilation time builder.buffer += 'time: ' + this.totalTimer.elapsedMilliseconds(); if (kind == Skew.StatisticsKind.LONG) { for (var i = 0, list = this.passTimers, count = list.length; i < count; i = i + 1 | 0) { var passTimer = in_List.get(list, i); builder.buffer += '\n ' + in_List.get(Skew.in_PassKind._strings, passTimer.kind) + ': ' + passTimer.timer.elapsedMilliseconds(); } } return builder.buffer; }; Skew.CallGraphPass = function() { Skew.Pass.call(this); }; __extends(Skew.CallGraphPass, Skew.Pass); Skew.CallGraphPass.prototype.kind = function() { return Skew.PassKind.CALL_GRAPH; }; Skew.CallGraphPass.prototype.run = function(context) { context.callGraph = new Skew.CallGraph(context.global); }; Skew.CallSite = function(callNode, enclosingSymbol) { this.callNode = callNode; this.enclosingSymbol = enclosingSymbol; }; Skew.CallInfo = function(symbol) { this.symbol = symbol; this.callSites = []; }; Skew.CallGraph = function(global) { this.callInfo = []; this.symbolToInfoIndex = new Map(); this._visitObject(global); }; Skew.CallGraph.prototype.callInfoForSymbol = function(symbol) { assert(this.symbolToInfoIndex.has(symbol.id)); return in_List.get(this.callInfo, in_IntMap.get1(this.symbolToInfoIndex, symbol.id)); }; Skew.CallGraph.prototype._visitObject = function(symbol) { for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._visitObject(object); } for (var i1 = 0, list1 = symbol.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); this._recordCallSite($function, null, null); this._visitNode($function.block, $function); } for (var i2 = 0, list2 = symbol.variables, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); this._visitNode(variable.value, variable); } }; Skew.CallGraph.prototype._visitNode = function(node, context) { if (node != null) { for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this._visitNode(child, context); } if (node.kind == Skew.NodeKind.CALL && node.symbol != null) { assert(Skew.in_SymbolKind.isFunction(node.symbol.kind)); this._recordCallSite(node.symbol.forwarded().asFunctionSymbol(), node, context); } } }; Skew.CallGraph.prototype._recordCallSite = function(symbol, node, context) { var index = in_IntMap.get(this.symbolToInfoIndex, symbol.id, -1); var info = index < 0 ? new Skew.CallInfo(symbol) : in_List.get(this.callInfo, index); if (index < 0) { in_IntMap.set(this.symbolToInfoIndex, symbol.id, this.callInfo.length); this.callInfo.push(info); } if (node != null) { info.callSites.push(new Skew.CallSite(node, context)); } }; Skew.InliningPass = function() { Skew.Pass.call(this); }; __extends(Skew.InliningPass, Skew.Pass); Skew.InliningPass.prototype.kind = function() { return Skew.PassKind.INLINING; }; Skew.InliningPass.prototype.run = function(context) { var graph = new Skew.Inlining.InliningGraph(context.callGraph, context.log, context.options.inlineAllFunctions); for (var i = 0, list = graph.inliningInfo, count = list.length; i < count; i = i + 1 | 0) { var info = in_List.get(list, i); Skew.Inlining.inlineSymbol(graph, info); } }; Skew.Inlining = {}; Skew.Inlining.inlineSymbol = function(graph, info) { var ref; if (!info.shouldInline) { return; } // Inlining nested functions first is more efficient because it results in // fewer inlining operations. This won't enter an infinite loop because // inlining for all such functions has already been disabled. for (var i1 = 0, list = info.bodyCalls, count = list.length; i1 < count; i1 = i1 + 1 | 0) { var bodyCall = in_List.get(list, i1); Skew.Inlining.inlineSymbol(graph, bodyCall); } var spreadingAnnotations = info.symbol.spreadingAnnotations(); for (var i = 0, count2 = info.callSites.length; i < count2; i = i + 1 | 0) { var callSite = in_List.get(info.callSites, i); // Some calls may be reused for other node types during constant folding if (callSite == null || callSite.callNode.kind != Skew.NodeKind.CALL) { continue; } // Make sure the call site hasn't been tampered with. An example of where // this can happen is constant folding "false ? 0 : foo.foo" to "foo.foo". // The children of "foo.foo" are stolen and parented under the hook // expression as part of a become() call. Skipping inlining in this case // just means we lose out on those inlining opportunities. This isn't the // end of the world and is a pretty rare occurrence. var node = callSite.callNode; if (node.childCount() != (info.symbol.$arguments.length + 1 | 0)) { continue; } // Propagate spreading annotations that must be preserved through inlining if (spreadingAnnotations != null) { var annotations = callSite.enclosingSymbol.annotations; if (annotations == null) { annotations = []; callSite.enclosingSymbol.annotations = annotations; } for (var i2 = 0, list1 = spreadingAnnotations, count1 = list1.length; i2 < count1; i2 = i2 + 1 | 0) { var annotation = in_List.get(list1, i2); in_List.appendOne(annotations, annotation); } } // Make sure each call site is inlined once by setting the call site to // null. The call site isn't removed from the list since we don't want // to mess up the indices of another call to inlineSymbol further up // the call stack. in_List.set(info.callSites, i, null); // If there are unused arguments, drop those expressions entirely if // they don't have side effects: // // def bar(a int, b int) int { // return a // } // // def test int { // return bar(0, foo(0)) + bar(1, 2) // } // // This should compile to: // // def test int { // return bar(0, foo(0)) + 2 // } // if (!(info.unusedArguments.length == 0)) { var hasSideEffects = false; for (var child = node.callValue().nextSibling(); child != null; child = child.nextSibling()) { if (!child.hasNoSideEffects()) { hasSideEffects = true; break; } } if (hasSideEffects) { continue; } } (ref = info.symbol).inlinedCount = ref.inlinedCount + 1 | 0; var clone = info.inlineValue.clone(); var value = node.firstChild().remove(); var values = []; while (node.hasChildren()) { assert(node.firstChild().resolvedType != null); values.push(node.firstChild().remove()); } // Make sure not to update the type if the function dynamic because the // expression inside the function may have a more specific type that is // necessary during code generation if (node.resolvedType != Skew.Type.DYNAMIC) { clone.resolvedType = node.resolvedType; } assert((value.kind == Skew.NodeKind.PARAMETERIZE ? value.parameterizeValue() : value).kind == Skew.NodeKind.NAME && value.symbol == info.symbol); assert(clone.resolvedType != null); node.become(clone); Skew.Inlining.recursivelySubstituteArguments(node, node, info.symbol.$arguments, values); // Remove the inlined result entirely if appropriate var parent = node.parent(); if (parent != null && parent.kind == Skew.NodeKind.EXPRESSION && node.hasNoSideEffects()) { parent.remove(); } } }; Skew.Inlining.recursivelySubstituteArguments = function(root, node, $arguments, values) { // Substitute the argument if this is an argument name var symbol = node.symbol; if (symbol != null && Skew.in_SymbolKind.isVariable(symbol.kind)) { var index = $arguments.indexOf(symbol.asVariableSymbol()); if (index != -1) { var parent = node.parent(); // If we're in a wrapped type cast, replace the cast itself if (parent.kind == Skew.NodeKind.CAST) { var valueType = parent.castValue().resolvedType; var targetType = parent.castType().resolvedType; if (valueType.isWrapped() && targetType.symbol != null && valueType.symbol.asObjectSymbol().wrappedType.symbol == targetType.symbol) { node = parent; } } if (node == root) { node.become(in_List.get(values, index)); } else { node.replaceWith(in_List.get(values, index)); } return; } } // Otherwise, recursively search for substitutions in all child nodes for (var child = node.firstChild(), next = null; child != null; child = next) { next = child.nextSibling(); Skew.Inlining.recursivelySubstituteArguments(root, child, $arguments, values); } }; Skew.Inlining.InliningInfo = function(symbol, inlineValue, callSites, unusedArguments) { this.symbol = symbol; this.inlineValue = inlineValue; this.callSites = callSites; this.unusedArguments = unusedArguments; this.shouldInline = true; this.bodyCalls = []; }; // Each node in the inlining graph is a symbol of an inlineable function and // each directional edge is from a first function to a second function that is // called directly within the body of the first function. Indirect function // calls that may become direct calls through inlining can be discovered by // traversing edges of this graph. Skew.Inlining.InliningGraph = function(graph, log, inlineAllFunctions) { this.inliningInfo = []; this.symbolToInfoIndex = new Map(); // Create the nodes in the graph for (var i = 0, list = graph.callInfo, count = list.length; i < count; i = i + 1 | 0) { var callInfo = in_List.get(list, i); var symbol = callInfo.symbol; if (symbol.isInliningPrevented()) { continue; } var info = Skew.Inlining.InliningGraph._createInliningInfo(callInfo); if (info != null) { if (inlineAllFunctions || symbol.isInliningForced()) { in_IntMap.set(this.symbolToInfoIndex, symbol.id, this.inliningInfo.length); this.inliningInfo.push(info); } } else if (symbol.isInliningForced()) { log.semanticWarningInliningFailed(symbol.range, symbol.name); } } // Create the edges in the graph for (var i2 = 0, list2 = this.inliningInfo, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var info1 = in_List.get(list2, i2); for (var i1 = 0, list1 = graph.callInfoForSymbol(info1.symbol).callSites, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var callSite = in_List.get(list1, i1); if (callSite.enclosingSymbol.kind == Skew.SymbolKind.FUNCTION_GLOBAL) { var index = in_IntMap.get(this.symbolToInfoIndex, callSite.enclosingSymbol.id, -1); if (index != -1) { in_List.get(this.inliningInfo, index).bodyCalls.push(info1); } } } } // Detect and disable infinitely expanding inline operations for (var i3 = 0, list3 = this.inliningInfo, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var info2 = in_List.get(list3, i3); info2.shouldInline = !Skew.Inlining.InliningGraph._containsInfiniteExpansion(info2, []); } }; Skew.Inlining.InliningGraph._containsInfiniteExpansion = function(info, symbols) { // This shouldn't get very long in normal programs so O(n) here is fine if (symbols.indexOf(info.symbol) != -1) { return true; } // Do a depth-first search on the graph and check for cycles symbols.push(info.symbol); for (var i = 0, list = info.bodyCalls, count = list.length; i < count; i = i + 1 | 0) { var bodyCall = in_List.get(list, i); if (Skew.Inlining.InliningGraph._containsInfiniteExpansion(bodyCall, symbols)) { return true; } } in_List.removeLast(symbols); return false; }; Skew.Inlining.InliningGraph._createInliningInfo = function(info) { var symbol = info.symbol; // Inline functions consisting of a single return statement if (symbol.kind == Skew.SymbolKind.FUNCTION_GLOBAL) { var block = symbol.block; if (block == null) { return null; } // Replace functions with empty bodies with null if (!block.hasChildren()) { var unusedArguments = []; for (var i = 0, count1 = symbol.$arguments.length; i < count1; i = i + 1 | 0) { unusedArguments.push(i); } return new Skew.Inlining.InliningInfo(symbol, new Skew.Node(Skew.NodeKind.NULL).withType(Skew.Type.NULL), info.callSites, unusedArguments); } var first = block.firstChild(); var inlineValue = null; // If the first value in the function is a return statement, then the // function body doesn't need to only have one statement. Subsequent // statements are just dead code and will never be executed anyway. if (first.kind == Skew.NodeKind.RETURN) { inlineValue = first.returnValue(); } // Otherwise, this statement must be a lone expression statement else if (first.kind == Skew.NodeKind.EXPRESSION && first.nextSibling() == null) { inlineValue = first.expressionValue(); } if (inlineValue != null) { // Count the number of times each symbol is observed. Argument // variables that are used more than once may need a let statement // to avoid changing the semantics of the call site. For now, just // only inline functions where each argument is used exactly once. var argumentCounts = new Map(); for (var i2 = 0, list = symbol.$arguments, count2 = list.length; i2 < count2; i2 = i2 + 1 | 0) { var argument = in_List.get(list, i2); in_IntMap.set(argumentCounts, argument.id, 0); } if (Skew.Inlining.InliningGraph._recursivelyCountArgumentUses(inlineValue, argumentCounts)) { var unusedArguments1 = []; var isSimpleSubstitution = true; for (var i1 = 0, count3 = symbol.$arguments.length; i1 < count3; i1 = i1 + 1 | 0) { var count = in_IntMap.get1(argumentCounts, in_List.get(symbol.$arguments, i1).id); if (count == 0) { unusedArguments1.push(i1); } else if (count != 1) { isSimpleSubstitution = false; break; } } if (isSimpleSubstitution) { return new Skew.Inlining.InliningInfo(symbol, inlineValue, info.callSites, unusedArguments1); } } } } return null; }; // This returns false if inlining is impossible Skew.Inlining.InliningGraph._recursivelyCountArgumentUses = function(node, argumentCounts) { // Prevent inlining of lambda expressions. They have their own function // symbols that reference the original block and won't work with cloning. // Plus inlining lambdas leads to code bloat. if (node.kind == Skew.NodeKind.LAMBDA) { return false; } // Inlining is impossible at this node if it's impossible for any child node for (var child = node.firstChild(); child != null; child = child.nextSibling()) { if (!Skew.Inlining.InliningGraph._recursivelyCountArgumentUses(child, argumentCounts)) { return false; } } var symbol = node.symbol; if (symbol != null) { var count = in_IntMap.get(argumentCounts, symbol.id, -1); if (count != -1) { in_IntMap.set(argumentCounts, symbol.id, count + 1 | 0); // Prevent inlining of functions that modify their arguments locally. For // example, inlining this would lead to incorrect code: // // def foo(x int, y int) { // x += y // } // // def test { // foo(1, 2) // } // if (node.isAssignTarget()) { return false; } } } return true; }; Skew.TypeKind = { LAMBDA: 0, SPECIAL: 1, SYMBOL: 2 }; Skew.Type = function(kind, symbol) { this.id = Skew.Type._nextID = Skew.Type._nextID + 1 | 0; this.kind = kind; this.symbol = symbol; this.environment = null; this.substitutions = null; this.argumentTypes = null; this.returnType = null; this.substitutionCache = null; }; Skew.Type.prototype.parameters = function() { return this.symbol == null ? null : Skew.in_SymbolKind.isObject(this.symbol.kind) ? this.symbol.asObjectSymbol().parameters : Skew.in_SymbolKind.isFunction(this.symbol.kind) ? this.symbol.asFunctionSymbol().parameters : null; }; Skew.Type.prototype.isParameterized = function() { return this.substitutions != null; }; Skew.Type.prototype.isWrapped = function() { return this.symbol != null && this.symbol.kind == Skew.SymbolKind.OBJECT_WRAPPED; }; Skew.Type.prototype.isClass = function() { return this.symbol != null && this.symbol.kind == Skew.SymbolKind.OBJECT_CLASS; }; Skew.Type.prototype.isInterface = function() { return this.symbol != null && this.symbol.kind == Skew.SymbolKind.OBJECT_INTERFACE; }; Skew.Type.prototype.isEnumOrFlags = function() { return this.symbol != null && Skew.in_SymbolKind.isEnumOrFlags(this.symbol.kind); }; Skew.Type.prototype.isFlags = function() { return this.symbol != null && this.symbol.kind == Skew.SymbolKind.OBJECT_FLAGS; }; Skew.Type.prototype.isParameter = function() { return this.symbol != null && Skew.in_SymbolKind.isParameter(this.symbol.kind); }; // Type parameters are not guaranteed to be nullable since generics are // implemented through type erasure and the substituted type may be "int" Skew.Type.prototype.isReference = function() { return this.symbol == null || !this.symbol.isValueType() && !Skew.in_SymbolKind.isParameter(this.symbol.kind) && (this.symbol.kind != Skew.SymbolKind.OBJECT_WRAPPED || this.symbol.asObjectSymbol().wrappedType.isReference()); }; Skew.Type.prototype.toString = function() { if (this.kind == Skew.TypeKind.SYMBOL) { if (this.isParameterized()) { var name = this.symbol.fullName() + '<'; for (var i = 0, count = this.substitutions.length; i < count; i = i + 1 | 0) { if (i != 0) { name += ', '; } name += in_List.get(this.substitutions, i).toString(); } return name + '>'; } return this.symbol.fullName(); } if (this.kind == Skew.TypeKind.LAMBDA) { var result = 'fn('; for (var i1 = 0, count1 = this.argumentTypes.length; i1 < count1; i1 = i1 + 1 | 0) { if (i1 != 0) { result += ', '; } result += in_List.get(this.argumentTypes, i1).toString(); } return result + (this.returnType != null ? ') ' + this.returnType.toString() : ')'); } return this == Skew.Type.DYNAMIC ? 'dynamic' : 'null'; }; Skew.Type.prototype.baseType = function() { return this.isClass() ? this.symbol.asObjectSymbol().baseType : null; }; Skew.Type.prototype.interfaceTypes = function() { return this.isClass() ? this.symbol.asObjectSymbol().interfaceTypes : null; }; Skew.Environment = function(parameters, substitutions) { this.id = Skew.Environment._nextID = Skew.Environment._nextID + 1 | 0; this.parameters = parameters; this.substitutions = substitutions; this.mergeCache = null; }; Skew.TypeCache = function() { this.boolType = null; this.boxType = null; this.doubleType = null; this.intMapType = null; this.intType = null; this.listType = null; this.mathType = null; this.stringMapType = null; this.stringType = null; this.boolToStringSymbol = null; this.doublePowerSymbol = null; this.doubleToStringSymbol = null; this.intPowerSymbol = null; this.intToStringSymbol = null; this.mathPowSymbol = null; this.stringCountSymbol = null; this.stringFromCodePointsSymbol = null; this.stringFromCodePointSymbol = null; this.stringFromCodeUnitsSymbol = null; this.stringFromCodeUnitSymbol = null; this.entryPointSymbol = null; this._environments = new Map(); this._lambdaTypes = new Map(); this._parameters = []; }; Skew.TypeCache.prototype.loadGlobals = function(log, global) { this.boolType = Skew.TypeCache._loadGlobalObject(global, 'bool', Skew.SymbolKind.OBJECT_CLASS, Skew.SymbolFlags.IS_VALUE_TYPE); this.boxType = Skew.TypeCache._loadGlobalObject(global, 'Box', Skew.SymbolKind.OBJECT_CLASS, 0); this.doubleType = Skew.TypeCache._loadGlobalObject(global, 'double', Skew.SymbolKind.OBJECT_CLASS, Skew.SymbolFlags.IS_VALUE_TYPE); this.intMapType = Skew.TypeCache._loadGlobalObject(global, 'IntMap', Skew.SymbolKind.OBJECT_CLASS, 0); this.intType = Skew.TypeCache._loadGlobalObject(global, 'int', Skew.SymbolKind.OBJECT_CLASS, Skew.SymbolFlags.IS_VALUE_TYPE); this.listType = Skew.TypeCache._loadGlobalObject(global, 'List', Skew.SymbolKind.OBJECT_CLASS, 0); this.mathType = Skew.TypeCache._loadGlobalObject(global, 'Math', Skew.SymbolKind.OBJECT_NAMESPACE, 0); this.stringMapType = Skew.TypeCache._loadGlobalObject(global, 'StringMap', Skew.SymbolKind.OBJECT_CLASS, 0); this.stringType = Skew.TypeCache._loadGlobalObject(global, 'string', Skew.SymbolKind.OBJECT_CLASS, 0); this.boolToStringSymbol = Skew.TypeCache._loadInstanceFunction(this.boolType, 'toString'); this.doublePowerSymbol = Skew.TypeCache._loadInstanceFunction(this.doubleType, '**'); this.doubleToStringSymbol = Skew.TypeCache._loadInstanceFunction(this.doubleType, 'toString'); this.intPowerSymbol = Skew.TypeCache._loadInstanceFunction(this.intType, '**'); this.intToStringSymbol = Skew.TypeCache._loadInstanceFunction(this.intType, 'toString'); this.mathPowSymbol = Skew.TypeCache._loadGlobalFunction(this.mathType, 'pow'); this.stringCountSymbol = Skew.TypeCache._loadInstanceFunction(this.stringType, 'count'); this.stringFromCodePointsSymbol = Skew.TypeCache._loadGlobalFunction(this.stringType, 'fromCodePoints'); this.stringFromCodePointSymbol = Skew.TypeCache._loadGlobalFunction(this.stringType, 'fromCodePoint'); this.stringFromCodeUnitsSymbol = Skew.TypeCache._loadGlobalFunction(this.stringType, 'fromCodeUnits'); this.stringFromCodeUnitSymbol = Skew.TypeCache._loadGlobalFunction(this.stringType, 'fromCodeUnit'); }; Skew.TypeCache.prototype.isEquivalentToBool = function(type) { return this.unwrappedType(type) == this.boolType; }; Skew.TypeCache.prototype.isEquivalentToInt = function(type) { return this.isInteger(this.unwrappedType(type)); }; Skew.TypeCache.prototype.isEquivalentToDouble = function(type) { return this.unwrappedType(type) == this.doubleType; }; Skew.TypeCache.prototype.isEquivalentToString = function(type) { return this.unwrappedType(type) == this.stringType; }; Skew.TypeCache.prototype.isInteger = function(type) { return type == this.intType || type.isEnumOrFlags(); }; Skew.TypeCache.prototype.isNumeric = function(type) { return this.isInteger(type) || type == this.doubleType; }; Skew.TypeCache.prototype.isList = function(type) { return type.symbol == this.listType.symbol; }; Skew.TypeCache.prototype.isIntMap = function(type) { return type.symbol == this.intMapType.symbol; }; Skew.TypeCache.prototype.isStringMap = function(type) { return type.symbol == this.stringMapType.symbol; }; Skew.TypeCache.prototype.isBaseType = function(derived, base) { if (derived.isClass() && base.isClass()) { while (true) { var baseType = derived.baseType(); if (baseType == null) { break; } derived = this.substitute(baseType, derived.environment); if (derived == base) { return true; } } } return false; }; Skew.TypeCache.prototype.isImplementedInterface = function(classType, interfaceType) { if (classType.isClass() && interfaceType.isInterface()) { while (classType != null) { var interfaceTypes = classType.interfaceTypes(); if (interfaceTypes != null) { for (var i = 0, list = interfaceTypes, count = list.length; i < count; i = i + 1 | 0) { var type = in_List.get(list, i); if (this.substitute(type, classType.environment) == interfaceType) { return true; } } } var baseType = classType.baseType(); if (baseType == null) { break; } classType = this.substitute(baseType, classType.environment); } } return false; }; Skew.TypeCache.prototype.unwrappedType = function(type) { if (type.isWrapped()) { var inner = type.symbol.asObjectSymbol().wrappedType; if (inner != null) { return this.unwrappedType(this.substitute(inner, type.environment)); } } return type; }; Skew.TypeCache.prototype.canImplicitlyConvert = function(from, to) { if (from == to) { return true; } if (from == Skew.Type.DYNAMIC || to == Skew.Type.DYNAMIC) { return true; } if (from == Skew.Type.NULL && to.isReference()) { return true; } if (from == this.intType && to == this.doubleType) { return true; } if (this.isBaseType(from, to)) { return true; } if (this.isImplementedInterface(from, to)) { return true; } if (from.isEnumOrFlags() && !to.isEnumOrFlags() && this.isNumeric(to)) { return true; } return false; }; Skew.TypeCache.prototype.canExplicitlyConvert = function(from, to) { from = this.unwrappedType(from); to = this.unwrappedType(to); if (this.canImplicitlyConvert(from, to)) { return true; } if (this._canCastToNumeric(from) && this._canCastToNumeric(to)) { return true; } if (this.isBaseType(to, from)) { return true; } if (this.isImplementedInterface(to, from)) { return true; } if (to.isEnumOrFlags() && this.isNumeric(from)) { return true; } return false; }; Skew.TypeCache.prototype.commonImplicitType = function(left, right) { // Short-circuit early for identical types if (left == right) { return left; } // Dynamic is a hole in the type system if (left == Skew.Type.DYNAMIC || right == Skew.Type.DYNAMIC) { return Skew.Type.DYNAMIC; } // Check implicit conversions if (this.canImplicitlyConvert(left, right)) { return right; } if (this.canImplicitlyConvert(right, left)) { return left; } // Implement common implicit types for numeric types if (this.isNumeric(left) && this.isNumeric(right)) { return this.isInteger(left) && this.isInteger(right) ? this.intType : this.doubleType; } // Check for a common base class if (left.isClass() && right.isClass()) { return Skew.TypeCache._commonBaseType(left, right); } return null; }; Skew.TypeCache.prototype.createInt = function(value) { return new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.IntContent(value)).withType(this.intType); }; Skew.TypeCache.prototype.createDouble = function(value) { return new Skew.Node(Skew.NodeKind.CONSTANT).withContent(new Skew.DoubleContent(value)).withType(this.doubleType); }; Skew.TypeCache.prototype.createListType = function(itemType) { return this.substitute(this.listType, this.createEnvironment(this.listType.parameters(), [itemType])); }; Skew.TypeCache.prototype.createIntMapType = function(valueType) { return this.substitute(this.intMapType, this.createEnvironment(this.intMapType.parameters(), [valueType])); }; Skew.TypeCache.prototype.createStringMapType = function(valueType) { return this.substitute(this.stringMapType, this.createEnvironment(this.stringMapType.parameters(), [valueType])); }; Skew.TypeCache.prototype.createEnvironment = function(parameters, substitutions) { assert(parameters.length == substitutions.length); // Hash the inputs var hash = Skew.TypeCache._hashTypes(Skew.TypeCache._hashParameters(parameters), substitutions); var bucket = in_IntMap.get(this._environments, hash, null); // Check existing environments in the bucket for a match if (bucket != null) { for (var i = 0, list = bucket, count = list.length; i < count; i = i + 1 | 0) { var existing = in_List.get(list, i); if (in_List.equals(parameters, existing.parameters) && in_List.equals(substitutions, existing.substitutions)) { return existing; } } } // Make a new bucket else { bucket = []; in_IntMap.set(this._environments, hash, bucket); } // Make a new environment var environment = new Skew.Environment(parameters, substitutions); bucket.push(environment); return environment; }; Skew.TypeCache.prototype.createLambdaType = function(argumentTypes, returnType) { // This is used as a sentinel on LAMBDA_TYPE nodes assert(returnType != Skew.Type.NULL); var hash = Skew.TypeCache._hashTypes(returnType != null ? returnType.id : -1, argumentTypes); var bucket = in_IntMap.get(this._lambdaTypes, hash, null); // Check existing types in the bucket for a match if (bucket != null) { for (var i = 0, list = bucket, count = list.length; i < count; i = i + 1 | 0) { var existing = in_List.get(list, i); if (in_List.equals(argumentTypes, existing.argumentTypes) && returnType == existing.returnType) { return existing; } } } // Make a new bucket else { bucket = []; in_IntMap.set(this._lambdaTypes, hash, bucket); } // Make a new lambda type var type = new Skew.Type(Skew.TypeKind.LAMBDA, null); // Make a copy in case the caller mutates this later type.argumentTypes = argumentTypes.slice(); type.returnType = returnType; bucket.push(type); return type; }; Skew.TypeCache.prototype.mergeEnvironments = function(a, b, restrictions) { if (a == null) { return b; } if (b == null) { return a; } var parameters = a.parameters.slice(); var substitutions = this.substituteAll(a.substitutions, b); for (var i = 0, count = b.parameters.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(b.parameters, i); var substitution = in_List.get(b.substitutions, i); if (!(parameters.indexOf(parameter) != -1) && (restrictions == null || restrictions.indexOf(parameter) != -1)) { parameters.push(parameter); substitutions.push(substitution); } } return this.createEnvironment(parameters, substitutions); }; Skew.TypeCache.prototype.parameterize = function(type) { var parameters = type.parameters(); if (parameters == null) { return type; } assert(!type.isParameterized()); var substitutions = []; for (var i = 0, list = parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); substitutions.push(parameter.resolvedType); } return this.substitute(type, this.createEnvironment(parameters, substitutions)); }; Skew.TypeCache.prototype.substituteAll = function(types, environment) { var substitutions = []; for (var i = 0, list = types, count = list.length; i < count; i = i + 1 | 0) { var type = in_List.get(list, i); substitutions.push(this.substitute(type, environment)); } return substitutions; }; Skew.TypeCache.prototype.substitute = function(type, environment) { var existing = type.environment; if (environment == null || environment == existing) { return type; } // Merge the type environments (this matters for nested generics). For // object types, limit the parameters in the environment to just those // on this type and the base type. var parameters = type.parameters(); if (existing != null) { environment = this.mergeEnvironments(existing, environment, type.kind == Skew.TypeKind.SYMBOL && Skew.in_SymbolKind.isFunctionOrOverloadedFunction(type.symbol.kind) ? null : parameters); } // Check to see if this has been computed before var rootType = type.kind == Skew.TypeKind.SYMBOL ? type.symbol.resolvedType : type; if (rootType.substitutionCache == null) { rootType.substitutionCache = new Map(); } var substituted = in_IntMap.get(rootType.substitutionCache, environment.id, null); if (substituted != null) { return substituted; } substituted = type; if (type.kind == Skew.TypeKind.LAMBDA) { var argumentTypes = []; var returnType = null; // Substitute function arguments for (var i = 0, list = type.argumentTypes, count = list.length; i < count; i = i + 1 | 0) { var argumentType = in_List.get(list, i); argumentTypes.push(this.substitute(argumentType, environment)); } // Substitute return type if (type.returnType != null) { returnType = this.substitute(type.returnType, environment); } substituted = this.createLambdaType(argumentTypes, returnType); } else if (type.kind == Skew.TypeKind.SYMBOL) { var symbol = type.symbol; // Parameters just need simple substitution if (Skew.in_SymbolKind.isParameter(symbol.kind)) { var index = environment.parameters.indexOf(symbol.asParameterSymbol()); if (index != -1) { substituted = in_List.get(environment.substitutions, index); } } // Symbols with type parameters are more complicated // Overloaded functions are also included even though they don't have // type parameters because the type environment needs to be bundled // for later substitution into individual matched overloads else if (parameters != null || Skew.in_SymbolKind.isFunctionOrOverloadedFunction(symbol.kind)) { substituted = new Skew.Type(Skew.TypeKind.SYMBOL, symbol); substituted.environment = environment; // Generate type substitutions if (parameters != null) { var found = true; for (var i1 = 0, list1 = parameters, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var parameter = in_List.get(list1, i1); found = environment.parameters.indexOf(parameter) != -1; if (!found) { break; } } if (found) { substituted.substitutions = []; for (var i2 = 0, list2 = parameters, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var parameter1 = in_List.get(list2, i2); substituted.substitutions.push(this.substitute(parameter1.resolvedType, environment)); } } } // Substitute function arguments if (type.argumentTypes != null) { substituted.argumentTypes = []; for (var i3 = 0, list3 = type.argumentTypes, count3 = list3.length; i3 < count3; i3 = i3 + 1 | 0) { var argumentType1 = in_List.get(list3, i3); substituted.argumentTypes.push(this.substitute(argumentType1, environment)); } } // Substitute return type if (type.returnType != null) { substituted.returnType = this.substitute(type.returnType, environment); } } } in_IntMap.set(rootType.substitutionCache, environment.id, substituted); return substituted; }; // Substitute the type parameters from one function into the other Skew.TypeCache.prototype.substituteFunctionParameters = function(type, from, to) { if (from.parameters != null && to.parameters != null && from.parameters.length == to.parameters.length) { var substitutions = []; for (var i = 0, list = from.parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); substitutions.push(parameter.resolvedType); } type = this.substitute(type, this.createEnvironment(to.parameters, substitutions)); } return type; }; Skew.TypeCache.prototype.areFunctionSymbolsEquivalent = function(left, leftEnvironment, right, rightEnvironment) { var leftType = left.resolvedType; var rightType = right.resolvedType; var leftReturn = leftType.returnType; var rightReturn = rightType.returnType; // Account for return types of functions from generic base types if (leftReturn != null) { leftReturn = this.substitute(leftReturn, leftEnvironment); } if (rightReturn != null) { rightReturn = this.substitute(rightReturn, rightEnvironment); } // Overloading by return type is not allowed, so only compare argument types if (this.substitute(left.argumentOnlyType, leftEnvironment) == this.substitute(right.argumentOnlyType, rightEnvironment)) { return leftReturn == rightReturn ? Skew.TypeCache.Equivalence.EQUIVALENT : Skew.TypeCache.Equivalence.EQUIVALENT_EXCEPT_RETURN_TYPE; } // For generic functions, substitute dummy type parameters into both // functions and then compare. For example, these are equivalent: // // def foo(bar X) // def foo(baz Y) // if (left.parameters != null && right.parameters != null) { var leftArguments = leftType.argumentTypes; var rightArguments = rightType.argumentTypes; var argumentCount = leftArguments.length; var parameterCount = left.parameters.length; if (argumentCount == rightArguments.length && parameterCount == right.parameters.length) { // Generate enough dummy type parameters for (var i = this._parameters.length, count = parameterCount; i < count; i = i + 1 | 0) { var symbol = new Skew.ParameterSymbol(Skew.SymbolKind.PARAMETER_FUNCTION, 'T' + i.toString()); symbol.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, symbol); symbol.state = Skew.SymbolState.INITIALIZED; this._parameters.push(symbol.resolvedType); } // Substitute the same type parameters into both functions var parameters = this._parameters.length == parameterCount ? this._parameters : in_List.slice2(this._parameters, 0, parameterCount); var leftParametersEnvironment = this.createEnvironment(left.parameters, parameters); var rightParametersEnvironment = this.createEnvironment(right.parameters, parameters); // Compare each argument for (var i1 = 0, count1 = argumentCount; i1 < count1; i1 = i1 + 1 | 0) { if (this.substitute(this.substitute(in_List.get(leftArguments, i1), leftEnvironment), leftParametersEnvironment) != this.substitute(this.substitute(in_List.get(rightArguments, i1), rightEnvironment), rightParametersEnvironment)) { return Skew.TypeCache.Equivalence.NOT_EQUIVALENT; } } return leftReturn == null && rightReturn == null || leftReturn != null && rightReturn != null && this.substitute(leftReturn, leftParametersEnvironment) == this.substitute(rightReturn, rightParametersEnvironment) ? Skew.TypeCache.Equivalence.EQUIVALENT : Skew.TypeCache.Equivalence.EQUIVALENT_EXCEPT_RETURN_TYPE; } } return Skew.TypeCache.Equivalence.NOT_EQUIVALENT; }; Skew.TypeCache.prototype._canCastToNumeric = function(type) { return type == this.intType || type == this.doubleType || type == this.boolType; }; Skew.TypeCache._loadGlobalObject = function(global, name, kind, flags) { assert(Skew.in_SymbolKind.isObject(kind)); var symbol = in_StringMap.get(global.members, name, null); assert(symbol != null); assert(symbol.kind == kind); var type = new Skew.Type(Skew.TypeKind.SYMBOL, symbol.asObjectSymbol()); symbol.resolvedType = type; symbol.flags |= flags; return type; }; Skew.TypeCache._loadInstanceFunction = function(type, name) { var symbol = in_StringMap.get(type.symbol.asObjectSymbol().members, name, null); assert(symbol != null); assert(symbol.kind == Skew.SymbolKind.FUNCTION_INSTANCE || symbol.kind == Skew.SymbolKind.OVERLOADED_INSTANCE); return symbol; }; Skew.TypeCache._loadGlobalFunction = function(type, name) { var symbol = in_StringMap.get(type.symbol.asObjectSymbol().members, name, null); assert(symbol != null); assert(symbol.kind == Skew.SymbolKind.FUNCTION_GLOBAL || symbol.kind == Skew.SymbolKind.OVERLOADED_GLOBAL); return symbol; }; Skew.TypeCache._hashParameters = function(parameters) { var hash = 0; for (var i = 0, list = parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); hash = Skew.hashCombine(hash, parameter.id); } return hash; }; Skew.TypeCache._hashTypes = function(hash, types) { for (var i = 0, list = types, count = list.length; i < count; i = i + 1 | 0) { var type = in_List.get(list, i); hash = Skew.hashCombine(hash, type.id); } return hash; }; Skew.TypeCache._commonBaseType = function(left, right) { var a = left; while (a != null) { var b = right; while (b != null) { if (a == b) { return a; } b = b.baseType(); } a = a.baseType(); } return null; }; Skew.TypeCache.Equivalence = { EQUIVALENT: 0, EQUIVALENT_EXCEPT_RETURN_TYPE: 1, NOT_EQUIVALENT: 2 }; Skew.FoldingPass = function() { Skew.Pass.call(this); }; __extends(Skew.FoldingPass, Skew.Pass); Skew.FoldingPass.prototype.kind = function() { return Skew.PassKind.FOLDING; }; Skew.FoldingPass.prototype.run = function(context) { new Skew.Folding.ConstantFolder(context.cache, context.options, null).visitObject(context.global); }; Skew.Folding = {}; Skew.Folding.ConstantFolder = function(_cache, _options, _prepareSymbol) { this._cache = _cache; this._options = _options; this._prepareSymbol = _prepareSymbol; this._constantCache = new Map(); }; Skew.Folding.ConstantFolder.prototype.visitObject = function(symbol) { for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this.visitObject(object); } for (var i1 = 0, list1 = symbol.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); if ($function.block != null) { this.foldConstants($function.block); } } for (var i2 = 0, list2 = symbol.variables, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); if (variable.value != null) { this.foldConstants(variable.value); } } }; // Use this instead of node.become(Node.createConstant(content)) to avoid more GC Skew.Folding.ConstantFolder.prototype._flatten = function(node, content) { node.removeChildren(); node.kind = Skew.NodeKind.CONSTANT; node.content = content; node.symbol = null; }; // Use this instead of node.become(Node.createBool(value)) to avoid more GC Skew.Folding.ConstantFolder.prototype._flattenBool = function(node, value) { assert(this._cache.isEquivalentToBool(node.resolvedType) || node.resolvedType == Skew.Type.DYNAMIC); this._flatten(node, new Skew.BoolContent(value)); }; // Use this instead of node.become(Node.createInt(value)) to avoid more GC Skew.Folding.ConstantFolder.prototype._flattenInt = function(node, value) { assert(this._cache.isEquivalentToInt(node.resolvedType) || node.resolvedType == Skew.Type.DYNAMIC); this._flatten(node, new Skew.IntContent(value)); }; // Use this instead of node.become(Node.createDouble(value)) to avoid more GC Skew.Folding.ConstantFolder.prototype._flattenDouble = function(node, value) { assert(this._cache.isEquivalentToDouble(node.resolvedType) || node.resolvedType == Skew.Type.DYNAMIC); this._flatten(node, new Skew.DoubleContent(value)); }; // Use this instead of node.become(Node.createString(value)) to avoid more GC Skew.Folding.ConstantFolder.prototype._flattenString = function(node, value) { assert(this._cache.isEquivalentToString(node.resolvedType) || node.resolvedType == Skew.Type.DYNAMIC); this._flatten(node, new Skew.StringContent(value)); }; Skew.Folding.ConstantFolder.prototype.foldConstants = function(node) { var kind = node.kind; // Transform "a + (b + c)" => "(a + b) + c" before operands are folded if (kind == Skew.NodeKind.ADD && node.resolvedType == this._cache.stringType && node.binaryLeft().resolvedType == this._cache.stringType && node.binaryRight().resolvedType == this._cache.stringType) { this._rotateStringConcatenation(node); } // Fold operands before folding this node for (var child = node.firstChild(); child != null; child = child.nextSibling()) { this.foldConstants(child); } // Separating the case bodies into separate functions makes the JavaScript JIT go faster switch (kind) { case Skew.NodeKind.BLOCK: { this._foldBlock(node); break; } case Skew.NodeKind.CALL: { this._foldCall(node); break; } case Skew.NodeKind.CAST: { this._foldCast(node); break; } case Skew.NodeKind.DOT: { this._foldDot(node); break; } case Skew.NodeKind.HOOK: { this._foldHook(node); break; } case Skew.NodeKind.NAME: { this._foldName(node); break; } case Skew.NodeKind.COMPLEMENT: case Skew.NodeKind.NEGATIVE: case Skew.NodeKind.NOT: case Skew.NodeKind.POSITIVE: { this._foldUnary(node); break; } default: { if (Skew.in_NodeKind.isBinary(kind)) { this._foldBinary(node); } break; } } }; Skew.Folding.ConstantFolder.prototype._rotateStringConcatenation = function(node) { var left = node.binaryLeft(); var right = node.binaryRight(); assert(node.kind == Skew.NodeKind.ADD); assert(left.resolvedType == this._cache.stringType || left.resolvedType == Skew.Type.DYNAMIC); assert(right.resolvedType == this._cache.stringType || right.resolvedType == Skew.Type.DYNAMIC); // "a + (b + c)" => "(a + b) + c" if (right.kind == Skew.NodeKind.ADD) { assert(right.binaryLeft().resolvedType == this._cache.stringType || right.binaryLeft().resolvedType == Skew.Type.DYNAMIC); assert(right.binaryRight().resolvedType == this._cache.stringType || right.binaryRight().resolvedType == Skew.Type.DYNAMIC); node.rotateBinaryRightToLeft(); } }; Skew.Folding.ConstantFolder.prototype._foldStringConcatenation = function(node) { var left = node.binaryLeft(); var right = node.binaryRight(); assert(left.resolvedType == this._cache.stringType || left.resolvedType == Skew.Type.DYNAMIC); assert(right.resolvedType == this._cache.stringType || right.resolvedType == Skew.Type.DYNAMIC); if (right.isString()) { // "a" + "b" => "ab" if (left.isString()) { this._flattenString(node, left.asString() + right.asString()); } else if (left.kind == Skew.NodeKind.ADD) { var leftLeft = left.binaryLeft(); var leftRight = left.binaryRight(); assert(leftLeft.resolvedType == this._cache.stringType || leftLeft.resolvedType == Skew.Type.DYNAMIC); assert(leftRight.resolvedType == this._cache.stringType || leftRight.resolvedType == Skew.Type.DYNAMIC); // (a + "b") + "c" => a + "bc" if (leftRight.isString()) { this._flattenString(leftRight, leftRight.asString() + right.asString()); node.become(left.remove()); } } } }; Skew.Folding.ConstantFolder.prototype._foldTry = function(node) { var tryBlock = node.tryBlock(); // var finallyBlock = node.finallyBlock // A try block without any statements cannot possibly throw if (!tryBlock.hasChildren()) { node.remove(); return -1; } return 0; }; Skew.Folding.ConstantFolder.prototype._foldIf = function(node) { var test = node.ifTest(); var trueBlock = node.ifTrue(); var falseBlock = node.ifFalse(); // No reason to keep an empty "else" block if (falseBlock != null && !falseBlock.hasChildren()) { falseBlock.remove(); falseBlock = null; } // Always true if statement if (test.isTrue()) { // Inline the contents of the true block node.replaceWithChildrenFrom(trueBlock); } // Always false if statement else if (test.isFalse()) { // Remove entirely if (falseBlock == null) { node.remove(); } // Inline the contents of the false block else { node.replaceWithChildrenFrom(falseBlock); } } // Remove if statements with empty true blocks else if (!trueBlock.hasChildren()) { // "if (a) {} else b;" => "if (!a) b;" if (falseBlock != null && falseBlock.hasChildren()) { test.invertBooleanCondition(this._cache); trueBlock.remove(); } // "if (a) {}" => "" else if (test.hasNoSideEffects()) { node.remove(); } // "if (a) {}" => "a;" else { node.become(Skew.Node.createExpression(test.remove())); } } }; Skew.Folding.ConstantFolder.prototype._foldSwitch = function(node) { var value = node.switchValue(); var defaultCase = null; // Check for a default case for (var child = value.nextSibling(); child != null; child = child.nextSibling()) { if (child.hasOneChild()) { defaultCase = child; break; } } // Remove the default case if it's empty if (defaultCase != null && !defaultCase.caseBlock().hasChildren()) { defaultCase.remove(); defaultCase = null; } // Check for a constant value and inline the corresponding case block if (value.kind == Skew.NodeKind.CONSTANT) { var hasNonConstant = false; // Search all case blocks for a match for (var child1 = value.nextSibling(), nextChild = null; child1 != null; child1 = nextChild) { nextChild = child1.nextSibling(); var block = child1.caseBlock(); for (var caseValue = child1.firstChild(), nextCase = null; caseValue != block; caseValue = nextCase) { nextCase = caseValue.nextSibling(); // If there's a non-constant value, we can't tell if it's taken or not if (caseValue.kind != Skew.NodeKind.CONSTANT) { hasNonConstant = true; } // Remove cases that definitely don't apply else if (!Skew.in_Content.equals(value.content, caseValue.content)) { caseValue.remove(); } // Only inline this case if all previous values have been constants, // otherwise we can't be sure that none of those would have matched else if (!hasNonConstant) { node.replaceWithChildrenFrom(block); return; } } // Remove the case entirely if all values were trimmed if (child1.hasOneChild() && child1 != defaultCase) { child1.remove(); } } // Inline the default case if it's present and it can be proven to be taken if (!hasNonConstant) { if (defaultCase != null) { node.replaceWithChildrenFrom(defaultCase.caseBlock()); } else { node.remove(); } return; } } // If the default case is missing, all other empty cases can be removed too if (defaultCase == null) { for (var child2 = node.lastChild(), previous = null; child2 != value; child2 = previous) { previous = child2.previousSibling(); if (!child2.caseBlock().hasChildren()) { child2.remove(); } } } // Replace "switch (foo) {}" with "foo;" if (node.hasOneChild()) { node.become(Skew.Node.createExpression(value.remove()).withRange(node.range)); } }; Skew.Folding.ConstantFolder.prototype._foldVariables = function(node) { // Remove symbols entirely that are being inlined everywhere for (var child = node.firstChild(), next = null; child != null; child = next) { assert(child.kind == Skew.NodeKind.VARIABLE); next = child.nextSibling(); var symbol = child.symbol.asVariableSymbol(); if (symbol.isConst() && this.constantForSymbol(symbol) != null) { child.remove(); } } // Empty variable statements are not allowed if (!node.hasChildren()) { node.remove(); } }; Skew.Folding.ConstantFolder.prototype._foldBlock = function(node) { for (var child = node.firstChild(), next = null; child != null; child = next) { next = child.nextSibling(); var kind = child.kind; // Remove everything after a jump if (Skew.in_NodeKind.isJump(kind)) { while (child.nextSibling() != null) { child.nextSibling().remove(); } break; } // Remove constants and "while false { ... }" entirely if (kind == Skew.NodeKind.EXPRESSION && child.expressionValue().hasNoSideEffects() || kind == Skew.NodeKind.WHILE && child.whileTest().isFalse()) { child.remove(); } // Remove dead assignments else if (kind == Skew.NodeKind.EXPRESSION && child.expressionValue().kind == Skew.NodeKind.ASSIGN) { this._foldAssignment(child); } else if (kind == Skew.NodeKind.VARIABLES) { this._foldVariables(child); } // Remove unused try statements since they can cause deoptimizations else if (kind == Skew.NodeKind.TRY) { this._foldTry(child); } // Statically evaluate if statements where possible else if (kind == Skew.NodeKind.IF) { this._foldIf(child); } // Fold switch statements else if (kind == Skew.NodeKind.SWITCH) { this._foldSwitch(child); } } }; // "a = 0; b = 0; a = 1;" => "b = 0; a = 1;" Skew.Folding.ConstantFolder.prototype._foldAssignment = function(node) { assert(node.kind == Skew.NodeKind.EXPRESSION && node.expressionValue().kind == Skew.NodeKind.ASSIGN); var value = node.expressionValue(); var left = value.binaryLeft(); var right = value.binaryRight(); // Only do this for simple variable assignments var dotVariable = left.kind == Skew.NodeKind.DOT && Skew.Folding.ConstantFolder._isVariableReference(left.dotTarget()) ? left.dotTarget().symbol : null; var variable = Skew.Folding.ConstantFolder._isVariableReference(left) || dotVariable != null ? left.symbol : null; if (variable == null) { return; } // Make sure the assigned value doesn't need the previous value. We bail // on expressions with side effects like function calls and on expressions // that reference the variable. if (!right.hasNoSideEffects() || Skew.Folding.ConstantFolder._hasNestedReference(right, variable)) { return; } // Scan backward over previous statements var previous = node.previousSibling(); while (previous != null) { // Only pattern-match expressions if (previous.kind == Skew.NodeKind.EXPRESSION) { var previousValue = previous.expressionValue(); // Remove duplicate assignments if (previousValue.kind == Skew.NodeKind.ASSIGN) { var previousLeft = previousValue.binaryLeft(); var previousRight = previousValue.binaryRight(); var previousDotVariable = previousLeft.kind == Skew.NodeKind.DOT && Skew.Folding.ConstantFolder._isVariableReference(previousLeft.dotTarget()) ? previousLeft.dotTarget().symbol : null; var previousVariable = Skew.Folding.ConstantFolder._isVariableReference(previousLeft) || previousDotVariable != null && previousDotVariable == dotVariable ? previousLeft.symbol : null; // Check for assignment to the same variable and remove the assignment // if it's a match. Make sure to keep the assigned value around if it // has side effects. if (previousVariable == variable) { if (previousRight.hasNoSideEffects()) { previous.remove(); } else { previousValue.replaceWith(previousRight.remove()); } break; } // Stop if we can't determine that this statement doesn't involve // this variable's value. If it does involve this variable's value, // then it isn't safe to remove duplicate assignments past this // statement. if (!previousRight.hasNoSideEffects() || Skew.Folding.ConstantFolder._hasNestedReference(previousRight, variable)) { break; } } // Also stop here if we can't determine that this statement doesn't // involve this variable's value else if (!previousValue.hasNoSideEffects()) { break; } } // Also stop here if we can't determine that this statement doesn't // involve this variable's value else { break; } previous = previous.previousSibling(); } }; Skew.Folding.ConstantFolder.prototype._foldDot = function(node) { var symbol = node.symbol; // Only replace this with a constant if the target has no side effects. // This catches constants declared on imported types. if (Skew.Folding.ConstantFolder._shouldFoldSymbol(symbol) && !node.isAssignTarget() && (node.dotTarget() == null || node.dotTarget().hasNoSideEffects())) { var content = this.constantForSymbol(symbol.asVariableSymbol()); if (content != null) { this._flatten(node, content); } } }; Skew.Folding.ConstantFolder.prototype._foldName = function(node) { var symbol = node.symbol; // Don't fold loop variables since they aren't actually constant across loop iterations if (Skew.Folding.ConstantFolder._shouldFoldSymbol(symbol) && !node.isAssignTarget() && !symbol.isLoopVariable()) { var content = this.constantForSymbol(symbol.asVariableSymbol()); if (content != null) { this._flatten(node, content); } } }; Skew.Folding.ConstantFolder.prototype._foldCall = function(node) { var value = node.callValue(); var symbol = value.symbol; // Fold instance function calls if (value.kind == Skew.NodeKind.DOT) { var target = value.dotTarget(); // Folding of double.toString can't be done in a platform-independent // manner. The obvious cases are NaN and infinity, but even fractions // are emitted differently on different platforms. Instead of having // constant folding change how the code behaves, just don't fold double // toString calls. // // "bool.toString" // "int.toString" // if (target != null && target.kind == Skew.NodeKind.CONSTANT) { if (Skew.Folding.ConstantFolder._isKnownCall(symbol, this._cache.boolToStringSymbol)) { this._flattenString(node, target.asBool().toString()); } else if (Skew.Folding.ConstantFolder._isKnownCall(symbol, this._cache.intToStringSymbol)) { this._flattenString(node, target.asInt().toString()); } } } // Fold global function calls else if (value.kind == Skew.NodeKind.NAME) { // "\"abc\".count" => "3" if (Skew.Folding.ConstantFolder._isKnownCall(symbol, this._cache.stringCountSymbol) && node.lastChild().isString()) { this._flattenInt(node, Unicode.codeUnitCountForCodePoints(in_string.codePoints(node.lastChild().asString()), this._options.target.stringEncoding())); } // "3 ** 2" => "9" else if (Skew.Folding.ConstantFolder._isKnownCall(symbol, this._cache.intPowerSymbol) && node.lastChild().isInt() && value.nextSibling().isInt()) { this._flattenInt(node, in_int.power(value.nextSibling().asInt(), node.lastChild().asInt())); } // "0.0625 ** 0.25" => "0.5" // "Math.pow(0.0625, 0.25)" => "0.5" else if ((Skew.Folding.ConstantFolder._isKnownCall(symbol, this._cache.doublePowerSymbol) || Skew.Folding.ConstantFolder._isKnownCall(symbol, this._cache.mathPowSymbol)) && node.lastChild().isDouble() && value.nextSibling().isDouble()) { this._flattenDouble(node, Math.pow(value.nextSibling().asDouble(), node.lastChild().asDouble())); } // "string.fromCodePoint(100)" => "\"d\"" // "string.fromCodeUnit(100)" => "\"d\"" else if ((Skew.Folding.ConstantFolder._isKnownCall(symbol, this._cache.stringFromCodePointSymbol) || Skew.Folding.ConstantFolder._isKnownCall(symbol, this._cache.stringFromCodeUnitSymbol)) && node.lastChild().isInt()) { // "fromCodePoint" is a superset of "fromCodeUnit" this._flattenString(node, in_string.fromCodePoint(node.lastChild().asInt())); } // "string.fromCodePoints([97, 98, 99])" => "\"abc\"" // "string.fromCodeUnits([97, 98, 99])" => "\"abc\"" else if ((Skew.Folding.ConstantFolder._isKnownCall(symbol, this._cache.stringFromCodePointsSymbol) || Skew.Folding.ConstantFolder._isKnownCall(symbol, this._cache.stringFromCodeUnitsSymbol)) && node.lastChild().kind == Skew.NodeKind.INITIALIZER_LIST) { var codePoints = []; for (var child = node.lastChild().firstChild(); child != null; child = child.nextSibling()) { if (!child.isInt()) { return; } codePoints.push(child.asInt()); } // "fromCodePoints" is a superset of "fromCodeUnits" this._flattenString(node, in_string.fromCodePoints(codePoints)); } } }; Skew.Folding.ConstantFolder.prototype._foldCast = function(node) { var type = node.castType().resolvedType; var value = node.castValue(); if (value.kind == Skew.NodeKind.CONSTANT) { var content = value.content; var kind = content.kind(); // Cast "bool" values if (kind == Skew.ContentKind.BOOL) { if (this._cache.isEquivalentToBool(type)) { this._flattenBool(node, value.asBool()); } else if (this._cache.isEquivalentToInt(type)) { this._flattenInt(node, value.asBool() | 0); } else if (this._cache.isEquivalentToDouble(type)) { this._flattenDouble(node, +value.asBool()); } } // Cast "int" values else if (kind == Skew.ContentKind.INT) { if (this._cache.isEquivalentToBool(type)) { this._flattenBool(node, !!value.asInt()); } else if (this._cache.isEquivalentToInt(type)) { this._flattenInt(node, value.asInt()); } else if (this._cache.isEquivalentToDouble(type)) { this._flattenDouble(node, value.asInt()); } } // Cast "double" values else if (kind == Skew.ContentKind.DOUBLE) { if (this._cache.isEquivalentToBool(type)) { this._flattenBool(node, !!value.asDouble()); } else if (this._cache.isEquivalentToInt(type)) { this._flattenInt(node, value.asDouble() | 0); } else if (this._cache.isEquivalentToDouble(type)) { this._flattenDouble(node, value.asDouble()); } } } }; Skew.Folding.ConstantFolder.prototype._foldUnary = function(node) { var value = node.unaryValue(); var kind = node.kind; if (value.kind == Skew.NodeKind.CONSTANT) { var content = value.content; var contentKind = content.kind(); // Fold "bool" values if (contentKind == Skew.ContentKind.BOOL) { if (kind == Skew.NodeKind.NOT) { this._flattenBool(node, !value.asBool()); } } // Fold "int" values else if (contentKind == Skew.ContentKind.INT) { if (kind == Skew.NodeKind.POSITIVE) { this._flattenInt(node, +value.asInt()); } else if (kind == Skew.NodeKind.NEGATIVE) { this._flattenInt(node, -value.asInt() | 0); } else if (kind == Skew.NodeKind.COMPLEMENT) { this._flattenInt(node, ~value.asInt()); } } // Fold "float" or "double" values else if (contentKind == Skew.ContentKind.DOUBLE) { if (kind == Skew.NodeKind.POSITIVE) { this._flattenDouble(node, +value.asDouble()); } else if (kind == Skew.NodeKind.NEGATIVE) { this._flattenDouble(node, -value.asDouble()); } } } // Partial evaluation ("!!x" isn't necessarily "x" if we don't know the type) else if (kind == Skew.NodeKind.NOT && value.resolvedType != Skew.Type.DYNAMIC) { switch (value.kind) { case Skew.NodeKind.NOT: case Skew.NodeKind.EQUAL: case Skew.NodeKind.NOT_EQUAL: case Skew.NodeKind.LOGICAL_OR: case Skew.NodeKind.LOGICAL_AND: case Skew.NodeKind.LESS_THAN: case Skew.NodeKind.GREATER_THAN: case Skew.NodeKind.LESS_THAN_OR_EQUAL: case Skew.NodeKind.GREATER_THAN_OR_EQUAL: { value.invertBooleanCondition(this._cache); node.become(value.remove()); break; } } } }; Skew.Folding.ConstantFolder.prototype._foldConstantIntegerAddOrSubtract = function(node, variable, constant, delta) { var isAdd = node.kind == Skew.NodeKind.ADD; var needsContentUpdate = delta != 0; var isRightConstant = constant == node.binaryRight(); var shouldNegateConstant = !isAdd && isRightConstant; var value = constant.asInt(); // Make this an add for simplicity if (shouldNegateConstant) { value = -value | 0; } // Include the delta from the parent node if present value = value + delta | 0; // 0 + a => a // 0 - a => -a // a + 0 => a // a - 0 => a if (value == 0) { node.become(isAdd || isRightConstant ? variable.remove() : Skew.Node.createUnary(Skew.NodeKind.NEGATIVE, variable.remove()).withType(node.resolvedType)); return; } // Check for nested addition or subtraction if (variable.kind == Skew.NodeKind.ADD || variable.kind == Skew.NodeKind.SUBTRACT) { var left = variable.binaryLeft(); var right = variable.binaryRight(); assert(left.resolvedType == this._cache.intType || left.resolvedType == Skew.Type.DYNAMIC); assert(right.resolvedType == this._cache.intType || right.resolvedType == Skew.Type.DYNAMIC); // (a + 1) + 2 => a + 3 var isLeftConstant = left.isInt(); if (isLeftConstant || right.isInt()) { this._foldConstantIntegerAddOrSubtract(variable, isLeftConstant ? right : left, isLeftConstant ? left : right, value); node.become(variable.remove()); return; } } // Adjust the value so it has the correct sign if (shouldNegateConstant) { value = -value | 0; } // The negative sign can often be removed by code transformation if (value < 0) { // a + -1 => a - 1 // a - -1 => a + 1 if (isRightConstant) { node.kind = isAdd ? Skew.NodeKind.SUBTRACT : Skew.NodeKind.ADD; value = -value | 0; needsContentUpdate = true; } // -1 + a => a - 1 else if (isAdd) { node.kind = Skew.NodeKind.SUBTRACT; value = -value | 0; variable.swapWith(constant); needsContentUpdate = true; } } // Avoid extra allocations if (needsContentUpdate) { constant.content = new Skew.IntContent(value); } // Also handle unary negation on "variable" this._foldAddOrSubtract(node); }; Skew.Folding.ConstantFolder.prototype._foldAddOrSubtract = function(node) { var isAdd = node.kind == Skew.NodeKind.ADD; var left = node.binaryLeft(); var right = node.binaryRight(); // -a + b => b - a if (left.kind == Skew.NodeKind.NEGATIVE && isAdd) { left.become(left.unaryValue().remove()); left.swapWith(right); node.kind = Skew.NodeKind.SUBTRACT; } // a + -b => a - b // a - -b => a + b else if (right.kind == Skew.NodeKind.NEGATIVE) { right.become(right.unaryValue().remove()); node.kind = isAdd ? Skew.NodeKind.SUBTRACT : Skew.NodeKind.ADD; } // 0 + a => a // 0 - a => -a else if (left.isZero()) { node.become(isAdd ? right.remove() : Skew.Node.createUnary(Skew.NodeKind.NEGATIVE, right.remove()).withType(node.resolvedType)); } // a + 0 => a // a - 0 => a else if (right.isZero()) { node.become(left.remove()); } }; Skew.Folding.ConstantFolder.prototype._foldConstantIntegerMultiply = function(node, variable, constant) { assert(constant.isInt()); // Apply identities var variableIsInt = variable.resolvedType == this._cache.intType; var value = constant.asInt(); // Replacing values with 0 only works for integers. Doubles can be NaN and // NaN times anything is NaN, zero included. if (value == 0 && variableIsInt) { if (variable.hasNoSideEffects()) { node.become(constant.remove()); } return; } // This identity works even with NaN if (value == 1) { node.become(variable.remove()); return; } // Multiply by a power of 2 should be a left-shift operation, which is // more concise and always faster (or at least never slower) than the // alternative. Division can't be replaced by a right-shift operation // because that would lead to incorrect results for negative numbers. if (variableIsInt) { var shift = Skew.Folding.ConstantFolder._logBase2(value); if (shift != -1) { // "x * 2 * 4" => "x << 3" if (variable.kind == Skew.NodeKind.SHIFT_LEFT && variable.binaryRight().isInt()) { shift = shift + variable.binaryRight().asInt() | 0; variable.replaceWith(variable.binaryLeft().remove()); } constant.content = new Skew.IntContent(shift); node.kind = Skew.NodeKind.SHIFT_LEFT; } } }; // "((a >> 8) & 255) << 8" => "a & (255 << 8)" // "((a >>> 8) & 255) << 8" => "a & (255 << 8)" // "((a >> 7) & 255) << 8" => "(a << 1) & (255 << 8)" // "((a >>> 7) & 255) << 8" => "(a << 1) & (255 << 8)" // "((a >> 8) & 255) << 7" => "(a >> 1) & (255 << 7)" // "((a >>> 8) & 255) << 7" => "(a >>> 1) & (255 << 7)" Skew.Folding.ConstantFolder.prototype._foldConstantBitwiseAndInsideShift = function(node, andLeft, andRight) { assert(node.kind == Skew.NodeKind.SHIFT_LEFT && node.binaryRight().isInt()); if (andRight.isInt() && (andLeft.kind == Skew.NodeKind.SHIFT_RIGHT || andLeft.kind == Skew.NodeKind.UNSIGNED_SHIFT_RIGHT) && andLeft.binaryRight().isInt()) { var mask = andRight.asInt(); var leftShift = node.binaryRight().asInt(); var rightShift = andLeft.binaryRight().asInt(); var value = andLeft.binaryLeft().remove(); if (leftShift < rightShift) { value = Skew.Node.createBinary(andLeft.kind, value, this._cache.createInt(rightShift - leftShift | 0)).withType(this._cache.intType); } else if (leftShift > rightShift) { value = Skew.Node.createBinary(Skew.NodeKind.SHIFT_LEFT, value, this._cache.createInt(leftShift - rightShift | 0)).withType(this._cache.intType); } node.become(Skew.Node.createBinary(Skew.NodeKind.BITWISE_AND, value, this._cache.createInt(mask << leftShift)).withType(node.resolvedType)); } }; Skew.Folding.ConstantFolder.prototype._foldConstantBitwiseAndInsideBitwiseOr = function(node) { assert(node.kind == Skew.NodeKind.BITWISE_OR && node.binaryLeft().kind == Skew.NodeKind.BITWISE_AND); var left = node.binaryLeft(); var right = node.binaryRight(); var leftLeft = left.binaryLeft(); var leftRight = left.binaryRight(); // "(a & b) | (a & c)" => "a & (b | c)" if (right.kind == Skew.NodeKind.BITWISE_AND) { var rightLeft = right.binaryLeft(); var rightRight = right.binaryRight(); if (leftRight.isInt() && rightRight.isInt() && Skew.Folding.ConstantFolder._isSameVariableReference(leftLeft, rightLeft)) { var mask = leftRight.asInt() | rightRight.asInt(); node.become(Skew.Node.createBinary(Skew.NodeKind.BITWISE_AND, leftLeft.remove(), this._cache.createInt(mask)).withType(node.resolvedType)); } } // "(a & b) | c" => "a | c" when "(a | b) == ~0" else if (right.isInt() && leftRight.isInt() && (leftRight.asInt() | right.asInt()) == ~0) { left.become(leftLeft.remove()); } }; Skew.Folding.ConstantFolder.prototype._foldBinaryWithConstant = function(node, left, right) { // There are lots of other folding opportunities for most binary operators // here but those usually have a negligible performance and/or size impact // on the generated code and instead slow the compiler down. Only certain // ones are implemented below. switch (node.kind) { // These are important for dead code elimination case Skew.NodeKind.LOGICAL_AND: { if (left.isFalse() || right.isTrue()) { node.become(left.remove()); } else if (left.isTrue()) { node.become(right.remove()); } break; } case Skew.NodeKind.LOGICAL_OR: { if (left.isTrue() || right.isFalse()) { node.become(left.remove()); } else if (left.isFalse()) { node.become(right.remove()); } break; } // Constants are often added up in compound expressions. Folding // addition/subtraction improves minification in JavaScript and often // helps with readability. case Skew.NodeKind.ADD: case Skew.NodeKind.SUBTRACT: { if (left.isInt()) { this._foldConstantIntegerAddOrSubtract(node, right, left, 0); } else if (right.isInt()) { this._foldConstantIntegerAddOrSubtract(node, left, right, 0); } else { this._foldAddOrSubtract(node); } break; } // Multiplication is special-cased here because in JavaScript, optimizing // away the general-purpose Math.imul function may result in large // speedups when it's implemented with a polyfill. case Skew.NodeKind.MULTIPLY: { if (right.isInt()) { this._foldConstantIntegerMultiply(node, left, right); } break; } // This improves generated code for inlined bit packing functions case Skew.NodeKind.SHIFT_LEFT: case Skew.NodeKind.SHIFT_RIGHT: case Skew.NodeKind.UNSIGNED_SHIFT_RIGHT: { // "x << 0" => "x" // "x >> 0" => "x" // "x >>> 0" => "x" if (this._cache.isEquivalentToInt(left.resolvedType) && right.isInt() && right.asInt() == 0) { node.become(left.remove()); } // Handle special cases of "&" nested inside "<<" else if (node.kind == Skew.NodeKind.SHIFT_LEFT && left.kind == Skew.NodeKind.BITWISE_AND && right.isInt()) { this._foldConstantBitwiseAndInsideShift(node, left.binaryLeft(), left.binaryRight()); } // "x << 1 << 2" => "x << 3" // "x >> 1 >> 2" => "x >> 3" // "x >>> 1 >>> 2" => "x >>> 3" else if (node.kind == left.kind && left.binaryRight().isInt() && right.isInt()) { this._flattenInt(right, left.binaryRight().asInt() + right.asInt() | 0); left.replaceWith(left.binaryLeft().remove()); } break; } case Skew.NodeKind.BITWISE_AND: { if (right.isInt() && this._cache.isEquivalentToInt(left.resolvedType)) { var value = right.asInt(); // "x & ~0" => "x" if (value == ~0) { node.become(left.remove()); } // "x & 0" => "0" else if (value == 0 && left.hasNoSideEffects()) { node.become(right.remove()); } } break; } case Skew.NodeKind.BITWISE_OR: { if (right.isInt() && this._cache.isEquivalentToInt(left.resolvedType)) { var value1 = right.asInt(); // "x | 0" => "x" if (value1 == 0) { node.become(left.remove()); return; } // "x | ~0" => "~0" else if (value1 == ~0 && left.hasNoSideEffects()) { node.become(right.remove()); return; } } if (left.kind == Skew.NodeKind.BITWISE_AND) { this._foldConstantBitwiseAndInsideBitwiseOr(node); } break; } } }; Skew.Folding.ConstantFolder.prototype._foldBinary = function(node) { var kind = node.kind; if (kind == Skew.NodeKind.ADD && node.resolvedType == this._cache.stringType) { this._foldStringConcatenation(node); return; } var left = node.binaryLeft(); var right = node.binaryRight(); // Canonicalize the order of commutative operators if ((kind == Skew.NodeKind.MULTIPLY || kind == Skew.NodeKind.BITWISE_AND || kind == Skew.NodeKind.BITWISE_OR) && left.kind == Skew.NodeKind.CONSTANT && right.kind != Skew.NodeKind.CONSTANT) { var temp = left; left = right; right = temp; left.swapWith(right); } if (left.kind == Skew.NodeKind.CONSTANT && right.kind == Skew.NodeKind.CONSTANT) { var leftContent = left.content; var rightContent = right.content; var leftKind = leftContent.kind(); var rightKind = rightContent.kind(); // Fold equality operators if (leftKind == Skew.ContentKind.STRING && rightKind == Skew.ContentKind.STRING) { switch (kind) { case Skew.NodeKind.EQUAL: { this._flattenBool(node, Skew.in_Content.asString(leftContent) == Skew.in_Content.asString(rightContent)); break; } case Skew.NodeKind.NOT_EQUAL: { this._flattenBool(node, Skew.in_Content.asString(leftContent) != Skew.in_Content.asString(rightContent)); break; } case Skew.NodeKind.LESS_THAN: { this._flattenBool(node, in_string.compare(Skew.in_Content.asString(leftContent), Skew.in_Content.asString(rightContent)) < 0); break; } case Skew.NodeKind.GREATER_THAN: { this._flattenBool(node, in_string.compare(Skew.in_Content.asString(leftContent), Skew.in_Content.asString(rightContent)) > 0); break; } case Skew.NodeKind.LESS_THAN_OR_EQUAL: { this._flattenBool(node, in_string.compare(Skew.in_Content.asString(leftContent), Skew.in_Content.asString(rightContent)) <= 0); break; } case Skew.NodeKind.GREATER_THAN_OR_EQUAL: { this._flattenBool(node, in_string.compare(Skew.in_Content.asString(leftContent), Skew.in_Content.asString(rightContent)) >= 0); break; } } return; } // Fold "bool" values else if (leftKind == Skew.ContentKind.BOOL && rightKind == Skew.ContentKind.BOOL) { switch (kind) { case Skew.NodeKind.LOGICAL_AND: { this._flattenBool(node, Skew.in_Content.asBool(leftContent) && Skew.in_Content.asBool(rightContent)); break; } case Skew.NodeKind.LOGICAL_OR: { this._flattenBool(node, Skew.in_Content.asBool(leftContent) || Skew.in_Content.asBool(rightContent)); break; } case Skew.NodeKind.EQUAL: { this._flattenBool(node, Skew.in_Content.asBool(leftContent) == Skew.in_Content.asBool(rightContent)); break; } case Skew.NodeKind.NOT_EQUAL: { this._flattenBool(node, Skew.in_Content.asBool(leftContent) != Skew.in_Content.asBool(rightContent)); break; } } return; } // Fold "int" values else if (leftKind == Skew.ContentKind.INT && rightKind == Skew.ContentKind.INT) { switch (kind) { case Skew.NodeKind.ADD: { this._flattenInt(node, Skew.in_Content.asInt(leftContent) + Skew.in_Content.asInt(rightContent) | 0); break; } case Skew.NodeKind.BITWISE_AND: { this._flattenInt(node, Skew.in_Content.asInt(leftContent) & Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.BITWISE_OR: { this._flattenInt(node, Skew.in_Content.asInt(leftContent) | Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.BITWISE_XOR: { this._flattenInt(node, Skew.in_Content.asInt(leftContent) ^ Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.DIVIDE: { this._flattenInt(node, Skew.in_Content.asInt(leftContent) / Skew.in_Content.asInt(rightContent) | 0); break; } case Skew.NodeKind.EQUAL: { this._flattenBool(node, Skew.in_Content.asInt(leftContent) == Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.GREATER_THAN: { this._flattenBool(node, Skew.in_Content.asInt(leftContent) > Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.GREATER_THAN_OR_EQUAL: { this._flattenBool(node, Skew.in_Content.asInt(leftContent) >= Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.LESS_THAN: { this._flattenBool(node, Skew.in_Content.asInt(leftContent) < Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.LESS_THAN_OR_EQUAL: { this._flattenBool(node, Skew.in_Content.asInt(leftContent) <= Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.MULTIPLY: { this._flattenInt(node, __imul(Skew.in_Content.asInt(leftContent), Skew.in_Content.asInt(rightContent))); break; } case Skew.NodeKind.NOT_EQUAL: { this._flattenBool(node, Skew.in_Content.asInt(leftContent) != Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.REMAINDER: { this._flattenInt(node, Skew.in_Content.asInt(leftContent) % Skew.in_Content.asInt(rightContent) | 0); break; } case Skew.NodeKind.SHIFT_LEFT: { this._flattenInt(node, Skew.in_Content.asInt(leftContent) << Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.SHIFT_RIGHT: { this._flattenInt(node, Skew.in_Content.asInt(leftContent) >> Skew.in_Content.asInt(rightContent)); break; } case Skew.NodeKind.SUBTRACT: { this._flattenInt(node, Skew.in_Content.asInt(leftContent) - Skew.in_Content.asInt(rightContent) | 0); break; } case Skew.NodeKind.UNSIGNED_SHIFT_RIGHT: { this._flattenInt(node, Skew.in_Content.asInt(leftContent) >>> Skew.in_Content.asInt(rightContent) | 0); break; } } return; } // Fold "double" values else if (leftKind == Skew.ContentKind.DOUBLE && rightKind == Skew.ContentKind.DOUBLE) { switch (kind) { case Skew.NodeKind.ADD: { this._flattenDouble(node, Skew.in_Content.asDouble(leftContent) + Skew.in_Content.asDouble(rightContent)); break; } case Skew.NodeKind.SUBTRACT: { this._flattenDouble(node, Skew.in_Content.asDouble(leftContent) - Skew.in_Content.asDouble(rightContent)); break; } case Skew.NodeKind.MULTIPLY: { this._flattenDouble(node, Skew.in_Content.asDouble(leftContent) * Skew.in_Content.asDouble(rightContent)); break; } case Skew.NodeKind.DIVIDE: { this._flattenDouble(node, Skew.in_Content.asDouble(leftContent) / Skew.in_Content.asDouble(rightContent)); break; } case Skew.NodeKind.EQUAL: { this._flattenBool(node, Skew.in_Content.asDouble(leftContent) == Skew.in_Content.asDouble(rightContent)); break; } case Skew.NodeKind.NOT_EQUAL: { this._flattenBool(node, Skew.in_Content.asDouble(leftContent) != Skew.in_Content.asDouble(rightContent)); break; } case Skew.NodeKind.LESS_THAN: { this._flattenBool(node, Skew.in_Content.asDouble(leftContent) < Skew.in_Content.asDouble(rightContent)); break; } case Skew.NodeKind.GREATER_THAN: { this._flattenBool(node, Skew.in_Content.asDouble(leftContent) > Skew.in_Content.asDouble(rightContent)); break; } case Skew.NodeKind.LESS_THAN_OR_EQUAL: { this._flattenBool(node, Skew.in_Content.asDouble(leftContent) <= Skew.in_Content.asDouble(rightContent)); break; } case Skew.NodeKind.GREATER_THAN_OR_EQUAL: { this._flattenBool(node, Skew.in_Content.asDouble(leftContent) >= Skew.in_Content.asDouble(rightContent)); break; } } return; } } this._foldBinaryWithConstant(node, left, right); }; Skew.Folding.ConstantFolder.prototype._foldHook = function(node) { var test = node.hookTest(); if (test.isTrue()) { node.become(node.hookTrue().remove()); } else if (test.isFalse()) { node.become(node.hookFalse().remove()); } }; Skew.Folding.ConstantFolder.prototype.constantForSymbol = function(symbol) { if (this._constantCache.has(symbol.id)) { return in_IntMap.get1(this._constantCache, symbol.id); } if (this._prepareSymbol != null) { this._prepareSymbol(symbol); } var constant = null; var value = symbol.value; if (symbol.isConst() && value != null) { in_IntMap.set(this._constantCache, symbol.id, null); value = value.clone(); this.foldConstants(value); if (value.kind == Skew.NodeKind.CONSTANT) { constant = value.content; } } in_IntMap.set(this._constantCache, symbol.id, constant); return constant; }; Skew.Folding.ConstantFolder._isVariableReference = function(node) { return node.kind == Skew.NodeKind.NAME && node.symbol != null && Skew.in_SymbolKind.isVariable(node.symbol.kind); }; Skew.Folding.ConstantFolder._isSameVariableReference = function(a, b) { return Skew.Folding.ConstantFolder._isVariableReference(a) && Skew.Folding.ConstantFolder._isVariableReference(b) && a.symbol == b.symbol || a.kind == Skew.NodeKind.CAST && b.kind == Skew.NodeKind.CAST && Skew.Folding.ConstantFolder._isSameVariableReference(a.castValue(), b.castValue()); }; Skew.Folding.ConstantFolder._hasNestedReference = function(node, symbol) { assert(symbol != null); if (node.symbol == symbol) { return true; } for (var child = node.firstChild(); child != null; child = child.nextSibling()) { if (Skew.Folding.ConstantFolder._hasNestedReference(child, symbol)) { return true; } } return false; }; Skew.Folding.ConstantFolder._shouldFoldSymbol = function(symbol) { return symbol != null && symbol.isConst() && (symbol.kind != Skew.SymbolKind.VARIABLE_INSTANCE || symbol.isImported()); }; Skew.Folding.ConstantFolder._isKnownCall = function(symbol, knownSymbol) { return symbol == knownSymbol || symbol != null && Skew.in_SymbolKind.isFunction(symbol.kind) && (symbol.asFunctionSymbol().overloaded == knownSymbol || Skew.in_SymbolKind.isFunction(knownSymbol.kind) && symbol.asFunctionSymbol().overloaded != null && symbol.asFunctionSymbol().overloaded == knownSymbol.asFunctionSymbol().overloaded && symbol.asFunctionSymbol().argumentOnlyType == knownSymbol.asFunctionSymbol().argumentOnlyType); }; // Returns the log2(value) or -1 if log2(value) is not an integer Skew.Folding.ConstantFolder._logBase2 = function(value) { if (value < 1 || (value & value - 1) != 0) { return -1; } var result = 0; while (value > 1) { value >>= 1; result = result + 1 | 0; } return result; }; Skew.MotionPass = function() { Skew.Pass.call(this); }; __extends(Skew.MotionPass, Skew.Pass); Skew.MotionPass.prototype.kind = function() { return Skew.PassKind.MOTION; }; Skew.MotionPass.prototype.run = function(context) { var motionContext = new Skew.Motion.Context(); Skew.Motion.symbolMotion(context.global, context.options, motionContext); motionContext.finish(); }; Skew.Motion = {}; Skew.Motion.symbolMotion = function(symbol, options, context) { // Move non-imported objects off imported objects in_List.removeIf(symbol.objects, function(object) { Skew.Motion.symbolMotion(object, options, context); if (symbol.isImported() && !object.isImported() || !options.target.supportsNestedTypes() && !Skew.in_SymbolKind.isNamespaceOrGlobal(symbol.kind)) { context.moveSymbolIntoNewNamespace(object); return true; } return false; }); // Move global functions with implementations off of imported objects and interfaces in_List.removeIf(symbol.functions, function($function) { if ($function.kind == Skew.SymbolKind.FUNCTION_GLOBAL && (symbol.isImported() && !$function.isImported() || symbol.kind == Skew.SymbolKind.OBJECT_INTERFACE)) { context.moveSymbolIntoNewNamespace($function); return true; } return false; }); // Move stuff off of enums and flags if (Skew.in_SymbolKind.isEnumOrFlags(symbol.kind)) { symbol.objects.forEach(function(object) { context.moveSymbolIntoNewNamespace(object); }); symbol.functions.forEach(function($function) { context.moveSymbolIntoNewNamespace($function); }); in_List.removeIf(symbol.variables, function(variable) { if (variable.kind != Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS) { context.moveSymbolIntoNewNamespace(variable); return true; } return false; }); symbol.objects = []; symbol.functions = []; } // Move variables off of interfaces else if (symbol.kind == Skew.SymbolKind.OBJECT_INTERFACE) { symbol.variables.forEach(function(variable) { context.moveSymbolIntoNewNamespace(variable); }); symbol.variables = []; } }; Skew.Motion.Context = function() { this._namespaces = new Map(); }; // Avoid mutation during iteration Skew.Motion.Context.prototype.finish = function() { var values = Array.from(this._namespaces.values()); // Sort so the order is deterministic values.sort(Skew.Symbol.SORT_OBJECTS_BY_ID); for (var i = 0, list = values, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); object.parent.asObjectSymbol().objects.push(object); } }; Skew.Motion.Context.prototype.moveSymbolIntoNewNamespace = function(symbol) { var parent = symbol.parent; var namespace = in_IntMap.get(this._namespaces, parent.id, null); var object = namespace != null ? namespace.asObjectSymbol() : null; // Create a parallel namespace next to the parent if (namespace == null) { var common = parent.parent.asObjectSymbol(); var name = 'in_' + parent.name; var candidate = in_StringMap.get(common.members, name, null); if (candidate != null && candidate.kind == Skew.SymbolKind.OBJECT_NAMESPACE) { object = candidate.asObjectSymbol(); } else { object = new Skew.ObjectSymbol(Skew.SymbolKind.OBJECT_NAMESPACE, common.scope.generateName(name)); object.range = parent.range; object.resolvedType = new Skew.Type(Skew.TypeKind.SYMBOL, object); object.state = Skew.SymbolState.INITIALIZED; object.scope = new Skew.ObjectScope(common.scope, object); object.parent = common; in_StringMap.set(common.members, name, object); in_IntMap.set(this._namespaces, parent.id, object); } } // Move this function into that parallel namespace symbol.parent = object; if (Skew.in_SymbolKind.isObject(symbol.kind)) { object.objects.push(symbol.asObjectSymbol()); } else if (Skew.in_SymbolKind.isFunction(symbol.kind)) { object.functions.push(symbol.asFunctionSymbol()); // Inflate functions with type parameters from the parent (TODO: Need to inflate call sites too) if (parent.asObjectSymbol().parameters != null) { var $function = symbol.asFunctionSymbol(); if ($function.parameters == null) { $function.parameters = []; } in_List.prepend1($function.parameters, parent.asObjectSymbol().parameters); } } else if (Skew.in_SymbolKind.isVariable(symbol.kind)) { object.variables.push(symbol.asVariableSymbol()); } }; Skew.GlobalizingPass = function() { Skew.Pass.call(this); }; __extends(Skew.GlobalizingPass, Skew.Pass); Skew.GlobalizingPass.prototype.kind = function() { return Skew.PassKind.GLOBALIZING; }; Skew.GlobalizingPass.prototype.run = function(context) { var globalizeAllFunctions = context.options.globalizeAllFunctions; var virtualLookup = globalizeAllFunctions || context.options.isAlwaysInlinePresent ? new Skew.VirtualLookup(context.global) : null; var motionContext = new Skew.Motion.Context(); for (var i1 = 0, list1 = context.callGraph.callInfo, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var info = in_List.get(list1, i1); var symbol = info.symbol; // Turn certain instance functions into global functions if (symbol.kind == Skew.SymbolKind.FUNCTION_INSTANCE && (Skew.in_SymbolKind.isEnumOrFlags(symbol.parent.kind) || symbol.parent.kind == Skew.SymbolKind.OBJECT_WRAPPED || symbol.parent.kind == Skew.SymbolKind.OBJECT_INTERFACE && symbol.block != null || symbol.parent.isImported() && !symbol.isImported() || (globalizeAllFunctions || symbol.isInliningForced()) && !symbol.isImportedOrExported() && !virtualLookup.isVirtual(symbol))) { var $function = symbol.asFunctionSymbol(); $function.kind = Skew.SymbolKind.FUNCTION_GLOBAL; $function.$arguments.unshift($function.$this); $function.resolvedType.argumentTypes.unshift($function.$this.resolvedType); $function.$this = null; // The globalized function needs instance type parameters if ($function.parent.asObjectSymbol().parameters != null) { in_List.removeOne($function.parent.asObjectSymbol().functions, $function); motionContext.moveSymbolIntoNewNamespace($function); } // Update all call sites for (var i = 0, list = info.callSites, count = list.length; i < count; i = i + 1 | 0) { var callSite = in_List.get(list, i); var value = callSite.callNode.callValue(); // Rewrite "super(foo)" to "bar(self, foo)" if (value.kind == Skew.NodeKind.SUPER) { var $this = callSite.enclosingSymbol.asFunctionSymbol().$this; value.replaceWith(Skew.Node.createSymbolReference($this)); } // Rewrite "self.foo(bar)" to "foo(self, bar)" else { value.replaceWith((value.kind == Skew.NodeKind.PARAMETERIZE ? value.parameterizeValue() : value).dotTarget().remove()); } callSite.callNode.prependChild(Skew.Node.createSymbolReference($function)); } } } motionContext.finish(); }; Skew.VirtualLookup = function(global) { this._map = new Map(); this._visitObject(global); }; Skew.VirtualLookup.prototype.isVirtual = function(symbol) { return this._map.has(symbol.id); }; Skew.VirtualLookup.prototype._visitObject = function(symbol) { for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._visitObject(object); } for (var i2 = 0, list2 = symbol.functions, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var $function = in_List.get(list2, i2); if ($function.overridden != null) { in_IntMap.set(this._map, $function.overridden.id, 0); in_IntMap.set(this._map, $function.id, 0); } if (symbol.kind == Skew.SymbolKind.OBJECT_INTERFACE && $function.kind == Skew.SymbolKind.FUNCTION_INSTANCE && $function.forwardTo == null) { if ($function.implementations != null) { for (var i1 = 0, list1 = $function.implementations, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var implementation = in_List.get(list1, i1); in_IntMap.set(this._map, implementation.id, 0); } } in_IntMap.set(this._map, $function.id, 0); } } }; Skew.MergingPass = function() { Skew.Pass.call(this); }; __extends(Skew.MergingPass, Skew.Pass); Skew.MergingPass.prototype.kind = function() { return Skew.PassKind.MERGING; }; Skew.MergingPass.prototype.run = function(context) { Skew.Merging.mergeObject(context.log, null, context.global, context.global); }; Skew.Merging = {}; Skew.Merging.mergeObject = function(log, parent, target, symbol) { target.scope = new Skew.ObjectScope(parent != null ? parent.scope : null, target); symbol.scope = target.scope; symbol.parent = parent; // This is a heuristic for getting the range to be from the primary definition if (symbol.kind == target.kind && symbol.variables.length > target.variables.length) { target.range = symbol.range; } if (symbol.parameters != null) { for (var i = 0, list = symbol.parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); parameter.scope = parent.scope; parameter.parent = target; // Type parameters cannot merge with any members var other = in_StringMap.get(target.members, parameter.name, null); if (other != null) { log.semanticErrorDuplicateSymbol(parameter.range, parameter.name, other.range); continue; } in_StringMap.set(target.members, parameter.name, parameter); } } Skew.Merging.mergeObjects(log, target, symbol.objects); Skew.Merging.mergeFunctions(log, target, symbol.functions, Skew.Merging.MergeBehavior.NORMAL); Skew.Merging.mergeVariables(log, target, symbol.variables); }; Skew.Merging.mergeObjects = function(log, parent, children) { var members = parent.members; in_List.removeIf(children, function(child) { var other = in_StringMap.get(members, child.name, null); // Simple case: no merging if (other == null) { in_StringMap.set(members, child.name, child); Skew.Merging.mergeObject(log, parent, child, child); return false; } // Can only merge with another of the same kind or with a namespace if (other.kind == Skew.SymbolKind.OBJECT_NAMESPACE) { var swap = other.range; other.range = child.range; child.range = swap; other.kind = child.kind; } else if (child.kind == Skew.SymbolKind.OBJECT_NAMESPACE) { child.kind = other.kind; } else if (child.kind != other.kind) { log.semanticErrorDuplicateSymbol(child.range, child.name, other.range); return true; } // Classes can only have one base type var object = other.asObjectSymbol(); if (child.$extends != null) { if (object.$extends != null) { log.semanticErrorDuplicateBaseType(child.$extends.range, child.name, object.$extends.range); return true; } object.$extends = child.$extends; } // Merge base interfaces if (child.$implements != null) { if (object.$implements != null) { in_List.append1(object.$implements, child.$implements); } else { object.$implements = child.$implements; } } // Cannot merge two objects that both have type parameters if (child.parameters != null && object.parameters != null) { log.semanticErrorDuplicateTypeParameters(Skew.Merging.rangeOfParameters(child.parameters), child.name, Skew.Merging.rangeOfParameters(object.parameters)); return true; } // Merge "child" into "other" Skew.Merging.mergeObject(log, parent, object, child); object.mergeInformationFrom(child); in_List.append1(object.objects, child.objects); in_List.append1(object.functions, child.functions); in_List.append1(object.variables, child.variables); if (child.parameters != null) { object.parameters = child.parameters; } if (child.guards != null) { if (object.guards == null) { object.guards = []; } for (var i = 0, list = child.guards, count = list.length; i < count; i = i + 1 | 0) { var guard = in_List.get(list, i); for (var g = guard; g != null; g = g.elseGuard) { g.parent = object; g.contents.parent = object; } object.guards.push(guard); } } return true; }); }; Skew.Merging.mergeFunctions = function(log, parent, children, behavior) { var members = parent.members; for (var i1 = 0, list1 = children, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var child = in_List.get(list1, i1); var other = in_StringMap.get(members, child.name, null); // Create a scope for this function's type parameters if (behavior == Skew.Merging.MergeBehavior.NORMAL) { var scope = new Skew.FunctionScope(parent.scope, child); child.scope = scope; child.parent = parent; if (child.parameters != null) { for (var i = 0, list = child.parameters, count = list.length; i < count; i = i + 1 | 0) { var parameter = in_List.get(list, i); parameter.scope = scope; parameter.parent = child; // Type parameters cannot merge with other parameters on this function var previous = in_StringMap.get(scope.parameters, parameter.name, null); if (previous != null) { log.semanticErrorDuplicateSymbol(parameter.range, parameter.name, previous.range); continue; } in_StringMap.set(scope.parameters, parameter.name, parameter); } } } // Simple case: no merging if (other == null) { in_StringMap.set(members, child.name, child); continue; } var childKind = Skew.Merging.overloadedKind(child.kind); var otherKind = Skew.Merging.overloadedKind(other.kind); // Merge with another symbol of the same overloaded group type if (childKind != otherKind || !Skew.in_SymbolKind.isOverloadedFunction(childKind)) { if (behavior == Skew.Merging.MergeBehavior.NORMAL) { log.semanticErrorDuplicateSymbol(child.range, child.name, other.range); } else { log.semanticErrorBadOverride(other.range, other.name, parent.baseType, child.range); } continue; } // Merge with a group of overloaded functions if (Skew.in_SymbolKind.isOverloadedFunction(other.kind)) { other.asOverloadedFunctionSymbol().symbols.push(child); if (behavior == Skew.Merging.MergeBehavior.NORMAL) { child.overloaded = other.asOverloadedFunctionSymbol(); } continue; } // Create an overload group var overloaded = new Skew.OverloadedFunctionSymbol(childKind, child.name, [other.asFunctionSymbol(), child]); in_StringMap.set(members, child.name, overloaded); other.asFunctionSymbol().overloaded = overloaded; if (behavior == Skew.Merging.MergeBehavior.NORMAL) { child.overloaded = overloaded; } overloaded.scope = parent.scope; overloaded.parent = parent; } }; Skew.Merging.overloadedKind = function(kind) { return kind == Skew.SymbolKind.FUNCTION_CONSTRUCTOR || kind == Skew.SymbolKind.FUNCTION_GLOBAL ? Skew.SymbolKind.OVERLOADED_GLOBAL : kind == Skew.SymbolKind.FUNCTION_ANNOTATION ? Skew.SymbolKind.OVERLOADED_ANNOTATION : kind == Skew.SymbolKind.FUNCTION_INSTANCE ? Skew.SymbolKind.OVERLOADED_INSTANCE : kind; }; Skew.Merging.mergeVariables = function(log, parent, children) { var members = parent.members; for (var i = 0, list = children, count = list.length; i < count; i = i + 1 | 0) { var child = in_List.get(list, i); var other = in_StringMap.get(members, child.name, null); child.scope = new Skew.VariableScope(parent.scope, child); child.parent = parent; // Variables never merge if (other != null) { log.semanticErrorDuplicateSymbol(child.range, child.name, other.range); continue; } in_StringMap.set(members, child.name, child); } }; Skew.Merging.rangeOfParameters = function(parameters) { return Skew.Range.span(in_List.first(parameters).range, in_List.last(parameters).range); }; Skew.Merging.MergeBehavior = { NORMAL: 0, INTO_DERIVED_CLASS: 1 }; Skew.InterfaceRemovalPass = function() { Skew.Pass.call(this); this._interfaceImplementations = new Map(); this._interfaces = []; }; __extends(Skew.InterfaceRemovalPass, Skew.Pass); Skew.InterfaceRemovalPass.prototype.kind = function() { return Skew.PassKind.INTERFACE_REMOVAL; }; Skew.InterfaceRemovalPass.prototype.run = function(context) { this._scanForInterfaces(context.global); for (var i2 = 0, list2 = this._interfaces, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var symbol = in_List.get(list2, i2); if (symbol.isImportedOrExported()) { continue; } var implementations = in_IntMap.get(this._interfaceImplementations, symbol.id, null); if (implementations == null || implementations.length == 1) { symbol.kind = Skew.SymbolKind.OBJECT_NAMESPACE; // Remove this interface from its implementation if (implementations != null) { var object = in_List.first(implementations); for (var i = 0, list = object.interfaceTypes, count = list.length; i < count; i = i + 1 | 0) { var type = in_List.get(list, i); if (type.symbol == symbol) { in_List.removeOne(object.interfaceTypes, type); break; } } // Mark these symbols as forwarded, which is used by the globalization // pass and the JavaScript emitter to ignore this interface for (var i1 = 0, list1 = symbol.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); if ($function.implementations != null) { $function.forwardTo = in_List.first($function.implementations); } } symbol.forwardTo = object; } } } }; Skew.InterfaceRemovalPass.prototype._scanForInterfaces = function(symbol) { for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); this._scanForInterfaces(object); } if (symbol.kind == Skew.SymbolKind.OBJECT_INTERFACE) { this._interfaces.push(symbol); } if (symbol.interfaceTypes != null) { for (var i1 = 0, list1 = symbol.interfaceTypes, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var type = in_List.get(list1, i1); var key = type.symbol.id; var implementations = in_IntMap.get(this._interfaceImplementations, key, null); if (implementations == null) { implementations = []; in_IntMap.set(this._interfaceImplementations, key, implementations); } implementations.push(symbol); } } }; Skew.RenamingPass = function() { Skew.Pass.call(this); }; __extends(Skew.RenamingPass, Skew.Pass); Skew.RenamingPass.prototype.kind = function() { return Skew.PassKind.RENAMING; }; Skew.RenamingPass.prototype.run = function(context) { Skew.Renaming.renameGlobal(context.log, context.global); }; Skew.Renaming = {}; Skew.Renaming.renameGlobal = function(log, global) { // Collect all functions var functions = []; Skew.Renaming.collectFunctionAndRenameObjectsAndVariables(global, functions); // Compute naming groups var labels = new Skew.UnionFind().allocate2(functions.length); var groups = []; var firstScopeForObject = new Map(); for (var i = 0, count1 = functions.length; i < count1; i = i + 1 | 0) { in_List.get(functions, i).namingGroup = i; groups.push(null); } for (var i2 = 0, list1 = functions, count3 = list1.length; i2 < count3; i2 = i2 + 1 | 0) { var $function = in_List.get(list1, i2); if ($function.overridden != null) { labels.union($function.namingGroup, $function.overridden.namingGroup); } if ($function.implementations != null) { for (var i1 = 0, list = $function.implementations, count2 = list.length; i1 < count2; i1 = i1 + 1 | 0) { var implementation = in_List.get(list, i1); labels.union($function.namingGroup, implementation.namingGroup); } } } for (var i3 = 0, list2 = functions, count4 = list2.length; i3 < count4; i3 = i3 + 1 | 0) { var function1 = in_List.get(list2, i3); var label = labels.find(function1.namingGroup); var group = in_List.get(groups, label); function1.namingGroup = label; if (group == null) { group = []; in_List.set(groups, label, group); } else { assert(function1.name == in_List.first(group).name); } group.push(function1); // Certain parent objects such as namespaces may have multiple scopes. // However, we want to resolve name collisions in the same scope to detect // collisions across all scopes. Do this by using the first scope. if (!firstScopeForObject.has(function1.parent.id)) { in_IntMap.set(firstScopeForObject, function1.parent.id, function1.scope.parent); } } // Rename stuff for (var i7 = 0, list3 = groups, count8 = list3.length; i7 < count8; i7 = i7 + 1 | 0) { var group1 = in_List.get(list3, i7); if (group1 == null) { continue; } var isImportedOrExported = false; var shouldRename = false; var isInvalid = false; var rename = null; for (var i4 = 0, count5 = group1.length; i4 < count5; i4 = i4 + 1 | 0) { var function2 = in_List.get(group1, i4); if (function2.isImportedOrExported()) { isImportedOrExported = true; } // Make sure there isn't more than one renamed symbol if (function2.rename != null) { if (rename != null && rename != function2.rename) { log.semanticErrorDuplicateRename(function2.range, function2.name, rename, function2.rename); } rename = function2.rename; } // Rename functions with unusual names and make sure overloaded functions have unique names if (!shouldRename) { if (Skew.Renaming.isInvalidIdentifier(function2.name)) { isInvalid = true; shouldRename = true; } else if (function2.overloaded != null && function2.overloaded.symbols.length > 1) { shouldRename = true; } } } // Bake in the rename annotation now if (rename != null) { for (var i5 = 0, count6 = group1.length; i5 < count6; i5 = i5 + 1 | 0) { var function3 = in_List.get(group1, i5); function3.flags |= Skew.SymbolFlags.IS_RENAMED; function3.name = rename; function3.rename = null; } continue; } // One function with a pinned name causes the whole group to avoid renaming if (!shouldRename || isImportedOrExported && !isInvalid) { continue; } var first = in_List.first(group1); var $arguments = first.$arguments.length; var count = 0; var start = first.name; if (($arguments == 0 || $arguments == 1 && first.kind == Skew.SymbolKind.FUNCTION_GLOBAL) && Skew.Renaming.unaryPrefixes.has(start)) { start = in_StringMap.get1(Skew.Renaming.unaryPrefixes, start); } else if (Skew.Renaming.prefixes.has(start)) { start = in_StringMap.get1(Skew.Renaming.prefixes, start); } else { if (start.startsWith('@')) { start = in_string.slice1(start, 1); } if (Skew.Renaming.isInvalidIdentifier(start)) { start = Skew.Renaming.generateValidIdentifier(start); } } // Generate a new name var name = start; while (group1.some(function($function) { return in_IntMap.get1(firstScopeForObject, $function.parent.id).isNameUsed(name); })) { count = count + 1 | 0; name = start + count.toString(); } for (var i6 = 0, count7 = group1.length; i6 < count7; i6 = i6 + 1 | 0) { var function4 = in_List.get(group1, i6); in_IntMap.get1(firstScopeForObject, function4.parent.id).reserveName(name, null); function4.name = name; } } }; Skew.Renaming.collectFunctionAndRenameObjectsAndVariables = function(symbol, functions) { for (var i = 0, list = symbol.objects, count = list.length; i < count; i = i + 1 | 0) { var object = in_List.get(list, i); if (object.rename != null) { object.name = object.rename; object.rename = null; } Skew.Renaming.collectFunctionAndRenameObjectsAndVariables(object, functions); } for (var i1 = 0, list1 = symbol.functions, count1 = list1.length; i1 < count1; i1 = i1 + 1 | 0) { var $function = in_List.get(list1, i1); functions.push($function); } for (var i2 = 0, list2 = symbol.variables, count2 = list2.length; i2 < count2; i2 = i2 + 1 | 0) { var variable = in_List.get(list2, i2); if (variable.rename != null) { variable.name = variable.rename; variable.rename = null; } } }; Skew.Renaming.isAlpha = function(c) { return c >= 97 && c <= 122 || c >= 65 && c <= 90 || c == 95; }; Skew.Renaming.isNumber = function(c) { return c >= 48 && c <= 57; }; Skew.Renaming.isInvalidIdentifier = function(name) { for (var i = 0, count = name.length; i < count; i = i + 1 | 0) { var c = in_string.get1(name, i); if (!Skew.Renaming.isAlpha(c) && (i == 0 || !Skew.Renaming.isNumber(c))) { return true; } } return false; }; Skew.Renaming.generateValidIdentifier = function(name) { var text = ''; for (var i = 0, count = name.length; i < count; i = i + 1 | 0) { var c = in_string.get1(name, i); if (Skew.Renaming.isAlpha(c) || Skew.Renaming.isNumber(c)) { text += in_string.get(name, i); } } if (text != '' && name.endsWith('=')) { return 'set' + Skew.withUppercaseFirstLetter(text); } return text == '' || !Skew.Renaming.isAlpha(in_string.get1(text, 0)) ? '_' + text : text; }; Skew.ScopeKind = { FUNCTION: 0, LOCAL: 1, OBJECT: 2, VARIABLE: 3 }; Skew.ScopeSearch = { NORMAL: 0, ALSO_CHECK_FOR_SETTER: 1 }; Skew.FuzzyScopeSearch = { SELF_ONLY: 0, SELF_AND_PARENTS: 1 }; Skew.Scope = function(parent) { this.parent = parent; this.used = null; this._enclosingFunctionOrLambda = null; this._enclosingFunction = null; this._enclosingLoop = null; }; Skew.Scope.prototype._find = function(name) { return null; }; Skew.Scope.prototype._findWithFuzzyMatching = function(matcher) { }; // Need to check for a setter at the same time as for a normal symbol // because the one in the closer scope must be picked. If both are in // the same scope, pick the setter. Skew.Scope.prototype.find = function(name, search) { var symbol = null; var setterName = search == Skew.ScopeSearch.ALSO_CHECK_FOR_SETTER ? name + '=' : null; for (var scope = this; scope != null && symbol == null; scope = scope.parent) { if (setterName != null) { symbol = scope._find(setterName); } if (symbol == null) { symbol = scope._find(name); } } return symbol; }; Skew.Scope.prototype.findWithFuzzyMatching = function(name, kind, search) { var matcher = new Skew.FuzzySymbolMatcher(name, kind); for (var scope = this; scope != null; scope = scope.parent) { scope._findWithFuzzyMatching(matcher); if (search == Skew.FuzzyScopeSearch.SELF_ONLY) { break; } } return matcher.bestSoFar(); }; Skew.Scope.prototype.asObjectScope = function() { assert(this.kind() == Skew.ScopeKind.OBJECT); return this; }; Skew.Scope.prototype.asFunctionScope = function() { assert(this.kind() == Skew.ScopeKind.FUNCTION); return this; }; Skew.Scope.prototype.asVariableScope = function() { assert(this.kind() == Skew.ScopeKind.VARIABLE); return this; }; Skew.Scope.prototype.asLocalScope = function() { assert(this.kind() == Skew.ScopeKind.LOCAL); return this; }; Skew.Scope.prototype.findEnclosingFunctionOrLambda = function() { if (this._enclosingFunctionOrLambda != null) { return this._enclosingFunctionOrLambda; } var scope = this; while (scope != null) { if (scope.kind() == Skew.ScopeKind.FUNCTION) { this._enclosingFunctionOrLambda = scope.asFunctionScope(); return this._enclosingFunctionOrLambda; } scope = scope.parent; } return null; }; Skew.Scope.prototype.findEnclosingFunction = function() { if (this._enclosingFunction != null) { return this._enclosingFunction; } var scope = this.findEnclosingFunctionOrLambda(); while (scope != null) { if (scope.kind() == Skew.ScopeKind.FUNCTION && scope.asFunctionScope().symbol.kind != Skew.SymbolKind.FUNCTION_LOCAL) { this._enclosingFunction = scope.asFunctionScope(); return this._enclosingFunction; } scope = scope.parent; } return null; }; Skew.Scope.prototype.findEnclosingLoop = function() { if (this._enclosingLoop != null) { return this._enclosingLoop; } var scope = this; while (scope != null && scope.kind() == Skew.ScopeKind.LOCAL) { if (scope.asLocalScope().type == Skew.LocalType.LOOP) { this._enclosingLoop = scope.asLocalScope(); return this._enclosingLoop; } scope = scope.parent; } return null; }; Skew.Scope.prototype.generateName = function(prefix) { var count = 0; var name = prefix; while (this.isNameUsed(name)) { name = prefix + (count = count + 1 | 0).toString(); } this.reserveName(name, null); return name; }; Skew.Scope.prototype.reserveName = function(name, symbol) { if (this.used == null) { this.used = new Map(); } if (!this.used.has(name)) { in_StringMap.set(this.used, name, symbol); } }; Skew.Scope.prototype.isNameUsed = function(name) { if (this.find(name, Skew.ScopeSearch.NORMAL) != null) { return true; } for (var scope = this; scope != null; scope = scope.parent) { if (scope.used != null && scope.used.has(name)) { return true; } } return false; }; Skew.ObjectScope = function(parent, symbol) { Skew.Scope.call(this, parent); this.symbol = symbol; }; __extends(Skew.ObjectScope, Skew.Scope); Skew.ObjectScope.prototype.kind = function() { return Skew.ScopeKind.OBJECT; }; Skew.ObjectScope.prototype._find = function(name) { return in_StringMap.get(this.symbol.members, name, null); }; Skew.ObjectScope.prototype._findWithFuzzyMatching = function(matcher) { in_StringMap.each(this.symbol.members, function(name, member) { matcher.include(member); }); }; Skew.FunctionScope = function(parent, symbol) { Skew.Scope.call(this, parent); this.symbol = symbol; this.parameters = new Map(); }; __extends(Skew.FunctionScope, Skew.Scope); Skew.FunctionScope.prototype.kind = function() { return Skew.ScopeKind.FUNCTION; }; Skew.FunctionScope.prototype._find = function(name) { return in_StringMap.get(this.parameters, name, null); }; Skew.FunctionScope.prototype._findWithFuzzyMatching = function(matcher) { in_StringMap.each(this.parameters, function(name, parameter) { matcher.include(parameter); }); }; Skew.VariableScope = function(parent, symbol) { Skew.Scope.call(this, parent); this.symbol = symbol; }; __extends(Skew.VariableScope, Skew.Scope); Skew.VariableScope.prototype.kind = function() { return Skew.ScopeKind.VARIABLE; }; Skew.LocalType = { LOOP: 0, NORMAL: 1 }; Skew.LocalScope = function(parent, type) { Skew.Scope.call(this, parent); this.locals = new Map(); this.type = type; }; __extends(Skew.LocalScope, Skew.Scope); Skew.LocalScope.prototype.kind = function() { return Skew.ScopeKind.LOCAL; }; Skew.LocalScope.prototype._find = function(name) { return in_StringMap.get(this.locals, name, null); }; Skew.LocalScope.prototype._findWithFuzzyMatching = function(matcher) { in_StringMap.each(this.locals, function(name, local) { matcher.include(local); }); }; Skew.LocalScope.prototype.define = function(symbol, log) { symbol.scope = this; // Check for duplicates var other = in_StringMap.get(this.locals, symbol.name, null); if (other != null) { log.semanticErrorDuplicateSymbol(symbol.range, symbol.name, other.range); return; } // Check for shadowing var scope = this.parent; while (scope.kind() == Skew.ScopeKind.LOCAL) { var local = in_StringMap.get(scope.asLocalScope().locals, symbol.name, null); if (local != null) { log.semanticErrorShadowedSymbol(symbol.range, symbol.name, local.range); return; } scope = scope.parent; } scope.reserveName(symbol.name, symbol); in_StringMap.set(this.locals, symbol.name, symbol); }; // This does a simple control flow analysis without constructing a full // control flow graph. The result of this analysis is setting the flag // HAS_CONTROL_FLOW_AT_END on all blocks where control flow reaches the end. // // It makes a few assumptions around exceptions to make life easier. Normal // code without throw statements is assumed not to throw. For example, all // property accesses are assumed to succeed and not throw null pointer errors. // This is mostly consistent with how C++ operates for better or worse, and // is also consistent with how people read code. It also assumes flow always // can enter every catch block. Otherwise, why is it there? Skew.ControlFlowAnalyzer = function() { this._isLoopBreakTarget = []; this._isControlFlowLive = []; }; Skew.ControlFlowAnalyzer.prototype.pushBlock = function(node) { var parent = node.parent(); // Push control flow this._isControlFlowLive.push(this._isControlFlowLive.length == 0 || in_List.last(this._isControlFlowLive)); // Push loop info if (parent != null && Skew.in_NodeKind.isLoop(parent.kind)) { this._isLoopBreakTarget.push(false); } }; Skew.ControlFlowAnalyzer.prototype.popBlock = function(node) { var parent = node.parent(); // Pop control flow var isLive = in_List.takeLast(this._isControlFlowLive); if (isLive) { node.flags |= Skew.NodeFlags.HAS_CONTROL_FLOW_AT_END; } // Pop loop info if (parent != null && Skew.in_NodeKind.isLoop(parent.kind) && !in_List.takeLast(this._isLoopBreakTarget) && (parent.kind == Skew.NodeKind.WHILE && parent.whileTest().isTrue() || parent.kind == Skew.NodeKind.FOR && parent.forTest().isTrue())) { in_List.setLast(this._isControlFlowLive, false); } }; Skew.ControlFlowAnalyzer.prototype.visitStatementInPostOrder = function(node) { if (!in_List.last(this._isControlFlowLive)) { return; } switch (node.kind) { case Skew.NodeKind.BREAK: { if (!(this._isLoopBreakTarget.length == 0)) { in_List.setLast(this._isLoopBreakTarget, true); } in_List.setLast(this._isControlFlowLive, false); break; } case Skew.NodeKind.RETURN: case Skew.NodeKind.THROW: case Skew.NodeKind.CONTINUE: { in_List.setLast(this._isControlFlowLive, false); break; } case Skew.NodeKind.IF: { var test = node.ifTest(); var trueBlock = node.ifTrue(); var falseBlock = node.ifFalse(); if (test.isTrue()) { if (!trueBlock.hasControlFlowAtEnd()) { in_List.setLast(this._isControlFlowLive, false); } } else if (test.isFalse() && falseBlock != null) { if (!falseBlock.hasControlFlowAtEnd()) { in_List.setLast(this._isControlFlowLive, false); } } else if (trueBlock != null && falseBlock != null) { if (!trueBlock.hasControlFlowAtEnd() && !falseBlock.hasControlFlowAtEnd()) { in_List.setLast(this._isControlFlowLive, false); } } break; } case Skew.NodeKind.SWITCH: { var child = node.switchValue().nextSibling(); var foundDefaultCase = false; while (child != null && !child.caseBlock().hasControlFlowAtEnd()) { if (child.hasOneChild()) { foundDefaultCase = true; } child = child.nextSibling(); } if (child == null && foundDefaultCase) { in_List.setLast(this._isControlFlowLive, false); } break; } } }; Skew.in_PassKind = {}; Skew.in_SymbolKind = {}; Skew.in_SymbolKind.isType = function(self) { return self >= Skew.SymbolKind.PARAMETER_FUNCTION && self <= Skew.SymbolKind.OBJECT_WRAPPED; }; Skew.in_SymbolKind.isParameter = function(self) { return self >= Skew.SymbolKind.PARAMETER_FUNCTION && self <= Skew.SymbolKind.PARAMETER_OBJECT; }; Skew.in_SymbolKind.isObject = function(self) { return self >= Skew.SymbolKind.OBJECT_CLASS && self <= Skew.SymbolKind.OBJECT_WRAPPED; }; Skew.in_SymbolKind.isEnumOrFlags = function(self) { return self == Skew.SymbolKind.OBJECT_ENUM || self == Skew.SymbolKind.OBJECT_FLAGS; }; Skew.in_SymbolKind.isFunction = function(self) { return self >= Skew.SymbolKind.FUNCTION_ANNOTATION && self <= Skew.SymbolKind.FUNCTION_LOCAL; }; Skew.in_SymbolKind.isOverloadedFunction = function(self) { return self >= Skew.SymbolKind.OVERLOADED_ANNOTATION && self <= Skew.SymbolKind.OVERLOADED_INSTANCE; }; Skew.in_SymbolKind.isFunctionOrOverloadedFunction = function(self) { return self >= Skew.SymbolKind.FUNCTION_ANNOTATION && self <= Skew.SymbolKind.OVERLOADED_INSTANCE; }; Skew.in_SymbolKind.isVariable = function(self) { return self >= Skew.SymbolKind.VARIABLE_ARGUMENT && self <= Skew.SymbolKind.VARIABLE_LOCAL; }; Skew.in_SymbolKind.isLocalOrArgumentVariable = function(self) { return self == Skew.SymbolKind.VARIABLE_ARGUMENT || self == Skew.SymbolKind.VARIABLE_LOCAL; }; Skew.in_SymbolKind.isNamespaceOrGlobal = function(self) { return self == Skew.SymbolKind.OBJECT_NAMESPACE || self == Skew.SymbolKind.OBJECT_GLOBAL; }; Skew.in_SymbolKind.isGlobalReference = function(self) { return self == Skew.SymbolKind.VARIABLE_ENUM_OR_FLAGS || self == Skew.SymbolKind.VARIABLE_GLOBAL || self == Skew.SymbolKind.FUNCTION_GLOBAL || self == Skew.SymbolKind.FUNCTION_CONSTRUCTOR || self == Skew.SymbolKind.OVERLOADED_GLOBAL || Skew.in_SymbolKind.isType(self); }; Skew.in_SymbolKind.hasInstances = function(self) { return self == Skew.SymbolKind.OBJECT_CLASS || self == Skew.SymbolKind.OBJECT_ENUM || self == Skew.SymbolKind.OBJECT_FLAGS || self == Skew.SymbolKind.OBJECT_INTERFACE || self == Skew.SymbolKind.OBJECT_WRAPPED; }; Skew.in_SymbolKind.isOnInstances = function(self) { return self == Skew.SymbolKind.FUNCTION_INSTANCE || self == Skew.SymbolKind.VARIABLE_INSTANCE || self == Skew.SymbolKind.OVERLOADED_INSTANCE; }; Skew.in_SymbolKind.isLocal = function(self) { return self == Skew.SymbolKind.FUNCTION_LOCAL || self == Skew.SymbolKind.VARIABLE_LOCAL || self == Skew.SymbolKind.VARIABLE_ARGUMENT; }; Skew.in_Content = {}; Skew.in_Content.asBool = function(self) { assert(self.kind() == Skew.ContentKind.BOOL); return self.value; }; Skew.in_Content.asInt = function(self) { assert(self.kind() == Skew.ContentKind.INT); return self.value; }; Skew.in_Content.asDouble = function(self) { assert(self.kind() == Skew.ContentKind.DOUBLE); return self.value; }; Skew.in_Content.asString = function(self) { assert(self.kind() == Skew.ContentKind.STRING); return self.value; }; Skew.in_Content.equals = function(self, other) { if (self.kind() == other.kind()) { switch (self.kind()) { case Skew.ContentKind.BOOL: { return Skew.in_Content.asBool(self) == Skew.in_Content.asBool(other); } case Skew.ContentKind.INT: { return Skew.in_Content.asInt(self) == Skew.in_Content.asInt(other); } case Skew.ContentKind.DOUBLE: { return Skew.in_Content.asDouble(self) == Skew.in_Content.asDouble(other); } case Skew.ContentKind.STRING: { return Skew.in_Content.asString(self) == Skew.in_Content.asString(other); } } } return false; }; Skew.in_NodeKind = {}; Skew.in_NodeKind.isBitOperation = function(self) { return self == Skew.NodeKind.COMPLEMENT || self >= Skew.NodeKind.BITWISE_AND && self <= Skew.NodeKind.BITWISE_XOR || self >= Skew.NodeKind.ASSIGN_BITWISE_AND && self <= Skew.NodeKind.ASSIGN_BITWISE_XOR; }; Skew.in_NodeKind.isLoop = function(self) { return self == Skew.NodeKind.FOR || self == Skew.NodeKind.FOREACH || self == Skew.NodeKind.WHILE; }; Skew.in_NodeKind.isExpression = function(self) { return self >= Skew.NodeKind.ASSIGN_INDEX && self <= Skew.NodeKind.ASSIGN_UNSIGNED_SHIFT_RIGHT; }; Skew.in_NodeKind.isInitializer = function(self) { return self == Skew.NodeKind.INITIALIZER_LIST || self == Skew.NodeKind.INITIALIZER_MAP; }; Skew.in_NodeKind.isUnary = function(self) { return self >= Skew.NodeKind.COMPLEMENT && self <= Skew.NodeKind.PREFIX_INCREMENT; }; Skew.in_NodeKind.isUnaryAssign = function(self) { return self >= Skew.NodeKind.POSTFIX_DECREMENT && self <= Skew.NodeKind.PREFIX_INCREMENT; }; Skew.in_NodeKind.isUnaryPostfix = function(self) { return self == Skew.NodeKind.POSTFIX_DECREMENT || self == Skew.NodeKind.POSTFIX_INCREMENT; }; Skew.in_NodeKind.isBinary = function(self) { return self >= Skew.NodeKind.ADD && self <= Skew.NodeKind.ASSIGN_UNSIGNED_SHIFT_RIGHT; }; Skew.in_NodeKind.isBinaryAssign = function(self) { return self >= Skew.NodeKind.ASSIGN && self <= Skew.NodeKind.ASSIGN_UNSIGNED_SHIFT_RIGHT; }; // Note that add and multiply are NOT associative in finite-precision arithmetic Skew.in_NodeKind.isBinaryAssociative = function(self) { switch (self) { case Skew.NodeKind.BITWISE_AND: case Skew.NodeKind.BITWISE_OR: case Skew.NodeKind.BITWISE_XOR: case Skew.NodeKind.LOGICAL_AND: case Skew.NodeKind.LOGICAL_OR: { return true; } } return false; }; Skew.in_NodeKind.isBinaryComparison = function(self) { return self >= Skew.NodeKind.GREATER_THAN && self <= Skew.NodeKind.LESS_THAN_OR_EQUAL; }; Skew.in_NodeKind.isShift = function(self) { return self == Skew.NodeKind.SHIFT_LEFT || self == Skew.NodeKind.SHIFT_RIGHT || self == Skew.NodeKind.UNSIGNED_SHIFT_RIGHT; }; Skew.in_NodeKind.isJump = function(self) { return self == Skew.NodeKind.BREAK || self == Skew.NodeKind.CONTINUE || self == Skew.NodeKind.RETURN; }; Skew.in_NodeKind.isAssign = function(self) { return Skew.in_NodeKind.isUnaryAssign(self) || Skew.in_NodeKind.isBinaryAssign(self) || self == Skew.NodeKind.ASSIGN_INDEX; }; Skew.in_TokenKind = {}; Skew.in_TokenKind.toString = function(self) { assert(Skew.in_TokenKind._toString.has(self)); return in_IntMap.get1(Skew.in_TokenKind._toString, self); }; var Terminal = {}; Terminal.setColor = function(color) { if (process.stdout.isTTY) { process.stdout.write('\x1B[0;' + in_IntMap.get1(Terminal.colorToEscapeCode, color).toString() + 'm'); } }; Terminal.Color = { DEFAULT: 0, BOLD: 1, GRAY: 2, RED: 3, GREEN: 4, YELLOW: 5, BLUE: 6, MAGENTA: 7, CYAN: 8 }; var IO = {}; IO.isDirectory = function(path) { try { return require('fs').statSync(path).isDirectory(); } catch (e) { } return false; }; IO.readDirectory = function(path) { try { var entries = require('fs').readdirSync(path); entries.sort(function(a, b) { return in_string.compare(a, b); }); return entries; } catch (e) { } return null; }; IO.readFile = function(path) { try { var contents = require('fs').readFileSync(path, 'utf8'); return contents.split('\r\n').join('\n'); } catch (e) { } return null; }; IO.writeFile = function(path, contents) { var fs = require('fs'); var p = require('path'); var mkdir_p = null; mkdir_p = function(dir) { if (dir !== p.dirname(dir)) { mkdir_p(p.dirname(dir)); try { fs.mkdirSync(dir); } catch (e) { } } }; mkdir_p(p.dirname(path)); try { fs.writeFileSync(path, contents); return true; } catch (e) { } return false; }; var in_List = {}; in_List.get = function(self, index) { assert(0 <= index && index < self.length); return self[index]; }; in_List.set = function(self, index, value) { assert(0 <= index && index < self.length); return self[index] = value; }; in_List.setLast = function(self, x) { return in_List.set(self, self.length - 1 | 0, x); }; in_List.removeLast = function(self) { assert(!(self.length == 0)); self.pop(); }; in_List.swap = function(self, i, j) { assert(0 <= i && i < self.length); assert(0 <= j && j < self.length); var temp = in_List.get(self, i); in_List.set(self, i, in_List.get(self, j)); in_List.set(self, j, temp); }; in_List.last = function(self) { assert(!(self.length == 0)); return in_List.get(self, self.length - 1 | 0); }; in_List.prepend1 = function(self, values) { assert(values != self); var count = values.length; for (var i = 0, count1 = count; i < count1; i = i + 1 | 0) { self.unshift(in_List.get(values, (count - i | 0) - 1 | 0)); } }; in_List.append1 = function(self, values) { assert(values != self); for (var i = 0, list = values, count1 = list.length; i < count1; i = i + 1 | 0) { var value = in_List.get(list, i); self.push(value); } }; in_List.insert1 = function(self, index, values) { assert(values != self); for (var i = 0, list = values, count1 = list.length; i < count1; i = i + 1 | 0) { var value = in_List.get(list, i); in_List.insert2(self, index, value); index = index + 1 | 0; } }; in_List.insert2 = function(self, index, value) { assert(0 <= index && index <= self.length); self.splice(index, 0, value); }; in_List.removeFirst = function(self) { assert(!(self.length == 0)); self.shift(); }; in_List.removeAt = function(self, index) { assert(0 <= index && index < self.length); self.splice(index, 1); }; in_List.appendOne = function(self, value) { if (!(self.indexOf(value) != -1)) { self.push(value); } }; in_List.removeOne = function(self, value) { var index = self.indexOf(value); if (index >= 0) { in_List.removeAt(self, index); } }; in_List.removeIf = function(self, callback) { var index = 0; // Remove elements in place for (var i = 0, count1 = self.length; i < count1; i = i + 1 | 0) { if (!callback(in_List.get(self, i))) { if (index < i) { in_List.set(self, index, in_List.get(self, i)); } index = index + 1 | 0; } } // Shrink the array to the correct size while (index < self.length) { in_List.removeLast(self); } }; in_List.equals = function(self, other) { if (self.length != other.length) { return false; } for (var i = 0, count1 = self.length; i < count1; i = i + 1 | 0) { if (in_List.get(self, i) != in_List.get(other, i)) { return false; } } return true; }; in_List.slice1 = function(self, start) { assert(0 <= start && start <= self.length); return self.slice(start); }; in_List.slice2 = function(self, start, end) { assert(0 <= start && start <= end && end <= self.length); return self.slice(start, end); }; in_List.first = function(self) { assert(!(self.length == 0)); return in_List.get(self, 0); }; in_List.takeLast = function(self) { assert(!(self.length == 0)); return self.pop(); }; var in_StringMap = {}; in_StringMap.set = function(self, key, value) { self.set(key, value); return value; }; in_StringMap.insert = function(self, key, value) { self.set(key, value); return self; }; in_StringMap.get = function(self, key, defaultValue) { var value = self.get(key); // Compare against undefined so the key is only hashed once for speed return value !== void 0 ? value : defaultValue; }; in_StringMap.each = function(self, x) { self.forEach(function(value, key) { x(key, value); }); }; in_StringMap.get1 = function(self, key) { assert(self.has(key)); return self.get(key); }; var in_IntMap = {}; in_IntMap.set = function(self, key, value) { self.set(key, value); return value; }; in_IntMap.insert = function(self, key, value) { self.set(key, value); return self; }; in_IntMap.get = function(self, key, defaultValue) { var value = self.get(key); // Compare against undefined so the key is only hashed once for speed return value !== void 0 ? value : defaultValue; }; in_IntMap.get1 = function(self, key) { assert(self.has(key)); return self.get(key); }; var in_int = {}; in_int.power = function(self, x) { var y = self; var z = x < 0 ? 0 : 1; while (x > 0) { if ((x & 1) != 0) { z = __imul(z, y); } x >>= 1; y = __imul(y, y); } return z; }; in_int.compare = function(self, x) { return (x < self | 0) - (x > self | 0) | 0; }; var in_string = {}; in_string.fromCodePoints = function(codePoints) { var builder = new StringBuilder(); for (var i = 0, list = codePoints, count1 = list.length; i < count1; i = i + 1 | 0) { var codePoint = in_List.get(list, i); builder.buffer += in_string.fromCodePoint(codePoint); } return builder.buffer; }; in_string.compare = function(self, x) { return (x < self | 0) - (x > self | 0) | 0; }; in_string.repeat = function(self, times) { var result = ''; for (var i = 0, count1 = times; i < count1; i = i + 1 | 0) { result += self; } return result; }; in_string.codePoints = function(self) { var codePoints = []; var instance = Unicode.StringIterator.INSTANCE; instance.reset(self, 0); while (true) { var codePoint = instance.nextCodePoint(); if (codePoint < 0) { return codePoints; } codePoints.push(codePoint); } }; in_string.slice1 = function(self, start) { assert(0 <= start && start <= self.length); return self.slice(start); }; in_string.slice2 = function(self, start, end) { assert(0 <= start && start <= end && end <= self.length); return self.slice(start, end); }; in_string.get1 = function(self, index) { assert(0 <= index && index < self.length); return self.charCodeAt(index); }; in_string.get = function(self, index) { assert(0 <= index && index < self.length); return self[index]; }; in_string.fromCodePoint = function(codePoint) { return codePoint < 65536 ? String.fromCharCode(codePoint) : String.fromCharCode((codePoint - 65536 >> 10) + 55296 | 0) + String.fromCharCode((codePoint - 65536 & (1 << 10) - 1) + 56320 | 0); }; var TARGET = Target.JAVASCRIPT; Unicode.StringIterator.INSTANCE = new Unicode.StringIterator(); Skew.HEX = '0123456789ABCDEF'; Skew.BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; Skew.operatorInfo = in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(new Map(), Skew.NodeKind.COMPLEMENT, new Skew.OperatorInfo('~', Skew.Precedence.UNARY_PREFIX, Skew.Associativity.NONE, Skew.OperatorKind.OVERRIDABLE, [0], Skew.NodeKind.NULL)), Skew.NodeKind.NEGATIVE, new Skew.OperatorInfo('-', Skew.Precedence.UNARY_PREFIX, Skew.Associativity.NONE, Skew.OperatorKind.OVERRIDABLE, [0, 1], Skew.NodeKind.NULL)), Skew.NodeKind.NOT, new Skew.OperatorInfo('!', Skew.Precedence.UNARY_PREFIX, Skew.Associativity.NONE, Skew.OperatorKind.OVERRIDABLE, [0], Skew.NodeKind.NULL)), Skew.NodeKind.POSITIVE, new Skew.OperatorInfo('+', Skew.Precedence.UNARY_PREFIX, Skew.Associativity.NONE, Skew.OperatorKind.OVERRIDABLE, [0, 1], Skew.NodeKind.NULL)), Skew.NodeKind.POSTFIX_DECREMENT, new Skew.OperatorInfo('--', Skew.Precedence.UNARY_POSTFIX, Skew.Associativity.NONE, Skew.OperatorKind.OVERRIDABLE, [0], Skew.NodeKind.SUBTRACT)), Skew.NodeKind.POSTFIX_INCREMENT, new Skew.OperatorInfo('++', Skew.Precedence.UNARY_POSTFIX, Skew.Associativity.NONE, Skew.OperatorKind.OVERRIDABLE, [0], Skew.NodeKind.ADD)), Skew.NodeKind.PREFIX_DECREMENT, new Skew.OperatorInfo('--', Skew.Precedence.UNARY_PREFIX, Skew.Associativity.NONE, Skew.OperatorKind.OVERRIDABLE, [0], Skew.NodeKind.SUBTRACT)), Skew.NodeKind.PREFIX_INCREMENT, new Skew.OperatorInfo('++', Skew.Precedence.UNARY_PREFIX, Skew.Associativity.NONE, Skew.OperatorKind.OVERRIDABLE, [0], Skew.NodeKind.ADD)), Skew.NodeKind.ADD, new Skew.OperatorInfo('+', Skew.Precedence.ADD, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [0, 1], Skew.NodeKind.NULL)), Skew.NodeKind.BITWISE_AND, new Skew.OperatorInfo('&', Skew.Precedence.BITWISE_AND, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.BITWISE_OR, new Skew.OperatorInfo('|', Skew.Precedence.BITWISE_OR, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.BITWISE_XOR, new Skew.OperatorInfo('^', Skew.Precedence.BITWISE_XOR, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.COMPARE, new Skew.OperatorInfo('<=>', Skew.Precedence.COMPARE, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.DIVIDE, new Skew.OperatorInfo('/', Skew.Precedence.MULTIPLY, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.EQUAL, new Skew.OperatorInfo('==', Skew.Precedence.EQUAL, Skew.Associativity.LEFT, Skew.OperatorKind.FIXED, [1], Skew.NodeKind.NULL)), Skew.NodeKind.GREATER_THAN, new Skew.OperatorInfo('>', Skew.Precedence.COMPARE, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.GREATER_THAN_OR_EQUAL, new Skew.OperatorInfo('>=', Skew.Precedence.COMPARE, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.IN, new Skew.OperatorInfo('in', Skew.Precedence.COMPARE, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.LESS_THAN, new Skew.OperatorInfo('<', Skew.Precedence.COMPARE, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.LESS_THAN_OR_EQUAL, new Skew.OperatorInfo('<=', Skew.Precedence.COMPARE, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.LOGICAL_AND, new Skew.OperatorInfo('&&', Skew.Precedence.LOGICAL_AND, Skew.Associativity.LEFT, Skew.OperatorKind.FIXED, [1], Skew.NodeKind.NULL)), Skew.NodeKind.LOGICAL_OR, new Skew.OperatorInfo('||', Skew.Precedence.LOGICAL_OR, Skew.Associativity.LEFT, Skew.OperatorKind.FIXED, [1], Skew.NodeKind.NULL)), Skew.NodeKind.MODULUS, new Skew.OperatorInfo('%%', Skew.Precedence.MULTIPLY, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.MULTIPLY, new Skew.OperatorInfo('*', Skew.Precedence.MULTIPLY, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.NOT_EQUAL, new Skew.OperatorInfo('!=', Skew.Precedence.EQUAL, Skew.Associativity.LEFT, Skew.OperatorKind.FIXED, [1], Skew.NodeKind.NULL)), Skew.NodeKind.POWER, new Skew.OperatorInfo('**', Skew.Precedence.UNARY_PREFIX, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.REMAINDER, new Skew.OperatorInfo('%', Skew.Precedence.MULTIPLY, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.SHIFT_LEFT, new Skew.OperatorInfo('<<', Skew.Precedence.SHIFT, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.SHIFT_RIGHT, new Skew.OperatorInfo('>>', Skew.Precedence.SHIFT, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.SUBTRACT, new Skew.OperatorInfo('-', Skew.Precedence.ADD, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [0, 1], Skew.NodeKind.NULL)), Skew.NodeKind.UNSIGNED_SHIFT_RIGHT, new Skew.OperatorInfo('>>>', Skew.Precedence.SHIFT, Skew.Associativity.LEFT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)), Skew.NodeKind.ASSIGN, new Skew.OperatorInfo('=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.FIXED, [1], Skew.NodeKind.NULL)), Skew.NodeKind.ASSIGN_ADD, new Skew.OperatorInfo('+=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.ADD)), Skew.NodeKind.ASSIGN_BITWISE_AND, new Skew.OperatorInfo('&=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.BITWISE_AND)), Skew.NodeKind.ASSIGN_BITWISE_OR, new Skew.OperatorInfo('|=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.BITWISE_OR)), Skew.NodeKind.ASSIGN_BITWISE_XOR, new Skew.OperatorInfo('^=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.BITWISE_XOR)), Skew.NodeKind.ASSIGN_DIVIDE, new Skew.OperatorInfo('/=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.DIVIDE)), Skew.NodeKind.ASSIGN_MODULUS, new Skew.OperatorInfo('%%=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.MODULUS)), Skew.NodeKind.ASSIGN_MULTIPLY, new Skew.OperatorInfo('*=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.MULTIPLY)), Skew.NodeKind.ASSIGN_POWER, new Skew.OperatorInfo('**=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.POWER)), Skew.NodeKind.ASSIGN_REMAINDER, new Skew.OperatorInfo('%=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.REMAINDER)), Skew.NodeKind.ASSIGN_SHIFT_LEFT, new Skew.OperatorInfo('<<=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.SHIFT_LEFT)), Skew.NodeKind.ASSIGN_SHIFT_RIGHT, new Skew.OperatorInfo('>>=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.SHIFT_RIGHT)), Skew.NodeKind.ASSIGN_SUBTRACT, new Skew.OperatorInfo('-=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.SUBTRACT)), Skew.NodeKind.ASSIGN_UNSIGNED_SHIFT_RIGHT, new Skew.OperatorInfo('>>>=', Skew.Precedence.ASSIGN, Skew.Associativity.RIGHT, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.UNSIGNED_SHIFT_RIGHT)), Skew.NodeKind.ASSIGN_INDEX, new Skew.OperatorInfo('[]=', Skew.Precedence.MEMBER, Skew.Associativity.NONE, Skew.OperatorKind.OVERRIDABLE, [2], Skew.NodeKind.NULL)), Skew.NodeKind.INDEX, new Skew.OperatorInfo('[]', Skew.Precedence.MEMBER, Skew.Associativity.NONE, Skew.OperatorKind.OVERRIDABLE, [1], Skew.NodeKind.NULL)); Skew.validArgumentCounts = null; Skew.DEFAULT_MESSAGE_LIMIT = 10; Skew.VALID_TARGETS = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), 'cpp', new Skew.CPlusPlusTarget()), 'cs', new Skew.CSharpTarget()), 'ts', new Skew.TypeScriptTarget()), 'js', new Skew.JavaScriptTarget()), 'lisp-tree', new Skew.LispTreeTarget()); Skew.VERSION = '0.9.19'; Skew.REMOVE_WHITESPACE_BEFORE = in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(new Map(), Skew.TokenKind.COLON, 0), Skew.TokenKind.COMMA, 0), Skew.TokenKind.DOT, 0), Skew.TokenKind.QUESTION_MARK, 0), Skew.TokenKind.RIGHT_PARENTHESIS, 0); Skew.FORBID_XML_AFTER = in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(new Map(), Skew.TokenKind.CHARACTER, 0), Skew.TokenKind.DECREMENT, 0), Skew.TokenKind.DOUBLE, 0), Skew.TokenKind.DYNAMIC, 0), Skew.TokenKind.STRING_INTERPOLATION_END, 0), Skew.TokenKind.FALSE, 0), Skew.TokenKind.IDENTIFIER, 0), Skew.TokenKind.INCREMENT, 0), Skew.TokenKind.INT, 0), Skew.TokenKind.INT_BINARY, 0), Skew.TokenKind.INT_HEX, 0), Skew.TokenKind.INT_OCTAL, 0), Skew.TokenKind.NULL, 0), Skew.TokenKind.RIGHT_BRACE, 0), Skew.TokenKind.RIGHT_BRACKET, 0), Skew.TokenKind.RIGHT_PARENTHESIS, 0), Skew.TokenKind.STRING, 0), Skew.TokenKind.SUPER, 0), Skew.TokenKind.TRUE, 0); Skew.yy_accept = [Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.END_OF_FILE, Skew.TokenKind.ERROR, Skew.TokenKind.WHITESPACE, Skew.TokenKind.NEWLINE, Skew.TokenKind.NOT, Skew.TokenKind.ERROR, Skew.TokenKind.COMMENT, Skew.TokenKind.REMAINDER, Skew.TokenKind.BITWISE_AND, Skew.TokenKind.ERROR, Skew.TokenKind.LEFT_PARENTHESIS, Skew.TokenKind.RIGHT_PARENTHESIS, Skew.TokenKind.MULTIPLY, Skew.TokenKind.PLUS, Skew.TokenKind.COMMA, Skew.TokenKind.MINUS, Skew.TokenKind.DOT, Skew.TokenKind.DIVIDE, Skew.TokenKind.INT, Skew.TokenKind.INT, Skew.TokenKind.COLON, Skew.TokenKind.SEMICOLON, Skew.TokenKind.LESS_THAN, Skew.TokenKind.ASSIGN, Skew.TokenKind.GREATER_THAN, Skew.TokenKind.QUESTION_MARK, Skew.TokenKind.ERROR, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.LEFT_BRACKET, Skew.TokenKind.RIGHT_BRACKET, Skew.TokenKind.BITWISE_XOR, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.LEFT_BRACE, Skew.TokenKind.BITWISE_OR, Skew.TokenKind.RIGHT_BRACE, Skew.TokenKind.TILDE, Skew.TokenKind.WHITESPACE, Skew.TokenKind.NEWLINE, Skew.TokenKind.NOT_EQUAL, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.STRING, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.COMMENT, Skew.TokenKind.MODULUS, Skew.TokenKind.ASSIGN_REMAINDER, Skew.TokenKind.LOGICAL_AND, Skew.TokenKind.ASSIGN_BITWISE_AND, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.CHARACTER, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.POWER, Skew.TokenKind.ASSIGN_MULTIPLY, Skew.TokenKind.INCREMENT, Skew.TokenKind.ASSIGN_PLUS, Skew.TokenKind.DECREMENT, Skew.TokenKind.ASSIGN_MINUS, Skew.TokenKind.DOT_DOT, Skew.TokenKind.COMMENT_ERROR, Skew.TokenKind.ASSIGN_DIVIDE, Skew.TokenKind.XML_END_EMPTY, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.INT, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.DOUBLE_COLON, Skew.TokenKind.XML_START_CLOSE, Skew.TokenKind.SHIFT_LEFT, Skew.TokenKind.LESS_THAN_OR_EQUAL, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.EQUAL, Skew.TokenKind.ARROW, Skew.TokenKind.GREATER_THAN_OR_EQUAL, Skew.TokenKind.SHIFT_RIGHT, Skew.TokenKind.NULL_DOT, Skew.TokenKind.ASSIGN_NULL, Skew.TokenKind.NULL_JOIN, Skew.TokenKind.ANNOTATION, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.INDEX, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.ASSIGN_BITWISE_XOR, Skew.TokenKind.AS, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IF, Skew.TokenKind.IN, Skew.TokenKind.IS, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.ASSIGN_BITWISE_OR, Skew.TokenKind.LOGICAL_OR, Skew.TokenKind.NOT_EQUAL_ERROR, Skew.TokenKind.ASSIGN_MODULUS, Skew.TokenKind.ASSIGN_POWER, Skew.TokenKind.COMMENT_ERROR, Skew.TokenKind.DOUBLE, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.DOUBLE, Skew.TokenKind.INT_BINARY, Skew.TokenKind.INT_OCTAL, Skew.TokenKind.INT_HEX, Skew.TokenKind.ASSIGN_SHIFT_LEFT, Skew.TokenKind.COMPARE, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.EQUAL_ERROR, Skew.TokenKind.ASSIGN_SHIFT_RIGHT, Skew.TokenKind.UNSIGNED_SHIFT_RIGHT, Skew.TokenKind.ANNOTATION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.ASSIGN_INDEX, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.FOR, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.TRY, Skew.TokenKind.VAR, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.ASSIGN_UNSIGNED_SHIFT_RIGHT, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.CASE, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.ELSE, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.NULL, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.TRUE, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.DOUBLE, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.LIST, Skew.TokenKind.LIST_NEW, Skew.TokenKind.BREAK, Skew.TokenKind.CATCH, Skew.TokenKind.CONST, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.FALSE, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.SUPER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.THROW, Skew.TokenKind.WHILE, Skew.TokenKind.SET, Skew.TokenKind.SET_NEW, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.RETURN, Skew.TokenKind.SWITCH, Skew.TokenKind.YY_INVALID_ACTION, Skew.TokenKind.IDENTIFIER, Skew.TokenKind.DEFAULT, Skew.TokenKind.DYNAMIC, Skew.TokenKind.FINALLY, Skew.TokenKind.XML_CHILD, Skew.TokenKind.CONTINUE, Skew.TokenKind.YY_INVALID_ACTION]; Skew.yy_ec = [0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 5, 6, 1, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 20, 20, 20, 20, 20, 21, 21, 22, 23, 24, 25, 26, 27, 28, 29, 29, 29, 29, 30, 29, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 33, 34, 35, 31, 1, 36, 37, 38, 39, 40, 41, 31, 42, 43, 31, 44, 45, 46, 47, 48, 49, 31, 50, 51, 52, 53, 54, 55, 56, 57, 31, 58, 59, 60, 61, 1]; Skew.yy_meta = [0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 4, 4, 5, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1]; Skew.yy_base = [0, 0, 0, 320, 321, 317, 316, 292, 57, 0, 56, 57, 55, 321, 321, 54, 55, 321, 52, 300, 58, 75, 81, 293, 321, 61, 44, 46, 82, 0, 0, 88, 321, 289, 262, 262, 70, 63, 266, 81, 85, 257, 269, 21, 66, 272, 265, 94, 88, 321, 321, 304, 303, 279, 109, 321, 300, 0, 277, 321, 321, 321, 110, 321, 298, 275, 321, 321, 321, 321, 321, 321, 0, 321, 321, 119, 130, 143, 109, 134, 0, 321, 321, 274, 272, 281, 271, 321, 321, 108, 321, 321, 321, 0, 0, 279, 269, 253, 321, 0, 252, 93, 244, 249, 242, 237, 242, 239, 235, 0, 0, 0, 239, 231, 233, 238, 230, 102, 229, 235, 261, 236, 321, 321, 321, 321, 321, 0, 147, 153, 160, 157, 164, 0, 321, 321, 259, 321, 321, 249, 0, 257, 321, 217, 235, 230, 231, 134, 232, 231, 226, 214, 228, 0, 218, 209, 221, 208, 211, 218, 0, 0, 212, 240, 200, 175, 238, 321, 219, 218, 207, 0, 208, 197, 205, 194, 200, 0, 205, 199, 0, 193, 192, 203, 185, 0, 199, 178, 177, 179, 183, 212, 321, 321, 0, 0, 0, 188, 181, 168, 0, 147, 144, 0, 147, 0, 0, 321, 321, 152, 104, 78, 87, 35, 0, 0, 63, 33, 0, 0, 0, 321, 0, 321, 204, 209, 214, 216, 219, 224, 227, 229]; Skew.yy_def = [0, 223, 1, 223, 223, 223, 223, 223, 224, 225, 223, 223, 226, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 227, 228, 223, 223, 223, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 223, 223, 223, 223, 223, 223, 223, 224, 223, 224, 225, 223, 223, 223, 223, 226, 223, 226, 223, 223, 223, 223, 223, 223, 223, 229, 223, 223, 223, 223, 223, 223, 223, 230, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 231, 228, 223, 223, 223, 223, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 223, 223, 223, 223, 223, 223, 223, 229, 223, 223, 223, 223, 223, 230, 223, 223, 223, 223, 223, 223, 231, 223, 223, 223, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 223, 223, 223, 223, 223, 223, 223, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 223, 223, 223, 223, 223, 223, 223, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 223, 223, 223, 228, 228, 228, 228, 228, 228, 223, 228, 228, 228, 228, 223, 228, 0, 223, 223, 223, 223, 223, 223, 223, 223]; Skew.yy_nxt = [0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 22, 22, 23, 24, 25, 26, 27, 28, 29, 30, 30, 30, 31, 4, 32, 33, 34, 35, 36, 37, 38, 39, 30, 40, 30, 30, 30, 41, 30, 30, 42, 43, 44, 30, 45, 46, 30, 30, 47, 48, 49, 50, 55, 58, 63, 60, 65, 69, 67, 86, 87, 88, 89, 222, 114, 72, 115, 70, 82, 66, 68, 59, 61, 73, 74, 83, 84, 85, 64, 221, 56, 75, 220, 76, 76, 76, 76, 75, 90, 76, 76, 76, 76, 103, 95, 77, 101, 91, 116, 92, 120, 77, 78, 122, 55, 77, 117, 106, 102, 63, 104, 77, 96, 79, 107, 219, 109, 131, 131, 108, 218, 80, 110, 138, 139, 97, 111, 128, 128, 128, 128, 121, 56, 64, 145, 146, 75, 123, 76, 76, 76, 76, 132, 132, 132, 159, 129, 217, 129, 160, 77, 130, 130, 130, 130, 128, 128, 128, 128, 216, 77, 130, 130, 130, 130, 131, 131, 165, 130, 130, 130, 130, 132, 132, 132, 173, 174, 165, 189, 215, 189, 214, 213, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 54, 54, 54, 54, 54, 57, 212, 57, 57, 57, 62, 62, 62, 62, 62, 93, 93, 94, 94, 94, 127, 211, 127, 127, 127, 133, 133, 140, 140, 140, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 188, 187, 186, 185, 184, 183, 182, 181, 180, 179, 178, 177, 176, 175, 172, 171, 170, 169, 168, 167, 166, 164, 163, 162, 161, 158, 157, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, 144, 143, 142, 141, 137, 136, 135, 134, 126, 223, 125, 223, 124, 52, 51, 119, 118, 113, 112, 105, 100, 99, 98, 81, 71, 53, 52, 51, 223, 3, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223]; Skew.yy_chk = [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 10, 12, 11, 15, 18, 16, 26, 26, 27, 27, 217, 43, 20, 43, 18, 25, 15, 16, 10, 11, 20, 20, 25, 25, 25, 12, 216, 8, 21, 213, 21, 21, 21, 21, 22, 28, 22, 22, 22, 22, 37, 31, 21, 36, 28, 44, 28, 47, 22, 21, 48, 54, 21, 44, 39, 36, 62, 37, 22, 31, 21, 39, 212, 40, 78, 78, 39, 211, 21, 40, 89, 89, 31, 40, 75, 75, 75, 75, 47, 54, 62, 101, 101, 76, 48, 76, 76, 76, 76, 79, 79, 79, 117, 77, 210, 77, 117, 76, 77, 77, 77, 77, 128, 128, 128, 128, 209, 76, 129, 129, 129, 129, 131, 131, 128, 130, 130, 130, 130, 132, 132, 132, 147, 147, 128, 165, 204, 165, 202, 201, 165, 165, 165, 165, 189, 189, 189, 189, 190, 190, 190, 190, 224, 224, 224, 224, 224, 225, 199, 225, 225, 225, 226, 226, 226, 226, 226, 227, 227, 228, 228, 228, 229, 198, 229, 229, 229, 230, 230, 231, 231, 231, 197, 191, 188, 187, 186, 184, 183, 182, 181, 179, 178, 176, 175, 174, 173, 172, 170, 169, 168, 166, 164, 163, 162, 159, 158, 157, 156, 155, 154, 152, 151, 150, 149, 148, 146, 145, 144, 143, 141, 139, 136, 121, 120, 119, 118, 116, 115, 114, 113, 112, 108, 107, 106, 105, 104, 103, 102, 100, 97, 96, 95, 86, 85, 84, 83, 65, 64, 58, 56, 53, 52, 51, 46, 45, 42, 41, 38, 35, 34, 33, 23, 19, 7, 6, 5, 3, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223]; Skew.YY_JAM_STATE = 223; Skew.YY_ACCEPT_LENGTH = 224; Skew.NATIVE_LIBRARY_CPP = '\n@import {\n def __doubleToString(x double) string\n def __intToString(x int) string\n\n @rename("std::isnan")\n def __doubleIsNaN(x double) bool\n\n @rename("std::isfinite")\n def __doubleIsFinite(x double) bool\n}\n\nclass bool {\n def toString string {\n return self ? "true" : "false"\n }\n}\n\nclass int {\n def toString string {\n return __intToString(self)\n }\n\n def >>>(x int) int {\n return (self as dynamic.unsigned >> x) as int\n }\n}\n\nclass double {\n def toString string {\n return __doubleToString(self)\n }\n\n def isNaN bool {\n return __doubleIsNaN(self)\n }\n\n def isFinite bool {\n return __doubleIsFinite(self)\n }\n}\n\nclass string {\n @rename("compare")\n def <=>(x string) int\n\n @rename("contains")\n def in(x string) bool\n}\n\n@rename("Skew::List")\nclass List {\n @rename("contains")\n def in(x T) bool\n}\n\n@rename("Skew::StringMap")\nclass StringMap {\n @rename("contains")\n def in(x string) bool\n}\n\n@rename("Skew::IntMap")\nclass IntMap {\n @rename("contains")\n def in(x int) bool\n}\n\n@import\n@rename("Skew::StringBuilder")\nclass StringBuilder {\n}\n\n@import\n@rename("Skew::Math")\nnamespace Math {\n}\n\n@import\ndef assert(truth bool)\n'; Skew.UNICODE_LIBRARY = '\nnamespace Unicode {\n enum Encoding {\n UTF8\n UTF16\n UTF32\n }\n\n const STRING_ENCODING Encoding =\n TARGET == .CPLUSPLUS ? .UTF8 :\n TARGET == .CSHARP || TARGET == .JAVASCRIPT ? .UTF16 :\n .UTF32\n\n class StringIterator {\n var value = ""\n var index = 0\n var stop = 0\n\n def reset(text string, start int) StringIterator {\n value = text\n index = start\n stop = text.count\n return self\n }\n\n def countCodePointsUntil(stop int) int {\n var count = 0\n while index < stop && nextCodePoint >= 0 {\n count++\n }\n return count\n }\n\n def previousCodePoint int\n def nextCodePoint int\n\n if STRING_ENCODING == .UTF8 {\n def previousCodePoint int {\n if index <= 0 { return -1 }\n var a = value[--index]\n if (a & 0xC0) != 0x80 { return a }\n if index <= 0 { return -1 }\n var b = value[--index]\n if (b & 0xC0) != 0x80 { return ((b & 0x1F) << 6) | (a & 0x3F) }\n if index <= 0 { return -1 }\n var c = value[--index]\n if (c & 0xC0) != 0x80 { return ((c & 0x0F) << 12) | ((b & 0x3F) << 6) | (a & 0x3F) }\n if index >= stop { return -1 }\n var d = value[--index]\n return ((d & 0x07) << 18) | ((c & 0x3F) << 12) | ((b & 0x3F) << 6) | (a & 0x3F)\n }\n\n def nextCodePoint int {\n if index >= stop { return -1 }\n var a = value[index++]\n if a < 0xC0 { return a }\n if index >= stop { return -1 }\n var b = value[index++]\n if a < 0xE0 { return ((a & 0x1F) << 6) | (b & 0x3F) }\n if index >= stop { return -1 }\n var c = value[index++]\n if a < 0xF0 { return ((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F) }\n if index >= stop { return -1 }\n var d = value[index++]\n return ((a & 0x07) << 18) | ((b & 0x3F) << 12) | ((c & 0x3F) << 6) | (d & 0x3F)\n }\n }\n\n else if STRING_ENCODING == .UTF16 {\n def previousCodePoint int {\n if index <= 0 { return -1 }\n var a = value[--index]\n if (a & 0xFC00) != 0xDC00 { return a }\n if index <= 0 { return -1 }\n var b = value[--index]\n return (b << 10) + a + (0x10000 - (0xD800 << 10) - 0xDC00)\n }\n\n def nextCodePoint int {\n if index >= stop { return -1 }\n var a = value[index++]\n if (a & 0xFC00) != 0xD800 { return a }\n if index >= stop { return -1 }\n var b = value[index++]\n return (a << 10) + b + (0x10000 - (0xD800 << 10) - 0xDC00)\n }\n }\n\n else {\n def previousCodePoint int {\n if index <= 0 { return -1 }\n return value[--index]\n }\n\n def nextCodePoint int {\n if index >= stop { return -1 }\n return value[index++]\n }\n }\n }\n\n namespace StringIterator {\n const INSTANCE = StringIterator.new\n }\n\n def codeUnitCountForCodePoints(codePoints List, encoding Encoding) int {\n var count = 0\n\n switch encoding {\n case .UTF8 {\n for codePoint in codePoints {\n if codePoint < 0x80 { count++ }\n else if codePoint < 0x800 { count += 2 }\n else if codePoint < 0x10000 { count += 3 }\n else { count += 4 }\n }\n }\n\n case .UTF16 {\n for codePoint in codePoints {\n if codePoint < 0x10000 { count++ }\n else { count += 2 }\n }\n }\n\n case .UTF32 {\n count = codePoints.count\n }\n }\n\n return count\n }\n}\n\nclass string {\n if Unicode.STRING_ENCODING == .UTF32 {\n def codePoints List {\n return codeUnits\n }\n }\n\n else {\n def codePoints List {\n var codePoints List = []\n var instance = Unicode.StringIterator.INSTANCE\n instance.reset(self, 0)\n\n while true {\n var codePoint = instance.nextCodePoint\n if codePoint < 0 {\n return codePoints\n }\n codePoints.append(codePoint)\n }\n }\n }\n}\n\nnamespace string {\n def fromCodePoints(codePoints List) string {\n var builder = StringBuilder.new\n for codePoint in codePoints {\n builder.append(fromCodePoint(codePoint))\n }\n return builder.toString\n }\n\n if Unicode.STRING_ENCODING == .UTF8 {\n def fromCodePoint(codePoint int) string {\n return\n codePoint < 0x80 ? fromCodeUnit(codePoint) : (\n codePoint < 0x800 ? fromCodeUnit(((codePoint >> 6) & 0x1F) | 0xC0) : (\n codePoint < 0x10000 ? fromCodeUnit(((codePoint >> 12) & 0x0F) | 0xE0) : (\n fromCodeUnit(((codePoint >> 18) & 0x07) | 0xF0)\n ) + fromCodeUnit(((codePoint >> 12) & 0x3F) | 0x80)\n ) + fromCodeUnit(((codePoint >> 6) & 0x3F) | 0x80)\n ) + fromCodeUnit((codePoint & 0x3F) | 0x80)\n }\n }\n\n else if Unicode.STRING_ENCODING == .UTF16 {\n def fromCodePoint(codePoint int) string {\n return codePoint < 0x10000 ? fromCodeUnit(codePoint) :\n fromCodeUnit(((codePoint - 0x10000) >> 10) + 0xD800) +\n fromCodeUnit(((codePoint - 0x10000) & ((1 << 10) - 1)) + 0xDC00)\n }\n }\n\n else {\n def fromCodePoint(codePoint int) string {\n return fromCodeUnit(codePoint)\n }\n }\n}\n'; Skew.NATIVE_LIBRARY_JS = '\nconst __create fn(dynamic) dynamic = dynamic.Object.create ? dynamic.Object.create : prototype => {\n return {"__proto__": prototype}\n}\n\nconst __extends = (derived dynamic, base dynamic) => {\n derived.prototype = __create(base.prototype)\n derived.prototype.constructor = derived\n}\n\nconst __imul fn(int, int) int = dynamic.Math.imul ? dynamic.Math.imul : (a, b) => {\n return ((a as dynamic) * (b >>> 16) << 16) + (a as dynamic) * (b & 65535) | 0\n}\n\nconst __prototype dynamic\nconst __isInt = (value dynamic) => value == (value | 0)\nconst __isBool = (value dynamic) => value == !!value\nconst __isDouble = (value dynamic) => dynamic.typeof(value) == "number"\nconst __isString = (value dynamic) => dynamic.typeof(value) == "string"\nconst __asString = (value dynamic) => value == null ? value : value + ""\n\ndef assert(truth bool) {\n if !truth {\n throw dynamic.Error("Assertion failed")\n }\n}\n\n# Override this to true to remove asserts from many of the functions below so that they may be inlined\nconst JS_INLINE_NATIVE = false\n\n@import\nnamespace Math {}\n\n@rename("boolean")\nclass bool {}\n\n@rename("number")\nclass int {}\n\n@rename("number")\nclass double {\n @alwaysinline\n def isFinite bool {\n return dynamic.isFinite(self)\n }\n\n @alwaysinline\n def isNaN bool {\n return dynamic.isNaN(self)\n }\n}\n\nclass string {\n def <=>(x string) int {\n return ((x as dynamic < self) as int) - ((x as dynamic > self) as int)\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def slice(start int) string {\n return (self as dynamic).slice(start)\n }\n } else {\n def slice(start int) string {\n assert(0 <= start && start <= count)\n return (self as dynamic).slice(start)\n }\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def slice(start int, end int) string {\n return (self as dynamic).slice(start, end)\n }\n } else {\n def slice(start int, end int) string {\n assert(0 <= start && start <= end && end <= count)\n return (self as dynamic).slice(start, end)\n }\n }\n\n @alwaysinline\n def startsWith(text string) bool {\n return (self as dynamic).startsWith(text)\n }\n\n @alwaysinline\n def endsWith(text string) bool {\n return (self as dynamic).endsWith(text)\n }\n\n @alwaysinline\n def replaceAll(before string, after string) string {\n return after.join(self.split(before))\n }\n\n @alwaysinline\n def in(value string) bool {\n return indexOf(value) != -1\n }\n\n @alwaysinline\n def count int {\n return (self as dynamic).length\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def [](index int) int {\n return (self as dynamic).charCodeAt(index)\n }\n } else {\n def [](index int) int {\n assert(0 <= index && index < count)\n return (self as dynamic).charCodeAt(index)\n }\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def get(index int) string {\n return (self as dynamic)[index]\n }\n } else {\n def get(index int) string {\n assert(0 <= index && index < count)\n return (self as dynamic)[index]\n }\n }\n\n def repeat(times int) string {\n var result = ""\n for i in 0..times {\n result += self\n }\n return result\n }\n\n @alwaysinline\n def join(parts List) string {\n return (parts as dynamic).join(self)\n }\n\n def codeUnits List {\n var result List = []\n for i in 0..count {\n result.append(self[i])\n }\n return result\n }\n}\n\nnamespace string {\n @alwaysinline\n def fromCodeUnit(codeUnit int) string {\n return dynamic.String.fromCharCode(codeUnit)\n }\n\n def fromCodeUnits(codeUnits List) string {\n var result = ""\n for codeUnit in codeUnits {\n result += string.fromCodeUnit(codeUnit)\n }\n return result\n }\n}\n\nclass StringBuilder {\n var buffer = ""\n\n def new {\n }\n\n @alwaysinline\n def append(x string) {\n buffer += x\n }\n\n @alwaysinline\n def toString string {\n return buffer\n }\n}\n\n@rename("Array")\nclass List {\n @rename("unshift")\n def prepend(x T)\n\n @rename("push")\n def append(x T)\n\n @rename("every")\n def all(x fn(T) bool) bool\n\n @rename("some")\n def any(x fn(T) bool) bool\n\n @rename("slice")\n def clone List\n\n @rename("forEach")\n def each(x fn(T))\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def slice(start int) List {\n return (self as dynamic).slice(start)\n }\n } else {\n def slice(start int) List {\n assert(0 <= start && start <= count)\n return (self as dynamic).slice(start)\n }\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def slice(start int, end int) List {\n return (self as dynamic).slice(start, end)\n }\n } else {\n def slice(start int, end int) List {\n assert(0 <= start && start <= end && end <= count)\n return (self as dynamic).slice(start, end)\n }\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def [](index int) T {\n return (self as dynamic)[index]\n }\n } else {\n def [](index int) T {\n assert(0 <= index && index < count)\n return (self as dynamic)[index]\n }\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def []=(index int, value T) T {\n return (self as dynamic)[index] = value\n }\n } else {\n def []=(index int, value T) T {\n assert(0 <= index && index < count)\n return (self as dynamic)[index] = value\n }\n }\n\n @alwaysinline\n def in(value T) bool {\n return indexOf(value) != -1\n }\n\n @alwaysinline\n def isEmpty bool {\n return count == 0\n }\n\n @alwaysinline\n def count int {\n return (self as dynamic).length\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def first T {\n return self[0]\n }\n } else {\n def first T {\n assert(!isEmpty)\n return self[0]\n }\n }\n\n def last T {\n assert(!isEmpty)\n return self[count - 1]\n }\n\n def prepend(values List) {\n assert(values != self)\n var count = values.count\n for i in 0..count {\n prepend(values[count - i - 1])\n }\n }\n\n def append(values List) {\n assert(values != self)\n for value in values {\n append(value)\n }\n }\n\n def insert(index int, values List) {\n assert(values != self)\n for value in values {\n insert(index, value)\n index++\n }\n }\n\n def insert(index int, value T) {\n assert(0 <= index && index <= count)\n (self as dynamic).splice(index, 0, value)\n }\n\n def removeFirst {\n assert(!isEmpty)\n (self as dynamic).shift()\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def takeFirst T {\n return (self as dynamic).shift()\n }\n } else {\n def takeFirst T {\n assert(!isEmpty)\n return (self as dynamic).shift()\n }\n }\n\n def removeLast {\n assert(!isEmpty)\n (self as dynamic).pop()\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def takeLast T {\n return (self as dynamic).pop()\n }\n } else {\n def takeLast T {\n assert(!isEmpty)\n return (self as dynamic).pop()\n }\n }\n\n def removeAt(index int) {\n assert(0 <= index && index < count)\n (self as dynamic).splice(index, 1)\n }\n\n if JS_INLINE_NATIVE {\n @alwaysinline\n def takeAt(index int) T {\n return (self as dynamic).splice(index, 1)[0]\n }\n } else {\n def takeAt(index int) T {\n assert(0 <= index && index < count)\n return (self as dynamic).splice(index, 1)[0]\n }\n }\n\n def takeRange(start int, end int) List {\n assert(0 <= start && start <= end && end <= count)\n return (self as dynamic).splice(start, end - start)\n }\n\n def appendOne(value T) {\n if !(value in self) {\n append(value)\n }\n }\n\n def removeOne(value T) {\n var index = indexOf(value)\n if index >= 0 {\n removeAt(index)\n }\n }\n\n def removeRange(start int, end int) {\n assert(0 <= start && start <= end && end <= count)\n (self as dynamic).splice(start, end - start)\n }\n\n def removeIf(callback fn(T) bool) {\n var index = 0\n\n # Remove elements in place\n for i in 0..count {\n if !callback(self[i]) {\n if index < i {\n self[index] = self[i]\n }\n index++\n }\n }\n\n # Shrink the array to the correct size\n while index < count {\n removeLast\n }\n }\n\n def equals(other List) bool {\n if count != other.count {\n return false\n }\n for i in 0..count {\n if self[i] != other[i] {\n return false\n }\n }\n return true\n }\n}\n\nnamespace List {\n @alwaysinline\n def new List {\n return [] as dynamic\n }\n}\n\nnamespace StringMap {\n @alwaysinline\n def new StringMap {\n return dynamic.Map.new\n }\n}\n\nclass StringMap {\n if JS_INLINE_NATIVE {\n @alwaysinline\n def [](key string) T {\n return (self as dynamic).get(key)\n }\n } else {\n def [](key string) T {\n assert(key in self)\n return (self as dynamic).get(key)\n }\n }\n\n def []=(key string, value T) T {\n (self as dynamic).set(key, value)\n return value\n }\n\n def {...}(key string, value T) StringMap {\n (self as dynamic).set(key, value)\n return self\n }\n\n @alwaysinline\n def in(key string) bool {\n return (self as dynamic).has(key)\n }\n\n @alwaysinline\n def count int {\n return (self as dynamic).size\n }\n\n @alwaysinline\n def isEmpty bool {\n return count == 0\n }\n\n def get(key string, defaultValue T) T {\n const value = (self as dynamic).get(key)\n return value != dynamic.void(0) ? value : defaultValue # Compare against undefined so the key is only hashed once for speed\n }\n\n @alwaysinline\n def keys List {\n return dynamic.Array.from((self as dynamic).keys())\n }\n\n @alwaysinline\n def values List {\n return dynamic.Array.from((self as dynamic).values())\n }\n\n @alwaysinline\n def clone StringMap {\n return dynamic.Map.new(self)\n }\n\n @alwaysinline\n def remove(key string) {\n (self as dynamic).delete(key)\n }\n\n def each(x fn(string, T)) {\n (self as dynamic).forEach((value, key) => {\n x(key, value)\n })\n }\n}\n\nnamespace IntMap {\n @alwaysinline\n def new IntMap {\n return dynamic.Map.new\n }\n}\n\nclass IntMap {\n if JS_INLINE_NATIVE {\n @alwaysinline\n def [](key int) T {\n return (self as dynamic).get(key)\n }\n } else {\n def [](key int) T {\n assert(key in self)\n return (self as dynamic).get(key)\n }\n }\n\n def []=(key int, value T) T {\n (self as dynamic).set(key, value)\n return value\n }\n\n def {...}(key int, value T) IntMap {\n (self as dynamic).set(key, value)\n return self\n }\n\n @alwaysinline\n def in(key int) bool {\n return (self as dynamic).has(key)\n }\n\n @alwaysinline\n def count int {\n return (self as dynamic).size\n }\n\n @alwaysinline\n def isEmpty bool {\n return count == 0\n }\n\n def get(key int, defaultValue T) T {\n const value = (self as dynamic).get(key)\n return value != dynamic.void(0) ? value : defaultValue # Compare against undefined so the key is only hashed once for speed\n }\n\n @alwaysinline\n def keys List {\n return dynamic.Array.from((self as dynamic).keys())\n }\n\n @alwaysinline\n def values List {\n return dynamic.Array.from((self as dynamic).values())\n }\n\n @alwaysinline\n def clone IntMap {\n return dynamic.Map.new(self)\n }\n\n @alwaysinline\n def remove(key int) {\n (self as dynamic).delete(key)\n }\n\n def each(x fn(int, T)) {\n (self as dynamic).forEach((value, key) => {\n x(key, value)\n })\n }\n}\n'; Skew.NATIVE_LIBRARY = '\nconst RELEASE = false\nconst ASSERTS = !RELEASE\n\nenum Target {\n NONE\n CPLUSPLUS\n CSHARP\n JAVASCRIPT\n}\n\nconst TARGET Target = .NONE\n\ndef @alwaysinline\ndef @deprecated\ndef @deprecated(message string)\ndef @entry\ndef @export\ndef @import\ndef @neverinline\ndef @prefer\ndef @rename(name string)\ndef @skip\ndef @spreads\n\n@spreads {\n def @using(name string) # For use with C#\n def @include(name string) # For use with C++\n}\n\n@import if TARGET == .NONE\n@skip if !ASSERTS\ndef assert(truth bool)\n\n@import if TARGET == .NONE\nnamespace Math {\n @prefer\n def abs(x double) double\n def abs(x int) int\n\n def acos(x double) double\n def asin(x double) double\n def atan(x double) double\n def atan2(x double, y double) double\n\n def sin(x double) double\n def cos(x double) double\n def tan(x double) double\n\n def floor(x double) double\n def ceil(x double) double\n def round(x double) double\n\n def exp(x double) double\n def log(x double) double\n def pow(x double, y double) double\n def random double\n def randomInRange(min double, max double) double\n def randomInRange(minInclusive int, maxExclusive int) int\n def sqrt(x double) double\n\n @prefer {\n def max(x double, y double) double\n def min(x double, y double) double\n def max(x double, y double, z double) double\n def min(x double, y double, z double) double\n def max(x double, y double, z double, w double) double\n def min(x double, y double, z double, w double) double\n def clamp(x double, min double, max double) double\n }\n\n def max(x int, y int) int\n def min(x int, y int) int\n def max(x int, y int, z int) int\n def min(x int, y int, z int) int\n def max(x int, y int, z int, w int) int\n def min(x int, y int, z int, w int) int\n def clamp(x int, min int, max int) int\n\n def E double { return 2.718281828459045 }\n def INFINITY double { return 1 / 0.0 }\n def NAN double { return 0 / 0.0 }\n def PI double { return 3.141592653589793 }\n def SQRT_2 double { return 2 ** 0.5 }\n}\n\n@import\nclass bool {\n def ! bool\n def toString string\n}\n\n@import\nclass int {\n def + int\n def - int\n def ~ int\n\n def +(x int) int\n def -(x int) int\n def *(x int) int\n def /(x int) int\n def %(x int) int\n def **(x int) int\n def <=>(x int) int\n def <<(x int) int\n def >>(x int) int\n def >>>(x int) int\n def &(x int) int\n def |(x int) int\n def ^(x int) int\n\n def %%(x int) int {\n return ((self % x) + x) % x\n }\n\n def <<=(x int) int\n def >>=(x int) int\n def &=(x int) int\n def |=(x int) int\n def ^=(x int) int\n\n if TARGET != .CSHARP && TARGET != .CPLUSPLUS {\n def >>>=(x int)\n }\n\n if TARGET != .JAVASCRIPT {\n def ++ int\n def -- int\n\n def %=(x int) int\n def +=(x int) int\n def -=(x int) int\n def *=(x int) int\n def /=(x int) int\n }\n\n def toString string\n}\n\nnamespace int {\n def MIN int { return -0x7FFFFFFF - 1 }\n def MAX int { return 0x7FFFFFFF }\n}\n\n@import\nclass double {\n def + double\n def ++ double\n def - double\n def -- double\n\n def *(x double) double\n def +(x double) double\n def -(x double) double\n def /(x double) double\n def **(x double) double\n def <=>(x double) int\n\n def %%(x double) double {\n return self - Math.floor(self / x) * x\n }\n\n def *=(x double) double\n def +=(x double) double\n def -=(x double) double\n def /=(x double) double\n\n def isFinite bool\n def isNaN bool\n\n def toString string\n}\n\n@import\nclass string {\n def +(x string) string\n def +=(x string) string\n def <=>(x string) int\n\n def count int\n def in(x string) bool\n def indexOf(x string) int\n def lastIndexOf(x string) int\n def startsWith(x string) bool\n def endsWith(x string) bool\n\n def [](x int) int\n def get(x int) string\n def slice(start int) string\n def slice(start int, end int) string\n def codePoints List\n def codeUnits List\n\n def split(x string) List\n def join(x List) string\n def repeat(x int) string\n def replaceAll(before string, after string) string\n\n def toLowerCase string\n def toUpperCase string\n def toString string { return self }\n}\n\nnamespace string {\n def fromCodePoint(x int) string\n def fromCodePoints(x List) string\n def fromCodeUnit(x int) string\n def fromCodeUnits(x List) string\n}\n\n@import if TARGET == .NONE\nclass StringBuilder {\n def new\n def append(x string)\n def toString string\n}\n\n@import\nclass List {\n def new\n def [...](x T) List\n\n def [](x int) T\n def []=(x int, y T) T\n\n def count int\n def isEmpty bool\n def resize(count int, defaultValue T)\n\n @prefer\n def append(x T)\n def append(x List)\n def appendOne(x T)\n\n @prefer\n def prepend(x T)\n def prepend(x List)\n\n @prefer\n def insert(x int, value T)\n def insert(x int, values List)\n\n def removeAll(x T)\n def removeAt(x int)\n def removeDuplicates\n def removeFirst\n def removeIf(x fn(T) bool)\n def removeLast\n def removeOne(x T)\n def removeRange(start int, end int)\n\n def takeFirst T\n def takeLast T\n def takeAt(x int) T\n def takeRange(start int, end int) List\n\n def first T\n def first=(x T) T { return self[0] = x }\n def last T\n def last=(x T) T { return self[count - 1] = x }\n\n def in(x T) bool\n def indexOf(x T) int\n def lastIndexOf(x T) int\n\n def all(x fn(T) bool) bool\n def any(x fn(T) bool) bool\n def clone List\n def each(x fn(T))\n def equals(x List) bool\n def filter(x fn(T) bool) List\n def map(x fn(T) R) List\n def reverse\n def shuffle\n def slice(start int) List\n def slice(start int, end int) List\n def sort(x fn(T, T) int)\n def swap(x int, y int)\n}\n\n@import\nclass StringMap {\n def new\n def {...}(key string, value T) StringMap\n\n def [](key string) T\n def []=(key string, value T) T\n\n def count int\n def isEmpty bool\n def keys List\n def values List\n\n def clone StringMap\n def each(x fn(string, T))\n def get(key string, defaultValue T) T\n def in(key string) bool\n def remove(key string)\n}\n\n@import\nclass IntMap {\n def new\n def {...}(key int, value T) IntMap\n\n def [](key int) T\n def []=(key int, value T) T\n\n def count int\n def isEmpty bool\n def keys List\n def values List\n\n def clone IntMap\n def each(x fn(int, T))\n def get(key int, defaultValue T) T\n def in(key int) bool\n def remove(key int)\n}\n\nclass Box {\n var value T\n}\n\n################################################################################\n# Implementations\n\nclass int {\n def **(x int) int {\n var y = self\n var z = x < 0 ? 0 : 1\n while x > 0 {\n if (x & 1) != 0 { z *= y }\n x >>= 1\n y *= y\n }\n return z\n }\n\n def <=>(x int) int {\n return ((x < self) as int) - ((x > self) as int)\n }\n}\n\nclass double {\n @alwaysinline\n def **(x double) double {\n return Math.pow(self, x)\n }\n\n def <=>(x double) int {\n return ((x < self) as int) - ((x > self) as int)\n }\n}\n\nclass List {\n def resize(count int, defaultValue T) {\n assert(count >= 0)\n while self.count < count { append(defaultValue) }\n while self.count > count { removeLast }\n }\n\n def removeAll(value T) {\n var index = 0\n\n # Remove elements in place\n for i in 0..count {\n if self[i] != value {\n if index < i {\n self[index] = self[i]\n }\n index++\n }\n }\n\n # Shrink the array to the correct size\n while index < count {\n removeLast\n }\n }\n\n def removeDuplicates {\n var index = 0\n\n # Remove elements in place\n for i in 0..count {\n var found = false\n var value = self[i]\n for j in 0..i {\n if value == self[j] {\n found = true\n break\n }\n }\n if !found {\n if index < i {\n self[index] = self[i]\n }\n index++\n }\n }\n\n # Shrink the array to the correct size\n while index < count {\n removeLast\n }\n }\n\n def shuffle {\n var n = count\n for i in 0..n - 1 {\n swap(i, i + ((Math.random * (n - i)) as int))\n }\n }\n\n def swap(i int, j int) {\n assert(0 <= i && i < count)\n assert(0 <= j && j < count)\n var temp = self[i]\n self[i] = self[j]\n self[j] = temp\n }\n}\n\nnamespace Math {\n def randomInRange(min double, max double) double {\n assert(min <= max)\n var value = min + (max - min) * random\n assert(min <= value && value <= max)\n return value\n }\n\n def randomInRange(minInclusive int, maxExclusive int) int {\n assert(minInclusive <= maxExclusive)\n var value = minInclusive + ((maxExclusive - minInclusive) * random) as int\n assert(minInclusive <= value && (minInclusive != maxExclusive ? value < maxExclusive : value == maxExclusive))\n return value\n }\n\n if TARGET != .JAVASCRIPT {\n def max(x double, y double, z double) double {\n return max(max(x, y), z)\n }\n\n def min(x double, y double, z double) double {\n return min(min(x, y), z)\n }\n\n def max(x int, y int, z int) int {\n return max(max(x, y), z)\n }\n\n def min(x int, y int, z int) int {\n return min(min(x, y), z)\n }\n\n def max(x double, y double, z double, w double) double {\n return max(max(x, y), max(z, w))\n }\n\n def min(x double, y double, z double, w double) double {\n return min(min(x, y), min(z, w))\n }\n\n def max(x int, y int, z int, w int) int {\n return max(max(x, y), max(z, w))\n }\n\n def min(x int, y int, z int, w int) int {\n return min(min(x, y), min(z, w))\n }\n }\n\n def clamp(x double, min double, max double) double {\n return x < min ? min : x > max ? max : x\n }\n\n def clamp(x int, min int, max int) int {\n return x < min ? min : x > max ? max : x\n }\n}\n'; Skew.NATIVE_LIBRARY_CS = '\n@using("System.Diagnostics")\ndef assert(truth bool) {\n dynamic.Debug.Assert(truth)\n}\n\n@using("System")\nvar __random dynamic.Random = null\n\n@using("System")\n@import\nnamespace Math {\n @rename("Abs") if TARGET == .CSHARP\n def abs(x double) double\n @rename("Abs") if TARGET == .CSHARP\n def abs(x int) int\n\n @rename("Acos") if TARGET == .CSHARP\n def acos(x double) double\n @rename("Asin") if TARGET == .CSHARP\n def asin(x double) double\n @rename("Atan") if TARGET == .CSHARP\n def atan(x double) double\n @rename("Atan2") if TARGET == .CSHARP\n def atan2(x double, y double) double\n\n @rename("Sin") if TARGET == .CSHARP\n def sin(x double) double\n @rename("Cos") if TARGET == .CSHARP\n def cos(x double) double\n @rename("Tan") if TARGET == .CSHARP\n def tan(x double) double\n\n @rename("Floor") if TARGET == .CSHARP\n def floor(x double) double\n @rename("Ceiling") if TARGET == .CSHARP\n def ceil(x double) double\n @rename("Round") if TARGET == .CSHARP\n def round(x double) double\n\n @rename("Exp") if TARGET == .CSHARP\n def exp(x double) double\n @rename("Log") if TARGET == .CSHARP\n def log(x double) double\n @rename("Pow") if TARGET == .CSHARP\n def pow(x double, y double) double\n @rename("Sqrt") if TARGET == .CSHARP\n def sqrt(x double) double\n\n @rename("Max") if TARGET == .CSHARP\n def max(x double, y double) double\n @rename("Max") if TARGET == .CSHARP\n def max(x int, y int) int\n\n @rename("Min") if TARGET == .CSHARP\n def min(x double, y double) double\n @rename("Min") if TARGET == .CSHARP\n def min(x int, y int) int\n\n def random double {\n __random ?= dynamic.Random.new()\n return __random.NextDouble()\n }\n}\n\nclass double {\n def isFinite bool {\n return !isNaN && !dynamic.double.IsInfinity(self)\n }\n\n def isNaN bool {\n return dynamic.double.IsNaN(self)\n }\n}\n\n@using("System.Text")\n@import\nclass StringBuilder {\n @rename("Append")\n def append(x string)\n\n @rename("ToString")\n def toString string\n}\n\nclass bool {\n @rename("ToString")\n def toString string {\n return self ? "true" : "false"\n }\n}\n\nclass int {\n @rename("ToString")\n def toString string\n\n def >>>(x int) int {\n return dynamic.unchecked(self as dynamic.uint >> x) as int\n }\n}\n\nclass double {\n @rename("ToString")\n def toString string\n}\n\nclass string {\n @rename("CompareTo")\n def <=>(x string) int\n\n @rename("StartsWith")\n def startsWith(x string) bool\n\n @rename("EndsWith")\n def endsWith(x string) bool\n\n @rename("Contains")\n def in(x string) bool\n\n @rename("IndexOf")\n def indexOf(x string) int\n\n @rename("LastIndexOf")\n def lastIndexOf(x string) int\n\n @rename("Replace")\n def replaceAll(before string, after string) string\n\n @rename("Substring") {\n def slice(start int) string\n def slice(start int, end int) string\n }\n\n @rename("ToLower")\n def toLowerCase string\n\n @rename("ToUpper")\n def toUpperCase string\n\n def count int {\n return (self as dynamic).Length\n }\n\n def get(index int) string {\n return fromCodeUnit(self[index])\n }\n\n def repeat(times int) string {\n var result = ""\n for i in 0..times {\n result += self\n }\n return result\n }\n\n @using("System.Linq")\n @using("System")\n def split(separator string) List {\n var separators = [separator]\n return dynamic.Enumerable.ToList((self as dynamic).Split(dynamic.Enumerable.ToArray(separators as dynamic), dynamic.StringSplitOptions.None))\n }\n\n def join(parts List) string {\n return dynamic.string.Join(self, parts)\n }\n\n def slice(start int, end int) string {\n return (self as dynamic).Substring(start, end - start)\n }\n\n def codeUnits List {\n var result List = []\n for i in 0..count {\n result.append(self[i])\n }\n return result\n }\n}\n\nnamespace string {\n def fromCodeUnit(codeUnit int) string {\n return dynamic.string.new(codeUnit as dynamic.char, 1)\n }\n\n def fromCodeUnits(codeUnits List) string {\n var builder = StringBuilder.new\n for codeUnit in codeUnits {\n builder.append(codeUnit as dynamic.char)\n }\n return builder.toString\n }\n}\n\n@using("System.Collections.Generic")\nclass List {\n @rename("Contains")\n def in(x T) bool\n\n @rename("Add")\n def append(value T)\n\n @rename("AddRange")\n def append(value List)\n\n def sort(x fn(T, T) int) {\n # C# doesn\'t allow an anonymous function to be passed directly\n (self as dynamic).Sort((a T, b T) => x(a, b))\n }\n\n @rename("Reverse")\n def reverse\n\n @rename("RemoveAll")\n def removeIf(x fn(T) bool)\n\n @rename("RemoveAt")\n def removeAt(x int)\n\n @rename("Remove")\n def removeOne(x T)\n\n @rename("TrueForAll")\n def all(x fn(T) bool) bool\n\n @rename("ForEach")\n def each(x fn(T))\n\n @rename("FindAll")\n def filter(x fn(T) bool) List\n\n @rename("ConvertAll")\n def map(x fn(T) R) List\n\n @rename("IndexOf")\n def indexOf(x T) int\n\n @rename("LastIndexOf")\n def lastIndexOf(x T) int\n\n @rename("Insert")\n def insert(x int, value T)\n\n @rename("InsertRange")\n def insert(x int, value List)\n\n def appendOne(x T) {\n if !(x in self) {\n append(x)\n }\n }\n\n def removeRange(start int, end int) {\n (self as dynamic).RemoveRange(start, end - start)\n }\n\n @using("System.Linq") {\n @rename("SequenceEqual")\n def equals(x List) bool\n\n @rename("First")\n def first T\n\n @rename("Last")\n def last T\n }\n\n def any(callback fn(T) bool) bool {\n return !all(x => !callback(x))\n }\n\n def isEmpty bool {\n return count == 0\n }\n\n def count int {\n return (self as dynamic).Count\n }\n\n def prepend(value T) {\n insert(0, value)\n }\n\n def prepend(values List) {\n var count = values.count\n for i in 0..count {\n prepend(values[count - i - 1])\n }\n }\n\n def removeFirst {\n removeAt(0)\n }\n\n def removeLast {\n removeAt(count - 1)\n }\n\n def takeFirst T {\n var value = first\n removeFirst\n return value\n }\n\n def takeLast T {\n var value = last\n removeLast\n return value\n }\n\n def takeAt(x int) T {\n var value = self[x]\n removeAt(x)\n return value\n }\n\n def takeRange(start int, end int) List {\n var value = slice(start, end)\n removeRange(start, end)\n return value\n }\n\n def slice(start int) List {\n return slice(start, count)\n }\n\n def slice(start int, end int) List {\n return (self as dynamic).GetRange(start, end - start)\n }\n\n def clone List {\n var clone = new\n clone.append(self)\n return clone\n }\n}\n\n@using("System.Collections.Generic")\n@rename("Dictionary")\nclass StringMap {\n def count int {\n return (self as dynamic).Count\n }\n\n @rename("ContainsKey")\n def in(key string) bool\n\n @rename("Remove")\n def remove(key string)\n\n def isEmpty bool {\n return count == 0\n }\n\n def {...}(key string, value T) StringMap {\n (self as dynamic).Add(key, value)\n return self\n }\n\n def get(key string, value T) T {\n return key in self ? self[key] : value\n }\n\n def keys List {\n return dynamic.System.Linq.Enumerable.ToList((self as dynamic).Keys)\n }\n\n def values List {\n return dynamic.System.Linq.Enumerable.ToList((self as dynamic).Values)\n }\n\n def clone StringMap {\n var clone = new\n for key in keys {\n clone[key] = self[key]\n }\n return clone\n }\n\n def each(x fn(string, T)) {\n for pair in self as dynamic {\n x(pair.Key, pair.Value)\n }\n }\n}\n\n@using("System.Collections.Generic")\n@rename("Dictionary")\nclass IntMap {\n def count int {\n return (self as dynamic).Count\n }\n\n @rename("ContainsKey")\n def in(key int) bool\n\n @rename("Remove")\n def remove(key int)\n\n def isEmpty bool {\n return count == 0\n }\n\n def {...}(key int, value T) IntMap {\n (self as dynamic).Add(key, value)\n return self\n }\n\n def get(key int, value T) T {\n return key in self ? self[key] : value\n }\n\n def keys List {\n return dynamic.System.Linq.Enumerable.ToList((self as dynamic).Keys)\n }\n\n def values List {\n return dynamic.System.Linq.Enumerable.ToList((self as dynamic).Values)\n }\n\n def clone IntMap {\n var clone = new\n for key in keys {\n clone[key] = self[key]\n }\n return clone\n }\n\n def each(x fn(int, T)) {\n for pair in self as dynamic {\n x(pair.Key, pair.Value)\n }\n }\n}\n'; Skew.CSharpEmitter._isKeyword = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), 'abstract', 0), 'as', 0), 'base', 0), 'bool', 0), 'break', 0), 'byte', 0), 'case', 0), 'catch', 0), 'char', 0), 'checked', 0), 'class', 0), 'const', 0), 'continue', 0), 'decimal', 0), 'default', 0), 'delegate', 0), 'do', 0), 'double', 0), 'else', 0), 'enum', 0), 'event', 0), 'explicit', 0), 'extern', 0), 'false', 0), 'finally', 0), 'fixed', 0), 'float', 0), 'for', 0), 'foreach', 0), 'goto', 0), 'if', 0), 'implicit', 0), 'in', 0), 'int', 0), 'interface', 0), 'internal', 0), 'is', 0), 'lock', 0), 'long', 0), 'namespace', 0), 'new', 0), 'null', 0), 'object', 0), 'operator', 0), 'out', 0), 'override', 0), 'params', 0), 'private', 0), 'protected', 0), 'public', 0), 'readonly', 0), 'ref', 0), 'return', 0), 'sbyte', 0), 'sealed', 0), 'short', 0), 'sizeof', 0), 'stackalloc', 0), 'static', 0), 'string', 0), 'struct', 0), 'switch', 0), 'this', 0), 'throw', 0), 'true', 0), 'try', 0), 'typeof', 0), 'uint', 0), 'ulong', 0), 'unchecked', 0), 'unsafe', 0), 'ushort', 0), 'using', 0), 'virtual', 0), 'void', 0), 'volatile', 0), 'while', 0); Skew.JavaScriptEmitter._first = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$'; Skew.JavaScriptEmitter._rest = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$0123456789'; Skew.JavaScriptEmitter._isFunctionProperty = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), 'apply', 0), 'call', 0), 'length', 0), 'name', 0); Skew.JavaScriptEmitter._isKeyword = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), 'arguments', 0), 'await', 0), 'Boolean', 0), 'break', 0), 'case', 0), 'catch', 0), 'class', 0), 'const', 0), 'constructor', 0), 'continue', 0), 'Date', 0), 'debugger', 0), 'default', 0), 'delete', 0), 'do', 0), 'double', 0), 'else', 0), 'enum', 0), 'eval', 0), 'export', 0), 'extends', 0), 'false', 0), 'finally', 0), 'float', 0), 'for', 0), 'function', 0), 'Function', 0), 'if', 0), 'implements', 0), 'import', 0), 'in', 0), 'instanceof', 0), 'int', 0), 'interface', 0), 'let', 0), 'new', 0), 'null', 0), 'Number', 0), 'Object', 0), 'package', 0), 'private', 0), 'protected', 0), 'public', 0), 'return', 0), 'static', 0), 'String', 0), 'super', 0), 'switch', 0), 'this', 0), 'throw', 0), 'true', 0), 'try', 0), 'typeof', 0), 'var', 0), 'void', 0), 'while', 0), 'with', 0), 'yield', 0); Skew.JavaScriptEmitter._keywordCallMap = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), 'delete', 0), 'typeof', 0), 'void', 0); Skew.JavaScriptEmitter._specialVariableMap = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), '__asString', Skew.JavaScriptEmitter.SpecialVariable.AS_STRING), '__create', Skew.JavaScriptEmitter.SpecialVariable.CREATE), '__extends', Skew.JavaScriptEmitter.SpecialVariable.EXTENDS), '__imul', Skew.JavaScriptEmitter.SpecialVariable.MULTIPLY), '__isBool', Skew.JavaScriptEmitter.SpecialVariable.IS_BOOL), '__isDouble', Skew.JavaScriptEmitter.SpecialVariable.IS_DOUBLE), '__isInt', Skew.JavaScriptEmitter.SpecialVariable.IS_INT), '__isString', Skew.JavaScriptEmitter.SpecialVariable.IS_STRING), '__prototype', Skew.JavaScriptEmitter.SpecialVariable.PROTOTYPE); // https://github.com/Microsoft/TypeScript/issues/2536 Skew.TypeScriptEmitter._isKeyword = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), 'break', 0), 'case', 0), 'catch', 0), 'class', 0), 'const', 0), 'continue', 0), 'debugger', 0), 'default', 0), 'delete', 0), 'do', 0), 'else', 0), 'enum', 0), 'export', 0), 'extends', 0), 'false', 0), 'finally', 0), 'for', 0), 'function', 0), 'if', 0), 'implements', 0), 'import', 0), 'in', 0), 'instanceof', 0), 'interface', 0), 'namespace', 0), 'new', 0), 'null', 0), 'return', 0), 'super', 0), 'switch', 0), 'this', 0), 'throw', 0), 'true', 0), 'try', 0), 'typeof', 0), 'var', 0), 'void', 0), 'while', 0), 'with', 0), 'as', 0), 'implements', 0), 'interface', 0), 'let', 0), 'package', 0), 'private', 0), 'protected', 0), 'public', 0), 'static', 0), 'yield', 0), 'arguments', 0), 'Symbol', 0); Skew.TypeScriptEmitter._specialVariableMap = in_StringMap.insert(in_StringMap.insert(new Map(), '__asString', Skew.TypeScriptEmitter.SpecialVariable.AS_STRING), '__isInt', Skew.TypeScriptEmitter.SpecialVariable.IS_INT); Skew.CPlusPlusEmitter._isNative = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), 'auto', 0), 'bool', 0), 'char', 0), 'char16_t', 0), 'char32_t', 0), 'const_cast', 0), 'double', 0), 'dynamic_cast', 0), 'false', 0), 'float', 0), 'INFINITY', 0), 'int', 0), 'long', 0), 'NAN', 0), 'NULL', 0), 'nullptr', 0), 'reinterpret_cast', 0), 'short', 0), 'signed', 0), 'static_assert', 0), 'static_cast', 0), 'this', 0), 'true', 0), 'unsigned', 0), 'void', 0), 'wchar_t', 0); Skew.CPlusPlusEmitter._isKeyword = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), 'alignas', 0), 'alignof', 0), 'and', 0), 'and_eq', 0), 'asm', 0), 'bitand', 0), 'bitor', 0), 'break', 0), 'case', 0), 'catch', 0), 'class', 0), 'compl', 0), 'const', 0), 'constexpr', 0), 'continue', 0), 'decltype', 0), 'default', 0), 'delete', 0), 'do', 0), 'else', 0), 'enum', 0), 'explicit', 0), 'export', 0), 'extern', 0), 'for', 0), 'friend', 0), 'goto', 0), 'if', 0), 'inline', 0), 'mutable', 0), 'namespace', 0), 'new', 0), 'noexcept', 0), 'not', 0), 'not_eq', 0), 'operator', 0), 'or', 0), 'or_eq', 0), 'private', 0), 'protected', 0), 'public', 0), 'register', 0), 'return', 0), 'sizeof', 0), 'static', 0), 'struct', 0), 'switch', 0), 'template', 0), 'thread_local', 0), 'throw', 0), 'try', 0), 'typedef', 0), 'typeid', 0), 'typename', 0), 'union', 0), 'using', 0), 'virtual', 0), 'volatile', 0), 'while', 0), 'xor', 0), 'xor_eq', 0); Skew.Symbol._nextID = 0; Skew.Node._nextID = 0; Skew.Parsing.expressionParser = null; Skew.Parsing.typeParser = null; Skew.Parsing.identifierToSymbolKind = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), 'class', Skew.SymbolKind.OBJECT_CLASS), 'def', Skew.SymbolKind.FUNCTION_GLOBAL), 'enum', Skew.SymbolKind.OBJECT_ENUM), 'flags', Skew.SymbolKind.OBJECT_FLAGS), 'interface', Skew.SymbolKind.OBJECT_INTERFACE), 'namespace', Skew.SymbolKind.OBJECT_NAMESPACE), 'over', Skew.SymbolKind.FUNCTION_GLOBAL), 'type', Skew.SymbolKind.OBJECT_WRAPPED); Skew.Parsing.customOperators = in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(new Map(), Skew.TokenKind.ASSIGN_BITWISE_AND, 0), Skew.TokenKind.ASSIGN_BITWISE_OR, 0), Skew.TokenKind.ASSIGN_BITWISE_XOR, 0), Skew.TokenKind.ASSIGN_DIVIDE, 0), Skew.TokenKind.ASSIGN_INDEX, 0), Skew.TokenKind.ASSIGN_MINUS, 0), Skew.TokenKind.ASSIGN_MODULUS, 0), Skew.TokenKind.ASSIGN_MULTIPLY, 0), Skew.TokenKind.ASSIGN_PLUS, 0), Skew.TokenKind.ASSIGN_POWER, 0), Skew.TokenKind.ASSIGN_REMAINDER, 0), Skew.TokenKind.ASSIGN_SHIFT_LEFT, 0), Skew.TokenKind.ASSIGN_SHIFT_RIGHT, 0), Skew.TokenKind.ASSIGN_UNSIGNED_SHIFT_RIGHT, 0), Skew.TokenKind.BITWISE_AND, 0), Skew.TokenKind.BITWISE_OR, 0), Skew.TokenKind.BITWISE_XOR, 0), Skew.TokenKind.COMPARE, 0), Skew.TokenKind.DECREMENT, 0), Skew.TokenKind.DIVIDE, 0), Skew.TokenKind.IN, 0), Skew.TokenKind.INCREMENT, 0), Skew.TokenKind.INDEX, 0), Skew.TokenKind.LIST, 0), Skew.TokenKind.MINUS, 0), Skew.TokenKind.MODULUS, 0), Skew.TokenKind.MULTIPLY, 0), Skew.TokenKind.NOT, 0), Skew.TokenKind.PLUS, 0), Skew.TokenKind.POWER, 0), Skew.TokenKind.REMAINDER, 0), Skew.TokenKind.SET, 0), Skew.TokenKind.SHIFT_LEFT, 0), Skew.TokenKind.SHIFT_RIGHT, 0), Skew.TokenKind.TILDE, 0), Skew.TokenKind.UNSIGNED_SHIFT_RIGHT, 0), Skew.TokenKind.XML_CHILD, 0); Skew.Parsing.forbiddenCustomOperators = in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(new Map(), Skew.TokenKind.ASSIGN, Skew.Parsing.ForbiddenGroup.ASSIGN), Skew.TokenKind.EQUAL, Skew.Parsing.ForbiddenGroup.EQUAL), Skew.TokenKind.GREATER_THAN, Skew.Parsing.ForbiddenGroup.COMPARE), Skew.TokenKind.GREATER_THAN_OR_EQUAL, Skew.Parsing.ForbiddenGroup.COMPARE), Skew.TokenKind.LESS_THAN, Skew.Parsing.ForbiddenGroup.COMPARE), Skew.TokenKind.LESS_THAN_OR_EQUAL, Skew.Parsing.ForbiddenGroup.COMPARE), Skew.TokenKind.LOGICAL_AND, Skew.Parsing.ForbiddenGroup.LOGICAL), Skew.TokenKind.LOGICAL_OR, Skew.Parsing.ForbiddenGroup.LOGICAL), Skew.TokenKind.NOT_EQUAL, Skew.Parsing.ForbiddenGroup.EQUAL); // These are prefixed with "the operator \"...\" is not customizable because " Skew.Parsing.forbiddenGroupDescription = in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(new Map(), Skew.Parsing.ForbiddenGroup.ASSIGN, 'value types are not supported by the language'), Skew.Parsing.ForbiddenGroup.COMPARE, 'it\'s automatically implemented using the "<=>" operator (customize the "<=>" operator instead)'), Skew.Parsing.ForbiddenGroup.EQUAL, "that wouldn't work with generics, which are implemented with type erasure"), Skew.Parsing.ForbiddenGroup.LOGICAL, 'of its special short-circuit evaluation behavior'); Skew.Parsing.assignmentOperators = in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(new Map(), Skew.TokenKind.ASSIGN, Skew.NodeKind.ASSIGN), Skew.TokenKind.ASSIGN_BITWISE_AND, Skew.NodeKind.ASSIGN_BITWISE_AND), Skew.TokenKind.ASSIGN_BITWISE_OR, Skew.NodeKind.ASSIGN_BITWISE_OR), Skew.TokenKind.ASSIGN_BITWISE_XOR, Skew.NodeKind.ASSIGN_BITWISE_XOR), Skew.TokenKind.ASSIGN_DIVIDE, Skew.NodeKind.ASSIGN_DIVIDE), Skew.TokenKind.ASSIGN_MINUS, Skew.NodeKind.ASSIGN_SUBTRACT), Skew.TokenKind.ASSIGN_MODULUS, Skew.NodeKind.ASSIGN_MODULUS), Skew.TokenKind.ASSIGN_MULTIPLY, Skew.NodeKind.ASSIGN_MULTIPLY), Skew.TokenKind.ASSIGN_PLUS, Skew.NodeKind.ASSIGN_ADD), Skew.TokenKind.ASSIGN_POWER, Skew.NodeKind.ASSIGN_POWER), Skew.TokenKind.ASSIGN_REMAINDER, Skew.NodeKind.ASSIGN_REMAINDER), Skew.TokenKind.ASSIGN_SHIFT_LEFT, Skew.NodeKind.ASSIGN_SHIFT_LEFT), Skew.TokenKind.ASSIGN_SHIFT_RIGHT, Skew.NodeKind.ASSIGN_SHIFT_RIGHT), Skew.TokenKind.ASSIGN_UNSIGNED_SHIFT_RIGHT, Skew.NodeKind.ASSIGN_UNSIGNED_SHIFT_RIGHT); Skew.Parsing.dotInfixParselet = function(context, left) { var innerComments = context.stealComments(); var token = context.next(); var range = context.current().range; if (!context.expect(Skew.TokenKind.IDENTIFIER)) { // Create a empty range instead of using createParseError so IDE code completion still works range = new Skew.Range(token.range.source, token.range.end, token.range.end); } return new Skew.Node(Skew.NodeKind.DOT).withContent(new Skew.StringContent(range.toString())).appendChild(left).withRange(context.spanSince(left.range)).withInternalRange(range).withInnerComments(innerComments); }; Skew.Parsing.initializerParselet = function(context) { var token = context.next(); var kind = token.kind == Skew.TokenKind.LEFT_BRACE ? Skew.NodeKind.INITIALIZER_MAP : Skew.NodeKind.INITIALIZER_LIST; var node = Skew.Node.createInitializer(kind); if (token.kind == Skew.TokenKind.LEFT_BRACE || token.kind == Skew.TokenKind.LEFT_BRACKET) { var expectColon = kind != Skew.NodeKind.INITIALIZER_LIST; var end = expectColon ? Skew.TokenKind.RIGHT_BRACE : Skew.TokenKind.RIGHT_BRACKET; while (true) { context.eat(Skew.TokenKind.NEWLINE); var comments = context.stealComments(); if (context.peek1(end)) { Skew.Parsing._warnAboutIgnoredComments(context, comments); break; } var first = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); var colon = context.current(); if (!expectColon) { node.appendChild(first); } else { if (!context.expect(Skew.TokenKind.COLON)) { break; } var second = Skew.Parsing.expressionParser.parse(context, Skew.Precedence.LOWEST); first = Skew.Node.createPair(first, second).withRange(Skew.Range.span(first.range, second.range)).withInternalRange(colon.range); node.appendChild(first); } first.comments = Skew.Comment.concat(comments, Skew.Parsing.parseTrailingComment(context)); if (!context.eat(Skew.TokenKind.COMMA)) { break; } first.comments = Skew.Comment.concat(first.comments, Skew.Parsing.parseTrailingComment(context)); } context.skipWhitespace(); Skew.Parsing.scanForToken(context, end); } else if (token.kind == Skew.TokenKind.LIST_NEW || token.kind == Skew.TokenKind.SET_NEW) { node.appendChild(new Skew.Node(Skew.NodeKind.NAME).withContent(new Skew.StringContent('new')).withRange(new Skew.Range(token.range.source, token.range.start + 1 | 0, token.range.end - 1 | 0))); } return node.withRange(context.spanSince(token.range)); }; Skew.Parsing.parameterizedParselet = function(context, left) { var value = Skew.Node.createParameterize(left); var token = context.next(); while (true) { var type = Skew.Parsing.typeParser.parse(context, Skew.Precedence.LOWEST); value.appendChild(type); if (!context.eat(Skew.TokenKind.COMMA)) { break; } } Skew.Parsing.scanForToken(context, Skew.TokenKind.PARAMETER_LIST_END); return value.withRange(context.spanSince(left.range)).withInternalRange(context.spanSince(token.range)); }; Skew.Resolving.Resolver._annotationSymbolFlags = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), '@alwaysinline', Skew.SymbolFlags.IS_INLINING_FORCED), '@deprecated', Skew.SymbolFlags.IS_DEPRECATED), '@entry', Skew.SymbolFlags.IS_ENTRY_POINT), '@export', Skew.SymbolFlags.IS_EXPORTED), '@import', Skew.SymbolFlags.IS_IMPORTED), '@neverinline', Skew.SymbolFlags.IS_INLINING_PREVENTED), '@prefer', Skew.SymbolFlags.IS_PREFERRED), '@rename', Skew.SymbolFlags.IS_RENAMED), '@skip', Skew.SymbolFlags.IS_SKIPPED), '@spreads', Skew.SymbolFlags.SHOULD_SPREAD); Skew.LambdaConversion.Scope._nextID = 0; Skew.Type.DYNAMIC = new Skew.Type(Skew.TypeKind.SPECIAL, null); Skew.Type.NULL = new Skew.Type(Skew.TypeKind.SPECIAL, null); Skew.Type._nextID = 0; Skew.Environment._nextID = 0; Skew.Renaming.unaryPrefixes = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), '!', 'not'), '+', 'positive'), '++', 'increment'), '-', 'negative'), '--', 'decrement'), '~', 'complement'); Skew.Renaming.prefixes = in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(in_StringMap.insert(new Map(), '%', 'remainder'), '%%', 'modulus'), '&', 'and'), '*', 'multiply'), '**', 'power'), '+', 'add'), '-', 'subtract'), '/', 'divide'), '<<', 'leftShift'), '<=>', 'compare'), '>>', 'rightShift'), '>>>', 'unsignedRightShift'), '^', 'xor'), 'in', 'contains'), '|', 'or'), '%%=', 'modulusUpdate'), '%=', 'remainderUpdate'), '&=', 'andUpdate'), '**=', 'powerUpdate'), '*=', 'multiplyUpdate'), '+=', 'addUpdate'), '-=', 'subtractUpdate'), '/=', 'divideUpdate'), '<<=', 'leftShiftUpdate'), '>>=', 'rightShiftUpdate'), '^=', 'xorUpdate'), '|=', 'orUpdate'), '[]', 'get'), '[]=', 'set'), '<>...', 'append'), '[...]', 'append'), '[new]', 'new'), '{...}', 'insert'), '{new}', 'new'); Skew.in_PassKind._strings = ['EMITTING', 'LEXING', 'PARSING', 'RESOLVING', 'LAMBDA_CONVERSION', 'CALL_GRAPH', 'INLINING', 'FOLDING', 'MOTION', 'GLOBALIZING', 'MERGING', 'INTERFACE_REMOVAL', 'RENAMING']; Skew.in_SymbolKind._strings = ['PARAMETER_FUNCTION', 'PARAMETER_OBJECT', 'OBJECT_CLASS', 'OBJECT_ENUM', 'OBJECT_FLAGS', 'OBJECT_GLOBAL', 'OBJECT_INTERFACE', 'OBJECT_NAMESPACE', 'OBJECT_WRAPPED', 'FUNCTION_ANNOTATION', 'FUNCTION_CONSTRUCTOR', 'FUNCTION_GLOBAL', 'FUNCTION_INSTANCE', 'FUNCTION_LOCAL', 'OVERLOADED_ANNOTATION', 'OVERLOADED_GLOBAL', 'OVERLOADED_INSTANCE', 'VARIABLE_ARGUMENT', 'VARIABLE_ENUM_OR_FLAGS', 'VARIABLE_GLOBAL', 'VARIABLE_INSTANCE', 'VARIABLE_LOCAL']; Skew.in_NodeKind._strings = ['ANNOTATION', 'BLOCK', 'CASE', 'CATCH', 'VARIABLE', 'BREAK', 'COMMENT_BLOCK', 'CONTINUE', 'EXPRESSION', 'FOR', 'FOREACH', 'IF', 'RETURN', 'SWITCH', 'THROW', 'TRY', 'VARIABLES', 'WHILE', 'ASSIGN_INDEX', 'CALL', 'CAST', 'CONSTANT', 'DOT', 'HOOK', 'INDEX', 'INITIALIZER_LIST', 'INITIALIZER_MAP', 'LAMBDA', 'LAMBDA_TYPE', 'NAME', 'NULL', 'NULL_DOT', 'PAIR', 'PARAMETERIZE', 'PARSE_ERROR', 'SEQUENCE', 'STRING_INTERPOLATION', 'SUPER', 'TYPE', 'TYPE_CHECK', 'XML', 'COMPLEMENT', 'NEGATIVE', 'NOT', 'POSITIVE', 'POSTFIX_DECREMENT', 'POSTFIX_INCREMENT', 'PREFIX_DECREMENT', 'PREFIX_INCREMENT', 'ADD', 'BITWISE_AND', 'BITWISE_OR', 'BITWISE_XOR', 'COMPARE', 'DIVIDE', 'EQUAL', 'IN', 'LOGICAL_AND', 'LOGICAL_OR', 'MODULUS', 'MULTIPLY', 'NOT_EQUAL', 'NULL_JOIN', 'POWER', 'REMAINDER', 'SHIFT_LEFT', 'SHIFT_RIGHT', 'SUBTRACT', 'UNSIGNED_SHIFT_RIGHT', 'GREATER_THAN', 'GREATER_THAN_OR_EQUAL', 'LESS_THAN', 'LESS_THAN_OR_EQUAL', 'ASSIGN', 'ASSIGN_ADD', 'ASSIGN_BITWISE_AND', 'ASSIGN_BITWISE_OR', 'ASSIGN_BITWISE_XOR', 'ASSIGN_DIVIDE', 'ASSIGN_MODULUS', 'ASSIGN_MULTIPLY', 'ASSIGN_NULL', 'ASSIGN_POWER', 'ASSIGN_REMAINDER', 'ASSIGN_SHIFT_LEFT', 'ASSIGN_SHIFT_RIGHT', 'ASSIGN_SUBTRACT', 'ASSIGN_UNSIGNED_SHIFT_RIGHT', 'A', 'B', 'C', 'D', 'E', 'F', 'G']; Skew.in_TokenKind._toString = in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(in_IntMap.insert(new Map(), Skew.TokenKind.COMMENT, 'comment'), Skew.TokenKind.NEWLINE, 'newline'), Skew.TokenKind.WHITESPACE, 'whitespace'), Skew.TokenKind.AS, '"as"'), Skew.TokenKind.BREAK, '"break"'), Skew.TokenKind.CASE, '"case"'), Skew.TokenKind.CATCH, '"catch"'), Skew.TokenKind.CONST, '"const"'), Skew.TokenKind.CONTINUE, '"continue"'), Skew.TokenKind.DEFAULT, '"default"'), Skew.TokenKind.DYNAMIC, '"dynamic"'), Skew.TokenKind.ELSE, '"else"'), Skew.TokenKind.FALSE, '"false"'), Skew.TokenKind.FINALLY, '"finally"'), Skew.TokenKind.FOR, '"for"'), Skew.TokenKind.IF, '"if"'), Skew.TokenKind.IN, '"in"'), Skew.TokenKind.IS, '"is"'), Skew.TokenKind.NULL, '"null"'), Skew.TokenKind.RETURN, '"return"'), Skew.TokenKind.SUPER, '"super"'), Skew.TokenKind.SWITCH, '"switch"'), Skew.TokenKind.THROW, '"throw"'), Skew.TokenKind.TRUE, '"true"'), Skew.TokenKind.TRY, '"try"'), Skew.TokenKind.VAR, '"var"'), Skew.TokenKind.WHILE, '"while"'), Skew.TokenKind.ARROW, '"=>"'), Skew.TokenKind.ASSIGN, '"="'), Skew.TokenKind.ASSIGN_BITWISE_AND, '"&="'), Skew.TokenKind.ASSIGN_BITWISE_OR, '"|="'), Skew.TokenKind.ASSIGN_BITWISE_XOR, '"^="'), Skew.TokenKind.ASSIGN_DIVIDE, '"/="'), Skew.TokenKind.ASSIGN_INDEX, '"[]="'), Skew.TokenKind.ASSIGN_MINUS, '"-="'), Skew.TokenKind.ASSIGN_MODULUS, '"%%="'), Skew.TokenKind.ASSIGN_MULTIPLY, '"*="'), Skew.TokenKind.ASSIGN_PLUS, '"+="'), Skew.TokenKind.ASSIGN_POWER, '"**="'), Skew.TokenKind.ASSIGN_REMAINDER, '"%="'), Skew.TokenKind.ASSIGN_SHIFT_LEFT, '"<<="'), Skew.TokenKind.ASSIGN_SHIFT_RIGHT, '">>="'), Skew.TokenKind.ASSIGN_UNSIGNED_SHIFT_RIGHT, '">>>="'), Skew.TokenKind.BITWISE_AND, '"&"'), Skew.TokenKind.BITWISE_OR, '"|"'), Skew.TokenKind.BITWISE_XOR, '"^"'), Skew.TokenKind.COLON, '":"'), Skew.TokenKind.COMMA, '","'), Skew.TokenKind.COMPARE, '"<=>"'), Skew.TokenKind.DECREMENT, '"--"'), Skew.TokenKind.DIVIDE, '"/"'), Skew.TokenKind.DOT, '"."'), Skew.TokenKind.DOT_DOT, '".."'), Skew.TokenKind.DOUBLE_COLON, '"::"'), Skew.TokenKind.EQUAL, '"=="'), Skew.TokenKind.GREATER_THAN, '">"'), Skew.TokenKind.GREATER_THAN_OR_EQUAL, '">="'), Skew.TokenKind.INCREMENT, '"++"'), Skew.TokenKind.INDEX, '"[]"'), Skew.TokenKind.LEFT_BRACE, '"{"'), Skew.TokenKind.LEFT_BRACKET, '"["'), Skew.TokenKind.LEFT_PARENTHESIS, '"("'), Skew.TokenKind.LESS_THAN, '"<"'), Skew.TokenKind.LESS_THAN_OR_EQUAL, '"<="'), Skew.TokenKind.LIST, '"[...]"'), Skew.TokenKind.LIST_NEW, '"[new]"'), Skew.TokenKind.LOGICAL_AND, '"&&"'), Skew.TokenKind.LOGICAL_OR, '"||"'), Skew.TokenKind.MINUS, '"-"'), Skew.TokenKind.MODULUS, '"%%"'), Skew.TokenKind.MULTIPLY, '"*"'), Skew.TokenKind.NOT, '"!"'), Skew.TokenKind.NOT_EQUAL, '"!="'), Skew.TokenKind.NULL_DOT, '"?."'), Skew.TokenKind.NULL_JOIN, '"??"'), Skew.TokenKind.PLUS, '"+"'), Skew.TokenKind.POWER, '"**"'), Skew.TokenKind.QUESTION_MARK, '"?"'), Skew.TokenKind.REMAINDER, '"%"'), Skew.TokenKind.RIGHT_BRACE, '"}"'), Skew.TokenKind.RIGHT_BRACKET, '"]"'), Skew.TokenKind.RIGHT_PARENTHESIS, '")"'), Skew.TokenKind.SEMICOLON, '";"'), Skew.TokenKind.SET, '"{...}"'), Skew.TokenKind.SET_NEW, '"{new}"'), Skew.TokenKind.SHIFT_LEFT, '"<<"'), Skew.TokenKind.SHIFT_RIGHT, '">>"'), Skew.TokenKind.TILDE, '"~"'), Skew.TokenKind.UNSIGNED_SHIFT_RIGHT, '">>>"'), Skew.TokenKind.ANNOTATION, 'annotation'), Skew.TokenKind.CHARACTER, 'character'), Skew.TokenKind.DOUBLE, 'double'), Skew.TokenKind.END_OF_FILE, 'end of input'), Skew.TokenKind.IDENTIFIER, 'identifier'), Skew.TokenKind.INT, 'integer'), Skew.TokenKind.INT_BINARY, 'integer'), Skew.TokenKind.INT_HEX, 'integer'), Skew.TokenKind.INT_OCTAL, 'integer'), Skew.TokenKind.STRING, 'string'), Skew.TokenKind.PARAMETER_LIST_END, '">"'), Skew.TokenKind.PARAMETER_LIST_START, '"<"'), Skew.TokenKind.XML_CHILD, '"<>..."'), Skew.TokenKind.XML_END, '">"'), Skew.TokenKind.XML_END_EMPTY, '"/>"'), Skew.TokenKind.XML_START, '"<"'), Skew.TokenKind.XML_START_CLOSE, '") { sources.prepend(Source.new("", NATIVE_LIBRARY_CPP)) } over createEmitter(context PassContext) Emitter { return CPlusPlusEmitter.new(context.options, context.cache) } } class CPlusPlusEmitter : Emitter { const _options CompilerOptions const _cache TypeCache var _previousNode Node = null var _previousSymbol Symbol = null var _namespaceStack List = [] var _symbolsCheckedForInclude IntMap = {} var _includeNames StringMap = {} var _loopLabels IntMap = {} var _enclosingFunction FunctionSymbol = null var _dummyFunction = FunctionSymbol.new(.FUNCTION_INSTANCE, "") over visit(global ObjectSymbol) { # Generate the entry point var entryPoint = _cache.entryPointSymbol if entryPoint != null { entryPoint.name = "main" # The entry point must not be in a namespace if entryPoint.parent != global { entryPoint.parent.asObjectSymbol.functions.removeOne(entryPoint) entryPoint.parent = global global.functions.append(entryPoint) } # The entry point in C++ takes an array, not a list if entryPoint.arguments.count == 1 { var argument = entryPoint.arguments.first var argc = VariableSymbol.new(.VARIABLE_ARGUMENT, entryPoint.scope.generateName("argc")) var argv = VariableSymbol.new(.VARIABLE_ARGUMENT, entryPoint.scope.generateName("argv")) argc.initializeWithType(_cache.intType) argv.type = Node.createName("char**").withType(.DYNAMIC) argv.resolvedType = .DYNAMIC argv.state = .INITIALIZED entryPoint.arguments = [argc, argv] entryPoint.resolvedType.argumentTypes = [argc.resolvedType, argv.resolvedType] # Create the list from the array if entryPoint.block != null { var advance = Node.createName("*\(argv.name)++").withType(.DYNAMIC) var block = Node.createBlock.appendChild(Node.createExpression(Node.createCall(Node.createDot( Node.createSymbolReference(argument), "append").withType(.DYNAMIC)).withType(.DYNAMIC).appendChild(advance))) var check = Node.createIf(advance.clone, Node.createBlock.appendChild( Node.createWhile(Node.createName("*\(argv.name)").withType(.DYNAMIC), block)), null) argument.kind = .VARIABLE_LOCAL argument.value = Node.createCall(Node.createDot(Node.createType(argument.resolvedType), "new").withType(.DYNAMIC)).withType(.DYNAMIC) entryPoint.block.prependChild(check) entryPoint.block.prependChild(Node.createVariables.appendChild(Node.createVariable(argument))) } } } # Make sure strings reference the right namespace. This has to be set # here instead of in the library code to avoid a cyclic definition. _cache.stringType.symbol.name = "Skew::string" # Avoid emitting unnecessary stuff shakingPass(global, _cache.entryPointSymbol, .USE_TYPES) _markVirtualFunctions(global) # Nested types in C++ can't be forward declared var sorted = _sortedObjects(global) for symbol in sorted { _moveNestedObjectToEnclosingNamespace(symbol) } # Emit code in passes to deal with C++'s forward declarations for symbol in sorted { _declareObject(symbol) } for symbol in sorted { _defineObject(symbol) } _adjustNamespace(null) for symbol in sorted { if !symbol.isImported { for variable in symbol.variables { if variable.kind == .VARIABLE_GLOBAL { _emitVariable(variable, .IMPLEMENT) } } } } _adjustNamespace(null) for symbol in sorted { if !symbol.isImported { for function in symbol.functions { _emitFunction(function, .IMPLEMENT) } _emitMarkFunction(symbol, .IMPLEMENT) } } _finalizeEmittedFile _createSource(_options.outputFile, .ALWAYS_EMIT) } enum CodeMode { DECLARE DEFINE IMPLEMENT } def _emitNewlineBeforeSymbol(symbol Symbol, mode CodeMode) { if _previousSymbol != null && (!_previousSymbol.kind.isVariable || !symbol.kind.isVariable || symbol.comments != null) && (mode != .DEFINE || !_previousSymbol.kind.isFunction || !symbol.kind.isFunction || symbol.comments != null) && (mode != .DECLARE || _previousSymbol.kind != .OBJECT_CLASS || symbol.kind != .OBJECT_CLASS) { _emit("\n") } _previousSymbol = null } def _emitNewlineAfterSymbol(symbol Symbol) { _previousSymbol = symbol } def _emitNewlineBeforeStatement(node Node) { if _previousNode != null && (node.comments != null || !_isCompactNodeKind(_previousNode.kind) || !_isCompactNodeKind(node.kind)) { _emit("\n") } _previousNode = null } def _emitNewlineAfterStatement(node Node) { _previousNode = node } def _adjustNamespace(symbol Symbol) { # Get the namespace chain for this symbol var symbols List = [] while symbol != null && symbol.kind != .OBJECT_GLOBAL { if symbol.kind == .OBJECT_NAMESPACE || symbol.kind == .OBJECT_WRAPPED { symbols.prepend(symbol) } symbol = symbol.parent } # Find the intersection var limit = Math.min(_namespaceStack.count, symbols.count) var i = 0 while i < limit { if _namespaceStack[i] != symbols[i] { break } i++ } # Leave the old namespace while _namespaceStack.count > i { var object = _namespaceStack.takeLast _decreaseIndent _emit(_indent + "}\n") _emitNewlineAfterSymbol(object) } # Enter the new namespace while _namespaceStack.count < symbols.count { var object = symbols[_namespaceStack.count] _emitNewlineBeforeSymbol(object, .DEFINE) _emit(_indent + "namespace " + _mangleName(object) + " {\n") _increaseIndent _namespaceStack.append(object) } } def _emitComments(comments List) { if comments != null { for comment in comments { for line in comment.lines { _emit(_indent + "//" + line + "\n") } if comment.hasGapBelow { _emit("\n") } } } } def _moveNestedObjectToEnclosingNamespace(symbol ObjectSymbol) { var parent = symbol.parent while parent != null && parent.kind == .OBJECT_CLASS { parent = parent.parent } if symbol.parent != parent { symbol.parent.asObjectSymbol.objects.removeOne(symbol) symbol.parent = parent parent.asObjectSymbol.objects.append(symbol) } } def _declareObject(symbol ObjectSymbol) { if symbol.isImported { return } switch symbol.kind { case .OBJECT_ENUM, .OBJECT_FLAGS { _adjustNamespace(symbol) _emitNewlineBeforeSymbol(symbol, .DECLARE) if symbol.kind == .OBJECT_FLAGS { _emit(_indent + "namespace " + _mangleName(symbol) + " {\n") _increaseIndent if !symbol.variables.isEmpty { _emit(_indent + "enum {\n") } } else { _emit(_indent + "enum struct " + _mangleName(symbol) + " {\n") } _increaseIndent for variable in symbol.variables { _emitVariable(variable, .DECLARE) } _decreaseIndent if symbol.kind == .OBJECT_FLAGS { if !symbol.variables.isEmpty { _emit(_indent + "};\n") } _decreaseIndent _emit(_indent + "}\n") } else { _emit(_indent + "};\n") } _emitNewlineAfterSymbol(symbol) } case .OBJECT_CLASS, .OBJECT_INTERFACE { _adjustNamespace(symbol) _emitNewlineBeforeSymbol(symbol, .DECLARE) _emitTypeParameters(symbol.parameters) _emit(_indent + "struct " + _mangleName(symbol) + ";\n") _emitNewlineAfterSymbol(symbol) } } } def _defineObject(symbol ObjectSymbol) { if symbol.isImported { return } switch symbol.kind { case .OBJECT_CLASS, .OBJECT_INTERFACE { _adjustNamespace(symbol) _emitNewlineBeforeSymbol(symbol, .DEFINE) _emitComments(symbol.comments) _emitTypeParameters(symbol.parameters) _emit(_indent + "struct " + _mangleName(symbol)) if symbol.extends != null || symbol.implements != null { _emit(" : ") if symbol.extends != null { _emitExpressionOrType(symbol.extends, symbol.baseType, .BARE) } if symbol.implements != null { for node in symbol.implements { if node != symbol.implements.first || symbol.extends != null { _emit(", ") } _emitExpressionOrType(node, node.resolvedType, .BARE) } } } else { _emit(" : virtual Skew::Object") } _emit(" {\n") _increaseIndent for function in symbol.functions { _emitFunction(function, .DEFINE) } for variable in symbol.variables { _emitVariable(variable, .DEFINE) } _emitMarkFunction(symbol, .DEFINE) _decreaseIndent _emit(_indent + "};\n") _emitNewlineAfterSymbol(symbol) } case .OBJECT_NAMESPACE, .OBJECT_WRAPPED { _adjustNamespace(symbol) for function in symbol.functions { _emitFunction(function, .DEFINE) } for variable in symbol.variables { _emitVariable(variable, .DEFINE) } } } } def _emitEnclosingSymbolPrefix(parent ObjectSymbol) { _emit(_fullName(parent)) if parent.parameters != null { _emit("<") for parameter in parent.parameters { if parameter != parent.parameters.first { _emit(", ") } _emit(_mangleName(parameter)) } _emit(">") } _emit("::") } def _emitFunction(symbol FunctionSymbol, mode CodeMode) { var parent = symbol.parent.asObjectSymbol var block = symbol.block if symbol.isImported || mode == .IMPLEMENT && block == null { return } # We can't use lambdas in C++ since they don't have the right semantics so no variable insertion is needed if symbol.this != null { symbol.this.name = "this" symbol.this.flags |= .IS_EXPORTED } _enclosingFunction = symbol _emitNewlineBeforeSymbol(symbol, mode) _emitComments(symbol.comments) if mode == .IMPLEMENT { _emitTypeParameters(parent.parameters) # TODO: Merge these with the ones on the symbol when symbols have both } _emitTypeParameters(symbol.parameters) _emit(_indent) if mode == .DEFINE && symbol.kind == .FUNCTION_GLOBAL && symbol.parent.kind == .OBJECT_CLASS { _emit("static ") } if symbol.kind != .FUNCTION_CONSTRUCTOR { if mode == .DEFINE && (symbol.isVirtual || block == null) { _emit("virtual ") } _emitExpressionOrType(symbol.returnType, symbol.resolvedType.returnType, .DECLARATION) } if mode == .IMPLEMENT && parent.kind != .OBJECT_GLOBAL { _emitEnclosingSymbolPrefix(parent) } _emit(_mangleName(symbol)) _emitArgumentList(symbol) if mode == .IMPLEMENT { # Move the super constructor call out of the function body if symbol.kind == .FUNCTION_CONSTRUCTOR && block.hasChildren { var first = block.firstChild if first.kind == .EXPRESSION { var call = first.expressionValue if call.kind == .CALL && call.callValue.kind == .SUPER { _emit(" : ") first.remove _emitExpression(call, .LOWEST) } } } _emitBlock(block) _emit("\n") } else { if symbol.overridden != null && symbol.kind != .FUNCTION_CONSTRUCTOR { _emit(" override") } if block == null { _emit(" = 0") } _emit(";\n") } _emitNewlineAfterSymbol(symbol) _enclosingFunction = null } def _emitMarkFunction(symbol ObjectSymbol, mode CodeMode) { if symbol.kind == .OBJECT_CLASS { if mode == .DEFINE { _emit("\n" + _indent + "#ifdef SKEW_GC_MARK_AND_SWEEP\n") _increaseIndent _emit(_indent + "virtual void __gc_mark() override;\n") _decreaseIndent _emit(_indent + "#endif\n") } else if mode == .IMPLEMENT { _emitNewlineBeforeSymbol(_dummyFunction, mode) _emit(_indent + "#ifdef SKEW_GC_MARK_AND_SWEEP\n") _increaseIndent _emitTypeParameters(symbol.parameters) _emit(_indent + "void ") _emitEnclosingSymbolPrefix(symbol) _emit("__gc_mark() {\n") _increaseIndent if symbol.baseClass != null { _emit(_indent + _fullName(symbol.baseClass) + "::__gc_mark();\n") } for variable in symbol.variables { if variable.kind == .VARIABLE_INSTANCE && variable.parent == symbol && _isReferenceType(variable.resolvedType) { _emit(_indent + "Skew::GC::mark(" + _mangleName(variable) + ");\n") } } _decreaseIndent _emit(_indent + "}\n") _decreaseIndent _emit(_indent + "#endif\n") _emitNewlineAfterSymbol(_dummyFunction) } } } def _emitTypeParameters(parameters List) { if parameters != null { _emit(_indent + "template <") for parameter in parameters { if parameter != parameters.first { _emit(", ") } _emit("typename " + _mangleName(parameter)) } _emit(">\n") } } def _emitArgumentList(symbol FunctionSymbol) { _emit("(") for argument in symbol.arguments { if argument != symbol.arguments.first { _emit(", ") } _emitExpressionOrType(argument.type, argument.resolvedType, .DECLARATION) _emit(_mangleName(argument)) } _emit(")") } def _emitVariable(symbol VariableSymbol, mode CodeMode) { if symbol.isImported { return } var avoidFullName = symbol.kind == .VARIABLE_GLOBAL && symbol.parent.kind != .OBJECT_CLASS if mode == .IMPLEMENT && symbol.kind != .VARIABLE_LOCAL { _adjustNamespace(avoidFullName ? symbol.parent : null) } _emitNewlineBeforeSymbol(symbol, mode) _emitComments(symbol.comments) if symbol.kind == .VARIABLE_ENUM_OR_FLAGS { symbol.value.resolvedType = _cache.intType # Enum values are initialized with integers, so avoid any casts _emit(_indent + _mangleName(symbol) + " = ") _emitExpression(symbol.value, .COMMA) _emit(",\n") } else { _emit(_indent) if mode == .DEFINE && symbol.kind == .VARIABLE_GLOBAL { _emit(symbol.parent.kind == .OBJECT_CLASS ? "static " : "extern ") } # Global variables must be stored in roots to avoid accidental garbage collection if symbol.kind == .VARIABLE_GLOBAL && _isReferenceType(symbol.resolvedType) { _emit("Skew::Root<") _emitType(symbol.resolvedType, .BARE) _emit("> ") } else { _emitType(symbol.resolvedType, .DECLARATION) } if mode == .DEFINE { _emit(_mangleName(symbol)) } else { _emit(avoidFullName ? _mangleName(symbol) : _fullName(symbol)) if symbol.value != null { _emit(" = ") _emitExpression(symbol.value, .ASSIGN) } } _emit(";\n") } _emitNewlineAfterSymbol(symbol) } def _emitStatements(node Node) { _previousNode = null for child = node.firstChild; child != null; child = child.nextSibling { _emitNewlineBeforeStatement(child) _emitComments(child.comments) _emitStatement(child) _emitNewlineAfterStatement(child) } _previousNode = null } def _emitBlock(node Node) { _emit(" {\n") _increaseIndent _emitStatements(node) _decreaseIndent _emit(_indent + "}") } def _scanForSwitchBreak(node Node, loop Node) { if node.kind == .BREAK { for parent = node.parent; parent != loop; parent = parent.parent { if parent.kind == .SWITCH { var label = _loopLabels.get(loop.id, null) if label == null { label = VariableSymbol.new(.VARIABLE_LOCAL, _enclosingFunction.scope.generateName("label")) _loopLabels[loop.id] = label } _loopLabels[node.id] = label break } } } # Stop at nested loops since those will be tested later else if node == loop || !node.kind.isLoop { for child = node.firstChild; child != null; child = child.nextSibling { _scanForSwitchBreak(child, loop) } } } def _emitStatement(node Node) { if node.kind.isLoop { _scanForSwitchBreak(node, node) } switch node.kind { case .COMMENT_BLOCK {} case .VARIABLES { for child = node.firstChild; child != null; child = child.nextSibling { _emitVariable(child.symbol.asVariableSymbol, .IMPLEMENT) } } case .EXPRESSION { _emit(_indent) _emitExpression(node.expressionValue, .LOWEST) _emit(";\n") } case .BREAK { var label = _loopLabels.get(node.id, null) if label != null { _emit(_indent + "goto " + _mangleName(label) + ";\n") } else { _emit(_indent + "break;\n") } } case .CONTINUE { _emit(_indent + "continue;\n") } case .IF { _emit(_indent) _emitIf(node) _emit("\n") } case .SWITCH { var switchValue = node.switchValue _emit(_indent + "switch (") _emitExpression(switchValue, .LOWEST) _emit(") {\n") _increaseIndent for child = switchValue.nextSibling; child != null; child = child.nextSibling { var block = child.caseBlock if child.previousSibling != switchValue { _emit("\n") } if child.hasOneChild { _emit(_indent + "default:") } else { for value = child.firstChild; value != block; value = value.nextSibling { if value.previousSibling != null { _emit("\n") } _emit(_indent + "case ") _emitExpression(value, .LOWEST) _emit(":") } } _emit(" {\n") _increaseIndent _emitStatements(block) if block.hasControlFlowAtEnd { _emit(_indent + "break;\n") } _decreaseIndent _emit(_indent + "}\n") } _decreaseIndent _emit(_indent + "}\n") } case .RETURN { _emit(_indent + "return") var value = node.returnValue if value != null { _emit(" ") _emitExpression(value, .LOWEST) } _emit(";\n") } case .THROW { _emit(_indent + "throw ") _emitExpression(node.throwValue, .LOWEST) _emit(";\n") } case .FOR { var setup = node.forSetup var test = node.forTest var update = node.forUpdate _emit(_indent + "for (") if !setup.isEmptySequence { if setup.kind == .VARIABLES { _emitType(setup.firstChild.symbol.asVariableSymbol.resolvedType, .DECLARATION) for child = setup.firstChild; child != null; child = child.nextSibling { var symbol = child.symbol.asVariableSymbol assert(child.kind == .VARIABLE) if child.previousSibling != null { _emit(", ") if _isReferenceType(symbol.resolvedType) { _emit("*") } } _emit(_mangleName(symbol) + " = ") _emitExpression(symbol.value, .COMMA) } } else { _emitExpression(setup, .LOWEST) } } _emit("; ") if !test.isEmptySequence { _emitExpression(test, .LOWEST) } _emit("; ") if !update.isEmptySequence { _emitExpression(update, .LOWEST) } _emit(")") _emitBlock(node.forBlock) _emit("\n") } case .TRY { var tryBlock = node.tryBlock var finallyBlock = node.finallyBlock _emit(_indent + "try") _emitBlock(tryBlock) _emit("\n") for child = tryBlock.nextSibling; child != finallyBlock; child = child.nextSibling { if child.comments != null { _emit("\n") _emitComments(child.comments) } _emit(_indent + "catch") if child.symbol != null { _emit(" (") _emitType(child.symbol.resolvedType, .DECLARATION) _emit(_mangleName(child.symbol) + ")") } else { _emit(" (...)") } _emitBlock(child.catchBlock) _emit("\n") } if finallyBlock != null { if finallyBlock.comments != null { _emit("\n") _emitComments(finallyBlock.comments) } _emit(_indent + "finally") _emitBlock(finallyBlock) _emit("\n") } } case .WHILE { _emit(_indent + "while (") _emitExpression(node.whileTest, .LOWEST) _emit(")") _emitBlock(node.whileBlock) _emit("\n") } case .FOREACH { var symbol = node.symbol.asVariableSymbol var value = node.foreachValue _emit(_indent + "for (") _emitType(symbol.resolvedType, .DECLARATION) _emit(_mangleName(symbol) + " : ") if _isReferenceType(value.resolvedType) { _emit("*") _emitExpression(value, .UNARY_PREFIX) } else { _emitExpression(value, .LOWEST) } _emit(")") _emitBlock(node.foreachBlock) _emit("\n") } default { assert(false) } } if node.kind.isLoop { var label = _loopLabels.get(node.id, null) if label != null { _emit(_indent + _mangleName(label) + (node.nextSibling != null ? ":\n" : ":;\n")) } } } def _emitIf(node Node) { _emit("if (") _emitExpression(node.ifTest, .LOWEST) _emit(")") _emitBlock(node.ifTrue) var block = node.ifFalse if block != null { var singleIf = block.hasOneChild && block.firstChild.kind == .IF ? block.firstChild : null _emit("\n\n") _emitComments(block.comments) if singleIf != null { _emitComments(singleIf.comments) } _emit(_indent + "else") if singleIf != null { _emit(" ") _emitIf(singleIf) } else { _emitBlock(block) } } } def _emitContent(content Content) { switch content.kind { case .BOOL { _emit(content.asBool.toString) } case .INT { _emit(content.asInt.toString) } case .DOUBLE { var value = content.asDouble if !value.isFinite { _includeNames[""] = 0 } _emit( value.isNaN ? "NAN" : value == Math.INFINITY ? "INFINITY" : value == -Math.INFINITY ? "-INFINITY" : doubleToStringWithDot(value)) } case .STRING { var text = content.asString var quoted = quoteString(text, .DOUBLE, .OCTAL_WORKAROUND) _emit("\0" in text ? "Skew::string(\(quoted), \(text.count))" : quoted + "_s") # TODO: This needs to work with UTF8 } } } def _emitCommaSeparatedExpressions(from Node, to Node) { while from != to { _emitExpression(from, .COMMA) from = from.nextSibling if from != to { _emit(", ") } } } def _emitExpression(node Node, precedence Precedence) { var kind = node.kind var symbol = node.symbol if symbol != null { _handleSymbol(symbol) } switch kind { case .TYPE, .LAMBDA_TYPE { _emitType(node.resolvedType, .BARE) } case .NULL { _emit("nullptr") } case .NAME { _emit(symbol != null ? _fullName(symbol) : node.asString) # Need to unwrap GC roots using ".get()" when global variables are referenced if symbol != null && symbol.kind == .VARIABLE_GLOBAL && _isReferenceType(symbol.resolvedType) && ( node.parent == null || node.parent.kind != .DOT && (node.parent.kind != .ASSIGN || node != node.parent.binaryLeft)) { _emit(".get()") } } case .DOT { var target = node.dotTarget var type = target.resolvedType _emitExpression(target, .MEMBER) _emit((type != null && _isReferenceType(type) ? "->" : ".") + (symbol != null ? _mangleName(symbol) : node.asString)) } case .CONSTANT { if node.resolvedType.isEnumOrFlags { _emit("(") _emitType(node.resolvedType, .NORMAL) _emit(")") } _emitContent(node.content) } case .CALL { var value = node.callValue var wrap = false if value.kind == .SUPER { _emit(_fullName(symbol)) } else if symbol != null && symbol.kind == .FUNCTION_CONSTRUCTOR { wrap = precedence == Precedence.MEMBER if wrap { _emit("(") } _emit("new ") _emitType(node.resolvedType, .BARE) } else if value.kind == .DOT && value.asString == "new" { wrap = precedence == Precedence.MEMBER if wrap { _emit("(") } _emit("new ") _emitExpression(value.dotTarget, .MEMBER) } else { _emitExpression(value, .UNARY_POSTFIX) } _emit("(") _emitCommaSeparatedExpressions(node.firstChild.nextSibling, null) _emit(")") if wrap { _emit(")") } } case .CAST { var resolvedType = node.resolvedType var type = node.castType var value = node.castValue if value.kind == .NULL && node.resolvedType == _cache.stringType { _emitType(_cache.stringType, .BARE) _emit("()") } else if type.kind == .TYPE && type.resolvedType == .DYNAMIC { _emitExpression(value, precedence) } # Automatically promote integer literals to doubles instead of using a cast else if _cache.isEquivalentToDouble(resolvedType) && value.isInt { _emitExpression(_cache.createDouble(value.asInt), precedence) } # Only emit a cast if the underlying types are different else if _unwrappedType(value.resolvedType) != _unwrappedType(type.resolvedType) || type.resolvedType == .DYNAMIC { if Precedence.UNARY_POSTFIX < precedence { _emit("(") } _emit("(") _emitExpressionOrType(type, resolvedType, .NORMAL) _emit(")") _emitExpression(value, .UNARY_POSTFIX) if Precedence.UNARY_POSTFIX < precedence { _emit(")") } } # Otherwise, pretend the cast isn't there else { _emitExpression(value, precedence) } } case .TYPE_CHECK { var value = node.typeCheckValue var type = node.typeCheckType if Precedence.EQUAL < precedence { _emit("(") } _emit("dynamic_cast<") _emitExpressionOrType(type, type.resolvedType, .NORMAL) _emit(">(") _emitExpression(value, .LOWEST) _emit(") != nullptr") if Precedence.EQUAL < precedence { _emit(")") } } case .INDEX { var left = node.indexLeft if _isReferenceType(left.resolvedType) { _emit("(*") _emitExpression(left, .UNARY_PREFIX) _emit(")") } else { _emitExpression(left, .UNARY_POSTFIX) } _emit("[") _emitExpression(node.indexRight, .LOWEST) _emit("]") } case .ASSIGN_INDEX { var left = node.assignIndexLeft if Precedence.ASSIGN < precedence { _emit("(") } if _isReferenceType(left.resolvedType) { _emit("(*") _emitExpression(left, .UNARY_PREFIX) _emit(")") } else { _emitExpression(left, .UNARY_POSTFIX) } _emit("[") _emitExpression(node.assignIndexCenter, .LOWEST) _emit("] = ") _emitExpression(node.assignIndexRight, .ASSIGN) if Precedence.ASSIGN < precedence { _emit(")") } } case .PARAMETERIZE { var value = node.parameterizeValue if value.isType { _emitType(node.resolvedType, .NORMAL) } else { _emitExpression(value, precedence) _emit("<") for child = value.nextSibling; child != null; child = child.nextSibling { if child.previousSibling != value { _emit(", ") } _emitExpressionOrType(child, child.resolvedType, .NORMAL) } _emit(">") } } case .SEQUENCE { if Precedence.COMMA <= precedence { _emit("(") } _emitCommaSeparatedExpressions(node.firstChild, null) if Precedence.COMMA <= precedence { _emit(")") } } case .HOOK { if Precedence.ASSIGN < precedence { _emit("(") } _emitExpression(node.hookTest, .LOGICAL_OR) _emit(" ? ") _emitExpression(node.hookTrue, .ASSIGN) _emit(" : ") _emitExpression(node.hookFalse, .ASSIGN) if Precedence.ASSIGN < precedence { _emit(")") } } case .INITIALIZER_LIST, .INITIALIZER_MAP { var wrap = precedence == Precedence.MEMBER if wrap { _emit("(") } _emit("new ") _emitType(node.resolvedType, .BARE) if node.hasChildren { _emit("({") _emitCommaSeparatedExpressions(node.firstChild, null) _emit("})") } else { _emit("()") } if wrap { _emit(")") } } case .PAIR { _includeNames[""] = 0 _emit("std::make_pair(") _emitCommaSeparatedExpressions(node.firstChild, null) _emit(")") } case .COMPLEMENT, .NEGATIVE, .NOT, .POSITIVE, .POSTFIX_DECREMENT, .POSTFIX_INCREMENT, .PREFIX_DECREMENT, .PREFIX_INCREMENT { var value = node.unaryValue var info = operatorInfo[kind] var sign = node.sign if info.precedence < precedence { _emit("(") } if !kind.isUnaryPostfix { _emit(info.text) # Prevent "x - -1" from becoming "x--1" if sign != .NULL && sign == value.sign { _emit(" ") } } _emitExpression(value, info.precedence) if kind.isUnaryPostfix { _emit(info.text) } if info.precedence < precedence { _emit(")") } } default { if kind.isBinary { var parent = node.parent if parent != null && ( # Clang warns about "&&" inside "||" or "&" inside "|" without parentheses parent.kind == .LOGICAL_OR && kind == .LOGICAL_AND || parent.kind == .BITWISE_OR && kind == .BITWISE_AND || # Clang and GCC also warn about add/subtract inside bitwise operations and shifts without parentheses (parent.kind == .BITWISE_AND || parent.kind == .BITWISE_OR || parent.kind == .BITWISE_XOR || parent.kind.isShift) && (kind == .ADD || kind == .SUBTRACT)) { precedence = .MEMBER } var info = operatorInfo[kind] if info.precedence < precedence { _emit("(") } _emitExpression(node.binaryLeft, info.precedence.incrementIfRightAssociative(info.associativity)) _emit(" " + info.text + " ") _emitExpression(node.binaryRight, info.precedence.incrementIfLeftAssociative(info.associativity)) if info.precedence < precedence { _emit(")") } } else { assert(false) } } } } enum CppEmitMode { BARE NORMAL DECLARATION } def _emitExpressionOrType(node Node, type Type, mode CppEmitMode) { if node != null && (type == null || type == .DYNAMIC) { _emitExpression(node, .LOWEST) if mode == .DECLARATION { _emit(" ") } } else { _emitType(type, mode) } } def _emitType(type Type, mode CppEmitMode) { if type == null { _emit("void") } else { type = _unwrappedType(type) if type == .DYNAMIC { _emit("void") } else if type.kind == .LAMBDA { var hasReturnType = type.returnType != null var argumentCount = type.argumentTypes.count _emit((hasReturnType ? "Skew::Fn" : "Skew::FnVoid") + "\(argumentCount)") if hasReturnType || argumentCount != 0 { _emit("<") if hasReturnType { _emitType(type.returnType, .NORMAL) } for i in 0..argumentCount { if i != 0 || hasReturnType { _emit(", ") } _emitType(type.argumentTypes[i], .NORMAL) } _emit(">") } } else { assert(type.kind == .SYMBOL) _handleSymbol(type.symbol) _emit(_fullName(type.symbol)) if type.isParameterized { _emit("<") for i in 0..type.substitutions.count { if i != 0 { _emit(", ") } _emitType(type.substitutions[i], .NORMAL) } _emit(">") } } } if type != null && _isReferenceType(type) && mode != .BARE { _emit(" *") } else if mode == .DECLARATION { _emit(" ") } } def _unwrappedType(type Type) Type { return type.isFlags ? _cache.intType : _cache.unwrappedType(type) } def _isReferenceType(type Type) bool { return type.isReference && type != _cache.stringType } def _finalizeEmittedFile { var includes = _includeNames.keys if !includes.isEmpty { includes.sort(SORT_STRINGS) # Sort so the order is deterministic for include in includes { _emitPrefix("#include " + (include.startsWith("<") && include.endsWith(">") ? include : "\"" + include + "\"") + "\n") } _emitPrefix("\n") } _adjustNamespace(null) _previousSymbol = null _symbolsCheckedForInclude = {} _includeNames = {} } def _handleSymbol(symbol Symbol) { if !symbol.kind.isLocal && !(symbol.id in _symbolsCheckedForInclude) { _symbolsCheckedForInclude[symbol.id] = 0 if symbol.annotations != null { for annotation in symbol.annotations { if annotation.symbol != null && annotation.symbol.fullName == "include" { var value = annotation.annotationValue if value.childCount == 2 { _includeNames[value.lastChild.asString] = 0 } } } } if symbol.parent != null { _handleSymbol(symbol.parent) } } } } namespace CPlusPlusEmitter { def _isCompactNodeKind(kind NodeKind) bool { return kind == .EXPRESSION || kind == .VARIABLES || kind.isJump } def _fullName(symbol Symbol) string { var parent = symbol.parent if parent != null && parent.kind != .OBJECT_GLOBAL && !symbol.kind.isParameter { return _fullName(parent) + "::" + _mangleName(symbol) } return _mangleName(symbol) } def _mangleName(symbol Symbol) string { symbol = symbol.forwarded if symbol.kind == .FUNCTION_CONSTRUCTOR { return _mangleName(symbol.parent) } if !symbol.isImportedOrExported && symbol.name in _isNative || symbol.name in _isKeyword { return "_" + symbol.name } return symbol.name } const _isNative = { "auto": 0, "bool": 0, "char": 0, "char16_t": 0, "char32_t": 0, "const_cast": 0, "double": 0, "dynamic_cast": 0, "false": 0, "float": 0, "INFINITY": 0, "int": 0, "long": 0, "NAN": 0, "NULL": 0, "nullptr": 0, "reinterpret_cast": 0, "short": 0, "signed": 0, "static_assert": 0, "static_cast": 0, "this": 0, "true": 0, "unsigned": 0, "void": 0, "wchar_t": 0, } const _isKeyword = { "alignas": 0, "alignof": 0, "and": 0, "and_eq": 0, "asm": 0, "bitand": 0, "bitor": 0, "break": 0, "case": 0, "catch": 0, "class": 0, "compl": 0, "const": 0, "constexpr": 0, "continue": 0, "decltype": 0, "default": 0, "delete": 0, "do": 0, "else": 0, "enum": 0, "explicit": 0, "export": 0, "extern": 0, "for": 0, "friend": 0, "goto": 0, "if": 0, "inline": 0, "mutable": 0, "namespace": 0, "new": 0, "noexcept": 0, "not": 0, "not_eq": 0, "operator": 0, "or": 0, "or_eq": 0, "private": 0, "protected": 0, "public": 0, "register": 0, "return": 0, "sizeof": 0, "static": 0, "struct": 0, "switch": 0, "template": 0, "thread_local": 0, "throw": 0, "try": 0, "typedef": 0, "typeid": 0, "typename": 0, "union": 0, "using": 0, "virtual": 0, "volatile": 0, "while": 0, "xor": 0, "xor_eq": 0, } } } ================================================ FILE: src/backend/csharp.sk ================================================ namespace Skew { class CSharpTarget : CompilerTarget { over name string { return "C#" } over extension string { return "cs" } over stopAfterResolve bool { return false } over requiresIntegerSwitchStatements bool { return true } over supportsListForeach bool { return true } over supportsNestedTypes bool { return true } over stringEncoding Unicode.Encoding { return .UTF16 } over editOptions(options CompilerOptions) { options.define("TARGET", "CSHARP") } over includeSources(sources List) { sources.prepend(Source.new("", NATIVE_LIBRARY_CS)) } over createEmitter(context PassContext) Emitter { return CSharpEmitter.new(context.options, context.cache) } } class CSharpEmitter : Emitter { const _options CompilerOptions const _cache TypeCache var _previousNode Node = null var _previousSymbol Symbol = null var _namespaceStack List = [] var _symbolsCheckedForUsing IntMap = {} var _usingNames StringMap = {} var _loopLabels IntMap = {} var _enclosingFunction FunctionSymbol = null over visit(global ObjectSymbol) { _indentAmount = " " _moveGlobalsIntoClasses(global) # Generate the entry point var entryPoint = _cache.entryPointSymbol if entryPoint != null { entryPoint.name = "Main" # The entry point in C# takes an array, not a list if entryPoint.arguments.count == 1 { var argument = entryPoint.arguments.first var array = VariableSymbol.new(.VARIABLE_ARGUMENT, argument.name) array.type = Node.createName("string[]").withType(.DYNAMIC) array.resolvedType = .DYNAMIC entryPoint.arguments = [array] entryPoint.resolvedType.argumentTypes = [array.resolvedType] # Create the list from the array if entryPoint.block != null { array.name = entryPoint.scope.generateName(array.name) argument.kind = .VARIABLE_LOCAL argument.value = Node.createCall(Node.createDot(Node.createType(argument.resolvedType), "new").withType(.DYNAMIC)) .withType(.DYNAMIC).appendChild(Node.createSymbolReference(array)) entryPoint.block.prependChild(Node.createVariables.appendChild(Node.createVariable(argument))) } } } # Avoid emitting unnecessary stuff shakingPass(global, entryPoint, .USE_TYPES) _markVirtualFunctions(global) var emitIndividualFiles = _options.outputDirectory != null var objects = _collectObjects(global) for object in objects { # Convert "flags" types to wrapped types if object.kind == .OBJECT_FLAGS { object.kind = .OBJECT_WRAPPED object.wrappedType = _cache.intType # Enum values become normal global variables for variable in object.variables { if variable.kind == .VARIABLE_ENUM_OR_FLAGS { variable.kind = .VARIABLE_GLOBAL variable.flags |= .IS_CSHARP_CONST } } } } # All code in C# is inside objects, so just emit objects recursively for object in objects { # Nested objects will be emitted by their parent if object.parent != null && object.parent.kind == .OBJECT_CLASS { continue } _emitObject(object) # Emit each object into its own file if requested if emitIndividualFiles { _finalizeEmittedFile _createSource(_options.outputDirectory + "/" + _fullName(object) + ".cs", .SKIP_IF_EMPTY) } } # Emit a single file if requested if !emitIndividualFiles { _finalizeEmittedFile _createSource(_options.outputFile, .ALWAYS_EMIT) } } def _moveGlobalsIntoClasses(symbol ObjectSymbol) { if !symbol.kind.isNamespaceOrGlobal { return } # Just change namespaces into classes if there aren't nested objects if symbol.kind == .OBJECT_NAMESPACE && symbol.objects.isEmpty && (!symbol.functions.isEmpty || !symbol.variables.isEmpty) { symbol.kind = .OBJECT_CLASS return } var globals ObjectSymbol = null var lazilyCreateGlobals = => { if globals == null { globals = ObjectSymbol.new(.OBJECT_CLASS, symbol.scope.generateName(symbol.kind == .OBJECT_NAMESPACE ? symbol.name + "Globals" : "Globals")) globals.resolvedType = Type.new(.SYMBOL, globals) globals.state = .INITIALIZED globals.parent = symbol symbol.objects.append(globals) } } for object in symbol.objects { _moveGlobalsIntoClasses(object) } symbol.functions.removeIf(function => { if function.kind != .FUNCTION_ANNOTATION && !function.isImported { lazilyCreateGlobals() function.parent = globals globals.functions.append(function) return true } return false }) symbol.variables.removeIf(variable => { if variable.kind == .VARIABLE_GLOBAL && !variable.isImported { lazilyCreateGlobals() variable.parent = globals globals.variables.append(variable) return true } return false }) } def _adjustNamespace(symbol Symbol) { # Get the namespace chain for this symbol var symbols List = [] while symbol != null && symbol.kind != .OBJECT_GLOBAL { if symbol.kind == .OBJECT_NAMESPACE { symbols.prepend(symbol) } symbol = symbol.parent } # Find the intersection var limit = Math.min(_namespaceStack.count, symbols.count) var i = 0 while i < limit { if _namespaceStack[i] != symbols[i] { break } i++ } # Leave the old namespace while _namespaceStack.count > i { var object = _namespaceStack.takeLast _decreaseIndent _emit(_indent + "}\n") _emitNewlineAfterSymbol(object) } # Enter the new namespace while _namespaceStack.count < symbols.count { var object = symbols[_namespaceStack.count] _emitNewlineBeforeSymbol(object) _emit(_indent + "namespace " + _mangleName(object) + "\n") _emit(_indent + "{\n") _increaseIndent _namespaceStack.append(object) } } def _finalizeEmittedFile { var usings = _usingNames.keys if !usings.isEmpty { usings.sort(SORT_STRINGS) # Sort so the order is deterministic for using in usings { _emitPrefix("using " + using + ";\n") } _emitPrefix("\n") } _adjustNamespace(null) _previousSymbol = null _symbolsCheckedForUsing = {} _usingNames = {} } def _handleSymbol(symbol Symbol) { if !symbol.kind.isLocal && !(symbol.id in _symbolsCheckedForUsing) { _symbolsCheckedForUsing[symbol.id] = 0 if symbol.annotations != null { for annotation in symbol.annotations { if annotation.symbol != null && annotation.symbol.fullName == "using" { var value = annotation.annotationValue if value.childCount == 2 { _usingNames[value.lastChild.asString] = 0 } } } } if symbol.parent != null { _handleSymbol(symbol.parent) } } } def _emitNewlineBeforeSymbol(symbol Symbol) { if _previousSymbol != null && (!_previousSymbol.kind.isVariable || !symbol.kind.isVariable || symbol.comments != null) { _emit("\n") } _previousSymbol = null } def _emitNewlineAfterSymbol(symbol Symbol) { _previousSymbol = symbol } def _emitNewlineBeforeStatement(node Node) { if _previousNode != null && (node.comments != null || !_isCompactNodeKind(_previousNode.kind) || !_isCompactNodeKind(node.kind)) { _emit("\n") } _previousNode = null } def _emitNewlineAfterStatement(node Node) { _previousNode = node } def _emitComments(comments List) { if comments != null { for comment in comments { for line in comment.lines { _emit(_indent + "//" + line + "\n") } if comment.hasGapBelow { _emit("\n") } } } } def _emitObject(symbol ObjectSymbol) { _handleSymbol(symbol) if symbol.isImported || symbol.kind.isNamespaceOrGlobal { return } _adjustNamespace(symbol) _emitNewlineBeforeSymbol(symbol) _emitComments(symbol.comments) _emit(_indent + "public ") if symbol.isAbstract { _emit("abstract ") } switch symbol.kind { case .OBJECT_CLASS { _emit("class ") } case .OBJECT_ENUM, .OBJECT_FLAGS { _emit("enum ") } case .OBJECT_INTERFACE { _emit("interface ") } case .OBJECT_WRAPPED, .OBJECT_NAMESPACE { _emit("static class ") } default { assert(false) } } _emit(_mangleName(symbol)) _emitTypeParameters(symbol.parameters) if (symbol.extends != null || symbol.implements != null) && symbol.kind != .OBJECT_WRAPPED { _emit(" : ") if symbol.extends != null { _emitExpressionOrType(symbol.extends, symbol.baseType) } if symbol.implements != null { for node in symbol.implements { if node != symbol.implements.first || symbol.extends != null { _emit(", ") } _emitExpressionOrType(node, node.resolvedType) } } } _emit("\n" + _indent + "{\n") _increaseIndent for object in symbol.objects { _emitObject(object) } for variable in symbol.variables { _emitVariable(variable) } for function in symbol.functions { _emitFunction(function) } _decreaseIndent _emit(_indent + "}\n") _emitNewlineAfterSymbol(symbol) } def _emitTypeParameters(parameters List) { if parameters != null { _emit("<") for parameter in parameters { if parameter != parameters.first { _emit(", ") } _emit(_mangleName(parameter)) } _emit(">") } } def _emitArgumentList(symbol FunctionSymbol) { _emit("(") for argument in symbol.arguments { if argument != symbol.arguments.first { _emit(", ") } _emitExpressionOrType(argument.type, argument.resolvedType) _emit(" " + _mangleName(argument)) } _emit(")") } def _emitVariable(symbol VariableSymbol) { _handleSymbol(symbol) if symbol.isImported { return } _emitNewlineBeforeSymbol(symbol) _emitComments(symbol.comments) if symbol.kind == .VARIABLE_ENUM_OR_FLAGS { _emit(_indent + _mangleName(symbol)) if symbol.value != null { symbol.value.resolvedType = _cache.intType # Enum values are initialized with integers _emit(" = ") _emitExpression(symbol.value, .COMMA) } _emit(",\n") } else { _emit(_indent + "public ") if symbol.kind == .VARIABLE_GLOBAL { _emit(symbol.isCSharpConst ? "const " : "static ") } _emitExpressionOrType(symbol.type, symbol.resolvedType) _emit(" " + _mangleName(symbol)) if symbol.value != null { _emit(" = ") _emitExpression(symbol.value, .COMMA) } _emit(";\n") } _emitNewlineAfterSymbol(symbol) } def _emitFunction(symbol FunctionSymbol) { _handleSymbol(symbol) if symbol.isImported { return } # C# has sane capture rules for "this" so no variable insertion is needed if symbol.this != null { symbol.this.name = "this" symbol.this.flags |= .IS_EXPORTED } _enclosingFunction = symbol _emitNewlineBeforeSymbol(symbol) _emitComments(symbol.comments) _emit(_indent) if symbol.parent.kind != .OBJECT_INTERFACE { _emit("public ") } if symbol.kind == .FUNCTION_GLOBAL { _emit("static ") } if symbol.kind != .FUNCTION_CONSTRUCTOR { if symbol.parent.kind != .OBJECT_INTERFACE { if symbol.block == null { _emit("abstract ") } else if symbol.overridden != null { _emit("override ") } else if symbol.isVirtual { _emit("virtual ") } } _emitExpressionOrType(symbol.returnType, symbol.resolvedType.returnType) _emit(" ") } _emit(_mangleName(symbol)) _emitTypeParameters(symbol.parameters) _emitArgumentList(symbol) var block = symbol.block if block == null { _emit(";\n") } else { # Move the super constructor call out of the function body if symbol.kind == .FUNCTION_CONSTRUCTOR && block.hasChildren { var first = block.firstChild if first.kind == .EXPRESSION { var call = first.expressionValue if call.kind == .CALL && call.callValue.kind == .SUPER { _emit(" : ") first.remove _emitExpression(call, .LOWEST) } } } _emit("\n") _emitBlock(block) _emit("\n") } _emitNewlineAfterSymbol(symbol) _enclosingFunction = null } def _emitType(type Type) { if type == null { _emit("void") return } type = _cache.unwrappedType(type) if type == .DYNAMIC { _emit("dynamic") } else if type.kind == .LAMBDA { var argumentTypes = type.argumentTypes var returnType = type.returnType _emit(returnType != null ? "System.Func" : "System.Action") if !argumentTypes.isEmpty || returnType != null { _emit("<") for i in 0..argumentTypes.count { if i != 0 { _emit(", ") } _emitType(argumentTypes[i]) } if returnType != null { if !argumentTypes.isEmpty { _emit(", ") } _emitType(returnType) } _emit(">") } } else { assert(type.kind == .SYMBOL) _handleSymbol(type.symbol) _emit(_fullName(type.symbol)) if type.isParameterized { _emit("<") if _cache.isIntMap(type) || _cache.isStringMap(type) { _emit(_cache.isIntMap(type) ? "int" : "string") _emit(", ") _emitType(type.substitutions.first) } else { for i in 0..type.substitutions.count { if i != 0 { _emit(", ") } _emitType(type.substitutions[i]) } } _emit(">") } } } def _emitExpressionOrType(node Node, type Type) { if node != null && (type == null || type == .DYNAMIC) { _emitExpression(node, .LOWEST) } else { _emitType(type) } } def _emitStatements(node Node) { _previousNode = null for child = node.firstChild; child != null; child = child.nextSibling { _emitNewlineBeforeStatement(child) _emitComments(child.comments) _emitStatement(child) _emitNewlineAfterStatement(child) } _previousNode = null } def _emitBlock(node Node) { assert(node.kind == .BLOCK) _emit(_indent + "{\n") _increaseIndent _emitStatements(node) _decreaseIndent _emit(_indent + "}") } def _emitIf(node Node) { _emit("if (") _emitExpression(node.ifTest, .LOWEST) _emit(")\n") _emitBlock(node.ifTrue) _emit("\n") var block = node.ifFalse if block != null { var singleIf = block.hasOneChild && block.firstChild.kind == .IF ? block.firstChild : null if block.comments != null || singleIf != null && singleIf.comments != null { _emit("\n") _emitComments(block.comments) if singleIf != null { _emitComments(singleIf.comments) } } _emit(_indent + "else") if singleIf != null { _emit(" ") _emitIf(singleIf) } else { _emit("\n") _emitBlock(block) _emit("\n") } } } def _scanForSwitchBreak(node Node, loop Node) { if node.kind == .BREAK { for parent = node.parent; parent != loop; parent = parent.parent { if parent.kind == .SWITCH { var label = _loopLabels.get(loop.id, null) if label == null { label = VariableSymbol.new(.VARIABLE_LOCAL, _enclosingFunction.scope.generateName("label")) _loopLabels[loop.id] = label } _loopLabels[node.id] = label break } } } # Stop at nested loops since those will be tested later else if node == loop || !node.kind.isLoop { for child = node.firstChild; child != null; child = child.nextSibling { _scanForSwitchBreak(child, loop) } } } def _emitStatement(node Node) { if node.kind.isLoop { _scanForSwitchBreak(node, node) } switch node.kind { case .COMMENT_BLOCK {} case .VARIABLES { for child = node.firstChild; child != null; child = child.nextSibling { var symbol = child.symbol.asVariableSymbol _emit(_indent) _emitExpressionOrType(symbol.type, symbol.resolvedType) _emit(" " + _mangleName(symbol)) if symbol.value != null { _emit(" = ") _emitExpression(symbol.value, .ASSIGN) } _emit(";\n") } } case .EXPRESSION { _emit(_indent) _emitExpression(node.expressionValue, .LOWEST) _emit(";\n") } case .BREAK { var label = _loopLabels.get(node.id, null) if label != null { _emit(_indent + "goto " + _mangleName(label) + ";\n") } else { _emit(_indent + "break;\n") } } case .CONTINUE { _emit(_indent + "continue;\n") } case .IF { _emit(_indent) _emitIf(node) } case .SWITCH { var switchValue = node.switchValue _emit(_indent + "switch (") _emitExpression(switchValue, .LOWEST) _emit(")\n" + _indent + "{\n") _increaseIndent for child = switchValue.nextSibling; child != null; child = child.nextSibling { var block = child.caseBlock if child.previousSibling != switchValue { _emit("\n") } if child.hasOneChild { _emit(_indent + "default:") } else { for value = child.firstChild; value != block; value = value.nextSibling { if value.previousSibling != null { _emit("\n") } _emit(_indent + "case ") _emitExpression(value, .LOWEST) _emit(":") } } _emit("\n" + _indent + "{\n") _increaseIndent _emitStatements(block) if block.hasControlFlowAtEnd { _emit(_indent + "break;\n") } _decreaseIndent _emit(_indent + "}\n") } _decreaseIndent _emit(_indent + "}\n") } case .RETURN { _emit(_indent + "return") var value = node.returnValue if value != null { _emit(" ") _emitExpression(value, .LOWEST) } _emit(";\n") } case .THROW { _emit(_indent + "throw ") _emitExpression(node.throwValue, .LOWEST) _emit(";\n") } case .FOREACH { _emit(_indent + "foreach (var " + _mangleName(node.symbol) + " in ") _emitExpression(node.foreachValue, .LOWEST) _emit(")\n") _emitBlock(node.foreachBlock) _emit("\n") } case .FOR { var setup = node.forSetup var test = node.forTest var update = node.forUpdate _emit(_indent + "for (") if !setup.isEmptySequence { if setup.kind == .VARIABLES { var symbol = setup.firstChild.symbol.asVariableSymbol _emitExpressionOrType(symbol.type, symbol.resolvedType) _emit(" ") for child = setup.firstChild; child != null; child = child.nextSibling { symbol = child.symbol.asVariableSymbol assert(child.kind == .VARIABLE) if child.previousSibling != null { _emit(", ") } _emit(_mangleName(symbol) + " = ") _emitExpression(symbol.value, .COMMA) } } else { _emitExpression(setup, .LOWEST) } } _emit("; ") if !test.isEmptySequence { _emitExpression(test, .LOWEST) } _emit("; ") if !update.isEmptySequence { _emitExpression(update, .LOWEST) } _emit(")\n") _emitBlock(node.forBlock) _emit("\n") } case .TRY { var tryBlock = node.tryBlock var finallyBlock = node.finallyBlock _emit(_indent + "try\n") _emitBlock(tryBlock) _emit("\n") for child = tryBlock.nextSibling; child != finallyBlock; child = child.nextSibling { if child.comments != null { _emit("\n") _emitComments(child.comments) } _emit(_indent + "catch") if child.symbol != null { _emit(" (") _emitExpressionOrType(child.symbol.asVariableSymbol.type, child.symbol.resolvedType) _emit(" " + _mangleName(child.symbol) + ")") } _emit("\n") _emitBlock(child.catchBlock) _emit("\n") } if finallyBlock != null { if finallyBlock.comments != null { _emit("\n") _emitComments(finallyBlock.comments) } _emit(_indent + "finally\n") _emitBlock(finallyBlock) _emit("\n") } } case .WHILE { _emit(_indent + "while (") _emitExpression(node.whileTest, .LOWEST) _emit(")\n") _emitBlock(node.whileBlock) _emit("\n") } default { assert(false) } } if node.kind.isLoop { var label = _loopLabels.get(node.id, null) if label != null { _emit(_indent + _mangleName(label) + (node.nextSibling != null ? ":\n" : ":;\n")) } } } def _emitContent(content Content) { switch content.kind { case .BOOL { _emit(content.asBool.toString) } case .INT { _emit(content.asInt.toString) } case .DOUBLE { var value = content.asDouble if !value.isFinite { _usingNames["System"] = 0 } _emit( value.isNaN ? "Double.NaN" : value == Math.INFINITY ? "Double.PositiveInfinity" : value == -Math.INFINITY ? "Double.NegativeInfinity" : doubleToStringWithDot(value)) } case .STRING { _emit(quoteString(content.asString, .DOUBLE, .NORMAL)) } } } def _emitCommaSeparatedExpressions(from Node, to Node) { while from != to { _emitExpression(from, .COMMA) from = from.nextSibling if from != to { _emit(", ") } } } def _emitExpression(node Node, precedence Precedence) { var kind = node.kind var symbol = node.symbol if symbol != null { _handleSymbol(symbol) } switch kind { case .TYPE, .LAMBDA_TYPE { _emitType(node.resolvedType) } case .NULL { _emit("null") } case .NAME { _emit(symbol != null ? _fullName(symbol) : node.asString) } case .DOT { _emitExpression(node.dotTarget, .MEMBER) _emit("." + (symbol != null ? _mangleName(symbol) : node.asString)) } case .CONSTANT { var wrap = precedence == .MEMBER && node.isNumberLessThanZero && (!node.isDouble || node.asDouble.isFinite) if wrap { _emit("(") } if node.resolvedType.isEnumOrFlags { _emit("(") _emitType(node.resolvedType) _emit(")") } _emitContent(node.content) if wrap { _emit(")") } } case .CALL { var value = node.callValue var wrap = value.kind == .LAMBDA if wrap { _emit("new ") _emitType(value.resolvedType) _emit("(") } if value.kind == .SUPER { _emit("base") if symbol.kind != .FUNCTION_CONSTRUCTOR { _emit(".") _emit(_mangleName(symbol)) } } else if symbol != null && symbol.kind == .FUNCTION_CONSTRUCTOR { _emit("new ") _emitType(node.resolvedType) } else if value.kind == .DOT && value.asString == "new" { _emit("new ") _emitExpression(value.dotTarget, .MEMBER) } else { _emitExpression(value, .UNARY_POSTFIX) } if wrap { _emit(")") } _emit("(") _emitCommaSeparatedExpressions(value.nextSibling, null) _emit(")") } case .CAST { var resolvedType = node.resolvedType var type = node.castType var value = node.castValue if type.kind == .TYPE && type.resolvedType == .DYNAMIC { _emitExpression(value, precedence) } # Automatically promote integer literals to doubles instead of using a cast else if _cache.isEquivalentToDouble(resolvedType) && value.isInt { _emitExpression(_cache.createDouble(value.asInt), precedence) } # C# doesn't have a cast from bool to int else if _cache.isNumeric(resolvedType) && value.resolvedType == _cache.boolType { _emitExpression(Node.createHook(value.remove, _cache.createInt(1), _cache.createInt(0)).withType(_cache.intType), precedence) } # C# doesn't have a cast from int to bool else if resolvedType == _cache.boolType && _cache.isNumeric(value.resolvedType) { _emitExpression(Node.createBinary(.NOT_EQUAL, value.remove, _cache.createInt(0)).withType(_cache.boolType), precedence) } # Only emit a cast if the underlying types are different else if _cache.unwrappedType(value.resolvedType) != _cache.unwrappedType(type.resolvedType) || type.resolvedType == .DYNAMIC { if Precedence.UNARY_POSTFIX < precedence { _emit("(") } _emit("(") _emitExpressionOrType(type, type.resolvedType) _emit(")") _emitExpression(value, .UNARY_POSTFIX) if Precedence.UNARY_POSTFIX < precedence { _emit(")") } } # Otherwise, pretend the cast isn't there else { _emitExpression(value, precedence) } } case .TYPE_CHECK { var value = node.typeCheckValue var type = node.typeCheckType if Precedence.COMPARE < precedence { _emit("(") } _emitExpression(value, .LOWEST) _emit(" is ") _emitExpressionOrType(type, type.resolvedType) if Precedence.COMPARE < precedence { _emit(")") } } case .INITIALIZER_LIST { _emit("new ") _emitType(node.resolvedType) if node.hasChildren { _emit(" { ") _emitCommaSeparatedExpressions(node.firstChild, null) _emit(" }") } else { _emit("()") } } case .INDEX { _emitExpression(node.indexLeft, .UNARY_POSTFIX) _emit("[") _emitExpression(node.indexRight, .LOWEST) _emit("]") } case .ASSIGN_INDEX { if Precedence.ASSIGN < precedence { _emit("(") } _emitExpression(node.assignIndexLeft, .UNARY_POSTFIX) _emit("[") _emitExpression(node.assignIndexCenter, .LOWEST) _emit("] = ") _emitExpression(node.assignIndexRight, .ASSIGN) if Precedence.ASSIGN < precedence { _emit(")") } } case .PARAMETERIZE { var value = node.parameterizeValue if value.isType { _emitType(node.resolvedType) } else { _emitExpression(value, precedence) _emit("<") _emitCommaSeparatedExpressions(value.nextSibling, null) _emit(">") } } case .SEQUENCE { if Precedence.COMMA <= precedence { _emit("(") } _emitCommaSeparatedExpressions(node.firstChild, null) if Precedence.COMMA <= precedence { _emit(")") } } case .HOOK { if Precedence.ASSIGN < precedence { _emit("(") } _emitExpression(node.hookTest, .LOGICAL_OR) _emit(" ? ") _emitExpression(node.hookTrue, .ASSIGN) _emit(" : ") _emitExpression(node.hookFalse, .ASSIGN) if Precedence.ASSIGN < precedence { _emit(")") } } case .LAMBDA { var oldEnclosingFunction = _enclosingFunction _enclosingFunction = symbol.asFunctionSymbol _emitArgumentList(symbol.asFunctionSymbol) _emit(" =>\n") _emitBlock(symbol.asFunctionSymbol.block) _enclosingFunction = oldEnclosingFunction } case .COMPLEMENT, .NEGATIVE, .NOT, .POSITIVE, .POSTFIX_DECREMENT, .POSTFIX_INCREMENT, .PREFIX_DECREMENT, .PREFIX_INCREMENT { var value = node.unaryValue var info = operatorInfo[kind] var sign = node.sign if info.precedence < precedence { _emit("(") } if !kind.isUnaryPostfix { _emit(info.text) # Prevent "x - -1" from becoming "x--1" if sign != .NULL && sign == value.sign { _emit(" ") } } _emitExpression(value, info.precedence) if kind.isUnaryPostfix { _emit(info.text) } if info.precedence < precedence { _emit(")") } } default { if kind.isBinary { var left = node.binaryLeft var right = node.binaryRight # Some types stupidly don't implement operator "==" if (kind == .EQUAL || kind == .NOT_EQUAL) && left.resolvedType.isParameter && right.resolvedType.isParameter { if kind == .NOT_EQUAL { _emit("!") } _emit("EqualityComparer<") _emitType(left.resolvedType) _emit(">.Default.Equals(") _emitExpression(left, .COMMA) _emit(", ") _emitExpression(right, .COMMA) _emit(")") _usingNames["System.Collections.Generic"] = 0 } else { var info = operatorInfo[kind] if info.precedence < precedence { _emit("(") } _emitExpression(left, info.precedence.incrementIfRightAssociative(info.associativity)) _emit(" " + info.text + " ") _emitExpression(right, info.precedence.incrementIfLeftAssociative(info.associativity)) if info.precedence < precedence { _emit(")") } } } else { assert(false) } } } } } namespace CSharpEmitter { def _isCompactNodeKind(kind NodeKind) bool { return kind == .EXPRESSION || kind == .VARIABLES || kind.isJump } def _fullName(symbol Symbol) string { var parent = symbol.parent if parent != null && parent.kind != .OBJECT_GLOBAL && !symbol.kind.isParameter { var enclosingName = _fullName(parent) if symbol.kind == .FUNCTION_CONSTRUCTOR { return enclosingName } return enclosingName + "." + _mangleName(symbol) } return _mangleName(symbol) } def _mangleName(symbol Symbol) string { symbol = symbol.forwarded if symbol.kind == .FUNCTION_CONSTRUCTOR { symbol = symbol.parent } if !symbol.isImportedOrExported && symbol.name in _isKeyword { return "_" + symbol.name } return symbol.name } const _isKeyword = { "abstract": 0, "as": 0, "base": 0, "bool": 0, "break": 0, "byte": 0, "case": 0, "catch": 0, "char": 0, "checked": 0, "class": 0, "const": 0, "continue": 0, "decimal": 0, "default": 0, "delegate": 0, "do": 0, "double": 0, "else": 0, "enum": 0, "event": 0, "explicit": 0, "extern": 0, "false": 0, "finally": 0, "fixed": 0, "float": 0, "for": 0, "foreach": 0, "goto": 0, "if": 0, "implicit": 0, "in": 0, "int": 0, "interface": 0, "internal": 0, "is": 0, "lock": 0, "long": 0, "namespace": 0, "new": 0, "null": 0, "object": 0, "operator": 0, "out": 0, "override": 0, "params": 0, "private": 0, "protected": 0, "public": 0, "readonly": 0, "ref": 0, "return": 0, "sbyte": 0, "sealed": 0, "short": 0, "sizeof": 0, "stackalloc": 0, "static": 0, "string": 0, "struct": 0, "switch": 0, "this": 0, "throw": 0, "true": 0, "try": 0, "typeof": 0, "uint": 0, "ulong": 0, "unchecked": 0, "unsafe": 0, "ushort": 0, "using": 0, "virtual": 0, "void": 0, "volatile": 0, "while": 0, } } } ================================================ FILE: src/backend/emitter.sk ================================================ namespace Skew { enum PassKind { EMITTING } class EmittingPass : Pass { over kind PassKind { return .EMITTING } over run(context PassContext) { var emitter = context.options.target.createEmitter(context) if emitter != null { emitter.visit(context.global) context.outputs = emitter.sources } } } enum EmitMode { ALWAYS_EMIT SKIP_IF_EMPTY } class Emitter { var _sources List = [] var _prefix = StringBuilder.new var _code = StringBuilder.new var _indentAmount = " " var _indent = "" def sources List { return _sources } def visit(global ObjectSymbol) def _increaseIndent { _indent += _indentAmount } def _decreaseIndent { _indent = _indent.slice(_indentAmount.count) } def _emit(text string) { _code.append(text) } def _emitPrefix(text string) { _prefix.append(text) } def _createSource(name string, mode EmitMode) { var code = _code.toString if mode == .ALWAYS_EMIT || code != "" { _prefix.append(code) _sources.append(Source.new(name, _prefix.toString)) } _prefix = StringBuilder.new _code = StringBuilder.new } def _collectObjects(global ObjectSymbol) List { var objects List = [] _findObjects(objects, global) return objects } def _sortedObjects(global ObjectSymbol) List { var objects = _collectObjects(global) # Sort by inheritance and containment for i in 0..objects.count { var j = i # Select an object that comes before all other types while j < objects.count { var object = objects[j] var k = i # Check to see if this comes before all other types while k < objects.count { if j != k && _objectComesBefore(objects[k], object) { break } k++ } if k == objects.count { break } j++ } # Swap the object into the correct order if j < objects.count { objects.swap(i, j) } } return objects } def _markVirtualFunctions(symbol ObjectSymbol) { for object in symbol.objects { _markVirtualFunctions(object) } for function in symbol.functions { if function.overridden != null { function.overridden.flags |= .IS_VIRTUAL function.flags |= .IS_VIRTUAL } if function.implementations != null { for other in function.implementations { other.flags |= .IS_VIRTUAL function.flags |= .IS_VIRTUAL } } } } def _findObjects(objects List, object ObjectSymbol) { objects.append(object) for o in object.objects { _findObjects(objects, o) } } } namespace Emitter { def _isContainedBy(inner ObjectSymbol, outer ObjectSymbol) bool { if inner.parent == null { return false } if inner.parent == outer { return true } return _isContainedBy(inner.parent.asObjectSymbol, outer) } def _objectComesBefore(before ObjectSymbol, after ObjectSymbol) bool { return after.hasBaseClass(before) || after.hasInterface(before) || _isContainedBy(after, before) || after.forwardTo == before } } const HEX = "0123456789ABCDEF" enum QuoteStyle { DOUBLE SINGLE SHORTEST TYPESCRIPT_TEMPLATE } enum QuoteOctal { NORMAL OCTAL_WORKAROUND } def quoteString(text string, style QuoteStyle, octal QuoteOctal) string { var count = text.count # Use whichever quote character is less frequent if style == .SHORTEST { var singleQuotes = 0 var doubleQuotes = 0 for i in 0..count { var c = text[i] if c == '"' { doubleQuotes++ } else if c == '\'' { singleQuotes++ } } style = singleQuotes <= doubleQuotes ? .SINGLE : .DOUBLE } var builder = StringBuilder.new var quoteString = style == .SINGLE ? "'" : "\"" var quote = style == .TYPESCRIPT_TEMPLATE ? '`' : style == .SINGLE ? '\'' : '"' var escaped = "" var start = 0 # Append long runs of unescaped characters using a single slice for speed if style != .TYPESCRIPT_TEMPLATE { builder.append(quoteString) } for i in 0..count { var c = text[i] if c == quote { escaped = "\\" + quoteString } else if c == '\n' { escaped = "\\n" } else if c == '\r' { escaped = "\\r" } else if c == '\t' { escaped = "\\t" } else if c == '\0' { # Avoid issues around accidental octal encoding var next = i + 1 < count ? text[i + 1] : '\0' escaped = octal == .OCTAL_WORKAROUND && next >= '0' && next <= '9' ? "\\000" : "\\0" } else if c == '\\' { escaped = "\\\\" } else if c == '$' && style == .TYPESCRIPT_TEMPLATE { escaped = "\\$" } else if c < ' ' { escaped = "\\x" + HEX.get(c >> 4) + HEX.get(c & 15) } else { continue } builder.append(text.slice(start, i)) builder.append(escaped) start = i + 1 } builder.append(text.slice(start, count)) if style != .TYPESCRIPT_TEMPLATE { builder.append(quoteString) } return builder.toString } enum Associativity { NONE LEFT RIGHT } enum Precedence { def incrementIfLeftAssociative(associativity Associativity) Precedence { return (self + ((associativity == .LEFT) as int)) as Precedence } def incrementIfRightAssociative(associativity Associativity) Precedence { return (self + ((associativity == .RIGHT) as int)) as Precedence } } } ================================================ FILE: src/backend/javascript.sk ================================================ namespace Skew { class JavaScriptTarget : CompilerTarget { over name string { return "JavaScript" } over extension string { return "js" } over stopAfterResolve bool { return false } over supportsNestedTypes bool { return true } over removeSingletonInterfaces bool { return true } over stringEncoding Unicode.Encoding { return .UTF16 } over editOptions(options CompilerOptions) { options.define("TARGET", "JAVASCRIPT") } over includeSources(sources List) { sources.prepend(Source.new("", NATIVE_LIBRARY_JS)) } over createEmitter(context PassContext) Emitter { return JavaScriptEmitter.new(context, context.options, context.cache) } } class JavaScriptEmitter : Emitter { enum BooleanSwap { SWAP NO_SWAP } enum ExtractGroupsMode { ALL_SYMBOLS ONLY_LOCAL_VARIABLES ONLY_INSTANCE_VARIABLES } class SymbolGroup { const symbols List const count int } enum AfterToken { AFTER_KEYWORD AFTER_PARENTHESIS } enum BracesMode { MUST_KEEP_BRACES CAN_OMIT_BRACES } enum SpecialVariable { NONE AS_STRING CREATE EXTENDS IS_BOOL IS_DOUBLE IS_INT IS_STRING MULTIPLY PROTOTYPE } const _context PassContext const _options CompilerOptions const _cache TypeCache const _isSpecialVariableNeeded IntMap = {} const _loopLabels IntMap = {} const _specialVariables IntMap = {} var _global ObjectSymbol = null var _symbolsToExport List = [] var _allSpecialVariables List = null var _exportsNamespace ObjectSymbol = null var _enclosingFunction FunctionSymbol = null # This includes lambdas during patching but not during emission var _enclosingLoop Node = null var _namespacePrefix = "" var _previousNode Node = null var _previousSymbol Symbol = null # There's this stupid corner case in JavaScript syntax where a for-in loop # allows the variable to be initialized. That looks like this: # # // This is valid JavaScript # for (var x = 'y' in z) {} # # This means that the variable initializer in the for-in loop cannot be # initialized using an "in" expression without parenthesizing it first: # # // This is invalid JavaScript # for (var x = 'y' in z; x; x = !x) {} # # // The initializer must be parenthesized to be valid JavaScript # for (var x = ('y' in z); x; x = !x) {} # # Rather than parenthesizing all "in" expressions, this variable tracks # whether the current state is nested inside a for-in initializer and # only parenthesizes then. var _parenthesizeInExpressions = 0 # Source map support var _currentColumn = 0 var _currentLine = 0 var _generator = SourceMapGenerator.new var _previousSource Source = null var _previousStart = 0 var _sourceMap = false # A union-find data structure is used to quickly merge symbols into # groups. All local variables inside a function are merged with that # function. The map create a quick way of getting from a symbol to its # union/find index. const _allSymbols List = [] const _localVariableUnionFind = UnionFind.new const _namingGroupIndexForSymbol IntMap = {} const _symbolCounts IntMap = {} var _nextSymbolName = 0 # For minification var _mangle = false var _minify = false var _needsSemicolon = false var _newline = "\n" var _space = " " var _previousCodeUnit = '\0' # For tracking "this" vs "self" var _currentSelf VariableSymbol = null var _needsSelf = false over visit(global ObjectSymbol) { _mangle = _options.jsMangle _minify = _options.jsMinify _sourceMap = _options.jsSourceMap _global = global if _minify { _indentAmount = "" _newline = "" _space = "" } # Load special-cased variables for variable in global.variables { var special = _specialVariableMap.get(variable.name, .NONE) if special != .NONE { _specialVariables[special] = variable } } assert(SpecialVariable.AS_STRING in _specialVariables) assert(SpecialVariable.CREATE in _specialVariables) assert(SpecialVariable.EXTENDS in _specialVariables) assert(SpecialVariable.IS_BOOL in _specialVariables) assert(SpecialVariable.IS_DOUBLE in _specialVariables) assert(SpecialVariable.IS_INT in _specialVariables) assert(SpecialVariable.IS_STRING in _specialVariables) assert(SpecialVariable.MULTIPLY in _specialVariables) assert(SpecialVariable.PROTOTYPE in _specialVariables) # These don't need to be initialized _specialVariables[SpecialVariable.PROTOTYPE].value = null # Sort these so their order is deterministic _allSpecialVariables = _specialVariables.values _allSpecialVariables.sort(Symbol.SORT_VARIABLES_BY_ID) # Preprocess the code if _mangle { _liftGlobals(global) } if _options.inlineAllFunctions { _maybeInlineFunctions(global) } shakingPass(global, _cache.entryPointSymbol, .IGNORE_TYPES) _prepareGlobal(global) _convertLambdasToFunctions(global) var objects = _sortedObjects(global) # Make sure the "__create" variable is inserted when used even if "__extends" isn't used var create = _specialVariables[SpecialVariable.CREATE] if create in global.variables { _isSpecialVariableNeeded[create.id] = 0 } # The entire body of code is wrapped in a closure for safety _emit(_indent + "(function(" + (_exportsNamespace != null ? _mangleName(_exportsNamespace) : "") + ")" + _space + "{" + _newline + "") _increaseIndent # Emit special-cased variables that must come first for variable in _allSpecialVariables { if variable.id in _isSpecialVariableNeeded { if variable.value != null && variable.value.kind == .LAMBDA { _emitFunction(_convertLambdaToFunction(variable)) } else { _emitVariable(variable) } } } # Emit objects and functions for object in objects { _emitObject(object) } # Emit variables var statement = Node.createVariables for object in objects { _namespacePrefix = "" for o = object; o.kind != .OBJECT_GLOBAL; o = o.parent.asObjectSymbol { _namespacePrefix = _mangleName(o) + "." + _namespacePrefix } for variable in object.variables { if !(variable in _allSpecialVariables) { if _mangle && _namespacePrefix == "" && !variable.isImportedOrExported { statement.appendChild(Node.createVariable(variable)) } else { _emitVariable(variable) } } } } _namespacePrefix = "" # Group adjacent variables into a single statement during mangling if statement.hasChildren { _emitNewlineBeforeSymbol(statement.firstChild.symbol) _emitStatement(statement) _emitNewlineAfterSymbol(statement.firstChild.symbol) for child = statement.firstChild; child != null; child = child.nextSibling { child.removeChildren } } # Emit entry point var entryPointSymbol = _cache.entryPointSymbol if entryPointSymbol != null { var type = entryPointSymbol.resolvedType var callText = _fullName(entryPointSymbol) + (type.argumentTypes.isEmpty ? "()" : "(process.argv.slice(2))") _emitSemicolonIfNeeded _emit(_newline + _indent + (type.returnType == _cache.intType ? "process.exit(" + callText + ")" : callText)) _emitSemicolonAfterStatement } # End the closure wrapping everything _decreaseIndent _emit(_indent + "})(" + (_exportsNamespace != null ? "this" : "") + ");\n") var codeName = _options.outputDirectory != null ? _options.outputDirectory + "/compiled.js" : _options.outputFile var mapName = codeName != null ? codeName + ".map" : null # Obfuscate the sourceMappingURL so it's not incorrectly picked up as the # sourceMappingURL for the compiled JavaScript compiler file if _sourceMap { _emit("/") _emit("/# sourceMappingURL=" + splitPath(mapName).entry + "\n") } _createSource(codeName, .ALWAYS_EMIT) # Create the source map if _sourceMap { _emit(_generator.toString) _createSource(mapName, .ALWAYS_EMIT) } } over _emit(text string) { if _minify || _sourceMap { var n = text.count for i in 0..n { if text[i] == '\n' { _currentColumn = 0 _currentLine++ } else { _currentColumn++ } } if n != 0 { _previousCodeUnit = text[n - 1] } } _code.append(text) } def _liftGlobals(global ObjectSymbol) { var globalObjects List = [] var globalFunctions List = [] var globalVariables List = [] _liftGlobals(global, globalObjects, globalFunctions, globalVariables) for object in globalObjects { object.parent = global } for function in globalFunctions { function.parent = global } for variable in globalVariables { variable.parent = global } global.objects.append(globalObjects) global.functions.append(globalFunctions) global.variables.append(globalVariables) } def _liftGlobals(symbol ObjectSymbol, globalObjects List, globalFunctions List, globalVariables List) { var shouldLiftGlobals = symbol.parent != null # Scan over child objects symbol.objects.removeIf(object => { _liftGlobals(object, globalObjects, globalFunctions, globalVariables) if shouldLiftGlobals && !object.isImportedOrExported { globalObjects.append(object) return true } return false }) symbol.functions.removeIf(function => { if shouldLiftGlobals && function.kind == .FUNCTION_GLOBAL && !function.isImportedOrExported { globalFunctions.append(function) return true } return false }) # Scan over child variables symbol.variables.removeIf(variable => { if shouldLiftGlobals && variable.kind == .VARIABLE_GLOBAL && !variable.isImportedOrExported { globalVariables.append(variable) return true } return false }) } def _collectInlineableFunctions(symbol ObjectSymbol, listAppends List, mapInserts List) { for object in symbol.objects { _collectInlineableFunctions(object, listAppends, mapInserts) } for function in symbol.functions { if function.block == null || !function.block.hasTwoChildren { continue } var arguments = function.arguments # "foo([], 0)" => "[0]" where "foo" is "def foo(a, b) { a.push(b); return a }" if arguments.count == 2 { var first = function.block.firstChild var second = function.block.lastChild if first.kind == .EXPRESSION && first.expressionValue.kind == .CALL && second.kind == .RETURN && second.returnValue != null { var call = first.expressionValue var callValue = call.callValue if call.hasTwoChildren && callValue.kind == .DOT && callValue.asString == "push" && _isReferenceTo(callValue.dotTarget, arguments[0]) && _isReferenceTo(call.lastChild, arguments[1]) && _isReferenceTo(second.returnValue, arguments[0]) { for callSite in _context.callGraph.callInfoForSymbol(function).callSites { if callSite != null && callSite.callNode.kind == .CALL { assert(callSite.callNode.symbol == function) listAppends.append(callSite.callNode) } } } } } # "foo({}, 0, 1)" => "{0: 1}" where "foo" is "def foo(a, b, c) { a[b] = c; return a }" else if arguments.count == 3 { var keyType = arguments[1].resolvedType var first = function.block.firstChild var second = function.block.lastChild if (keyType == .DYNAMIC || _cache.isEquivalentToInt(keyType) || _cache.isEquivalentToString(keyType)) && first.kind == .EXPRESSION && first.expressionValue.kind == .ASSIGN_INDEX && second.kind == .RETURN && second.returnValue != null { var assign = first.expressionValue if _isReferenceTo(assign.assignIndexLeft, arguments[0]) && _isReferenceTo(assign.assignIndexCenter, arguments[1]) && _isReferenceTo(assign.assignIndexRight, arguments[2]) && _isReferenceTo(second.returnValue, arguments[0]) { for callSite in _context.callGraph.callInfoForSymbol(function).callSites { if callSite != null && callSite.callNode.kind == .CALL { assert(callSite.callNode.symbol == function) mapInserts.append(callSite.callNode) } } } } } } } # This uses iteration until fixed point to avoid dependence on inlining order def _maybeInlineFunctions(global ObjectSymbol) { var listAppends List = [] var mapInserts List = [] _collectInlineableFunctions(global, listAppends, mapInserts) # List append fixed point var changed = true while changed { changed = false for i in 0..listAppends.count { var node = listAppends[i] # This will be null if it was already inlined if node == null { continue } var firstArgument = node.callValue.nextSibling var secondArgument = firstArgument.nextSibling # List expressions are sometimes casted if firstArgument.kind == .CAST { firstArgument = firstArgument.castValue } # Only check when the inputs are constants if firstArgument.kind == .INITIALIZER_LIST { node.become(firstArgument.remove.appendChild(secondArgument.remove)) listAppends[i] = null changed = true } } } # Map insert fixed point changed = true while changed { changed = false for i in 0..mapInserts.count { var node = mapInserts[i] # This will be null if it was already inlined if node == null { continue } var firstArgument = node.callValue.nextSibling var secondArgument = firstArgument.nextSibling var thirdArgument = secondArgument.nextSibling # Map expressions are sometimes casted if firstArgument.kind == .CAST { firstArgument = firstArgument.castValue } # Only check when the inputs are constants if firstArgument.kind == .INITIALIZER_MAP && (secondArgument.isInt || secondArgument.isString) { node.become(firstArgument.remove.appendChild(Node.createPair(secondArgument.remove, thirdArgument.remove).withType(.DYNAMIC))) mapInserts[i] = null changed = true } } } } def _prepareGlobal(global ObjectSymbol) { # Lower certain stuff into JavaScript (for example, "x as bool" becomes "!!x") _patchObject(global) _exportSymbols # Skip everything below if we aren't mangling if !_mangle { return } # These will be culled by tree shaking regardless of whether they are needed for variable in _allSpecialVariables { if variable.id in _isSpecialVariableNeeded { _allocateNamingGroupIndex(variable) _patchNode(variable.value) } } # Rename symbols based on frequency for better compression _renameSymbols } def _convertLambdaToFunction(variable VariableSymbol) FunctionSymbol { var function = variable.value.symbol.asFunctionSymbol function.kind = .FUNCTION_GLOBAL function.parent = variable.parent function.name = variable.name if function.block.parent != null { function.block.remove } return function } def _convertLambdasToFunctions(symbol ObjectSymbol) { for object in symbol.objects { _convertLambdasToFunctions(object) } symbol.variables.removeIf(variable => { if variable.kind == .VARIABLE_GLOBAL && variable.isConst && !variable.isExported && variable.value != null && variable.value.kind == .LAMBDA { symbol.functions.append(_convertLambdaToFunction(variable)) return true } return false }) } def _allocateNamingGroupIndex(symbol Symbol) { if _mangle && !(symbol.id in _namingGroupIndexForSymbol) { var index = _localVariableUnionFind.allocate _namingGroupIndexForSymbol[symbol.id] = index _allSymbols.append(symbol) # Explicitly add function arguments since they won't be reached by # normal tree traversal if symbol.kind.isFunction { var this = symbol.asFunctionSymbol.this if this != null { _allocateNamingGroupIndex(this) } for argument in symbol.asFunctionSymbol.arguments { _allocateNamingGroupIndex(argument) } } } } def _renameSymbols { # This holds the groups used for naming. Unioning two labels using # this object will cause both groups of symbols to have the same name. var namingGroupsUnionFind = UnionFind.new.allocate(_allSymbols.count) # These are optional and only reduce the number of generated names var order List = [] _aliasLocalVariables(namingGroupsUnionFind, order) _aliasUnrelatedProperties(namingGroupsUnionFind, order) # Ensure all overridden symbols have the same generated name. This is # manditory for correctness, otherwise virtual functions break. var namingGroupMap IntMap = {} for symbol in _allSymbols { if symbol.kind.isFunction { var function = symbol.asFunctionSymbol assert(function.id in _namingGroupIndexForSymbol) var id = namingGroupMap.get(function.namingGroup, -1) if id == -1 { namingGroupMap[function.namingGroup] = _namingGroupIndexForSymbol[function.id] } else { namingGroupsUnionFind.union(id, _namingGroupIndexForSymbol[function.id]) } } } # Collect all reserved names together into one big set for querying var reservedNames StringMap = _isKeyword.clone for symbol in _allSymbols { if !_shouldRenameSymbol(symbol) { reservedNames[symbol.name] = 0 } } # Everything that should have the same name is now grouped together. # Generate and assign names to all internal symbols, but use shorter # names for more frequently used symbols. var sortedGroups List = [] for group in _extractGroups(namingGroupsUnionFind, .ALL_SYMBOLS) { var count = 0 for symbol in group { if _shouldRenameSymbol(symbol) { count += _symbolCounts.get(symbol.id, 0) } } sortedGroups.append(SymbolGroup.new(group, count)) } # Create a total order to make builds deterministic when maps use hashing sortedGroups.sort((a, b) => { var difference = b.count <=> a.count if difference == 0 { difference = b.symbols.count <=> a.symbols.count for i = 0; difference == 0 && i < a.symbols.count; i++ { difference = a.symbols[i].id <=> b.symbols[i].id } } return difference }) for group in sortedGroups { var name = "" for symbol in group.symbols { if _shouldRenameSymbol(symbol) { if name == "" { name = _generateSymbolName(reservedNames) } symbol.name = name } } } } # Merge local variables from different functions together in the order # they were declared. This will cause every argument list to use the same # variables in the same order, which should offer better gzip: # # function d(a, b) {} # function e(a, b, c) {} # def _aliasLocalVariables(unionFind UnionFind, order List) { _zipTogetherInOrder(unionFind, order, _extractGroups(_localVariableUnionFind, .ONLY_LOCAL_VARIABLES)) } # Merge all related types together into naming groups. This ensures names # will be unique within a subclass hierarchy allowing names to be # duplicated in separate subclass hierarchies. def _aliasUnrelatedProperties(unionFind UnionFind, order List) { var relatedTypesUnionFind = UnionFind.new.allocate(_allSymbols.count) for i in 0.._allSymbols.count { var symbol = _allSymbols[i] if symbol.kind == .OBJECT_CLASS { var baseClass = symbol.asObjectSymbol.baseClass if baseClass != null { relatedTypesUnionFind.union(i, _namingGroupIndexForSymbol[baseClass.id]) } for variable in symbol.asObjectSymbol.variables { relatedTypesUnionFind.union(i, _namingGroupIndexForSymbol[variable.id]) } } } _zipTogetherInOrder(unionFind, order, _extractGroups(relatedTypesUnionFind, .ONLY_INSTANCE_VARIABLES)) } def _zipTogetherInOrder(unionFind UnionFind, order List, groups List>) { for group in groups { for i in 0..group.count { var symbol = group[i] var index = _namingGroupIndexForSymbol[symbol.id] if i >= order.count { order.append(index) } else { unionFind.union(index, order[i]) } } } } def _generateSymbolName(reservedNames StringMap) string { while true { var name = _numberToName(_nextSymbolName) _nextSymbolName++ if !(name in reservedNames) { return name } } } def _extractGroups(unionFind UnionFind, mode ExtractGroupsMode) List> { var labelToGroup IntMap> = {} for symbol in _allSymbols { if mode == .ONLY_LOCAL_VARIABLES && !symbol.kind.isLocalOrArgumentVariable || mode == .ONLY_INSTANCE_VARIABLES && symbol.kind != .VARIABLE_INSTANCE { continue } assert(symbol.id in _namingGroupIndexForSymbol) var label = unionFind.find(_namingGroupIndexForSymbol[symbol.id]) var group = labelToGroup.get(label, null) if group == null { group = [] labelToGroup[label] = group } group.append(symbol) } # Sort each resulting group to make builds deterministic when maps use hashing var groups = labelToGroup.values for group in groups { group.sort(Symbol.SORT_BY_ID) } return groups } def _addMapping(range Range) { if _sourceMap && range != null { var source = range.source var start = range.start if _previousSource != source || _previousStart != start { var location = source.indexToLineColumn(start) _generator.addMapping(source, location.line, location.column, _currentLine, _currentColumn) _previousStart = start _previousSource = source } } } def _emitSemicolonAfterStatement { if !_minify { _emit(";\n") } else { _needsSemicolon = true } } def _emitSemicolonIfNeeded { if _needsSemicolon { _emit(";") _needsSemicolon = false } _maybeEmitMinifedNewline } # Lots of text editors choke up on long lines, so add a newline every now # and then for usability's sake def _maybeEmitMinifedNewline { if _minify && _currentColumn > 1024 { _emit("\n") } } def _emitNewlineBeforeSymbol(symbol Symbol) { _emitSemicolonIfNeeded if !_minify && _previousSymbol != null && (!_previousSymbol.kind.isObject || !symbol.kind.isObject || symbol.comments != null || _previousSymbol.kind.isEnumOrFlags || symbol.kind.isEnumOrFlags) && (!_previousSymbol.kind.isVariable || !symbol.kind.isVariable || symbol.comments != null) { _emit("\n") } _previousSymbol = null _addMapping(symbol.range) } def _emitNewlineAfterSymbol(symbol Symbol) { _previousSymbol = symbol } def _emitNewlineBeforeStatement(node Node) { if !_minify && _previousNode != null && (node.comments != null || !_isCompactNodeKind(_previousNode.kind) || !_isCompactNodeKind(node.kind)) { _emit("\n") } else { _maybeEmitMinifedNewline } _previousNode = null } def _emitNewlineAfterStatement(node Node) { _previousNode = node } def _emitComments(comments List) { if comments != null && !_minify { for comment in comments { for line in comment.lines { _emit(_indent + "//" + line + "\n") } if comment.hasGapBelow { _emit("\n") } } } } def _emitObject(symbol ObjectSymbol) { if symbol.isImported { return } var foundPrimaryConstructor = false _namespacePrefix = symbol.parent != null ? _computeNamespacePrefix(symbol.parent.asObjectSymbol) : "" switch symbol.kind { case .OBJECT_NAMESPACE, .OBJECT_INTERFACE, .OBJECT_WRAPPED { if symbol.forwardTo == null && symbol != _exportsNamespace { _emitNewlineBeforeSymbol(symbol) _emitComments(symbol.comments) _emit(_indent + (_namespacePrefix == "" ? "var " : _namespacePrefix) + _mangleName(symbol) + _space + "=" + _space + "{}") _emitSemicolonAfterStatement _emitNewlineAfterSymbol(symbol) } } case .OBJECT_ENUM, .OBJECT_FLAGS { _emitNewlineBeforeSymbol(symbol) _emitComments(symbol.comments) _emit(_indent + (_namespacePrefix == "" ? "var " : _namespacePrefix) + _mangleName(symbol) + _space + "=" + _space + "{") _increaseIndent var isFirst = true for variable in symbol.variables { if variable.kind == .VARIABLE_ENUM_OR_FLAGS { if isFirst { isFirst = false } else { _emit(",") } _emit(_newline) _emitNewlineBeforeSymbol(variable) _emitComments(variable.comments) _emit(_indent + _mangleName(variable) + ":" + _space) _emitExpression(variable.value, .COMMA) _emitNewlineAfterSymbol(variable) } } _decreaseIndent if !isFirst && !_minify { _emit("\n" + _indent) } _emit("}") _emitSemicolonAfterStatement _emitNewlineAfterSymbol(symbol) } case .OBJECT_CLASS { var variable = _specialVariables[SpecialVariable.EXTENDS] for function in symbol.functions { if function.isPrimaryConstructor { if function.comments == null && symbol.comments != null { function.comments = symbol.comments } _emitFunction(function) if symbol.baseClass != null || symbol.kind == .OBJECT_CLASS && symbol.extends != null { if !_minify { _emit("\n" + _indent) } _emitSemicolonIfNeeded _addMapping(variable.range) _emit(_mangleName(variable) + "(" + _fullName(symbol) + "," + _space) if symbol.baseClass != null { _emit(_fullName(symbol.baseClass)) } else { assert(symbol.kind == .OBJECT_CLASS && symbol.extends != null) _emitExpression(symbol.extends, .LOWEST) } _emit(")") _emitSemicolonAfterStatement } foundPrimaryConstructor = true break } } # Emit a namespace if the class is never constructed if !foundPrimaryConstructor { _emitNewlineBeforeSymbol(symbol) _emit(_indent + (_namespacePrefix == "" && !symbol.isExported ? "var " : _namespacePrefix) + _mangleName(symbol) + _space + "=" + _space + "{}") _emitSemicolonAfterStatement } } } if symbol.kind != .OBJECT_GLOBAL { _namespacePrefix += _mangleName(symbol) + "." } if symbol.usePrototypeCache { _emitSemicolonIfNeeded _emit(_newline + _indent + _mangleName(_specialVariables[SpecialVariable.PROTOTYPE]) + _space + "=" + _space + _fullName(symbol) + ".prototype") _emitSemicolonAfterStatement } # Ignore instance functions if the class is never constructed for function in symbol.functions { if foundPrimaryConstructor ? !function.isPrimaryConstructor : function.kind == .FUNCTION_GLOBAL { _emitFunction(function) } } } def _emitArgumentList(arguments List) { for argument in arguments { if argument != arguments.first { _emit("," + _space) } _addMapping(argument.range) _emit(_mangleName(argument)) } } def _emitFunction(symbol FunctionSymbol) { if symbol.block == null { return } _emitNewlineBeforeSymbol(symbol) _emitComments(symbol.comments) var isExpression = _namespacePrefix != "" || symbol.isExported var name = _mangleName(symbol.isPrimaryConstructor ? symbol.parent : symbol) if isExpression { _emit(_indent + ( symbol.kind != .FUNCTION_INSTANCE ? _namespacePrefix : symbol.parent.usePrototypeCache ? _mangleName(_specialVariables[SpecialVariable.PROTOTYPE]) + "." : _namespacePrefix + "prototype.") + name + _space + "=" + _space + "function(") } else { _emit(_indent + "function " + name + "(") } _emitArgumentList(symbol.arguments) _emit(")" + _space + "{" + _newline) _increaseIndent _enclosingFunction = symbol _emitStatements(symbol.block) _enclosingFunction = null _decreaseIndent _emit(_indent + "}") if isExpression { _emitSemicolonAfterStatement } else { _needsSemicolon = false _emit(_newline) } _emitNewlineAfterSymbol(symbol) # Secondary constructors need the same prototype as the primary constructor if symbol.kind == .FUNCTION_CONSTRUCTOR && !symbol.isPrimaryConstructor { _emitSemicolonIfNeeded _emit(_newline + _indent + _fullName(symbol) + ".prototype" + _space + "=" + _space + ( symbol.parent.usePrototypeCache ? _mangleName(_specialVariables[SpecialVariable.PROTOTYPE]) : _fullName(symbol.parent) + ".prototype")) _emitSemicolonAfterStatement } } def _emitVariable(symbol VariableSymbol) { if symbol.isImported { return } if symbol.kind != .VARIABLE_INSTANCE && symbol.kind != .VARIABLE_ENUM_OR_FLAGS && (symbol.value != null || _namespacePrefix == "" || symbol.kind.isLocalOrArgumentVariable) { _emitNewlineBeforeSymbol(symbol) _emitComments(symbol.comments) _emit(_indent + (_namespacePrefix == "" || symbol.kind.isLocalOrArgumentVariable ? "var " : _namespacePrefix) + _mangleName(symbol)) if symbol.value != null { _emit(_space + "=" + _space) _emitExpression(symbol.value, .COMMA) } _emitSemicolonAfterStatement _emitNewlineAfterSymbol(symbol) } } def _emitStatements(node Node) { _previousNode = null for child = node.firstChild; child != null; child = child.nextSibling { _emitSemicolonIfNeeded _emitNewlineBeforeStatement(child) _addMapping(child.range) _emitComments(child.comments) _emitStatement(child) _emitNewlineAfterStatement(child) } _previousNode = null } def _emitBlock(node Node, after AfterToken, mode BracesMode) { var shouldMinify = mode == .CAN_OMIT_BRACES && _minify _addMapping(node.range) if shouldMinify && !node.hasChildren { _emit(";") } else if shouldMinify && node.hasOneChild && node.firstChild.kind != .COMMENT_BLOCK { if after == .AFTER_KEYWORD { _emit(" ") } _emitStatement(node.firstChild) } else { _emit(_space + "{" + _newline) if node.hasChildren { _increaseIndent _emitStatements(node) _decreaseIndent } _emit(_indent + "}") _needsSemicolon = false } } def _emitVariables(node Node) { _emit("var ") for child = node.firstChild; child != null; child = child.nextSibling { if child.previousSibling != null { _emit("," + _space) } var symbol = child.symbol.asVariableSymbol _emit(_mangleName(symbol)) if symbol.value != null { _emit(_space + "=" + _space) _emitExpression(symbol.value, .COMMA) } } } def _canRemoveSpaceBeforeKeyword(node Node) bool { var kind = node.kind return kind.isUnary && !kind.isUnaryPostfix || node.isString || node.isNumberLessThanZero || kind.isInitializer || (kind == .HOOK || kind == .SEQUENCE) && _canRemoveSpaceBeforeKeyword(node.firstChild) } def _emitSpaceBeforeKeyword(node Node) { if !_minify || !_canRemoveSpaceBeforeKeyword(node) { _emit(" ") } } def _emitStatement(node Node) { switch node.kind { case .COMMENT_BLOCK {} case .VARIABLES { _emit(_indent) _emitVariables(node) _emitSemicolonAfterStatement } case .EXPRESSION { _emit(_indent) _emitExpression(node.expressionValue, .LOWEST) _emitSemicolonAfterStatement } case .BREAK { var label = _loopLabels.get(node.id, null) _emit(_indent + "break") if label != null { _emit(" " + _mangleName(label)) } _emitSemicolonAfterStatement } case .CONTINUE { _emit(_indent + "continue") _emitSemicolonAfterStatement } case .RETURN { _emit(_indent + "return") var value = node.returnValue if value != null { var comments = value.comments if !_minify && comments != null { # JavaScript needs parentheses here to avoid ASI issues _emit(" (\n") _increaseIndent _emitComments(comments) _emit(_indent) _emitExpression(value, .LOWEST) _decreaseIndent _emit(")") } else { _emitSpaceBeforeKeyword(value) _emitExpression(value, .LOWEST) } } _emitSemicolonAfterStatement } case .THROW { var value = node.throwValue _emit(_indent + "throw") _emitSpaceBeforeKeyword(value) _emitExpression(value, .LOWEST) _emitSemicolonAfterStatement } case .FOR { var setup = node.forSetup var test = node.forTest var update = node.forUpdate _emit(_indent) _emitLoopLabel(node) _emit("for" + _space + "(") if !setup.isEmptySequence { _parenthesizeInExpressions++ if setup.kind == .VARIABLES { _emitVariables(setup) } else { _emitExpression(setup, .LOWEST) } _parenthesizeInExpressions-- } _emit(";") if !test.isEmptySequence { _emit(_space) _emitExpression(test, .LOWEST) } _emit(";") if !update.isEmptySequence { _emit(_space) _emitExpression(update, .LOWEST) } _emit(")") _emitBlock(node.forBlock, .AFTER_PARENTHESIS, .CAN_OMIT_BRACES) _emit(_newline) } case .FOREACH { _emit(_indent) _emitLoopLabel(node) _emit("for" + _space + "(var " + _mangleName(node.symbol) + " in ") _emitExpression(node.foreachValue, .LOWEST) _emit(")") _emitBlock(node.foreachBlock, .AFTER_PARENTHESIS, .CAN_OMIT_BRACES) _emit(_newline) } case .IF { _emit(_indent) _emitIf(node) _emit(_newline) } case .SWITCH { var switchValue = node.switchValue _emit(_indent + "switch" + _space + "(") _emitExpression(switchValue, .LOWEST) _emit(")" + _space + "{" + _newline) _increaseIndent for child = switchValue.nextSibling; child != null; child = child.nextSibling { var block = child.caseBlock _emitSemicolonIfNeeded if child.previousSibling != switchValue { _emit(_newline) } _emitComments(child.comments) if child.hasOneChild { _emit(_indent + "default:") } else { for value = child.firstChild; value != block; value = value.nextSibling { if value.previousSibling != null { _emit(_newline) } _emitComments(value.comments) _emit(_indent + "case") _emitSpaceBeforeKeyword(value) _emitExpression(value, .LOWEST) _emit(":") } } if !_minify { _emit(" {\n") _increaseIndent } _emitStatements(block) if block.hasControlFlowAtEnd { _emitSemicolonIfNeeded _emit(_indent + "break") _emitSemicolonAfterStatement } if !_minify { _decreaseIndent _emit(_indent + "}\n") } } _decreaseIndent _emit(_indent + "}" + _newline) _needsSemicolon = false } case .TRY { var tryBlock = node.tryBlock var finallyBlock = node.finallyBlock _emit(_indent + "try") _emitBlock(tryBlock, .AFTER_KEYWORD, .MUST_KEEP_BRACES) _emit(_newline) for child = tryBlock.nextSibling; child != finallyBlock; child = child.nextSibling { _emit(_newline) _emitComments(child.comments) _emit(_indent + "catch" + _space + "(" + _mangleName(child.symbol) + ")") _emitBlock(child.catchBlock, .AFTER_KEYWORD, .MUST_KEEP_BRACES) _emit(_newline) } if finallyBlock != null { _emit(_newline) _emitComments(finallyBlock.comments) _emit(_indent + "finally") _emitBlock(finallyBlock, .AFTER_KEYWORD, .MUST_KEEP_BRACES) _emit(_newline) } } case .WHILE { _emit(_indent) _emitLoopLabel(node) _emit("while" + _space + "(") _emitExpression(node.whileTest, .LOWEST) _emit(")") _emitBlock(node.whileBlock, .AFTER_PARENTHESIS, .CAN_OMIT_BRACES) _emit(_newline) } default { assert(false) } } } def _emitLoopLabel(node Node) { var label = _loopLabels.get(node.id, null) if label != null { _emit(_mangleName(label) + ":" + _space) } } def _emitIf(node Node) { var trueBlock = node.ifTrue var falseBlock = node.ifFalse _emit("if" + _space + "(") _emitExpression(node.ifTest, .LOWEST) _emit(")") # Make sure to always keep braces to avoid the dangling "else" case # "if (a) if (b) c; else d; else e;" # "if (a) { if (b) if (c) d; else e; } else f;" # "if (a) { if (b) c; else if (d) e; } else f;" # "if (a) { while (true) if (b) break; } else c;" var braces = BracesMode.CAN_OMIT_BRACES if falseBlock != null { var statement = trueBlock.blockStatement if statement != null && (statement.kind == .IF || statement.kind == .FOR && statement.forBlock.blockStatement != null || statement.kind == .FOREACH && statement.foreachBlock.blockStatement != null || statement.kind == .WHILE && statement.whileBlock.blockStatement != null) { braces = .MUST_KEEP_BRACES } } _emitBlock(node.ifTrue, .AFTER_PARENTHESIS, braces) if falseBlock != null { var singleIf = _singleIf(falseBlock) _emitSemicolonIfNeeded _emit(_newline + _newline) _emitComments(falseBlock.comments) if singleIf != null { _emitComments(singleIf.comments) } _emit(_indent + "else") if singleIf != null { _emit(" ") _emitIf(singleIf) } else { _emitBlock(falseBlock, .AFTER_KEYWORD, .CAN_OMIT_BRACES) } } } def _emitContent(content Content) { switch content.kind { case .BOOL { _emit(content.asBool.toString) } case .INT { _emit(content.asInt.toString) } case .DOUBLE { var value = content.asDouble var text = value.isNaN ? "NaN" : value == Math.INFINITY ? "Infinity" : value == -Math.INFINITY ? "-Infinity" : # The C# implementation of double.ToString() uses an uppercase "E" TARGET == .CSHARP ? value.toString.toLowerCase : value.toString # "0.123" => ".123" # "-0.123" => "-.123" if _minify { if text.startsWith("0.") && text != "0." { text = text.slice(1) } else if text.startsWith("-0.") && text != "-0." { text = "-" + text.slice(2) } } _emit(text) } case .STRING { _emit(quoteString(content.asString, .SHORTEST, .OCTAL_WORKAROUND)) } } } def _emitCommaSeparatedExpressions(from Node, to Node) { while from != to { _emitExpression(from, .COMMA) from = from.nextSibling if from != to { _emit("," + _space) _maybeEmitMinifedNewline } } } # Calling a function in an expression that starts with something like "function(){}()" # must be wrapped in parentheses to avoid looking like a function statement def _lambdaMayNeedParentheses(node Node) bool { var parent = node.parent if parent == null { return false # Expression statements always have parents } switch parent.kind { case .CALL { return node == parent.callValue && _lambdaMayNeedParentheses(parent) } case .DOT { return _lambdaMayNeedParentheses(parent) } case .INDEX { return node == parent.indexLeft && _lambdaMayNeedParentheses(parent) } case .ASSIGN_INDEX { return node == parent.assignIndexLeft && _lambdaMayNeedParentheses(parent) } default { if parent.kind.isBinary { return node == parent.binaryLeft && _lambdaMayNeedParentheses(parent) } return true # Not sure, wrap to be safe } } } # Returns true if the provided call node must be parenthesized due to being inside a dot expression def _checkForDotParentOfCall(node Node) bool { assert(node.kind == .CALL) var p = node.parent while p != null { switch p.kind { case .CAST, .PARAMETERIZE { p = p.parent } case .DOT { return true } default { break } } } return false } def _emitExpression(node Node, precedence Precedence) { var kind = node.kind _addMapping(node.range) switch kind { case .TYPE { _emit(_fullName(node.resolvedType.symbol)) } case .NULL { _emit("null") } case .NAME { var symbol = node.symbol _emit(symbol != null ? _fullName(symbol) : node.asString) } case .DOT { _emitExpression(node.dotTarget, .MEMBER) _emit("." + (node.symbol != null ? _mangleName(node.symbol) : node.asString)) } case .CONSTANT { var wrap = precedence == .MEMBER && (node.isInt || node.isDouble && (node.asDouble.isFinite || node.asDouble < 0)) if wrap { _emit("(") } # Prevent "x - -1" from becoming "x--1" if _minify && node.isNumberLessThanZero && _previousCodeUnit == '-' { _emit(" ") } _emitContent(node.content) if wrap { _emit(")") } } case .CALL { var value = node.callValue var call = value.kind == .SUPER var isKeyword = value.kind == .NAME && value.symbol == null && value.asString in _keywordCallMap var parenthesize = isKeyword && Precedence.UNARY_POSTFIX < precedence var wrap = value.kind == .LAMBDA && _lambdaMayNeedParentheses(node) var isNew = false if parenthesize { _emit("(") } if wrap { _emit("(") } if !call && node.symbol != null && node.symbol.kind == .FUNCTION_CONSTRUCTOR { _emit("new " + _fullName(node.symbol)) isNew = true } else if !call && value.kind == .DOT && value.asString == "new" { _emit("new ") _emitExpression(value.dotTarget, .MEMBER) isNew = true } else { _emitExpression(value, .UNARY_POSTFIX) if call { _emit(".call") } } if wrap { _emit(")") } # Omit parentheses during mangling when possible if !isNew || !_mangle || call || value.nextSibling != null || _checkForDotParentOfCall(node) { _emit(isKeyword ? " " : "(") if call { _emit(_mangleName(_enclosingFunction.this)) } for child = value.nextSibling; child != null; child = child.nextSibling { if call || child.previousSibling != value { _emit("," + _space) _maybeEmitMinifedNewline } _emitExpression(child, .COMMA) } if !isKeyword { _emit(")") } } if parenthesize { _emit(")") } } case .INITIALIZER_LIST, .INITIALIZER_MAP { var useBraces = kind == .INITIALIZER_MAP var isIndented = false if !_minify { for child = node.firstChild; child != null; child = child.nextSibling { if child.comments != null { isIndented = true break } } } _emit(useBraces ? "{" : "[") if isIndented { _increaseIndent } for child = node.firstChild; child != null; child = child.nextSibling { if child.previousSibling != null { _emit("," + (isIndented ? "" : _space)) _maybeEmitMinifedNewline } if isIndented { _emit("\n") _emitComments(child.comments) _emit(_indent) } _emitExpression(child, .COMMA) } if isIndented { _decreaseIndent _emit("\n" + _indent) } _emit(useBraces ? "}" : "]") } case .PAIR { _emitExpression(node.firstValue, .LOWEST) _emit(":" + _space) _emitExpression(node.secondValue, .LOWEST) } case .INDEX { _emitExpression(node.indexLeft, .UNARY_POSTFIX) _emit("[") _emitExpression(node.indexRight, .LOWEST) _emit("]") } case .ASSIGN_INDEX { if Precedence.ASSIGN < precedence { _emit("(") } _emitExpression(node.assignIndexLeft, .UNARY_POSTFIX) _emit("[") _emitExpression(node.assignIndexCenter, .LOWEST) _emit("]" + _space + "=" + _space + "") _emitExpression(node.assignIndexRight, .ASSIGN) if Precedence.ASSIGN < precedence { _emit(")") } } case .CAST { _emitExpression(node.castValue, precedence) } case .PARAMETERIZE { _emitExpression(node.parameterizeValue, precedence) } case .SEQUENCE { if Precedence.COMMA <= precedence { _emit("(") } _emitCommaSeparatedExpressions(node.firstChild, null) if Precedence.COMMA <= precedence { _emit(")") } } case .SUPER { _emit(_fullName(node.symbol)) } case .HOOK { if Precedence.ASSIGN < precedence { _emit("(") } _emitExpression(node.hookTest, .LOGICAL_OR) _emit(_space + "?") var left = node.hookTrue if left.comments != null { _emit(_newline) _increaseIndent _emitComments(left.comments) _emit(_indent) _emitExpression(left, .ASSIGN) _decreaseIndent } else { _emit(_space) _emitExpression(left, .ASSIGN) } _emit(_space + ":") var right = node.hookFalse if right.comments != null { _emit(_newline) _increaseIndent _emitComments(right.comments) _emit(_indent) _emitExpression(right, .ASSIGN) _decreaseIndent } else { _emit(_space) _emitExpression(right, .ASSIGN) } if Precedence.ASSIGN < precedence { _emit(")") } } case .LAMBDA { var symbol = node.symbol.asFunctionSymbol _emit("function(") _emitArgumentList(symbol.arguments) _emit(")") _emitBlock(symbol.block, .AFTER_PARENTHESIS, .MUST_KEEP_BRACES) } case .COMPLEMENT, .NEGATIVE, .NOT, .POSITIVE, .POSTFIX_DECREMENT, .POSTFIX_INCREMENT, .PREFIX_DECREMENT, .PREFIX_INCREMENT { var value = node.unaryValue var info = operatorInfo[kind] if info.precedence < precedence { _emit("(") } if kind.isUnaryPostfix { _emitExpression(value, info.precedence) _emit(info.text) } else { # Prevent "x - -1" from becoming "x--1" if _minify && (kind == .POSITIVE || kind == .NEGATIVE || kind == .PREFIX_INCREMENT || kind == .PREFIX_DECREMENT) && info.text[0] == _previousCodeUnit { _emit(" ") } _emit(info.text) _emitExpression(value, info.precedence) } if info.precedence < precedence { _emit(")") } } case .TYPE_CHECK { var type = node.typeCheckType var resolvedType = type.resolvedType if resolvedType.isWrapped { resolvedType = _cache.unwrappedType(resolvedType) } if resolvedType.kind == .SYMBOL || type.kind != .TYPE { if Precedence.COMPARE < precedence { _emit("(") } _emitExpression(node.typeCheckValue, Precedence.COMPARE) _emit(" instanceof ") if resolvedType.kind == .SYMBOL { _emit(_fullName(resolvedType.symbol)) } else { _emitExpression(type, Precedence.COMPARE) } if Precedence.COMPARE < precedence { _emit(")") } } else { _emitExpression(node.typeCheckValue, precedence) } } default { if kind.isBinary { var info = operatorInfo[kind] var left = node.binaryLeft var right = node.binaryRight var extraEquals = left.resolvedType == .DYNAMIC || right.resolvedType == .DYNAMIC ? "=" : "" var needsParentheses = info.precedence < precedence || kind == .IN && _parenthesizeInExpressions != 0 if needsParentheses { _emit("(") } _emitExpression(node.binaryLeft, info.precedence.incrementIfRightAssociative(info.associativity)) # Always emit spaces around keyword operators, even when minifying var comments = _minify ? null : right.comments _emit(kind == .IN ? (left.isString ? _space : " ") + "in" + (comments != null ? "" : " ") : _space + (kind == .EQUAL ? "==" + extraEquals : kind == .NOT_EQUAL ? "!=" + extraEquals : info.text)) if comments != null { _emit(_newline) _increaseIndent _emitComments(comments) _emit(_indent) _emitExpression(right, info.precedence.incrementIfLeftAssociative(info.associativity)) _decreaseIndent } else { if kind != .IN { _emit(_space) } _emitExpression(right, info.precedence.incrementIfLeftAssociative(info.associativity)) } if needsParentheses { _emit(")") } } else { assert(false) } } } } def _patchObject(symbol ObjectSymbol) { _allocateNamingGroupIndex(symbol) # Subclasses need the extension stub if !symbol.isImported && (symbol.baseClass != null || symbol.kind == .OBJECT_CLASS && symbol.extends != null) { _specialVariable(.EXTENDS) _specialVariable(.CREATE) } # Scan over child objects for object in symbol.objects { _patchObject(object) if symbol == _global && object.isExported { _symbolsToExport.append(object) } } # Scan over child functions var isPrimaryConstructor = true var prototypeCount = 0 for function in symbol.functions { var block = function.block var this = function.this _allocateNamingGroupIndex(function) # Check to see if we need an explicit "self" parameter while patching the block _needsSelf = false _currentSelf = this _enclosingFunction = function _patchNode(block) _enclosingFunction = null # Only insert the "self" variable if required to handle capture inside lambdas if _needsSelf { _unionVariableWithFunction(this, function) if block != null { this.kind = .VARIABLE_LOCAL this.value = Node.createName("this").withType(.DYNAMIC) var variable = Node.createVariable(this) var merged = false # When mangling, add the "self" variable to an existing variable statement if present if _mangle && block.hasChildren { var firstChild = block.firstChild if firstChild.kind == .VARIABLES { firstChild.prependChild(variable) merged = true } else if firstChild.kind == .FOR { if firstChild.forSetup.kind == .VARIABLES { firstChild.forSetup.prependChild(variable) merged = true } else if firstChild.forSetup.isEmptySequence { firstChild.forSetup.replaceWith(Node.createVariables.appendChild(variable)) merged = true } } } if !merged { block.prependChild(Node.createVariables.appendChild(variable)) } } } else if this != null { this.name = "this" this.flags |= .IS_EXPORTED } for argument in function.arguments { _allocateNamingGroupIndex(argument) _unionVariableWithFunction(argument, function) } # Rename extra constructors overloads so they don't conflict if function.kind == .FUNCTION_CONSTRUCTOR && isPrimaryConstructor { function.flags |= .IS_PRIMARY_CONSTRUCTOR isPrimaryConstructor = false } # Mark the prototype variable as needed when the prototype is used else if _mangle && (function.kind == .FUNCTION_INSTANCE || function.kind == .FUNCTION_CONSTRUCTOR && !isPrimaryConstructor) { if ++prototypeCount == 2 { var variable = _specialVariable(.PROTOTYPE) _symbolCounts[variable.id] = _symbolCounts.get(variable.id, 0) + 1 symbol.flags |= .USE_PROTOTYPE_CACHE } } if symbol == _global && function.isExported { _symbolsToExport.append(function) } } # Scan over child variables for variable in symbol.variables { _allocateNamingGroupIndex(variable) _patchNode(variable.value) if symbol == _global && variable.isExported { _symbolsToExport.append(variable) } } } def _exportSymbols { if _symbolsToExport.isEmpty { return } _exportsNamespace = ObjectSymbol.new(.OBJECT_NAMESPACE, _global.scope.generateName("exports")) _exportsNamespace.resolvedType = Type.new(.SYMBOL, _exportsNamespace) _exportsNamespace.state = .INITIALIZED _exportsNamespace.scope = ObjectScope.new(_global.scope, _exportsNamespace) _exportsNamespace.parent = _global _global.objects.append(_exportsNamespace) _allocateNamingGroupIndex(_exportsNamespace) for symbol in _symbolsToExport { assert(symbol.parent != null) assert(symbol.parent.kind.isObject) var oldParent = symbol.parent.asObjectSymbol symbol.parent = _exportsNamespace if symbol.kind.isObject { oldParent.objects.removeOne(symbol.asObjectSymbol) _exportsNamespace.objects.append(symbol.asObjectSymbol) } else if symbol.kind.isFunction { oldParent.functions.removeOne(symbol.asFunctionSymbol) _exportsNamespace.functions.append(symbol.asFunctionSymbol) } else if symbol.kind.isVariable { oldParent.variables.removeOne(symbol.asVariableSymbol) _exportsNamespace.variables.append(symbol.asVariableSymbol) } else { assert(false) } } } def _createIntBinary(kind NodeKind, left Node, right Node) Node { if kind == .MULTIPLY { return Node.createSymbolCall(_specialVariable(.MULTIPLY)).appendChild(left).appendChild(right) } return _wrapWithIntCast(Node.createBinary(kind, left, right).withType(_cache.intType)) } def _wrapWithNot(node Node) Node { return Node.createUnary(.NOT, node).withType(_cache.boolType).withRange(node.range) } def _wrapWithIntCast(node Node) Node { return Node.createBinary(.BITWISE_OR, node, _cache.createInt(0)).withType(_cache.intType).withRange(node.range) } def _removeIntCast(node Node) { if node.kind == .BITWISE_OR && node.binaryRight.isInt && node.binaryRight.asInt == 0 { node.replaceWith(node.binaryLeft.remove) } } def _patchUnaryArithmetic(node Node) { if node.resolvedType == _cache.intType && !_alwaysConvertsOperandsToInt(node.parent) { var value = node.unaryValue if value.resolvedType == _cache.intType { if value.isInt { value.content = IntContent.new(-value.asInt) node.become(value.remove) } else { node.become(_wrapWithIntCast(node.cloneAndStealChildren)) } } } } def _patchBinaryArithmetic(node Node) { # Make sure arithmetic integer operators don't emit doubles outside the # integer range. Allowing this causes JIT slowdowns due to extra checks # during compilation and potential deoptimizations during execution. # Special-case the integer "%" operator where the right operand may be # "0" since that generates "NaN" which is not representable as an int. if node.resolvedType == _cache.intType && !_alwaysConvertsOperandsToInt(node.parent) && ( node.kind != .REMAINDER && node.kind != .UNSIGNED_SHIFT_RIGHT || !node.binaryRight.isInt || node.binaryRight.asInt == 0) { var left = node.binaryLeft var right = node.binaryRight if left.resolvedType == _cache.intType && right.resolvedType == _cache.intType { node.become(_createIntBinary(node.kind, left.remove, right.remove).withRange(node.range)) } } } def _patchTypeCheck(node Node) { var value = node.typeCheckValue var type = _cache.unwrappedType(node.typeCheckType.resolvedType) if type == _cache.boolType { node.become(Node.createSymbolCall(_specialVariable(.IS_BOOL)).appendChild(value.remove)) } else if _cache.isInteger(type) { node.become(Node.createSymbolCall(_specialVariable(.IS_INT)).appendChild(value.remove)) } else if type == _cache.doubleType { node.become(Node.createSymbolCall(_specialVariable(.IS_DOUBLE)).appendChild(value.remove)) } else if type == _cache.stringType { node.become(Node.createSymbolCall(_specialVariable(.IS_STRING)).appendChild(value.remove)) } else if type.kind == .LAMBDA { node.typeCheckType.replaceWith(Node.createName("Function").withType(.DYNAMIC)) } } # Group each variable inside the function with the function itself so that # they can be renamed together and won't cause any collisions inside the # function def _unionVariableWithFunction(symbol Symbol, function Symbol) { if _mangle && function != null { assert(symbol.id in _namingGroupIndexForSymbol) assert(function.id in _namingGroupIndexForSymbol) _localVariableUnionFind.union( _namingGroupIndexForSymbol[symbol.id], _namingGroupIndexForSymbol[function.id]) } } def _patchNode(node Node) { if node == null { return } var oldEnclosingFunction = _enclosingFunction var oldLoop = _enclosingLoop var symbol = node.symbol var kind = node.kind if _mangle && symbol != null { _allocateNamingGroupIndex(symbol) _symbolCounts[symbol.id] = _symbolCounts.get(symbol.id, 0) + 1 } if kind == .LAMBDA { _enclosingFunction = symbol.asFunctionSymbol } else if kind.isLoop { _enclosingLoop = node } if kind == .CAST { _patchNode(node.castValue) } else { for child = node.firstChild; child != null; child = child.nextSibling { _patchNode(child) } } if kind == .LAMBDA { _enclosingFunction = oldEnclosingFunction } else if kind.isLoop { _enclosingLoop = oldLoop } # Split this into a separate function because this function is hot and V8 doesn't # optimize it otherwise (it's optimized "too many times" whatever that means) _patchNodeHelper(node) } def _patchNodeHelper(node Node) { switch node.kind { case .ADD, .SUBTRACT, .MULTIPLY, .DIVIDE, .REMAINDER, .UNSIGNED_SHIFT_RIGHT { _patchBinaryArithmetic(node) } case .BREAK { _patchBreak(node) } case .CAST { _patchCast(node) } case .FOREACH { _unionVariableWithFunction(node.symbol, _enclosingFunction) } case .LAMBDA { _patchLambda(node) } case .NAME { _patchName(node) } case .NEGATIVE { _patchUnaryArithmetic(node) } case .TRY { _patchTry(node) } case .TYPE_CHECK { _patchTypeCheck(node) } case .VARIABLE { _unionVariableWithFunction(node.symbol, _enclosingFunction) } } if _mangle { switch node.kind { case .ASSIGN_INDEX { _peepholeMangleAssignIndex(node) } case .BLOCK { _peepholeMangleBlock(node) } case .CALL { _peepholeMangleCall(node) } case .CONSTANT { _peepholeMangleConstant(node) } case .FOR { _peepholeMangleFor(node) } case .HOOK { _peepholeMangleHook(node) } case .IF { _peepholeMangleIf(node) } case .INDEX { _peepholeMangleIndex(node) } case .PAIR { _peepholeManglePair(node) } case .WHILE { _peepholeMangleWhile(node) } default { if node.kind.isBinary { _peepholeMangleBinary(node) } } } } } def _peepholeManglePair(node Node) { if _isIdentifierString(node.firstValue) { node.firstValue.kind = .NAME } } def _peepholeMangleConstant(node Node) { switch node.content.kind { case .BOOL { node.become(_wrapWithNot(_cache.createInt(node.asBool ? 0 : 1).withRange(node.range))) } case .INT { var value = node.asInt # "-2147483648" => "1 << 31" if value != 0 { var count = value.toString.count var shift = 0 # Count zero bits while (value & 1) == 0 { value >>>= 1 shift++ } # Do the substitution if it makes sense if shift != 0 && value.toString.count + 2 + shift.toString.count < count { node.become(Node.createBinary(.SHIFT_LEFT, _cache.createInt(value), _cache.createInt(shift)).withType(_cache.intType).withRange(node.range)) } } } case .DOUBLE { var value = node.asDouble var reciprocal = 1 / value # Shorten long reciprocals (don't replace multiplication with division # because that's not numerically identical). These should be constant- # folded by the JIT at compile-time. # # "x * 0.3333333333333333" => "x * (1 / 3)" # for i in 1..10 { if reciprocal * i == ((reciprocal * i) as int) && value.toString.count >= 10 { node.become(Node.createBinary(.DIVIDE, _cache.createDouble(i), _cache.createDouble(reciprocal * i)).withType(_cache.doubleType).withRange(node.range)) break } } } } } def _patchName(node Node) { if node.symbol != null && node.symbol == _currentSelf && _enclosingFunction != null && _enclosingFunction.kind == .FUNCTION_LOCAL { _needsSelf = true } } def _peepholeMangleCall(node Node) { var value = node.callValue var parent = node.parent # "x + y.toString()" => "x + y" where "x" is a string # "x.toString() + ''" => "x + ''" if value.nextSibling == null && value.kind == .DOT && value.asString == "toString" && value.symbol != null && value.symbol.isImportedOrExported && parent.kind == .ADD && ( node == parent.binaryRight && _cache.isEquivalentToString(parent.binaryLeft.resolvedType) || parent.binaryRight.isString) { node.become(value.dotTarget.remove) } } # The "break" statement inside a switch should break out of the enclosing # loop: # # while true { # switch x { # case 0 { # break # } # } # } # # becomes: # # label: while (true) { # switch (x) { # case 0: { # break label; # } # } # } # def _patchBreak(node Node) { var loop = _enclosingLoop for parent = node.parent; parent != loop; parent = parent.parent { if parent.kind == .SWITCH { var label = _loopLabels.get(loop.id, null) if label == null { label = VariableSymbol.new(.VARIABLE_LOCAL, _enclosingFunction.scope.generateName("label")) _allocateNamingGroupIndex(label) _unionVariableWithFunction(label, _enclosingFunction) _loopLabels[loop.id] = label } _loopLabels[node.id] = label break } } } def _patchLambda(node Node) { var function = node.symbol.asFunctionSymbol for argument in function.arguments { _allocateNamingGroupIndex(argument) _unionVariableWithFunction(argument, function) } _unionVariableWithFunction(function, _enclosingFunction) } def _recursiveSubstituteSymbol(node Node, old Symbol, new Symbol) { if node.symbol == old { node.symbol = new } for child = node.firstChild; child != null; child = child.nextSibling { _recursiveSubstituteSymbol(child, old, new) } } def _patchTry(node Node) { if node.hasChildren && !node.hasOneChild { var tryBlock = node.tryBlock var finallyBlock = node.finallyBlock var firstCatch = finallyBlock != null ? finallyBlock.previousSibling : node.lastChild var variable = VariableSymbol.new(.VARIABLE_LOCAL, firstCatch.kind == .CATCH && firstCatch.symbol != null ? firstCatch.symbol.name : _enclosingFunction.scope.generateName("e")) variable.resolvedType = .DYNAMIC var block = Node.createBlock.appendChild(Node.createThrow(Node.createSymbolReference(variable))) # Iterate backwards over the catch blocks for child = firstCatch, previous = child.previousSibling; child != tryBlock; child = previous, previous = child.previousSibling { var catchBlock = child.remove.catchBlock.remove # Substitute the variable into the contents of the block if child.symbol != null { _recursiveSubstituteSymbol(catchBlock, child.symbol, variable) } # Build up the chain of tests in reverse if child.symbol != null && child.symbol.resolvedType != .DYNAMIC { var test = Node.createTypeCheck(Node.createSymbolReference(variable), Node.createType(child.symbol.resolvedType)).withType(_cache.boolType) block = Node.createBlock.appendChild(catchBlock.hasChildren ? Node.createIf(test, catchBlock, block) : Node.createIf(_wrapWithNot(test), block, null)) } else { block = catchBlock } } node.insertChildAfter(tryBlock, Node.createCatch(variable, block)) # Make sure the new variable name is mangled _allocateNamingGroupIndex(variable) _unionVariableWithFunction(variable, _enclosingFunction) } } def _peepholeMangleBinary(node Node) { var kind = node.kind var left = node.binaryLeft var right = node.binaryRight # "(a, b) || c" => "a, b || c" # "(a, b) && c" => "a, b && c" if (kind == .LOGICAL_OR || kind == .LOGICAL_AND) && left.kind == .SEQUENCE { var binary = Node.createBinary(kind, left.lastChild.cloneAndStealChildren, right.remove).withType(.DYNAMIC) _peepholeMangleBinary(binary) left.lastChild.replaceWith(binary) node.become(left.remove) } # "a + (b + c)" => "(a + b) + c" else if kind.isBinaryAssociative && right.kind == kind { while true { node.rotateBinaryRightToLeft node = node.binaryLeft if !node.kind.isBinaryAssociative || node.binaryRight.kind != node.kind { break } } } else if (kind == .GREATER_THAN_OR_EQUAL || kind == .LESS_THAN_OR_EQUAL) && _cache.isEquivalentToInt(left.resolvedType) && _cache.isEquivalentToInt(right.resolvedType) { if left.isInt { var value = left.asInt # "2 >= a" => "3 > a" if node.kind == .GREATER_THAN_OR_EQUAL && _canIncrement(value) { left.content = IntContent.new(value + 1) node.kind = .GREATER_THAN } # "2 <= a" => "1 < a" else if node.kind == .LESS_THAN_OR_EQUAL && _canDecrement(value) { left.content = IntContent.new(value - 1) node.kind = .LESS_THAN } } else if right.isInt { var value = right.asInt # "a >= 2" => "a > 1" if node.kind == .GREATER_THAN_OR_EQUAL && _canDecrement(value) { right.content = IntContent.new(value - 1) node.kind = .GREATER_THAN } # "a <= 2" => "a < 3" else if node.kind == .LESS_THAN_OR_EQUAL && _canIncrement(value) { right.content = IntContent.new(value + 1) node.kind = .LESS_THAN } } } } # Simplifies the node assuming it's used in a boolean context. Note that # this may replace the passed-in node, which will then need to be queried # again if it's needed for further stuff. def _peepholeMangleBoolean(node Node, canSwap BooleanSwap) BooleanSwap { var kind = node.kind if kind == .EQUAL || kind == .NOT_EQUAL { var left = node.binaryLeft var right = node.binaryRight var replacement = _isFalsy(right) ? left : _isFalsy(left) ? right : null # "if (a != 0) b;" => "if (a) b;" if replacement != null { # This minification is not valid for strings and doubles because # they both have multiple falsy values (NaN and 0, null, and "") if left.resolvedType != null && left.resolvedType != .DYNAMIC && !_cache.isEquivalentToDouble(left.resolvedType) && !_cache.isEquivalentToString(left.resolvedType) && right.resolvedType != null && right.resolvedType != .DYNAMIC && !_cache.isEquivalentToDouble(right.resolvedType) && !_cache.isEquivalentToString(right.resolvedType) { replacement.remove node.become(kind == .EQUAL ? _wrapWithNot(replacement) : replacement) } } else if _cache.isInteger(left.resolvedType) && _cache.isInteger(right.resolvedType) && (kind == .NOT_EQUAL || kind == .EQUAL && canSwap == .SWAP) { # "if (a != -1) c;" => "if (~a) c;" # "if (a == -1) c; else d;" => "if (~a) d; else c;" if right.isInt && right.asInt == -1 { node.become(Node.createUnary(.COMPLEMENT, left.remove).withType(_cache.intType)) } # "if (-1 != b) c;" => "if (~b) c;" # "if (-1 == b) c; else d;" => "if (~b) d; else c;" else if left.isInt && left.asInt == -1 { node.become(Node.createUnary(.COMPLEMENT, right.remove).withType(_cache.intType)) } # "if (a != b) c;" => "if (a ^ b) c;" # "if (a == b) c; else d;" => "if (a ^ b) d; else c;" # "if ((a + b | 0) != (c + d | 0)) e;" => "if (a + b ^ c + d) e;" else { node.kind = .BITWISE_XOR _removeIntCast(node.binaryLeft) _removeIntCast(node.binaryRight) } return kind == .EQUAL ? .SWAP : .NO_SWAP } } # "if (a != 0 || b != 0) c;" => "if (a || b) c;" else if kind == .LOGICAL_AND || kind == .LOGICAL_OR { _peepholeMangleBoolean(node.binaryLeft, .NO_SWAP) _peepholeMangleBoolean(node.binaryRight, .NO_SWAP) } # "if (!a) b; else c;" => "if (a) c; else b;" # "a == 0 ? b : c;" => "a ? c : b;" # This is not an "else if" check since EQUAL may be turned into NOT above if node.kind == .NOT && canSwap == .SWAP { node.become(node.unaryValue.remove) return .SWAP } # "if (a, !b) c; else d;" => "if (a, b) d; else c;" if node.kind == .SEQUENCE { return _peepholeMangleBoolean(node.lastChild, canSwap) } return .NO_SWAP } def _peepholeMangleIf(node Node) { var test = node.ifTest var trueBlock = node.ifTrue var falseBlock = node.ifFalse var trueStatement = trueBlock.blockStatement var swapped = _peepholeMangleBoolean(test, falseBlock != null || trueStatement != null && trueStatement.kind == .EXPRESSION ? .SWAP : .NO_SWAP) # "if (a) b; else ;" => "if (a) b;" if falseBlock != null && !falseBlock.hasChildren { falseBlock.remove falseBlock = null } if falseBlock != null { var falseStatement = falseBlock.blockStatement # "if (!a) b; else c;" => "if (a) c; else b;" if swapped == .SWAP { var block = trueBlock trueBlock = falseBlock falseBlock = block var statement = trueStatement trueStatement = falseStatement falseStatement = statement trueBlock.swapWith(falseBlock) } if trueStatement != null && falseStatement != null { # "if (a) b; else c;" => "a ? b : c;" if trueStatement.kind == .EXPRESSION && falseStatement.kind == .EXPRESSION { var hook = Node.createHook(test.remove, trueStatement.expressionValue.remove, falseStatement.expressionValue.remove).withType(.DYNAMIC) _peepholeMangleHook(hook) node.become(Node.createExpression(hook)) } # "if (a) return b; else return c;" => "return a ? b : c;" else if trueStatement.kind == .RETURN && falseStatement.kind == .RETURN { var trueValue = trueStatement.returnValue var falseValue = falseStatement.returnValue if trueValue != null && falseValue != null { var hook = Node.createHook(test.remove, trueValue.remove, falseValue.remove).withType(.DYNAMIC) _peepholeMangleHook(hook) node.become(Node.createReturn(hook)) } } } } # "if (a) b;" => "a && b;" # "if (!a) b;" => "a || b;" else if trueStatement != null && trueStatement.kind == .EXPRESSION { var binary = Node.createBinary(swapped == .SWAP ? .LOGICAL_OR : .LOGICAL_AND, test.remove, trueStatement.expressionValue.remove).withType(.DYNAMIC) _peepholeMangleBinary(binary) node.become(Node.createExpression(binary)) } # "if (a) if (b) c;" => "if (a && b) c;" else { var singleIf = _singleIf(trueBlock) if singleIf != null && singleIf.ifFalse == null { var block = singleIf.ifTrue test.replaceWith(Node.createBinary(.LOGICAL_AND, test.cloneAndStealChildren, singleIf.ifTest.remove).withType(.DYNAMIC)) trueBlock.replaceWith(block.remove) } } } def _peepholeMangleWhile(node Node) { var test = node.whileTest var block = node.whileBlock _peepholeMangleBoolean(test.remove, .NO_SWAP) # "while (a) {}" => "for (; a;) {}" var loop = Node.createFor(Node.createSequence.withType(.DYNAMIC), test, Node.createSequence.withType(.DYNAMIC), block.remove).withRange(node.range) _peepholeMangleFor(loop) node.become(loop) } def _peepholeMangleFor(node Node) { var test = node.forTest _peepholeMangleBoolean(test, .NO_SWAP) # "for (; true;) {}" => "for (;;) {}" if test.kind == .NOT && test.unaryValue.isInt && test.unaryValue.asInt == 0 { var empty = Node.createSequence.withType(.DYNAMIC) test.replaceWith(empty) test = empty } # "for (a;;) if (b) break;" => "for (a; b;) {}" if node.forUpdate.isEmptySequence { var statement = node.forBlock.blockStatement if statement != null && statement.kind == .IF && statement.ifFalse == null { var branch = statement.ifTrue.blockStatement if branch != null && branch.kind == .BREAK { var condition = statement.remove.ifTest.remove condition.invertBooleanCondition(_cache) if test.isEmptySequence { test.replaceWith(condition) } else { condition = Node.createBinary(.LOGICAL_AND, test.cloneAndStealChildren, condition).withType(.DYNAMIC) _peepholeMangleBinary(condition) test.become(condition) } } } } } def _peepholeMangleHook(node Node) { var test = node.hookTest var trueValue = node.hookTrue var falseValue = node.hookFalse var swapped = _peepholeMangleBoolean(test, .SWAP) # "!a ? b : c;" => "a ? c : b;" if swapped == .SWAP { var temp = trueValue trueValue = falseValue falseValue = temp trueValue.swapWith(falseValue) } # "a.b ? c : null" => "a.b && c" if falseValue.kind == .CAST && falseValue.castValue.kind == .NULL && test.resolvedType != null && test.resolvedType != .DYNAMIC && test.resolvedType.isReference { node.become(Node.createBinary(.LOGICAL_AND, test.remove, trueValue.remove).withType(node.resolvedType)) return } # "a ? a : b" => "a || b" # "a = b ? a : c" => "(a = b) || c" if test.looksTheSameAs(trueValue) && test.hasNoSideEffects || test.kind.isBinaryAssign && test.binaryLeft.looksTheSameAs(trueValue) && test.binaryLeft.hasNoSideEffects { node.become(Node.createBinary(.LOGICAL_OR, test.remove, falseValue.remove).withType(node.resolvedType)) return } # "a ? b : a" => "a && b" if test.looksTheSameAs(falseValue) && test.hasNoSideEffects { node.become(Node.createBinary(.LOGICAL_AND, test.remove, trueValue.remove).withType(node.resolvedType)) return } # "a ? b : b" => "a, b" if trueValue.looksTheSameAs(falseValue) { node.become(test.hasNoSideEffects ? trueValue.remove : Node.createSequence(test.remove, trueValue.remove)) return } # Collapse partially-identical hook expressions if falseValue.kind == .HOOK { var falseTest = falseValue.hookTest var falseTrueValue = falseValue.hookTrue var falseFalseValue = falseValue.hookFalse # "a ? b : c ? b : d" => "a || c ? b : d" # "a ? b : c || d ? b : e" => "a || c || d ? b : e" if trueValue.looksTheSameAs(falseTrueValue) { var both = Node.createBinary(.LOGICAL_OR, test.cloneAndStealChildren, falseTest.remove).withType(.DYNAMIC) _peepholeMangleBinary(both) test.replaceWith(both) falseValue.replaceWith(falseFalseValue.remove) _peepholeMangleHook(node) return } } # Collapse partially-identical binary expressions if trueValue.kind == falseValue.kind && trueValue.kind.isBinary { var trueLeft = trueValue.binaryLeft var trueRight = trueValue.binaryRight var falseLeft = falseValue.binaryLeft var falseRight = falseValue.binaryRight # "a ? b = c : b = d;" => "b = a ? c : d;" if trueLeft.looksTheSameAs(falseLeft) { var hook = Node.createHook(test.remove, trueRight.remove, falseRight.remove).withType(.DYNAMIC) _peepholeMangleHook(hook) node.become(Node.createBinary(trueValue.kind, trueLeft.remove, hook).withType(node.resolvedType)) } # "a ? b + 100 : c + 100;" => "(a ? b + c) + 100;" else if trueRight.looksTheSameAs(falseRight) && !trueValue.kind.isBinaryAssign { var hook = Node.createHook(test.remove, trueLeft.remove, falseLeft.remove).withType(.DYNAMIC) _peepholeMangleHook(hook) node.become(Node.createBinary(trueValue.kind, hook, trueRight.remove).withType(node.resolvedType)) } } # "(a, b) ? c : d" => "a, b ? c : d" if test.kind == .SEQUENCE { node.prependChild(test.remove.lastChild.remove) test.appendChild(node.cloneAndStealChildren) node.become(test) } } def _peepholeMangleAssignIndex(node Node) { var left = node.assignIndexLeft var center = node.assignIndexCenter var right = node.assignIndexRight if _isIdentifierString(center) { node.become(Node.createBinary(.ASSIGN, Node.createDot(left.remove, center.asString) .withRange(Range.span(left.range, center.range)).withType(.DYNAMIC), right.remove) .withRange(node.range).withType(node.resolvedType)) } } def _peepholeMangleIndex(node Node) { var left = node.indexLeft var right = node.indexRight if _isIdentifierString(right) { node.become(Node.createDot(left.remove, right.asString).withRange(node.range).withType(node.resolvedType)) } } def _peepholeMangleBlock(node Node) { for child = node.firstChild, next Node = null; child != null; child = next { var previous = child.previousSibling next = child.nextSibling switch child.kind { # Make sure we entirely remove blocks only containing comment blocks case .COMMENT_BLOCK { child.remove } # "var a; var b;" => "var a, b;" case .VARIABLES { if previous != null && previous.kind == .VARIABLES { child.replaceWith(previous.remove.appendChildrenFrom(child)) } } # "a; b; c;" => "a, b, c;" case .EXPRESSION { if child.expressionValue.hasNoSideEffects { child.remove } else if previous != null && previous.kind == .EXPRESSION { var sequence = Node.createSequence(previous.remove.expressionValue.remove, child.expressionValue.remove) child.become(Node.createExpression(sequence)) } } case .RETURN { while previous != null { # "if (a) return b; return c;" => "return a ? b : c;" if child.returnValue != null && previous.kind == .IF && previous.ifFalse == null { var statement = previous.ifTrue.blockStatement if statement != null && statement.kind == .RETURN && statement.returnValue != null { var hook = Node.createHook(previous.remove.ifTest.remove, statement.returnValue.remove, child.returnValue.remove).withType(.DYNAMIC) _peepholeMangleHook(hook) child.become(Node.createReturn(hook)) } else { break } } # "a; return b;" => "return a, b;" else if child.returnValue != null && previous.kind == .EXPRESSION { var sequence = Node.createSequence(previous.remove.expressionValue.remove, child.returnValue.remove) child.become(Node.createReturn(sequence)) } else { break } previous = child.previousSibling } } case .IF { while previous != null { # "if (a) b; if (c) b;" => "if (a || c) b;" if child.ifFalse == null && previous.kind == .IF && previous.ifFalse == null && previous.ifTrue.looksTheSameAs(child.ifTrue) { child.ifTest.replaceWith(Node.createBinary(.LOGICAL_OR, previous.remove.ifTest.remove, child.ifTest.cloneAndStealChildren).withType(.DYNAMIC)) } # "a; if (b) c;" => "if (a, b) c;" else if previous.kind == .EXPRESSION { var sequence = Node.createSequence(previous.remove.expressionValue.remove, child.ifTest.cloneAndStealChildren) child.ifTest.replaceWith(sequence) } else { break } previous = child.previousSibling } # "void foo() { if (a) return; b(); c() }" => "void foo() { if (!a) { b(); c() } }" # "while (a) { if (b) continue; c(); d() }" => "while (a) { if (!b) { c(); d() } }" if child.ifFalse == null { var trueBlock = child.ifTrue if trueBlock.hasChildren { var statement = trueBlock.lastChild if (statement.kind == .RETURN && statement.returnValue == null || statement.kind == .CONTINUE) && _isJumpImplied(node, statement.kind) { var block Node # If the if statement block without the jump is empty, then flip # the condition of the if statement and reuse the block. Otherwise, # create an else branch for the if statement and use that block. statement.remove if !trueBlock.hasChildren { child.ifTest.invertBooleanCondition(_cache) block = trueBlock } else if next != null { block = Node.createBlock child.appendChild(block) assert(block == child.ifFalse) } else { return # Returning here is fine because this is the last child } # Move the rest of this block into the block for the if statement while child.nextSibling != null { block.appendChild(child.nextSibling.remove) } _peepholeMangleBlock(block) _peepholeMangleIf(child) # "a(); if (b) return; c();" => "a(); if (!b) c();" => "a(); !b && c();" => "a(), !b && c();" if child.kind == .EXPRESSION && previous != null && previous.kind == .EXPRESSION { var sequence = Node.createSequence(previous.remove.expressionValue.remove, child.expressionValue.remove) child.become(Node.createExpression(sequence)) } return } } } } case .FOR { var setup = child.forSetup # "var a; for (;;) {}" => "for (var a;;) {}" if previous != null && setup.isEmptySequence && previous.kind == .VARIABLES { setup.replaceWith(previous.remove.appendChildrenFrom(setup)) } # "var a; for (var b;;) {}" => "for (var a, b;;) {}" else if previous != null && setup.kind == .VARIABLES && previous.kind == .VARIABLES { setup.replaceWith(previous.remove.appendChildrenFrom(setup)) } # "a; for (b;;) {}" => "for (a, b;;) {}" else if previous != null && setup.kind.isExpression && previous.kind == .EXPRESSION { setup.replaceWith(Node.createSequence(previous.remove.expressionValue.remove, setup.cloneAndStealChildren)) } } case .SWITCH { var switchValue = child.switchValue var defaultCase = child.defaultCase if defaultCase != null { var hasFlowAtEnd = false # See if any non-default case will flow past the end of the switch block for caseChild = switchValue.nextSibling; caseChild != defaultCase; caseChild = caseChild.nextSibling { if caseChild.caseBlock.hasControlFlowAtEnd { hasFlowAtEnd = true } } # "switch (a) { case b: return; default: c; break; }" => "switch (a) { case b: return; } c;" if !hasFlowAtEnd { node.insertChildrenAfterFrom(defaultCase.caseBlock, child) next = child.nextSibling defaultCase.remove defaultCase = null } } # "switch (a) {}" => "a;" if child.hasOneChild { next = Node.createExpression(switchValue.remove) child.replaceWith(next) continue } # "switch (a) { case b: c; break; }" => "if (a == b) c;" else if child.hasTwoChildren { var singleCase = child.lastChild if singleCase.hasTwoChildren { var value = singleCase.firstChild next = Node.createIf(Node.createBinary(.EQUAL, switchValue.remove, value.remove).withType(_cache.boolType), singleCase.caseBlock.remove, null) _peepholeMangleIf(next) child.replaceWith(next) continue } } # "switch (a) { case b: c; break; default: d; break; }" => "if (a == b) c; else d;" else if child.hasThreeChildren { var firstCase = switchValue.nextSibling var secondCase = child.lastChild if firstCase.hasTwoChildren && secondCase.hasOneChild { var value = firstCase.firstChild next = Node.createIf(Node.createBinary(.EQUAL, switchValue.remove, value.remove).withType(_cache.boolType), firstCase.caseBlock.remove, secondCase.caseBlock.remove) _peepholeMangleIf(next) child.replaceWith(next) continue } } # Optimize specific patterns of switch statements if switchValue.kind == .NAME && defaultCase == null { _peepholeMangleSwitchCases(child) } } } } } # "switch (a) { case 0: return 0; case 1: return 1; case 2: return 2; }" => "if (a >= 0 && a <= 2) return a" # "switch (a) { case 0: return 1; case 1: return 2; case 2: return 3; }" => "if (a >= 0 && a <= 2) return a + 1" def _peepholeMangleSwitchCases(node Node) { var switchValue = node.switchValue var firstCase = switchValue.nextSibling if !_cache.isEquivalentToInt(switchValue.resolvedType) { return } var sharedDelta = 0 var count = 0 var min = 0 var max = 0 for child = firstCase; child != null; child = child.nextSibling { var singleStatement = child.caseBlock.blockStatement if !child.hasTwoChildren || singleStatement == null || singleStatement.kind != .RETURN { return } var caseValue = child.firstChild var returnValue = singleStatement.returnValue if !caseValue.isInt || returnValue == null || !returnValue.isInt { return } var caseInt = caseValue.asInt var returnInt = returnValue.asInt var delta = returnInt - caseInt if count == 0 { sharedDelta = delta min = caseInt max = caseInt } else if delta != sharedDelta { return } else { min = Math.min(min, caseInt) max = Math.max(max, caseInt) } count++ } # Make sure the pattern is matched if count == 0 { return } var block = Node.createBlock.appendChild(Node.createReturn( sharedDelta > 0 ? _createIntBinary(.ADD, switchValue.remove, _cache.createInt(sharedDelta)) : sharedDelta < 0 ? _createIntBinary(.SUBTRACT, switchValue.remove, _cache.createInt(-sharedDelta)) : switchValue.remove)) # Replace the large "switch" statement with a smaller "if" statement if the entire range is covered if max - min == count - 1 { var lower = Node.createBinary(.GREATER_THAN_OR_EQUAL, switchValue.clone, _cache.createInt(min)).withType(_cache.boolType) var upper = Node.createBinary(.LESS_THAN_OR_EQUAL, switchValue.clone, _cache.createInt(max)).withType(_cache.boolType) # Convert ">=" and "<=" to ">" and "<" where possible _peepholeMangleBinary(lower) _peepholeMangleBinary(upper) node.replaceWith(Node.createIf(Node.createBinary(.LOGICAL_AND, lower, upper).withType(_cache.boolType), block, null)) } # Just combine everything into one case else { var combined = Node.createCase for child = firstCase; child != null; child = child.nextSibling { combined.appendChild(child.firstChild.remove) } node.replaceWith(Node.createSwitch(switchValue.clone).appendChild(combined.appendChild(block))) } } def _patchCast(node Node) { var value = node.castValue var type = node.resolvedType var valueType = value.resolvedType # Wrapping should be transparent in the emitted code if type.isWrapped || valueType.isWrapped { return } # Cast to bool if type == _cache.boolType { if valueType != _cache.boolType { node.become(_wrapWithNot(_wrapWithNot(value.remove))) } } # Cast to int else if _cache.isInteger(type) { if !_cache.isInteger(valueType) && !_alwaysConvertsOperandsToInt(node.parent) { node.become(_wrapWithIntCast(value.remove)) } else if value.isInt { node.become(value.remove.withType(node.resolvedType)) } } # Cast to double else if type == _cache.doubleType { if !_cache.isNumeric(valueType) { node.become(Node.createUnary(.POSITIVE, value.remove).withRange(node.range).withType(_cache.doubleType)) } } # Cast to string else if type == _cache.stringType { if valueType != _cache.stringType && valueType != .NULL { node.become(Node.createSymbolCall(_specialVariable(.AS_STRING)).appendChild(value.remove)) } } } def _specialVariable(name SpecialVariable) VariableSymbol { assert(name in _specialVariables) var variable = _specialVariables[name] _isSpecialVariableNeeded[variable.id] = 0 return variable } } namespace JavaScriptEmitter { def _isReferenceTo(node Node, symbol Symbol) bool { if node.kind == .CAST { node = node.castValue } return node.kind == .NAME && node.symbol == symbol } def _isJumpImplied(node Node, kind NodeKind) bool { assert(node.kind == .BLOCK) assert(kind == .RETURN || kind == .CONTINUE) var parent = node.parent if kind == .RETURN && (parent == null || parent.kind == .LAMBDA) || kind == .CONTINUE && parent != null && parent.kind.isLoop { return true } if parent != null && parent.kind == .IF && parent.nextSibling == null { return _isJumpImplied(parent.parent, kind) } return false } def _canIncrement(value int) bool { return value < 0x7FFFFFFF } def _canDecrement(value int) bool { return value >= -0x7FFFFFFF } def _isIdentifierString(node Node) bool { if node.isString { var value = node.asString for i in 0..value.count { var c = value[i] if (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && c != '_' && c != '$' && (i == 0 || c < '0' || c > '9') { return false } } return value != "" && !(value in _isKeyword) } return false } def _singleIf(block Node) Node { if block == null { return null } var statement = block.blockStatement if statement != null && statement.kind == .IF { return statement } return null } const _first = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$" const _rest = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$0123456789" def _numberToName(number int) string { var name = _first.get(number % _first.count) number = number / _first.count while number > 0 { number-- name += _rest.get(number % _rest.count) number = number / _rest.count } return name } def _isCompactNodeKind(kind NodeKind) bool { return kind == .EXPRESSION || kind == .VARIABLES || kind.isJump } def _isFalsy(node Node) bool { switch node.kind { case .NULL { return true } case .CAST { return _isFalsy(node.castValue) } case .CONSTANT { var content = node.content switch content.kind { case .INT { return content.asInt == 0 } case .DOUBLE { return content.asDouble == 0 || content.asDouble.isNaN } case .STRING { return content.asString == "" } } } } return false } def _fullName(symbol Symbol) string { var parent = symbol.parent if parent != null && parent.kind != .OBJECT_GLOBAL { var enclosingName = _fullName(parent) if symbol.isPrimaryConstructor || symbol.isImported && symbol.kind == .FUNCTION_CONSTRUCTOR { return enclosingName } assert(symbol.kind != .OVERLOADED_INSTANCE) if symbol.kind == .FUNCTION_INSTANCE { enclosingName += ".prototype" } return enclosingName + "." + _mangleName(symbol) } return _mangleName(symbol) } def _shouldRenameSymbol(symbol Symbol) bool { # Don't rename annotations since "@rename" is used for renaming and is identified by name return !symbol.isImportedOrExported && !symbol.isRenamed && !symbol.isPrimaryConstructor && symbol.kind != .FUNCTION_ANNOTATION && symbol.kind != .OBJECT_GLOBAL && symbol.kind != .FUNCTION_LOCAL } def _mangleName(symbol Symbol) string { symbol = symbol.forwarded if symbol.isPrimaryConstructor { symbol = symbol.parent } if !symbol.isImportedOrExported && (symbol.name in _isKeyword || symbol.parent != null && symbol.parent.kind == .OBJECT_CLASS && !symbol.kind.isOnInstances && symbol.name in _isFunctionProperty) { return "$" + symbol.name } return symbol.name } def _computeNamespacePrefix(symbol ObjectSymbol) string { assert(symbol.kind.isObject) return symbol.kind == .OBJECT_GLOBAL ? "" : _computeNamespacePrefix(symbol.parent.asObjectSymbol) + _mangleName(symbol) + "." } def _alwaysConvertsOperandsToInt(node Node) bool { if node != null { switch node.kind { case .ASSIGN_BITWISE_AND, .ASSIGN_BITWISE_OR, .ASSIGN_BITWISE_XOR, .ASSIGN_SHIFT_LEFT, .ASSIGN_SHIFT_RIGHT, .BITWISE_AND, .BITWISE_OR, .BITWISE_XOR, .COMPLEMENT, .SHIFT_LEFT, .SHIFT_RIGHT { return true } } } return false } const _isFunctionProperty = { "apply": 0, "call": 0, "length": 0, "name": 0, } const _isKeyword = { "arguments": 0, "await": 0, "Boolean": 0, "break": 0, "case": 0, "catch": 0, "class": 0, "const": 0, "constructor": 0, "continue": 0, "Date": 0, "debugger": 0, "default": 0, "delete": 0, "do": 0, "double": 0, "else": 0, "enum": 0, "eval": 0, "export": 0, "extends": 0, "false": 0, "finally": 0, "float": 0, "for": 0, "function": 0, "Function": 0, "if": 0, "implements": 0, "import": 0, "in": 0, "instanceof": 0, "int": 0, "interface": 0, "let": 0, "new": 0, "null": 0, "Number": 0, "Object": 0, "package": 0, "private": 0, "protected": 0, "public": 0, "return": 0, "static": 0, "String": 0, "super": 0, "switch": 0, "this": 0, "throw": 0, "true": 0, "try": 0, "typeof": 0, "var": 0, "void": 0, "while": 0, "with": 0, "yield": 0, } const _keywordCallMap = { "delete": 0, "typeof": 0, "void": 0, } const _specialVariableMap = { "__asString": SpecialVariable.AS_STRING, "__create": SpecialVariable.CREATE, "__extends": SpecialVariable.EXTENDS, "__imul": SpecialVariable.MULTIPLY, "__isBool": SpecialVariable.IS_BOOL, "__isDouble": SpecialVariable.IS_DOUBLE, "__isInt": SpecialVariable.IS_INT, "__isString": SpecialVariable.IS_STRING, "__prototype": SpecialVariable.PROTOTYPE, } } } ================================================ FILE: src/backend/lisptree.sk ================================================ namespace Skew { class LispTreeTarget : CompilerTarget { over name string { return "S-expression" } over extension string { return "lisp" } over createEmitter(context PassContext) Emitter { return LispTreeEmitter.new(context.options) } } class LispTreeEmitter : Emitter { const _options CompilerOptions over visit(global ObjectSymbol) { _visitObject(global) _emit("\n") _createSource(_options.outputDirectory != null ? _options.outputDirectory + "/compiled.lisp" : _options.outputFile, .ALWAYS_EMIT) } def _visitObject(symbol ObjectSymbol) { _emit("(" + _mangleKind(symbol.kind.toString) + " " + quoteString(symbol.name, .DOUBLE, .OCTAL_WORKAROUND)) _increaseIndent for object in symbol.objects { _emit("\n" + _indent) _visitObject(object) } for function in symbol.functions { _emit("\n" + _indent) _visitFunction(function) } for variable in symbol.variables { _emit("\n" + _indent) _visitVariable(variable) } _decreaseIndent _emit(")") } def _visitFunction(symbol FunctionSymbol) { _emit("(" + _mangleKind(symbol.kind.toString) + " " + quoteString(symbol.name, .DOUBLE, .OCTAL_WORKAROUND)) _increaseIndent for argument in symbol.arguments { _emit("\n" + _indent) _visitVariable(argument) } _emit("\n" + _indent) _visitNode(symbol.returnType) _emit("\n" + _indent) _visitNode(symbol.block) _decreaseIndent _emit(")") } def _visitVariable(symbol VariableSymbol) { _emit("(" + _mangleKind(symbol.kind.toString) + " " + quoteString(symbol.name, .DOUBLE, .OCTAL_WORKAROUND) + " ") _visitNode(symbol.type) _emit(" ") _visitNode(symbol.value) _emit(")") } def _visitNode(node Node) { if node == null { _emit("nil") return } _emit("(" + _mangleKind(node.kind.toString)) var content = node.content if content != null { switch content.kind { case .INT { _emit(" " + content.asInt.toString) } case .BOOL { _emit(" " + content.asBool.toString) } case .DOUBLE { _emit(" " + content.asDouble.toString) } case .STRING { _emit(" " + quoteString(content.asString, .DOUBLE, .OCTAL_WORKAROUND)) } } } if node.kind == .VARIABLE { _emit(" ") _visitVariable(node.symbol.asVariableSymbol) } else if node.kind == .LAMBDA { _emit(" ") _visitFunction(node.symbol.asFunctionSymbol) } else if node.hasChildren { _increaseIndent for child = node.firstChild; child != null; child = child.nextSibling { _emit("\n" + _indent) _visitNode(child) } _decreaseIndent } _emit(")") } def _mangleKind(kind string) string { return kind.toLowerCase.replaceAll("_", "-") } } # These dump() functions are helpful for debugging syntax trees namespace LispTreeEmitter { def dump(global ObjectSymbol) string { var emitter = LispTreeEmitter.new(CompilerOptions.new) emitter.visit(global) emitter._createSource(null, .ALWAYS_EMIT) return emitter.sources.first.contents } def dump(node Node) string { var emitter = LispTreeEmitter.new(CompilerOptions.new) emitter._visitNode(node) emitter._createSource(null, .ALWAYS_EMIT) return emitter.sources.first.contents } } } ================================================ FILE: src/backend/sourcemap.sk ================================================ namespace Skew { class SourceMapping { const sourceIndex int const originalLine int # 0-based const originalColumn int # 0-based const generatedLine int # 0-based const generatedColumn int # 0-based } # Based on https://github.com/mozilla/source-map class SourceMapGenerator { var _mappings List = [] var _sources List = [] def addMapping(source Source, originalLine int, originalColumn int, generatedLine int, generatedColumn int) { var sourceIndex = _sources.indexOf(source) if sourceIndex == -1 { sourceIndex = _sources.count _sources.append(source) } _mappings.append(SourceMapping.new(sourceIndex, originalLine, originalColumn, generatedLine, generatedColumn)) } def toString string { var sourceNames List = [] var sourceContents List = [] for source in _sources { sourceNames.append(quoteString(source.name, .DOUBLE, .OCTAL_WORKAROUND)) sourceContents.append(quoteString(source.contents, .DOUBLE, .OCTAL_WORKAROUND)) } var builder = StringBuilder.new builder.append("{\"version\":3,\"sources\":[") builder.append(",".join(sourceNames)) builder.append("],\"sourcesContent\":[") builder.append(",".join(sourceContents)) builder.append("],\"names\":[],\"mappings\":\"") # Sort the mappings in increasing order by generated location _mappings.sort((a, b) => { var delta = a.generatedLine <=> b.generatedLine return delta != 0 ? delta : a.generatedColumn <=> b.generatedColumn }) var previousGeneratedColumn = 0 var previousGeneratedLine = 0 var previousOriginalColumn = 0 var previousOriginalLine = 0 var previousSourceIndex = 0 # Generate the base64 VLQ encoded mappings for mapping in _mappings { var generatedLine = mapping.generatedLine # Insert ',' for the same line and ';' for a line if previousGeneratedLine == generatedLine { if previousGeneratedColumn == mapping.generatedColumn && (previousGeneratedLine != 0 || previousGeneratedColumn != 0) { continue } builder.append(",") } else { previousGeneratedColumn = 0 while previousGeneratedLine < generatedLine { builder.append(";") previousGeneratedLine++ } } # Record the generated column (the line is recorded using ';' above) builder.append(encodeVLQ(mapping.generatedColumn - previousGeneratedColumn)) previousGeneratedColumn = mapping.generatedColumn # Record the generated source builder.append(encodeVLQ(mapping.sourceIndex - previousSourceIndex)) previousSourceIndex = mapping.sourceIndex # Record the original line builder.append(encodeVLQ(mapping.originalLine - previousOriginalLine)) previousOriginalLine = mapping.originalLine # Record the original column builder.append(encodeVLQ(mapping.originalColumn - previousOriginalColumn)) previousOriginalColumn = mapping.originalColumn } builder.append("\"}\n") return builder.toString } } # A single base 64 digit can contain 6 bits of data. For the base 64 variable # length quantities we use in the source map spec, the first bit is the sign, # the next four bits are the actual value, and the 6th bit is the continuation # bit. The continuation bit tells us whether there are more digits in this # value following this digit. # # Continuation # | Sign # | | # V V # 101011 # def encodeVLQ(value int) string { var vlq = value < 0 ? -value << 1 | 1 : value << 1 var encoded = "" while true { var digit = vlq & 31 vlq >>= 5 # If there are still more digits in this value, we must make sure the # continuation bit is marked if vlq != 0 { digit |= 32 } encoded += BASE64.get(digit) if vlq == 0 { break } } return encoded } const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" } ================================================ FILE: src/backend/typescript.sk ================================================ namespace Skew { class TypeScriptTarget : CompilerTarget { over name string { return "TypeScript" } over extension string { return "ts" } over stopAfterResolve bool { return false } over requiresIntegerSwitchStatements bool { return true } over supportsListForeach bool { return true } over supportsNestedTypes bool { return true } over removeSingletonInterfaces bool { return true } over stringEncoding Unicode.Encoding { return .UTF16 } over editOptions(options CompilerOptions) { options.define("TARGET", "JAVASCRIPT") } over includeSources(sources List) { sources.prepend(Source.new("", NATIVE_LIBRARY_JS)) } over createEmitter(context PassContext) Emitter { return TypeScriptEmitter.new(context.log, context.options, context.cache) } } class TypeScriptEmitter : Emitter { enum SpecialVariable { NONE AS_STRING IS_INT } class MultipleCtors { const ctors List const canUseArgumentCount bool } const _log Log const _options CompilerOptions const _cache TypeCache const _specialVariables IntMap = {} const _ctors IntMap = {} const _enclosingNamespaces List = [] var _emittedComments List = [] var _previousNode Node = null var _previousSymbol Symbol = null var _symbolsCheckedForImport IntMap = {} var _importedFiles StringMap> = {} var _loopLabels IntMap = {} var _enclosingFunction FunctionSymbol = null var _expectedNextEnumValue = 0 var _currentFile = "" over visit(global ObjectSymbol) { _indentAmount = " " # Generate the entry point const entryPoint = _cache.entryPointSymbol if entryPoint != null { entryPoint.name = "main" } # Load special-cased variables for variable in global.variables { var special = _specialVariableMap.get(variable.name, .NONE) if special != .NONE { _specialVariables[special] = variable variable.flags |= .IS_EXPORTED } } assert(SpecialVariable.AS_STRING in _specialVariables) assert(SpecialVariable.IS_INT in _specialVariables) # Avoid emitting unnecessary stuff shakingPass(global, entryPoint, .USE_TYPES) _markVirtualFunctions(global) const emitIndividualFiles = _options.outputDirectory != null const symbolsByFile = StringMap>.new # Bucket things by the source file they came from const collisions = StringMap>.new const add = (s Symbol) => { var name = "" if s.range != null { name = s.range.source.name } if !(name in symbolsByFile) { symbolsByFile[name] = [] } symbolsByFile[name].append(s) # Track collisions if !s.isImported { var list = collisions.get(s.name, []) list.append(s) collisions[s.name] = list } } # There can only be one constructor in TypeScript var fixAllMultipleCtors fn(ObjectSymbol) fixAllMultipleCtors = (p ObjectSymbol) => { for s in p.objects { fixAllMultipleCtors(s) if s.kind == .OBJECT_CLASS && !s.isImported { var ctors = s.functions.filter(f => f.kind == .FUNCTION_CONSTRUCTOR) if ctors.count > 1 { s.functions = s.functions.filter(f => f.kind != .FUNCTION_CONSTRUCTOR) const canUseArgumentCount = ctors.all(c1 => ctors.filter(c2 => c1.arguments.count == c2.arguments.count).count == 1) _ctors[s.id] = MultipleCtors.new(ctors, canUseArgumentCount) } } } } fixAllMultipleCtors(global) var addAll fn(ObjectSymbol) addAll = (p ObjectSymbol) => { # If this namespace has comments, move its comments to # the first child of this namespace in the same file if p.comments != null { var all List = [] for s in p.variables { all.append(s) } for s in p.functions { all.append(s) } for s in p.objects { all.append(s) } # Iterate over the comments in reverse because we are prefixing for i in 0..p.comments.count { var c = p.comments[p.comments.count - i - 1] var best Symbol = null for s in all { if s.range.source == c.range.source { if best == null || best.range.start > s.range.start { best = s } } } if best != null { best.comments = Comment.concat([c], best.comments) } else if _options.warnAboutIgnoredComments { _log.syntaxWarningIgnoredCommentInEmitter(c.range) } } } for s in p.variables { add(s) } for s in p.functions { add(s) } for s in p.objects { if _shouldFlattenNamespace(s) { addAll(s) } else { add(s) } } } addAll(global) # Rename all collisions collisions.each((name, list) => { if list.count > 1 { for s in list { var i = 1 while true { var rename = "\(name)\(i)" if !(rename in collisions) { collisions[rename] = [] s.name = rename break } i++ } } } }) # Emit each global object into a separate file symbolsByFile.each((file, symbols) => { _currentFile = file for s in symbols { if s.kind.isObject { _emitObject(s as ObjectSymbol) } } for s in symbols { if s.kind.isFunction { _emitFunction(s as FunctionSymbol) } } for s in symbols { if s.kind.isVariable { _emitVariable(s as VariableSymbol) } } # Emit each object into its own file if requested if emitIndividualFiles { _finalizeEmittedFile _createSource(_options.outputDirectory + "/" + _tsFileName(file), .SKIP_IF_EMPTY) } }) # Emit a single file if requested if !emitIndividualFiles { _finalizeEmittedFile _createSource(_options.outputFile, .ALWAYS_EMIT) } } def _specialVariable(name SpecialVariable) VariableSymbol { assert(name in _specialVariables) var variable = _specialVariables[name] _handleSymbol(variable) return variable } def _replaceReturnsWithVariable(node Node, variable VariableSymbol) { if node.kind == .RETURN { node.become(Node.createReturn(Node.createSymbolReference(variable))) } for child = node.firstChild; child != null; child = child.nextSibling { _replaceReturnsWithVariable(child, variable) } } def _tsFileName(skewFile string) string { skewFile = skewFile.replaceAll("<", "") skewFile = skewFile.replaceAll(">", "") if skewFile.endsWith(".sk") { skewFile = skewFile.slice(0, skewFile.count - ".sk".count) } return skewFile + ".ts" } def _relativeImport(file string) string { var currentParts = _currentFile.split("/") var fileParts = file.split("/") currentParts.removeLast while !currentParts.isEmpty && !fileParts.isEmpty && currentParts.first == fileParts.first { currentParts.removeFirst fileParts.removeFirst } if currentParts.isEmpty { fileParts.prepend(".") } else { for _ in currentParts { fileParts.prepend("..") } } return "/".join(fileParts) } def _finalizeEmittedFile { var importedFiles = _importedFiles.keys if !importedFiles.isEmpty { importedFiles.sort(SORT_STRINGS) # Sort so the order is deterministic for file in importedFiles { var importedNames = _importedFiles[file].keys importedNames.sort(SORT_STRINGS) # Sort so the order is deterministic var where = quoteString(_relativeImport(file), .DOUBLE, .NORMAL) _emitPrefix("import { \(", ".join(importedNames)) } from \(where);\n") } _emitPrefix("\n") } _previousSymbol = null _symbolsCheckedForImport = {} _importedFiles = {} } def _handleSymbol(symbol Symbol) { if !symbol.kind.isLocal && !(symbol.id in _symbolsCheckedForImport) { _symbolsCheckedForImport[symbol.id] = 0 var parent = symbol.parent if parent != null && ( symbol.kind == .VARIABLE_ENUM_OR_FLAGS || parent.kind == .OBJECT_WRAPPED || (parent.kind == .OBJECT_NAMESPACE && !_shouldFlattenNamespace(parent)) || (symbol.kind.isObject && parent.kind == .OBJECT_CLASS) || (symbol.kind == .FUNCTION_GLOBAL && parent.kind == .OBJECT_CLASS) || (symbol.kind == .VARIABLE_GLOBAL && parent.kind == .OBJECT_CLASS)) { _handleSymbol(parent) } else if !symbol.isImported && symbol.range != null && ( symbol.kind == .OBJECT_CLASS || symbol.kind == .OBJECT_ENUM || symbol.kind == .OBJECT_FLAGS || symbol.kind == .OBJECT_WRAPPED || symbol.kind == .OBJECT_INTERFACE || symbol.kind == .OBJECT_NAMESPACE || (symbol.kind == .FUNCTION_GLOBAL && parent.kind != .OBJECT_CLASS) || (symbol.kind == .VARIABLE_GLOBAL && parent.kind != .OBJECT_CLASS)) { var file = symbol.range.source.name if _currentFile != file { file = _tsFileName(file) file = file.slice(0, file.count - ".ts".count) if !(file in _importedFiles) { _importedFiles[file] = {} } _importedFiles[file][_mangleName(symbol)] = 0 } } } } def _emitNewlineBeforeSymbol(symbol Symbol) { if _previousSymbol != null && ( symbol.comments != null || (!_previousSymbol.kind.isVariable || !symbol.kind.isVariable) && (!_previousSymbol.kind.isFunction || _previousSymbol.asFunctionSymbol.block != null || !symbol.kind.isFunction || symbol.asFunctionSymbol.block != null)) { _emit("\n") } _previousSymbol = null } def _emitNewlineAfterSymbol(symbol Symbol) { _previousSymbol = symbol } def _emitNewlineBeforeStatement(node Node) { if _previousNode != null && (node.comments != null || !_isCompactNodeKind(_previousNode.kind) || !_isCompactNodeKind(node.kind)) { _emit("\n") } _previousNode = null } def _emitNewlineAfterStatement(node Node) { _previousNode = node } def _emitComments(comments List) { if comments != null { for comment in comments { for line in comment.lines { _emit(_indent + "//" + line + "\n") } if comment.hasGapBelow { _emit("\n") } if _options.warnAboutIgnoredComments { _emittedComments.append(comment) } } } } def _emitTrailingComment(comment Comment) { if comment != null { assert(comment.lines.count == 1) _emit(" //" + comment.lines.first) if _options.warnAboutIgnoredComments { _emittedComments.append(comment) } } } def _emitObject(symbol ObjectSymbol) { _handleSymbol(symbol) if symbol.isImported || symbol.kind == .OBJECT_GLOBAL { return } _emitNewlineBeforeSymbol(symbol) _emitComments(symbol.comments) _emit(_indent) _emit("export ") if symbol.isAbstract { _emit("abstract ") } switch symbol.kind { case .OBJECT_CLASS { _emit("class ") } case .OBJECT_ENUM, .OBJECT_FLAGS { var toString = symbol.members.get("toString", null) if toString != null && toString.kind.isFunction && .IS_INLINING_FORCED in toString.flags && .IS_AUTOMATICALLY_GENERATED in toString.flags && (toString as FunctionSymbol).inlinedCount == 0 { _emit("const ") } _emit("enum ") } case .OBJECT_INTERFACE { _emit("interface ") } case .OBJECT_WRAPPED { _emit("type ") } case .OBJECT_NAMESPACE { _emit("namespace ") } default { assert(false) } } _emit(_mangleName(symbol)) _emitTypeParameters(symbol.parameters) if symbol.kind == .OBJECT_WRAPPED { _emit(" = ") _emitExpressionOrType(symbol.extends, symbol.wrappedType) _emit("\n") _emitNewlineAfterSymbol(symbol) } else { if symbol.extends != null || symbol.implements != null { if symbol.extends != null { _emit(" extends ") _emitExpressionOrType(symbol.extends, symbol.baseType) } if symbol.implements != null { _emit(" implements ") for node in symbol.implements { if node != symbol.implements.first { _emit(", ") } _emitExpressionOrType(node, node.resolvedType) } } } _emit(" {\n") _increaseIndent _expectedNextEnumValue = 0 if symbol.kind == .OBJECT_NAMESPACE { _enclosingNamespaces.append(symbol) } var variablesComeFirst = symbol.kind == .OBJECT_CLASS if variablesComeFirst { for variable in symbol.variables { _emitVariable(variable) } } var multiple = _ctors.get(symbol.id, null) if multiple != null { _emitConstructor(symbol, multiple.ctors, multiple.canUseArgumentCount) } for function in symbol.functions { _emitFunction(function) } if !variablesComeFirst { for variable in symbol.variables { _emitVariable(variable) } } if symbol.kind == .OBJECT_NAMESPACE { _enclosingNamespaces.removeLast } _emitComments(symbol.commentsInsideEndOfBlock) _decreaseIndent _emit(_indent + "}\n") _emitNewlineAfterSymbol(symbol) } if symbol.objects.count > 0 || (symbol.kind == .OBJECT_WRAPPED && (symbol.variables.count > 0 || symbol.functions.count > 0)) { _emitNewlineBeforeSymbol(symbol) _emit(_indent + "export namespace ") _emit(_mangleName(symbol)) _emit(" {\n") _increaseIndent if symbol.kind == .OBJECT_WRAPPED { _enclosingNamespaces.append(symbol) } for object in symbol.objects { _emitObject(object) } if symbol.kind == .OBJECT_WRAPPED { for function in symbol.functions { _emitFunction(function) } for variable in symbol.variables { _emitVariable(variable) } } if symbol.kind == .OBJECT_WRAPPED { _enclosingNamespaces.removeLast } _decreaseIndent _emit(_indent + "}\n") } } def _emitConstructor(object ObjectSymbol, ctors List, canUseArgumentCount bool) { # Optimize for standard TypeScript idioms if we can if canUseArgumentCount { # Forward-declare the function signatures _emitNewlineBeforeSymbol(ctors.first) for ctor in ctors { _emitComments(ctor.comments) _emit(_indent) _emit("constructor") _emitTypeParameters(ctor.parameters) _emitArgumentList(ctor) _emit(";\n") } _emitNewlineAfterSymbol(ctors.first) # Define the implementation _emitNewlineBeforeSymbol(ctors.first) _emit(_indent) _emit("constructor() {\n") _increaseIndent for ctor in ctors { var block = ctor.block var prefix = ctor == ctors.first ? _indent : "\n\(_indent)else " assert(block != null) assert(block.kind == .BLOCK) # JavaScript arrow functions have sane capture rules for "this" so no variable insertion is needed if ctor.this != null { ctor.this.name = "this" ctor.this.flags |= .IS_EXPORTED } _enclosingFunction = ctor _emit(prefix + "if (arguments.length == \(ctor.arguments.count)) {\n") _increaseIndent if !ctor.arguments.isEmpty { _emit(_indent + "let [\(", ".join(ctor.arguments.map(arg => _mangleName(arg))))]: [") for arg in ctor.arguments { if arg != ctor.arguments.first { _emit(", ") } _emitExpressionOrType(arg.type, arg.resolvedType) } _emit("] = arguments as any;\n") } _emitStatements(block) _decreaseIndent _emit(_indent + "}\n") _enclosingFunction = null } _decreaseIndent _emit(_indent) _emit("}\n") _emitNewlineAfterSymbol(ctors.first) } # Otherwise, fall back to something that is still correct: disambiguating with an index else { # Forward-declare the function signatures _emitNewlineBeforeSymbol(ctors.first) for ctor in ctors { _emitComments(ctor.comments) _emit(_indent) _emit("constructor") _emitTypeParameters(ctor.parameters) _emit("(_: \(ctors.indexOf(ctor))") for arg in ctor.arguments { _emit(", \(_mangleName(arg)): ") _emitExpressionOrType(arg.type, arg.resolvedType) } _emit(");\n") } _emitNewlineAfterSymbol(ctors.first) # Define the implementation _emitNewlineBeforeSymbol(ctors.first) _emit(_indent) _emit("constructor() {\n") _increaseIndent for ctor in ctors { var block = ctor.block var prefix = ctor == ctors.first ? _indent : "\n\(_indent)else " assert(block != null) assert(block.kind == .BLOCK) # JavaScript arrow functions have sane capture rules for "this" so no variable insertion is needed if ctor.this != null { ctor.this.name = "this" ctor.this.flags |= .IS_EXPORTED } _enclosingFunction = ctor _emit(prefix + "if (arguments[0] == \(ctors.indexOf(ctor))) {\n") _increaseIndent if !ctor.arguments.isEmpty { _emit(_indent + "let [\("".join(ctor.arguments.map(arg => ", " + _mangleName(arg))))]: [number") for arg in ctor.arguments { _emit(", ") _emitExpressionOrType(arg.type, arg.resolvedType) } _emit("] = arguments as any;\n") } _emitStatements(block) _decreaseIndent _emit(_indent + "}\n") _enclosingFunction = null } _decreaseIndent _emit(_indent) _emit("}\n") _emitNewlineAfterSymbol(ctors.first) } } def _emitTypeParameters(parameters List) { if parameters != null { _emit("<") for parameter in parameters { if parameter != parameters.first { _emit(", ") } _emit(_mangleName(parameter)) } _emit(">") } } def _emitArgumentList(symbol FunctionSymbol) { _emit("(") for argument in symbol.arguments { if argument != symbol.arguments.first { _emit(", ") } _emit(_mangleName(argument) + ": ") _emitExpressionOrType(argument.type, argument.resolvedType) } _emit(")") } def _emitVariable(symbol VariableSymbol) { _handleSymbol(symbol) if symbol.isImported { return } var trailing = Comment.lastTrailingComment(symbol.comments) var notTrailing = Comment.withoutLastTrailingComment(symbol.comments) _emitNewlineBeforeSymbol(symbol) _emitComments(notTrailing) _emit(_indent) if symbol.kind == .VARIABLE_ENUM_OR_FLAGS { _emit(_mangleName(symbol)) if symbol.value != null { symbol.value.resolvedType = _cache.intType # Enum values are initialized with integers if symbol.value.asInt != _expectedNextEnumValue { _emit(" = ") _emitExpression(symbol.value, .COMMA) } _expectedNextEnumValue = symbol.value.asInt + 1 } _emit(",") } else { if symbol.kind == .VARIABLE_GLOBAL && symbol.parent != null && symbol.parent.kind == .OBJECT_CLASS { _emit("static ") } else if symbol.kind != .VARIABLE_INSTANCE { _emit("export ") _emit("let ") } var emitValue = symbol.value != null && symbol.kind != .VARIABLE_INSTANCE if emitValue && _canOmitTypeAnnotation(symbol.value) { _emit(_mangleName(symbol)) } else { _emit(_mangleName(symbol) + ": ") _emitExpressionOrType(symbol.type, symbol.resolvedType) } if emitValue { _emit(" = ") _emitExpression(symbol.value, .COMMA) } _emit(";") } _emitTrailingComment(trailing) _emit("\n") _emitNewlineAfterSymbol(symbol) } # Various heuristics to make nicer-looking code without introducing too many type errors def _canOmitTypeAnnotation(value Node) bool { var type = _cache.unwrappedType(value.resolvedType) if type == .DYNAMIC { return false } if value.kind == .CALL || value.kind == .DOT { return true } if value.kind == .NAME { return _enclosingFunction == null || value.symbol != _enclosingFunction.this } if type == _cache.boolType || _cache.isNumeric(type) { return true } if type == _cache.stringType && value.kind != .NULL && (value.kind != .CAST || value.castValue.kind != .NULL) { return true } return false } def _emitFunction(symbol FunctionSymbol) { _handleSymbol(symbol) if symbol.isImported { return } # JavaScript arrow functions have sane capture rules for "this" so no variable insertion is needed if symbol.this != null { symbol.this.name = "this" symbol.this.flags |= .IS_EXPORTED } _enclosingFunction = symbol _emitNewlineBeforeSymbol(symbol) _emitComments(symbol.comments) _emit(_indent) var block = symbol.block if block == null && symbol.parent != null && symbol.parent.kind == .OBJECT_CLASS { _emit("abstract ") } if symbol.kind == .FUNCTION_CONSTRUCTOR { _emit("constructor") } else { if symbol.kind == .FUNCTION_GLOBAL && symbol.parent != null && symbol.parent.kind == .OBJECT_CLASS { _emit("static ") } else if symbol.kind != .FUNCTION_INSTANCE { _emit("export function ") } _emit(_mangleName(symbol)) } _emitTypeParameters(symbol.parameters) _emitArgumentList(symbol) if symbol.kind != .FUNCTION_CONSTRUCTOR { _emit(": ") _emitExpressionOrType(symbol.returnType, symbol.resolvedType.returnType) } if block == null { _emit(";\n") } else { var comments List = [] if _options.warnAboutIgnoredComments { _scanForComments(block, comments) _emittedComments = [] } _emitBlock(block) if _options.warnAboutIgnoredComments { for c in comments { if !(c in _emittedComments) { _log.syntaxWarningIgnoredCommentInEmitter(c.range) } } } _emit("\n") } _emitNewlineAfterSymbol(symbol) _enclosingFunction = null } def _scanForComments(node Node, comments List) { if node.comments != null { comments.append(node.comments) } if node.innerComments != null { comments.append(node.innerComments) } for child = node.firstChild; child != null; child = child.nextSibling { _scanForComments(child, comments) } } def _emitType(type Type) { if type == null { _emit("void") return } if type == .DYNAMIC { _emit("any") } else if type.kind == .LAMBDA { var argumentTypes = type.argumentTypes var returnType = type.returnType _emit("(") for i in 0..argumentTypes.count { if i != 0 { _emit(", ") } _emit("v\(i): ") _emitType(argumentTypes[i]) } _emit(") => ") if returnType != null { _emitType(returnType) } else { _emit("void") } } else if _cache.isIntMap(type) || _cache.isStringMap(type) { _emit("Map<") _emit(_cache.isIntMap(type) ? "number" : "string") _emit(", ") _emitType(type.substitutions.first) _emit(">") } else { assert(type.kind == .SYMBOL) _handleSymbol(type.symbol) _emit(_fullName(type.symbol)) if type.isParameterized { _emit("<") for i in 0..type.substitutions.count { if i != 0 { _emit(", ") } _emitType(type.substitutions[i]) } _emit(">") } } } def _emitExpressionOrType(node Node, type Type) { if node != null && (type == null || type == .DYNAMIC) { # Treat the type "dynamic.Object" as an alias for "dynamic" instead of what it actually means if type == .DYNAMIC && node.kind == .NAME && node.asString == "Object" { _emitType(.DYNAMIC) } else { _emitExpression(node, .LOWEST) } } else { _emitType(type) } } def _emitStatements(node Node) { _previousNode = null for child = node.firstChild; child != null; child = child.nextSibling { var trailing = Comment.lastTrailingComment(child.comments) var notTrailing = Comment.withoutLastTrailingComment(child.comments) _emitNewlineBeforeStatement(child) _emitComments(notTrailing) _emitStatement(child, trailing) _emitNewlineAfterStatement(child) } _previousNode = null } def _emitBlock(node Node) { assert(node.kind == .BLOCK) _emit(" {\n") _increaseIndent _emitStatements(node) _decreaseIndent _emit(_indent + "}") } def _emitIf(node Node) { _emit("if (") _emitExpression(node.ifTest, .LOWEST) _emit(")") var then = node.ifTrue var thenComments = then.comments # Some people put comments before blocks in if statements if thenComments != null { _emit("\n") _emitComments(thenComments) _emit(_indent + "{\n") _increaseIndent _emitStatements(then) _decreaseIndent _emit(_indent + "}") } else { _emitBlock(then) } var block = node.ifFalse if block != null { var singleIf = block.hasOneChild && block.firstChild.kind == .IF ? block.firstChild : null if block.comments != null || singleIf != null && singleIf.comments != null { _emit("\n") _emit("\n") _emitComments(block.comments) if singleIf != null { _emitComments(singleIf.comments) } _emit(_indent + "else") } else { _emit(" else") } if singleIf != null { _emit(" ") _emitIf(singleIf) } else { _emitBlock(block) _emit("\n") } } else { _emit("\n") } } def _scanForSwitchBreak(node Node, loop Node) { if node.kind == .BREAK { for parent = node.parent; parent != loop; parent = parent.parent { if parent.kind == .SWITCH { var label = _loopLabels.get(loop.id, null) if label == null { label = VariableSymbol.new(.VARIABLE_LOCAL, _enclosingFunction.scope.generateName("label")) _loopLabels[loop.id] = label } _loopLabels[node.id] = label break } } } # Stop at nested loops since those will be tested later else if node == loop || !node.kind.isLoop { for child = node.firstChild; child != null; child = child.nextSibling { _scanForSwitchBreak(child, loop) } } } def _emitStatement(node Node, trailing Comment) { if node.kind.isLoop { _scanForSwitchBreak(node, node) var label = _loopLabels.get(node.id, null) if label != null { _emit(_indent + _mangleName(label) + (node.nextSibling != null ? ":\n" : ":;\n")) } } switch node.kind { case .COMMENT_BLOCK {} case .VARIABLES { for child = node.firstChild; child != null; child = child.nextSibling { var symbol = child.symbol.asVariableSymbol var value = symbol.value _emit(_indent + "let ") if value != null && _canOmitTypeAnnotation(value) { _emit(_mangleName(symbol)) } else { _emit(_mangleName(symbol) + ": ") _emitExpressionOrType(symbol.type, symbol.resolvedType) } if value != null { var comments = _commentsFromExpression(value) if comments != null { _emit(" =\n") _increaseIndent _emitComments(comments) _emit(_indent) _emitExpression(value, .ASSIGN) _decreaseIndent } else { _emit(" = ") _emitExpression(value, .ASSIGN) } } _emit(";") _emitTrailingComment(trailing) _emit("\n") } } case .EXPRESSION { _emit(_indent) _emitExpression(node.expressionValue, .LOWEST) _emit(";") _emitTrailingComment(trailing) _emit("\n") } case .BREAK { var label = _loopLabels.get(node.id, null) if label != null { _emit(_indent + "break " + _mangleName(label) + ";") } else { _emit(_indent + "break;") } _emitTrailingComment(trailing) _emit("\n") } case .CONTINUE { _emit(_indent + "continue;") _emitTrailingComment(trailing) _emit("\n") } case .IF { _emit(_indent) if trailing != null { _emitComments([trailing]) } _emitIf(node) } case .SWITCH { var switchValue = node.switchValue _emit(_indent + "switch (") _emitExpression(switchValue, .LOWEST) _emit(") {\n") _increaseIndent for child = switchValue.nextSibling; child != null; child = child.nextSibling { var block = child.caseBlock var blockComments = block.comments if child.previousSibling != switchValue { _emit("\n") } _emitComments(child.comments) if child.hasOneChild { _emit(_indent + "default:") } else { for value = child.firstChild; value != block; value = value.nextSibling { if value.previousSibling != null { _emit("\n") } _emitComments(_commentsFromExpression(value)) _emit(_indent + "case ") _emitExpression(value, .LOWEST) _emit(":") } } # Some people put comments before blocks in case statements if blockComments != null { _emit("\n") _emitComments(blockComments) _emit(_indent + "{\n") } else { _emit(" {\n") } _increaseIndent _emitStatements(block) if block.hasControlFlowAtEnd { _emit(_indent + "break;\n") } _decreaseIndent _emit(_indent + "}\n") } _decreaseIndent _emit(_indent + "}") _emitTrailingComment(trailing) _emit("\n") } case .RETURN { _emit(_indent + "return") var value = node.returnValue if value != null { var comments = value.comments if comments != null { # JavaScript needs parentheses here to avoid ASI issues _emit(" (\n") _increaseIndent _emitComments(comments) _emit(_indent) _emitExpression(value, .LOWEST) _decreaseIndent _emit(")") } else { _emit(" ") _emitExpression(value, .LOWEST) } } _emit(";") _emitTrailingComment(trailing) _emit("\n") } case .THROW { _emit(_indent + "throw ") _emitExpression(node.throwValue, .LOWEST) _emit(";") _emitTrailingComment(trailing) _emit("\n") } case .FOREACH { var value = node.foreachValue _emit(_indent + "for (const " + _mangleName(node.symbol)) _emit(_cache.isList(value.resolvedType) ? " of " : " in ") _emitExpression(value, .LOWEST) _emit(")") _emitBlock(node.foreachBlock) _emitTrailingComment(trailing) _emit("\n") } case .FOR { var setup = node.forSetup var test = node.forTest var update = node.forUpdate _emit(_indent + "for (") if !setup.isEmptySequence { if setup.kind == .VARIABLES { var symbol = setup.firstChild.symbol.asVariableSymbol _emit("let ") for child = setup.firstChild; child != null; child = child.nextSibling { symbol = child.symbol.asVariableSymbol assert(child.kind == .VARIABLE) if child.previousSibling != null { _emit(", ") } if _canOmitTypeAnnotation(symbol.value) { _emit(_mangleName(symbol)) } else { _emit(_mangleName(symbol) + ": ") _emitExpressionOrType(symbol.type, symbol.resolvedType) } _emit(" = ") _emitExpression(symbol.value, .COMMA) } } else { _emitExpression(setup, .LOWEST) } } _emit("; ") if !test.isEmptySequence { _emitExpression(test, .LOWEST) } _emit("; ") if !update.isEmptySequence { _emitExpression(update, .LOWEST) } _emit(")") _emitBlock(node.forBlock) _emitTrailingComment(trailing) _emit("\n") } case .TRY { var tryBlock = node.tryBlock var finallyBlock = node.finallyBlock if trailing != null { _emitComments([trailing]) } _emit(_indent + "try") _emitBlock(tryBlock) _emit("\n") for child = tryBlock.nextSibling; child != finallyBlock; child = child.nextSibling { if child.comments != null { _emit("\n") _emitComments(child.comments) } _emit(_indent + "catch") if child.symbol != null { _emit(" (" + _mangleName(child.symbol) + ")") } _emitBlock(child.catchBlock) _emit("\n") } if finallyBlock != null { if finallyBlock.comments != null { _emit("\n") _emitComments(finallyBlock.comments) } _emit(_indent + "finally") _emitBlock(finallyBlock) _emit("\n") } } case .WHILE { _emit(_indent + "while (") _emitExpression(node.whileTest, .LOWEST) _emit(")") _emitBlock(node.whileBlock) _emitTrailingComment(trailing) _emit("\n") } default { assert(false) } } } def _emitContent(content Content) { switch content.kind { case .BOOL { _emit(content.asBool.toString) } case .INT { _emit(content.asInt.toString) } case .DOUBLE { var value = content.asDouble _emit( value.isNaN ? "NaN" : value == Math.INFINITY ? "Infinity" : value == -Math.INFINITY ? "-Infinity" : value.toString) } case .STRING { _emit(quoteString(content.asString, .SHORTEST, .NORMAL)) } } } def _commentsFromExpression(node Node) List { var comments = node.comments switch node.kind { case .CAST { return Comment.concat(comments, node.castValue.comments) } case .CALL { return Comment.concat(comments, node.callValue.comments) } } return comments } def _emitCommaSeparatedExpressions(from Node, to Node) { var isIndented = false for child = from; child != to; child = child.nextSibling { if _commentsFromExpression(child) != null { isIndented = true break } } if isIndented { _increaseIndent } while from != to { var comments = _commentsFromExpression(from) var trailing = Comment.lastTrailingComment(comments) var notTrailing = Comment.withoutLastTrailingComment(comments) if isIndented { _emit("\n") _emitComments(notTrailing) _emit(_indent) } _emitExpression(from, .COMMA) from = from.nextSibling if from != to { _emit(isIndented ? "," : ", ") } _emitTrailingComment(trailing) } if isIndented { _decreaseIndent _emit("\n") _emit(_indent) } } def _emitExpression(node Node, precedence Precedence) { var kind = node.kind var symbol = node.symbol if symbol != null { _handleSymbol(symbol) } switch kind { case .TYPE, .LAMBDA_TYPE { _emitType(node.resolvedType) } case .NULL { _emit("null") } case .NAME { _emit(symbol != null ? _fullName(symbol) : node.asString) } case .DOT { var innerComments = node.innerComments _emitExpression(node.dotTarget, .MEMBER) if innerComments != null { _increaseIndent _emit("\n") _emitComments(innerComments) _emit(_indent) _decreaseIndent } _emit("." + (symbol != null ? _mangleName(symbol) : node.asString)) } case .STRING_INTERPOLATION { _emit("`") var isString = true for child = node.firstChild; child != null; child = child.nextSibling { if isString { _emit(quoteString(child.asString, .TYPESCRIPT_TEMPLATE, .NORMAL)) } else { _emit("${") var value = child # Omit implied ".toString()" calls on interpolated values if value.kind == .CALL { var target = value.callValue if target.nextSibling == null && target.kind == .DOT && target.asString == "toString" { value = target.dotTarget } } _emitExpression(value, .LOWEST) _emit("}") } isString = !isString } _emit("`") } case .CONSTANT { var wrap = precedence == .MEMBER && node.isNumberLessThanZero && (!node.isDouble || node.asDouble.isFinite) if wrap { _emit("(") } _emitContent(node.content) if node.resolvedType.isEnumOrFlags { _emit(" as ") _emitType(node.resolvedType) } if wrap { _emit(")") } } case .CALL { var value = node.callValue var wrap = value.kind == .LAMBDA # Turn "new Object" into "{}" if value.kind == .DOT && value.asString == "new" && value.nextSibling == null { var target = value.dotTarget if target.kind == .NAME && target.asString == "Object" { _emit("{}") return } } if wrap { _emit("(") } if value.kind == .SUPER { _emit("super") if symbol.kind != .FUNCTION_CONSTRUCTOR { _emit(".") _emit(_mangleName(symbol)) } } else if symbol != null && symbol.kind == .FUNCTION_CONSTRUCTOR { _emit("new ") _emitType(node.resolvedType) } else if value.kind == .DOT && value.asString == "new" { _emit("new ") _emitExpression(value.dotTarget, .MEMBER) } else { _emitExpression(value, .UNARY_POSTFIX) } if wrap { _emit(")") } _emit("(") if symbol != null && symbol.kind == .FUNCTION_CONSTRUCTOR { var multiple = _ctors.get(symbol.parent.id, null) if multiple != null && !multiple.canUseArgumentCount { _emit("\(multiple.ctors.indexOf(symbol as FunctionSymbol))") if value.nextSibling != null { _emit(", ") } } } _emitCommaSeparatedExpressions(value.nextSibling, null) _emit(")") } case .CAST { var type = node.castType var value = node.castValue var unwrappedSource = _cache.unwrappedType(value.resolvedType) var unwrappedTarget = _cache.unwrappedType(type.resolvedType) # Skip the cast in certain cases if type.kind == .TYPE && (type.resolvedType == .DYNAMIC || value.kind == .NULL) { _emitExpression(value, precedence) } # Conversion from integer to any numeric type can be ignored else if _cache.isInteger(unwrappedSource) && _cache.isNumeric(unwrappedTarget) { _emitExpression(value, precedence) } # Cast from bool to a number else if _cache.isNumeric(unwrappedTarget) && value.resolvedType == _cache.boolType { _emitExpression(Node.createHook(value.remove, _cache.createInt(1), _cache.createInt(0)).withType(_cache.intType), precedence) } # Cast to bool else if unwrappedTarget == _cache.boolType && unwrappedSource != _cache.boolType { _emitExpression(Node.createUnary(.NOT, Node.createUnary(.NOT, value.remove).withType(_cache.boolType)).withType(_cache.boolType), precedence) } # Cast to int else if _cache.isInteger(unwrappedTarget) && !_cache.isInteger(unwrappedSource) { _emitExpression(Node.createBinary(.BITWISE_OR, value.remove, Node.createInt(0).withType(_cache.intType)).withType(_cache.intType), precedence) } # Cast to double else if unwrappedTarget == _cache.doubleType && unwrappedSource != _cache.doubleType { _emitExpression(Node.createUnary(.POSITIVE, value.remove).withType(_cache.doubleType), precedence) } # Cast to string else if unwrappedTarget == _cache.stringType && unwrappedSource != _cache.stringType { _emitExpression(Node.createSymbolCall(_specialVariable(.AS_STRING)).appendChild(value.remove).withType(_cache.stringType), precedence) } # Only emit a cast if the underlying types are different else if unwrappedSource != unwrappedTarget || type.resolvedType == .DYNAMIC { if Precedence.ASSIGN < precedence { _emit("(") } _emitExpression(value, .ASSIGN) _emit(" as ") _emitExpressionOrType(type, type.resolvedType) if Precedence.ASSIGN < precedence { _emit(")") } } # Otherwise, pretend the cast isn't there else { _emitExpression(value, precedence) } } case .TYPE_CHECK { var value = node.typeCheckValue var type = node.typeCheckType var targetType = _cache.unwrappedType(type.resolvedType) if _cache.isInteger(targetType) { _emitExpression(Node.createSymbolCall(_specialVariable(.IS_INT)).appendChild(value.remove).withType(_cache.boolType), precedence) return } if Precedence.COMPARE < precedence { _emit("(") } if targetType == _cache.doubleType { _emit("typeof ") _emitExpression(value, .UNARY_PREFIX) _emit(" === 'number'") } else if targetType == _cache.stringType { _emit("typeof ") _emitExpression(value, .UNARY_PREFIX) _emit(" === 'string'") } else if targetType == _cache.boolType { _emit("typeof ") _emitExpression(value, .UNARY_PREFIX) _emit(" === 'boolean'") } else { _emitExpression(value, .LOWEST) _emit(" instanceof ") if type.resolvedType == .DYNAMIC { _emitExpression(type, .LOWEST) } else { _emitExpressionOrType(type, type.resolvedType) } } if Precedence.COMPARE < precedence { _emit(")") } } case .INITIALIZER_LIST { _emit("[") _emitCommaSeparatedExpressions(node.firstChild, null) _emit("]") } case .INITIALIZER_MAP { if !node.hasChildren { _emit("{}") } else { _emit("{\n") _increaseIndent for child = node.firstChild; child != null; child = child.nextSibling { _emitComments(child.comments) _emit(_indent) _emitExpression(child.firstValue, .COMMA) _emit(": ") _emitExpression(child.secondValue, .COMMA) _emit(",\n") } _decreaseIndent _emit(_indent + "}") } } case .INDEX { _emitExpression(node.indexLeft, .UNARY_POSTFIX) _emit("[") _emitExpression(node.indexRight, .LOWEST) _emit("]") } case .ASSIGN_INDEX { if Precedence.ASSIGN < precedence { _emit("(") } _emitExpression(node.assignIndexLeft, .UNARY_POSTFIX) _emit("[") _emitExpression(node.assignIndexCenter, .LOWEST) _emit("] = ") _emitExpression(node.assignIndexRight, .ASSIGN) if Precedence.ASSIGN < precedence { _emit(")") } } case .PARAMETERIZE { var value = node.parameterizeValue if value.isType { _emitType(node.resolvedType) } else { _emitExpression(value, precedence) _emit("<") _emitCommaSeparatedExpressions(value.nextSibling, null) _emit(">") } } case .SEQUENCE { if Precedence.COMMA <= precedence { _emit("(") } _emitCommaSeparatedExpressions(node.firstChild, null) if Precedence.COMMA <= precedence { _emit(")") } } case .HOOK { if Precedence.ASSIGN < precedence { _emit("(") } _emitExpression(node.hookTest, .LOGICAL_OR) _emit(" ?") var left = node.hookTrue var leftComments = _commentsFromExpression(left) if leftComments != null { _emit("\n") _increaseIndent _emitComments(leftComments) _emit(_indent) _emitExpression(left, .ASSIGN) _decreaseIndent } else { _emit(" ") _emitExpression(left, .ASSIGN) } _emit(" :") var right = node.hookFalse var rightComments = _commentsFromExpression(right) if rightComments != null { _emit("\n") _increaseIndent _emitComments(rightComments) _emit(_indent) _emitExpression(right, .ASSIGN) _decreaseIndent } else { _emit(" ") _emitExpression(right, .ASSIGN) } if Precedence.ASSIGN < precedence { _emit(")") } } case .LAMBDA { var oldEnclosingFunction = _enclosingFunction _enclosingFunction = symbol.asFunctionSymbol _emitArgumentList(symbol.asFunctionSymbol) _emit(" =>") _emitBlock(symbol.asFunctionSymbol.block) _enclosingFunction = oldEnclosingFunction } case .COMPLEMENT, .NEGATIVE, .NOT, .POSITIVE, .POSTFIX_DECREMENT, .POSTFIX_INCREMENT, .PREFIX_DECREMENT, .PREFIX_INCREMENT { var value = node.unaryValue var info = operatorInfo[kind] var sign = node.sign if info.precedence < precedence { _emit("(") } if !kind.isUnaryPostfix { _emit(info.text) # Prevent "x - -1" from becoming "x--1" if sign != .NULL && sign == value.sign { _emit(" ") } } _emitExpression(value, info.precedence) if kind.isUnaryPostfix { _emit(info.text) } if info.precedence < precedence { _emit(")") } } default { if kind.isBinary { var left = node.binaryLeft var right = node.binaryRight # Handle truncating integer division if node.resolvedType == _cache.intType && kind == .DIVIDE && node.parent != null && node.parent.kind != .BITWISE_OR { const divide = Node.createBinary(.DIVIDE, left.remove, right.remove).withType(_cache.intType) const zero = Node.createInt(0).withType(_cache.intType) _emitExpression(Node.createBinary(.BITWISE_OR, divide, zero).withType(_cache.intType), precedence) return } var info = operatorInfo[kind] if info.precedence < precedence { _emit("(") } _emitExpression(left, info.precedence.incrementIfRightAssociative(info.associativity)) _emit(" " + info.text + (kind == .EQUAL || kind == .NOT_EQUAL ? "=" : "")) var comments = _commentsFromExpression(right) if comments != null { var leading = Comment.firstTrailingComment(comments) var notLeading = Comment.withoutFirstTrailingComment(comments) _emitTrailingComment(leading) _emit("\n") _increaseIndent _emitComments(notLeading) _emit(_indent) _emitExpression(right, info.precedence.incrementIfLeftAssociative(info.associativity)) _decreaseIndent } else { _emit(" ") _emitExpression(right, info.precedence.incrementIfLeftAssociative(info.associativity)) } if info.precedence < precedence { _emit(")") } } else { assert(false) } } } } def _fullName(symbol Symbol) string { var parent = symbol.parent if parent != null && parent.kind != .OBJECT_GLOBAL && (!_shouldFlattenNamespace(parent) || parent.isImported) && !symbol.kind.isParameter && !(parent in _enclosingNamespaces) { var enclosingName = _fullName(parent) if symbol.kind == .FUNCTION_CONSTRUCTOR { return enclosingName } return enclosingName + "." + _mangleName(symbol) } return _mangleName(symbol) } } namespace TypeScriptEmitter { def _isInsideNamespaceOrGlobal(symbol Symbol) bool { var parent = symbol.parent return parent != null && (parent.kind == .OBJECT_GLOBAL || parent.kind == .OBJECT_NAMESPACE && _isInsideNamespaceOrGlobal(parent)) } def _shouldFlattenNamespace(symbol Symbol) bool { return symbol.kind == .OBJECT_NAMESPACE && _isInsideNamespaceOrGlobal(symbol) } def _isCompactNodeKind(kind NodeKind) bool { return kind == .EXPRESSION || kind == .VARIABLES || kind.isJump } def _mangleName(symbol Symbol) string { symbol = symbol.forwarded if symbol.kind == .FUNCTION_CONSTRUCTOR { symbol = symbol.parent } if !symbol.isImportedOrExported && symbol.name in _isKeyword { return "_" + symbol.name } var parent = symbol.parent if parent != null && parent.kind == .OBJECT_NAMESPACE && parent.name.startsWith("in_") { var prefix = _mangleName(parent) if prefix.startsWith("in_") { prefix = prefix.slice(3) } return prefix + "_" + symbol.name } return symbol.name } # https://github.com/Microsoft/TypeScript/issues/2536 const _isKeyword = { # Reserved Words "break": 0, "case": 0, "catch": 0, "class": 0, "const": 0, "continue": 0, "debugger": 0, "default": 0, "delete": 0, "do": 0, "else": 0, "enum": 0, "export": 0, "extends": 0, "false": 0, "finally": 0, "for": 0, "function": 0, "if": 0, "implements": 0, "import": 0, "in": 0, "instanceof": 0, "interface": 0, "namespace": 0, "new": 0, "null": 0, "return": 0, "super": 0, "switch": 0, "this": 0, "throw": 0, "true": 0, "try": 0, "typeof": 0, "var": 0, "void": 0, "while": 0, "with": 0, # Strict mode reserved words "as": 0, "implements": 0, "interface": 0, "let": 0, "package": 0, "private": 0, "protected": 0, "public": 0, "static": 0, "yield": 0, # Other special names that must be avoided "arguments": 0, "Symbol": 0, } const _specialVariableMap = { "__asString": SpecialVariable.AS_STRING, "__isInt": SpecialVariable.IS_INT, } } } ================================================ FILE: src/core/content.sk ================================================ namespace Skew { enum ContentKind { BOOL INT DOUBLE STRING } interface Content { def kind ContentKind def asBool bool { assert(kind == .BOOL) return (self as BoolContent).value } def asInt int { assert(kind == .INT) return (self as IntContent).value } def asDouble double { assert(kind == .DOUBLE) return (self as DoubleContent).value } def asString string { assert(kind == .STRING) return (self as StringContent).value } def equals(other Content) bool { if kind == other.kind { switch kind { case .BOOL { return asBool == other.asBool } case .INT { return asInt == other.asInt } case .DOUBLE { return asDouble == other.asDouble } case .STRING { return asString == other.asString } } } return false } } class BoolContent :: Content { const value bool def kind ContentKind { return .BOOL } } class IntContent :: Content { const value int def kind ContentKind { return .INT } } class DoubleContent :: Content { const value double def kind ContentKind { return .DOUBLE } } class StringContent :: Content { const value string def kind ContentKind { return .STRING } } } ================================================ FILE: src/core/node.sk ================================================ namespace Skew { enum NodeKind { # Other ANNOTATION BLOCK CASE CATCH VARIABLE # Statements BREAK COMMENT_BLOCK CONTINUE EXPRESSION FOR FOREACH IF RETURN SWITCH THROW TRY VARIABLES WHILE # Expressions ASSIGN_INDEX CALL CAST CONSTANT DOT HOOK INDEX INITIALIZER_LIST INITIALIZER_MAP LAMBDA LAMBDA_TYPE NAME NULL NULL_DOT PAIR PARAMETERIZE PARSE_ERROR SEQUENCE STRING_INTERPOLATION SUPER TYPE TYPE_CHECK XML # Unary operators COMPLEMENT NEGATIVE NOT POSITIVE POSTFIX_DECREMENT POSTFIX_INCREMENT PREFIX_DECREMENT PREFIX_INCREMENT # Binary operators ADD BITWISE_AND BITWISE_OR BITWISE_XOR COMPARE DIVIDE EQUAL IN LOGICAL_AND LOGICAL_OR MODULUS MULTIPLY NOT_EQUAL NULL_JOIN POWER REMAINDER SHIFT_LEFT SHIFT_RIGHT SUBTRACT UNSIGNED_SHIFT_RIGHT # Binary comparison operators GREATER_THAN GREATER_THAN_OR_EQUAL LESS_THAN LESS_THAN_OR_EQUAL # Binary assigment operators ASSIGN ASSIGN_ADD ASSIGN_BITWISE_AND ASSIGN_BITWISE_OR ASSIGN_BITWISE_XOR ASSIGN_DIVIDE ASSIGN_MODULUS ASSIGN_MULTIPLY ASSIGN_NULL ASSIGN_POWER ASSIGN_REMAINDER ASSIGN_SHIFT_LEFT ASSIGN_SHIFT_RIGHT ASSIGN_SUBTRACT ASSIGN_UNSIGNED_SHIFT_RIGHT def isStatement bool { return self >= ASSIGN && self <= WHILE } def isBitOperation bool { return self == COMPLEMENT || self >= BITWISE_AND && self <= BITWISE_XOR || self >= ASSIGN_BITWISE_AND && self <= ASSIGN_BITWISE_XOR } def isLoop bool { return self == FOR || self == FOREACH || self == WHILE } def isExpression bool { return self >= ASSIGN_INDEX && self <= ASSIGN_UNSIGNED_SHIFT_RIGHT } def isInitializer bool { return self == INITIALIZER_LIST || self == INITIALIZER_MAP } def isUnary bool { return self >= COMPLEMENT && self <= PREFIX_INCREMENT } def isUnaryAssign bool { return self >= POSTFIX_DECREMENT && self <= PREFIX_INCREMENT } def isUnaryPostfix bool { return self == POSTFIX_DECREMENT || self == POSTFIX_INCREMENT } def isBinary bool { return self >= ADD && self <= ASSIGN_UNSIGNED_SHIFT_RIGHT } def isBinaryAssign bool { return self >= ASSIGN && self <= ASSIGN_UNSIGNED_SHIFT_RIGHT } # Note that add and multiply are NOT associative in finite-precision arithmetic def isBinaryAssociative bool { switch self { case BITWISE_AND, BITWISE_OR, BITWISE_XOR, LOGICAL_AND, LOGICAL_OR { return true } } return false } def isBinaryComparison bool { return self >= GREATER_THAN && self <= LESS_THAN_OR_EQUAL } def isShift bool { return self == SHIFT_LEFT || self == SHIFT_RIGHT || self == UNSIGNED_SHIFT_RIGHT } def isJump bool { return self == BREAK || self == CONTINUE || self == RETURN } def isAssign bool { return isUnaryAssign || isBinaryAssign || self == ASSIGN_INDEX } } flags NodeFlags { # This flag is only for blocks. A simple control flow analysis is run # during code resolution and blocks where control flow reaches the end of # the block have this flag set. HAS_CONTROL_FLOW_AT_END # Use this flag to tell the IDE support code to ignore this node. This is # useful for compiler-generated nodes that are used for lowering and that # need marked ranges for error reporting but that should not show up in # tooltips. IS_IGNORED_BY_IDE # An implicit return is a return statement inside an expression lambda. For # example, the lambda "x => x" is compiled into "x => { return x }" where # the return statement has this flag set. IS_IMPLICIT_RETURN # This flag marks list nodes that help implement initializer expressions. IS_INITIALIZER_EXPANSION # This flag marks nodes that were wrapped in parentheses in the original # source code. It's used for warnings about C-style syntax in conditional # statements and to call a lambda returned from a getter. IS_INSIDE_PARENTHESES # This flag is set on nodes that are expected to be types. SHOULD_EXPECT_TYPE # This flag marks nodes that were converted from ASSIGN_NULL to ASSIGN nodes. WAS_ASSIGN_NULL # This flag marks nodes that were converted from NULL_JOIN to HOOK nodes. WAS_NULL_JOIN } # Nodes represent executable code (variable initializers and function bodies) class Node { const id = _createID var kind NodeKind var flags NodeFlags = 0 var range Range = null var internalRange Range = null var symbol Symbol = null var content Content = null var resolvedType Type = null var comments List = null var innerComments List = null var _parent Node = null var _firstChild Node = null var _lastChild Node = null var _previousSibling Node = null var _nextSibling Node = null def _cloneWithoutChildren Node { var clone = new(kind) clone.flags = flags clone.range = range clone.internalRange = internalRange clone.symbol = symbol clone.content = content clone.resolvedType = resolvedType clone.comments = comments?.clone clone.innerComments = innerComments?.clone return clone } # When used with become(), this provides a convenient way to wrap a node in # an operation without the caller needing to be aware of replaceWith(): # # node.become(Node.createUnary(.NOT, node.cloneAndStealChildren)) # def cloneAndStealChildren Node { var clone = _cloneWithoutChildren while hasChildren { clone.appendChild(_firstChild.remove) } return clone } def clone Node { var clone = _cloneWithoutChildren if kind == .LAMBDA { clone.symbol = symbol.asFunctionSymbol.clone clone.appendChild(clone.symbol.asFunctionSymbol.block) } else if kind == .VARIABLE { clone.symbol = symbol.asVariableSymbol.clone clone.appendChild(clone.symbol.asVariableSymbol.value) } else { for child = _firstChild; child != null; child = child._nextSibling { clone.appendChild(child.clone) } } return clone } # Change self node in place to become the provided node. The parent node is # not changed, so become() can be called within a nested method and does not # need to report the updated node reference to the caller since the reference # does not change. def become(node Node) { if node == self { return } assert(node._parent == null) kind = node.kind flags = node.flags range = node.range internalRange = node.internalRange symbol = node.symbol content = node.content resolvedType = node.resolvedType comments = node.comments removeChildren appendChildrenFrom(node) } def parent Node { return _parent } def firstChild Node { return _firstChild } def lastChild Node { return _lastChild } def previousSibling Node { return _previousSibling } def nextSibling Node { return _nextSibling } def hasControlFlowAtEnd bool { return .HAS_CONTROL_FLOW_AT_END in flags } def isIgnoredByIDE bool { return .IS_IGNORED_BY_IDE in flags } def isImplicitReturn bool { return .IS_IMPLICIT_RETURN in flags } def isInitializerExpansion bool { return .IS_INITIALIZER_EXPANSION in flags } def isInsideParentheses bool { return .IS_INSIDE_PARENTHESES in flags } def wasAssignNull bool { return .WAS_ASSIGN_NULL in flags } def wasNullJoin bool { return .WAS_NULL_JOIN in flags } def shouldExpectType bool { for node = self; node != null; node = node.parent { if .SHOULD_EXPECT_TYPE in node.flags { return true } } return false } # This is cheaper than childCount == 0 def hasChildren bool { return _firstChild != null } # This is cheaper than childCount == 1 def hasOneChild bool { return hasChildren && _firstChild == _lastChild } # This is cheaper than childCount == 2 def hasTwoChildren bool { return hasChildren && _firstChild.nextSibling == _lastChild } # This is cheaper than childCount == 3 def hasThreeChildren bool { return hasChildren && _firstChild.nextSibling == _lastChild.previousSibling } # This is cheaper than childCount == 4 def hasFourChildren bool { return hasChildren && _firstChild.nextSibling != null && _firstChild.nextSibling.nextSibling == _lastChild.previousSibling } def childCount int { var count = 0 for child = _firstChild; child != null; child = child._nextSibling { count++ } return count } def withFlags(value NodeFlags) Node { flags = value return self } def withType(value Type) Node { resolvedType = value return self } def withSymbol(value Symbol) Node { symbol = value return self } def withContent(value Content) Node { content = value return self } def withRange(value Range) Node { range = value return self } def withInternalRange(value Range) Node { internalRange = value return self } def withComments(value List) Node { assert(comments == null) comments = value return self } def withInnerComments(value List) Node { assert(innerComments == null) innerComments = value return self } def internalRangeOrRange Range { return internalRange ?? range } def prependChild(node Node) Node { if node == null { return self } assert(node != self) assert(node._parent == null) assert(node._previousSibling == null) assert(node._nextSibling == null) node._parent = self if hasChildren { node._nextSibling = _firstChild _firstChild._previousSibling = node _firstChild = node } else { _lastChild = _firstChild = node } return self } def appendChild(node Node) Node { if node == null { return self } assert(node != self) assert(node._parent == null) assert(node._previousSibling == null) assert(node._nextSibling == null) node._parent = self if hasChildren { node._previousSibling = _lastChild _lastChild._nextSibling = node _lastChild = node } else { _lastChild = _firstChild = node } return self } def appendChildrenFrom(node Node) Node { assert(node != self) while node.hasChildren { appendChild(node._firstChild.remove) } return self } def insertChildBefore(after Node, before Node) Node { if before == null { return self } assert(before != after) assert(before._parent == null) assert(before._previousSibling == null) assert(before._nextSibling == null) assert(after == null || after._parent == self) if after == null { return appendChild(before) } before._parent = self before._previousSibling = after._previousSibling before._nextSibling = after if after._previousSibling != null { assert(after == after._previousSibling._nextSibling) after._previousSibling._nextSibling = before } else { assert(after == _firstChild) _firstChild = before } after._previousSibling = before return self } def insertChildAfter(before Node, after Node) Node { if after == null { return self } assert(before != after) assert(after._parent == null) assert(after._previousSibling == null) assert(after._nextSibling == null) assert(before == null || before._parent == self) if before == null { return prependChild(after) } after._parent = self after._previousSibling = before after._nextSibling = before._nextSibling if before._nextSibling != null { assert(before == before._nextSibling._previousSibling) before._nextSibling._previousSibling = after } else { assert(before == _lastChild) _lastChild = after } before._nextSibling = after return self } def insertChildrenAfterFrom(from Node, after Node) { while from.hasChildren { insertChildAfter(after, from.lastChild.remove) } } def remove Node { assert(_parent != null) if _previousSibling != null { assert(_previousSibling._nextSibling == self) _previousSibling._nextSibling = _nextSibling } else { assert(_parent._firstChild == self) _parent._firstChild = _nextSibling } if _nextSibling != null { assert(_nextSibling._previousSibling == self) _nextSibling._previousSibling = _previousSibling } else { assert(_parent._lastChild == self) _parent._lastChild = _previousSibling } _parent = null _previousSibling = null _nextSibling = null return self } def removeChildren { while hasChildren { _firstChild.remove } } def replaceWith(node Node) Node { assert(node != self) assert(_parent != null) assert(node._parent == null) assert(node._previousSibling == null) assert(node._nextSibling == null) node._parent = _parent node._previousSibling = _previousSibling node._nextSibling = _nextSibling if _previousSibling != null { assert(_previousSibling._nextSibling == self) _previousSibling._nextSibling = node } else { assert(_parent._firstChild == self) _parent._firstChild = node } if _nextSibling != null { assert(_nextSibling._previousSibling == self) _nextSibling._previousSibling = node } else { assert(_parent._lastChild == self) _parent._lastChild = node } if _parent.kind == .LAMBDA { assert(self == _parent.symbol.asFunctionSymbol.block) _parent.symbol.asFunctionSymbol.block = node } else if _parent.kind == .VARIABLE { assert(self == _parent.symbol.asVariableSymbol.value) _parent.symbol.asVariableSymbol.value = node } _parent = null _previousSibling = null _nextSibling = null return self } def replaceWithChildrenFrom(node Node) Node { assert(node != self) var parent = _parent while node.hasChildren { parent.insertChildBefore(self, node._firstChild.remove) } return remove } def swapWith(node Node) { assert(node != self) assert(_parent != null && _parent == node._parent) var parent = _parent var nextSibling = _nextSibling if node == _previousSibling { parent.insertChildBefore(node, remove) } else if node == nextSibling { parent.insertChildAfter(node, remove) } else { parent.insertChildBefore(node, remove) parent.insertChildBefore(nextSibling, node.remove) } } } namespace Node { def _createID int { return ++_nextID } var _nextID = 0 def _symbolsOrStringsLookTheSame(left Node, right Node) bool { return left.symbol != null && left.symbol == right.symbol || left.symbol == null && right.symbol == null && left.asString == right.asString } def _childrenLookTheSame(left Node, right Node) bool { var leftChild = left.firstChild var rightChild = right.firstChild while leftChild != null && rightChild != null { if !_looksTheSame(leftChild, rightChild) { return false } leftChild = leftChild.nextSibling rightChild = rightChild.nextSibling } return leftChild == null && rightChild == null } def _looksTheSame(left Node, right Node) bool { if left.kind == right.kind { switch left.kind { case .NULL { return true } case .NAME { return _symbolsOrStringsLookTheSame(left, right) } case .DOT { return _symbolsOrStringsLookTheSame(left, right) && _looksTheSame(left.dotTarget, right.dotTarget) } case .CONSTANT { switch left.content.kind { case .INT { return right.isInt && left.asInt == right.asInt } case .BOOL { return right.isBool && left.asBool == right.asBool } case .DOUBLE { return right.isDouble && left.asDouble == right.asDouble } case .STRING { return right.isString && left.asString == right.asString } } } case .BLOCK, .BREAK, .CONTINUE, .EXPRESSION, .IF, .RETURN, .THROW, .WHILE, .ASSIGN_INDEX, .CALL, .HOOK, .INDEX, .INITIALIZER_LIST, .INITIALIZER_MAP, .PAIR, .SEQUENCE, .COMPLEMENT, .NEGATIVE, .NOT, .POSITIVE, .POSTFIX_DECREMENT, .POSTFIX_INCREMENT, .PREFIX_DECREMENT, .PREFIX_INCREMENT { return _childrenLookTheSame(left, right) } default { if left.kind.isBinary { return _childrenLookTheSame(left, right) } } } } # Null literals are always implicitly casted, so unwrap implicit casts if left.kind == .CAST { return _looksTheSame(left.castValue, right) } if right.kind == .CAST { return _looksTheSame(left, right.castValue) } return false } } # Node-specific queries class Node { def isSuperCallStatement bool { return kind == .EXPRESSION && (expressionValue.kind == .SUPER || expressionValue.kind == .CALL && expressionValue.callValue.kind == .SUPER) } def isEmptySequence bool { return kind == .SEQUENCE && !hasChildren } def isTrue bool { return kind == .CONSTANT && content.kind == .BOOL && content.asBool } def isFalse bool { return kind == .CONSTANT && content.kind == .BOOL && !content.asBool } def isType bool { return kind == .TYPE || kind == .LAMBDA_TYPE || (kind == .NAME || kind == .DOT || kind == .PARAMETERIZE) && symbol != null && symbol.kind.isType } def isAssignTarget bool { return _parent != null && (_parent.kind.isUnaryAssign || _parent.kind.isBinaryAssign && self == _parent.binaryLeft) } def isZero bool { return isInt && asInt == 0 || isDouble && asDouble == 0 } def isNumberLessThanZero bool { return isInt && asInt < 0 || isDouble && asDouble < 0 } def hasNoSideEffects bool { assert(kind.isExpression) switch kind { case .CONSTANT, .NAME, .NULL, .TYPE { return true } case .CAST { return castValue.hasNoSideEffects } case .HOOK { return hookTest.hasNoSideEffects && hookTrue.hasNoSideEffects && hookFalse.hasNoSideEffects } case .DOT { return dotTarget.hasNoSideEffects } case .COMPLEMENT, .NEGATIVE, .NOT, .POSITIVE, .POSTFIX_DECREMENT, .POSTFIX_INCREMENT, .PREFIX_DECREMENT, .PREFIX_INCREMENT { return !kind.isUnaryAssign && unaryValue.hasNoSideEffects } default { if kind.isBinary { return !kind.isBinaryAssign && binaryLeft.hasNoSideEffects && binaryRight.hasNoSideEffects } } } return false } def looksTheSameAs(node Node) bool { return _looksTheSame(self, node) } def invertBooleanCondition(cache TypeCache) { assert(kind.isExpression) switch kind { case .CONSTANT { if content.kind == .BOOL { content = BoolContent.new(!content.asBool) } return } case .NOT { become(unaryValue.remove) return } case .EQUAL { kind = .NOT_EQUAL return } case .NOT_EQUAL { kind = .EQUAL return } case .LOGICAL_OR { kind = .LOGICAL_AND binaryLeft.invertBooleanCondition(cache) binaryRight.invertBooleanCondition(cache) return } case .LOGICAL_AND { kind = .LOGICAL_OR binaryLeft.invertBooleanCondition(cache) binaryRight.invertBooleanCondition(cache) return } # Non-equality comparison operators involving floating-point numbers # can't be inverted because one or both of those values may be NAN. # Equality comparisons still work fine because inverting the test # inverts the result as expected: # # Test | Result # --------------+---------- # 0 == NAN | false # 0 != NAN | true # 0 < NAN | false # 0 > NAN | false # 0 <= NAN | false # 0 >= NAN | false # NAN == NAN | false # NAN != NAN | true # NAN < NAN | false # NAN > NAN | false # NAN <= NAN | false # NAN >= NAN | false # case .LESS_THAN, .GREATER_THAN, .LESS_THAN_OR_EQUAL, .GREATER_THAN_OR_EQUAL { var commonType = cache.commonImplicitType(binaryLeft.resolvedType, binaryRight.resolvedType) if commonType != null && commonType != cache.doubleType { switch kind { case .LESS_THAN { kind = .GREATER_THAN_OR_EQUAL } case .GREATER_THAN { kind = .LESS_THAN_OR_EQUAL } case .LESS_THAN_OR_EQUAL { kind = .GREATER_THAN } case .GREATER_THAN_OR_EQUAL { kind = .LESS_THAN } } return } } case .SEQUENCE { _lastChild.invertBooleanCondition(cache) return } } become(createUnary(.NOT, cloneAndStealChildren).withType(cache.boolType)) } def replaceVariableWith(node Node) Node { assert(kind == .VARIABLE) assert(parent != null) assert(parent.kind == .VARIABLES) # "var x = 0" becomes "node" if _previousSibling == null && _nextSibling == null { parent.replaceWith(node) } # "var x = 0, y = 0" becomes "node; var y = 0" else if _previousSibling == null { parent._parent.insertChildBefore(parent, node) } # "var x = 0, y = 0" becomes "var x = 0; node" else if _nextSibling == null { parent._parent.insertChildAfter(parent, node) } # "var x = 0, y = 0, z = 0" becomes "var x = 0; node; var z = 0" else { var variables = Node.createVariables parent._parent.insertChildAfter(parent, node) parent._parent.insertChildAfter(node, variables) while _nextSibling != null { variables.appendChild(_nextSibling.remove) } } return remove } # "a + (b + c)" => "(a + b) + c" def rotateBinaryRightToLeft { assert(kind == binaryRight.kind) var left = binaryLeft var right = binaryRight var rightLeft = right.binaryLeft var rightRight = right.binaryRight # "a + (b + c)" => "(b + c) + a" left.swapWith(right) # "a + (b + c)" => "(c + b) + a" rightLeft.swapWith(rightRight) # "a + (b + c)" => "(a + c + b)" right.prependChild(left.remove) # "a + (b + c)" => "(a + b) + c" appendChild(rightRight.remove) } # If a variable is inside a variable cluster, break up the variable cluster # into separate clusters so that variable is in a cluster all by itself. That # way the variable can easily be replaced by something else (an assigment, # for example. This does not handle variables inside loop headers. # # "var a, b, c, d, e" => c.extractVariableFromVariables => "var a, b; var c; var d, e" # def extractVariableFromVariables { assert(kind == .VARIABLE) assert(parent != null && parent.kind == .VARIABLES) assert(parent.parent != null && parent.parent.kind == .BLOCK) # Split off variables before this one if previousSibling != null { var variables = Node.createVariables while previousSibling != null { variables.prependChild(previousSibling.remove) } parent.parent.insertChildBefore(parent, variables) } # Split off variables after this one if nextSibling != null { var variables = Node.createVariables while nextSibling != null { variables.appendChild(nextSibling.remove) } parent.parent.insertChildAfter(parent, variables) } } def sign NodeKind { if kind == .NEGATIVE || kind == .PREFIX_DECREMENT || isNumberLessThanZero { return .NEGATIVE } if kind == .POSITIVE || kind == .PREFIX_INCREMENT { return .POSITIVE } return .NULL } } # Factory functions namespace Node { def createAnnotation(value Node, test Node) Node { assert(value.kind.isExpression) assert(test == null || test.kind.isExpression) return new(.ANNOTATION).appendChild(value).appendChild(test) } def createBlock Node { return new(.BLOCK) } def createCase Node { return new(.CASE) } def createCatch(symbol VariableSymbol, block Node) Node { assert(block.kind == .BLOCK) return new(.CATCH).appendChild(block).withSymbol(symbol) } # This adds the initializer expression to the tree for ease of traversal def createVariable(symbol VariableSymbol) Node { return new(.VARIABLE).appendChild(symbol.value).withSymbol(symbol) } def createBreak Node { return new(.BREAK) } def createCommentBlock Node { return new(.COMMENT_BLOCK) } def createContinue Node { return new(.CONTINUE) } def createExpression(value Node) Node { assert(value.kind.isExpression) return new(.EXPRESSION).appendChild(value) } def createFor(setup Node, test Node, update Node, block Node) Node { assert(setup.kind.isExpression || setup.kind == .VARIABLES) assert(test.kind.isExpression) assert(update.kind.isExpression) assert(block.kind == .BLOCK) return new(.FOR).appendChild(setup).appendChild(test).appendChild(update).appendChild(block) } def createForeach(symbol VariableSymbol, value Node, block Node) Node { assert(value.kind.isExpression) assert(block.kind == .BLOCK) return new(.FOREACH).withSymbol(symbol).appendChild(value).appendChild(block) } def createIf(test Node, trueBlock Node, falseBlock Node) Node { assert(test.kind.isExpression) assert(trueBlock.kind == .BLOCK) assert(falseBlock == null || falseBlock.kind == .BLOCK) return new(.IF).appendChild(test).appendChild(trueBlock).appendChild(falseBlock) } def createReturn(value Node) Node { assert(value == null || value.kind.isExpression) return new(.RETURN).appendChild(value) } def createSwitch(value Node) Node { assert(value.kind.isExpression) return new(.SWITCH).appendChild(value) } def createThrow(value Node) Node { assert(value.kind.isExpression) return new(.THROW).appendChild(value) } def createTry(tryBlock Node) Node { assert(tryBlock.kind == .BLOCK) return new(.TRY).appendChild(tryBlock) } def createVariables Node { return new(.VARIABLES) } def createWhile(test Node, block Node) Node { return new(.WHILE).appendChild(test).appendChild(block) } def createAssignIndex(left Node, center Node, right Node) Node { assert(left.kind.isExpression) assert(center.kind.isExpression) assert(right.kind.isExpression) return new(.ASSIGN_INDEX).appendChild(left).appendChild(center).appendChild(right) } def createIndex(left Node, right Node) Node { assert(left.kind.isExpression) assert(right.kind.isExpression) return new(.INDEX).appendChild(left).appendChild(right) } def createCall(target Node) Node { assert(target.kind.isExpression) return new(.CALL).appendChild(target) } def createCast(value Node, type Node) Node { assert(value.kind.isExpression) assert(type.kind.isExpression) return new(.CAST).appendChild(value).appendChild(type) } def createBool(value bool) Node { return createConstant(BoolContent.new(value)) } def createInt(value int) Node { return createConstant(IntContent.new(value)) } def createDouble(value double) Node { return createConstant(DoubleContent.new(value)) } def createString(value string) Node { return createConstant(StringContent.new(value)) } def createConstant(value Content) Node { return new(.CONSTANT).withContent(value) } def createDot(target Node, name string) Node { return new(.DOT).withContent(StringContent.new(name)).appendChild(target) } def createHook(test Node, trueValue Node, falseValue Node) Node { assert(test.kind.isExpression) assert(trueValue.kind.isExpression) assert(falseValue.kind.isExpression) return new(.HOOK).appendChild(test).appendChild(trueValue).appendChild(falseValue) } def createList Node { return new(.INITIALIZER_LIST) } def createInitializer(kind NodeKind) Node { assert(kind.isInitializer) return new(kind) } # This adds the block to the tree for ease of traversal def createLambda(symbol FunctionSymbol) Node { return new(.LAMBDA).appendChild(symbol.block).withSymbol(symbol) } def createName(text string) Node { return new(.NAME).withContent(StringContent.new(text)) } def createNull Node { return new(.NULL) } def createNullDot(target Node, name string) Node { return new(.NULL_DOT).withContent(StringContent.new(name)).appendChild(target) } def createPair(first Node, second Node) Node { assert(first.kind.isExpression) assert(second.kind.isExpression) return new(.PAIR).appendChild(first).appendChild(second) } def createParameterize(value Node) Node { assert(value.kind.isExpression) return new(.PARAMETERIZE).appendChild(value) } def createParseError Node { return new(.PARSE_ERROR) } def createSequence Node { return new(.SEQUENCE) } def createSequence(before Node, after Node) Node { assert(before.kind.isExpression) assert(after.kind.isExpression) assert(before.parent == null) assert(after.parent == null) if before.kind == .SEQUENCE { if after.kind == .SEQUENCE { return before.withType(after.resolvedType).appendChildrenFrom(after) } return before.withType(after.resolvedType).appendChild(after) } if after.kind == .SEQUENCE { return after.prependChild(before) } return createSequence.withType(after.resolvedType).appendChild(before).appendChild(after) } def createStringInterpolation Node { return new(.STRING_INTERPOLATION) } def createSuper Node { return new(.SUPER) } def createType(type Type) Node { return new(.TYPE).withType(type) } def createTypeCheck(value Node, type Node) Node { assert(value.kind.isExpression) assert(type.kind.isExpression) return new(.TYPE_CHECK).appendChild(value).appendChild(type) } def createXML(tag Node, attributes Node, children Node, closingTag Node) Node { assert(tag.kind.isExpression) assert(attributes.kind == .SEQUENCE) assert(children.kind == .BLOCK) assert(closingTag == null || closingTag.kind.isExpression) return new(.XML).appendChild(tag).appendChild(attributes).appendChild(children).appendChild(closingTag) } def createUnary(kind NodeKind, value Node) Node { assert(kind.isUnary) assert(value.kind.isExpression) return new(kind).appendChild(value) } def createBinary(kind NodeKind, left Node, right Node) Node { assert(kind.isBinary) assert(left.kind.isExpression) assert(right.kind.isExpression) return new(kind).appendChild(left).appendChild(right) } def createLambdaType Node { return new(.LAMBDA_TYPE).appendChild(createType(.NULL)) } def createSymbolReference(symbol Symbol) Node { return createName(symbol.name).withSymbol(symbol).withType(symbol.resolvedType) } def createMemberReference(target Node, member Symbol) Node { return createDot(target, member.name).withSymbol(member).withType(member.resolvedType) } def createSymbolCall(symbol Symbol) Node { return createCall(createSymbolReference(symbol)).withSymbol(symbol).withType(symbol.resolvedType.returnType) } } # Getters, most of which should be inlineable when asserts are skipped in release class Node { def isInt bool { return kind == .CONSTANT && content.kind == .INT } def isBool bool { return kind == .CONSTANT && content.kind == .BOOL } def isDouble bool { return kind == .CONSTANT && content.kind == .DOUBLE } def isString bool { return kind == .CONSTANT && content.kind == .STRING } def asInt int { assert(kind == .CONSTANT) return content.asInt } def asBool bool { assert(kind == .CONSTANT) return content.asBool } def asDouble double { assert(kind == .CONSTANT) return content.asDouble } def asString string { assert(kind == .NAME || kind == .DOT || kind == .CONSTANT || kind == .NULL_DOT) return content.asString } def blockStatement Node { assert(kind == .BLOCK) return hasOneChild ? _firstChild : null } def firstValue Node { assert(kind == .PAIR) assert(childCount == 2) assert(_firstChild.kind.isExpression) return _firstChild } def secondValue Node { assert(kind == .PAIR) assert(childCount == 2) assert(_lastChild.kind.isExpression) return _lastChild } def dotTarget Node { assert(kind == .DOT || kind == .NULL_DOT) assert(childCount <= 1) assert(_firstChild == null || _firstChild.kind.isExpression) return _firstChild } def annotationValue Node { assert(kind == .ANNOTATION) assert(childCount == 1 || childCount == 2) assert(_firstChild.kind.isExpression) return _firstChild } def annotationTest Node { assert(kind == .ANNOTATION) assert(childCount == 1 || childCount == 2) assert(_firstChild._nextSibling == null || _firstChild._nextSibling.kind.isExpression) return _firstChild._nextSibling } def caseBlock Node { assert(kind == .CASE) assert(childCount >= 1) assert(_lastChild.kind == .BLOCK) return _lastChild } def catchBlock Node { assert(kind == .CATCH) assert(childCount == 1) assert(_firstChild.kind == .BLOCK) return _firstChild } def variableValue Node { assert(kind == .VARIABLE) assert(childCount <= 1) assert(_firstChild == null || _firstChild.kind.isExpression) return _firstChild } def expressionValue Node { assert(kind == .EXPRESSION) assert(childCount == 1) assert(_firstChild.kind.isExpression) return _firstChild } def returnValue Node { assert(kind == .RETURN) assert(childCount <= 1) assert(_firstChild == null || _firstChild.kind.isExpression) return _firstChild } def switchValue Node { assert(kind == .SWITCH) assert(childCount >= 1) assert(_firstChild.kind.isExpression) return _firstChild } def defaultCase Node { assert(kind == .SWITCH) assert(childCount >= 1) return !hasOneChild && _lastChild.hasOneChild ? _lastChild : null # The default case is always the last one } def parameterizeValue Node { assert(kind == .PARAMETERIZE) assert(childCount >= 1) assert(_firstChild.kind.isExpression) return _firstChild } def callValue Node { assert(kind == .CALL) assert(childCount >= 1) assert(_firstChild.kind.isExpression) return _firstChild } def castValue Node { assert(kind == .CAST) assert(childCount == 2) assert(_firstChild.kind.isExpression) return _firstChild } def castType Node { assert(kind == .CAST) assert(childCount == 2) assert(_lastChild.kind.isExpression) return _lastChild } def typeCheckValue Node { assert(kind == .TYPE_CHECK) assert(childCount == 2) assert(_firstChild.kind.isExpression) return _firstChild } def typeCheckType Node { assert(kind == .TYPE_CHECK) assert(childCount == 2) assert(_lastChild.kind.isExpression) return _lastChild } def xmlTag Node { assert(kind == .XML) assert(childCount == 3 || childCount == 4) assert(_firstChild.kind.isExpression) return _firstChild } def xmlAttributes Node { assert(kind == .XML) assert(childCount == 3 || childCount == 4) assert(_firstChild._nextSibling.kind == .SEQUENCE) return _firstChild._nextSibling } def xmlChildren Node { assert(kind == .XML) assert(childCount == 3 || childCount == 4) assert(_firstChild._nextSibling._nextSibling.kind == .BLOCK) return _firstChild._nextSibling._nextSibling } def xmlClosingTag Node { assert(kind == .XML) assert(childCount == 3 || childCount == 4) assert( _firstChild._nextSibling._nextSibling._nextSibling == null || _firstChild._nextSibling._nextSibling._nextSibling.kind.isExpression) return _firstChild._nextSibling._nextSibling._nextSibling } def unaryValue Node { assert(kind.isUnary) assert(childCount == 1) assert(_firstChild.kind.isExpression) return _firstChild } def binaryLeft Node { assert(kind.isBinary) assert(childCount == 2) assert(_firstChild.kind.isExpression) return _firstChild } def binaryRight Node { assert(kind.isBinary) assert(childCount == 2) assert(_lastChild.kind.isExpression) return _lastChild } def throwValue Node { assert(childCount == 1) assert(_firstChild.kind.isExpression) return _firstChild } def tryBlock Node { assert(kind == .TRY) assert(childCount >= 1) assert(_firstChild.kind == .BLOCK) return _firstChild } def finallyBlock Node { assert(kind == .TRY) assert(childCount >= 1) var finallyBlock = _lastChild return finallyBlock != tryBlock && finallyBlock.kind == .BLOCK ? finallyBlock : null } def whileTest Node { assert(kind == .WHILE) assert(childCount == 2) assert(_firstChild.kind.isExpression) return _firstChild } def whileBlock Node { assert(kind == .WHILE) assert(childCount == 2) assert(_lastChild.kind == .BLOCK) return _lastChild } def forSetup Node { assert(kind == .FOR) assert(childCount == 4) assert(_firstChild.kind.isExpression || _firstChild.kind == .VARIABLES) return _firstChild } def forTest Node { assert(kind == .FOR) assert(childCount == 4) assert(_firstChild._nextSibling.kind.isExpression) return _firstChild._nextSibling } def forUpdate Node { assert(kind == .FOR) assert(childCount == 4) assert(_lastChild._previousSibling.kind.isExpression) return _lastChild._previousSibling } def forBlock Node { assert(kind == .FOR) assert(childCount == 4) assert(_lastChild.kind == .BLOCK) return _lastChild } def foreachValue Node { assert(kind == .FOREACH) assert(childCount == 2) assert(_firstChild.kind.isExpression) return _firstChild } def foreachBlock Node { assert(kind == .FOREACH) assert(childCount == 2) assert(_lastChild.kind == .BLOCK) return _lastChild } def ifTest Node { assert(kind == .IF) assert(childCount == 2 || childCount == 3) assert(_firstChild.kind.isExpression) return _firstChild } def ifTrue Node { assert(kind == .IF) assert(childCount == 2 || childCount == 3) assert(_firstChild._nextSibling.kind == .BLOCK) return _firstChild._nextSibling } def ifFalse Node { assert(kind == .IF) assert(childCount == 2 || childCount == 3) assert(_firstChild._nextSibling._nextSibling == null || _firstChild._nextSibling._nextSibling.kind == .BLOCK) return _firstChild._nextSibling._nextSibling } def hookTest Node { assert(kind == .HOOK) assert(childCount == 3) assert(_firstChild.kind.isExpression) return _firstChild } def hookTrue Node { assert(kind == .HOOK) assert(childCount == 3) assert(_firstChild._nextSibling.kind.isExpression) return _firstChild._nextSibling } def hookFalse Node { assert(kind == .HOOK) assert(childCount == 3) assert(_lastChild.kind.isExpression) return _lastChild } def indexLeft Node { assert(kind == .INDEX) assert(childCount == 2) assert(_firstChild.kind.isExpression) return _firstChild } def indexRight Node { assert(kind == .INDEX) assert(childCount == 2) assert(_firstChild._nextSibling.kind.isExpression) return _firstChild._nextSibling } def assignIndexLeft Node { assert(kind == .ASSIGN_INDEX) assert(childCount == 3) assert(_firstChild.kind.isExpression) return _firstChild } def assignIndexCenter Node { assert(kind == .ASSIGN_INDEX) assert(childCount == 3) assert(_firstChild._nextSibling.kind.isExpression) return _firstChild._nextSibling } def assignIndexRight Node { assert(kind == .ASSIGN_INDEX) assert(childCount == 3) assert(_lastChild.kind.isExpression) return _lastChild } def lambdaBlock Node { assert(kind == .LAMBDA) assert(childCount == 1) assert(_firstChild.kind == .BLOCK) return _firstChild } def lambdaReturnType Node { assert(kind == .LAMBDA_TYPE) assert(childCount >= 1) assert(_lastChild.kind.isExpression) return _lastChild } } } ================================================ FILE: src/core/operators.sk ================================================ namespace Skew { class OperatorInfo { const text string const precedence Precedence const associativity Associativity const kind OperatorKind const validArgumentCounts List const assignKind NodeKind } enum OperatorKind { FIXED OVERRIDABLE } var operatorInfo = { # Unary operators NodeKind.COMPLEMENT: OperatorInfo.new("~", .UNARY_PREFIX, .NONE, .OVERRIDABLE, [0], .NULL), NodeKind.NEGATIVE: OperatorInfo.new("-", .UNARY_PREFIX, .NONE, .OVERRIDABLE, [0, 1], .NULL), NodeKind.NOT: OperatorInfo.new("!", .UNARY_PREFIX, .NONE, .OVERRIDABLE, [0], .NULL), NodeKind.POSITIVE: OperatorInfo.new("+", .UNARY_PREFIX, .NONE, .OVERRIDABLE, [0, 1], .NULL), NodeKind.POSTFIX_DECREMENT: OperatorInfo.new("--", .UNARY_POSTFIX, .NONE, .OVERRIDABLE, [0], .SUBTRACT), NodeKind.POSTFIX_INCREMENT: OperatorInfo.new("++", .UNARY_POSTFIX, .NONE, .OVERRIDABLE, [0], .ADD), NodeKind.PREFIX_DECREMENT: OperatorInfo.new("--", .UNARY_PREFIX, .NONE, .OVERRIDABLE, [0], .SUBTRACT), NodeKind.PREFIX_INCREMENT: OperatorInfo.new("++", .UNARY_PREFIX, .NONE, .OVERRIDABLE, [0], .ADD), # Binary operators NodeKind.ADD: OperatorInfo.new("+", .ADD, .LEFT, .OVERRIDABLE, [0, 1], .NULL), NodeKind.BITWISE_AND: OperatorInfo.new("&", .BITWISE_AND, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.BITWISE_OR: OperatorInfo.new("|", .BITWISE_OR, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.BITWISE_XOR: OperatorInfo.new("^", .BITWISE_XOR, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.COMPARE: OperatorInfo.new("<=>", .COMPARE, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.DIVIDE: OperatorInfo.new("/", .MULTIPLY, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.EQUAL: OperatorInfo.new("==", .EQUAL, .LEFT, .FIXED, [1], .NULL), NodeKind.GREATER_THAN: OperatorInfo.new(">", .COMPARE, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.GREATER_THAN_OR_EQUAL: OperatorInfo.new(">=", .COMPARE, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.IN: OperatorInfo.new("in", .COMPARE, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.LESS_THAN: OperatorInfo.new("<", .COMPARE, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.LESS_THAN_OR_EQUAL: OperatorInfo.new("<=", .COMPARE, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.LOGICAL_AND: OperatorInfo.new("&&", .LOGICAL_AND, .LEFT, .FIXED, [1], .NULL), NodeKind.LOGICAL_OR: OperatorInfo.new("||", .LOGICAL_OR, .LEFT, .FIXED, [1], .NULL), NodeKind.MODULUS: OperatorInfo.new("%%", .MULTIPLY, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.MULTIPLY: OperatorInfo.new("*", .MULTIPLY, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.NOT_EQUAL: OperatorInfo.new("!=", .EQUAL, .LEFT, .FIXED, [1], .NULL), NodeKind.POWER: OperatorInfo.new("**", .UNARY_PREFIX, .RIGHT, .OVERRIDABLE, [1], .NULL), NodeKind.REMAINDER: OperatorInfo.new("%", .MULTIPLY, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.SHIFT_LEFT: OperatorInfo.new("<<", .SHIFT, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.SHIFT_RIGHT: OperatorInfo.new(">>", .SHIFT, .LEFT, .OVERRIDABLE, [1], .NULL), NodeKind.SUBTRACT: OperatorInfo.new("-", .ADD, .LEFT, .OVERRIDABLE, [0, 1], .NULL), NodeKind.UNSIGNED_SHIFT_RIGHT: OperatorInfo.new(">>>", .SHIFT, .LEFT, .OVERRIDABLE, [1], .NULL), # Binary assignment operators NodeKind.ASSIGN: OperatorInfo.new("=", .ASSIGN, .RIGHT, .FIXED, [1], .NULL), NodeKind.ASSIGN_ADD: OperatorInfo.new("+=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .ADD), NodeKind.ASSIGN_BITWISE_AND: OperatorInfo.new("&=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .BITWISE_AND), NodeKind.ASSIGN_BITWISE_OR: OperatorInfo.new("|=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .BITWISE_OR), NodeKind.ASSIGN_BITWISE_XOR: OperatorInfo.new("^=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .BITWISE_XOR), NodeKind.ASSIGN_DIVIDE: OperatorInfo.new("/=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .DIVIDE), NodeKind.ASSIGN_MODULUS: OperatorInfo.new("%%=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .MODULUS), NodeKind.ASSIGN_MULTIPLY: OperatorInfo.new("*=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .MULTIPLY), NodeKind.ASSIGN_POWER: OperatorInfo.new("**=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .POWER), NodeKind.ASSIGN_REMAINDER: OperatorInfo.new("%=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .REMAINDER), NodeKind.ASSIGN_SHIFT_LEFT: OperatorInfo.new("<<=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .SHIFT_LEFT), NodeKind.ASSIGN_SHIFT_RIGHT: OperatorInfo.new(">>=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .SHIFT_RIGHT), NodeKind.ASSIGN_SUBTRACT: OperatorInfo.new("-=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .SUBTRACT), NodeKind.ASSIGN_UNSIGNED_SHIFT_RIGHT: OperatorInfo.new(">>>=", .ASSIGN, .RIGHT, .OVERRIDABLE, [1], .UNSIGNED_SHIFT_RIGHT), # Index operators NodeKind.ASSIGN_INDEX: OperatorInfo.new("[]=", .MEMBER, .NONE, .OVERRIDABLE, [2], .NULL), NodeKind.INDEX: OperatorInfo.new("[]", .MEMBER, .NONE, .OVERRIDABLE, [1], .NULL), } var validArgumentCounts StringMap> = null def argumentCountForOperator(text string) List { if validArgumentCounts == null { validArgumentCounts = {} for value in operatorInfo.values { validArgumentCounts[value.text] = value.validArgumentCounts } validArgumentCounts["<>..."] = [1] validArgumentCounts["[...]"] = [1] validArgumentCounts["[new]"] = [0, 1] validArgumentCounts["{...}"] = [2] validArgumentCounts["{new}"] = [0, 2] } return validArgumentCounts.get(text, null) } } ================================================ FILE: src/core/support.sk ================================================ namespace Skew { const SORT_STRINGS = (a string, b string) => a <=> b def hashCombine(left int, right int) int { return left ^ right - 0x61c88647 + (left << 6) + (left >> 2) } class UnionFind { var parents List = [] def allocate int { var index = parents.count parents.append(index) return index } def allocate(count int) UnionFind { for i in 0..count { parents.append(parents.count) } return self } def union(left int, right int) { parents[find(left)] = find(right) } def find(index int) int { assert(index >= 0 && index < parents.count) var parent = parents[index] if parent != index { parent = find(parent) parents[index] = parent } return parent } } class SplitPath { const directory string const entry string } def splitPath(path string) SplitPath { var slashIndex = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) return slashIndex == -1 ? SplitPath.new(".", path) : SplitPath.new(path.slice(0, slashIndex), path.slice(slashIndex + 1)) } def withUppercaseFirstLetter(text string) string { return text == "" ? text : text.get(0).toUpperCase + text.slice(1) } def indentIndex(text string) int { var i = 0 while i < text.count && (text[i] == ' ' || text[i] == '\t') { i++ } return i } def indentOfLine(line string) string { return line.slice(0, indentIndex(line)) } def lineWithoutIndent(line string) string { return line.slice(indentIndex(line)) } def formatNumber(number double) string { return (Math.round(number * 10) / 10).toString } def bytesToString(bytes int) string { const KB = 1 << 10 const MB = 1 << 20 const GB = 1 << 30 if bytes == 1 { return "1 byte" } if bytes < KB { return "\(bytes) bytes" } if bytes < MB { return "\(formatNumber(bytes / (KB as double)))kb" } if bytes < GB { return "\(formatNumber(bytes / (MB as double)))mb" } return "\(formatNumber(bytes / (GB as double)))gb" } def doubleToStringWithDot(value double) string { assert(value.isFinite) # These cases are different for each language target and must be handled before this var text = value.toString # The C# implementation of double.ToString() uses an uppercase "E" if TARGET == .CSHARP { text = text.toLowerCase } # "1" => "1.0" # "1.5" => "1.5" # "1e+100" => "1.0e+100" # "1.5e+100" => "1.5e+100" if !("." in text) { var e = text.indexOf("e") if e != -1 { text = text.slice(0, e) + ".0" + text.slice(e) } else { text += ".0" } } return text } # The cost of changing the case of a letter is 0.5 instead of 1 def caseAwareLevenshteinEditDistance(a string, b string) double { var an = a.count var bn = b.count var v0 List = [] var v1 List = [] for i in 0..bn + 1 { v0.append(i) v1.append(i) } for i in 0..an { var ca = a[i] v1[0] = i + 1 for j in 0..bn { var cb = b[j] v1[j + 1] = Math.min( v0[j] + (ca == cb ? 0 : toLowerCase(ca) == toLowerCase(cb) ? 0.5 : 1), Math.min(v1[j], v0[j + 1]) + 1) } for j in 0..bn + 1 { v0[j] = v1[j] } } return v1[bn] } def toLowerCase(c int) int { return c >= 'A' && c <= 'Z' ? 'a' - 'A' + c : c } def replaceSingleQuotesWithDoubleQuotes(text string) string { assert(text.startsWith("'")) assert(text.endsWith("'")) var builder = StringBuilder.new var start = 1 var limit = text.count - 1 builder.append("\"") for i = start; i < limit; i++ { var c = text[i] if c == '\"' { builder.append(text.slice(start, i)) builder.append("\\\"") start = i + 1 } else if c == '\\' { if text[i + 1] == '\'' { builder.append(text.slice(start, i)) builder.append("'") start = i + 2 } i++ } } builder.append(text.slice(start, limit)) builder.append("\"") return builder.toString } } namespace Skew.PrettyPrint { def plural(value int, word string) string { return "\(value) \(word)\(plural(value))" } def plural(value int) string { return value == 1 ? "" : "s" } def joinQuoted(parts List, trailing string) string { return join(parts.map(part => "\"\(part)\""), trailing) } def join(parts List, trailing string) string { if parts.count < 3 { return " \(trailing) ".join(parts) } var text = "" for i in 0..parts.count { if i != 0 { text += ", " if i + 1 == parts.count { text += trailing + " " } } text += parts[i] } return text } def wrapWords(text string, width int) List { # An invalid length means wrapping is disabled if width < 1 { return [text] } var words = text.split(" ") var lines List = [] var line = "" # Run the word wrapping algorithm var i = 0 while i < words.count { var word = words[i] var lineLength = line.count var wordLength = word.count var estimatedLength = lineLength + 1 + wordLength i++ # Collapse adjacent spaces if word == "" { continue } # Start the line if line == "" { while word.count > width { lines.append(word.slice(0, width)) word = word.slice(width, word.count) } line = word } # Continue line else if estimatedLength < width { line += " " + word } # Continue and wrap else if estimatedLength == width { lines.append(line + " " + word) line = "" } # Wrap and try again else { lines.append(line) line = "" i-- } } # Don't add an empty trailing line unless there are no other lines if line != "" || lines.isEmpty { lines.append(line) } return lines } } # Language-specific stuff if TARGET == .JAVASCRIPT { def parseDoubleLiteral(text string) double { return +(text as dynamic) } } else if TARGET == .CSHARP { def parseDoubleLiteral(text string) double { return dynamic.double.Parse(text) } } else { @import def parseDoubleLiteral(text string) double } ================================================ FILE: src/core/symbol.sk ================================================ namespace Skew { enum SymbolKind { PARAMETER_FUNCTION PARAMETER_OBJECT OBJECT_CLASS OBJECT_ENUM OBJECT_FLAGS OBJECT_GLOBAL OBJECT_INTERFACE OBJECT_NAMESPACE OBJECT_WRAPPED FUNCTION_ANNOTATION FUNCTION_CONSTRUCTOR FUNCTION_GLOBAL FUNCTION_INSTANCE FUNCTION_LOCAL OVERLOADED_ANNOTATION OVERLOADED_GLOBAL OVERLOADED_INSTANCE VARIABLE_ARGUMENT VARIABLE_ENUM_OR_FLAGS VARIABLE_GLOBAL VARIABLE_INSTANCE VARIABLE_LOCAL def isType bool { return self >= PARAMETER_FUNCTION && self <= OBJECT_WRAPPED } def isParameter bool { return self >= PARAMETER_FUNCTION && self <= PARAMETER_OBJECT } def isObject bool { return self >= OBJECT_CLASS && self <= OBJECT_WRAPPED } def isEnumOrFlags bool { return self == OBJECT_ENUM || self == OBJECT_FLAGS } def isFunction bool { return self >= FUNCTION_ANNOTATION && self <= FUNCTION_LOCAL } def isOverloadedFunction bool { return self >= OVERLOADED_ANNOTATION && self <= OVERLOADED_INSTANCE } def isFunctionOrOverloadedFunction bool { return self >= FUNCTION_ANNOTATION && self <= OVERLOADED_INSTANCE } def isVariable bool { return self >= VARIABLE_ARGUMENT && self <= VARIABLE_LOCAL } def isLocalOrArgumentVariable bool { return self == VARIABLE_ARGUMENT || self == VARIABLE_LOCAL } def isNamespaceOrGlobal bool { return self == OBJECT_NAMESPACE || self == OBJECT_GLOBAL } def isGlobalReference bool { return self == VARIABLE_ENUM_OR_FLAGS || self == VARIABLE_GLOBAL || self == FUNCTION_GLOBAL || self == FUNCTION_CONSTRUCTOR || self == OVERLOADED_GLOBAL || isType } def hasInstances bool { return self == OBJECT_CLASS || self == OBJECT_ENUM || self == OBJECT_FLAGS || self == OBJECT_INTERFACE || self == OBJECT_WRAPPED } def isOnInstances bool { return self == FUNCTION_INSTANCE || self == VARIABLE_INSTANCE || self == OVERLOADED_INSTANCE } def isLocal bool { return self == FUNCTION_LOCAL || self == VARIABLE_LOCAL || self == VARIABLE_ARGUMENT } } enum SymbolState { UNINITIALIZED INITIALIZING INITIALIZED } flags SymbolFlags { # Internal IS_AUTOMATICALLY_GENERATED IS_CONST IS_GETTER IS_LOOP_VARIABLE IS_OVER IS_SETTER IS_VALUE_TYPE SHOULD_INFER_RETURN_TYPE # Modifiers IS_DEPRECATED IS_ENTRY_POINT IS_EXPORTED IS_IMPORTED IS_INLINING_FORCED IS_INLINING_PREVENTED IS_PREFERRED IS_PROTECTED IS_RENAMED IS_SKIPPED SHOULD_SPREAD # Pass-specific IS_CSHARP_CONST IS_DYNAMIC_LAMBDA IS_GUARD_CONDITIONAL IS_OBSOLETE IS_PRIMARY_CONSTRUCTOR IS_VIRTUAL USE_PROTOTYPE_CACHE } class Symbol { const id = _createID var kind SymbolKind var name string var rename string = null var range Range = null # The location of the name in the source code var parent Symbol = null # Automatically set by the merging step var resolvedType Type = null # Automatically set by the resolving step var scope Scope = null # Automatically set by the merging step (resolving step for local variables) var state SymbolState = .UNINITIALIZED var annotations List = null var comments List = null var forwardTo Symbol = null # Set by the interface removal step var flags SymbolFlags = 0 var nextMergedSymbol Symbol = null # This allows traversal of all declarations for IDE tooltips def _cloneFrom(symbol Symbol) { rename = symbol.rename range = symbol.range scope = symbol.scope state = symbol.state flags = symbol.flags } # Flags def isAutomaticallyGenerated bool { return .IS_AUTOMATICALLY_GENERATED in flags } def isConst bool { return .IS_CONST in flags } def isGetter bool { return .IS_GETTER in flags } def isLoopVariable bool { return .IS_LOOP_VARIABLE in flags } def isOver bool { return .IS_OVER in flags } def isSetter bool { return .IS_SETTER in flags } def isValueType bool { return .IS_VALUE_TYPE in flags } def shouldInferReturnType bool { return .SHOULD_INFER_RETURN_TYPE in flags } # Modifiers def isDeprecated bool { return .IS_DEPRECATED in flags } def isEntryPoint bool { return .IS_ENTRY_POINT in flags } def isExported bool { return .IS_EXPORTED in flags } def isImported bool { return .IS_IMPORTED in flags } def isInliningForced bool { return .IS_INLINING_FORCED in flags } def isInliningPrevented bool { return .IS_INLINING_PREVENTED in flags } def isPreferred bool { return .IS_PREFERRED in flags } def isProtected bool { return .IS_PROTECTED in flags } def isRenamed bool { return .IS_RENAMED in flags } def isSkipped bool { return .IS_SKIPPED in flags } def shouldSpread bool { return .SHOULD_SPREAD in flags } # Pass-specific flags def isCSharpConst bool { return .IS_CSHARP_CONST in flags } def isDynamicLambda bool { return .IS_DYNAMIC_LAMBDA in flags } def isGuardConditional bool { return .IS_GUARD_CONDITIONAL in flags } def isObsolete bool { return .IS_OBSOLETE in flags } def isPrimaryConstructor bool { return .IS_PRIMARY_CONSTRUCTOR in flags } def isVirtual bool { return .IS_VIRTUAL in flags } def usePrototypeCache bool { return .USE_PROTOTYPE_CACHE in flags } # Combinations def isImportedOrExported bool { return (.IS_IMPORTED | .IS_EXPORTED) in flags } def asParameterSymbol ParameterSymbol { assert(kind.isParameter) return self as ParameterSymbol } def asObjectSymbol ObjectSymbol { assert(kind.isObject) return self as ObjectSymbol } def asFunctionSymbol FunctionSymbol { assert(kind.isFunction) return self as FunctionSymbol } def asOverloadedFunctionSymbol OverloadedFunctionSymbol { assert(kind.isOverloadedFunction) return self as OverloadedFunctionSymbol } def asVariableSymbol VariableSymbol { assert(kind.isVariable) return self as VariableSymbol } def fullName string { if parent != null && parent.kind != .OBJECT_GLOBAL && !kind.isParameter { return parent.fullName + "." + name } return name } def forwarded Symbol { var symbol = self while symbol.forwardTo != null { symbol = symbol.forwardTo } return symbol } def spreadingAnnotations List { var result List = null if annotations != null { for annotation in annotations { if annotation.symbol != null && annotation.symbol.shouldSpread { result ?= [] result.append(annotation) } } } return result } def mergeInformationFrom(symbol Symbol) { # Link merged symbols together var link = self while link.nextMergedSymbol != null { link = link.nextMergedSymbol } link.nextMergedSymbol = symbol # Combine annotations if annotations == null { annotations = symbol.annotations } else if symbol.annotations != null { annotations.append(symbol.annotations) } # Combine comments if comments == null { comments = symbol.comments } else if symbol.comments != null { comments.append(symbol.comments) } rename ?= symbol.rename } } namespace Symbol { var _nextID = 0 const SORT_BY_ID = (a Symbol, b Symbol) => a.id <=> b.id const SORT_OBJECTS_BY_ID = (a ObjectSymbol, b ObjectSymbol) => a.id <=> b.id const SORT_VARIABLES_BY_ID = (a VariableSymbol, b VariableSymbol) => a.id <=> b.id const SORT_BY_NAME = (a Symbol, b Symbol) int => { var an = a.name var bn = b.name var ac = an.count var bc = bn.count for i = 0, c = Math.max(ac, bc); i < c; i++ { var ai = i < ac ? an[i] : -1 var bi = i < bc ? bn[i] : -1 if ai < bi { return -1 } if ai > bi { return 1 } } return a.id <=> b.id } def _createID int { return ++_nextID } def _substituteSymbols(node Node, symbols IntMap) { if node.symbol != null { node.symbol = symbols.get(node.symbol.id, node.symbol) } for child = node.firstChild; child != null; child = child.nextSibling { _substituteSymbols(child, symbols) } } } class ParameterSymbol : Symbol { def clone ParameterSymbol { var clone = new(kind, name) clone._cloneFrom(self) clone.resolvedType = Type.new(.SYMBOL, clone) return clone } } class Guard { var parent ObjectSymbol var test Node var contents ObjectSymbol var elseGuard Guard } class ObjectSymbol : Symbol { var extends Node = null var implements List = null var baseType Type = null var baseClass ObjectSymbol = null var interfaceTypes List = null var wrappedType Type = null var members StringMap = {} # Automatically expanded to include members from the base class by the resolving step var objects List = [] var functions List = [] var variables List = [] var parameters List = null var guards List = null # Compile-time if statements var hasCheckedInterfacesAndAbstractStatus = false var isAbstractBecauseOf FunctionSymbol = null # Used for diagnostics var commentsInsideEndOfBlock List = null # These have nowhere else to go right now def isAbstract bool { return isAbstractBecauseOf != null } def hasBaseClass(symbol Symbol) bool { return baseClass != null && (baseClass == symbol || baseClass.hasBaseClass(symbol)) } def hasInterface(symbol Symbol) bool { return interfaceTypes != null && interfaceTypes.any(type => type.symbol == symbol) } def isSameOrHasBaseClass(symbol Symbol) bool { return self == symbol || hasBaseClass(symbol) } } class FunctionSymbol : Symbol { var overridden FunctionSymbol = null # For derived class functions var overloaded OverloadedFunctionSymbol = null # Links overloaded functions to the other overloads on that type (not on derived types) var implementations List = null # For interface functions var parameters List = null var arguments List = [] var this VariableSymbol = null # For instance functions and constructors var argumentOnlyType Type = null # For quickly comparing the argument types of two function symbols var returnType Node = null var block Node = null var namingGroup = -1 # Automatically filled out by the renaming step var inlinedCount = 0 def clone FunctionSymbol { var clone = new(kind, name) var symbols IntMap = {} clone._cloneFrom(self) if state == .INITIALIZED { clone.resolvedType = Type.new(.SYMBOL, clone) clone.resolvedType.returnType = resolvedType.returnType clone.resolvedType.argumentTypes = resolvedType.argumentTypes.clone clone.argumentOnlyType = argumentOnlyType } if parameters != null { clone.parameters = [] for parameter in parameters { var cloned = parameter.clone symbols[parameter.id] = cloned clone.parameters.append(cloned) } } for argument in arguments { var cloned = argument.clone symbols[argument.id] = cloned clone.arguments.append(cloned) } if returnType != null { clone.returnType = returnType.clone } if block != null { clone.block = block.clone Symbol._substituteSymbols(clone.block, symbols) } return clone } } class VariableSymbol : Symbol { var type Node = null var value Node = null def clone VariableSymbol { var clone = new(kind, name) clone._cloneFrom(self) clone.resolvedType = resolvedType if type != null { clone.type = type.clone } if value != null { clone.value = value.clone Symbol._substituteSymbols(clone.value, {id: clone}) } return clone } def initializeWithType(target Type) { assert(state == .UNINITIALIZED) assert(type == null) assert(resolvedType == null) state = .INITIALIZED resolvedType = target type = Node.createType(target) } } class OverloadedFunctionSymbol : Symbol { var symbols List } enum FuzzySymbolKind { EVERYTHING TYPE_ONLY GLOBAL_ONLY INSTANCE_ONLY } class FuzzySymbolMatcher { var _name string var _kind FuzzySymbolKind var _bestScore double var _bestMatch Symbol def new(name string, kind FuzzySymbolKind) { _name = name _kind = kind _bestScore = name.count * 0.5 _bestMatch = null } def _isBetterScore(score double, match Symbol) bool { if score < _bestScore { return true } # Do tie-breaking using a consistent ordering so that language targets # with unordered maps (C++ for example) can iterate over symbols in an # unspecified order for speed and still deterministically arrive at the # same result. if score == _bestScore && (_bestMatch == null || match.id < _bestMatch.id) { return true } return false } def include(match Symbol) { if _kind == .INSTANCE_ONLY && !match.kind.isOnInstances || _kind == .GLOBAL_ONLY && match.kind.isOnInstances || _kind == .TYPE_ONLY && !match.kind.isType || match.state == .INITIALIZING { return } var score = caseAwareLevenshteinEditDistance(_name, match.name) if score <= match.name.count * 0.5 && _isBetterScore(score, match) { _bestScore = score _bestMatch = match } } def bestSoFar Symbol { return _bestMatch } } } ================================================ FILE: src/cpp/fast.cpp ================================================ #include #if _WIN32 #include #else #include #endif static void *__fast_next; static size_t __fast_available; static void *__fast_allocate(size_t size) { enum { CHUNK_SIZE = 1 << 20, ALIGN = 8, }; // Always allocate a multiple of the alignment size size = (size + ALIGN - 1) & ~(ALIGN - 1); // Grow if needed if (__fast_available < size) { size_t chunk = (size + CHUNK_SIZE - 1) & ~(CHUNK_SIZE - 1); assert(size <= chunk); // Ignore the remaining memory in the old chunk and grab a new chunk instead #if _WIN32 __fast_next = VirtualAlloc(nullptr, chunk, MEM_COMMIT, PAGE_READWRITE); assert(__fast_next != nullptr); #else __fast_next = mmap(nullptr, chunk, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); assert(__fast_next != MAP_FAILED); #endif __fast_available = chunk; } // Just use a simple bump allocator void *data = __fast_next; __fast_next = (char *)__fast_next + size; __fast_available -= size; return data; } void *operator new (size_t size) { return __fast_allocate(size); } void *operator new [] (size_t size) { return __fast_allocate(size); } void operator delete (void *data) throw() {} void operator delete [] (void *data) throw() {} // Overriding malloc() and free() is really hard on Windows for some reason #if !_WIN32 extern "C" void *malloc(size_t size) { return __fast_allocate(size); } extern "C" void free(void *data) {} #endif ================================================ FILE: src/cpp/skew.cpp ================================================ #include #include #if _WIN32 #include #undef max #undef min #else #include #endif //////////////////////////////////////////////////////////////////////////////// Skew::string::string() : _isNull(true) { } Skew::string::string(const char *x) : _data(x ? x : ""), _isNull(!x) { } Skew::string::string(const char *x, int count) : _data(x, x + count), _isNull(false) { } Skew::string::string(const std::string &x) : _data(x), _isNull(false) { } bool Skew::string::operator == (const string &x) const { return _isNull == x._isNull && _data == x._data; } bool Skew::string::operator != (const string &x) const { return _isNull != x._isNull || _data != x._data; } const char *Skew::string::c_str() const { return _isNull ? nullptr : _data.c_str(); } const std::string &Skew::string::std_str() const { return _data; } Skew::string Skew::string::operator + (const string &x) const { assert(!_isNull); assert(!x._isNull); return _data + x._data; } Skew::string &Skew::string::operator += (const string &x) { assert(!_isNull); assert(!x._isNull); _data += x._data; return *this; } int Skew::string::compare(const string &x) const { assert(!_isNull); assert(!x._isNull); return (_data > x._data) - (_data < x._data); } int Skew::string::count() const { return (int)_data.size(); } bool Skew::string::contains(const string &x) const { assert(!_isNull); assert(!x._isNull); return _data.find(x._data) != std::string::npos; } int Skew::string::indexOf(const string &x) const { assert(!_isNull); assert(!x._isNull); auto it = _data.find(x._data); return it != std::string::npos ? (int)it : -1; } int Skew::string::lastIndexOf(const string &x) const { assert(!_isNull); assert(!x._isNull); auto it = _data.rfind(x._data); return it != std::string::npos ? (int)it : -1; } bool Skew::string::startsWith(const string &x) const { assert(!_isNull); assert(!x._isNull); return _data.size() >= x._data.size() && !memcmp(_data.data(), x._data.data(), x._data.size()); } bool Skew::string::endsWith(const string &x) const { assert(!_isNull); assert(!x._isNull); return _data.size() >= x._data.size() && !memcmp(_data.data() + _data.size() - x._data.size(), x._data.data(), x._data.size()); } int Skew::string::operator [] (int x) const { assert(0 <= x && x < count()); return (int)(unsigned char)_data[x]; // Code units should not be negative } Skew::string Skew::string::get(int x) const { assert(0 <= x && x < count()); return std::string(1, _data[x]); } Skew::string Skew::string::slice(int start) const { assert(0 <= start && start <= count()); return _data.substr(start); } Skew::string Skew::string::slice(int start, int end) const { assert(0 <= start && start <= end && end <= count()); return _data.substr(start, end - start); } Skew::List *Skew::string::codeUnits() const { auto result = new List; for (unsigned char x : _data) { result->append(x); } return result; } Skew::List *Skew::string::split(const string &x) const { assert(!_isNull); assert(!x._isNull); auto result = new List; size_t start = 0; while (true) { auto it = _data.find(x._data, start); if (it == std::string::npos) { break; } result->append(_data.substr(start, it - start)); start = it + x._data.size(); } result->append(_data.substr(start)); return result; } Skew::string Skew::string::join(const List *x) const { assert(!_isNull); std::string result(""); for (auto b = x->begin(), e = x->end(), it = b; it != e; it++) { if (it != b) { result += _data; } assert(!it->_isNull); result += it->_data; } return result; } Skew::string Skew::string::repeat(int x) const { assert(x >= 0); std::string result(""); result.reserve(_data.size() * x); while (x-- > 0) { result += _data; } return result; } Skew::string Skew::string::replaceAll(const string &before, const string &after) const { assert(!_isNull); assert(!before._isNull); assert(!after._isNull); string result(""); size_t start = 0; while (true) { auto it = _data.find(before._data, start); if (it == std::string::npos) { break; } result._data += _data.substr(start, it - start); result._data += after._data; start = it + before._data.size(); } result._data += _data.substr(start); return result; } Skew::string Skew::string::toLowerCase() const { auto result = _data; std::transform(_data.begin(), _data.end(), result.begin(), ::tolower); return result; } Skew::string Skew::string::toUpperCase() const { auto result = _data; std::transform(_data.begin(), _data.end(), result.begin(), ::toupper); return result; } Skew::string Skew::string::fromCodeUnit(int x) { return std::string(1, x); } Skew::string Skew::string::fromCodeUnits(const List *x) { std::string result(""); result.reserve(x->count()); for (char y : *x) { result += y; } return result; } Skew::string operator "" _s (const char *data, size_t count) { return Skew::string(data, (int)count); } //////////////////////////////////////////////////////////////////////////////// Skew::StringBuilder::StringBuilder() { } void Skew::StringBuilder::append(const string &x) { _data += x.std_str(); } Skew::string Skew::StringBuilder::toString() const { return _data; } //////////////////////////////////////////////////////////////////////////////// double Skew::Math::abs(double x) { return ::fabs(x); } int Skew::Math::abs(int x) { return ::abs(x); } double Skew::Math::acos(double x) { return ::acos(x); } double Skew::Math::asin(double x) { return ::asin(x); } double Skew::Math::atan(double x) { return ::atan(x); } double Skew::Math::atan2(double x, double y) { return ::atan2(x, y); } double Skew::Math::sin(double x) { return ::sin(x); } double Skew::Math::cos(double x) { return ::cos(x); } double Skew::Math::tan(double x) { return ::tan(x); } double Skew::Math::floor(double x) { return ::floor(x); } double Skew::Math::ceil(double x) { return ::ceil(x); } double Skew::Math::round(double x) { return ::round(x); } double Skew::Math::exp(double x) { return ::exp(x); } double Skew::Math::log(double x) { return ::log(x); } double Skew::Math::pow(double x, double y) { return ::pow(x, y); } static uint64_t __MurmurHash3(uint64_t h) { h ^= h >> 33; h *= 0xFF51AFD7ED558CCDull; h ^= h >> 33; h *= 0xC4CEB9FE1A85EC53ull; h ^= h >> 33; return h; } // This implementation is from V8: http://v8project.blogspot.com/2015/12/theres-mathrandom-and-then-theres.html double Skew::Math::random() { static uint64_t state0; static uint64_t state1; static bool setup; if (!setup) { #ifdef _WIN32 LARGE_INTEGER counter; QueryPerformanceCounter(&counter); state0 = __MurmurHash3(counter.QuadPart); #else timeval data; gettimeofday(&data, nullptr); state0 = __MurmurHash3(((uint64_t)data.tv_sec << 32) | (uint64_t)data.tv_usec); #endif state1 = __MurmurHash3(state0); setup = true; } uint64_t s1 = state0; uint64_t s0 = state1; state0 = s0; s1 ^= s1 << 23; s1 ^= s1 >> 17; s1 ^= s0; s1 ^= s0 >> 26; state1 = s1; // Exponent for double values for [1.0 .. 2.0) static const uint64_t kExponentBits = 0x3FF0000000000000ull; static const uint64_t kMantissaMask = 0x000FFFFFFFFFFFFFull; uint64_t random = ((state0 + state1) & kMantissaMask) | kExponentBits; double result = 0; static_assert(sizeof(result) == sizeof(random), ""); memcpy(&result, &random, sizeof(result)); // Use this instead of reinterpret_cast to avoid type-punning return result - 1; } double Skew::Math::sqrt(double x) { return ::sqrt(x); } double Skew::Math::max(double x, double y) { return x > y ? x : y; } int Skew::Math::max(int x, int y) { return x > y ? x : y; } double Skew::Math::min(double x, double y) { return x < y ? x : y; } int Skew::Math::min(int x, int y) { return x < y ? x : y; } // Try shorter strings first. Good test cases: 0.1, 9.8, 0.00000000001, 1.1 - 1.0 Skew::string __doubleToString(double value) { char buffer[64]; std::snprintf(&buffer[0], sizeof(buffer), "%.15g", value); if (std::stod(&buffer[0]) != value) { std::snprintf(&buffer[0], sizeof(buffer), "%.16g", value); if (std::stod(&buffer[0]) != value) { std::snprintf(&buffer[0], sizeof(buffer), "%.17g", value); } } if (!strcmp(buffer, "-0")) { return "0"; } return buffer; } Skew::string __intToString(int x) { return std::to_string(x); } //////////////////////////////////////////////////////////////////////////////// #ifdef SKEW_GC_MARK_AND_SWEEP #include #include static std::unordered_set marked; static std::stack stack; static Skew::Object *latest; enum class Delete { NOW, LATER, }; // Skew::Internal is a friend of Skew::Object so it can access private variables namespace Skew { struct Internal { static UntypedRoot *start(); static void mark(); static void sweep(Delete mode); static Object *next(Object *object) { return object->__gc_next; } }; } #ifdef SKEW_GC_PARALLEL #include #include #include struct DeleteLater { Skew::Object *object; Skew::Object **previous; }; enum { READ, WRITE }; static int fd[2]; static pid_t childProcess; static size_t liveObjectCount; static size_t nextCollectionThreshold; static std::vector deleteLater; static Skew::Root parallelHead; static void startParallelCollection() { if (childProcess) { return; } pipe(fd); fcntl(fd[READ], F_SETFL, fcntl(fd[READ], F_GETFL) | O_NONBLOCK); // Make sure the collection is always started with the latest object as // a known object that is guaranteed not to be collected in this // collection. That way the previous pointer for every collected object // should be valid and we don't have to worry about collecting the // latest object and not knowing which previous pointer to patch up. parallelHead = new Skew::Object(); // Child process if (!(childProcess = fork())) { close(fd[READ]); Skew::Internal::mark(); Skew::Internal::sweep(Delete::LATER); write(fd[WRITE], deleteLater.data(), deleteLater.size() * sizeof(DeleteLater)); close(fd[WRITE]); _exit(0); } // Parent process close(fd[WRITE]); } enum class Check { DO_NOT_BLOCK, BLOCK_UNTIL_DONE, }; static void checkParallelCollection(Check mode) { static uint8_t *buffer[1 << 16]; static size_t offset; ssize_t count; if (!childProcess) { return; } if (mode == Check::BLOCK_UNTIL_DONE) { fcntl(fd[READ], F_SETFL, fcntl(fd[READ], F_GETFL) & ~O_NONBLOCK); } // Read some data while ((count = read(fd[READ], buffer + offset, sizeof(buffer) - offset)) > 0) { size_t totalSize = offset + count; size_t objectCount = totalSize / sizeof(DeleteLater); size_t usedSize = objectCount * sizeof(DeleteLater); DeleteLater *records = reinterpret_cast(buffer); // Delete all complete records we received for (size_t i = 0; i < objectCount; i++) { const DeleteLater &record = records[i]; // Skew objects form a singly-linked list. Deleting an object // involves unlinking that object from the list, which involves // changing the next pointer of the previous link to the next link. // This is easy with a doubly-linked list but with a singly-linked // list we don't have the address of the previous link. To get around // this, the collection process sends the address of the previous // link's next pointer. Every object being deleted is guaranteed to // have a previous link because of the "parallelHead" object that was // created right before the collection started. The "parallelHead" // object is guaranteed not to be collected in this collection. *record.previous = Skew::Internal::next(record.object); delete record.object; } // Preserve any remaining bytes left over offset = totalSize - usedSize; memmove(buffer, buffer + usedSize, offset); if (mode == Check::DO_NOT_BLOCK) { break; } } // Check for exit if (waitpid(childProcess, nullptr, mode == Check::BLOCK_UNTIL_DONE ? 0 : WNOHANG)) { close(fd[READ]); childProcess = 0; // Set a threshold for the next collection so the garbage collector // only kicks in when there's a decent number of objects to collect nextCollectionThreshold = liveObjectCount + 1024; } } void Skew::GC::parallelCollect() { if (liveObjectCount > nextCollectionThreshold) { startParallelCollection(); } checkParallelCollection(Check::DO_NOT_BLOCK); } #endif void Skew::GC::blockingCollect() { #ifdef SKEW_GC_PARALLEL checkParallelCollection(Check::BLOCK_UNTIL_DONE); #endif Skew::Internal::mark(); Skew::Internal::sweep(Delete::NOW); marked.clear(); } void Skew::GC::mark(Object *object) { if (object && !marked.count(object)) { marked.insert(object); stack.push(object); } } Skew::UntypedRoot::UntypedRoot(Object *object) : _previous(Internal::start()), _next(_previous->_next), _object(object) { _previous->_next = this; _next->_previous = this; } Skew::UntypedRoot::~UntypedRoot() { _previous->_next = _next; _next->_previous = _previous; } Skew::Object::Object() : __gc_next(latest) { latest = this; #ifdef SKEW_GC_PARALLEL liveObjectCount++; #endif } Skew::Object::~Object() { #ifdef SKEW_GC_PARALLEL liveObjectCount--; #endif } // The first root is the start of a doubly-linked list of roots. It's returned as // a static local variable to avoid trouble from C++ initialization order. Roots // are global variables and initialization order of global variables is undefined. Skew::UntypedRoot *Skew::Internal::start() { static UntypedRoot start; return &start; } // Marking must be done with an explicit stack to avoid call stack overflow void Skew::Internal::mark() { for (auto end = start(), root = end->_next; root != end; root = root->_next) { GC::mark(root->_object); } while (!stack.empty()) { auto object = stack.top(); stack.pop(); object->__gc_mark(); } } // Sweeping removes unmarked objects from the linked list and deletes them void Skew::Internal::sweep(Delete mode) { for (Object *previous = nullptr, *current = latest, *next; current; current = next) { next = current->__gc_next; if (!marked.count(current)) { switch (mode) { case Delete::NOW: { (previous ? previous->__gc_next : latest) = next; delete current; break; } case Delete::LATER: { #ifdef SKEW_GC_PARALLEL deleteLater.push_back({current, previous ? &previous->__gc_next : nullptr}); #endif break; } } } else { previous = current; } } } #endif ================================================ FILE: src/cpp/skew.h ================================================ #ifdef SKEW_GC_MARK_AND_SWEEP #include namespace Skew { struct Internal; struct Object { Object(); // Adds this object to the global linked list of objects virtual ~Object(); protected: virtual void __gc_mark() {} // Recursively marks all child objects private: friend Internal; Object *__gc_next = nullptr; // GC space overhead is one pointer per object Object(const Object &); // Prevent copying Object &operator = (const Object &); // Prevent copying }; struct UntypedRoot { ~UntypedRoot(); protected: friend Internal; UntypedRoot &operator = (const UntypedRoot &root) { _object = root._object; return *this; } UntypedRoot(const UntypedRoot &root) : UntypedRoot(root._object) {} UntypedRoot() : _previous(this), _next(this), _object() {} // Only used for the first root UntypedRoot(Object *object); // Adds this root to the circular doubly-linked list of roots UntypedRoot *_previous; UntypedRoot *_next; Object *_object; }; template struct Root : UntypedRoot { Root(T *object = nullptr) : UntypedRoot(object) {} T *get() const { return dynamic_cast(_object); } operator T * () const { return dynamic_cast(_object); } T *operator -> () const { return dynamic_cast(_object); } Root &operator = (T *value) { _object = value; return *this; } }; template using VoidIfNotObject = typename std::enable_if::type>::value, void>::type; namespace GC { void blockingCollect(); void mark(Object *object); #ifdef SKEW_GC_PARALLEL void parallelCollect(); #endif template inline VoidIfNotObject mark(const T &value) {} // Don't mark anything that's not an object } } #else namespace Skew { struct Object { virtual ~Object() {} }; template struct Root { Root(T *object = nullptr) : _object(object) {} T *get() const { return _object; } operator T * () const { return _object; } T *operator -> () const { return _object; } Root &operator = (T *value) { _object = value; return *this; } private: T *_object; }; } #endif #include #include #include #include #include #include #include #include //////////////////////////////////////////////////////////////////////////////// namespace Skew { struct FnVoid0 : virtual Skew::Object { virtual void run() = 0; }; template struct Fn0 : virtual Skew::Object { virtual R run() = 0; }; template struct FnVoid1 : virtual Skew::Object { virtual void run(A1 a1) = 0; }; template struct Fn1 : virtual Skew::Object { virtual R run(A1 a1) = 0; }; template struct FnVoid2 : virtual Skew::Object { virtual void run(A1 a1, A2 a2) = 0; }; template struct Fn2 : virtual Skew::Object { virtual R run(A1 a1, A2 a2) = 0; }; template struct FnVoid3 : virtual Skew::Object { virtual void run(A1 a1, A2 a2, A3 a3) = 0; }; template struct Fn3 : virtual Skew::Object { virtual R run(A1 a1, A2 a2, A3 a3) = 0; }; template struct FnVoid4 : virtual Skew::Object { virtual void run(A1 a1, A2 a2, A3 a3, A4 a4) = 0; }; template struct Fn4 : virtual Skew::Object { virtual R run(A1 a1, A2 a2, A3 a3, A4 a4) = 0; }; template struct FnVoid5 : virtual Skew::Object { virtual void run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) = 0; }; template struct Fn5 : virtual Skew::Object { virtual R run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) = 0; }; template struct FnVoid6 : virtual Skew::Object { virtual void run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) = 0; }; template struct Fn6 : virtual Skew::Object { virtual R run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) = 0; }; template struct FnVoid7 : virtual Skew::Object { virtual void run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) = 0; }; template struct Fn7 : virtual Skew::Object { virtual R run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) = 0; }; template struct FnVoid8 : virtual Skew::Object { virtual void run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) = 0; }; template struct Fn8 : virtual Skew::Object { virtual R run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) = 0; }; template struct FnVoid9 : virtual Skew::Object { virtual void run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) = 0; }; template struct Fn9 : virtual Skew::Object { virtual R run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) = 0; }; template struct FnVoid10 : virtual Skew::Object { virtual void run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) = 0; }; template struct Fn10 : virtual Skew::Object { virtual R run(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) = 0; }; } //////////////////////////////////////////////////////////////////////////////// namespace Skew { template struct List; struct string { string(); string(const char *x); string(const char *x, int count); string(const std::string &x); bool operator == (const string &x) const; bool operator != (const string &x) const; const char *c_str() const; const std::string &std_str() const; string operator + (const string &x) const; string &operator += (const string &x); int compare(const string &x) const; int count() const; bool contains(const string &x) const; int indexOf(const string &x) const; int lastIndexOf(const string &x) const; bool startsWith(const string &x) const; bool endsWith(const string &x) const; int operator [] (int x) const; string get(int x) const; string slice(int start) const; string slice(int start, int end) const; List *codeUnits() const; List *split(const string &x) const; string join(const List *x) const; string repeat(int x) const; string replaceAll(const string &before, const string &after) const; string toLowerCase() const; string toUpperCase() const; static string fromCodeUnit(int x); static string fromCodeUnits(const List *x); private: friend struct std::hash; std::string _data; bool _isNull; }; } Skew::string operator "" _s (const char *data, size_t count); namespace std { template <> struct hash { size_t operator () (const Skew::string &x) const { return hash()(x._data); } }; } //////////////////////////////////////////////////////////////////////////////// namespace Skew { struct StringBuilder : virtual Skew::Object { StringBuilder(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override {} #endif void append(const string &x); string toString() const; private: std::string _data; }; } //////////////////////////////////////////////////////////////////////////////// namespace Skew { template struct CharInsteadOfBool { using Type = T; static T &cast(T &x) { return x; } static const T &cast(const T &x) { return x; } }; template <> struct CharInsteadOfBool { static_assert(sizeof(bool) == sizeof(char), ""); using Type = char; static bool &cast(char &x) { return reinterpret_cast(x); } static const bool &cast(const char &x) { return reinterpret_cast(x); } }; template struct List : virtual Skew::Object { List(); List(const std::initializer_list &x); const T *begin() const; const T *end() const; T *begin(); T *end(); const T &operator [] (int x) const; T &operator [] (int x); int count() const; bool isEmpty() const; void append(const T &x); void append(const List *x); void appendOne(const T &x); void prepend(const T &x); void prepend(const List *x); void insert(int x, const T &value); void insert(int x, const List *values); void removeAt(int x); void removeFirst(); void removeIf(Fn1 *x); void removeLast(); void removeOne(const T &x); void removeRange(int start, int end); T takeFirst(); T takeLast(); T takeAt(int x); List *takeRange(int start, int end); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif const T &first() const; const T &last() const; T &setFirst(const T &x); T &setLast(const T &x); bool contains(const T &x) const; int indexOf(const T &x) const; int lastIndexOf(const T &x) const; bool all(Fn1 *x) const; bool any(Fn1 *x) const; List *clone() const; void each(FnVoid1 *x) const; bool equals(const List *x) const; List *filter(Fn1 *x) const; template List *map(Fn1 *x) const; void reverse(); List *slice(int start) const; List *slice(int start, int end) const; void sort(Fn2 *x); private: std::vector::Type> _data; }; } //////////////////////////////////////////////////////////////////////////////// namespace Skew { template struct StringMap : virtual Skew::Object { StringMap(); StringMap(const std::initializer_list> &x); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif const T &operator [] (const string &x) const; T &operator [] (const string &x); int count() const; bool isEmpty() const; List *keys() const; List *values() const; StringMap *clone() const; void each(FnVoid2 *x) const; T get(const string &key, const T &defaultValue) const; bool contains(const string &key) const; void remove(const string &key); private: std::unordered_map _data; }; } //////////////////////////////////////////////////////////////////////////////// namespace Skew { template struct IntMap : virtual Skew::Object { IntMap(); IntMap(const std::initializer_list> &x); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif const T &operator [] (int x) const; T &operator [] (int x); int count() const; bool isEmpty() const; List *keys() const; List *values() const; IntMap *clone() const; void each(FnVoid2 *x) const; T get(int key, const T &defaultValue) const; bool contains(int key) const; void remove(int key); private: std::unordered_map _data; }; } //////////////////////////////////////////////////////////////////////////////// namespace Skew { namespace Math { double abs(double x); int abs(int x); double acos(double x); double asin(double x); double atan(double x); double atan2(double x, double y); double sin(double x); double cos(double x); double tan(double x); double floor(double x); double ceil(double x); double round(double x); double exp(double x); double log(double x); double pow(double x, double y); double random(); double sqrt(double x); double max(double x, double y); int max(int x, int y); double min(double x, double y); int min(int x, int y); } } Skew::string __doubleToString(double x); Skew::string __intToString(int x); //////////////////////////////////////////////////////////////////////////////// template Skew::List::List() { } template Skew::List::List(const std::initializer_list &x) : _data{x} { } #ifdef SKEW_GC_MARK_AND_SWEEP template void Skew::List::__gc_mark() { for (const auto &value : _data) { Skew::GC::mark(value); } } #endif template const T *Skew::List::begin() const { return _data.data(); } template const T *Skew::List::end() const { return _data.data() + _data.size(); } template T *Skew::List::begin() { return _data.data(); } template T *Skew::List::end() { return _data.data() + _data.size(); } template const T &Skew::List::operator [] (int x) const { assert(0 <= x && x < count()); return CharInsteadOfBool::cast(_data[x]); } template T &Skew::List::operator [] (int x) { assert(0 <= x && x < count()); return CharInsteadOfBool::cast(_data[x]); } template int Skew::List::count() const { return (int)_data.size(); } template bool Skew::List::isEmpty() const { return _data.empty(); } template void Skew::List::append(const T &x) { _data.push_back(x); } template void Skew::List::append(const List *x) { assert(x != this); _data.insert(_data.end(), x->_data.begin(), x->_data.end()); } template void Skew::List::appendOne(const T &x) { auto it = std::find(_data.begin(), _data.end(), x); if (it == _data.end()) { _data.push_back(x); } } template void Skew::List::prepend(const T &x) { _data.insert(_data.begin(), x); } template void Skew::List::prepend(const List *x) { assert(x != this); _data.insert(_data.begin(), x->_data.begin(), x->_data.end()); } template void Skew::List::insert(int x, const T &value) { assert(x >= 0 && x <= count()); _data.insert(_data.begin() + x, value); } template void Skew::List::insert(int x, const List *values) { assert(x >= 0 && x <= count()); assert(values != this); _data.insert(_data.begin() + x, values->_data.begin(), values->_data.end()); } template void Skew::List::removeAt(int x) { assert(x >= 0 && x < count()); _data.erase(_data.begin() + x); } template void Skew::List::removeFirst() { assert(!isEmpty()); _data.erase(_data.begin()); } template void Skew::List::removeIf(Fn1 *x) { _data.erase(std::remove_if(_data.begin(), _data.end(), [&](const T &y) { return x->run(y); }), _data.end()); } template void Skew::List::removeLast() { assert(!isEmpty()); _data.pop_back(); } template void Skew::List::removeOne(const T &x) { auto it = std::find(_data.begin(), _data.end(), x); if (it != _data.end()) { _data.erase(it); } } template void Skew::List::removeRange(int start, int end) { assert(0 <= start && start <= end && end <= count()); _data.erase(_data.begin() + start, _data.begin() + end); } template T Skew::List::takeFirst() { assert(!isEmpty()); T result = std::move(_data.front()); _data.erase(_data.begin()); return result; } template T Skew::List::takeLast() { assert(!isEmpty()); T result = std::move(_data.back()); _data.pop_back(); return result; } template T Skew::List::takeAt(int x) { assert(0 <= x && x < count()); T result = std::move(_data[x]); _data.erase(_data.begin() + x); return result; } template Skew::List *Skew::List::takeRange(int start, int end) { assert(0 <= start && start <= end && end <= count()); auto result = new List; result->_data.reserve(end - start); for (int i = start; i < end; i++) { result->_data.emplace_back(std::move(_data[i])); } _data.erase(_data.begin() + start, _data.begin() + end); return result; } template const T &Skew::List::first() const { assert(!isEmpty()); return CharInsteadOfBool::cast(_data.front()); } template const T &Skew::List::last() const { assert(!isEmpty()); return CharInsteadOfBool::cast(_data.back()); } template T &Skew::List::setFirst(const T &x) { assert(!isEmpty()); return CharInsteadOfBool::cast(_data.front()) = x; } template T &Skew::List::setLast(const T &x) { assert(!isEmpty()); return CharInsteadOfBool::cast(_data.back()) = x; } template bool Skew::List::contains(const T &x) const { return std::find(begin(), end(), x) != end(); } template int Skew::List::indexOf(const T &x) const { auto it = std::find(begin(), end(), x); return it == end() ? -1 : (int)(it - begin()); } template int Skew::List::lastIndexOf(const T &x) const { auto it = std::find(_data.rbegin(), _data.rend(), x); return it == _data.rend() ? -1 : count() - 1 - (int)(it - _data.rbegin()); } template bool Skew::List::all(Fn1 *x) const { for (const auto &it : _data) { if (!x->run(it)) { return false; } } return true; } template bool Skew::List::any(Fn1 *x) const { for (const auto &it : _data) { if (x->run(it)) { return true; } } return false; } template Skew::List *Skew::List::clone() const { auto result = new List; result->_data = _data; return result; } template void Skew::List::each(FnVoid1 *x) const { for (const auto &it : _data) { x->run(it); } } template bool Skew::List::equals(const List *x) const { if (count() != x->count()) { return false; } for (int i = count() - 1; i >= 0; i--) { if ((*this)[i] != (*x)[i]) { return false; } } return true; } template Skew::List *Skew::List::filter(Fn1 *x) const { auto result = new List; for (const auto &it : _data) { if (x->run(it)) { result->append(it); } } return result; } template template Skew::List *Skew::List::map(Fn1 *x) const { auto result = new List; for (const auto &it : _data) { result->append(x->run(it)); } return result; } template void Skew::List::reverse() { std::reverse(begin(), end()); } template Skew::List *Skew::List::slice(int start) const { assert(0 <= start && start <= count()); auto result = new List; result->_data.insert(result->_data.begin(), _data.begin() + start, _data.end()); return result; } template Skew::List *Skew::List::slice(int start, int end) const { assert(0 <= start && start <= end && end <= count()); auto result = new List; result->_data.insert(result->_data.begin(), _data.begin() + start, _data.begin() + end); return result; } template void Skew::List::sort(Fn2 *x) { std::sort(_data.begin(), _data.end(), [&x](const T &a, const T &b) { return x->run(a, b) < 0; }); } //////////////////////////////////////////////////////////////////////////////// template Skew::StringMap::StringMap() { } template Skew::StringMap::StringMap(const std::initializer_list> &x) { _data.reserve(x.size()); for (const auto &it : x) { _data.insert(it); } } #ifdef SKEW_GC_MARK_AND_SWEEP template void Skew::StringMap::__gc_mark() { for (const auto &value : _data) { Skew::GC::mark(value.second); } } #endif template const T &Skew::StringMap::operator [] (const string &x) const { assert(contains(x)); return _data[x]; } template T &Skew::StringMap::operator [] (const string &x) { return _data[x]; } template int Skew::StringMap::count() const { return (int)_data.size(); } template bool Skew::StringMap::isEmpty() const { return _data.empty(); } template Skew::List *Skew::StringMap::keys() const { auto result = new List; for (const auto &it : _data) { result->append(it.first); } return result; } template Skew::List *Skew::StringMap::values() const { auto result = new List; for (const auto &it : _data) { result->append(it.second); } return result; } template Skew::StringMap *Skew::StringMap::clone() const { auto result = new StringMap; result->_data = _data; return result; } template void Skew::StringMap::each(FnVoid2 *x) const { for (const auto &it : _data) { x->run(it.first, it.second); } } template T Skew::StringMap::get(const string &key, const T &defaultValue) const { auto it = _data.find(key); return it != _data.end() ? it->second : defaultValue; } template bool Skew::StringMap::contains(const string &key) const { return _data.count(key); } template void Skew::StringMap::remove(const string &key) { _data.erase(key); } //////////////////////////////////////////////////////////////////////////////// template Skew::IntMap::IntMap() { } template Skew::IntMap::IntMap(const std::initializer_list> &x) { _data.reserve(x.size()); for (const auto &it : x) { _data.insert(it); } } #ifdef SKEW_GC_MARK_AND_SWEEP template void Skew::IntMap::__gc_mark() { for (const auto &value : _data) { Skew::GC::mark(value.second); } } #endif template const T &Skew::IntMap::operator [] (int x) const { assert(contains(x)); return _data[x]; } template T &Skew::IntMap::operator [] (int x) { return _data[x]; } template int Skew::IntMap::count() const { return (int)_data.size(); } template bool Skew::IntMap::isEmpty() const { return _data.empty(); } template Skew::List *Skew::IntMap::keys() const { auto result = new List; for (const auto &it : _data) { result->append(it.first); } return result; } template Skew::List *Skew::IntMap::values() const { auto result = new List; for (const auto &it : _data) { result->append(it.second); } return result; } template Skew::IntMap *Skew::IntMap::clone() const { auto result = new IntMap; result->_data = _data; return result; } template void Skew::IntMap::each(FnVoid2 *x) const { for (const auto &it : _data) { x->run(it.first, it.second); } } template T Skew::IntMap::get(int key, const T &defaultValue) const { auto it = _data.find(key); return it != _data.end() ? it->second : defaultValue; } template bool Skew::IntMap::contains(int key) const { return _data.count(key); } template void Skew::IntMap::remove(int key) { _data.erase(key); } ================================================ FILE: src/cpp/support.cpp ================================================ #include #include #include #ifdef _WIN32 #include #else #include #include #include #include #include #endif //////////////////////////////////////////////////////////////////////////////// Skew::string IO::readFile(const Skew::string &path) { std::ifstream file(path.c_str()); if (!file) return Skew::string(); std::string contents((std::istreambuf_iterator(file)), std::istreambuf_iterator()); return Skew::string(contents).replaceAll("\r\n", "\n"); } bool IO::writeFile(const Skew::string &path, const Skew::string &contents) { std::ofstream file(path.c_str()); if (!file) return false; file << contents.c_str(); return true; } bool IO::isDirectory(const Skew::string &path) { #ifdef _WIN32 auto attributes = GetFileAttributesA(path.c_str()); return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; #else struct stat info; return stat(path.c_str(), &info) == 0 && info.st_mode & S_IFDIR; #endif } Skew::List *IO::readDirectory(const Skew::string &path) { #ifdef _WIN32 WIN32_FIND_DATA data; auto handle = FindFirstFileA(path.c_str(), &data); if (handle != INVALID_HANDLE_VALUE) { auto entries = new Skew::List(); do { entries->append(data.cFileName); } while (FindNextFile(handle, &data)); FindClose(handle); return entries; } #else if (auto dir = opendir(path.c_str())) { auto entries = new Skew::List(); while (auto entry = readdir(dir)) { entries->append(entry->d_name); } return entries; } #endif return nullptr; } //////////////////////////////////////////////////////////////////////////////// struct __TerminalInfo { int width; int height; bool isValid; #ifdef _WIN32 HANDLE handle = INVALID_HANDLE_VALUE; CONSOLE_SCREEN_BUFFER_INFO buffer; #else bool isTTY; #endif }; static __TerminalInfo &__getTerminalInfo() { static __TerminalInfo info; if (!info.isValid) { #ifdef _WIN32 info.handle = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(info.handle, &info.buffer); info.width = info.buffer.dwSize.X; info.height = info.buffer.dwSize.Y; #else winsize size; if (!ioctl(2, TIOCGWINSZ, &size)) { info.width = size.ws_col; info.height = size.ws_row; } info.isTTY = isatty(STDOUT_FILENO); #endif info.isValid = true; } return info; } void Terminal::_setColor(int escapeCode) { #ifdef _WIN32 auto &info = __getTerminalInfo(); int attributes = info.buffer.wAttributes; switch (escapeCode) { case 1: attributes |= FOREGROUND_INTENSITY; break; case 90: attributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break; case 91: attributes = FOREGROUND_RED | FOREGROUND_INTENSITY; break; case 92: attributes = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; case 93: attributes = FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; case 94: attributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; case 95: attributes = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; case 96: attributes = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; } SetConsoleTextAttribute(info.handle, attributes); #else if (__getTerminalInfo().isTTY) { std::cout << "\x1B[0;" << escapeCode << 'm'; } #endif } int Terminal::width() { return __getTerminalInfo().width; } int Terminal::height() { return __getTerminalInfo().height; } void Terminal::print(const Skew::string &text) { static Skew::string newline("\n"); write(text); write(newline); } void Terminal::flush() { #ifndef _WIN32 std::cout.flush(); #endif } void Terminal::write(const Skew::string &text) { #ifdef _WIN32 auto converted = text.replaceAll("\n", "\r\n"); // Use WriteConsoleA() instead of std::cout for a huge performance boost WriteConsoleA(__getTerminalInfo().handle, converted.c_str(), converted.count(), nullptr, nullptr); #else std::cout << text.c_str(); #endif } //////////////////////////////////////////////////////////////////////////////// double Timestamp::seconds() { #ifdef _WIN32 static LARGE_INTEGER frequency; LARGE_INTEGER counter; if (!frequency.QuadPart) QueryPerformanceFrequency(&frequency); QueryPerformanceCounter(&counter); return counter.QuadPart / (double)frequency.QuadPart; #else timeval data; gettimeofday(&data, nullptr); return data.tv_sec + data.tv_usec / 1.0e6; #endif } //////////////////////////////////////////////////////////////////////////////// double parseDoubleLiteral(const Skew::string &x) { double y = NAN; std::stringstream(x.c_str()) >> y; return y; } ================================================ FILE: src/cpp/support.h ================================================ namespace Skew { struct string; template struct List; } namespace IO { Skew::string readFile(const Skew::string &path); bool writeFile(const Skew::string &path, const Skew::string &contents); bool isDirectory(const Skew::string &path); Skew::List *readDirectory(const Skew::string &path); } namespace Terminal { void _setColor(int escapeCode); int width(); int height(); void print(const Skew::string &text); void flush(); void write(const Skew::string &text); } namespace Timestamp { double seconds(); } double parseDoubleLiteral(const Skew::string &x); ================================================ FILE: src/driver/jsapi.d.ts ================================================ declare module "skew" { export type SymbolKind = | "PARAMETER_FUNCTION" | "PARAMETER_OBJECT" | "OBJECT_CLASS" | "OBJECT_ENUM" | "OBJECT_FLAGS" | "OBJECT_GLOBAL" | "OBJECT_INTERFACE" | "OBJECT_NAMESPACE" | "OBJECT_WRAPPED" | "FUNCTION_ANNOTATION" | "FUNCTION_CONSTRUCTOR" | "FUNCTION_GLOBAL" | "FUNCTION_INSTANCE" | "FUNCTION_LOCAL" | "OVERLOADED_ANNOTATION" | "OVERLOADED_GLOBAL" | "OVERLOADED_INSTANCE" | "VARIABLE_ARGUMENT" | "VARIABLE_ENUM_OR_FLAGS" | "VARIABLE_GLOBAL" | "VARIABLE_INSTANCE" | "VARIABLE_LOCAL" export interface Source { name: string contents: string } export interface Range { source: string start: Location end: Location } export interface Location { line: number column: number } export interface CompilerOptions { release?: boolean foldAllConstants?: boolean globalizeAllFunctions?: boolean inlineAllFunctions?: boolean jsMangle?: boolean jsMinify?: boolean jsSourceMap?: boolean outputDirectory?: string outputFile?: string stopAfterResolve?: boolean defines?: { readonly [name: string]: string } target: "c#" | "ts" | "c++" | "js" | "lisp-tree" inputs: readonly Source[] } //////////////////////////////////////////////////////////////////////////////// export interface CompileReq extends CompilerOptions { type?: "compile" id?: any } export interface CompileRes { type: "compile" id: any outputs: Source[] log: Log } export interface Log { text: string diagnostics: Diagnostic[] } export interface Diagnostic { kind: "error" | "warning" range: Range text: string fixes: Fix[] } export interface Fix { kind: number // Only useful for telling which fixes are related range: Range expected: string description: string replacement: string } //////////////////////////////////////////////////////////////////////////////// export interface TooltipReq { type?: "tooltip-query" id?: any source: string line: number column: number ignoreDiagnostics: boolean } export interface TooltipRes { type: "tooltip-query" id: any source: string tooltip: string | null range: Range | null symbol: string | null } //////////////////////////////////////////////////////////////////////////////// export interface DefinitionQueryReq { type?: "definition-query" id?: any source: string line: number column: number } export interface DefinitionQueryRes { type: "definition-query" id: any source: string definition: Range | null range: Range | null symbol: string | null } //////////////////////////////////////////////////////////////////////////////// export interface SymbolsQueryReq { type?: "symbols-query", id?: any source?: string fuzzyName?: string } export interface SymbolsQueryRes { type: "symbols-query", id: any symbols: Symbol[] | null } export interface Symbol { name: string kind: SymbolKind parent: number // This is an index into the symbols array or -1 fullName: string range: Range } //////////////////////////////////////////////////////////////////////////////// export interface RenameQueryReq { type?: "rename-query" id?: any source: string line: number column: number } export interface RenameQueryRes { type: "rename-query" id: any source: string ranges: Range[] | null } //////////////////////////////////////////////////////////////////////////////// export interface CompletionQueryReq extends CompilerOptions { type?: "completion-query" id?: any source: string line: number column: number } export interface CompletionQueryRes { type: "completion-query" id: any source: string range: Range | null completions: Completion[] | null } export interface Completion { name: string kind: SymbolKind type: string comments: string[] | null } //////////////////////////////////////////////////////////////////////////////// export interface SignatureQueryReq { type?: "signature-query" id?: any source: string line: number column: number } export interface SignatureQueryRes { type: "signature-query" id: any source: string signature: string | null arguments: string[] | null argumentIndex: number } //////////////////////////////////////////////////////////////////////////////// export interface Compiler { compile(req: CompileReq): CompileRes tooltipQuery(req: TooltipReq): TooltipRes definitionQuery(req: DefinitionQueryReq): DefinitionQueryRes symbolsQuery(req: SymbolsQueryReq): SymbolsQueryRes renameQuery(req: RenameQueryReq): RenameQueryRes completionQuery(req: CompletionQueryReq): CompletionQueryRes signatureQuery(req: SignatureQueryReq): SignatureQueryRes message(req: Req): Res } export type Req = | CompileReq | TooltipReq | DefinitionQueryReq | SymbolsQueryReq | RenameQueryReq | CompletionQueryReq | SignatureQueryReq export type Res = | CompileRes | TooltipRes | DefinitionQueryRes | SymbolsQueryRes | RenameQueryRes | CompletionQueryRes | SignatureQueryRes export const VERSION: string export function create(): Compiler } ================================================ FILE: src/driver/jsapi.sk ================================================ namespace Skew.API { def sourcesToJSON(sources List) dynamic { return sources.map(source => { return { "name": source.name, "contents": source.contents, } }) } def diagnosticsToJSON(diagnostics List) dynamic { return diagnostics.map(diagnostic => { return { "kind": diagnostic.kind.toString.toLowerCase, "range": rangeToJSON(diagnostic.range), "text": diagnostic.text, "fixes": diagnostic.fixes == null ? [] : diagnostic.fixes.map(fix => ({ "kind": fix.kind, # Only useful for telling which fixes are related "range": rangeToJSON(fix.range), "expected": fix.range.toString, "description": fix.description, "replacement": fix.replacement, })) } }) } var rangeToJSON = (range Range) dynamic => { if range == null { return null } var source = range.source var start = source.indexToLineColumn(range.start) var end = source.indexToLineColumn(range.end) return { "source": source.name, "start": { "line": start.line, "column": start.column, }, "end": { "line": end.line, "column": end.column, }, } } def parseOptions(args dynamic, inputs List) CompilerOptions { if !(args.inputs is List) { throw dynamic.Error.new("Missing the required 'inputs' array") } var options = CompilerOptions.new var release = !!args.release options.foldAllConstants = !!args.foldAllConstants || release options.globalizeAllFunctions = !!args.globalizeAllFunctions || release options.inlineAllFunctions = !!args.inlineAllFunctions || release options.jsMangle = !!args.jsMangle || release options.jsMinify = !!args.jsMinify || release options.jsSourceMap = !!args.jsSourceMap options.outputDirectory = args.outputDirectory ? args.outputDirectory + "" : null options.outputFile = args.outputFile ? args.outputFile + "" : null options.stopAfterResolve = !!args.stopAfterResolve if args.defines { var defines = args.defines for key in dynamic.Object.keys(defines) as List { options.define(key, defines[key] + "") } } if release { options.define("RELEASE", "true") } switch args.target { case "c#" { options.target = CSharpTarget.new } case "ts" { options.target = TypeScriptTarget.new } case "c++" { options.target = CPlusPlusTarget.new } case "js" { options.target = JavaScriptTarget.new } case "lisp-tree" { options.target = LispTreeTarget.new } default { if !options.createTargetFromExtension { throw dynamic.Error.new("Invalid target '\(args.target)'") } } } for i in 0..args.inputs.length { var input = args.inputs[i] inputs.append(Source.new(input.name + "", input.contents + "")) } return options } var createCompilerInstance fn() dynamic = => { var result CompilerResult = null var inputs List = null var log Log = null var handleCompile = (message dynamic) dynamic => { inputs = [] log = Log.new result = compile(log, parseOptions(message, inputs), inputs) return { "type": "compile", "id": message.id, "outputs": sourcesToJSON(result.outputs), "log": { "text": log.toString, "diagnostics": diagnosticsToJSON(log.diagnostics), }, } } var handleTooltipQuery = (message dynamic) dynamic => { var name string = message.source + "" var line int = message.line | 0 var column int = message.column | 0 var ignoreDiagnostics bool = !!message.ignoreDiagnostics var range Range = null var tooltip string = null var symbol string = null if inputs != null { for source in inputs { if source.name == name { var index = source.lineColumnToIndex(line, column) if index != -1 { # Search diagnostics first if !ignoreDiagnostics && log != null { for diagnostic in log.diagnostics { if diagnostic.range != null && diagnostic.range.source == source && diagnostic.range.touches(index) { tooltip = diagnostic.text range = diagnostic.range break } } } # Search the syntax tree next if tooltip == null && result != null && result.global != null { var query = IDE.SymbolQuery.new(source, index) query.run(result.global) if query.symbol != null { tooltip = query.generateTooltip range = query.range symbol = query.symbol.fullName } } } break } } } return { "type": "tooltip-query", "id": message.id, "source": name, "tooltip": tooltip, "range": rangeToJSON(range), "symbol": symbol, } } var handleDefinitionQuery = (message dynamic) dynamic => { var name string = message.source + "" var line int = message.line | 0 var column int = message.column | 0 var range Range = null var definition Range = null var symbol string = null if inputs != null { for source in inputs { if source.name == name { var index = source.lineColumnToIndex(line, column) if index != -1 && result != null && result.global != null { var query = IDE.SymbolQuery.new(source, index) query.run(result.global) if query.symbol != null { definition = query.symbol.range range = query.range symbol = query.symbol.fullName } } break } } } return { "type": "definition-query", "id": message.id, "source": name, "definition": rangeToJSON(definition), "range": rangeToJSON(range), "symbol": symbol, } } var handleSymbolsQuery = (message dynamic) dynamic => { var symbols List = [] # List all symbols in a given source file if message.source { var name string = message.source + "" if inputs != null { for source in inputs { if source.name == name { if result != null && result.global != null { var query = IDE.SymbolsQuery.new(source) query.run(result.global) symbols = query.symbols } break } } } } # List all symbols matching a given substring else if message.fuzzyName { var nameSubstring string = message.fuzzyName + "" nameSubstring = nameSubstring.toLowerCase if inputs != null && result != null && result.global != null { var query = IDE.SymbolsQuery.new(null) query.run(result.global) for symbol in query.symbols { if symbol.name.toLowerCase.indexOf(nameSubstring) >= 0 { symbols.append(symbol) } } } } return { "type": "symbols-query", "id": message.id, "symbols": symbols.isEmpty ? null : symbols.map(symbol => ({ "name": symbol.name, "kind": symbol.kind.toString, "parent": symbol.parent == null || symbol == result.global ? -1 : symbols.indexOf(symbol.parent), "fullName": symbol.fullName, "range": rangeToJSON(symbol.range), })), } } var handleRenameQuery = (message dynamic) dynamic => { var name string = message.source + "" var line int = message.line | 0 var column int = message.column | 0 var ranges List = null if inputs != null { for source in inputs { if source.name == name { var index = source.lineColumnToIndex(line, column) if index != -1 && result != null && result.global != null { var query = IDE.RenameQuery.new(source, index) query.run(result.global) ranges = query.ranges } break } } } return { "type": "rename-query", "id": message.id, "source": name, "ranges": ranges == null ? null : ranges.map(rangeToJSON), } } var handleCompletionQuery = (message dynamic) dynamic => { var name string = message.source + "" var line int = message.line | 0 var column int = message.column | 0 var range Range = null var completions List = null var inputs List = [] var log = Log.new var options = parseOptions(message, inputs) # Completion queries involve compiling the source code again because it requires scope information for input in inputs { if input.name == name { var index = input.lineColumnToIndex(line, column) if index != -1 { options.stopAfterResolve = true options.completionContext = CompletionContext.new(input, index) compile(log, options, inputs) range = options.completionContext.range completions = options.completionContext.completions } break } } return { "type": "completion-query", "id": message.id, "source": name, "range": rangeToJSON(range), "completions": completions == null ? null : completions.map(symbol => ({ "name": symbol.name, "kind": symbol.kind.toString, "type": IDE.completionType(symbol), "comments": symbol.comments == null ? null : symbol.comments.map(comment => "\n".join(comment.lines)), })), } } var handleSignatureQuery = (message dynamic) dynamic => { var name string = message.source + "" var line int = message.line | 0 var column int = message.column | 0 var signature string = null var arguments List = null var argumentIndex = -1 if inputs != null { for source in inputs { if source.name == name { var index = source.lineColumnToIndex(line, column) if index != -1 && result != null && result.global != null { var query = IDE.SignatureQuery.new(source, index) query.run(result.global) if query.signature != null { signature = query.signatureString arguments = query.argumentStrings argumentIndex = query.argumentIndex } } break } } } return { "type": "signature-query", "id": message.id, "source": name, "signature": signature, "arguments": arguments, "argumentIndex": argumentIndex, } } var handleMessage = (message dynamic) dynamic => { switch message.type { case "compile" { return handleCompile(message) } case "tooltip-query" { return handleTooltipQuery(message) } case "definition-query" { return handleDefinitionQuery(message) } case "symbols-query" { return handleSymbolsQuery(message) } case "rename-query" { return handleRenameQuery(message) } case "completion-query" { return handleCompletionQuery(message) } case "signature-query" { return handleSignatureQuery(message) } default { throw dynamic.Error.new("Unexpected message type '\(message.type)'") } } } return { "compile": handleCompile, "tooltipQuery": handleTooltipQuery, "definitionQuery": handleDefinitionQuery, "symbolsQuery": handleSymbolsQuery, "renameQuery": handleRenameQuery, "completionQuery": handleCompletionQuery, "signatureQuery": handleSignatureQuery, "message": handleMessage, } } @entry if BUILD == .API def apiMain { # JavaScript API var this = (=> dynamic.this)() var api = dynamic.typeof(dynamic.exports) != "undefined" ? dynamic.exports : this.Skew ? this.Skew : this.Skew = {} api.VERSION = VERSION api.create = createCompilerInstance # Web Worker API (only installed if no other code in the worker sets "Skew.handleWorkerMessages" to false first) if dynamic.typeof(dynamic.WorkerGlobalScope) != "undefined" && this is dynamic.WorkerGlobalScope && api.handleWorkerMessages != true { var instance = createCompilerInstance() dynamic.onmessage = (event dynamic) => { dynamic.postMessage(instance.message(event.data)) } } } } ================================================ FILE: src/driver/options.sk ================================================ namespace Skew { class Log { def commandLineWarningDuplicateFlagValue(range Range, name string, previous Range) { append( newWarning(range, "Multiple values are specified for \"\(name)\", using the later value") .withNote(previous, "Ignoring the previous value")) } def commandLineErrorBadFlag(range Range, name string) { append(newError(range, "Unknown command line flag \"\(name)\"")) } def commandLineErrorMissingValue(range Range, text string) { append(newError(range, "Use \"\(text)\" to provide a value")) } def commandLineErrorExpectedToken(range Range, expected string, found string, text string) { append(newError(range, "Expected \"\(expected)\" but found \"\(found)\" in \"\(text)\"")) } def commandLineErrorNonBooleanValue(range Range, value string, text string) { append(newError(range, "Expected \"true\" or \"false\" but found \"\(value)\" in \"\(text)\"")) } def commandLineErrorNonIntegerValue(range Range, value string, text string) { append(newError(range, "Expected integer constant but found \"\(value)\" in \"\(text)\"")) } } } namespace Skew.Options { enum Type { BOOL INT STRING STRING_LIST } class Data { var parser Parser var type Type var option Option var name string var description string def nameText string { return name + (type == .BOOL ? "" : type == .STRING_LIST ? ":___" : "=___") } def aliases(names List) Data { for name in names { parser.map[name] = self } return self } } class Parser { var options List = [] var map StringMap = {} var optionalArguments IntMap = {} var normalArguments List = [] var source Source = null def define(type Type, option Option, name string, description string) Data { var data = Data.new(self, type, option, name, description) map[name] = data options.append(data) return data } def nodeForOption(option Option) Node { return optionalArguments.get(option, null) } def boolForOption(option Option, defaultValue bool) bool { var node = nodeForOption(option) return node != null ? node.content.asBool : defaultValue } def intForOption(option Option, defaultValue int) int { var node = nodeForOption(option) return node != null ? node.content.asInt : defaultValue } def rangeForOption(option Option) Range { var node = nodeForOption(option) return node?.range } def rangeListForOption(option Option) List { var node = nodeForOption(option) var ranges List = [] if node != null { for child = node.firstChild; child != null; child = child.nextSibling { ranges.append(child.range) } } return ranges } def parse(log Log, arguments List) { source = Source.new("", "") var ranges List = [] # Create a source for the arguments to work with the log system. The # trailing space is needed to be able to point to the character after # the last argument without wrapping onto the next line. for argument in arguments { var needsQuotes = " " in argument var start = source.contents.count + (needsQuotes as int) ranges.append(Range.new(source, start, start + argument.count)) source.contents += needsQuotes ? "'\(argument)' " : argument + " " } # Parse each argument for i in 0..arguments.count { var argument = arguments[i] var range = ranges[i] # Track all normal arguments separately if argument == "" || argument[0] != '-' && !(argument in map) { normalArguments.append(range) continue } # Parse a flag var equals = argument.indexOf("=") var colon = argument.indexOf(":") var separator = equals >= 0 && (colon < 0 || equals < colon) ? equals : colon var name = separator >= 0 ? argument.slice(0, separator) : argument var data = map.get(name, null) # Check that the flag exists if data == null { log.commandLineErrorBadFlag(range.fromStart(name.count), name) continue } # Validate the flag data var text = argument.slice(separator + 1) var separatorRange = separator < 0 ? null : range.slice(separator, separator + 1) var textRange = range.fromEnd(text.count) switch data.type { # Parse a single boolean value case .BOOL { if separator < 0 { text = "true" } else if argument[separator] != '=' { log.commandLineErrorExpectedToken(separatorRange, "=", argument.get(separator), argument) continue } else if text != "true" && text != "false" { log.commandLineErrorNonBooleanValue(textRange, text, argument) continue } if data.option in optionalArguments { log.commandLineWarningDuplicateFlagValue(textRange, name, optionalArguments[data.option].range) } optionalArguments[data.option] = Node.createBool(text == "true").withRange(textRange) } # Parse a single int value case .INT { if separator < 0 { log.commandLineErrorMissingValue(textRange, data.nameText) } else if argument[separator] != '=' { log.commandLineErrorExpectedToken(separatorRange, "=", argument.get(separator), argument) } else { var box = Parsing.parseIntLiteral(log, textRange) if box == null { log.commandLineErrorNonIntegerValue(textRange, text, argument) } else { if data.option in optionalArguments { log.commandLineWarningDuplicateFlagValue(textRange, name, optionalArguments[data.option].range) } optionalArguments[data.option] = Node.createInt(box.value).withRange(textRange) } } } # Parse a single string value case .STRING { if separator < 0 { log.commandLineErrorMissingValue(textRange, data.nameText) } else if argument[separator] != '=' { log.commandLineErrorExpectedToken(separatorRange, "=", argument.get(separator), argument) } else { if data.option in optionalArguments { log.commandLineWarningDuplicateFlagValue(textRange, name, optionalArguments[data.option].range) } optionalArguments[data.option] = Node.createString(text).withRange(textRange) } } # Parse an item in a list of string values case .STRING_LIST { if separator < 0 { log.commandLineErrorMissingValue(textRange, data.nameText) } else if argument[separator] != ':' { log.commandLineErrorExpectedToken(separatorRange, ":", argument.get(separator), argument) } else { var node Node if data.option in optionalArguments { node = optionalArguments[data.option] } else { node = Node.createInitializer(.INITIALIZER_LIST) optionalArguments[data.option] = node } node.appendChild(Node.createString(text).withRange(textRange)) } } } } } def usageText(wrapWidth int) string { var text = "" var columnWidth = 0 # Figure out the column width for option in options { var width = option.nameText.count + 4 if columnWidth < width { columnWidth = width } } # Format the options var columnText = " ".repeat(columnWidth) for option in options { var nameText = option.nameText var isFirst = true text += "\n " + nameText + " ".repeat(columnWidth - nameText.count - 2) for line in PrettyPrint.wrapWords(option.description, wrapWidth - columnWidth) { text += (isFirst ? "" : columnText) + line + "\n" isFirst = false } } return text + "\n" } } } ================================================ FILE: src/driver/terminal.sk ================================================ namespace Skew { enum Option { DEFINE FIX_ALL FOLD_CONSTANTS GC_STRATEGY GLOBALIZE_FUNCTIONS HELP IGNORED_COMMENT_WARNING INLINE_FUNCTIONS JS_MANGLE JS_MINIFY JS_SOURCE_MAP MESSAGE_LIMIT NO_OUTPUT OUTPUT_DIRECTORY OUTPUT_FILE RELEASE TARGET VERBOSE VERSION WARNINGS_ARE_ERRORS } const DEFAULT_MESSAGE_LIMIT = 10 @entry if BUILD == .SKEWC def skewcMain(arguments List) int { var log = Log.new var diagnosticLimit = 0 var printDiagnostic = (diagnostic Diagnostic) => { var terminalWidth = Terminal.width if diagnosticLimit > 0 && log.diagnostics.count >= diagnosticLimit { return } if diagnostic.range != null { printWithColor(.BOLD, diagnostic.range.locationString + ": ") } switch diagnostic.kind { case .WARNING { printWarning(diagnostic.text) } case .ERROR { printError(diagnostic.text) } } if diagnostic.range != null { var formatted = diagnostic.range.format(terminalWidth) Terminal.print(formatted.line) printWithColor(.GREEN, formatted.range + "\n") } if diagnostic.noteRange != null { var formatted = diagnostic.noteRange.format(terminalWidth) printWithColor(.BOLD, diagnostic.noteRange.locationString + ": ") printNote(diagnostic.noteText) Terminal.print(formatted.line) printWithColor(.GREEN, formatted.range + "\n") } } # Print diagnostics immediately when generated to improve perceived speed log.appendCallback = printDiagnostic # Translate frontend flags to compiler options var parser = Options.Parser.new var options = parseOptions(log, parser, arguments) diagnosticLimit = parser.intForOption(.MESSAGE_LIMIT, DEFAULT_MESSAGE_LIMIT) var fixAll = parser.boolForOption(.FIX_ALL, false) var fixCount = 0 # Optionally have the log transform warnings into errors if options != null { log.warningsAreErrors = options.warningsAreErrors } # Suppress logging during fixes if fixAll { log = Log.new } # Iterate until fixed point when applying fixes while true { var inputs List = [] var inputRanges List = [] readSources(log, parser.normalArguments, inputs, inputRanges) # Run the compilation if !log.hasErrors && options != null { var result = compile(log, options, inputs) # Write all outputs if !log.hasErrors { for output in result.outputs { if output.name != null && !IO.writeFile(output.name, output.contents) { var outputFile = parser.rangeForOption(.OUTPUT_FILE) var outputDirectory = parser.rangeForOption(.OUTPUT_DIRECTORY) log.commandLineErrorUnwritableFile(outputFile ?? outputDirectory, output.name) break } } # Print compilation statistics if !log.hasErrors { printWithColor(.GRAY, result.statistics(inputs, options.verbose ? .LONG : .SHORT) + "\n") } } } if !fixAll { break } # Attempt to automatically fix warnings and errors var applyLog = Log.new var count = applyFixes(log, applyLog, source => { for i in 0..inputs.count { if source.name == inputs[i].name { return inputRanges[i] } } return parser.rangeForOption(.FIX_ALL) }) fixCount += count log = applyLog if count == 0 || applyLog.hasErrors { break } } # Print diagnostics afterward when applying fixes since they aren't printed earlier if fixAll { for diagnostic in log.diagnostics { printDiagnostic(diagnostic) } Terminal.print("\(fixCount) \(fixCount == 1 ? "fix" : "fixes") applied") } # Print any errors and warnings printLogSummary(log, diagnosticLimit) # Optionally report a failure if any warnings were found if options != null && options.warningsAreErrors { return log.hasErrors || log.hasWarnings ? 1 : 0 } else { return log.hasErrors ? 1 : 0 } } def printWithColor(color Terminal.Color, text string) { Terminal.setColor(color) Terminal.write(text) Terminal.setColor(.DEFAULT) } def printError(text string) { printWithColor(.RED, "error: ") printWithColor(.BOLD, text + "\n") } def printNote(text string) { printWithColor(.GRAY, "note: ") printWithColor(.BOLD, text + "\n") } def printWarning(text string) { printWithColor(.MAGENTA, "warning: ") printWithColor(.BOLD, text + "\n") } def printUsage(parser Options.Parser) { printWithColor(.GREEN, "\nusage: ") printWithColor(.BOLD, "skewc [flags] [inputs]\n") Terminal.write(parser.usageText(Math.min(Terminal.width, 80))) } def printLogSummary(log Log, diagnosticLimit int) { var hasErrors = log.hasErrors var hasWarnings = log.hasWarnings var summary = "" if hasWarnings { summary += PrettyPrint.plural(log.warningCount, "warning") if hasErrors { summary += " and " } } if hasErrors { summary += PrettyPrint.plural(log.errorCount, "error") } if hasWarnings || hasErrors { Terminal.write(summary + " generated") if log.wasWarningCount > 0 { Terminal.write(" (warnings are being treated as errors due to \"--warnings-are-errors\")") } if diagnosticLimit > 0 && log.diagnostics.count > diagnosticLimit { printWithColor(.GRAY, " (only showing \(PrettyPrint.plural(diagnosticLimit, "message")), use \"--message-limit=0\" to see all)") } Terminal.write("\n") } } def readSources(log Log, normalArguments List, inputs List, inputRanges List) { var visit fn(Range, string, bool) visit = (range, path, isExplicit) => { if splitPath(path).entry.startsWith(".") { return } # Directories if IO.isDirectory(path) { var entries = IO.readDirectory(path) if entries == null { log.commandLineErrorUnreadableFile(range, path) } for entry in entries { if !entry.startsWith(".") { visit(range, path + "/" + entry, false) } } } # Files (ignore non-skew files that aren't explicitly specified) else if isExplicit || path.endsWith(".sk") { var contents = IO.readFile(path) if contents == null { log.commandLineErrorUnreadableFile(range, path) } else { inputs.append(Source.new(path, contents)) inputRanges.append(range) } } } # Recursively visit input directories for range in normalArguments { visit(range, range.toString, true) } } def parseOptions(log Log, parser Options.Parser, arguments List) CompilerOptions { # Configure the parser parser.define(.BOOL, .HELP, "--help", "Prints this message.").aliases(["-help", "?", "-?", "-h", "-H", "/?", "/h", "/H"]) parser.define(.STRING, .TARGET, "--target", "Sets the target format. Valid targets are \(joinKeys(VALID_TARGETS.keys)).") parser.define(.STRING, .OUTPUT_FILE, "--output-file", "Combines all output into a single file. Mutually exclusive with --output-dir.") parser.define(.STRING, .OUTPUT_DIRECTORY, "--output-dir", "Places all output files in the specified directory. Mutually exclusive with --output-file.") parser.define(.BOOL, .NO_OUTPUT, "--no-output", "Stops after the type checking pass and does not generate any output.") parser.define(.BOOL, .RELEASE, "--release", "Implies --js-mangle, --js-minify, --fold-constants, --inline-functions, --globalize-functions, and --define:RELEASE=true.") parser.define(.BOOL, .VERBOSE, "--verbose", "Prints out information about the compilation.") parser.define(.BOOL, .VERSION, "--version", "Prints the current compiler version (\(VERSION)) and exits.") parser.define(.INT, .MESSAGE_LIMIT, "--message-limit", "Sets the maximum number of messages to report. " + "Pass 0 to disable the message limit. The default is \(DEFAULT_MESSAGE_LIMIT).") parser.define(.STRING_LIST, .DEFINE, "--define", "Override variable values at compile time.") parser.define(.BOOL, .JS_MANGLE, "--js-mangle", "Transforms emitted JavaScript to be as small as possible. The \"@export\" annotation prevents renaming a symbol.") parser.define(.BOOL, .JS_MINIFY, "--js-minify", "Remove whitespace when compiling to JavaScript.") parser.define(.BOOL, .JS_SOURCE_MAP, "--js-source-map", "Generates a source map when targeting JavaScript. " + "The source map is saved with the \".map\" extension in the same directory as the main output file.") parser.define(.BOOL, .FOLD_CONSTANTS, "--fold-constants", "Evaluates constants at compile time and removes dead code inside functions.") parser.define(.BOOL, .INLINE_FUNCTIONS, "--inline-functions", "Uses heuristics to automatically inline simple global functions.") parser.define(.BOOL, .GLOBALIZE_FUNCTIONS, "--globalize-functions", "Convert instance functions to global functions for better inlining.") parser.define(.BOOL, .FIX_ALL, "--fix-all", "Attempt to automatically fix as many errors and warnings as possible. " + "THIS WILL WRITE OVER YOUR SOURCE CODE. Make sure you know what you're doing.") parser.define(.BOOL, .IGNORED_COMMENT_WARNING, "--ignored-comment-warning", "Warn when the compiler doesn't store a comment in the parse tree.") parser.define(.BOOL, .WARNINGS_ARE_ERRORS, "--warnings-are-errors", "Turns warnings into errors.") # Parse the command line arguments parser.parse(log, arguments) if log.hasErrors { return null } # Early-out when printing the usage text if parser.boolForOption(.HELP, arguments.isEmpty) { printUsage(parser) return null } # Early-out when printing the version if parser.boolForOption(.VERSION, false) { Terminal.print(VERSION) return null } # Set up the options for the compiler var options = CompilerOptions.new var releaseFlag = parser.boolForOption(.RELEASE, false) options.foldAllConstants = parser.boolForOption(.FOLD_CONSTANTS, releaseFlag) options.globalizeAllFunctions = parser.boolForOption(.GLOBALIZE_FUNCTIONS, releaseFlag) options.inlineAllFunctions = parser.boolForOption(.INLINE_FUNCTIONS, releaseFlag) options.jsMangle = parser.boolForOption(.JS_MANGLE, releaseFlag) options.jsMinify = parser.boolForOption(.JS_MINIFY, releaseFlag) options.jsSourceMap = parser.boolForOption(.JS_SOURCE_MAP, false) options.stopAfterResolve = parser.boolForOption(.NO_OUTPUT, false) options.verbose = parser.boolForOption(.VERBOSE, false) options.warnAboutIgnoredComments = parser.boolForOption(.IGNORED_COMMENT_WARNING, false) options.warningsAreErrors = parser.boolForOption(.WARNINGS_ARE_ERRORS, false) # Prepare the defines if releaseFlag { options.define("RELEASE", "true") } for range in parser.rangeListForOption(.DEFINE) { var name = range.toString var equals = name.indexOf("=") if equals < 0 { log.commandLineErrorExpectedDefineValue(range, name) continue } options.defines[name.slice(0, equals)] = Define.new(range.fromStart(equals), range.fromEnd(name.count - equals - 1)) } # There must be at least one source file var end = parser.source.contents.count var trailingSpace = Range.new(parser.source, end - 1, end) if parser.normalArguments.isEmpty && !options.stopAfterResolve { log.commandLineErrorNoInputFiles(trailingSpace) } # Parse the output location if !options.stopAfterResolve { var outputFile = parser.rangeForOption(.OUTPUT_FILE) var outputDirectory = parser.rangeForOption(.OUTPUT_DIRECTORY) if outputFile == null && outputDirectory == null { log.commandLineErrorMissingOutput(trailingSpace, "--output-file", "--output-dir") } else if outputFile != null && outputDirectory != null { log.commandLineErrorDuplicateOutput(outputFile.start > outputDirectory.start ? outputFile : outputDirectory, "--output-file", "--output-dir") } else if outputFile != null { options.outputFile = outputFile.toString } else { options.outputDirectory = outputDirectory.toString } } # Check the target format var target = parser.rangeForOption(.TARGET) if target != null { options.target = parseEnum(log, "target", VALID_TARGETS, target, null) } else if !options.createTargetFromExtension { log.commandLineErrorMissingTarget(trailingSpace) } return options } def applyFixes(log Log, applyLog Log, rangeForSource fn(Source) Range) int { var fixCount = 0 # Collect diagnostics by source file var map StringMap> = {} for diagnostic in log.diagnostics { if diagnostic.range != null && diagnostic.fixes != null && diagnostic.fixes.count == 1 { var name = diagnostic.range.source.name var diagnostics = map.get(name, null) if diagnostics == null { map[name] = diagnostics = [] } diagnostics.append(diagnostic) } } # Apply for each source file for diagnostics in map.values { var source = diagnostics.first.range.source var contents = source.contents diagnostics.sort((a, b) => b.range.start <=> a.range.start) # Apply fixes in reverse to avoid issues with changing offsets var last = contents.count for i in 0..diagnostics.count { var fix = diagnostics[i].fixes.first # Typo correction isn't robust enough right now to fix automatically if fix.kind == .SYMBOL_TYPO || fix.range.end > last { continue } contents = contents.slice(0, fix.range.start) + fix.replacement + contents.slice(fix.range.end) last = fix.range.start fixCount++ } # Write over the source file in place if !IO.writeFile(source.name, contents) { applyLog.commandLineErrorUnwritableFile(rangeForSource(source), source.name) } } return fixCount } class Log { def commandLineErrorExpectedDefineValue(range Range, name string) { append(newError(range, "Use \"--define:\(name)=___\" to provide a value")) } def commandLineErrorMissingOutput(range Range, first string, second string) { append(newError(range, "Specify the output location using either \"\(first)\" or \"\(second)\"")) } def commandLineErrorDuplicateOutput(range Range, first string, second string) { append(newError(range, "Cannot specify both \"\(first)\" and \"\(second)\"")) } def commandLineErrorUnreadableFile(range Range, name string) { append(newError(range, "Could not read from \"\(name)\"")) } def commandLineErrorUnwritableFile(range Range, name string) { append(newError(range, "Could not write to \"\(name)\"")) } def commandLineErrorNoInputFiles(range Range) { append(newError(range, "Missing input files")) } def commandLineErrorMissingTarget(range Range) { append(newError(range, "Specify the target format using \"--target\"")) } def commandLineErrorInvalidEnum(range Range, name string, found string, expected List) { append(newError(range, "Invalid \(name) \"\(found)\", must be either \(PrettyPrint.joinQuoted(expected, "or"))")) } } def joinKeys(keys List) string { keys.sort(SORT_STRINGS) return PrettyPrint.joinQuoted(keys, "and") } def parseEnum(log Log, name string, map StringMap, range Range, defaultValue T) T { if range != null { var key = range.toString if key in map { return map[key] } var keys = map.keys keys.sort(SORT_STRINGS) # Sort so the order is deterministic log.commandLineErrorInvalidEnum(range, name, key, keys) } return defaultValue } const VALID_TARGETS = { "cpp": CPlusPlusTarget.new, "cs": CSharpTarget.new, "ts": TypeScriptTarget.new, "js": JavaScriptTarget.new, "lisp-tree": LispTreeTarget.new, } } ================================================ FILE: src/driver/tests.sk ================================================ namespace Skew.Tests { class CompilerTest : Unit.Test { var _input string var _expected string var _options = CompilerOptions.new over run { rename(compactWhitespace(_input)) var log = Log.new var result = compile(log, _options, [Source.new("", _input)]) var output string if result.outputs.isEmpty { output = log.toString + log.fixesToString } else { output = "\n".join(result.outputs.map(source => (source.name == null ? "" : "[\(source.name)]\n") + source.contents)) } expectString(trimNewlines(_expected), trimNewlines(output)) } def cpp CompilerTest { _options.target = CPlusPlusTarget.new return self } def csharp CompilerTest { _options.target = CSharpTarget.new return self } def ts CompilerTest { _options.target = TypeScriptTarget.new return self } def js CompilerTest { _options.target = JavaScriptTarget.new return self } def jsMangle CompilerTest { _options.target = JavaScriptTarget.new _options.jsMangle = true _options.define("RELEASE", "true") return self } def jsMinify CompilerTest { _options.target = JavaScriptTarget.new _options.jsMinify = true return self } def foldAllConstants CompilerTest { _options.foldAllConstants = true return self } def globalizeAllFunctions CompilerTest { _options.globalizeAllFunctions = true return self } def inlineAllFunctions CompilerTest { _options.inlineAllFunctions = true return self } } class IDETest : Unit.Test { var _input string var _callback fn(IDETest, fn(string, string)) var _source Source = null var _result CompilerResult = null over run { rename(compactWhitespace(_input)) var log = Log.new var options = CompilerOptions.new options.stopAfterResolve = true _source = Source.new("", _input) _result = compile(log, options, [_source]) expectString("", log.toString) _callback(self, (a, b) => expectString(a, b)) } def tooltipQuery(line int, column int) string { var index = _source.lineColumnToIndex(line, column) if index == -1 { return "error: The location \"\(line):\(column)\" could not be found in the input" } var query = IDE.SymbolQuery.new(_source, index) query.run(_result.global) if query.symbol != null { return query.generateTooltip } return "" } def definitionQuery(line int, column int) string { var index = _source.lineColumnToIndex(line, column) if index == -1 { return "error: The location \"\(line):\(column)\" could not be found in the input" } var query = IDE.SymbolQuery.new(_source, index) query.run(_result.global) if query.symbol != null { return query.symbol.range.locationString } return "" } def renameQuery(line int, column int) string { var index = _source.lineColumnToIndex(line, column) if index == -1 { return "error: The location \"\(line):\(column)\" could not be found in the input" } var query = IDE.RenameQuery.new(_source, index) query.run(_result.global) if query.ranges != null { return ", ".join(query.ranges.map(range => range.locationString)) } return "" } def signatureQuery(line int, column int) string { var index = _source.lineColumnToIndex(line, column) if index == -1 { return "error: The location \"\(line):\(column)\" could not be found in the input" } var query = IDE.SignatureQuery.new(_source, index) query.run(_result.global) if query.signature != null { var signature = query.signatureString var argument = query.argumentStrings[query.argumentIndex] var position = signature.indexOf(argument) if position != -1 { return "\(signature.slice(0, position))[\(argument)]\(signature.slice(position + argument.count))" } } return "" } } class CompletionTest : Unit.Test { var _input string var _line int var _column int var _expected string over run { rename(compactWhitespace(_input)) var log = Log.new var source = Source.new("", _input) var index = source.lineColumnToIndex(_line, _column) if index == -1 { expectString(trimNewlines(_expected), "error: The location \"\(_line):\(_column)\" could not be found in the input") } else { var options = CompilerOptions.new options.stopAfterResolve = true options.completionContext = CompletionContext.new(source, index) compile(log, options, [source]) var builder = StringBuilder.new var range = options.completionContext.range builder.append(trimNewlines(log.toString)) if range != null { var formatted = range.format(0) var lastSpace = formatted.range.lastIndexOf(" ") var indent = lastSpace != -1 ? formatted.range.slice(0, lastSpace + 1) : "" builder.append("\n\(range.locationString): completions:\n\(formatted.line)\n\(formatted.range)") for symbol in options.completionContext.completions { builder.append("\n\(indent)[\(symbol.name)]") var type = IDE.completionType(symbol) if type != null { builder.append(" # \"\(IDE.completionType(symbol))\"") } if symbol.comments != null { var nested = "\n" + indent + " ".repeat(symbol.name.count + 2) + " #" for comment in symbol.comments { if !comment.hasGapBelow { for line in comment.lines { builder.append(nested + (line.endsWith("\n") ? line.slice(0, line.count - 1) : line)) } } } } } } expectString(trimNewlines(_expected), builder.toString) } } } class FormatTest : Unit.Test { over run { rename(compactWhitespace(_input)) var formatted = Range.new(Source.new("", _input), _start, _end).format(_maxLength) expectString(trimNewlines(_expected), trimNewlines(formatted.line + "\n" + formatted.range)) } const _input string const _expected string const _start int const _end int const _maxLength int } class SimpleTest : Unit.Test { over run { _callback((a, b) => expectString(a, b)) } const _callback fn(fn(string, string)) } def trimNewlines(text string) string { var length = text.count var start = 0 var end = length while start < length && text[start] == '\n' { start++ } while start < end && text[end - 1] == '\n' { end-- } return text.slice(start, end) } def compactWhitespace(text string) string { var wasSpace = false var result = "" for i in 0..text.count { var c = text[i] if c != '\n' && c != ' ' && c != '\t' { result += text.get(i) wasSpace = false } else if !wasSpace { result += " " wasSpace = true } } return result } def test(input string, expected string) CompilerTest { return CompilerTest.new(trimNewlines(input), expected) } def testFormat(input string, expected string, start int, end int, maxLength int) FormatTest { return FormatTest.new(trimNewlines(input), expected, start, end, maxLength) } def testIDE(input string, callback fn(IDETest, fn(string, string))) IDETest { return IDETest.new(trimNewlines(input), callback) } def testCompletion(input string, line int, column int, expected string) CompletionTest { return CompletionTest.new(trimNewlines(input), line, column, expected) } def test(name string, callback fn(fn(string, string))) SimpleTest { var test = SimpleTest.new(callback) test.rename(name) return test } def testExpect(text string, answer fn() bool, expected bool) Unit.Test { return test(text, expectString => expectString(answer().toString, expected.toString)) } def testExpect(text string, answer fn() int, expected int) Unit.Test { return test(text, expectString => expectString(answer().toString, expected.toString)) } def testExpect(text string, answer fn() double, expected double) Unit.Test { return test(text, expectString => expectString(answer().toString, expected.toString)) } def testExpect(text string, answer fn() string, expected string) Unit.Test { return test(text, expectString => expectString(answer(), expected)) } def testExpect(text string, answer fn() List, expected List) Unit.Test { return test(text, expectString => expectString(toString(answer()), toString(expected))) } def testExpect(text string, answer fn() List, expected List) Unit.Test { return test(text, expectString => expectString(toString(answer()), toString(expected))) } def testExpect(text string, answer fn() List, expected List) Unit.Test { return test(text, expectString => expectString(toString(answer()), toString(expected))) } def testExpect(text string, answer fn() IntMap, expected IntMap) Unit.Test { return test(text, expectString => expectString(toString(answer()), toString(expected))) } def testExpect(text string, answer fn() StringMap, expected StringMap) Unit.Test { return test(text, expectString => expectString(toString(answer()), toString(expected))) } def toString(values List) string { return toString(values.map(x => x.toString)) } def toString(values List) string { return toString(values.map(x => x.toString)) } def toString(values List) string { return "[" + ", ".join(values) + "]" } def toString(node Node) string { if node == null { return "null" } var parts = [node.kind.toString] for child = node.firstChild; child != null; child = child.nextSibling { parts.append(toString(child)) } return toString(parts) } def toString(value IntMap) string { var keys = value.keys keys.sort((a, b) => a <=> b) # Sort so the order is deterministic return toString(keys.map(k => k.toString), keys.map(k => value[k].toString)) } def toString(value StringMap) string { var keys = value.keys keys.sort((a, b) => a <=> b) # Sort so the order is deterministic return toString(keys, keys.map(k => value[k].toString)) } def toString(keys List, values List) string { assert(keys.count == values.count) var parts List = [] for i in 0..keys.count { parts.append(keys[i] + ": " + values[i]) } return "{" + ", ".join(parts) + "}" } if TARGET == .JAVASCRIPT { def fixRuntime { dynamic.Error.stackTraceLimit = Math.INFINITY } } else { def fixRuntime { } } @entry if BUILD == .TEST def testMain int { fixRuntime # End-to-end tests testCPlusPlus testCSharp testIDE testJavaScript testJavaScriptMangle testJavaScriptMinify testParsing testSimple # Unit tests testFormatting testLevenshteinEditDistance testLibrary testNode testQuoteReplacement testRanges testRuntime testUnicode var report = Unit.TerminalReport.new Unit.Test.runAll(report) return report.failedCount } } ================================================ FILE: src/frontend/flex.l ================================================ %% \n[ \t\r]* NEWLINE; [ \t\r]+ WHITESPACE; #.*? COMMENT; \/\/.*? COMMENT_ERROR; "as" AS; "break" BREAK; "case" CASE; "catch" CATCH; "const" CONST; "continue" CONTINUE; "default" DEFAULT; "dynamic" DYNAMIC; "else" ELSE; "false" FALSE; "finally" FINALLY; "for" FOR; "if" IF; "in" IN; "is" IS; "null" NULL; "return" RETURN; "super" SUPER; "switch" SWITCH; "throw" THROW; "true" TRUE; "try" TRY; "var" VAR; "while" WHILE; "[...]" LIST; "[new]" LIST_NEW; "{...}" SET; "{new}" SET_NEW; "[]=" ASSIGN_INDEX; "[]" INDEX; "(" LEFT_PARENTHESIS; ")" RIGHT_PARENTHESIS; "{" LEFT_BRACE; "}" RIGHT_BRACE; "[" LEFT_BRACKET; "]" RIGHT_BRACKET; "=>" ARROW; "~" TILDE; ".." DOT_DOT; "." DOT; "," COMMA; "?=" ASSIGN_NULL; "??" NULL_JOIN; "?." NULL_DOT; "?" QUESTION_MARK; "::" DOUBLE_COLON; ":" COLON; ";" SEMICOLON; "<>..." XML_CHILD; "" XML_END_EMPTY; "**=" ASSIGN_POWER; "+=" ASSIGN_PLUS; "-=" ASSIGN_MINUS; "*=" ASSIGN_MULTIPLY; "/=" ASSIGN_DIVIDE; "%%=" ASSIGN_MODULUS; "%=" ASSIGN_REMAINDER; "&=" ASSIGN_BITWISE_AND; "|=" ASSIGN_BITWISE_OR; "^=" ASSIGN_BITWISE_XOR; "<<=" ASSIGN_SHIFT_LEFT; ">>>=" ASSIGN_UNSIGNED_SHIFT_RIGHT; ">>=" ASSIGN_SHIFT_RIGHT; "**" POWER; "++" INCREMENT; "--" DECREMENT; "+" PLUS; "-" MINUS; "*" MULTIPLY; "/" DIVIDE; "%%" MODULUS; "%" REMAINDER; "||" LOGICAL_OR; "&&" LOGICAL_AND; "&" BITWISE_AND; "|" BITWISE_OR; "^" BITWISE_XOR; "<<" SHIFT_LEFT; ">>>" UNSIGNED_SHIFT_RIGHT; ">>" SHIFT_RIGHT; "!==" NOT_EQUAL_ERROR; "===" EQUAL_ERROR; "!=" NOT_EQUAL; "==" EQUAL; "<=>" COMPARE; "<=" LESS_THAN_OR_EQUAL; ">=" GREATER_THAN_OR_EQUAL; "<" LESS_THAN; ">" GREATER_THAN; "!" NOT; "=" ASSIGN; ['](\\.|[^\\'])*['] CHARACTER; ["](\\.|[^\\"])*["] STRING; @[A-Za-z_][A-Za-z0-9_]* ANNOTATION; [A-Za-z_][A-Za-z0-9_]* IDENTIFIER; [0-9]+\.[0-9]+[eE][-+]?[0-9]+ DOUBLE; [0-9]+\.[0-9]+ DOUBLE; 0b[0-1]+ INT_BINARY; 0o[0-7]+ INT_OCTAL; 0x[A-Fa-f0-9]+ INT_HEX; [0-9]+[eE][-+]?[0-9]+ DOUBLE; [0-9]+ INT; ================================================ FILE: src/frontend/flex.py ================================================ import os import re import sys import tempfile import subprocess def _run_flex(source): fd, path = tempfile.mkstemp() os.close(fd) flex = subprocess.Popen(['flex', '-B', '-7', '-o', path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = flex.communicate(input=source) sys.stdout.write(stdout) sys.stderr.write(stderr) if flex.returncode: raise Exception('flex failed to run') output = open(path).read() os.remove(path) return output def _find_array(source, name): match = re.search(name + r'\[\d+\]\s*=[^{]*\{([^}]*)\}', source) return map(int, match.groups(1)[0].split(',')) def _find_magic_number(source, pattern): match = re.search(pattern, source) return int(match.groups(1)[0]) def _find_actions(source): matches = re.findall(r'case\s+(\d+)\s*:\s*(?:/\*[^*]*\*/)?\s*YY_RULE_SETUP[^\0]*?\n(.*);\s*YY_BREAK', source) return [(int(m[0]), m[1]) for m in matches] def compile(source): output = _run_flex(source) result = {} # Comments from https://github.com/gobo-eiffel/gobo/blob/master/library/lexical/scanner/lx_compressed_tables.e result['yy_accept'] = _find_array(output, 'yy_accept') # Accepting id list result['yy_ec'] = _find_array(output, 'yy_ec') # ASCII to equivalence class result['yy_meta'] = _find_array(output, 'yy_meta') # Meta equivalence classes which are sets of classes with identical transitions out of templates result['yy_base'] = _find_array(output, 'yy_base') # Offsets into 'yy_nxt' for given states result['yy_def'] = _find_array(output, 'yy_def') # Where to go if 'yy_chk' disallows 'yy_nxt' entry result['yy_nxt'] = _find_array(output, 'yy_nxt') # States to enter upon reading symbol result['yy_chk'] = _find_array(output, 'yy_chk') # Check value to see if 'yy_nxt' applies result['yy_end_of_buffer'] = _find_magic_number(output, r'#define\s+YY_END_OF_BUFFER\s+(\d+)') result['jamstate'] = _find_magic_number(output, r'while\s*\(\s*yy_current_state\s*!=\s*(\d+)') result['actions'] = _find_actions(output) return result ================================================ FILE: src/frontend/lexer.py ================================================ import os import flex template = ''' ################################################################################ # # This is a generated file, all edits will be lost! # ################################################################################ namespace Skew { enum TokenKind { %(actions)s } %(yy_accept)s %(yy_ec)s %(yy_meta)s %(yy_base)s %(yy_def)s %(yy_nxt)s %(yy_chk)s %(jamstate)s %(yy_accept_length)s } ''' def create_table(result, name, type=None): return 'const %(name)s%(type)s = [%(entries)s]' % { 'type': ' ' + type if type else '', 'name': name, 'entries': ', '.join('%s' % x for x in result[name]), } # Read and compile the input path = os.path.dirname(__file__) source = open(os.path.join(path, 'flex.l')).read() result = flex.compile(source) # Assume all actions are sequential and start at 1 if [k for k, v in result['actions']] != range(1, len(result['actions']) + 1): raise Exception('all actions are not sequential') # Assume ECHO is the last action if result['actions'][-1][1] != 'ECHO': raise Exception('ECHO is not after the last action') # Assume yy_end_of_buffer is after the last action if result['yy_end_of_buffer'] != len(result['actions']) + 1: raise Exception('yy_end_of_buffer is not after the last action') # Patch the results result['actions'] = dict((k, v if v != 'ECHO' else 'ERROR') for k, v in result['actions'] + [(0, 'YY_INVALID_ACTION'), (result['yy_end_of_buffer'], 'END_OF_FILE')]) result['yy_accept'] = ['.%s' % result['actions'][x] for x in result['yy_accept']] result['actions'] = '\n'.join(' %s' % x for x in sorted(set(result['actions'].values()))) result['yy_accept_length'] = len(result['yy_accept']) result['yy_accept'] = create_table(result, 'yy_accept', type='List') result['yy_ec'] = create_table(result, 'yy_ec') result['yy_meta'] = create_table(result, 'yy_meta') result['yy_base'] = create_table(result, 'yy_base') result['yy_def'] = create_table(result, 'yy_def') result['yy_nxt'] = create_table(result, 'yy_nxt') result['yy_chk'] = create_table(result, 'yy_chk') result['jamstate'] = 'const YY_JAM_STATE = %s' % result['jamstate'] result['yy_accept_length'] = 'const YY_ACCEPT_LENGTH = %s' % result['yy_accept_length'] # Write the output open(os.path.join(path, 'lexer.sk'), 'w').write(template.strip() % result + '\n') ================================================ FILE: src/frontend/lexer.sk ================================================ ################################################################################ # # This is a generated file, all edits will be lost! # ################################################################################ namespace Skew { enum TokenKind { ANNOTATION ARROW AS ASSIGN ASSIGN_BITWISE_AND ASSIGN_BITWISE_OR ASSIGN_BITWISE_XOR ASSIGN_DIVIDE ASSIGN_INDEX ASSIGN_MINUS ASSIGN_MODULUS ASSIGN_MULTIPLY ASSIGN_NULL ASSIGN_PLUS ASSIGN_POWER ASSIGN_REMAINDER ASSIGN_SHIFT_LEFT ASSIGN_SHIFT_RIGHT ASSIGN_UNSIGNED_SHIFT_RIGHT BITWISE_AND BITWISE_OR BITWISE_XOR BREAK CASE CATCH CHARACTER COLON COMMA COMMENT COMMENT_ERROR COMPARE CONST CONTINUE DECREMENT DEFAULT DIVIDE DOT DOT_DOT DOUBLE DOUBLE_COLON DYNAMIC ELSE END_OF_FILE EQUAL EQUAL_ERROR ERROR FALSE FINALLY FOR GREATER_THAN GREATER_THAN_OR_EQUAL IDENTIFIER IF IN INCREMENT INDEX INT INT_BINARY INT_HEX INT_OCTAL IS LEFT_BRACE LEFT_BRACKET LEFT_PARENTHESIS LESS_THAN LESS_THAN_OR_EQUAL LIST LIST_NEW LOGICAL_AND LOGICAL_OR MINUS MODULUS MULTIPLY NEWLINE NOT NOT_EQUAL NOT_EQUAL_ERROR NULL NULL_DOT NULL_JOIN PLUS POWER QUESTION_MARK REMAINDER RETURN RIGHT_BRACE RIGHT_BRACKET RIGHT_PARENTHESIS SEMICOLON SET SET_NEW SHIFT_LEFT SHIFT_RIGHT STRING SUPER SWITCH THROW TILDE TRUE TRY UNSIGNED_SHIFT_RIGHT VAR WHILE WHITESPACE XML_CHILD XML_END_EMPTY XML_START_CLOSE YY_INVALID_ACTION } const yy_accept List = [.YY_INVALID_ACTION, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .END_OF_FILE, .ERROR, .WHITESPACE, .NEWLINE, .NOT, .ERROR, .COMMENT, .REMAINDER, .BITWISE_AND, .ERROR, .LEFT_PARENTHESIS, .RIGHT_PARENTHESIS, .MULTIPLY, .PLUS, .COMMA, .MINUS, .DOT, .DIVIDE, .INT, .INT, .COLON, .SEMICOLON, .LESS_THAN, .ASSIGN, .GREATER_THAN, .QUESTION_MARK, .ERROR, .IDENTIFIER, .LEFT_BRACKET, .RIGHT_BRACKET, .BITWISE_XOR, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .LEFT_BRACE, .BITWISE_OR, .RIGHT_BRACE, .TILDE, .WHITESPACE, .NEWLINE, .NOT_EQUAL, .YY_INVALID_ACTION, .STRING, .YY_INVALID_ACTION, .COMMENT, .MODULUS, .ASSIGN_REMAINDER, .LOGICAL_AND, .ASSIGN_BITWISE_AND, .YY_INVALID_ACTION, .CHARACTER, .YY_INVALID_ACTION, .POWER, .ASSIGN_MULTIPLY, .INCREMENT, .ASSIGN_PLUS, .DECREMENT, .ASSIGN_MINUS, .DOT_DOT, .COMMENT_ERROR, .ASSIGN_DIVIDE, .XML_END_EMPTY, .YY_INVALID_ACTION, .INT, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .DOUBLE_COLON, .XML_START_CLOSE, .SHIFT_LEFT, .LESS_THAN_OR_EQUAL, .YY_INVALID_ACTION, .EQUAL, .ARROW, .GREATER_THAN_OR_EQUAL, .SHIFT_RIGHT, .NULL_DOT, .ASSIGN_NULL, .NULL_JOIN, .ANNOTATION, .IDENTIFIER, .YY_INVALID_ACTION, .INDEX, .YY_INVALID_ACTION, .ASSIGN_BITWISE_XOR, .AS, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IF, .IN, .IS, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .ASSIGN_BITWISE_OR, .LOGICAL_OR, .NOT_EQUAL_ERROR, .ASSIGN_MODULUS, .ASSIGN_POWER, .COMMENT_ERROR, .DOUBLE, .YY_INVALID_ACTION, .DOUBLE, .INT_BINARY, .INT_OCTAL, .INT_HEX, .ASSIGN_SHIFT_LEFT, .COMPARE, .YY_INVALID_ACTION, .EQUAL_ERROR, .ASSIGN_SHIFT_RIGHT, .UNSIGNED_SHIFT_RIGHT, .ANNOTATION, .YY_INVALID_ACTION, .ASSIGN_INDEX, .YY_INVALID_ACTION, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .FOR, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .TRY, .VAR, .IDENTIFIER, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .ASSIGN_UNSIGNED_SHIFT_RIGHT, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .IDENTIFIER, .CASE, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .ELSE, .IDENTIFIER, .IDENTIFIER, .NULL, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .TRUE, .IDENTIFIER, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .YY_INVALID_ACTION, .DOUBLE, .YY_INVALID_ACTION, .LIST, .LIST_NEW, .BREAK, .CATCH, .CONST, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .FALSE, .IDENTIFIER, .IDENTIFIER, .SUPER, .IDENTIFIER, .THROW, .WHILE, .SET, .SET_NEW, .YY_INVALID_ACTION, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .IDENTIFIER, .RETURN, .SWITCH, .YY_INVALID_ACTION, .IDENTIFIER, .DEFAULT, .DYNAMIC, .FINALLY, .XML_CHILD, .CONTINUE, .YY_INVALID_ACTION] const yy_ec = [0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 5, 6, 1, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 20, 20, 20, 20, 20, 21, 21, 22, 23, 24, 25, 26, 27, 28, 29, 29, 29, 29, 30, 29, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 33, 34, 35, 31, 1, 36, 37, 38, 39, 40, 41, 31, 42, 43, 31, 44, 45, 46, 47, 48, 49, 31, 50, 51, 52, 53, 54, 55, 56, 57, 31, 58, 59, 60, 61, 1] const yy_meta = [0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 4, 4, 5, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1] const yy_base = [0, 0, 0, 320, 321, 317, 316, 292, 57, 0, 56, 57, 55, 321, 321, 54, 55, 321, 52, 300, 58, 75, 81, 293, 321, 61, 44, 46, 82, 0, 0, 88, 321, 289, 262, 262, 70, 63, 266, 81, 85, 257, 269, 21, 66, 272, 265, 94, 88, 321, 321, 304, 303, 279, 109, 321, 300, 0, 277, 321, 321, 321, 110, 321, 298, 275, 321, 321, 321, 321, 321, 321, 0, 321, 321, 119, 130, 143, 109, 134, 0, 321, 321, 274, 272, 281, 271, 321, 321, 108, 321, 321, 321, 0, 0, 279, 269, 253, 321, 0, 252, 93, 244, 249, 242, 237, 242, 239, 235, 0, 0, 0, 239, 231, 233, 238, 230, 102, 229, 235, 261, 236, 321, 321, 321, 321, 321, 0, 147, 153, 160, 157, 164, 0, 321, 321, 259, 321, 321, 249, 0, 257, 321, 217, 235, 230, 231, 134, 232, 231, 226, 214, 228, 0, 218, 209, 221, 208, 211, 218, 0, 0, 212, 240, 200, 175, 238, 321, 219, 218, 207, 0, 208, 197, 205, 194, 200, 0, 205, 199, 0, 193, 192, 203, 185, 0, 199, 178, 177, 179, 183, 212, 321, 321, 0, 0, 0, 188, 181, 168, 0, 147, 144, 0, 147, 0, 0, 321, 321, 152, 104, 78, 87, 35, 0, 0, 63, 33, 0, 0, 0, 321, 0, 321, 204, 209, 214, 216, 219, 224, 227, 229] const yy_def = [0, 223, 1, 223, 223, 223, 223, 223, 224, 225, 223, 223, 226, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 227, 228, 223, 223, 223, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 223, 223, 223, 223, 223, 223, 223, 224, 223, 224, 225, 223, 223, 223, 223, 226, 223, 226, 223, 223, 223, 223, 223, 223, 223, 229, 223, 223, 223, 223, 223, 223, 223, 230, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 231, 228, 223, 223, 223, 223, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 223, 223, 223, 223, 223, 223, 223, 229, 223, 223, 223, 223, 223, 230, 223, 223, 223, 223, 223, 223, 231, 223, 223, 223, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 223, 223, 223, 223, 223, 223, 223, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 223, 223, 223, 223, 223, 223, 223, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 223, 223, 223, 228, 228, 228, 228, 228, 228, 223, 228, 228, 228, 228, 223, 228, 0, 223, 223, 223, 223, 223, 223, 223, 223] const yy_nxt = [0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 22, 22, 23, 24, 25, 26, 27, 28, 29, 30, 30, 30, 31, 4, 32, 33, 34, 35, 36, 37, 38, 39, 30, 40, 30, 30, 30, 41, 30, 30, 42, 43, 44, 30, 45, 46, 30, 30, 47, 48, 49, 50, 55, 58, 63, 60, 65, 69, 67, 86, 87, 88, 89, 222, 114, 72, 115, 70, 82, 66, 68, 59, 61, 73, 74, 83, 84, 85, 64, 221, 56, 75, 220, 76, 76, 76, 76, 75, 90, 76, 76, 76, 76, 103, 95, 77, 101, 91, 116, 92, 120, 77, 78, 122, 55, 77, 117, 106, 102, 63, 104, 77, 96, 79, 107, 219, 109, 131, 131, 108, 218, 80, 110, 138, 139, 97, 111, 128, 128, 128, 128, 121, 56, 64, 145, 146, 75, 123, 76, 76, 76, 76, 132, 132, 132, 159, 129, 217, 129, 160, 77, 130, 130, 130, 130, 128, 128, 128, 128, 216, 77, 130, 130, 130, 130, 131, 131, 165, 130, 130, 130, 130, 132, 132, 132, 173, 174, 165, 189, 215, 189, 214, 213, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 54, 54, 54, 54, 54, 57, 212, 57, 57, 57, 62, 62, 62, 62, 62, 93, 93, 94, 94, 94, 127, 211, 127, 127, 127, 133, 133, 140, 140, 140, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 188, 187, 186, 185, 184, 183, 182, 181, 180, 179, 178, 177, 176, 175, 172, 171, 170, 169, 168, 167, 166, 164, 163, 162, 161, 158, 157, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, 144, 143, 142, 141, 137, 136, 135, 134, 126, 223, 125, 223, 124, 52, 51, 119, 118, 113, 112, 105, 100, 99, 98, 81, 71, 53, 52, 51, 223, 3, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223] const yy_chk = [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 10, 12, 11, 15, 18, 16, 26, 26, 27, 27, 217, 43, 20, 43, 18, 25, 15, 16, 10, 11, 20, 20, 25, 25, 25, 12, 216, 8, 21, 213, 21, 21, 21, 21, 22, 28, 22, 22, 22, 22, 37, 31, 21, 36, 28, 44, 28, 47, 22, 21, 48, 54, 21, 44, 39, 36, 62, 37, 22, 31, 21, 39, 212, 40, 78, 78, 39, 211, 21, 40, 89, 89, 31, 40, 75, 75, 75, 75, 47, 54, 62, 101, 101, 76, 48, 76, 76, 76, 76, 79, 79, 79, 117, 77, 210, 77, 117, 76, 77, 77, 77, 77, 128, 128, 128, 128, 209, 76, 129, 129, 129, 129, 131, 131, 128, 130, 130, 130, 130, 132, 132, 132, 147, 147, 128, 165, 204, 165, 202, 201, 165, 165, 165, 165, 189, 189, 189, 189, 190, 190, 190, 190, 224, 224, 224, 224, 224, 225, 199, 225, 225, 225, 226, 226, 226, 226, 226, 227, 227, 228, 228, 228, 229, 198, 229, 229, 229, 230, 230, 231, 231, 231, 197, 191, 188, 187, 186, 184, 183, 182, 181, 179, 178, 176, 175, 174, 173, 172, 170, 169, 168, 166, 164, 163, 162, 159, 158, 157, 156, 155, 154, 152, 151, 150, 149, 148, 146, 145, 144, 143, 141, 139, 136, 121, 120, 119, 118, 116, 115, 114, 113, 112, 108, 107, 106, 105, 104, 103, 102, 100, 97, 96, 95, 86, 85, 84, 83, 65, 64, 58, 56, 53, 52, 51, 46, 45, 42, 41, 38, 35, 34, 33, 23, 19, 7, 6, 5, 3, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223, 223] const YY_JAM_STATE = 223 const YY_ACCEPT_LENGTH = 224 } ================================================ FILE: src/frontend/log.sk ================================================ namespace Skew { enum DiagnosticKind { ERROR WARNING } class Fix { const kind FixKind const range Range const description string const replacement string } enum FixKind { ARRAY_SYNTAX CASE_BRACES CASE_COMMA EXTENDS_IMPLEMENTS EXTRA_CALL_PARENTHESES EXTRA_CAST EXTRA_COLON EXTRA_COMMA EXTRA_DEF_PARENTHESES EXTRA_SEMICOLON FOR_LOOP_VAR MISSING_DEF MISSING_VAR NEED_VAR NEW_OPERATOR NEW_RETURN_TYPE OCTAL_ADD_PREFIX OCTAL_REMOVE_ZEROS OPERATOR_TYPO SELF_VS_THIS SINGLE_QUOTES SLASH_COMMENT SYMBOL_TYPO UNNECESSARY_PARENTHESES VOID_RETURN } class Diagnostic { const kind DiagnosticKind const range Range const text string const wasWarning bool var noteRange Range = null var noteText = "" var fixes List = null def withFix(kind FixKind, range Range, description string, replacement string) Diagnostic { if range != null && replacement != null { (fixes ?= []).append(Fix.new(kind, range, description, replacement)) } return self } def withNote(range Range, text string) Diagnostic { if range != null { noteRange = range noteText = text } return self } } namespace Diagnostic { def format(kind string, range Range, text string) string { if range == null { return "\(kind): \(text)\n" } var formatted = range.format(0) return "\(range.locationString): \(kind): \(text)\n\(formatted.line)\n\(formatted.range)\n" } } class Log { var diagnostics List = [] var appendCallback fn(Diagnostic) = null var warningsAreErrors = false var _warningCount = 0 var _errorCount = 0 var _wasWarningCount = 0 def isEmpty bool { return diagnostics.isEmpty } def hasErrors bool { return _errorCount != 0 } def hasWarnings bool { return _warningCount != 0 } def warningCount int { return _warningCount } def errorCount int { return _errorCount } def wasWarningCount int { return _wasWarningCount } def newError(range Range, text string) Diagnostic { return Diagnostic.new(.ERROR, range, text, false) } def newWarning(range Range, text string) Diagnostic { return Diagnostic.new(warningsAreErrors ? .ERROR : .WARNING, range, text, warningsAreErrors) } def toString string { var builder = StringBuilder.new # Emit the log assuming an infinite terminal width for diagnostic in diagnostics { builder.append(Diagnostic.format(diagnostic.kind == .ERROR ? "error" : "warning", diagnostic.range, diagnostic.text)) # Append notes after the diagnostic they apply to if diagnostic.noteRange != null { builder.append(Diagnostic.format("note", diagnostic.noteRange, diagnostic.noteText)) } } return builder.toString } # This is useful for visualizing the diagnostic fixes def fixesToString string { var builder = StringBuilder.new for diagnostic in diagnostics { if diagnostic.fixes != null { for fix in diagnostic.fixes { var formatted = fix.range.format(0) var index = formatted.range.lastIndexOf(" ") var indent = index != -1 ? formatted.range.slice(0, index + 1) : "" builder.append(fix.range.locationString + ": fix: " + fix.description + "\n") builder.append("\(formatted.line)\n\(formatted.range)\n\(indent)[\(fix.replacement)]\n") } } } return builder.toString } def append(diagnostic Diagnostic) { diagnostics.append(diagnostic) if diagnostic.kind == .ERROR { _errorCount++ } else { _warningCount++ } if diagnostic.wasWarning { _wasWarningCount++ } if appendCallback != null { appendCallback(diagnostic) } } } # Syntax warnings can be thought of as linting class Log { def syntaxWarningIgnoredCommentInParser(range Range) { append(newWarning(range, "This comment was ignored by the parser")) } def syntaxWarningIgnoredCommentInEmitter(range Range) { append(newWarning(range, "This comment was ignored by the emitter")) } def syntaxWarningOctal(range Range) { var text = range.toString while text.startsWith("0") { text = text.slice(1) } append(newWarning(range, "Number interpreted as decimal (use the prefix \"0o\" for octal numbers)") .withFix(.OCTAL_REMOVE_ZEROS, range, "Remove the leading zeros to avoid confusion", text) .withFix(.OCTAL_ADD_PREFIX, range, "Add the prefix \"0o\" to interpret the number as octal", "0o" + text)) } def syntaxWarningExtraParentheses(range Range) { var leftSpace = range.rangeIncludingLeftWhitespace.start == range.start ? " " : "" var rightSpace = range.rangeIncludingRightWhitespace.end == range.end ? " " : "" var text = range.toString append( newWarning(range, "Unnecessary parentheses") .withFix(.UNNECESSARY_PARENTHESES, range, "Remove parentheses", leftSpace + text.slice(1, text.count - 1) + rightSpace)) } def syntaxWarningExtraComma(range Range) { append( newWarning(range, "Unnecessary comma") .withFix(.EXTRA_COMMA, range, "Remove comma", "")) } } class Log { def syntaxErrorInvalidEscapeSequence(range Range) { append(newError(range, "Invalid escape sequence")) } def syntaxErrorIntegerLiteralTooLarge(range Range) { append(newError(range, "Integer literal is too big to fit in 32 bits")) } def syntaxErrorInvalidCharacter(range Range) { append( newError(range, "Use double quotes for strings (single quotes are for character literals)") .withFix(.SINGLE_QUOTES, range, "Replace single quotes with double quotes", replaceSingleQuotesWithDoubleQuotes(range.toString))) } def syntaxErrorExtraData(range Range, text string) { append(newError(range, "Syntax error \"\(text == "\"" ? "\\\"": text)\"")) } def syntaxErrorExtraColonBeforeType(range Range) { append( newError(range, "Do not use a colon before a type expression") .withFix(.EXTRA_COLON, range, "Remove the colon", "")) } def syntaxErrorNewOperator(range Range, correction string) { append( newError(range, "There is no \"new\" operator, use \"\(correction)\" instead") .withFix(.NEW_OPERATOR, range, "Replace with \"\(correction)\"", correction)) } def syntaxErrorExtendsKeyword(range Range) { append( newError(range, "Use \":\" instead of \"extends\" to indicate a base class") .withFix(.EXTENDS_IMPLEMENTS, range, "Replace \"extends\" with \":\"", ":")) } def syntaxErrorImplementsKeyword(range Range) { append( newError(range, "Use \"::\" instead of \"implements\" to indicate implemented interfaces") .withFix(.EXTENDS_IMPLEMENTS, range, "Replace \"implements\" with \"::\"", "::")) } def syntaxErrorMissingVar(range Range) { append( newError(range, "Use \"var\" before variable declarations") .withFix(.MISSING_VAR, range, "Insert \"var\"", "var \(range)")) } def syntaxErrorMissingDef(range Range) { append( newError(range, "Use \"def\" before function declarations") .withFix(.MISSING_DEF, range, "Insert \"def\"", "def \(range)")) } def syntaxErrorStaticKeyword(range Range, parentKind SymbolKind, parentName string) { append( newError(range, parentKind == .OBJECT_GLOBAL || parentKind == .OBJECT_NAMESPACE ? "There is no \"static\" keyword" : "There is no \"static\" keyword (declare this symbol in a namespace called \"\(parentName)\" instead)")) } def syntaxErrorPublicKeyword(range Range) { append(newError(range, "There is no \"public\" keyword")) } def syntaxErrorPrivateOrProtected(range Range) { append(newError(range, "There is no \"\(range)\" keyword (to give something protected access, use a name starting with \"_\" instead)")) } def syntaxErrorExpectedCommaBetweenCases(range Range) { append( newError(range, "Use a comma between multiple values in a case statement (example: \"case 1, 2, 3 { ... }\")") .withFix(.CASE_COMMA, range, "Replace this with a comma", ",")) } def syntaxErrorColonAfterCaseOrDefault(range Range, breakRange Range) { var diagnostic = newError(range, "Surround the body of case and default statements with \"{\" and \"}\" instead of \":\" and \"break\"") if breakRange != null { assert(range.source == breakRange.source) var start = range.source.indexToLineColumn(range.end) var end = range.source.indexToLineColumn(breakRange.start) var text = range.source.contents.slice(range.end, breakRange.start) # Use the indentation of the case statement for the "}" if start.line < end.line { text = text.slice(0, text.count - end.column) + indentOfLine(range.source.contentsOfLine(start.line)) + lineWithoutIndent(range.source.contentsOfLine(end.line).slice(0, end.column)) } diagnostic.withFix(.CASE_BRACES, Range.span(range.rangeIncludingLeftWhitespace, breakRange), "Replace \":\" and \"break\" with \"{\" and \"}\"", " {" + text + "}") } append(diagnostic) } def syntaxErrorOperatorTypo(range Range, correction string) { append( newError(range, "Use the \"\(correction)\" operator instead") .withFix(.OPERATOR_TYPO, range, "Replace with \"\(correction)\"", correction)) } def syntaxErrorExtraVarInForLoop(range Range) { append( newError(range, "The \"var\" keyword is unnecessary here since for loops automatically declare their variables") .withFix(.FOR_LOOP_VAR, range?.rangeIncludingRightWhitespace, "Remove \"var\"", "")) } def syntaxErrorWrongListSyntax(range Range, correction string) { append( newError(range, "The array type is \"List\"") .withFix(.ARRAY_SYNTAX, range, "Replace with \"\(correction)\"", correction)) } def syntaxErrorSlashComment(range Range) { var text = range.toString var last = text.count - 1 assert(text.startsWith("//")) if text[last] == '\n' { text = text.slice(0, last) range = range.fromStart(last) } # Change a run of "////" into "####" var replacement = "" for i in 1..text.count { if text[i] == '/' { replacement += "#" } else { replacement += text.slice(i) break } } append( newError(range, "Comments start with \"#\" instead of \"//\"") .withFix(.SLASH_COMMENT, range, "Replace \"//\" with \"#\"", replacement)) } def syntaxErrorUnexpectedToken(token Token) { append(newError(token.range, "Unexpected \(token.kind)")) } def syntaxErrorExpectedToken(range Range, found TokenKind, expected TokenKind) { var diagnostic = newError(range, "Expected \(expected) but found \(found)") if found == .SEMICOLON && expected == .NEWLINE { diagnostic.withFix(.EXTRA_SEMICOLON, range, "Remove \";\"", "") } append(diagnostic) } def syntaxErrorEmptyFunctionParentheses(range Range) { append( newError(range, "Functions without arguments do not use parentheses") .withFix(.EXTRA_DEF_PARENTHESES, range, "Remove parentheses", "")) } def syntaxErrorBadDeclarationInsideType(range Range) { append(newError(range, "Cannot use this declaration here")) } def syntaxErrorBadOperatorCustomization(range Range, kind TokenKind, why string) { append(newError(range, "The \(kind) operator is not customizable because \(why)")) } def syntaxErrorVariableDeclarationNeedsVar(range Range, name Range) { append( newError(range, "Declare variables using \"var\" and put the type after the variable name") .withFix(.NEED_VAR, Range.span(range, name), "Declare \"\(name)\" correctly", "var \(name) \(range)")) } def syntaxErrorXMLClosingTagMismatch(range Range, found string, expected string, openingRange Range) { append( newError(range, "Expected \"\(expected)\" but found \"\(found)\" in XML literal") .withNote(openingRange, "Attempted to match opening tag here")) } def syntaxErrorOptionalArgument(range Range) { append(newError(range, "Optional arguments aren't supported yet")) } } namespace Log { def _expectedCountText(singular string, expected int, found int) string { return "Expected \(PrettyPrint.plural(expected, singular)) but found \(PrettyPrint.plural(found, singular))" } def _formatArgumentTypes(types List) string { if types == null { return "" } var names List = [] for type in types { names.append(type.toString) } return " of type\(PrettyPrint.plural(types.count)) \(PrettyPrint.join(names, "and"))" } } class Log { def semanticWarningInliningFailed(range Range, name string) { append(newWarning(range, "Cannot inline function \"\(name)\"")) } def semanticWarningIdenticalOperands(range Range, operator string) { append(newWarning(range, "Both sides of \"\(operator)\" are identical, is this a bug?")) } def semanticWarningSuspiciousAssignmentLocation(range Range) { append(newWarning(range, "Use of \"=\" here looks like a bug, did you mean to use \"==\"?")) } def semanticWarningShiftByZero(range Range) { append(newWarning(range, "Shifting an integer by zero doesn't do anything, is this a bug?")) } def semanticWarningUnusedExpression(range Range) { append(newWarning(range, "Unused expression")) } def semanticErrorXMLMissingAppend(range Range, type Type) { append(newError(range, "Implement a function called \"<>...\" on type \"\(type)\" to add support for child elements")) } def semanticErrorComparisonOperatorNotInt(range Range) { append(newError(range, "The comparison operator must have a return type of \"int\"")) } def semanticErrorDuplicateSymbol(range Range, name string, previous Range) { append( newError(range, "\"\(name)\" is already declared") .withNote(previous, "The previous declaration is here")) } def semanticErrorShadowedSymbol(range Range, name string, previous Range) { append( newError(range, "\"\(name)\" shadows a previous declaration") .withNote(previous, "The previous declaration is here")) } def semanticErrorDuplicateTypeParameters(range Range, name string, previous Range) { append( newError(range, "\"\(name)\" already has type parameters") .withNote(previous, "Type parameters were previously declared here")) } def semanticErrorDuplicateBaseType(range Range, name string, previous Range) { append( newError(range, "\"\(name)\" already has a base type") .withNote(previous, "The previous base type is here")) } def semanticErrorCyclicDeclaration(range Range, name string) { append(newError(range, "Cyclic declaration of \"\(name)\"")) } def semanticErrorUndeclaredSymbol(range Range, name string, correction string, correctionRange Range) { var diagnostic = newError(range, "\"\(name)\" is not declared" + (correction != null ? ", did you mean \"\(correction)\"?" : "")) if correction != null && correctionRange != null { diagnostic .withNote(correctionRange, "\"\(correction)\" is defined here") .withFix(.SYMBOL_TYPO, range, "Replace with \"\(correction)\"", correction) } append(diagnostic) } def semanticErrorUndeclaredSelfSymbol(range Range, name string) { append( newError(range, "\"\(name)\" is not declared (use \"self\" to refer to the object instance)") .withFix(.SELF_VS_THIS, range, "Replace \"\(name)\" with \"self\"", "self")) } def semanticErrorUnknownMemberSymbol(range Range, name string, type Type, correction string, correctionRange Range) { var diagnostic = newError(range, "\"\(name)\" is not declared on type \"\(type)\"" + (correction != null ? ", did you mean \"\(correction)\"?" : "")) if correction != null && correctionRange != null { diagnostic .withNote(correctionRange, "\"\(correction)\" is defined here") .withFix(.SYMBOL_TYPO, range, "Replace with \"\(correction)\"", correction) } append(diagnostic) } def semanticErrorVarMissingType(range Range, name string) { append(newError(range, "Unable to determine the type of \"\(name)\"")) } def semanticErrorVarMissingValue(range Range, name string) { append(newError(range, "The implicitly typed variable \"\(name)\" must be initialized")) } def semanticErrorConstMissingValue(range Range, name string) { append(newError(range, "The constant \"\(name)\" must be initialized")) } def semanticErrorInvalidCall(range Range, type Type) { append(newError(range, "Cannot call value of type \"\(type)\"")) } def semanticErrorCannotParameterize(range Range, type Type) { append(newError(range, "Cannot parameterize \"\(type)\"" + ( type.isParameterized ? " because it is already parameterized" : " because it has no type parameters"))) } def semanticErrorParameterCount(range Range, expected int, found int) { append(newError(range, _expectedCountText("type parameter", expected, found))) } def semanticErrorArgumentCount(range Range, expected int, found int, name string, function Range) { append( newError(range, _expectedCountText("argument", expected, found) + (name != null ? " when calling \"\(name)\"" : "")) .withNote(function, "The function declaration is here")) } def semanticErrorGetterRequiresWrap(range Range, name string, function Range) { append( newError(range, "Wrap calls to the function \"\(name)\" in parentheses to call the returned lambda") .withNote(function, "The function declaration is here")) } def semanticErrorGetterCalledTwice(range Range, name string, function Range) { var diagnostic = newError(range, "Cannot call the value returned from the function \"\(name)\" (this function was called automatically because it takes no arguments)") .withNote(function, "The function declaration is here") if range.toString == "()" { diagnostic.withFix(.EXTRA_CALL_PARENTHESES, range, "Remove the unnecessary \"()\"", "") } append(diagnostic) } def semanticErrorUseOfVoidFunction(range Range, name string, function Range) { append( newError(range, "The function \"\(name)\" does not return a value") .withNote(function, "The function declaration is here")) } def semanticErrorUseOfVoidLambda(range Range) { append(newError(range, "This call does not return a value")) } def semanticErrorBadImplicitVariableType(range Range, type Type) { append(newError(range, "Implicitly typed variables cannot be of type \"\(type)\"")) } def semanticErrorNoDefaultValue(range Range, type Type) { append(newError(range, "Cannot construct a default value of type \"\(type)\"")) } def semanticErrorMemberUnexpectedGlobal(range Range, name string) { append(newError(range, "Cannot access global member \"\(name)\" from an instance context")) } def semanticErrorMemberUnexpectedInstance(range Range, name string) { append(newError(range, "Cannot access instance member \"\(name)\" from a global context")) } def semanticErrorMemberUnexpectedTypeParameter(range Range, name string) { append(newError(range, "Cannot access type parameter \"\(name)\" here")) } def semanticErrorConstructorReturnType(range Range) { append( newError(range, "Constructors cannot have a return type") .withFix(.NEW_RETURN_TYPE, range?.rangeIncludingLeftWhitespace, "Remove the return type", "")) } def semanticErrorNoMatchingOverload(range Range, name string, count int, types List) { append(newError(range, "No overload of \"\(name)\" was found that takes \(PrettyPrint.plural(count, "argument"))\(_formatArgumentTypes(types))")) } def semanticErrorAmbiguousOverload(range Range, name string, count int, types List) { append(newError(range, "Multiple matching overloads of \"\(name)\" were found that can take \(PrettyPrint.plural(count, "argument"))\(_formatArgumentTypes(types))")) } def semanticErrorUnexpectedExpression(range Range, type Type) { append(newError(range, "Unexpected expression of type \"\(type)\"")) } def semanticErrorUnexpectedType(range Range, type Type) { append(newError(range, "Unexpected type \"\(type)\"")) } def semanticErrorIncompatibleTypes(range Range, from Type, to Type, isCastAllowed bool) { append(newError(range, "Cannot convert from type \"\(from)\" to type \"\(to)\"\(isCastAllowed ? " without a cast" : "")")) } def semanticErrorInvalidDefine(range Range, value string, type Type, name string) { append(newError(range, "Cannot convert \"\(value)\" to type \"\(type)\" for variable \"\(name)\"")) } def semanticWarningExtraCast(range Range, from Type, to Type) { append( newWarning(range, "Unnecessary cast from type \"\(from)\" to type \"\(to)\"") .withFix(.EXTRA_CAST, range?.rangeIncludingLeftWhitespace, "Remove the cast", "")) } def semanticWarningExtraTypeCheck(range Range, from Type, to Type) { append(newWarning(range, "Unnecessary type check, type \"\(from)\" is always type \"\(to)\"")) } def semanticWarningBadTypeCheck(range Range, type Type) { append(newError(range, "Cannot check against interface type \"\(type)\"")) } def semanticErrorWrongArgumentCount(range Range, name string, count int) { append(newError(range, "Expected \"\(name)\" to take \(PrettyPrint.plural(count, "argument"))")) } def semanticErrorWrongArgumentCountRange(range Range, name string, values List) { assert(!values.isEmpty) var first = values.first var count = values.count if count == 1 { semanticErrorWrongArgumentCount(range, name, first) } else { var counts List = [] var min = first var max = first var text string for value in values { min = Math.min(min, value) max = Math.max(max, value) counts.append(value.toString) } # Assuming values are unique, this means all values form a continuous range if max - min + 1 == count { if min == 0 { text = "Expected \"\(name)\" to take at most \(PrettyPrint.plural(max, "argument"))" } else { text = "Expected \"\(name)\" to take between \(min) and \(max) arguments" } } # Otherwise, the values are disjoint else { text = "Expected \"\(name)\" to take either \(PrettyPrint.join(counts, "or")) arguments" } append(newError(range, text)) } } def semanticErrorExpectedList(range Range, name string, type Type) { append(newError(range, "Expected argument \"\(name)\" to be of type \"List\" instead of type \"\(type)\"")) } def semanticErrorUnexpectedReturnValue(range Range) { append(newError(range, "Cannot return a value inside a function without a return type")) } def semanticErrorBadReturnType(range Range, type Type) { append(newError(range, "Cannot create a function with a return type of \"\(type)\"")) } def semanticErrorVoidReturnType(range Range) { append( newError(range, "There is no explicit \"void\" return type (to indicate that there's nothing to return, just don't put a return type)") .withFix(.VOID_RETURN, range?.rangeIncludingLeftWhitespace, "Remove \"void\"", "")) } def semanticErrorExpectedReturnValue(range Range, type Type) { append(newError(range, "Must return a value of type \"\(type)\"")) } def semanticErrorMissingReturn(range Range, name string, type Type) { append(newError(range, "All control paths for \"\(name)\" must return a value of type \"\(type)\"")) } def semanticErrorBadStorage(range Range) { append(newError(range, "Cannot store to this location")) } def semanticErrorStorageToConstSymbol(range Range, name string) { append(newError(range, "Cannot store to constant symbol \"\(name)\"")) } def semanticErrorAccessViolation(range Range, name string) { append(newError(range, "Cannot access protected symbol \"\(name)\" here")) } def semanticWarningDeprecatedUsage(range Range, name string) { append(newWarning(range, "Use of deprecated symbol \"\(name)\"")) } def semanticErrorUnparameterizedType(range Range, type Type) { append(newError(range, "Cannot use unparameterized type \"\(type)\" here")) } def semanticErrorParameterizedType(range Range, type Type) { append(newError(range, "Cannot use parameterized type \"\(type)\" here")) } def semanticErrorNoCommonType(range Range, left Type, right Type) { append(newError(range, "No common type for \"\(left)\" and \"\(right)\"")) } def semanticErrorInvalidAnnotation(range Range, annotation string, name string) { append(newError(range, "Cannot use the annotation \"\(annotation)\" on \"\(name)\"")) } def semanticWarningDuplicateAnnotation(range Range, annotation string, name string) { append(newWarning(range, "Duplicate annotation \"\(annotation)\" on \"\(name)\"")) } def semanticWarningRedundantAnnotation(range Range, annotation string, name string, parent string) { append(newWarning(range, "Redundant annotation \"\(annotation)\" on \"\(name)\" is already inherited from type \"\(parent)\"")) } def semanticErrorBadForValue(range Range, type Type) { append(newError(range, "Cannot iterate over type \"\(type)\"")) } def semanticWarningEmptyRange(range Range) { append(newWarning(range, "This range is empty")) } def semanticErrorMissingDotContext(range Range, name string) { append(newError(range, "Cannot access \"\(name)\" without type context")) } def semanticErrorInitializerTypeInferenceFailed(range Range) { append(newError(range, "Cannot infer a type for this literal")) } def semanticErrorInitializerRecursiveExpansion(range Range, newRange Range) { append( newError(range, "Attempting to resolve this literal led to recursive expansion") .withNote(newRange, "The constructor that was called recursively is here")) } def semanticErrorXMLCannotConstruct(range Range, type Type) { append(newError(range, "Cannot construct type \"\(type)\"")) } def semanticErrorDuplicateOverload(range Range, name string, previous Range) { append( newError(range, "Duplicate overloaded function \"\(name)\"") .withNote(previous, "The previous declaration is here")) } def semanticErrorInvalidExtends(range Range, type Type) { append(newError(range, "Cannot extend type \"\(type)\"")) } def semanticErrorInvalidImplements(range Range, type Type) { append(newError(range, "Cannot implement type \"\(type)\"")) } def semanticErrorDuplicateImplements(range Range, type Type, previous Range) { append( newError(range, "Duplicate implemented type \"\(type)\"") .withNote(previous, "The first occurrence is here")) } def semanticErrorBadInterfaceImplementation(range Range, classType Type, interfaceType Type, name string, reason Range) { append( newError(range, "Type \"\(classType)\" is missing an implementation of function \"\(name)\" from interface \"\(interfaceType)\"") .withNote(reason, "The function declaration is here")) } def semanticErrorBadInterfaceImplementationReturnType(range Range, name string, found Type, expected Type, interfaceType Type, reason Range) { append( newError(range, found != null && expected != null ? "Function \"\(name)\" has unexpected return type \"\(found)\", expected return type \"\(expected)\" " + "to match the function with the same name and argument types from interface \"\(interfaceType)\"" : "Expected the return type of function \"\(name)\" to match the function with the same name and argument types from interface \"\(interfaceType)\"") .withNote(reason, "The function declaration is here")) } def semanticErrorBadOverride(range Range, name string, base Type, overridden Range) { append( newError(range, "\"\(name)\" overrides another declaration with the same name in base type \"\(base)\"") .withNote(overridden, "The overridden declaration is here")) } def semanticErrorBadOverrideReturnType(range Range, name string, base Type, overridden Range) { append( newError(range, "\"\(name)\" overrides another function with the same name and argument types but a different return type in base type \"\(base)\"") .withNote(overridden, "The overridden function is here")) } def semanticErrorModifierMissingOverride(range Range, name string, overridden Range) { append( newError(range, "\"\(name)\" overrides another symbol with the same name but is declared using \"def\" instead of \"over\"") .withNote(overridden, "The overridden declaration is here")) } def semanticErrorModifierUnusedOverride(range Range, name string) { append(newError(range, "\"\(name)\" is declared using \"over\" instead of \"def\" but does not override anything")) } def semanticErrorBadSuper(range Range) { append(newError(range, "Cannot use \"super\" here")) } def semanticErrorBadJump(range Range, name string) { append(newError(range, "Cannot use \"\(name)\" outside a loop")) } def semanticErrorMustCallFunction(range Range, name string, lower int, upper int) { append(newError(range, lower == upper ? "The function \"\(name)\" takes \(PrettyPrint.plural(lower, "argument")) and must be called" : "The function \"\(name)\" takes between \(lower) and \(upper) arguments and must be called")) } def semanticErrorDuplicateEntryPoint(range Range, previous Range) { append( newError(range, "Multiple entry points are declared") .withNote(previous, "The first entry point is here")) } def semanticErrorInvalidEntryPointArguments(range Range, name string) { append(newError(range, "Entry point \"\(name)\" must take either no arguments or one argument of type \"List\"")) } def semanticErrorInvalidEntryPointReturnType(range Range, name string) { append(newError(range, "Entry point \"\(name)\" must return either nothing or a value of type \"int\"")) } def semanticErrorInvalidDefine(range Range, name string) { append(newError(range, "Could not find a variable named \"\(name)\" to override")) } def semanticErrorExpectedConstant(range Range) { append(newError(range, "This value must be a compile-time constant")) } def semanticWarningUnreadLocalVariable(range Range, name string) { append(newWarning(range, "Local variable \"\(name)\" is never read")) } def semanticErrorAbstractNew(range Range, type Type, reason Range, name string) { append( newError(range, "Cannot construct abstract type \"\(type)\"") .withNote(reason, "The type \"\(type)\" is abstract due to member \"\(name)\"")) } def semanticErrorUnimplementedFunction(range Range, name string) { append(newError(range, "Non-imported function \"\(name)\" is missing an implementation (use the \"@import\" annotation if it's implemented externally)")) } def semanticErrorDefaultCaseNotLast(range Range) { append(newError(range, "The default case in a switch statement must come last")) } def semanticErrorForLoopDifferentType(range Range, name string, found Type, expected Type) { append(newError(range, "Expected loop variable \"\(name)\" to be of type \"\(expected)\" instead of type \"\(found)\"")) } def semanticErrorDuplicateCase(range Range, previous Range) { append( newError(range, "Duplicate case value") .withNote(previous, "The first occurrence is here")) } def semanticErrorMissingWrappedType(range Range, name string) { append(newError(range, "Missing base type for wrapped type \"\(name)\"")) } def semanticErrorDuplicateRename(range Range, name string, optionA string, optionB string) { append(newError(range, "Cannot rename \"\(name)\" to both \"\(optionA)\" and \"\(optionB)\"")) } def semanticErrorMissingSuper(range Range) { append(newError(range, "Constructors for derived types must start with a call to \"super\"")) } def semanticErrorTooManyFlags(range Range, name string) { append(newError(range, "The type \"\(name)\" cannot have more than 32 flags")) } } } ================================================ FILE: src/frontend/parser.sk ================================================ namespace Skew { enum PassKind { PARSING } class ParsingPass : Pass { over kind PassKind { return .PARSING } over run(context PassContext) { for tokens in context.tokens { Parsing.parseFile(context.log, tokens, context.global, context.options.warnAboutIgnoredComments) } } } } namespace Skew.Parsing { var expressionParser Pratt var typeParser Pratt # Parser recovery is done by skipping to the next closing token after an error def scanForToken(context ParserContext, kind TokenKind) { if context.expect(kind) { return } # Scan forward for the token while !context.peek(.END_OF_FILE) { if context.eat(kind) { return } switch context.current.kind { # Stop at the next closing token case .RIGHT_PARENTHESIS, .RIGHT_BRACKET, .RIGHT_BRACE { return } # Optionally recover parsing before the next statement if it's unambiguous case .BREAK, .CATCH, .CONST, .CONTINUE, .ELSE, .FINALLY, .FOR, .IF, .RETURN, .TRY, .VAR, .WHILE { return } } context.next } } def parseIntLiteral(log Log, range Range) Box { var text = range.toString var isNegative = text.startsWith("-") # Parse negative signs for use with the "--define" flag var start = isNegative as int var count = text.count var doubleValue = 0.0 var intValue = 0 var base = 10 # Parse the base if start + 2 < count && text[start] == '0' { var c = text[start + 1] if c == 'b' { base = 2 start += 2 } else if c == 'o' { base = 8 start += 2 } else if c == 'x' { base = 16 start += 2 } } # There must be numbers after the base if start == count { return null } # Special-case hexadecimal since it's more complex if base == 16 { for i in start..count { var c = text[i] if (c < '0' || c > '9') && (c < 'A' || c > 'F') && (c < 'a' || c > 'f') { return null } var delta = c - (c <= '9' ? '0' : c <= 'F' ? 'A' - 10 : 'a' - 10) doubleValue = doubleValue * 16 + delta intValue = intValue * 16 + delta } } # All other bases are zero-relative else { for i in start..count { var delta = text[i] - '0' if delta < 0 || delta >= base { return null } doubleValue = doubleValue * base + delta intValue = intValue * base + delta } } # Integer literals are only an error if they are outside both the signed and # unsigned 32-bit integer ranges. Integers here are 32-bit signed integers # but it can be convenient to write literals using unsigned notation and # have the compiler do the wrapping (for example, all Mach-O files use a # magic number of 0xFEEDFACE). if doubleValue < -2147483648.0 || doubleValue > 4294967295.0 { log.syntaxErrorIntegerLiteralTooLarge(range) return Box.new(intValue) } # Warn about decimal integers that start with "0" because other languages # strangely treat these numbers as octal instead of decimal if base == 10 && intValue != 0 && text[0] == '0' { log.syntaxWarningOctal(range) } return Box.new(isNegative ? -intValue : intValue) } def checkExtraParentheses(context ParserContext, node Node) { if node.isInsideParentheses { context.log.syntaxWarningExtraParentheses(node.range) } } def _warnAboutIgnoredComments(context ParserContext, comments List) { if comments != null && context.warnAboutIgnoredComments { for comment in comments { context.log.syntaxWarningIgnoredCommentInParser(comment.range) } } } def parseTrailingComment(context ParserContext) List { switch context.current.kind { case .NEWLINE, .END_OF_FILE, .RIGHT_PARENTHESIS, .RIGHT_BRACE, .RIGHT_BRACKET { return context.stealComments } } return null } def parseAnnotations(context ParserContext, annotations List) List { while context.peek(.ANNOTATION) { var range = context.next.range var value = Node.createName(range.toString).withRange(range) # Change "@foo.bar.baz" into "foo.bar.@baz" if context.peek(.DOT) { var root = value.asString value.content = StringContent.new(root.slice(1)) while context.eat(.DOT) { var name = context.current.range if !context.expect(.IDENTIFIER) { break } value = Node.createDot(value, name.toString).withRange(context.spanSince(range)).withInternalRange(name) } value.content = StringContent.new("@" + value.asString) } # Parse parentheses if present var token = context.current if context.eat(.LEFT_PARENTHESIS) { var call = Node.createCall(value) parseCommaSeparatedList(context, call, .RIGHT_PARENTHESIS) value = call.withRange(context.spanSince(range)).withInternalRange(context.spanSince(token.range)) } # Parse a trailing if condition var test Node = null if context.eat(.IF) { test = expressionParser.parse(context, .LOWEST) } # All annotations must end in a newline to avoid confusion with the trailing if if !context.peek(.LEFT_BRACE) && !context.expect(.NEWLINE) { scanForToken(context, .NEWLINE) } annotations.append(Node.createAnnotation(value, test).withRange(context.spanSince(range))) } return annotations } # When the type is present, this parses something like "int x = 0" def parseVariables(context ParserContext, type Node) Node { var variables = Node.createVariables var token = context.current # Skip "var" or "const" if present if type == null { context.next } while true { var range = context.current.range if !context.expect(.IDENTIFIER) { return null } var symbol = VariableSymbol.new(.VARIABLE_LOCAL, range.toString) symbol.range = range if token.kind == .CONST { symbol.flags |= .IS_CONST } if type != null { symbol.type = type.clone } else { if context.peek(.COLON) { context.log.syntaxErrorExtraColonBeforeType(context.next.range) } if peekType(context) { symbol.type = parseType(context) } } if context.eat(.ASSIGN) { symbol.value = expressionParser.parse(context, .LOWEST) } variables.appendChild(Node.createVariable(symbol).withRange(context.spanSince(range))) if !context.eat(.COMMA) { break } } return variables.withRange(context.spanSince(type != null ? type.range : token.range)) } def parseJump(context ParserContext) Node { var token = context.next return (token.kind == .BREAK ? Node.createBreak : Node.createContinue).withRange(token.range) } def parseReturn(context ParserContext) Node { var token = context.next var value Node = null # Check for "return;" explicitly for a better error message if !context.skipSemicolon && !context.peek(.NEWLINE) && !context.peek(.COMMENT) && !context.peek(.RIGHT_BRACE) { value = expressionParser.parse(context, .LOWEST) checkExtraParentheses(context, value) } return Node.createReturn(value).withRange(context.spanSince(token.range)) } def parseSwitch(context ParserContext) Node { var token = context.next var value = expressionParser.parse(context, .LOWEST) checkExtraParentheses(context, value) var node = Node.createSwitch(value) context.skipWhitespace if !context.expect(.LEFT_BRACE) { return null } context.eat(.NEWLINE) while !context.peek(.RIGHT_BRACE) { var comments = context.stealComments # Ignore trailing comments if context.peek(.RIGHT_BRACE) || context.peek(.END_OF_FILE) { _warnAboutIgnoredComments(context, comments) break } # Parse a new case var child = Node.createCase var start = context.current var colon Range = null if context.eat(.CASE) { while true { var constantComments = context.stealComments var constant = expressionParser.parse(context, .LOWEST) checkExtraParentheses(context, constant) child.appendChild(constant) constant.comments = Comment.concat(constantComments, parseTrailingComment(context)) # A colon isn't valid syntax here, but try to have a nice error message if context.peek(.COLON) { colon = context.next.range context.skipWhitespace if context.eat(.CASE) { context.log.syntaxErrorExpectedCommaBetweenCases(context.spanSince(colon)) colon = null } else { break } } # Commas separate multiple values else if !context.eat(.COMMA) { break } constant.comments = Comment.concat(constant.comments, parseTrailingComment(context)) } } # Default cases have no values else { if !context.eat(.DEFAULT) { context.expect(.CASE) return null } # A colon isn't valid syntax here, but try to have a nice error message if context.peek(.COLON) { colon = context.next.range } } var block Node # Use a block instead of requiring "break" at the end if colon == null { block = parseBlock(context) if block == null { return null } } # If there was a syntax error, try to parse a C-style block else { var range = context.current.range block = Node.createBlock if !parseStatements(context, block, .C_STYLE_SWITCH) { return null } var breakRange Range = null if context.peek(.BREAK) { breakRange = context.next.range if context.skipSemicolon { breakRange = context.spanSince(breakRange) } } block.withRange(context.spanSince(range)) context.log.syntaxErrorColonAfterCaseOrDefault(colon, breakRange) context.eat(.NEWLINE) } # Create the case node.appendChild(child.appendChild(block).withRange(context.spanSince(start.range))) # Parse trailing comments and/or newline child.comments = Comment.concat(comments, parseTrailingComment(context)) if context.peek(.RIGHT_BRACE) || colon == null && !context.expect(.NEWLINE) { break } } if !context.expect(.RIGHT_BRACE) { return null } return node.withRange(context.spanSince(token.range)) } def parseFor(context ParserContext) Node { var token = context.next var parentheses = context.peek(.LEFT_PARENTHESIS) ? context.next : null # Doing "for var i = ..." is an error if context.peek(.VAR) { context.log.syntaxErrorExtraVarInForLoop(context.next.range) } var initialNameRange = context.current.range if !context.expect(.IDENTIFIER) { return null } # for a in b {} if context.eat(.IN) { var symbol = VariableSymbol.new(.VARIABLE_LOCAL, initialNameRange.toString) symbol.range = initialNameRange var value = expressionParser.parse(context, .LOWEST) if context.eat(.DOT_DOT) { var second = expressionParser.parse(context, .LOWEST) value = Node.createPair(value, second).withRange(Range.span(value.range, second.range)) } checkExtraParentheses(context, value) # Allow parentheses around the loop header if parentheses != null { if !context.expect(.RIGHT_PARENTHESIS) { return null } context.log.syntaxWarningExtraParentheses(context.spanSince(parentheses.range)) } var block = parseBlock(context) if block == null { return null } return Node.createForeach(symbol, value, block).withRange(context.spanSince(token.range)) } # for a = 0; a < 10; a++ {} var setup = Node.createVariables context.undo while true { var nameRange = context.current.range if !context.expect(.IDENTIFIER) { return null } var symbol = VariableSymbol.new(.VARIABLE_LOCAL, nameRange.toString) symbol.range = nameRange if peekType(context) { symbol.type = parseType(context) } if context.eat(.ASSIGN) { symbol.value = expressionParser.parse(context, .LOWEST) checkExtraParentheses(context, symbol.value) } setup.appendChild(Node.createVariable(symbol).withRange(context.spanSince(nameRange))) if !context.eat(.COMMA) { break } } setup.range = context.spanSince(initialNameRange) if !context.expect(.SEMICOLON) { return null } var test = expressionParser.parse(context, .LOWEST) if !context.expect(.SEMICOLON) { return null } var update = expressionParser.parse(context, .LOWEST) # This is the one place in the grammar that sequence expressions are allowed if context.eat(.COMMA) { update = Node.createSequence.appendChild(update) while true { var value = expressionParser.parse(context, .LOWEST) update.appendChild(value) if !context.eat(.COMMA) { break } } } # Allow parentheses around the loop header if parentheses != null { if !context.expect(.RIGHT_PARENTHESIS) { return null } context.log.syntaxWarningExtraParentheses(context.spanSince(parentheses.range)) } var block = parseBlock(context) if block == null { return null } return Node.createFor(setup, test, update, block).withRange(context.spanSince(token.range)) } def parseIf(context ParserContext) Node { var token = context.next var test = expressionParser.parse(context, .LOWEST) checkExtraParentheses(context, test) var trueBlock = parseBlock(context) if trueBlock == null { return null } return Node.createIf(test, trueBlock, null).withRange(context.spanSince(token.range)) } def parseThrow(context ParserContext) Node { var token = context.next var value = expressionParser.parse(context, .LOWEST) checkExtraParentheses(context, value) return Node.createThrow(value).withRange(context.spanSince(token.range)) } def parseTry(context ParserContext) Node { var token = context.next var tryBlock = parseBlock(context) if tryBlock == null { return null } return Node.createTry(tryBlock).withRange(context.spanSince(token.range)) } def parseWhile(context ParserContext) Node { var token = context.next var test = expressionParser.parse(context, .LOWEST) checkExtraParentheses(context, test) var block = parseBlock(context) if block == null { return null } return Node.createWhile(test, block).withRange(context.spanSince(token.range)) } def parseStatement(context ParserContext) Node { var token = context.current switch token.kind { case .BREAK, .CONTINUE { return parseJump(context) } case .CONST, .VAR { return parseVariables(context, null) } case .FOR { return parseFor(context) } case .IF { return parseIf(context) } case .RETURN { return parseReturn(context) } case .SWITCH { return parseSwitch(context) } case .THROW { return parseThrow(context) } case .TRY { return parseTry(context) } case .WHILE { return parseWhile(context) } } var value = expressionParser.parse(context, .LOWEST) checkExtraParentheses(context, value) # A special case for better errors when users try to use C-style variable declarations if !value.isInsideParentheses && looksLikeType(value) && context.peek(.IDENTIFIER) && context.canReportSyntaxError { context.log.syntaxErrorVariableDeclarationNeedsVar(value.range, context.current.range) return parseVariables(context, value) } var node = Node.createExpression(value).withRange(value.range) return node } def looksLikeType(node Node) bool { var kind = node.kind return kind == .NAME || kind == .TYPE || kind == .LAMBDA_TYPE || kind == .DOT && node.dotTarget != null && looksLikeType(node.dotTarget) || kind == .PARAMETERIZE && looksLikeType(node.parameterizeValue) } enum StatementsMode { NORMAL C_STYLE_SWITCH } def parseStatements(context ParserContext, parent Node, mode StatementsMode) bool { var previous Node = null var leading = parseTrailingComment(context) context.eat(.NEWLINE) while !context.peek(.RIGHT_BRACE) && !context.peek(.XML_START_CLOSE) && !context.peek(.END_OF_FILE) { # The block may start with a trailing comment var comments = Comment.concat(leading, context.stealComments) leading = null # When parsing a C-style switch, stop if it looks like the end of the case if mode == .C_STYLE_SWITCH && (context.peek(.CASE) || context.peek(.DEFAULT) || context.peek(.BREAK)) { _warnAboutIgnoredComments(context, comments) break } # Merge "else" statements with the previous "if" if context.peek(.ELSE) { var isValid = previous != null && previous.kind == .IF && previous.ifFalse == null if !isValid { context.unexpectedToken } context.next # Match "else if" if context.peek(.IF) { var statement = parseIf(context) if statement == null { return false } # Append to the if statement var falseBlock = Node.createBlock.withRange(statement.range).appendChild(statement) falseBlock.comments = comments if isValid { previous.appendChild(falseBlock) previous = statement } else { previous = null } } # Match "else" else { var falseBlock = parseBlock(context) if falseBlock == null { return false } # Append to the if statement falseBlock.comments = comments if isValid { previous.appendChild(falseBlock) previous = falseBlock } else { previous = null } } } # Merge "catch" statements with the previous "try" else if context.peek(.CATCH) { var isValid = previous != null && previous.kind == .TRY && previous.finallyBlock == null if !isValid { context.unexpectedToken } var catchToken = context.next var symbol VariableSymbol = null var nameRange = context.current.range # Optional typed variable if context.eat(.IDENTIFIER) { symbol = VariableSymbol.new(.VARIABLE_LOCAL, nameRange.toString) symbol.range = nameRange symbol.type = parseType(context) } # Parse the block var catchBlock = parseBlock(context) if catchBlock == null { return false } # Append to the try statement var child = Node.createCatch(symbol, catchBlock).withRange(context.spanSince(catchToken.range)) child.comments = comments if isValid { previous.appendChild(child) } else { previous = null } } # Merge "finally" statements with the previous "try" else if context.peek(.FINALLY) { var isValid = previous != null && previous.kind == .TRY && previous.finallyBlock == null if !isValid { context.unexpectedToken } context.next # Parse the block var finallyBlock = parseBlock(context) if finallyBlock == null { return false } # Append to the try statement finallyBlock.comments = comments if isValid { previous.appendChild(finallyBlock) } else { previous = null } } # Parse a new statement else { var current = context.current var statement = parseStatement(context) if statement == null { scanForToken(context, .NEWLINE) continue } # Prevent an infinite loop due to a syntax error at the start of an expression if context.current == current { context.next } previous = statement statement.comments = comments parent.appendChild(statement) } # Special-case semicolons before comments for better parser recovery var semicolon = context.skipSemicolon # Parse trailing comments and/or newline var trailing = parseTrailingComment(context) if trailing != null { if previous != null { previous.comments = Comment.concat(previous.comments, trailing) } else { _warnAboutIgnoredComments(context, trailing) } } if context.peek(.RIGHT_BRACE) || context.peek(.XML_START_CLOSE) { break } else if !context.peek(.ELSE) && !context.peek(.CATCH) && !context.peek(.FINALLY) { if semicolon { context.eat(.NEWLINE) } else { context.expect(.NEWLINE) } } } # Convert trailing comments to blocks var comments = Comment.concat(leading, context.stealComments) if comments != null { parent.appendChild(Node.createCommentBlock.withComments(comments)) } return true } def parseBlock(context ParserContext) Node { context.skipWhitespace var comments = context.stealComments var token = context.current if !context.expect(.LEFT_BRACE) { # Return a block to try to recover and continue parsing return Node.createBlock } var block = Node.createBlock if !parseStatements(context, block, .NORMAL) || !context.expect(.RIGHT_BRACE) { return null } return block.withRange(context.spanSince(token.range)).withComments(comments) } def parseType(context ParserContext) Node { return typeParser.parse(context, .LOWEST) } def peekType(context ParserContext) bool { return context.peek(.IDENTIFIER) || context.peek(.DYNAMIC) } def parseFunctionBlock(context ParserContext, symbol FunctionSymbol) bool { # "=> x" is the same as "{ return x }" if symbol.kind == .FUNCTION_LOCAL { if !context.expect(.ARROW) { return false } if context.peek(.LEFT_BRACE) { symbol.block = parseBlock(context) if symbol.block == null { return false } } else { var value = expressionParser.parse(context, .LOWEST) symbol.block = Node.createBlock.withRange(value.range).appendChild(Node.createReturn(value).withRange(value.range).withFlags(.IS_IMPLICIT_RETURN)) } } # Parse function body if present else if context.peek(.LEFT_BRACE) { symbol.block = parseBlock(context) if symbol.block == null { return false } } return true } def parseFunctionArguments(context ParserContext, symbol FunctionSymbol) bool { var leading = parseTrailingComment(context) var usingTypes = false while !context.eat(.RIGHT_PARENTHESIS) { context.skipWhitespace var comments = Comment.concat(leading, context.stealComments) leading = null var range = context.current.range if !context.expect(.IDENTIFIER) { scanForToken(context, .RIGHT_PARENTHESIS) break } var arg = VariableSymbol.new(.VARIABLE_ARGUMENT, range.toString) arg.range = range # Parse argument type if symbol.kind != .FUNCTION_LOCAL || (symbol.arguments.isEmpty ? peekType(context) || context.peek(.COLON) : usingTypes) { if context.peek(.COLON) { context.log.syntaxErrorExtraColonBeforeType(context.next.range) } arg.type = parseType(context) usingTypes = true } # Optional arguments aren't supported yet var assign = context.current.range if context.eat(.ASSIGN) { expressionParser.parse(context, .LOWEST) context.log.syntaxErrorOptionalArgument(context.spanSince(assign)) } symbol.arguments.append(arg) if !context.peek(.RIGHT_PARENTHESIS) { if !context.expect(.COMMA) { scanForToken(context, .RIGHT_PARENTHESIS) break } } arg.comments = Comment.concat(comments, parseTrailingComment(context)) } return true } def parseFunctionReturnTypeAndBlock(context ParserContext, symbol FunctionSymbol) bool { if context.peek(.COLON) { context.log.syntaxErrorExtraColonBeforeType(context.next.range) } if peekType(context) { symbol.returnType = parseType(context) } if context.peek(.NEWLINE) && context.peek(.LEFT_BRACE, 1) { context.next } return parseFunctionBlock(context, symbol) } def parseTypeParameters(context ParserContext, kind SymbolKind) List { var parameters List = [] while true { var range = context.current.range var name = range.toString if !context.expect(.IDENTIFIER) { return null } var symbol = ParameterSymbol.new(kind, name) symbol.range = range parameters.append(symbol) if !context.eat(.COMMA) { break } } if !context.expect(.PARAMETER_LIST_END) { return null } return parameters } const identifierToSymbolKind StringMap = { "class": .OBJECT_CLASS, "def": .FUNCTION_GLOBAL, "enum": .OBJECT_ENUM, "flags": .OBJECT_FLAGS, "interface": .OBJECT_INTERFACE, "namespace": .OBJECT_NAMESPACE, "over": .FUNCTION_GLOBAL, "type": .OBJECT_WRAPPED, } const customOperators = { TokenKind.ASSIGN_BITWISE_AND: 0, TokenKind.ASSIGN_BITWISE_OR: 0, TokenKind.ASSIGN_BITWISE_XOR: 0, TokenKind.ASSIGN_DIVIDE: 0, TokenKind.ASSIGN_INDEX: 0, TokenKind.ASSIGN_MINUS: 0, TokenKind.ASSIGN_MODULUS: 0, TokenKind.ASSIGN_MULTIPLY: 0, TokenKind.ASSIGN_PLUS: 0, TokenKind.ASSIGN_POWER: 0, TokenKind.ASSIGN_REMAINDER: 0, TokenKind.ASSIGN_SHIFT_LEFT: 0, TokenKind.ASSIGN_SHIFT_RIGHT: 0, TokenKind.ASSIGN_UNSIGNED_SHIFT_RIGHT: 0, TokenKind.BITWISE_AND: 0, TokenKind.BITWISE_OR: 0, TokenKind.BITWISE_XOR: 0, TokenKind.COMPARE: 0, TokenKind.DECREMENT: 0, TokenKind.DIVIDE: 0, TokenKind.IN: 0, TokenKind.INCREMENT: 0, TokenKind.INDEX: 0, TokenKind.LIST: 0, TokenKind.MINUS: 0, TokenKind.MODULUS: 0, TokenKind.MULTIPLY: 0, TokenKind.NOT: 0, TokenKind.PLUS: 0, TokenKind.POWER: 0, TokenKind.REMAINDER: 0, TokenKind.SET: 0, TokenKind.SHIFT_LEFT: 0, TokenKind.SHIFT_RIGHT: 0, TokenKind.TILDE: 0, TokenKind.UNSIGNED_SHIFT_RIGHT: 0, TokenKind.XML_CHILD: 0, } enum ForbiddenGroup { ASSIGN COMPARE EQUAL LOGICAL } const forbiddenCustomOperators IntMap = { TokenKind.ASSIGN: .ASSIGN, TokenKind.EQUAL: .EQUAL, TokenKind.GREATER_THAN: .COMPARE, TokenKind.GREATER_THAN_OR_EQUAL: .COMPARE, TokenKind.LESS_THAN: .COMPARE, TokenKind.LESS_THAN_OR_EQUAL: .COMPARE, TokenKind.LOGICAL_AND: .LOGICAL, TokenKind.LOGICAL_OR: .LOGICAL, TokenKind.NOT_EQUAL: .EQUAL, } # These are prefixed with "the operator \"...\" is not customizable because " const forbiddenGroupDescription = { ForbiddenGroup.ASSIGN: "value types are not supported by the language", ForbiddenGroup.COMPARE: "it's automatically implemented using the \"<=>\" operator (customize the \"<=>\" operator instead)", ForbiddenGroup.EQUAL: "that wouldn't work with generics, which are implemented with type erasure", ForbiddenGroup.LOGICAL: "of its special short-circuit evaluation behavior", } def parseAfterBlock(context ParserContext) bool { context.skipSemicolon # Check for ";" explicitly for a better error message return context.peek(.END_OF_FILE) || context.peek(.RIGHT_BRACE) || context.expect(.NEWLINE) } const assignmentOperators IntMap = { TokenKind.ASSIGN: .ASSIGN, TokenKind.ASSIGN_BITWISE_AND: .ASSIGN_BITWISE_AND, TokenKind.ASSIGN_BITWISE_OR: .ASSIGN_BITWISE_OR, TokenKind.ASSIGN_BITWISE_XOR: .ASSIGN_BITWISE_XOR, TokenKind.ASSIGN_DIVIDE: .ASSIGN_DIVIDE, TokenKind.ASSIGN_MINUS: .ASSIGN_SUBTRACT, TokenKind.ASSIGN_MODULUS: .ASSIGN_MODULUS, TokenKind.ASSIGN_MULTIPLY: .ASSIGN_MULTIPLY, TokenKind.ASSIGN_PLUS: .ASSIGN_ADD, TokenKind.ASSIGN_POWER: .ASSIGN_POWER, TokenKind.ASSIGN_REMAINDER: .ASSIGN_REMAINDER, TokenKind.ASSIGN_SHIFT_LEFT: .ASSIGN_SHIFT_LEFT, TokenKind.ASSIGN_SHIFT_RIGHT: .ASSIGN_SHIFT_RIGHT, TokenKind.ASSIGN_UNSIGNED_SHIFT_RIGHT: .ASSIGN_UNSIGNED_SHIFT_RIGHT, } def recursiveParseGuard(context ParserContext, parent ObjectSymbol, annotations List) Guard { var test Node = null if context.eat(.IF) { test = expressionParser.parse(context, .LOWEST) } if !context.expect(.LEFT_BRACE) { return null } var contents = ObjectSymbol.new(parent.kind, "") contents.flags |= .IS_GUARD_CONDITIONAL contents.parent = parent parseSymbols(context, contents, annotations) if !context.expect(.RIGHT_BRACE) || !context.peek(.ELSE) && !parseAfterBlock(context) { return null } var elseGuard Guard = null if context.eat(.ELSE) { elseGuard = recursiveParseGuard(context, parent, annotations) if elseGuard == null { return null } } return Guard.new(parent, test, contents, elseGuard) } def parseSymbol(context ParserContext, parent ObjectSymbol, annotations List) bool { # Parse comments before the symbol declaration var comments = context.stealComments # Ignore trailing comments if context.peek(.RIGHT_BRACE) || context.peek(.END_OF_FILE) { _warnAboutIgnoredComments(context, comments) return false } # Parse a compile-time if statement if context.peek(.IF) { _warnAboutIgnoredComments(context, comments) var guard = recursiveParseGuard(context, parent, annotations) if guard == null { return false } parent.guards ?= [] parent.guards.append(guard) return true } # Parse annotations before the symbol declaration if context.peek(.ANNOTATION) { annotations = parseAnnotations(context, annotations != null ? annotations.clone : []) # Parse an annotation block if context.eat(.LEFT_BRACE) { _warnAboutIgnoredComments(context, comments) parseSymbols(context, parent, annotations) return context.expect(.RIGHT_BRACE) && parseAfterBlock(context) } } var token = context.current var tokenKind = token.kind var text = token.range.toString var wasCorrected = false var symbol Symbol = null var kind = identifierToSymbolKind.get(text, .OBJECT_GLOBAL) # Skip extra identifiers such as "public" in "public var x = 0" while tokenKind == .IDENTIFIER && kind == .OBJECT_GLOBAL && ( context.peek(.IDENTIFIER, 1) || context.peek(.VAR, 1) || context.peek(.CONST, 1)) { switch text { case "static" { context.log.syntaxErrorStaticKeyword(token.range, parent.kind, parent.name) } case "public" { context.log.syntaxErrorPublicKeyword(token.range) } case "protected", "private" { context.log.syntaxErrorPrivateOrProtected(token.range) } default { context.unexpectedToken } } context.next token = context.current tokenKind = token.kind text = token.range.toString kind = identifierToSymbolKind.get(text, .OBJECT_GLOBAL) } # Special-case a top-level "x = 0" and "x: int = 0" but avoid trying to make "def =() {}" into a variable if tokenKind == .IDENTIFIER && ( context.peek(.ASSIGN, 1) && (!context.peek(.LEFT_PARENTHESIS, 2) || text != "def") || context.peek(.COLON, 1) && context.peek(.IDENTIFIER, 2)) { context.log.syntaxErrorMissingVar(token.range) tokenKind = .VAR wasCorrected = true } # Special-case a top-level "x() {}" else if tokenKind == .IDENTIFIER && context.peek(.LEFT_PARENTHESIS, 1) { context.log.syntaxErrorMissingDef(token.range) tokenKind = .IDENTIFIER kind = .FUNCTION_GLOBAL text = "def" wasCorrected = true } # Special-case enum and flags symbols if parent.kind.isEnumOrFlags && tokenKind == .IDENTIFIER && kind == .OBJECT_GLOBAL { while true { if text in identifierToSymbolKind || !context.expect(.IDENTIFIER) { break } var variable = VariableSymbol.new(.VARIABLE_ENUM_OR_FLAGS, text) variable.range = token.range variable.parent = parent variable.flags |= .IS_CONST parent.variables.append(variable) symbol = variable var comma = context.current if !context.eat(.COMMA) { break } variable.annotations = annotations?.clone variable.comments = comments token = context.current text = token.range.toString if context.peek(.NEWLINE) || context.peek(.RIGHT_BRACE) { context.log.syntaxWarningExtraComma(comma.range) break } } } else { # Parse the symbol kind switch tokenKind { case .CONST, .VAR { kind = parent.kind.hasInstances ? .VARIABLE_INSTANCE : .VARIABLE_GLOBAL } # For symbol kinds that can't live inside functions, use contextual # keywords instead of special keyword tokens. This means these names # are still available to be used as regular symbols: # # class div { # var class = "" # } # # var example =
# case .IDENTIFIER { if kind == .OBJECT_GLOBAL { context.unexpectedToken return false } if kind == .FUNCTION_GLOBAL && parent.kind.hasInstances { kind = .FUNCTION_INSTANCE } } default { context.unexpectedToken return false } } if !wasCorrected { context.next } var nameToken = context.current var range = nameToken.range var name = range.toString var isOperator = false # Only check for custom operators for instance functions if kind == .FUNCTION_INSTANCE { if nameToken.kind in customOperators { isOperator = true } else if nameToken.kind in forbiddenCustomOperators { context.log.syntaxErrorBadOperatorCustomization(range, nameToken.kind, forbiddenGroupDescription[forbiddenCustomOperators[nameToken.kind]]) isOperator = true } } # Parse the symbol name if isOperator { context.next } else if kind == .FUNCTION_GLOBAL && context.eat(.ANNOTATION) { kind = .FUNCTION_ANNOTATION } else if context.eat(.LIST_NEW) || context.eat(.SET_NEW) { if kind == .FUNCTION_INSTANCE { kind = .FUNCTION_CONSTRUCTOR } } else { if !context.expect(.IDENTIFIER) { return false } if kind == .FUNCTION_INSTANCE && name == "new" { kind = .FUNCTION_CONSTRUCTOR } } # Parse shorthand nested namespace declarations if kind.isObject { while context.eat(.DOT) { var nextToken = context.current if !context.expect(.IDENTIFIER) { return false } # Wrap this declaration in a namespace var nextParent = ObjectSymbol.new(.OBJECT_NAMESPACE, name) nextParent.range = range nextParent.parent = parent parent.objects.append(nextParent) parent = nextParent # Update the declaration token nameToken = nextToken range = nextToken.range name = range.toString } } # Parse the symbol body switch kind { case .VARIABLE_GLOBAL, .VARIABLE_INSTANCE { while true { var variable = VariableSymbol.new(kind, name) variable.range = range variable.parent = parent if tokenKind == .CONST { variable.flags |= .IS_CONST } if context.peek(.COLON) { context.log.syntaxErrorExtraColonBeforeType(context.next.range) } if peekType(context) { variable.type = parseType(context) } if context.eat(.ASSIGN) { variable.value = expressionParser.parse(context, .LOWEST) checkExtraParentheses(context, variable.value) } parent.variables.append(variable) if !context.eat(.COMMA) { symbol = variable break } variable.annotations = annotations?.clone variable.comments = comments nameToken = context.current range = nameToken.range name = range.toString if !context.expect(.IDENTIFIER) { return false } } } case .FUNCTION_ANNOTATION, .FUNCTION_CONSTRUCTOR, .FUNCTION_GLOBAL, .FUNCTION_INSTANCE { var function = FunctionSymbol.new(kind, name) function.range = range function.parent = parent if text == "over" { function.flags |= .IS_OVER } # Check for setters like "def foo=(x int) {}" but don't allow a space # between the name and the assignment operator if kind != .FUNCTION_ANNOTATION && nameToken.kind == .IDENTIFIER && context.peek(.ASSIGN) && context.current.range.start == nameToken.range.end { function.range = Range.span(function.range, context.next.range) function.flags |= .IS_SETTER function.name += "=" } # Parse type parameters if context.eat(.PARAMETER_LIST_START) { function.parameters = parseTypeParameters(context, .PARAMETER_FUNCTION) if function.parameters == null { return false } } # Parse function arguments var before = context.current if context.eat(.LEFT_PARENTHESIS) { if !parseFunctionArguments(context, function) { return false } # Functions without arguments are "getters" and don't use parentheses if function.arguments.isEmpty { context.log.syntaxErrorEmptyFunctionParentheses(context.spanSince(before.range)) } } if kind != .FUNCTION_ANNOTATION && !parseFunctionReturnTypeAndBlock(context, function) { return false } # Don't mark operators as getters to avoid confusion with unary operators and compiler-generated call expressions if !isOperator && function.arguments.isEmpty { function.flags |= .IS_GETTER } parent.functions.append(function) symbol = function } case .OBJECT_CLASS, .OBJECT_ENUM, .OBJECT_FLAGS, .OBJECT_INTERFACE, .OBJECT_NAMESPACE, .OBJECT_WRAPPED { var object = ObjectSymbol.new(kind, name) object.range = range object.parent = parent if kind != .OBJECT_NAMESPACE && context.eat(.PARAMETER_LIST_START) { object.parameters = parseTypeParameters(context, .PARAMETER_OBJECT) if object.parameters == null { return false } } # Allow "type Foo = int" if kind == .OBJECT_WRAPPED && context.eat(.ASSIGN) { object.extends = parseType(context) if object.extends == null { return false } } # Regular block structure "type Foo : int {}" else { # Base class var isExtends = context.peek(.IDENTIFIER) && context.current.range.toString == "extends" if isExtends { context.log.syntaxErrorExtendsKeyword(context.next.range) } if isExtends || context.eat(.COLON) { object.extends = parseType(context) if object.extends == null { return false } } # Interfaces var isImplements = context.peek(.IDENTIFIER) && context.current.range.toString == "implements" if isImplements { context.log.syntaxErrorImplementsKeyword(context.next.range) } if isImplements || context.eat(.DOUBLE_COLON) { object.implements = [] while true { var type = parseType(context) object.implements.append(type) if !context.eat(.COMMA) { break } } } context.skipWhitespace if !context.expect(.LEFT_BRACE) { scanForToken(context, .LEFT_BRACE) } parseSymbols(context, object, null) object.commentsInsideEndOfBlock = context.stealComments if !context.expect(.RIGHT_BRACE) { return false } } parent.objects.append(object) symbol = object } default { assert(false) } } # Forbid certain kinds of symbols inside certain object types if (parent.kind.isEnumOrFlags || parent.kind == .OBJECT_WRAPPED || parent.kind == .OBJECT_INTERFACE) && ( kind == .FUNCTION_CONSTRUCTOR || kind == .VARIABLE_INSTANCE) { context.log.syntaxErrorBadDeclarationInsideType(context.spanSince(token.range)) } } symbol.annotations = annotations?.clone symbol.comments = Comment.concat(comments, parseTrailingComment(context)) if !parseAfterBlock(context) { return false } return true } def parseSymbols(context ParserContext, parent ObjectSymbol, annotations List) { context.eat(.NEWLINE) while !context.peek(.END_OF_FILE) && !context.peek(.RIGHT_BRACE) { if !parseSymbol(context, parent, annotations) { break } } } def parseCommaSeparatedList(context ParserContext, parent Node, stop TokenKind) { var isFirst = true var leading = parseTrailingComment(context) context.skipWhitespace while true { var comments = Comment.concat(leading, context.stealComments) leading = null if isFirst && context.eat(stop) { _warnAboutIgnoredComments(context, comments) break } var value = expressionParser.parse(context, .LOWEST) value.comments = Comment.concat(comments, parseTrailingComment(context)) context.skipWhitespace parent.appendChild(value) if context.eat(stop) { break } if !context.expect(.COMMA) { scanForToken(context, stop) break } value.comments = Comment.concat(value.comments, parseTrailingComment(context)) context.skipWhitespace isFirst = false } } def parseHexCharacter(c int) int { if c >= '0' && c <= '9' { return c - '0' } if c >= 'A' && c <= 'F' { return c - 'A' + 10 } if c >= 'a' && c <= 'f' { return c - 'a' + 10 } return -1 } def createStringNode(log Log, range Range) Node { return Node.createString(parseStringLiteral(log, range)).withRange(range) } def parseStringLiteral(log Log, range Range) string { var text = range.toString var count = text.count assert(count >= 2) assert(text[0] == '"' || text[0] == '\'' || text[0] == ')') assert(text[count - 1] == '"' || text[count - 1] == '\'' || text[count - 1] == '(') var builder = StringBuilder.new var start = 1 var stop = count - (text[count - 1] == '(' ? 2 : 1) var i = start while i < stop { var c = text[i++] if c == '\\' { var escape = i - 1 builder.append(text.slice(start, escape)) if i < stop { c = text[i++] if c == 'n' { builder.append("\n") start = i } else if c == 'r' { builder.append("\r") start = i } else if c == 't' { builder.append("\t") start = i } else if c == '0' { builder.append("\0") start = i } else if c == '\\' || c == '"' || c == '\'' { builder.append(string.fromCodeUnit(c)) start = i } else if c == 'x' { if i < stop { var c0 = parseHexCharacter(text[i++]) if i < stop { var c1 = parseHexCharacter(text[i++]) if c0 != -1 && c1 != -1 { builder.append(string.fromCodeUnit(c0 << 4 | c1)) start = i } } } } } if start < i { log.syntaxErrorInvalidEscapeSequence(Range.new(range.source, range.start + escape, range.start + i)) } } } builder.append(text.slice(start, i)) return builder.toString } const intLiteral = (context ParserContext, token Token) Node => Node.createInt(parseIntLiteral(context.log, token.range).value).withRange(token.range) const stringLiteral = (context ParserContext, token Token) Node => createStringNode(context.log, token.range) def boolLiteral(value bool) fn(ParserContext, Token) Node { return (context, token) => Node.createBool(value).withRange(token.range) } def tokenLiteral(kind NodeKind) fn(ParserContext, Token) Node { return (context, token) => Node.new(kind).withRange(token.range) } def unaryPrefix(kind NodeKind) fn(ParserContext, Token, Node) Node { return (context, token, value) => Node.createUnary(kind, value).withRange(Range.span(token.range, value.range)).withInternalRange(token.range) } def unaryPostfix(kind NodeKind) fn(ParserContext, Node, Token) Node { return (context, value, token) => Node.createUnary(kind, value).withRange(Range.span(value.range, token.range)).withInternalRange(token.range) } def binaryInfix(kind NodeKind) fn(ParserContext, Node, Token, Node) Node { return (context, left, token, right) => { if kind == .ASSIGN && left.kind == .INDEX { left.appendChild(right) left.kind = .ASSIGN_INDEX return left.withRange(Range.span(left.range, right.range)).withInternalRange(Range.span(left.internalRange, right.range)) } return Node.createBinary(kind, left, right).withRange(Range.span(left.range, right.range)).withInternalRange(token.range) } } var dotInfixParselet = (context ParserContext, left Node) Node => { var innerComments = context.stealComments var token = context.next var range = context.current.range if !context.expect(.IDENTIFIER) { # Create a empty range instead of using createParseError so IDE code completion still works range = Range.new(token.range.source, token.range.end, token.range.end) } return Node.createDot(left, range.toString) .withRange(context.spanSince(left.range)) .withInternalRange(range) .withInnerComments(innerComments) } var initializerParselet = (context ParserContext) Node => { var token = context.next var kind NodeKind = token.kind == .LEFT_BRACE ? .INITIALIZER_MAP : .INITIALIZER_LIST var node = Node.createInitializer(kind) if token.kind == .LEFT_BRACE || token.kind == .LEFT_BRACKET { var expectColon = kind != .INITIALIZER_LIST var end TokenKind = expectColon ? .RIGHT_BRACE : .RIGHT_BRACKET while true { context.eat(.NEWLINE) var comments = context.stealComments if context.peek(end) { _warnAboutIgnoredComments(context, comments) break } var first = expressionParser.parse(context, .LOWEST) var colon = context.current if !expectColon { node.appendChild(first) } else { if !context.expect(.COLON) { break } var second = expressionParser.parse(context, .LOWEST) first = Node.createPair(first, second).withRange(Range.span(first.range, second.range)).withInternalRange(colon.range) node.appendChild(first) } first.comments = Comment.concat(comments, parseTrailingComment(context)) if !context.eat(.COMMA) { break } first.comments = Comment.concat(first.comments, parseTrailingComment(context)) } context.skipWhitespace scanForToken(context, end) } else if token.kind == .LIST_NEW || token.kind == .SET_NEW { node.appendChild(Node.createName("new").withRange(Range.new(token.range.source, token.range.start + 1, token.range.end - 1))) } return node.withRange(context.spanSince(token.range)) } var parameterizedParselet = (context ParserContext, left Node) Node => { var value = Node.createParameterize(left) var token = context.next while true { var type = parseType(context) value.appendChild(type) if !context.eat(.COMMA) { break } } scanForToken(context, .PARAMETER_LIST_END) return value.withRange(context.spanSince(left.range)).withInternalRange(context.spanSince(token.range)) } def createExpressionParser Pratt { var pratt = Pratt.new ######################################## # Literals ######################################## pratt.literal(.DOUBLE, (context, token) => Node.createDouble(parseDoubleLiteral(token.range.toString)).withRange(token.range)) pratt.literal(.FALSE, boolLiteral(false)) pratt.literal(.INT, intLiteral) pratt.literal(.INT_BINARY, intLiteral) pratt.literal(.INT_HEX, intLiteral) pratt.literal(.INT_OCTAL, intLiteral) pratt.literal(.NULL, tokenLiteral(.NULL)) pratt.literal(.STRING, stringLiteral) pratt.literal(.SUPER, tokenLiteral(.SUPER)) pratt.literal(.TRUE, boolLiteral(true)) pratt.literal(.CHARACTER, (context, token) => { var result = parseStringLiteral(context.log, token.range) var codePoint = 0 # There must be exactly one unicode code point var iterator = Unicode.StringIterator.INSTANCE.reset(result, 0) codePoint = iterator.nextCodePoint if codePoint == -1 || iterator.nextCodePoint != -1 { context.log.syntaxErrorInvalidCharacter(token.range) } # Don't return null when there's an error because that # error won't affect the rest of the compilation return Node.createInt(codePoint).withRange(token.range) }) ######################################## # Unary expressions ######################################## pratt.prefix(.MINUS, .UNARY_PREFIX, unaryPrefix(.NEGATIVE)) pratt.prefix(.NOT, .UNARY_PREFIX, unaryPrefix(.NOT)) pratt.prefix(.PLUS, .UNARY_PREFIX, unaryPrefix(.POSITIVE)) pratt.prefix(.TILDE, .UNARY_PREFIX, unaryPrefix(.COMPLEMENT)) pratt.prefix(.INCREMENT, .UNARY_PREFIX, unaryPrefix(.PREFIX_INCREMENT)) pratt.prefix(.DECREMENT, .UNARY_PREFIX, unaryPrefix(.PREFIX_DECREMENT)) pratt.postfix(.INCREMENT, .UNARY_PREFIX, unaryPostfix(.POSTFIX_INCREMENT)) pratt.postfix(.DECREMENT, .UNARY_PREFIX, unaryPostfix(.POSTFIX_DECREMENT)) ######################################## # Binary expressions ######################################## pratt.infix(.BITWISE_AND, .BITWISE_AND, binaryInfix(.BITWISE_AND)) pratt.infix(.BITWISE_OR, .BITWISE_OR, binaryInfix(.BITWISE_OR)) pratt.infix(.BITWISE_XOR, .BITWISE_XOR, binaryInfix(.BITWISE_XOR)) pratt.infix(.COMPARE, .COMPARE, binaryInfix(.COMPARE)) pratt.infix(.DIVIDE, .MULTIPLY, binaryInfix(.DIVIDE)) pratt.infix(.EQUAL, .EQUAL, binaryInfix(.EQUAL)) pratt.infix(.GREATER_THAN, .COMPARE, binaryInfix(.GREATER_THAN)) pratt.infix(.GREATER_THAN_OR_EQUAL, .COMPARE, binaryInfix(.GREATER_THAN_OR_EQUAL)) pratt.infix(.IN, .COMPARE, binaryInfix(.IN)) pratt.infix(.LESS_THAN, .COMPARE, binaryInfix(.LESS_THAN)) pratt.infix(.LESS_THAN_OR_EQUAL, .COMPARE, binaryInfix(.LESS_THAN_OR_EQUAL)) pratt.infix(.LOGICAL_AND, .LOGICAL_AND, binaryInfix(.LOGICAL_AND)) pratt.infix(.LOGICAL_OR, .LOGICAL_OR, binaryInfix(.LOGICAL_OR)) pratt.infix(.MINUS, .ADD, binaryInfix(.SUBTRACT)) pratt.infix(.MODULUS, .MULTIPLY, binaryInfix(.MODULUS)) pratt.infix(.MULTIPLY, .MULTIPLY, binaryInfix(.MULTIPLY)) pratt.infix(.NOT_EQUAL, .EQUAL, binaryInfix(.NOT_EQUAL)) pratt.infix(.PLUS, .ADD, binaryInfix(.ADD)) pratt.infix(.POWER, .UNARY_PREFIX, binaryInfix(.POWER)) pratt.infix(.REMAINDER, .MULTIPLY, binaryInfix(.REMAINDER)) pratt.infix(.SHIFT_LEFT, .SHIFT, binaryInfix(.SHIFT_LEFT)) pratt.infix(.SHIFT_RIGHT, .SHIFT, binaryInfix(.SHIFT_RIGHT)) pratt.infix(.UNSIGNED_SHIFT_RIGHT, .SHIFT, binaryInfix(.UNSIGNED_SHIFT_RIGHT)) pratt.infixRight(.ASSIGN, .ASSIGN, binaryInfix(.ASSIGN)) pratt.infixRight(.ASSIGN_BITWISE_AND, .ASSIGN, binaryInfix(.ASSIGN_BITWISE_AND)) pratt.infixRight(.ASSIGN_BITWISE_OR, .ASSIGN, binaryInfix(.ASSIGN_BITWISE_OR)) pratt.infixRight(.ASSIGN_BITWISE_XOR, .ASSIGN, binaryInfix(.ASSIGN_BITWISE_XOR)) pratt.infixRight(.ASSIGN_DIVIDE, .ASSIGN, binaryInfix(.ASSIGN_DIVIDE)) pratt.infixRight(.ASSIGN_MINUS, .ASSIGN, binaryInfix(.ASSIGN_SUBTRACT)) pratt.infixRight(.ASSIGN_MODULUS, .ASSIGN, binaryInfix(.ASSIGN_MODULUS)) pratt.infixRight(.ASSIGN_MULTIPLY, .ASSIGN, binaryInfix(.ASSIGN_MULTIPLY)) pratt.infixRight(.ASSIGN_NULL, .ASSIGN, binaryInfix(.ASSIGN_NULL)) pratt.infixRight(.ASSIGN_PLUS, .ASSIGN, binaryInfix(.ASSIGN_ADD)) pratt.infixRight(.ASSIGN_POWER, .ASSIGN, binaryInfix(.ASSIGN_POWER)) pratt.infixRight(.ASSIGN_REMAINDER, .ASSIGN, binaryInfix(.ASSIGN_REMAINDER)) pratt.infixRight(.ASSIGN_SHIFT_LEFT, .ASSIGN, binaryInfix(.ASSIGN_SHIFT_LEFT)) pratt.infixRight(.ASSIGN_SHIFT_RIGHT, .ASSIGN, binaryInfix(.ASSIGN_SHIFT_RIGHT)) pratt.infixRight(.ASSIGN_UNSIGNED_SHIFT_RIGHT, .ASSIGN, binaryInfix(.ASSIGN_UNSIGNED_SHIFT_RIGHT)) pratt.infixRight(.NULL_JOIN, .NULL_JOIN, binaryInfix(.NULL_JOIN)) ######################################## # Other expressions ######################################## pratt.parselet(.DOT, .MEMBER).infix = dotInfixParselet pratt.parselet(.INDEX, .LOWEST).prefix = initializerParselet pratt.parselet(.LEFT_BRACE, .LOWEST).prefix = initializerParselet pratt.parselet(.LEFT_BRACKET, .LOWEST).prefix = initializerParselet pratt.parselet(.LIST_NEW, .LOWEST).prefix = initializerParselet pratt.parselet(.SET_NEW, .LOWEST).prefix = initializerParselet pratt.parselet(.PARAMETER_LIST_START, .MEMBER).infix = parameterizedParselet # String interpolation pratt.parselet(.STRING_INTERPOLATION_START, .LOWEST).prefix = (context) => { var token = context.next var node = Node.createStringInterpolation.appendChild(createStringNode(context.log, token.range)) while true { var value = expressionParser.parse(context, .LOWEST) node.appendChild(value) context.skipWhitespace token = context.current if !context.eat(.STRING_INTERPOLATION_CONTINUE) && !context.expect(.STRING_INTERPOLATION_END) { return context.createParseError } node.appendChild(createStringNode(context.log, token.range)) if token.kind == .STRING_INTERPOLATION_END { break } } return node.withRange(context.spanSince(node.firstChild.range)) } # "x?.y" pratt.parselet(.NULL_DOT, .MEMBER).infix = (context, left) => { context.next var range = context.current.range if !context.expect(.IDENTIFIER) { return context.createParseError } return Node.createNullDot(left, range.toString).withRange(context.spanSince(left.range)).withInternalRange(range) } # Lambda expressions like "=> x" pratt.parselet(.ARROW, .LOWEST).prefix = (context) => { var token = context.current var symbol = FunctionSymbol.new(.FUNCTION_LOCAL, "") if !parseFunctionBlock(context, symbol) { return context.createParseError } symbol.range = context.spanSince(token.range) return Node.createLambda(symbol).withRange(symbol.range) } # Cast expressions pratt.parselet(.AS, .UNARY_PREFIX).infix = (context, left) => { var token = context.next var type = parseType(context) return Node.createCast(left, type).withRange(context.spanSince(left.range)).withInternalRange(token.range) } # Using "." as a unary prefix operator accesses members off the inferred type pratt.parselet(.DOT, .MEMBER).prefix = (context) => { var token = context.next var range = context.current.range if !context.expect(.IDENTIFIER) { return context.createParseError } return Node.createDot(null, range.toString).withRange(context.spanSince(token.range)).withInternalRange(range) } # Access members off of "dynamic" for untyped globals pratt.parselet(.DYNAMIC, .LOWEST).prefix = (context) => { var token = context.next if !context.expect(.DOT) { return context.createParseError } var range = context.current.range if !context.expect(.IDENTIFIER) { return context.createParseError } return Node.createDot(Node.createType(.DYNAMIC), range.toString).withRange(context.spanSince(token.range)).withInternalRange(range) } # Name expressions and lambda expressions like "x => x * x" pratt.parselet(.IDENTIFIER, .LOWEST).prefix = (context) => { var range = context.next.range var name = range.toString # Lambda expression if context.peek(.ARROW) { var symbol = FunctionSymbol.new(.FUNCTION_LOCAL, "") var argument = VariableSymbol.new(.VARIABLE_ARGUMENT, name) argument.range = range symbol.arguments.append(argument) if !parseFunctionBlock(context, symbol) { return context.createParseError } symbol.range = context.spanSince(range) return Node.createLambda(symbol).withRange(symbol.range) } # New expression (an error, but still parsed for a better error message) if context.peek(.IDENTIFIER) && name == "new" { var type = parseType(context) context.log.syntaxErrorNewOperator(context.spanSince(range), "\(type.range).new") return Node.createParseError.withRange(context.spanSince(range)) } return Node.createName(name).withRange(range) } # Type check expressions pratt.parselet(.IS, .COMPARE).infix = (context, left) => { var token = context.next var type = parseType(context) return Node.createTypeCheck(left, type).withRange(context.spanSince(left.range)).withInternalRange(token.range) } # Index expressions pratt.parselet(.LEFT_BRACKET, .MEMBER).infix = (context, left) => { var token = context.next var right = expressionParser.parse(context, .LOWEST) scanForToken(context, .RIGHT_BRACKET) return Node.createIndex(left, right).withRange(context.spanSince(left.range)).withInternalRange(context.spanSince(token.range)) } # Parenthetic groups and lambda expressions like "() => x" pratt.parselet(.LEFT_PARENTHESIS, .LOWEST).prefix = (context) => { var token = context.next # Try to parse a group if !context.peek(.RIGHT_PARENTHESIS) { var value = pratt.parse(context, .LOWEST) if (value.kind != .NAME || !peekType(context)) && context.eat(.RIGHT_PARENTHESIS) { if value.kind != .NAME || !context.peek(.ARROW) { return value.withRange(context.spanSince(token.range)).withFlags(.IS_INSIDE_PARENTHESES) } context.undo } context.undo } # Parse a lambda instead var symbol = FunctionSymbol.new(.FUNCTION_LOCAL, "") if !parseFunctionArguments(context, symbol) || !parseFunctionReturnTypeAndBlock(context, symbol) { return context.createParseError } symbol.range = context.spanSince(token.range) return Node.createLambda(symbol).withRange(symbol.range) } # Call expressions pratt.parselet(.LEFT_PARENTHESIS, .UNARY_POSTFIX).infix = (context, left) => { var node = Node.createCall(left) var token = context.next parseCommaSeparatedList(context, node, .RIGHT_PARENTHESIS) return node.withRange(context.spanSince(left.range)).withInternalRange(context.spanSince(token.range)) } # Hook expressions pratt.parselet(.QUESTION_MARK, .ASSIGN).infix = (context, left) => { context.next var middle = pratt.parse(context, (Precedence.ASSIGN - 1) as Precedence) if !context.expect(.COLON) { return context.createParseError } var right = pratt.parse(context, (Precedence.ASSIGN - 1) as Precedence) return Node.createHook(left, middle, right).withRange(context.spanSince(left.range)) } # XML literals pratt.parselet(.XML_START, .LOWEST).prefix = (context) => { var token = context.next var tag = parseDotChain(context) if tag == null { scanForToken(context, .XML_END) return context.createParseError } var attributes = Node.createSequence # Parse attributes context.skipWhitespace while context.peek(.IDENTIFIER) { var name = parseDotChain(context) var assignment = context.current var kind = assignmentOperators.get(assignment.kind, .NULL) if kind == .NULL { context.expect(.ASSIGN) scanForToken(context, .XML_END) return context.createParseError } context.next var value = expressionParser.parse(context, (Precedence.UNARY_PREFIX - 1) as Precedence) attributes.appendChild(Node.createBinary(kind, name, value).withRange(context.spanSince(name.range)).withInternalRange(assignment.range)) context.skipWhitespace } # Parse end of tag var block = Node.createBlock var closingTag Node = null context.skipWhitespace # Check for children if !context.eat(.XML_END_EMPTY) { if !context.expect(.XML_END) { scanForToken(context, .XML_END) return context.createParseError } # Parse children context.skipWhitespace if !parseStatements(context, block, .NORMAL) || !context.expect(.XML_START_CLOSE) { return context.createParseError } block.withRange(context.spanSince(token.range)) # Parse closing tag closingTag = parseDotChain(context) if closingTag == null { scanForToken(context, .XML_END) return context.createParseError } context.skipWhitespace if !context.expect(.XML_END) { scanForToken(context, .XML_END) return context.createParseError } # Validate closing tag (not a fatal error) if !tag.looksTheSameAs(closingTag) { context.log.syntaxErrorXMLClosingTagMismatch(closingTag.range, dotChainToString(closingTag), dotChainToString(tag), tag.range) } } return Node.createXML(tag, attributes, block, closingTag).withRange(context.spanSince(token.range)) } return pratt } def dotChainToString(node Node) string { assert(node.kind == .NAME || node.kind == .DOT) if node.kind == .NAME { return node.asString } return dotChainToString(node.dotTarget) + "." + node.asString } def parseDotChain(context ParserContext) Node { var current = context.current if !context.expect(.IDENTIFIER) { return null } var chain = Node.createName(current.range.toString).withRange(current.range) while context.eat(.DOT) { current = context.current if !context.expect(.IDENTIFIER) { return null } chain = Node.createDot(chain, current.range.toString).withRange(context.spanSince(chain.range)).withInternalRange(current.range) } return chain } def createTypeParser Pratt { var pratt = Pratt.new pratt.literal(.DYNAMIC, (context, token) => Node.createType(.DYNAMIC).withRange(token.range)) pratt.parselet(.DOT, .MEMBER).infix = dotInfixParselet pratt.parselet(.PARAMETER_LIST_START, .MEMBER).infix = parameterizedParselet # Name expressions or lambda type expressions like "fn(int) int" pratt.parselet(.IDENTIFIER, .LOWEST).prefix = (context) => { var node = Node.createLambdaType var returnType = node.lambdaReturnType var token = context.next var name = token.range.toString var isFirst = true if name != "fn" || !context.eat(.LEFT_PARENTHESIS) { return Node.createName(name).withRange(token.range) } # Parse argument types while !context.eat(.RIGHT_PARENTHESIS) { if !isFirst && !context.expect(.COMMA) { scanForToken(context, .RIGHT_PARENTHESIS) return context.createParseError } node.insertChildBefore(returnType, parseType(context)) isFirst = false } # Parse return type if present if peekType(context) { returnType.replaceWith(parseType(context)) } return node.withRange(context.spanSince(token.range)) } # The "Foo[]" syntax is an error but parse it anyway pratt.parselet(.INDEX, .MEMBER).infix = (context, left) => { context.next var range = context.spanSince(left.range) context.log.syntaxErrorWrongListSyntax(range, "List<\(left.range)>") return Node.createParseError.withRange(range) } return pratt } def parseFile(log Log, tokens List, global ObjectSymbol, warnAboutIgnoredComments bool) { expressionParser ?= createExpressionParser typeParser ?= createTypeParser var context = ParserContext.new(log, tokens, warnAboutIgnoredComments) parseSymbols(context, global, null) context.expect(.END_OF_FILE) } } ================================================ FILE: src/frontend/pratt.sk ================================================ namespace Skew { # The same operator precedence as C for the most part enum Precedence { LOWEST COMMA ASSIGN NULL_JOIN LOGICAL_OR LOGICAL_AND BITWISE_OR BITWISE_XOR BITWISE_AND EQUAL COMPARE SHIFT ADD MULTIPLY UNARY_PREFIX UNARY_POSTFIX MEMBER } class ParserContext { var log Log var inNonVoidFunction = false var _tokens List var _index = 0 const warnAboutIgnoredComments bool # Keep track of the previous syntax error so only one syntax error is emitted # per token when recovering from a parse error. For example: # # int x = (1 + (2 + # # In the code above, the only syntax error should be about an unexpected # end of file and not also about the two missing right parentheses. var _previousSyntaxError = -1 def current Token { return _tokens[_index] } def stealComments List { var token = current var comments = token.comments token.comments = null return comments } def next Token { var token = current if warnAboutIgnoredComments && token.comments != null { log.syntaxWarningIgnoredCommentInParser(token.comments.first.range) } if _index + 1 < _tokens.count { _index++ } return token } def spanSince(range Range) Range { var previous = _tokens[_index > 0 ? _index - 1 : 0] return previous.range.end < range.start ? range : Range.span(range, previous.range) } def peek(kind TokenKind) bool { return current.kind == kind } def peek(kind TokenKind, skip int) bool { assert(skip >= 1) return _tokens[Math.min(_index + skip, _tokens.count - 1)].kind == kind } def eat(kind TokenKind) bool { if peek(kind) { next return true } return false } def skipWhitespace { eat(.NEWLINE) } def skipSemicolon bool { if peek(.SEMICOLON) { expect(.NEWLINE) next return true } return false } def undo { assert(_index > 0) _index-- } def expect(kind TokenKind) bool { if !eat(kind) { if canReportSyntaxError { log.syntaxErrorExpectedToken(current.range, current.kind, kind) } return false } return true } def unexpectedToken { if canReportSyntaxError { log.syntaxErrorUnexpectedToken(current) } } def createParseError Node { return Node.createParseError.withRange(current.range) } def canReportSyntaxError bool { if _previousSyntaxError != _index { _previousSyntaxError = _index return true } return false } } class Parselet { var precedence Precedence var prefix fn(ParserContext) Node = null var infix fn(ParserContext, Node) Node = null } # A Pratt parser is a parser that associates up to two operations per token, # each with its own precedence. Pratt parsers excel at parsing expression # trees with deeply nested precedence levels. For an excellent writeup, see: # # http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ # class Pratt { var _table = IntMap.new def parselet(kind TokenKind, precedence Precedence) Parselet { var parselet = _table.get(kind, null) if parselet == null { var created = Parselet.new(precedence) parselet = created _table[kind] = created } else if precedence > parselet.precedence { parselet.precedence = precedence } return parselet } def parse(context ParserContext, precedence Precedence) Node { context.skipWhitespace var comments = context.stealComments var token = context.current var parselet = _table.get(token.kind, null) if parselet == null || parselet.prefix == null { context.unexpectedToken return context.createParseError } var node = resume(context, precedence, parselet.prefix(context)) assert(node != null && node.range != null) # Parselets must set the range of every node node.comments = Comment.concat(comments, node.comments) return node } def resume(context ParserContext, precedence Precedence, left Node) Node { while true { var kind = context.current.kind var parselet = _table.get(kind, null) if parselet == null || parselet.infix == null || parselet.precedence <= precedence { break } left = parselet.infix(context, left) assert(left != null && left.range != null) # Parselets must set the range of every node } return left } def literal(kind TokenKind, callback fn(ParserContext, Token) Node) { parselet(kind, .LOWEST).prefix = context => callback(context, context.next) } def prefix(kind TokenKind, precedence Precedence, callback fn(ParserContext, Token, Node) Node) { parselet(kind, .LOWEST).prefix = context => { var token = context.next return callback(context, token, parse(context, precedence)) } } def postfix(kind TokenKind, precedence Precedence, callback fn(ParserContext, Node, Token) Node) { parselet(kind, precedence).infix = (context, left) => callback(context, left, context.next) } def infix(kind TokenKind, precedence Precedence, callback fn(ParserContext, Node, Token, Node) Node) { parselet(kind, precedence).infix = (context, left) => { var token = context.next var comments = context.stealComments var right = parse(context, precedence) right.comments = Comment.concat(comments, right.comments) return callback(context, left, token, right) } } def infixRight(kind TokenKind, precedence Precedence, callback fn(ParserContext, Node, Token, Node) Node) { parselet(kind, precedence).infix = (context, left) => { var token = context.next return callback(context, left, token, parse(context, (precedence as int - 1) as Precedence)) # Subtract 1 for right-associativity } } } } ================================================ FILE: src/frontend/range.sk ================================================ namespace Skew { class FormattedRange { var line string var range string } class Range { const source Source const start int const end int def toString string { return source.contents.slice(start, end) } def locationString string { var location = source.indexToLineColumn(start) return "\(source.name):\(location.line + 1):\(location.column + 1)" } def overlaps(range Range) bool { return source == range.source && start < range.end && range.start < end } def touches(index int) bool { return start <= index && index <= end } def format(maxLength int) FormattedRange { assert(source != null) var start = source.indexToLineColumn(self.start) var end = source.indexToLineColumn(self.end) var line = source.contentsOfLine(start.line) var startColumn = start.column var endColumn = end.line == start.line ? end.column : line.count # Use a unicode iterator to count the actual code points so they don't get sliced through the middle var iterator = Unicode.StringIterator.INSTANCE.reset(line, 0) var codePoints List = [] var a = 0 var b = 0 # Expand tabs into spaces while true { if iterator.index == startColumn { a = codePoints.count } if iterator.index == endColumn { b = codePoints.count } var codePoint = iterator.nextCodePoint if codePoint < 0 { break } if codePoint == '\t' { for space in 0..8 - codePoints.count % 8 { codePoints.append(' ') } } else { codePoints.append(codePoint) } } # Ensure the line length doesn't exceed maxLength var count = codePoints.count if maxLength > 0 && count > maxLength { var centeredWidth = Math.min(b - a, maxLength / 2) var centeredStart = Math.max((maxLength - centeredWidth) / 2, 3) # Left aligned if a < centeredStart { line = string.fromCodePoints(codePoints.slice(0, maxLength - 3)) + "..." if b > maxLength - 3 { b = maxLength - 3 } } # Right aligned else if count - a < maxLength - centeredStart { var offset = count - maxLength line = "..." + string.fromCodePoints(codePoints.slice(offset + 3, count)) a -= offset b -= offset } # Center aligned else { var offset = a - centeredStart line = "..." + string.fromCodePoints(codePoints.slice(offset + 3, offset + maxLength - 3)) + "..." a -= offset b -= offset if b > maxLength - 3 { b = maxLength - 3 } } } else { line = string.fromCodePoints(codePoints) } return FormattedRange.new(line, " ".repeat(a) + (b - a < 2 ? "^" : "~".repeat(b - a))) } def fromStart(count int) Range { assert(count >= 0 && count <= end - start) return new(source, start, start + count) } def fromEnd(count int) Range { assert(count >= 0 && count <= end - start) return new(source, end - count, end) } def slice(offsetStart int, offsetEnd int) Range { assert(offsetStart >= 0 && offsetStart <= offsetEnd && offsetEnd <= end - start) return new(source, start + offsetStart, start + offsetEnd) } def rangeIncludingLeftWhitespace Range { var index = start var contents = source.contents while index > 0 { var c = contents[index - 1] if c != ' ' && c != '\t' { break } index-- } return new(source, index, end) } def rangeIncludingRightWhitespace Range { var index = end var contents = source.contents while index < contents.count { var c = contents[index] if c != ' ' && c != '\t' { break } index++ } return new(source, start, index) } } namespace Range { def span(start Range, end Range) Range { assert(start.source == end.source) assert(start.start <= end.end) return new(start.source, start.start, end.end) } def inner(start Range, end Range) Range { assert(start.source == end.source) assert(start.end <= end.start) return new(start.source, start.end, end.start) } def before(outer Range, inner Range) Range { assert(outer.source == inner.source) assert(outer.start <= inner.start) assert(outer.end >= inner.end) return new(outer.source, outer.start, inner.start) } def after(outer Range, inner Range) Range { assert(outer.source == inner.source) assert(outer.start <= inner.start) assert(outer.end >= inner.end) return new(outer.source, inner.end, outer.end) } def equal(left Range, right Range) bool { return left.source == right.source && left.start == right.start && left.end == right.end } } } ================================================ FILE: src/frontend/source.sk ================================================ namespace Skew { class LineColumn { var line int # 0-based index var column int # 0-based index } class Source { var name string var contents string # This maps line numbers to indices within contents var _lineOffsets List = null def entireRange Range { return Range.new(self, 0, contents.count) } def lineCount int { _computeLineOffsets return _lineOffsets.count - 1 # Ignore the line offset at 0 } def contentsOfLine(line int) string { _computeLineOffsets if line < 0 || line >= _lineOffsets.count { return "" } var start = _lineOffsets[line] var end = line + 1 < _lineOffsets.count ? _lineOffsets[line + 1] - 1 : contents.count return contents.slice(start, end) } def indexToLineColumn(index int) LineColumn { _computeLineOffsets # Binary search to find the line var count = _lineOffsets.count var line = 0 while count > 0 { var step = count / 2 var i = line + step if _lineOffsets[i] <= index { line = i + 1 count = count - step - 1 } else { count = step } } # Use the line to compute the column var column = line > 0 ? index - _lineOffsets[line - 1] : index return LineColumn.new(line - 1, column) } def lineColumnToIndex(line int, column int) int { _computeLineOffsets if line >= 0 && line < _lineOffsets.count { var index = _lineOffsets[line] if column >= 0 && index + column <= (line + 1 < _lineOffsets.count ? _lineOffsets[line + 1] : contents.count) { return index + column } } return -1 } def _computeLineOffsets { if _lineOffsets == null { _lineOffsets = [0] for i in 0..contents.count { if contents[i] == '\n' { _lineOffsets.append(i + 1) } } } } } } ================================================ FILE: src/frontend/token.sk ================================================ namespace Skew { enum PassKind { LEXING } class LexingPass : Pass { over kind PassKind { return .LEXING } over run(context PassContext) { for source in context.inputs { context.tokens.append(tokenize(context.log, source)) } } } class Comment { var range Range var lines List var hasGapBelow bool var isTrailing bool } namespace Comment { def concat(left List, right List) List { if left == null { return right } if right == null { return left } left.append(right) return left } def lastTrailingComment(comments List) Comment { if comments != null { var last = comments.last if last.isTrailing && last.lines.count == 1 { return last } } return null } def withoutLastTrailingComment(comments List) List { if comments != null { var last = comments.last if last.isTrailing && last.lines.count == 1 { return comments.slice(0, comments.count - 1) } } return comments } def firstTrailingComment(comments List) Comment { if comments != null { var first = comments.first if first.isTrailing && first.lines.count == 1 { return first } } return null } def withoutFirstTrailingComment(comments List) List { if comments != null { var first = comments.first if first.isTrailing && first.lines.count == 1 { return comments.slice(1) } } return comments } } class Token { var range Range var kind TokenKind var comments List } const REMOVE_WHITESPACE_BEFORE = { TokenKind.COLON: 0, TokenKind.COMMA: 0, TokenKind.DOT: 0, TokenKind.QUESTION_MARK: 0, TokenKind.RIGHT_PARENTHESIS: 0, } const FORBID_XML_AFTER = { TokenKind.CHARACTER: 0, TokenKind.DECREMENT: 0, TokenKind.DOUBLE: 0, TokenKind.DYNAMIC: 0, TokenKind.STRING_INTERPOLATION_END: 0, TokenKind.FALSE: 0, TokenKind.IDENTIFIER: 0, TokenKind.INCREMENT: 0, TokenKind.INT: 0, TokenKind.INT_BINARY: 0, TokenKind.INT_HEX: 0, TokenKind.INT_OCTAL: 0, TokenKind.NULL: 0, TokenKind.RIGHT_BRACE: 0, TokenKind.RIGHT_BRACKET: 0, TokenKind.RIGHT_PARENTHESIS: 0, TokenKind.STRING: 0, TokenKind.SUPER: 0, TokenKind.TRUE: 0, } # This is the inner loop from "flex", an ancient lexer generator. The output # of flex is pretty bad (obfuscated variable names and the opposite of modular # code) but it's fast and somewhat standard for compiler design. The code below # replaces a simple hand-coded lexer and offers much better performance. def tokenize(log Log, source Source) List { var comments List = null var tokens List = [] var text = source.contents var count = text.count var previousKind TokenKind = .NULL var previousWasComment = false var stack List = [] # For backing up var yy_last_accepting_state = 0 var yy_last_accepting_cpos = 0 # The current character pointer var yy_cp = 0 while yy_cp < count { var yy_current_state = 1 # Reset the NFA var yy_bp = yy_cp # The pointer to the beginning of the token var yy_act TokenKind = .ERROR # Special-case string interpolation var c = text[yy_cp] var isStringInterpolation = c == '"' if c == ')' { for i = stack.count - 1; i >= 0; i-- { var kind = stack[i].kind if kind == .STRING_INTERPOLATION_START { isStringInterpolation = true } else if kind != .LESS_THAN { break } } } if isStringInterpolation { var isExit = c == ')' yy_cp++ while yy_cp < count { c = text[yy_cp++] if c == '"' { yy_act = isExit ? .STRING_INTERPOLATION_END : .STRING break } if c == '\\' { if yy_cp == count { break } c = text[yy_cp++] if c == '(' { yy_act = isExit ? .STRING_INTERPOLATION_CONTINUE : .STRING_INTERPOLATION_START break } } } } # Special-case XML literals else if c == '>' && !stack.isEmpty && stack.last.kind == .XML_START { yy_cp++ yy_act = .XML_END } # Search for a match else { while yy_current_state != YY_JAM_STATE { if yy_cp >= count { break # This prevents syntax errors from causing infinite loops } c = text[yy_cp] var index = c < 127 ? c : 127 # All of the interesting characters are ASCII var yy_c = yy_ec[index] if yy_accept[yy_current_state] != .YY_INVALID_ACTION { yy_last_accepting_state = yy_current_state yy_last_accepting_cpos = yy_cp } while yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state { yy_current_state = yy_def[yy_current_state] if yy_current_state >= YY_ACCEPT_LENGTH { yy_c = yy_meta[yy_c] } } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c] yy_cp++ } # Find the action yy_act = yy_accept[yy_current_state] while yy_act == .YY_INVALID_ACTION { # Have to back up yy_cp = yy_last_accepting_cpos yy_current_state = yy_last_accepting_state yy_act = yy_accept[yy_current_state] } # Ignore whitespace if yy_act == .WHITESPACE { continue } # Stop at the end of the file if yy_act == .END_OF_FILE { break } } # Special-case XML literals if yy_act == .LESS_THAN && !(previousKind in FORBID_XML_AFTER) { yy_act = .XML_START } # This is the default action in flex, which is usually called ECHO else if yy_act == .ERROR { var iterator = Unicode.StringIterator.INSTANCE.reset(text, yy_bp) iterator.nextCodePoint var range = Range.new(source, yy_bp, iterator.index) log.syntaxErrorExtraData(range, range.toString) break } var token = Token.new(Range.new(source, yy_bp, yy_cp), yy_act, null) # Have a nice error message for certain tokens if yy_act == .COMMENT_ERROR { log.syntaxErrorSlashComment(token.range) token.kind = .COMMENT } else if yy_act == .NOT_EQUAL_ERROR { log.syntaxErrorOperatorTypo(token.range, "!=") token.kind = .NOT_EQUAL } else if yy_act == .EQUAL_ERROR { log.syntaxErrorOperatorTypo(token.range, "==") token.kind = .EQUAL } # Tokens that start with a greater than may need to be split, potentially multiple times var loop = true while loop { var tokenStartsWithGreaterThan = text[token.range.start] == '>' var tokenKind = token.kind loop = false # Remove tokens from the stack if they aren't working out while !stack.isEmpty { var top = stack.last var topKind = top.kind # Stop parsing a type if we find a token that no type expression uses if topKind == .LESS_THAN && tokenKind != .LESS_THAN && tokenKind != .IDENTIFIER && tokenKind != .COMMA && tokenKind != .DYNAMIC && tokenKind != .DOT && tokenKind != .LEFT_PARENTHESIS && tokenKind != .RIGHT_PARENTHESIS && !tokenStartsWithGreaterThan { stack.removeLast } else { break } } # Group open if tokenKind == .LEFT_PARENTHESIS || tokenKind == .LEFT_BRACE || tokenKind == .LEFT_BRACKET || tokenKind == .LESS_THAN || tokenKind == .STRING_INTERPOLATION_START || tokenKind == .XML_START { stack.append(token) } # Group close else if tokenKind == .RIGHT_PARENTHESIS || tokenKind == .RIGHT_BRACE || tokenKind == .RIGHT_BRACKET || tokenKind == .STRING_INTERPOLATION_END || tokenKind == .XML_END || tokenStartsWithGreaterThan { # Search for a matching opposite token while !stack.isEmpty { var top = stack.last var topKind = top.kind # Don't match ">" that don't work since they are just operators if tokenStartsWithGreaterThan && topKind != .LESS_THAN { break } # Consume the current token stack.removeLast # Stop if it's a match if tokenKind == .RIGHT_PARENTHESIS && topKind == .LEFT_PARENTHESIS || tokenKind == .RIGHT_BRACKET && topKind == .LEFT_BRACKET || tokenKind == .RIGHT_BRACE && topKind == .LEFT_BRACE || tokenKind == .STRING_INTERPOLATION_END && topKind == .STRING_INTERPOLATION_START { break } # Special-case angle brackets matches and ignore tentative matches that didn't work out if topKind == .LESS_THAN && tokenStartsWithGreaterThan { # Break apart operators that start with a closing angle bracket if tokenKind != .GREATER_THAN { var start = token.range.start tokens.append(Token.new(Range.new(source, start, start + 1), .PARAMETER_LIST_END, null)) token.range = Range.new(source, start + 1, token.range.end) token.kind = tokenKind == .SHIFT_RIGHT ? .GREATER_THAN : tokenKind == .UNSIGNED_SHIFT_RIGHT ? .SHIFT_RIGHT : tokenKind == .GREATER_THAN_OR_EQUAL ? .ASSIGN : tokenKind == .ASSIGN_SHIFT_RIGHT ? .GREATER_THAN_OR_EQUAL : tokenKind == .ASSIGN_UNSIGNED_SHIFT_RIGHT ? .ASSIGN_SHIFT_RIGHT : .NULL assert(token.kind != .NULL) loop = tokenKind != .GREATER_THAN_OR_EQUAL # Split this token again } else { token.kind = .PARAMETER_LIST_END } # Convert the "<" into a bound for type parameter lists top.kind = .PARAMETER_LIST_START # Stop the search since we found a match break } } } } # Remove newlines based on the previous token to enable line continuations. # Make sure to be conservative. We want to be like Python, not like # JavaScript ASI! Anything that is at all ambiguous should be disallowed. # # Examples: # - "var x = 0 \n .toString" # - "var x = 0 # comment \n .toString" # - "var x = 0 \n # comment \n .toString" # - "var x = 0 \n ### \n multi-line comment \n ### \n return 0" # if previousKind == .NEWLINE && token.kind == .NEWLINE { if comments != null && !previousWasComment { comments.last.hasGapBelow = true } previousWasComment = false continue } else if previousKind == .NEWLINE && token.kind in REMOVE_WHITESPACE_BEFORE { var last = tokens.takeLast if last.comments != null { comments ?= [] comments.append(last.comments) } } # Attach comments to tokens instead of having comments be tokens previousWasComment = token.kind == .COMMENT if previousWasComment { comments ?= [] if comments.isEmpty || comments.last.hasGapBelow { comments.append(Comment.new(token.range, [], false, false)) } var range = token.range var line = source.contents.slice(range.start + 1, range.end) var hashes = 0 for j in 0..line.count { if line[j] != '#' { break } hashes++ } if hashes != 0 { line = "/".repeat(hashes) + line.slice(hashes) } comments.last.lines.append(line) continue } previousKind = token.kind if previousKind != .NEWLINE { token.comments = comments comments = null } # Capture trailing comments if !tokens.isEmpty && comments != null && comments.count == 1 && comments.first.lines.count == 1 && !comments.first.hasGapBelow { comments.first.isTrailing = true token.comments = comments comments = null } # Accumulate the token for this iteration tokens.append(token) } # Every token stream ends in END_OF_FILE tokens.append(Token.new(Range.new(source, yy_cp, yy_cp), .END_OF_FILE, comments)) # Also return preprocessor token presence so the preprocessor can be avoided return tokens } enum TokenKind { # Type parameters are surrounded by "<" and ">" PARAMETER_LIST_END PARAMETER_LIST_START # XML entities are surrounded by "<" and ">" (or "" but those are defined by flex) XML_END XML_START # String interpolation looks like "start\( 1 )continue( 2 )end" STRING_INTERPOLATION_CONTINUE STRING_INTERPOLATION_END STRING_INTERPOLATION_START def toString string { assert(self in _toString) return _toString[self] } } namespace TokenKind { const _toString = { COMMENT: "comment", NEWLINE: "newline", WHITESPACE: "whitespace", AS: "\"as\"", BREAK: "\"break\"", CASE: "\"case\"", CATCH: "\"catch\"", CONST: "\"const\"", CONTINUE: "\"continue\"", DEFAULT: "\"default\"", DYNAMIC: "\"dynamic\"", ELSE: "\"else\"", FALSE: "\"false\"", FINALLY: "\"finally\"", FOR: "\"for\"", IF: "\"if\"", IN: "\"in\"", IS: "\"is\"", NULL: "\"null\"", RETURN: "\"return\"", SUPER: "\"super\"", SWITCH: "\"switch\"", THROW: "\"throw\"", TRUE: "\"true\"", TRY: "\"try\"", VAR: "\"var\"", WHILE: "\"while\"", ARROW: "\"=>\"", ASSIGN: "\"=\"", ASSIGN_BITWISE_AND: "\"&=\"", ASSIGN_BITWISE_OR: "\"|=\"", ASSIGN_BITWISE_XOR: "\"^=\"", ASSIGN_DIVIDE: "\"/=\"", ASSIGN_INDEX: "\"[]=\"", ASSIGN_MINUS: "\"-=\"", ASSIGN_MODULUS: "\"%%=\"", ASSIGN_MULTIPLY: "\"*=\"", ASSIGN_PLUS: "\"+=\"", ASSIGN_POWER: "\"**=\"", ASSIGN_REMAINDER: "\"%=\"", ASSIGN_SHIFT_LEFT: "\"<<=\"", ASSIGN_SHIFT_RIGHT: "\">>=\"", ASSIGN_UNSIGNED_SHIFT_RIGHT: "\">>>=\"", BITWISE_AND: "\"&\"", BITWISE_OR: "\"|\"", BITWISE_XOR: "\"^\"", COLON: "\":\"", COMMA: "\",\"", COMPARE: "\"<=>\"", DECREMENT: "\"--\"", DIVIDE: "\"/\"", DOT: "\".\"", DOT_DOT: "\"..\"", DOUBLE_COLON: "\"::\"", EQUAL: "\"==\"", GREATER_THAN: "\">\"", GREATER_THAN_OR_EQUAL: "\">=\"", INCREMENT: "\"++\"", INDEX: "\"[]\"", LEFT_BRACE: "\"{\"", LEFT_BRACKET: "\"[\"", LEFT_PARENTHESIS: "\"(\"", LESS_THAN: "\"<\"", LESS_THAN_OR_EQUAL: "\"<=\"", LIST: "\"[...]\"", LIST_NEW: "\"[new]\"", LOGICAL_AND: "\"&&\"", LOGICAL_OR: "\"||\"", MINUS: "\"-\"", MODULUS: "\"%%\"", MULTIPLY: "\"*\"", NOT: "\"!\"", NOT_EQUAL: "\"!=\"", NULL_DOT: "\"?.\"", NULL_JOIN: "\"??\"", PLUS: "\"+\"", POWER: "\"**\"", QUESTION_MARK: "\"?\"", REMAINDER: "\"%\"", RIGHT_BRACE: "\"}\"", RIGHT_BRACKET: "\"]\"", RIGHT_PARENTHESIS: "\")\"", SEMICOLON: "\";\"", SET: "\"{...}\"", SET_NEW: "\"{new}\"", SHIFT_LEFT: "\"<<\"", SHIFT_RIGHT: "\">>\"", TILDE: "\"~\"", UNSIGNED_SHIFT_RIGHT: "\">>>\"", ANNOTATION: "annotation", CHARACTER: "character", DOUBLE: "double", END_OF_FILE: "end of input", IDENTIFIER: "identifier", INT: "integer", INT_BINARY: "integer", INT_HEX: "integer", INT_OCTAL: "integer", STRING: "string", PARAMETER_LIST_END: "\">\"", PARAMETER_LIST_START: "\"<\"", XML_CHILD: "\"<>...\"", XML_END: "\">\"", XML_END_EMPTY: "\"/>\"", XML_START: "\"<\"", XML_START_CLOSE: "\" { try { var entries List = dynamic.require("fs").readdirSync(path) entries.sort((a, b) => a <=> b) return entries } return null } def readFile(path string) string { try { var contents string = dynamic.require("fs").readFileSync(path, "utf8") return contents.replaceAll("\r\n", "\n") } return null } def writeFile(path string, contents string) bool { const fs = dynamic.require("fs") const p = dynamic.require("path") var mkdir_p fn(string) mkdir_p = dir => { if dir != p.dirname(dir) { mkdir_p(p.dirname(dir)) try { fs.mkdirSync(dir) } } } mkdir_p(p.dirname(path)) try { fs.writeFileSync(path, contents) return true } return false } } } else if TARGET == .CSHARP { namespace IO { def isDirectory(path string) bool { try { return dynamic.System.IO.Directory.Exists(path) } return false } def readDirectory(path string) List { try { var entries List = dynamic.System.Linq.Enumerable.ToList(dynamic.System.IO.Directory.GetFileSystemEntries(path)) for i in 0..entries.count { entries[i] = dynamic.System.IO.Path.GetFileName(entries[i]) } entries.sort((a, b) => a <=> b) return entries } return null } def readFile(path string) string { try { var contents string = dynamic.System.IO.File.ReadAllText(path) return contents.replaceAll("\r\n", "\n") } return null } def writeFile(path string, contents string) bool { try { dynamic.System.IO.File.WriteAllText(path, contents) return true } return false } } } else { @import namespace IO { def isDirectory(path string) bool def readDirectory(path string) List def readFile(path string) string def writeFile(path string, contents string) bool } } ================================================ FILE: src/lib/terminal.sk ================================================ namespace Terminal { enum Color { DEFAULT BOLD GRAY RED GREEN YELLOW BLUE MAGENTA CYAN def toEscapeCode int { return colorToEscapeCode[self] } } const colorToEscapeCode = { Color.DEFAULT: 0, Color.BOLD: 1, Color.GRAY: 90, Color.RED: 91, Color.GREEN: 92, Color.YELLOW: 93, Color.BLUE: 94, Color.MAGENTA: 95, Color.CYAN: 96, } if TARGET == .JAVASCRIPT { def setColor(color Color) { if dynamic.process.stdout.isTTY { write("\x1B[0;\(color.toEscapeCode)m") } } def width int { return dynamic.process.stdout.columns } def height int { return dynamic.process.stdout.rows } def print(text string) { write(text + "\n") } def flush { } def write(text string) { dynamic.process.stdout.write(text) } } else if TARGET == .CSHARP { @using("System") def setColor(color Color) { switch color { case .BOLD, .DEFAULT { dynamic.Console.ResetColor() } case .GRAY { dynamic.Console.ForegroundColor = dynamic.ConsoleColor.DarkGray } case .RED { dynamic.Console.ForegroundColor = dynamic.ConsoleColor.Red } case .GREEN { dynamic.Console.ForegroundColor = dynamic.ConsoleColor.Green } case .YELLOW { dynamic.Console.ForegroundColor = dynamic.ConsoleColor.Yellow } case .BLUE { dynamic.Console.ForegroundColor = dynamic.ConsoleColor.Blue } case .MAGENTA { dynamic.Console.ForegroundColor = dynamic.ConsoleColor.Magenta } case .CYAN { dynamic.Console.ForegroundColor = dynamic.ConsoleColor.Cyan } } } @using("System") def width int { return dynamic.Console.BufferWidth } @using("System") def height int { return dynamic.Console.BufferHeight } @using("System") def print(text string) { dynamic.Console.WriteLine(text) } def flush { } @using("System") def write(text string) { dynamic.Console.Write(text) } } else { def setColor(color Color) { _setColor(color.toEscapeCode) } @import { def _setColor(escapeCode int) def width int def height int def print(text string) def flush def write(text string) } } } ================================================ FILE: src/lib/timestamp.sk ================================================ namespace Timestamp { if TARGET == .JAVASCRIPT { def seconds double { return (dynamic.typeof(dynamic.performance) != "undefined" && dynamic.performance.now ? dynamic.performance.now() : dynamic.Date.now()) / 1000 } } else if TARGET == .CSHARP { @using("System") def seconds double { return dynamic.DateTime.Now.Ticks / (dynamic.TimeSpan.TicksPerSecond as double) } } else { @import def seconds double } } ================================================ FILE: src/lib/unit.sk ================================================ namespace Unit { enum Status { FAILURE SUCCESS SKIPPED } class Report { def begin(count int) {} def completed(test Test, status Status) {} def end {} } class Failure { const expected string const observed string } ################################################################################ class Test { var _name = "" var _shouldSkip = false var _failure Failure = null def new { _all.append(self) } def name string { return _name } def failure Failure { return _failure } def shouldSkip bool { return _shouldSkip } def rename(name string) { _name = name } def skip { _shouldSkip = true } def before {} def after {} def run def expectString(expected string, observed string) { if expected != observed { _failure = Failure.new(expected, observed) throw null } } if TARGET == .JAVASCRIPT { def _tryRun { try { run } catch error dynamic { _failure ?= Failure.new("", ((error && error.stack || error) as dynamic) + "") } } } else if TARGET == .CSHARP { def _tryRun { try { run } catch error dynamic.System.Exception { _failure ?= Failure.new("", error.ToString()) } } } else { def _tryRun { try { run } catch { _failure ?= Failure.new("", "(runtime failure)") } } } } namespace Test { def runAll(report Report) { var tests = _all _all = [] report.begin(tests.count) for test in tests { if test.shouldSkip { report.completed(test, .SKIPPED) } else { test.before test._tryRun test.after report.completed(test, test._failure == null ? .SUCCESS : .FAILURE) } } report.end } var _all List = [] } ################################################################################ class TerminalReport : Report { var _failed List = [] var _wrapWidth = 0 var _startTime = 0.0 var _completed = 0 var _count = 0 var _skippedCount = 0 def skippedCount int { return _skippedCount } def failedCount int { return _failed.count } over begin(count int) { _wrapWidth = Terminal.width * 3 / 4 _startTime = Timestamp.seconds _count = count _completed = 0 Terminal.write("\n ") } over completed(test Test, status Status) { _completed++ switch status { case .FAILURE { _failed.append(test) Terminal.setColor(.RED) Terminal.write("x") } case .SUCCESS { Terminal.setColor(.GREEN) Terminal.write(".") } case .SKIPPED { _skippedCount++ Terminal.setColor(.YELLOW) Terminal.write("s") } } if _completed < _count && _wrapWidth != 0 && _completed % _wrapWidth == 0 { Terminal.write("\n ") } Terminal.setColor(.DEFAULT) Terminal.flush } over end { Terminal.print("\n") # Print the summary var totalTime = Math.floor((Timestamp.seconds - _startTime) * 10) as int Terminal.setColor(.GREEN) Terminal.write(" \(_count - _failed.count) passing") Terminal.setColor(.GRAY) Terminal.print(" (\(totalTime / 10).\(totalTime % 10)s)") if _failed.count != 0 { Terminal.setColor(.RED) Terminal.print(" \(_failed.count) failing") } if _skippedCount != 0 { Terminal.setColor(.YELLOW) Terminal.print(" \(_skippedCount) skipped") } Terminal.setColor(.DEFAULT) Terminal.print("") # Print the failed tests var indent = " ".repeat(_failed.count.toString.count + 5) for i in 0.._failed.count { var test = _failed[i] var text = " \(i + 1))" var failure = test.failure Terminal.setColor(.BOLD) Terminal.print(text + " ".repeat(indent.count - text.count) + test.name + "\n") _printDiff(indent, failure.expected == null ? [] : failure.expected.split("\n"), failure.observed == null ? [] : failure.observed.split("\n")) Terminal.setColor(.DEFAULT) Terminal.print("") } } } namespace TerminalReport { def _printDiff(indent string, expected List, observed List) { var m = expected.count var n = observed.count var matrix List = [] # Solve for the lowest common subsequence length if true { var ij = 0 for i in 0..m { for j in 0..n { matrix.append(expected[i] == observed[j] ? i > 0 && j > 0 ? matrix[ij - n - 1] + 1 : 1 : Math.max(i > 0 ? matrix[ij - n] : 0, j > 0 ? matrix[ij - 1] : 0)) ij++ } } } # Extract the diff in reverse var reversed List = [] if true { var i = m - 1 var j = n - 1 while i >= 0 || j >= 0 { var ij = i * n + j # Common if i >= 0 && j >= 0 && expected[i] == observed[j] { reversed.append(" " + expected[i]) i-- j-- } # Removal else if j >= 0 && (i < 0 || (j > 0 ? matrix[ij - 1] : 0) > (i > 0 ? matrix[ij - n] : 0)) { reversed.append("-" + observed[j]) j-- } # Insertion else { assert(i >= 0 && (j < 0 || (j > 0 ? matrix[ij - 1] : 0) <= (i > 0 ? matrix[ij - n] : 0))) reversed.append("+" + expected[i]) i-- } } } # Print out the diff for i in 0..reversed.count { var text = reversed[reversed.count - i - 1] var c = text[0] Terminal.setColor(c == '+' ? .GREEN : c == '-' ? .RED : .GRAY) Terminal.print(indent + text) } } } } ================================================ FILE: src/middle/callgraph.sk ================================================ namespace Skew { enum PassKind { CALL_GRAPH } class CallGraphPass : Pass { over kind PassKind { return .CALL_GRAPH } over run(context PassContext) { context.callGraph = CallGraph.new(context.global) } } class CallSite { const callNode Node const enclosingSymbol Symbol } class CallInfo { const symbol FunctionSymbol const callSites List = [] } class CallGraph { const callInfo List = [] const symbolToInfoIndex IntMap = {} def new(global ObjectSymbol) { _visitObject(global) } def callInfoForSymbol(symbol FunctionSymbol) CallInfo { assert(symbol.id in symbolToInfoIndex) return callInfo[symbolToInfoIndex[symbol.id]] } def _visitObject(symbol ObjectSymbol) { for object in symbol.objects { _visitObject(object) } for function in symbol.functions { _recordCallSite(function, null, null) _visitNode(function.block, function) } for variable in symbol.variables { _visitNode(variable.value, variable) } } def _visitNode(node Node, context Symbol) { if node != null { for child = node.firstChild; child != null; child = child.nextSibling { _visitNode(child, context) } if node.kind == .CALL && node.symbol != null { assert(node.symbol.kind.isFunction) _recordCallSite(node.symbol.forwarded.asFunctionSymbol, node, context) } } } def _recordCallSite(symbol FunctionSymbol, node Node, context Symbol) { var index = symbolToInfoIndex.get(symbol.id, -1) var info = index < 0 ? CallInfo.new(symbol) : callInfo[index] if index < 0 { symbolToInfoIndex[symbol.id] = callInfo.count callInfo.append(info) } if node != null { info.callSites.append(CallSite.new(node, context)) } } } } ================================================ FILE: src/middle/compiler.sk ================================================ namespace Skew { class CompilerTarget { def name string { return "" } def extension string { return "" } def stopAfterResolve bool { return true } def requiresIntegerSwitchStatements bool { return false } def supportsListForeach bool { return false } def supportsNestedTypes bool { return false } def needsLambdaLifting bool { return false } def removeSingletonInterfaces bool { return false } def stringEncoding Unicode.Encoding { return .UTF32 } def editOptions(options CompilerOptions) {} def includeSources(sources List) {} def createEmitter(context PassContext) Emitter { return null } } class Define { const name Range const value Range } class CompletionContext { const source Source const index int const _completions IntMap = {} var range Range = null def completions List { var values = _completions.values values.sort(Symbol.SORT_BY_NAME) return values } def addCompletion(symbol Symbol) { _completions[symbol.id] = symbol } } class CompilerOptions { var completionContext CompletionContext = null var defines StringMap = {} var foldAllConstants = false var globalizeAllFunctions = false var inlineAllFunctions = false var isAlwaysInlinePresent = false # This is set by the resolver var jsMangle = false var jsMinify = false var jsSourceMap = false var outputDirectory string = null var outputFile string = null var passes List = null var stopAfterResolve = false var target = CompilerTarget.new var verbose = false var warnAboutIgnoredComments = false var warningsAreErrors = false def new { passes = [ LexingPass.new, ParsingPass.new, MergingPass.new, ResolvingPass.new, LambdaConversionPass.new.onlyRunWhen(=> _continueAfterResolve && target.needsLambdaLifting), InterfaceRemovalPass.new.onlyRunWhen(=> _continueAfterResolve && target.removeSingletonInterfaces && globalizeAllFunctions), # The call graph is used as a shortcut so the tree only needs to be scanned once for all call-based optimizations CallGraphPass.new.onlyRunWhen(=> _continueAfterResolve), GlobalizingPass.new.onlyRunWhen(=> _continueAfterResolve), MotionPass.new.onlyRunWhen(=> _continueAfterResolve), RenamingPass.new.onlyRunWhen(=> _continueAfterResolve), FoldingPass.new.onlyRunWhen(=> _continueAfterResolve && foldAllConstants), InliningPass.new.onlyRunWhen(=> _continueAfterResolve && (inlineAllFunctions || isAlwaysInlinePresent)), FoldingPass.new.onlyRunWhen(=> _continueAfterResolve && (inlineAllFunctions || isAlwaysInlinePresent) && foldAllConstants), EmittingPass.new.onlyRunWhen(=> !stopAfterResolve), ] } def define(name string, value string) { var range = Source.new("", "--define:\(name)=\(value)").entireRange defines[name] = Define.new(range.slice(9, 9 + name.count), range.fromEnd(value.count)) } def _continueAfterResolve bool { return !stopAfterResolve && !target.stopAfterResolve } def createTargetFromExtension bool { if outputFile != null { var dot = outputFile.lastIndexOf(".") if dot != -1 { switch outputFile.slice(dot + 1) { case "cpp", "cxx", "cc" { target = CPlusPlusTarget.new } case "cs" { target = CSharpTarget.new } case "ts" { target = TypeScriptTarget.new } case "js" { target = JavaScriptTarget.new } default { return false } } return true } } return false } } class Timer { var _isStarted = false var _startTime = 0.0 var _totalSeconds = 0.0 def start { assert(!_isStarted) _isStarted = true _startTime = Timestamp.seconds } def stop { assert(_isStarted) _isStarted = false _totalSeconds += Timestamp.seconds - _startTime } def elapsedSeconds double { return _totalSeconds } def elapsedMilliseconds string { return formatNumber(_totalSeconds * 1000) + "ms" } def isZero bool { return _totalSeconds == 0 } } enum PassKind { # These values are defined near each pass } class PassContext { var log Log var options CompilerOptions var inputs List var cache = TypeCache.new var global = ObjectSymbol.new(.OBJECT_GLOBAL, "") var callGraph CallGraph = null var tokens List> = [] var outputs List = [] var isResolvePassComplete = false } class Pass { var _shouldRun fn() bool = null def kind PassKind def run(context PassContext) def shouldRun bool { return _shouldRun != null ? _shouldRun() : true } def onlyRunWhen(callback fn() bool) Pass { _shouldRun = callback return self } } class PassTimer { var kind PassKind var timer = Timer.new } enum StatisticsKind { SHORT LONG } class CompilerResult { var cache TypeCache var global ObjectSymbol var outputs List var passTimers List var totalTimer Timer def statistics(inputs List, kind StatisticsKind) string { var builder = StringBuilder.new var totalTime = totalTimer.elapsedSeconds var sourceStatistics = (name string, sources List) => { var totalBytes = 0 var totalLines = 0 for source in sources { totalBytes += source.contents.count if kind == .LONG { totalLines += source.lineCount } } builder.append("\(name)\(PrettyPrint.plural(sources.count)): ") builder.append(sources.count == 1 ? sources.first.name : "\(sources.count) files") builder.append(" (" + bytesToString(totalBytes)) builder.append(", " + bytesToString(Math.round(totalBytes / totalTime) as int) + "/s") if kind == .LONG { builder.append(", " + PrettyPrint.plural(totalLines, "line")) builder.append(", " + PrettyPrint.plural(Math.round(totalLines / totalTime) as int, "line") + "/s") } builder.append(")\n") } # Sources sourceStatistics("input", inputs) sourceStatistics("output", outputs) # Compilation time builder.append("time: \(totalTimer.elapsedMilliseconds)") if kind == .LONG { for passTimer in passTimers { builder.append("\n \(passTimer.kind): \(passTimer.timer.elapsedMilliseconds)") } } return builder.toString } } def compile(log Log, options CompilerOptions, inputs List) CompilerResult { inputs = inputs.clone options.target.includeSources(inputs) options.target.editOptions(options) inputs.prepend(Source.new("", UNICODE_LIBRARY)) inputs.prepend(Source.new("", NATIVE_LIBRARY)) var context = PassContext.new(log, options, inputs) var passTimers List = [] var totalTimer = Timer.new totalTimer.start # Run all passes, stop compilation if there are errors after resolving (wait until then to make IDE mode better) for pass in options.passes { if context.isResolvePassComplete && log.hasErrors { break } if pass.shouldRun { var passTimer = PassTimer.new(pass.kind) passTimers.append(passTimer) passTimer.timer.start pass.run(context) passTimer.timer.stop context.verify } } totalTimer.stop return CompilerResult.new(context.cache, context.global, context.outputs, passTimers, totalTimer) } class PassContext { @skip if RELEASE def verify { _verifyHierarchy(global) } def _verifySymbol(symbol Symbol) { if !isResolvePassComplete { return } # Special-case nested guards that aren't initialized when the outer guard has errors if symbol.state != .INITIALIZED { assert(symbol.kind.isObject) assert(symbol.isGuardConditional) assert(log.errorCount > 0) return } assert(symbol.state == .INITIALIZED) assert(symbol.resolvedType != null) if symbol.kind.isObject || symbol.kind.isFunction || symbol.kind.isParameter { if symbol.resolvedType == .DYNAMIC { assert(log.errorCount > 0) # Ignore errors due to cyclic declarations } else { assert(symbol.resolvedType.kind == .SYMBOL) assert(symbol.resolvedType.symbol == symbol) } } if symbol.kind.isFunction && symbol.resolvedType.kind == .SYMBOL { var function = symbol.asFunctionSymbol assert(symbol.resolvedType.returnType == function.returnType?.resolvedType) assert(symbol.resolvedType.argumentTypes.count == function.arguments.count) for i in 0..function.arguments.count { assert(symbol.resolvedType.argumentTypes[i] == function.arguments[i].resolvedType) } } if symbol.kind.isVariable { assert(symbol.resolvedType == symbol.asVariableSymbol.type.resolvedType) } } def _verifyHierarchy(symbol ObjectSymbol) { _verifySymbol(symbol) for object in symbol.objects { assert(object.parent == symbol) _verifyHierarchy(object) if object.extends != null { _verifyHierarchy(object.extends, null) } if object.implements != null { for node in object.implements { _verifyHierarchy(node, null) } } } for function in symbol.functions { assert(function.parent == symbol) _verifySymbol(function) if function.block != null { _verifyHierarchy(function.block, null) } } for variable in symbol.variables { assert(variable.parent == symbol) _verifySymbol(variable) assert(variable.state != .INITIALIZED || variable.type != null) if variable.type != null { _verifyHierarchy(variable.type, null) } if variable.value != null { _verifyHierarchy(variable.value, null) } } if symbol.guards != null { for guard in symbol.guards { _verifyHierarchy(guard, symbol) } } } def _verifyHierarchy(node Node, parent Node) { assert(node.parent == parent) # All expressions must have a type after the type resolution pass if isResolvePassComplete && node.kind.isExpression { assert(node.resolvedType != null) } if node.kind == .VARIABLE { assert(node.symbol != null) assert(node.symbol.kind == .VARIABLE_LOCAL) var variable = node.symbol.asVariableSymbol assert(variable.value == node.variableValue) _verifySymbol(variable) assert(variable.state != .INITIALIZED || variable.type != null) if variable.type != null { _verifyHierarchy(variable.type, null) } } else if node.kind == .LAMBDA { assert(node.symbol != null) assert(node.symbol.kind == .FUNCTION_LOCAL) assert(node.symbol.asFunctionSymbol.block == node.lambdaBlock) _verifySymbol(node.symbol) } for child = node.firstChild; child != null; child = child.nextSibling { _verifyHierarchy(child, node) } } def _verifyHierarchy(guard Guard, parent ObjectSymbol) { assert(guard.parent == parent) assert(guard.contents.parent == parent) if guard.test != null { _verifyHierarchy(guard.test, null) } _verifyHierarchy(guard.contents) if guard.elseGuard != null { _verifyHierarchy(guard.elseGuard, parent) } } } } ================================================ FILE: src/middle/controlflow.sk ================================================ namespace Skew { # This does a simple control flow analysis without constructing a full # control flow graph. The result of this analysis is setting the flag # HAS_CONTROL_FLOW_AT_END on all blocks where control flow reaches the end. # # It makes a few assumptions around exceptions to make life easier. Normal # code without throw statements is assumed not to throw. For example, all # property accesses are assumed to succeed and not throw null pointer errors. # This is mostly consistent with how C++ operates for better or worse, and # is also consistent with how people read code. It also assumes flow always # can enter every catch block. Otherwise, why is it there? class ControlFlowAnalyzer { var _isLoopBreakTarget List = [] var _isControlFlowLive List = [] def pushBlock(node Node) { var parent = node.parent # Push control flow _isControlFlowLive.append(_isControlFlowLive.isEmpty || _isControlFlowLive.last) # Push loop info if parent != null && parent.kind.isLoop { _isLoopBreakTarget.append(false) } } def popBlock(node Node) { var parent = node.parent # Pop control flow var isLive = _isControlFlowLive.takeLast if isLive { node.flags |= .HAS_CONTROL_FLOW_AT_END } # Pop loop info if parent != null && parent.kind.isLoop && !_isLoopBreakTarget.takeLast && ( parent.kind == .WHILE && parent.whileTest.isTrue || parent.kind == .FOR && parent.forTest.isTrue) { _isControlFlowLive.last = false } } def visitStatementInPostOrder(node Node) { if !_isControlFlowLive.last { return } switch node.kind { case .BREAK { if !_isLoopBreakTarget.isEmpty { _isLoopBreakTarget.last = true } _isControlFlowLive.last = false } case .RETURN, .THROW, .CONTINUE { _isControlFlowLive.last = false } case .IF { var test = node.ifTest var trueBlock = node.ifTrue var falseBlock = node.ifFalse if test.isTrue { if !trueBlock.hasControlFlowAtEnd { _isControlFlowLive.last = false } } else if test.isFalse && falseBlock != null { if !falseBlock.hasControlFlowAtEnd { _isControlFlowLive.last = false } } else if trueBlock != null && falseBlock != null { if !trueBlock.hasControlFlowAtEnd && !falseBlock.hasControlFlowAtEnd { _isControlFlowLive.last = false } } } case .SWITCH { var child = node.switchValue.nextSibling var foundDefaultCase = false while child != null && !child.caseBlock.hasControlFlowAtEnd { if child.hasOneChild { foundDefaultCase = true } child = child.nextSibling } if child == null && foundDefaultCase { _isControlFlowLive.last = false } } } } } } ================================================ FILE: src/middle/folding.sk ================================================ namespace Skew { enum PassKind { FOLDING } class FoldingPass : Pass { over kind PassKind { return .FOLDING } over run(context PassContext) { Folding.ConstantFolder.new(context.cache, context.options, null).visitObject(context.global) } } } namespace Skew.Folding { class ConstantFolder { const _cache TypeCache const _options CompilerOptions const _prepareSymbol fn(Symbol) const _constantCache IntMap = {} def visitObject(symbol ObjectSymbol) { for object in symbol.objects { visitObject(object) } for function in symbol.functions { if function.block != null { foldConstants(function.block) } } for variable in symbol.variables { if variable.value != null { foldConstants(variable.value) } } } # Use this instead of node.become(Node.createConstant(content)) to avoid more GC def _flatten(node Node, content Content) { node.removeChildren node.kind = .CONSTANT node.content = content node.symbol = null } # Use this instead of node.become(Node.createBool(value)) to avoid more GC def _flattenBool(node Node, value bool) { assert(_cache.isEquivalentToBool(node.resolvedType) || node.resolvedType == .DYNAMIC) _flatten(node, BoolContent.new(value)) } # Use this instead of node.become(Node.createInt(value)) to avoid more GC def _flattenInt(node Node, value int) { assert(_cache.isEquivalentToInt(node.resolvedType) || node.resolvedType == .DYNAMIC) _flatten(node, IntContent.new(value)) } # Use this instead of node.become(Node.createDouble(value)) to avoid more GC def _flattenDouble(node Node, value double) { assert(_cache.isEquivalentToDouble(node.resolvedType) || node.resolvedType == .DYNAMIC) _flatten(node, DoubleContent.new(value)) } # Use this instead of node.become(Node.createString(value)) to avoid more GC def _flattenString(node Node, value string) { assert(_cache.isEquivalentToString(node.resolvedType) || node.resolvedType == .DYNAMIC) _flatten(node, StringContent.new(value)) } def foldConstants(node Node) { var kind = node.kind # Transform "a + (b + c)" => "(a + b) + c" before operands are folded if kind == .ADD && node.resolvedType == _cache.stringType && node.binaryLeft.resolvedType == _cache.stringType && node.binaryRight.resolvedType == _cache.stringType { _rotateStringConcatenation(node) } # Fold operands before folding this node for child = node.firstChild; child != null; child = child.nextSibling { foldConstants(child) } # Separating the case bodies into separate functions makes the JavaScript JIT go faster switch kind { case .BLOCK { _foldBlock(node) } case .CALL { _foldCall(node) } case .CAST { _foldCast(node) } case .DOT { _foldDot(node) } case .HOOK { _foldHook(node) } case .NAME { _foldName(node) } case .COMPLEMENT, .NEGATIVE, .NOT, .POSITIVE { _foldUnary(node) } default { if kind.isBinary { _foldBinary(node) } } } } def _rotateStringConcatenation(node Node) { var left = node.binaryLeft var right = node.binaryRight assert(node.kind == .ADD) assert(left.resolvedType == _cache.stringType || left.resolvedType == .DYNAMIC) assert(right.resolvedType == _cache.stringType || right.resolvedType == .DYNAMIC) # "a + (b + c)" => "(a + b) + c" if right.kind == .ADD { assert(right.binaryLeft.resolvedType == _cache.stringType || right.binaryLeft.resolvedType == .DYNAMIC) assert(right.binaryRight.resolvedType == _cache.stringType || right.binaryRight.resolvedType == .DYNAMIC) node.rotateBinaryRightToLeft } } def _foldStringConcatenation(node Node) { var left = node.binaryLeft var right = node.binaryRight assert(left.resolvedType == _cache.stringType || left.resolvedType == .DYNAMIC) assert(right.resolvedType == _cache.stringType || right.resolvedType == .DYNAMIC) if right.isString { # "a" + "b" => "ab" if left.isString { _flattenString(node, left.asString + right.asString) } else if left.kind == .ADD { var leftLeft = left.binaryLeft var leftRight = left.binaryRight assert(leftLeft.resolvedType == _cache.stringType || leftLeft.resolvedType == .DYNAMIC) assert(leftRight.resolvedType == _cache.stringType || leftRight.resolvedType == .DYNAMIC) # (a + "b") + "c" => a + "bc" if leftRight.isString { _flattenString(leftRight, leftRight.asString + right.asString) node.become(left.remove) } } } } def _foldTry(node Node) int { var tryBlock = node.tryBlock # var finallyBlock = node.finallyBlock # A try block without any statements cannot possibly throw if !tryBlock.hasChildren { node.remove return -1 } return 0 } def _foldIf(node Node) { var test = node.ifTest var trueBlock = node.ifTrue var falseBlock = node.ifFalse # No reason to keep an empty "else" block if falseBlock != null && !falseBlock.hasChildren { falseBlock.remove falseBlock = null } # Always true if statement if test.isTrue { # Inline the contents of the true block node.replaceWithChildrenFrom(trueBlock) } # Always false if statement else if test.isFalse { # Remove entirely if falseBlock == null { node.remove } # Inline the contents of the false block else { node.replaceWithChildrenFrom(falseBlock) } } # Remove if statements with empty true blocks else if !trueBlock.hasChildren { # "if (a) {} else b;" => "if (!a) b;" if falseBlock != null && falseBlock.hasChildren { test.invertBooleanCondition(_cache) trueBlock.remove } # "if (a) {}" => "" else if test.hasNoSideEffects { node.remove } # "if (a) {}" => "a;" else { node.become(Node.createExpression(test.remove)) } } } def _foldSwitch(node Node) { var value = node.switchValue var defaultCase Node = null # Check for a default case for child = value.nextSibling; child != null; child = child.nextSibling { if child.hasOneChild { defaultCase = child break } } # Remove the default case if it's empty if defaultCase != null && !defaultCase.caseBlock.hasChildren { defaultCase.remove defaultCase = null } # Check for a constant value and inline the corresponding case block if value.kind == .CONSTANT { var hasNonConstant = false # Search all case blocks for a match for child = value.nextSibling, nextChild Node = null; child != null; child = nextChild { nextChild = child.nextSibling var block = child.caseBlock for caseValue = child.firstChild, nextCase Node = null; caseValue != block; caseValue = nextCase { nextCase = caseValue.nextSibling # If there's a non-constant value, we can't tell if it's taken or not if caseValue.kind != .CONSTANT { hasNonConstant = true } # Remove cases that definitely don't apply else if !value.content.equals(caseValue.content) { caseValue.remove } # Only inline this case if all previous values have been constants, # otherwise we can't be sure that none of those would have matched else if !hasNonConstant { node.replaceWithChildrenFrom(block) return } } # Remove the case entirely if all values were trimmed if child.hasOneChild && child != defaultCase { child.remove } } # Inline the default case if it's present and it can be proven to be taken if !hasNonConstant { if defaultCase != null { node.replaceWithChildrenFrom(defaultCase.caseBlock) } else { node.remove } return } } # If the default case is missing, all other empty cases can be removed too if defaultCase == null { for child = node.lastChild, previous Node = null; child != value; child = previous { previous = child.previousSibling if !child.caseBlock.hasChildren { child.remove } } } # Replace "switch (foo) {}" with "foo;" if node.hasOneChild { node.become(Node.createExpression(value.remove).withRange(node.range)) } } def _foldVariables(node Node) { # Remove symbols entirely that are being inlined everywhere for child = node.firstChild, next Node = null; child != null; child = next { assert(child.kind == .VARIABLE) next = child.nextSibling var symbol = child.symbol.asVariableSymbol if symbol.isConst && constantForSymbol(symbol) != null { child.remove } } # Empty variable statements are not allowed if !node.hasChildren { node.remove } } def _foldBlock(node Node) { for child = node.firstChild, next Node = null; child != null; child = next { next = child.nextSibling var kind = child.kind # Remove everything after a jump if kind.isJump { while child.nextSibling != null { child.nextSibling.remove } break } # Remove constants and "while false { ... }" entirely if kind == .EXPRESSION && child.expressionValue.hasNoSideEffects || kind == .WHILE && child.whileTest.isFalse { child.remove } # Remove dead assignments else if kind == .EXPRESSION && child.expressionValue.kind == .ASSIGN { _foldAssignment(child) } else if kind == .VARIABLES { _foldVariables(child) } # Remove unused try statements since they can cause deoptimizations else if kind == .TRY { _foldTry(child) } # Statically evaluate if statements where possible else if kind == .IF { _foldIf(child) } # Fold switch statements else if kind == .SWITCH { _foldSwitch(child) } } } # "a = 0; b = 0; a = 1;" => "b = 0; a = 1;" def _foldAssignment(node Node) { assert(node.kind == .EXPRESSION && node.expressionValue.kind == .ASSIGN) var value = node.expressionValue var left = value.binaryLeft var right = value.binaryRight # Only do this for simple variable assignments var dotVariable = left.kind == .DOT && _isVariableReference(left.dotTarget) ? left.dotTarget.symbol : null var variable = _isVariableReference(left) || dotVariable != null ? left.symbol : null if variable == null { return } # Make sure the assigned value doesn't need the previous value. We bail # on expressions with side effects like function calls and on expressions # that reference the variable. if !right.hasNoSideEffects || _hasNestedReference(right, variable) { return } # Scan backward over previous statements var previous = node.previousSibling while previous != null { # Only pattern-match expressions if previous.kind == .EXPRESSION { var previousValue = previous.expressionValue # Remove duplicate assignments if previousValue.kind == .ASSIGN { var previousLeft = previousValue.binaryLeft var previousRight = previousValue.binaryRight var previousDotVariable = previousLeft.kind == .DOT && _isVariableReference(previousLeft.dotTarget) ? previousLeft.dotTarget.symbol : null var previousVariable = _isVariableReference(previousLeft) || previousDotVariable != null && previousDotVariable == dotVariable ? previousLeft.symbol : null # Check for assignment to the same variable and remove the assignment # if it's a match. Make sure to keep the assigned value around if it # has side effects. if previousVariable == variable { if previousRight.hasNoSideEffects { previous.remove } else { previousValue.replaceWith(previousRight.remove) } break } # Stop if we can't determine that this statement doesn't involve # this variable's value. If it does involve this variable's value, # then it isn't safe to remove duplicate assignments past this # statement. if !previousRight.hasNoSideEffects || _hasNestedReference(previousRight, variable) { break } } # Also stop here if we can't determine that this statement doesn't # involve this variable's value else if !previousValue.hasNoSideEffects { break } } # Also stop here if we can't determine that this statement doesn't # involve this variable's value else { break } previous = previous.previousSibling } } def _foldDot(node Node) { var symbol = node.symbol # Only replace this with a constant if the target has no side effects. # This catches constants declared on imported types. if _shouldFoldSymbol(symbol) && !node.isAssignTarget && (node.dotTarget == null || node.dotTarget.hasNoSideEffects) { var content = constantForSymbol(symbol.asVariableSymbol) if content != null { _flatten(node, content) } } } def _foldName(node Node) { var symbol = node.symbol # Don't fold loop variables since they aren't actually constant across loop iterations if _shouldFoldSymbol(symbol) && !node.isAssignTarget && !symbol.isLoopVariable { var content = constantForSymbol(symbol.asVariableSymbol) if content != null { _flatten(node, content) } } } def _foldCall(node Node) { var value = node.callValue var symbol = value.symbol # Fold instance function calls if value.kind == .DOT { var target = value.dotTarget # Folding of double.toString can't be done in a platform-independent # manner. The obvious cases are NaN and infinity, but even fractions # are emitted differently on different platforms. Instead of having # constant folding change how the code behaves, just don't fold double # toString calls. # # "bool.toString" # "int.toString" # if target != null && target.kind == .CONSTANT { if _isKnownCall(symbol, _cache.boolToStringSymbol) { _flattenString(node, target.asBool.toString) } else if _isKnownCall(symbol, _cache.intToStringSymbol) { _flattenString(node, target.asInt.toString) } } } # Fold global function calls else if value.kind == .NAME { # "\"abc\".count" => "3" if _isKnownCall(symbol, _cache.stringCountSymbol) && node.lastChild.isString { _flattenInt(node, Unicode.codeUnitCountForCodePoints(node.lastChild.asString.codePoints, _options.target.stringEncoding)) } # "3 ** 2" => "9" else if _isKnownCall(symbol, _cache.intPowerSymbol) && node.lastChild.isInt && value.nextSibling.isInt { _flattenInt(node, value.nextSibling.asInt ** node.lastChild.asInt) } # "0.0625 ** 0.25" => "0.5" # "Math.pow(0.0625, 0.25)" => "0.5" else if (_isKnownCall(symbol, _cache.doublePowerSymbol) || _isKnownCall(symbol, _cache.mathPowSymbol)) && node.lastChild.isDouble && value.nextSibling.isDouble { _flattenDouble(node, value.nextSibling.asDouble ** node.lastChild.asDouble) } # "string.fromCodePoint(100)" => "\"d\"" # "string.fromCodeUnit(100)" => "\"d\"" else if (_isKnownCall(symbol, _cache.stringFromCodePointSymbol) || _isKnownCall(symbol, _cache.stringFromCodeUnitSymbol)) && node.lastChild.isInt { _flattenString(node, string.fromCodePoint(node.lastChild.asInt)) # "fromCodePoint" is a superset of "fromCodeUnit" } # "string.fromCodePoints([97, 98, 99])" => "\"abc\"" # "string.fromCodeUnits([97, 98, 99])" => "\"abc\"" else if (_isKnownCall(symbol, _cache.stringFromCodePointsSymbol) || _isKnownCall(symbol, _cache.stringFromCodeUnitsSymbol)) && node.lastChild.kind == .INITIALIZER_LIST { var codePoints List = [] for child = node.lastChild.firstChild; child != null; child = child.nextSibling { if !child.isInt { return } codePoints.append(child.asInt) } _flattenString(node, string.fromCodePoints(codePoints)) # "fromCodePoints" is a superset of "fromCodeUnits" } } } def _foldCast(node Node) { var type = node.castType.resolvedType var value = node.castValue if value.kind == .CONSTANT { var content = value.content var kind = content.kind # Cast "bool" values if kind == .BOOL { if _cache.isEquivalentToBool(type) { _flattenBool(node, value.asBool) } else if _cache.isEquivalentToInt(type) { _flattenInt(node, value.asBool as int) } else if _cache.isEquivalentToDouble(type) { _flattenDouble(node, value.asBool as double) } } # Cast "int" values else if kind == .INT { if _cache.isEquivalentToBool(type) { _flattenBool(node, value.asInt as bool) } else if _cache.isEquivalentToInt(type) { _flattenInt(node, value.asInt) } else if _cache.isEquivalentToDouble(type) { _flattenDouble(node, value.asInt) } } # Cast "double" values else if kind == .DOUBLE { if _cache.isEquivalentToBool(type) { _flattenBool(node, value.asDouble as bool) } else if _cache.isEquivalentToInt(type) { _flattenInt(node, value.asDouble as int) } else if _cache.isEquivalentToDouble(type) { _flattenDouble(node, value.asDouble) } } } } def _foldUnary(node Node) { var value = node.unaryValue var kind = node.kind if value.kind == .CONSTANT { var content = value.content var contentKind = content.kind # Fold "bool" values if contentKind == .BOOL { if kind == .NOT { _flattenBool(node, !value.asBool) } } # Fold "int" values else if contentKind == .INT { if kind == .POSITIVE { _flattenInt(node, +value.asInt) } else if kind == .NEGATIVE { _flattenInt(node, -value.asInt) } else if kind == .COMPLEMENT { _flattenInt(node, ~value.asInt) } } # Fold "float" or "double" values else if contentKind == .DOUBLE { if kind == .POSITIVE { _flattenDouble(node, +value.asDouble) } else if kind == .NEGATIVE { _flattenDouble(node, -value.asDouble) } } } # Partial evaluation ("!!x" isn't necessarily "x" if we don't know the type) else if kind == .NOT && value.resolvedType != .DYNAMIC { switch value.kind { case .NOT, .EQUAL, .NOT_EQUAL, .LOGICAL_OR, .LOGICAL_AND, .LESS_THAN, .GREATER_THAN, .LESS_THAN_OR_EQUAL, .GREATER_THAN_OR_EQUAL { value.invertBooleanCondition(_cache) node.become(value.remove) } } } } def _foldConstantIntegerAddOrSubtract(node Node, variable Node, constant Node, delta int) { var isAdd = node.kind == .ADD var needsContentUpdate = delta != 0 var isRightConstant = constant == node.binaryRight var shouldNegateConstant = !isAdd && isRightConstant var value = constant.asInt # Make this an add for simplicity if shouldNegateConstant { value = -value } # Include the delta from the parent node if present value += delta # 0 + a => a # 0 - a => -a # a + 0 => a # a - 0 => a if value == 0 { node.become(isAdd || isRightConstant ? variable.remove : Node.createUnary(.NEGATIVE, variable.remove).withType(node.resolvedType)) return } # Check for nested addition or subtraction if variable.kind == .ADD || variable.kind == .SUBTRACT { var left = variable.binaryLeft var right = variable.binaryRight assert(left.resolvedType == _cache.intType || left.resolvedType == .DYNAMIC) assert(right.resolvedType == _cache.intType || right.resolvedType == .DYNAMIC) # (a + 1) + 2 => a + 3 var isLeftConstant = left.isInt if isLeftConstant || right.isInt { _foldConstantIntegerAddOrSubtract(variable, isLeftConstant ? right : left, isLeftConstant ? left : right, value) node.become(variable.remove) return } } # Adjust the value so it has the correct sign if shouldNegateConstant { value = -value } # The negative sign can often be removed by code transformation if value < 0 { # a + -1 => a - 1 # a - -1 => a + 1 if isRightConstant { node.kind = isAdd ? .SUBTRACT : .ADD value = -value needsContentUpdate = true } # -1 + a => a - 1 else if isAdd { node.kind = .SUBTRACT value = -value variable.swapWith(constant) needsContentUpdate = true } } # Avoid extra allocations if needsContentUpdate { constant.content = IntContent.new(value) } # Also handle unary negation on "variable" _foldAddOrSubtract(node) } def _foldAddOrSubtract(node Node) { var isAdd = node.kind == .ADD var left = node.binaryLeft var right = node.binaryRight # -a + b => b - a if left.kind == .NEGATIVE && isAdd { left.become(left.unaryValue.remove) left.swapWith(right) node.kind = .SUBTRACT } # a + -b => a - b # a - -b => a + b else if right.kind == .NEGATIVE { right.become(right.unaryValue.remove) node.kind = isAdd ? .SUBTRACT : .ADD } # 0 + a => a # 0 - a => -a else if left.isZero { node.become(isAdd ? right.remove : Node.createUnary(.NEGATIVE, right.remove).withType(node.resolvedType)) } # a + 0 => a # a - 0 => a else if right.isZero { node.become(left.remove) } } def _foldConstantIntegerMultiply(node Node, variable Node, constant Node) { assert(constant.isInt) # Apply identities var variableIsInt = variable.resolvedType == _cache.intType var value = constant.asInt # Replacing values with 0 only works for integers. Doubles can be NaN and # NaN times anything is NaN, zero included. if value == 0 && variableIsInt { if variable.hasNoSideEffects { node.become(constant.remove) } return } # This identity works even with NaN if value == 1 { node.become(variable.remove) return } # Multiply by a power of 2 should be a left-shift operation, which is # more concise and always faster (or at least never slower) than the # alternative. Division can't be replaced by a right-shift operation # because that would lead to incorrect results for negative numbers. if variableIsInt { var shift = _logBase2(value) if shift != -1 { # "x * 2 * 4" => "x << 3" if variable.kind == .SHIFT_LEFT && variable.binaryRight.isInt { shift += variable.binaryRight.asInt variable.replaceWith(variable.binaryLeft.remove) } constant.content = IntContent.new(shift) node.kind = .SHIFT_LEFT } } } # "((a >> 8) & 255) << 8" => "a & (255 << 8)" # "((a >>> 8) & 255) << 8" => "a & (255 << 8)" # "((a >> 7) & 255) << 8" => "(a << 1) & (255 << 8)" # "((a >>> 7) & 255) << 8" => "(a << 1) & (255 << 8)" # "((a >> 8) & 255) << 7" => "(a >> 1) & (255 << 7)" # "((a >>> 8) & 255) << 7" => "(a >>> 1) & (255 << 7)" def _foldConstantBitwiseAndInsideShift(node Node, andLeft Node, andRight Node) { assert(node.kind == .SHIFT_LEFT && node.binaryRight.isInt) if andRight.isInt && (andLeft.kind == .SHIFT_RIGHT || andLeft.kind == .UNSIGNED_SHIFT_RIGHT) && andLeft.binaryRight.isInt { var mask = andRight.asInt var leftShift = node.binaryRight.asInt var rightShift = andLeft.binaryRight.asInt var value = andLeft.binaryLeft.remove if leftShift < rightShift { value = Node.createBinary(andLeft.kind, value, _cache.createInt(rightShift - leftShift)).withType(_cache.intType) } else if leftShift > rightShift { value = Node.createBinary(.SHIFT_LEFT, value, _cache.createInt(leftShift - rightShift)).withType(_cache.intType) } node.become(Node.createBinary(.BITWISE_AND, value, _cache.createInt(mask << leftShift)).withType(node.resolvedType)) } } def _foldConstantBitwiseAndInsideBitwiseOr(node Node) { assert(node.kind == .BITWISE_OR && node.binaryLeft.kind == .BITWISE_AND) var left = node.binaryLeft var right = node.binaryRight var leftLeft = left.binaryLeft var leftRight = left.binaryRight # "(a & b) | (a & c)" => "a & (b | c)" if right.kind == .BITWISE_AND { var rightLeft = right.binaryLeft var rightRight = right.binaryRight if leftRight.isInt && rightRight.isInt && _isSameVariableReference(leftLeft, rightLeft) { var mask = leftRight.asInt | rightRight.asInt node.become(Node.createBinary(.BITWISE_AND, leftLeft.remove, _cache.createInt(mask)).withType(node.resolvedType)) } } # "(a & b) | c" => "a | c" when "(a | b) == ~0" else if right.isInt && leftRight.isInt && (leftRight.asInt | right.asInt) == ~0 { left.become(leftLeft.remove) } } def _foldBinaryWithConstant(node Node, left Node, right Node) { # There are lots of other folding opportunities for most binary operators # here but those usually have a negligible performance and/or size impact # on the generated code and instead slow the compiler down. Only certain # ones are implemented below. switch node.kind { # These are important for dead code elimination case .LOGICAL_AND { if left.isFalse || right.isTrue { node.become(left.remove) } else if left.isTrue { node.become(right.remove) } } case .LOGICAL_OR { if left.isTrue || right.isFalse { node.become(left.remove) } else if left.isFalse { node.become(right.remove) } } # Constants are often added up in compound expressions. Folding # addition/subtraction improves minification in JavaScript and often # helps with readability. case .ADD, .SUBTRACT { if left.isInt { _foldConstantIntegerAddOrSubtract(node, right, left, 0) } else if right.isInt { _foldConstantIntegerAddOrSubtract(node, left, right, 0) } else { _foldAddOrSubtract(node) } } # Multiplication is special-cased here because in JavaScript, optimizing # away the general-purpose Math.imul function may result in large # speedups when it's implemented with a polyfill. case .MULTIPLY { if right.isInt { _foldConstantIntegerMultiply(node, left, right) } } # This improves generated code for inlined bit packing functions case .SHIFT_LEFT, .SHIFT_RIGHT, .UNSIGNED_SHIFT_RIGHT { # "x << 0" => "x" # "x >> 0" => "x" # "x >>> 0" => "x" if _cache.isEquivalentToInt(left.resolvedType) && right.isInt && right.asInt == 0 { node.become(left.remove) } # Handle special cases of "&" nested inside "<<" else if node.kind == .SHIFT_LEFT && left.kind == .BITWISE_AND && right.isInt { _foldConstantBitwiseAndInsideShift(node, left.binaryLeft, left.binaryRight) } # "x << 1 << 2" => "x << 3" # "x >> 1 >> 2" => "x >> 3" # "x >>> 1 >>> 2" => "x >>> 3" else if node.kind == left.kind && left.binaryRight.isInt && right.isInt { _flattenInt(right, left.binaryRight.asInt + right.asInt) left.replaceWith(left.binaryLeft.remove) } } case .BITWISE_AND { if right.isInt && _cache.isEquivalentToInt(left.resolvedType) { var value = right.asInt # "x & ~0" => "x" if value == ~0 { node.become(left.remove) } # "x & 0" => "0" else if value == 0 && left.hasNoSideEffects { node.become(right.remove) } } } case .BITWISE_OR { if right.isInt && _cache.isEquivalentToInt(left.resolvedType) { var value = right.asInt # "x | 0" => "x" if value == 0 { node.become(left.remove) return } # "x | ~0" => "~0" else if value == ~0 && left.hasNoSideEffects { node.become(right.remove) return } } if left.kind == .BITWISE_AND { _foldConstantBitwiseAndInsideBitwiseOr(node) } } } } def _foldBinary(node Node) { var kind = node.kind if kind == .ADD && node.resolvedType == _cache.stringType { _foldStringConcatenation(node) return } var left = node.binaryLeft var right = node.binaryRight # Canonicalize the order of commutative operators if (kind == .MULTIPLY || kind == .BITWISE_AND || kind == .BITWISE_OR) && left.kind == .CONSTANT && right.kind != .CONSTANT { var temp = left left = right right = temp left.swapWith(right) } if left.kind == .CONSTANT && right.kind == .CONSTANT { var leftContent = left.content var rightContent = right.content var leftKind = leftContent.kind var rightKind = rightContent.kind # Fold equality operators if leftKind == .STRING && rightKind == .STRING { switch kind { case .EQUAL { _flattenBool(node, leftContent.asString == rightContent.asString) } case .NOT_EQUAL { _flattenBool(node, leftContent.asString != rightContent.asString) } case .LESS_THAN { _flattenBool(node, leftContent.asString < rightContent.asString) } case .GREATER_THAN { _flattenBool(node, leftContent.asString > rightContent.asString) } case .LESS_THAN_OR_EQUAL { _flattenBool(node, leftContent.asString <= rightContent.asString) } case .GREATER_THAN_OR_EQUAL { _flattenBool(node, leftContent.asString >= rightContent.asString) } } return } # Fold "bool" values else if leftKind == .BOOL && rightKind == .BOOL { switch kind { case .LOGICAL_AND { _flattenBool(node, leftContent.asBool && rightContent.asBool) } case .LOGICAL_OR { _flattenBool(node, leftContent.asBool || rightContent.asBool) } case .EQUAL { _flattenBool(node, leftContent.asBool == rightContent.asBool) } case .NOT_EQUAL { _flattenBool(node, leftContent.asBool != rightContent.asBool) } } return } # Fold "int" values else if leftKind == .INT && rightKind == .INT { switch kind { case .ADD { _flattenInt(node, leftContent.asInt + rightContent.asInt) } case .BITWISE_AND { _flattenInt(node, leftContent.asInt & rightContent.asInt) } case .BITWISE_OR { _flattenInt(node, leftContent.asInt | rightContent.asInt) } case .BITWISE_XOR { _flattenInt(node, leftContent.asInt ^ rightContent.asInt) } case .DIVIDE { _flattenInt(node, leftContent.asInt / rightContent.asInt) } case .EQUAL { _flattenBool(node, leftContent.asInt == rightContent.asInt) } case .GREATER_THAN { _flattenBool(node, leftContent.asInt > rightContent.asInt) } case .GREATER_THAN_OR_EQUAL { _flattenBool(node, leftContent.asInt >= rightContent.asInt) } case .LESS_THAN { _flattenBool(node, leftContent.asInt < rightContent.asInt) } case .LESS_THAN_OR_EQUAL { _flattenBool(node, leftContent.asInt <= rightContent.asInt) } case .MULTIPLY { _flattenInt(node, leftContent.asInt * rightContent.asInt) } case .NOT_EQUAL { _flattenBool(node, leftContent.asInt != rightContent.asInt) } case .REMAINDER { _flattenInt(node, leftContent.asInt % rightContent.asInt) } case .SHIFT_LEFT { _flattenInt(node, leftContent.asInt << rightContent.asInt) } case .SHIFT_RIGHT { _flattenInt(node, leftContent.asInt >> rightContent.asInt) } case .SUBTRACT { _flattenInt(node, leftContent.asInt - rightContent.asInt) } case .UNSIGNED_SHIFT_RIGHT { _flattenInt(node, leftContent.asInt >>> rightContent.asInt) } } return } # Fold "double" values else if leftKind == .DOUBLE && rightKind == .DOUBLE { switch kind { case .ADD { _flattenDouble(node, leftContent.asDouble + rightContent.asDouble) } case .SUBTRACT { _flattenDouble(node, leftContent.asDouble - rightContent.asDouble) } case .MULTIPLY { _flattenDouble(node, leftContent.asDouble * rightContent.asDouble) } case .DIVIDE { _flattenDouble(node, leftContent.asDouble / rightContent.asDouble) } case .EQUAL { _flattenBool(node, leftContent.asDouble == rightContent.asDouble) } case .NOT_EQUAL { _flattenBool(node, leftContent.asDouble != rightContent.asDouble) } case .LESS_THAN { _flattenBool(node, leftContent.asDouble < rightContent.asDouble) } case .GREATER_THAN { _flattenBool(node, leftContent.asDouble > rightContent.asDouble) } case .LESS_THAN_OR_EQUAL { _flattenBool(node, leftContent.asDouble <= rightContent.asDouble) } case .GREATER_THAN_OR_EQUAL { _flattenBool(node, leftContent.asDouble >= rightContent.asDouble) } } return } } _foldBinaryWithConstant(node, left, right) } def _foldHook(node Node) { var test = node.hookTest if test.isTrue { node.become(node.hookTrue.remove) } else if test.isFalse { node.become(node.hookFalse.remove) } } def constantForSymbol(symbol VariableSymbol) Content { if symbol.id in _constantCache { return _constantCache[symbol.id] } if _prepareSymbol != null { _prepareSymbol(symbol) } var constant Content = null var value = symbol.value if symbol.isConst && value != null { _constantCache[symbol.id] = null value = value.clone foldConstants(value) if value.kind == .CONSTANT { constant = value.content } } _constantCache[symbol.id] = constant return constant } } namespace ConstantFolder { def _isVariableReference(node Node) bool { return node.kind == .NAME && node.symbol != null && node.symbol.kind.isVariable } def _isSameVariableReference(a Node, b Node) bool { return _isVariableReference(a) && _isVariableReference(b) && a.symbol == b.symbol || a.kind == .CAST && b.kind == .CAST && _isSameVariableReference(a.castValue, b.castValue) } def _hasNestedReference(node Node, symbol Symbol) bool { assert(symbol != null) if node.symbol == symbol { return true } for child = node.firstChild; child != null; child = child.nextSibling { if _hasNestedReference(child, symbol) { return true } } return false } def _shouldFoldSymbol(symbol Symbol) bool { return symbol != null && symbol.isConst && (symbol.kind != .VARIABLE_INSTANCE || symbol.isImported) } def _isKnownCall(symbol Symbol, knownSymbol Symbol) bool { return symbol == knownSymbol || symbol != null && symbol.kind.isFunction && ( symbol.asFunctionSymbol.overloaded == knownSymbol || knownSymbol.kind.isFunction && symbol.asFunctionSymbol.overloaded != null && symbol.asFunctionSymbol.overloaded == knownSymbol.asFunctionSymbol.overloaded && symbol.asFunctionSymbol.argumentOnlyType == knownSymbol.asFunctionSymbol.argumentOnlyType) } # Returns the log2(value) or -1 if log2(value) is not an integer def _logBase2(value int) int { if value < 1 || (value & (value - 1)) != 0 { return -1 } var result = 0 while value > 1 { value >>= 1 result++ } return result } } } ================================================ FILE: src/middle/globalizing.sk ================================================ namespace Skew { enum PassKind { GLOBALIZING } class GlobalizingPass : Pass { over kind PassKind { return .GLOBALIZING } over run(context PassContext) { var globalizeAllFunctions = context.options.globalizeAllFunctions var virtualLookup = globalizeAllFunctions || context.options.isAlwaysInlinePresent ? VirtualLookup.new(context.global) : null var motionContext = Motion.Context.new for info in context.callGraph.callInfo { var symbol = info.symbol # Turn certain instance functions into global functions if symbol.kind == .FUNCTION_INSTANCE && ( symbol.parent.kind.isEnumOrFlags || symbol.parent.kind == .OBJECT_WRAPPED || symbol.parent.kind == .OBJECT_INTERFACE && symbol.block != null || symbol.parent.isImported && !symbol.isImported || (globalizeAllFunctions || symbol.isInliningForced) && !symbol.isImportedOrExported && !virtualLookup.isVirtual(symbol)) { var function = symbol.asFunctionSymbol function.kind = .FUNCTION_GLOBAL function.arguments.prepend(function.this) function.resolvedType.argumentTypes.prepend(function.this.resolvedType) function.this = null # The globalized function needs instance type parameters if function.parent.asObjectSymbol.parameters != null { function.parent.asObjectSymbol.functions.removeOne(function) motionContext.moveSymbolIntoNewNamespace(function) } # Update all call sites for callSite in info.callSites { var value = callSite.callNode.callValue # Rewrite "super(foo)" to "bar(self, foo)" if value.kind == .SUPER { var this = callSite.enclosingSymbol.asFunctionSymbol.this value.replaceWith(Node.createSymbolReference(this)) } # Rewrite "self.foo(bar)" to "foo(self, bar)" else { value.replaceWith((value.kind == .PARAMETERIZE ? value.parameterizeValue : value).dotTarget.remove) } callSite.callNode.prependChild(Node.createSymbolReference(function)) } } } motionContext.finish } } class VirtualLookup { const _map IntMap = {} def new(global ObjectSymbol) { _visitObject(global) } def isVirtual(symbol FunctionSymbol) bool { return symbol.id in _map } def _visitObject(symbol ObjectSymbol) { for object in symbol.objects { _visitObject(object) } for function in symbol.functions { if function.overridden != null { _map[function.overridden.id] = 0 _map[function.id] = 0 } if symbol.kind == .OBJECT_INTERFACE && function.kind == .FUNCTION_INSTANCE && function.forwardTo == null { if function.implementations != null { for implementation in function.implementations { _map[implementation.id] = 0 } } _map[function.id] = 0 } } } } } ================================================ FILE: src/middle/ide.sk ================================================ namespace Skew.IDE { class SymbolQuery { const source Source const index int var resolvedType Type = null var symbol Symbol = null var range Range = null def generateTooltip string { if symbol == null { return null } var text = "" if symbol.comments != null { for comment in symbol.comments { if !comment.hasGapBelow { for line in comment.lines { text += "#\(line)\n" } } } } switch symbol.kind { case .FUNCTION_ANNOTATION, .FUNCTION_CONSTRUCTOR, .FUNCTION_GLOBAL, .FUNCTION_INSTANCE { var arguments = symbol.asFunctionSymbol.arguments text += "\(symbol.isOver ? "over" : "def") \(symbol.name)\(_parameters(symbol.asFunctionSymbol.parameters))" if resolvedType != null && resolvedType.argumentTypes != null { if !arguments.isEmpty { text += "(" for i in 0..arguments.count { if i != 0 { text += ", " } text += "\(arguments[i].name) \(resolvedType.argumentTypes[i])" } text += ")" } if resolvedType.returnType != null { text += " \(resolvedType.returnType)" } } else { text += " dynamic" } } case .VARIABLE_ARGUMENT, .VARIABLE_ENUM_OR_FLAGS, .VARIABLE_GLOBAL, .VARIABLE_INSTANCE, .VARIABLE_LOCAL { var value = symbol.asVariableSymbol.value text += "\(symbol.isConst ? "const" : "var") \(symbol.name)" text += resolvedType != null ? " \(resolvedType)" : " dynamic" if symbol.isConst && !symbol.isLoopVariable && value != null && value.kind == .CONSTANT { text += " = " switch value.content.kind { case .BOOL { text += value.asBool.toString } case .DOUBLE { text += value.asDouble.toString } case .INT { text += value.asInt.toString } case .STRING { text += quoteString(value.asString, .DOUBLE, .NORMAL) } } } } case .OBJECT_CLASS { text += "class \(symbol.name)\(_parameters(symbol.asObjectSymbol.parameters))" if symbol.asObjectSymbol.baseType != null { text += " : \(symbol.asObjectSymbol.baseType)" } if symbol.asObjectSymbol.interfaceTypes != null { var types = symbol.asObjectSymbol.interfaceTypes for i in 0..types.count { text += "\(i != 0 ? ", " : " :: ")\(types[i])" } } } case .OBJECT_WRAPPED { text += "type \(symbol.name)\(_parameters(symbol.asObjectSymbol.parameters))" if symbol.asObjectSymbol.wrappedType != null { text += " = \(symbol.asObjectSymbol.wrappedType)" } } case .OBJECT_ENUM { text += "enum \(symbol.name)" } case .OBJECT_FLAGS { text += "flags \(symbol.name)" } case .OBJECT_INTERFACE { text += "interface \(symbol.name)\(_parameters(symbol.asObjectSymbol.parameters))" } case .OBJECT_NAMESPACE { text += "namespace \(symbol.name)" } default { text += symbol.name } } return text } def run(global ObjectSymbol) { var findSymbolInSymbol fn(Symbol) bool var findSymbolInObject fn(ObjectSymbol) bool var findSymbolInParameter fn(ParameterSymbol) bool var findSymbolInFunction fn(FunctionSymbol) bool var findSymbolInVariable fn(VariableSymbol) bool var findSymbolInNode fn(Node) bool findSymbolInSymbol = symbol => { while true { if symbol.annotations != null { for node in symbol.annotations { if findSymbolInNode(node) { return true } } } if _findSymbolInRange(symbol.range, symbol) { resolvedType = symbol.resolvedType return true } if symbol.nextMergedSymbol == null { return false } symbol = symbol.nextMergedSymbol } } findSymbolInObject = symbol => { return findSymbolInSymbol(symbol) || symbol.objects.any(findSymbolInObject) || symbol.parameters != null && symbol.parameters.any(findSymbolInParameter) || symbol.functions.any(findSymbolInFunction) || symbol.variables.any(findSymbolInVariable) || findSymbolInNode(symbol.extends) || symbol.implements != null && symbol.implements.any(findSymbolInNode) } findSymbolInParameter = symbol => { return findSymbolInSymbol(symbol) } findSymbolInFunction = symbol => { if symbol.isAutomaticallyGenerated { return false } return findSymbolInSymbol(symbol) || symbol.parameters != null && symbol.parameters.any(findSymbolInParameter) || symbol.arguments.any(findSymbolInVariable) || findSymbolInNode(symbol.returnType) || findSymbolInNode(symbol.block) } findSymbolInVariable = symbol => { return findSymbolInSymbol(symbol) || findSymbolInNode(symbol.value) || findSymbolInNode(symbol.type) } findSymbolInNode = node => { if node != null { var kind = node.kind if kind == .VARIABLE { return findSymbolInVariable(node.symbol.asVariableSymbol) } for child = node.firstChild; child != null; child = child.nextSibling { if findSymbolInNode(child) { return true } } if !node.isIgnoredByIDE { if kind == .NAME && _findSymbolInRange(node.range, node.symbol) { resolvedType = node.resolvedType return true } if (kind == .DOT || kind.isUnary || kind.isBinary) && _findSymbolInRange(node.internalRangeOrRange, node.symbol) { resolvedType = kind == .DOT ? node.resolvedType : node.symbol?.resolvedType return true } } if kind == .LAMBDA { return node.symbol.asFunctionSymbol.arguments.any(findSymbolInVariable) || findSymbolInNode(node.symbol.asFunctionSymbol.returnType) } } return false } findSymbolInObject(global) } def _findSymbolInRange(queryRange Range, querySymbol Symbol) bool { if queryRange != null && queryRange.source == source && queryRange.touches(index) { symbol = querySymbol range = queryRange return true } return false } def _parameters(parameters List) string { var text = "" if parameters != null { text += "<" for i in 0..parameters.count { if i != 0 { text += ", " } text += parameters[i].name } text += ">" } return text } } class SymbolsQuery { const source Source const symbols List = [] def run(symbol ObjectSymbol) { _collectSymbol(symbol) for object in symbol.objects { run(object) } for function in symbol.functions { _collectSymbol(function) } for variable in symbol.variables { _collectSymbol(variable) } } def _collectSymbol(symbol Symbol) { if symbol.range != null && (source == null || symbol.range.source == source) && !symbol.isAutomaticallyGenerated { symbols.append(symbol) } } } class RenameQuery { const source Source const index int const ranges List = [] const _targets IntMap = {} var _targetName string = null def run(global ObjectSymbol) { var query = SymbolQuery.new(source, index) query.run(global) var symbol = query.symbol if symbol != null { _targetName = symbol.name _targets[symbol.id] = 0 # Make sure interfaces and overridden methods are all renamed together if symbol.kind.isFunction { _includeRelatedFunctions(global, symbol.asFunctionSymbol) } _visitObject(global) # Remove overlapping ranges since they are sometimes generated as a side effect of lowering var current Range = null ranges.sort((a, b) => a.source == b.source ? a.start <=> b.start : a.source.name <=> b.source.name) ranges.removeIf(range => { var previous = current current = range return previous != null && current.overlaps(previous) }) } } def _includeRelatedFunctions(global ObjectSymbol, symbol FunctionSymbol) { var functions List = [] var wasFound = false # Gather all non-local functions _searchForFunctions(global, functions) var labels = UnionFind.new.allocate(functions.count) # Assign each one an index for i in 0..functions.count { var function = functions[i] function.namingGroup = i if function == symbol { wasFound = true } } # Lambda functions won't be found if wasFound { # Merge related functions together (this is the O(n log n) way to find all related functions) for function in functions { if function.overridden != null { labels.union(function.namingGroup, function.overridden.namingGroup) } if function.implementations != null { for implementation in function.implementations { labels.union(function.namingGroup, implementation.namingGroup) } } } # Extract the relevant functions var label = labels.find(symbol.namingGroup) for function in functions { if labels.find(function.namingGroup) == label { _targets[function.id] = 0 } } } } def _appendRange(range Range) { # Sanity check the range to make sure it contains the target name if range != null && range.toString == _targetName { ranges.append(range) } } def _visitSymbol(symbol Symbol) { var isTarget = symbol.id in _targets while symbol != null { if symbol.annotations != null { for node in symbol.annotations { _visitNode(node) } } if isTarget && symbol.range != null { _appendRange(symbol.range) } symbol = symbol.nextMergedSymbol } } def _visitObject(symbol ObjectSymbol) { _visitSymbol(symbol) _visitParameters(symbol.parameters) _visitNode(symbol.extends) for object in symbol.objects { _visitObject(object) } for function in symbol.functions { _visitFunction(function) } for variable in symbol.variables { _visitVariable(variable) } if symbol.implements != null { for node in symbol.implements { _visitNode(node) } } } def _visitParameters(parameters List) { if parameters != null { for parameter in parameters { _visitSymbol(parameter) } } } def _visitFunction(symbol FunctionSymbol) { _visitSymbol(symbol) _visitParameters(symbol.parameters) _visitNode(symbol.returnType) _visitNode(symbol.block) for argument in symbol.arguments { _visitVariable(argument) } } def _visitVariable(symbol VariableSymbol) { _visitSymbol(symbol) _visitNode(symbol.type) _visitNode(symbol.value) } def _visitNode(node Node) { if node != null { var kind = node.kind if kind == .VARIABLE { _visitVariable(node.symbol.asVariableSymbol) return } for child = node.firstChild; child != null; child = child.nextSibling { _visitNode(child) } if kind == .LAMBDA { for argument in node.symbol.asFunctionSymbol.arguments { _visitVariable(argument) } _visitNode(node.symbol.asFunctionSymbol.returnType) } else if node.symbol != null && node.symbol.id in _targets && !node.isIgnoredByIDE { if kind == .NAME { _appendRange(node.range) } else if kind == .DOT { _appendRange(node.internalRange) } } } } } namespace RenameQuery { def _searchForFunctions(symbol ObjectSymbol, functions List) { for object in symbol.objects { _searchForFunctions(object, functions) } for function in symbol.functions { functions.append(function) } } } def completionType(symbol Symbol) string { if symbol.kind.isFunction { var text = "(" var function = symbol.asFunctionSymbol for argument in function.arguments { if text != "(" { text += ", " } text += "\(argument.name) \(argument.resolvedType)" } text += ")" if function.resolvedType.returnType != null { text += " \(function.resolvedType.returnType)" } return text } if symbol.kind.isVariable { return symbol.resolvedType.toString } switch symbol.kind { case .OBJECT_CLASS { return "class" } case .OBJECT_ENUM { return "enum" } case .OBJECT_FLAGS { return "flags" } case .OBJECT_INTERFACE { return "interface" } case .OBJECT_NAMESPACE { return "namespace" } case .OBJECT_WRAPPED { return "type" } } return null } class SignatureQuery { const source Source const index int var signature FunctionSymbol = null var argumentIndex = -1 def signatureString string { if signature == null { return null } var text = "\(signature.isOver ? "over" : "def") \(signature.name)(" for i in 0..signature.arguments.count { var argument = signature.arguments[i] if i != 0 { text += ", " } text += "\(argument.name) \(argument.resolvedType)" } text += ")" if signature.resolvedType.returnType != null { text += " \(signature.resolvedType.returnType)" } return text } def argumentStrings List { var list List = [] for argument in signature.arguments { list.append("\(argument.name) \(argument.resolvedType)") } return list } def run(global ObjectSymbol) { var findSignatureInSymbol fn(Symbol) bool var findSignatureInObject fn(ObjectSymbol) bool var findSignatureInFunction fn(FunctionSymbol) bool var findSignatureInVariable fn(VariableSymbol) bool var findSignatureInNode fn(Node) bool findSignatureInSymbol = symbol => { while true { if symbol.annotations != null { for node in symbol.annotations { if findSignatureInNode(node) { return true } } } if symbol.nextMergedSymbol == null { return false } symbol = symbol.nextMergedSymbol } } findSignatureInObject = symbol => { return findSignatureInSymbol(symbol) || symbol.objects.any(findSignatureInObject) || symbol.functions.any(findSignatureInFunction) || symbol.variables.any(findSignatureInVariable) || findSignatureInNode(symbol.extends) || symbol.implements != null && symbol.implements.any(findSignatureInNode) } findSignatureInFunction = symbol => { if symbol.isAutomaticallyGenerated { return false } return findSignatureInSymbol(symbol) || symbol.arguments.any(findSignatureInVariable) || findSignatureInNode(symbol.block) } findSignatureInVariable = symbol => { return findSignatureInSymbol(symbol) || findSignatureInNode(symbol.value) } findSignatureInNode = node => { if node != null { var kind = node.kind if kind == .VARIABLE { return findSignatureInVariable(node.symbol.asVariableSymbol) } for child = node.firstChild; child != null; child = child.nextSibling { if findSignatureInNode(child) { return true } } if kind == .CALL && node.symbol != null && node.internalRange != null && node.internalRange.source == source && node.internalRange.touches(index) { signature = node.symbol.asFunctionSymbol # Find the argument containing the query location, if any if !signature.arguments.isEmpty { argumentIndex = 0 var limit = signature.arguments.count for child = node.callValue.nextSibling; child != null; child = child.nextSibling { if child.range != null && index <= child.range.end || argumentIndex + 1 >= limit { break } argumentIndex++ } } return true } } return false } findSignatureInObject(global) } } } ================================================ FILE: src/middle/inlining.sk ================================================ namespace Skew { enum PassKind { INLINING } class InliningPass : Pass { over kind PassKind { return .INLINING } over run(context PassContext) { var graph = Inlining.InliningGraph.new(context.callGraph, context.log, context.options.inlineAllFunctions) for info in graph.inliningInfo { Inlining.inlineSymbol(graph, info) } } } } namespace Skew.Inlining { def inlineSymbol(graph InliningGraph, info InliningInfo) { if !info.shouldInline { return } # Inlining nested functions first is more efficient because it results in # fewer inlining operations. This won't enter an infinite loop because # inlining for all such functions has already been disabled. for bodyCall in info.bodyCalls { inlineSymbol(graph, bodyCall) } var spreadingAnnotations = info.symbol.spreadingAnnotations for i in 0..info.callSites.count { var callSite = info.callSites[i] # Some calls may be reused for other node types during constant folding if callSite == null || callSite.callNode.kind != .CALL { continue } # Make sure the call site hasn't been tampered with. An example of where # this can happen is constant folding "false ? 0 : foo.foo" to "foo.foo". # The children of "foo.foo" are stolen and parented under the hook # expression as part of a become() call. Skipping inlining in this case # just means we lose out on those inlining opportunities. This isn't the # end of the world and is a pretty rare occurrence. var node = callSite.callNode if node.childCount != info.symbol.arguments.count + 1 { continue } # Propagate spreading annotations that must be preserved through inlining if spreadingAnnotations != null { var annotations = callSite.enclosingSymbol.annotations if annotations == null { annotations = [] callSite.enclosingSymbol.annotations = annotations } for annotation in spreadingAnnotations { annotations.appendOne(annotation) } } # Make sure each call site is inlined once by setting the call site to # null. The call site isn't removed from the list since we don't want # to mess up the indices of another call to inlineSymbol further up # the call stack. info.callSites[i] = null # If there are unused arguments, drop those expressions entirely if # they don't have side effects: # # def bar(a int, b int) int { # return a # } # # def test int { # return bar(0, foo(0)) + bar(1, 2) # } # # This should compile to: # # def test int { # return bar(0, foo(0)) + 2 # } # if !info.unusedArguments.isEmpty { var hasSideEffects = false for child = node.callValue.nextSibling; child != null; child = child.nextSibling { if !child.hasNoSideEffects { hasSideEffects = true break } } if hasSideEffects { continue } } info.symbol.inlinedCount++ var clone = info.inlineValue.clone var value = node.firstChild.remove var values List = [] while node.hasChildren { assert(node.firstChild.resolvedType != null) values.append(node.firstChild.remove) } # Make sure not to update the type if the function dynamic because the # expression inside the function may have a more specific type that is # necessary during code generation if node.resolvedType != .DYNAMIC { clone.resolvedType = node.resolvedType } assert((value.kind == .PARAMETERIZE ? value.parameterizeValue : value).kind == .NAME && value.symbol == info.symbol) assert(clone.resolvedType != null) node.become(clone) recursivelySubstituteArguments(node, node, info.symbol.arguments, values) # Remove the inlined result entirely if appropriate var parent = node.parent if parent != null && parent.kind == .EXPRESSION && node.hasNoSideEffects { parent.remove } } } def recursivelyInlineFunctionCalls(graph InliningGraph, node Node) { # Recursively inline child nodes first for child = node.firstChild; child != null; child = child.nextSibling { recursivelyInlineFunctionCalls(graph, child) } # Inline calls after all children have been processed if node.kind == .CALL { var symbol = node.callValue.symbol if symbol != null { var index = graph.symbolToInfoIndex.get(symbol.id, -1) if index != -1 { inlineSymbol(graph, graph.inliningInfo[index]) } } } } def recursivelySubstituteArguments(root Node, node Node, arguments List, values List) { # Substitute the argument if this is an argument name var symbol = node.symbol if symbol != null && symbol.kind.isVariable { var index = arguments.indexOf(symbol.asVariableSymbol) if index != -1 { var parent = node.parent # If we're in a wrapped type cast, replace the cast itself if parent.kind == .CAST { var valueType = parent.castValue.resolvedType var targetType = parent.castType.resolvedType if valueType.isWrapped && targetType.symbol != null && valueType.symbol.asObjectSymbol.wrappedType.symbol == targetType.symbol { node = parent } } if node == root { node.become(values[index]) } else { node.replaceWith(values[index]) } return } } # Otherwise, recursively search for substitutions in all child nodes for child = node.firstChild, next Node = null; child != null; child = next { next = child.nextSibling recursivelySubstituteArguments(root, child, arguments, values) } } class InliningInfo { var symbol FunctionSymbol var inlineValue Node var callSites List var unusedArguments List var shouldInline = true var bodyCalls List = [] } # Each node in the inlining graph is a symbol of an inlineable function and # each directional edge is from a first function to a second function that is # called directly within the body of the first function. Indirect function # calls that may become direct calls through inlining can be discovered by # traversing edges of this graph. class InliningGraph { var inliningInfo List = [] var symbolToInfoIndex IntMap = {} def new(graph CallGraph, log Log, inlineAllFunctions bool) { # Create the nodes in the graph for callInfo in graph.callInfo { var symbol = callInfo.symbol if symbol.isInliningPrevented { continue } var info = _createInliningInfo(callInfo) if info != null { if inlineAllFunctions || symbol.isInliningForced { symbolToInfoIndex[symbol.id] = inliningInfo.count inliningInfo.append(info) } } else if symbol.isInliningForced { log.semanticWarningInliningFailed(symbol.range, symbol.name) } } # Create the edges in the graph for info in inliningInfo { for callSite in graph.callInfoForSymbol(info.symbol).callSites { if callSite.enclosingSymbol.kind == .FUNCTION_GLOBAL { var index = symbolToInfoIndex.get(callSite.enclosingSymbol.id, -1) if index != -1 { inliningInfo[index].bodyCalls.append(info) } } } } # Detect and disable infinitely expanding inline operations for info in inliningInfo { info.shouldInline = !_containsInfiniteExpansion(info, []) } } } namespace InliningGraph { def _containsInfiniteExpansion(info InliningInfo, symbols List) bool { # This shouldn't get very long in normal programs so O(n) here is fine if info.symbol in symbols { return true } # Do a depth-first search on the graph and check for cycles symbols.append(info.symbol) for bodyCall in info.bodyCalls { if _containsInfiniteExpansion(bodyCall, symbols) { return true } } symbols.removeLast return false } def _createInliningInfo(info CallInfo) InliningInfo { var symbol = info.symbol # Inline functions consisting of a single return statement if symbol.kind == .FUNCTION_GLOBAL { var block = symbol.block if block == null { return null } # Replace functions with empty bodies with null if !block.hasChildren { var unusedArguments List = [] for i in 0..symbol.arguments.count { unusedArguments.append(i) } return InliningInfo.new(symbol, Node.createNull.withType(.NULL), info.callSites, unusedArguments) } var first = block.firstChild var inlineValue Node = null # If the first value in the function is a return statement, then the # function body doesn't need to only have one statement. Subsequent # statements are just dead code and will never be executed anyway. if first.kind == .RETURN { inlineValue = first.returnValue } # Otherwise, this statement must be a lone expression statement else if first.kind == .EXPRESSION && first.nextSibling == null { inlineValue = first.expressionValue } if inlineValue != null { # Count the number of times each symbol is observed. Argument # variables that are used more than once may need a let statement # to avoid changing the semantics of the call site. For now, just # only inline functions where each argument is used exactly once. var argumentCounts IntMap = {} for argument in symbol.arguments { argumentCounts[argument.id] = 0 } if _recursivelyCountArgumentUses(inlineValue, argumentCounts) { var unusedArguments List = [] var isSimpleSubstitution = true for i in 0..symbol.arguments.count { var count = argumentCounts[symbol.arguments[i].id] if count == 0 { unusedArguments.append(i) } else if count != 1 { isSimpleSubstitution = false break } } if isSimpleSubstitution { return InliningInfo.new(symbol, inlineValue, info.callSites, unusedArguments) } } } } return null } # This returns false if inlining is impossible def _recursivelyCountArgumentUses(node Node, argumentCounts IntMap) bool { # Prevent inlining of lambda expressions. They have their own function # symbols that reference the original block and won't work with cloning. # Plus inlining lambdas leads to code bloat. if node.kind == .LAMBDA { return false } # Inlining is impossible at this node if it's impossible for any child node for child = node.firstChild; child != null; child = child.nextSibling { if !_recursivelyCountArgumentUses(child, argumentCounts) { return false } } var symbol = node.symbol if symbol != null { var count = argumentCounts.get(symbol.id, -1) if count != -1 { argumentCounts[symbol.id] = count + 1 # Prevent inlining of functions that modify their arguments locally. For # example, inlining this would lead to incorrect code: # # def foo(x int, y int) { # x += y # } # # def test { # foo(1, 2) # } # if node.isAssignTarget { return false } } } return true } } } ================================================ FILE: src/middle/interfaceremoval.sk ================================================ namespace Skew { enum PassKind { INTERFACE_REMOVAL } class InterfaceRemovalPass : Pass { const _interfaceImplementations IntMap> = {} const _interfaces List = [] over kind PassKind { return .INTERFACE_REMOVAL } over run(context PassContext) { _scanForInterfaces(context.global) for symbol in _interfaces { if symbol.isImportedOrExported { continue } var implementations = _interfaceImplementations.get(symbol.id, null) if implementations == null || implementations.count == 1 { symbol.kind = .OBJECT_NAMESPACE # Remove this interface from its implementation if implementations != null { var object = implementations.first for type in object.interfaceTypes { if type.symbol == symbol { object.interfaceTypes.removeOne(type) break } } # Mark these symbols as forwarded, which is used by the globalization # pass and the JavaScript emitter to ignore this interface for function in symbol.functions { if function.implementations != null { function.forwardTo = function.implementations.first } } symbol.forwardTo = object } } } } def _scanForInterfaces(symbol ObjectSymbol) { for object in symbol.objects { _scanForInterfaces(object) } if symbol.kind == .OBJECT_INTERFACE { _interfaces.append(symbol) } if symbol.interfaceTypes != null { for type in symbol.interfaceTypes { var key = type.symbol.id var implementations = _interfaceImplementations.get(key, null) if implementations == null { implementations = [] _interfaceImplementations[key] = implementations } implementations.append(symbol) } } } } } ================================================ FILE: src/middle/lambdaconversion.sk ================================================ namespace Skew { enum PassKind { LAMBDA_CONVERSION } class LambdaConversionPass : Pass { over kind PassKind { return .LAMBDA_CONVERSION } over run(context PassContext) { Skew.LambdaConversion.Converter.new(context.global, context.cache).run } } } namespace Skew.LambdaConversion { enum CaptureKind { FUNCTION LAMBDA LOOP } class Definition { const symbol VariableSymbol const node Node const scope Scope var isCaptured = false # If this symbol is captured, this contains the member variable on the # environment object where this symbol's storage is moved to var member VariableSymbol = null } class Use { const definition Definition const node Node } class Copy { const scope Scope # This contains the member variable on the environment containing this # copy where a copy of the corresponding scope is stored var member VariableSymbol = null } class Scope { const id = ++_nextID const kind CaptureKind const node Node const enclosingFunction FunctionSymbol var parent Scope var hasCapturedDefinitions = false var hasCapturingUses = false var environmentObject ObjectSymbol = null var environmentVariable VariableSymbol = null var environmentConstructor FunctionSymbol = null var environmentConstructorCall Node = null const definitions List = [] const uses List = [] const copies List = [] const definitionLookup IntMap = {} const copyLookup IntMap = {} def recordDefinition(symbol VariableSymbol, node Node) { assert(!(symbol.id in definitionLookup)) var definition = Definition.new(symbol, node, self) definitions.append(definition) definitionLookup[symbol.id] = definition } def recordUse(symbol VariableSymbol, node Node) { var isCaptured = false # Walk up the scope chain for scope = self; scope != null; scope = scope.parent { var definition = scope.definitionLookup.get(symbol.id, null) # Stop once the definition is found if definition != null { uses.append(Use.new(definition, node)) if isCaptured { definition.isCaptured = true scope.hasCapturedDefinitions = true hasCapturingUses = true } break } # Variables are captured if a lambda is in the scope chain if scope.kind == .LAMBDA { isCaptured = true } } } def createReferenceToScope(scope Scope) Node { # Skip to the enclosing scope with an environment var target = self while target.environmentObject == null { assert(!target.hasCapturedDefinitions && target.kind != .LAMBDA) target = target.parent } # Reference this scope if scope == target { return Node.createSymbolReference(target.environmentVariable) } # Reference a parent scope var copy = target.copyLookup[scope.id] return Node.createMemberReference( Node.createSymbolReference(target.environmentVariable), copy.member) } } namespace Scope { var _nextID = 0 } class Converter { const _global ObjectSymbol const _cache TypeCache const _scopes List = [] const _stack List = [] const _interfaces IntMap = {} const _calls List = [] var _skewNamespace ObjectSymbol = null var _enclosingFunction FunctionSymbol = null def run { _visitObject(_global) _convertCalls _convertLambdas } def _convertCalls { var swap = Node.createNull for node in _calls { var value = node.callValue var resolvedType = value.resolvedType if resolvedType.kind == .LAMBDA { var interfaceType = _interfaceTypeForLambdaType(resolvedType) var interfaceRun = interfaceType.symbol.asObjectSymbol.functions.first assert(interfaceRun.name == "run") value.replaceWith(swap) swap.replaceWith(Node.createMemberReference(value, interfaceRun)) } } } def _convertLambdas { # Propagate required environment copies up the scope chain for scope in _scopes { if scope.hasCapturingUses { for use in scope.uses { if use.definition.isCaptured { var definingScope = use.definition.scope for s = scope; s != definingScope; s = s.parent { if !(definingScope.id in s.copyLookup) { var copy = Copy.new(definingScope) s.copies.append(copy) s.copyLookup[definingScope.id] = copy } } } } } } for scope in _scopes { if scope.hasCapturedDefinitions || scope.kind == .LAMBDA { # Create an object to store the environment var object = _createObject(.OBJECT_CLASS, _generateEnvironmentName(scope), _global) var constructor = _createConstructor(object) var constructorCall = Node.createCall(Node.createMemberReference(Node.createSymbolReference(object), constructor)).withType(object.resolvedType) # The environment must store all captured variables for definition in scope.definitions { if definition.isCaptured { definition.member = _createInstanceVariable(object.scope.generateName(definition.symbol.name), definition.symbol.resolvedType, object) } } # Insert the constructor call declaration switch scope.kind { case .FUNCTION { # Store the environment instance in a variable var variable = _createVariable(.VARIABLE_LOCAL, scope.enclosingFunction.scope.generateName("env"), object.resolvedType) variable.value = constructorCall scope.environmentVariable = variable # Define the variable at the top of the function body var variables = Node.createVariables.appendChild(Node.createVariable(variable)) scope.node.prependChild(variables) # TODO: Insert this after the call to "super" # Assign captured arguments and "self" to the environment # TODO: Remove the extra indirection to "self", copy it directly into environments instead var previous = variables for definition in scope.definitions { if definition.isCaptured && (definition.symbol.kind == .VARIABLE_ARGUMENT || definition.symbol == scope.enclosingFunction.this) { var assignment = _createAssignment(variable, definition.member, definition.symbol) scope.node.insertChildAfter(previous, assignment) previous = assignment } } } case .LAMBDA { var function = scope.node.symbol.asFunctionSymbol function.kind = .FUNCTION_INSTANCE function.name = "run" function.this = _createVariable(.VARIABLE_LOCAL, "self", object.resolvedType) function.parent = object object.functions.append(function) scope.node.become(constructorCall) scope.environmentVariable = function.this constructorCall = scope.node # Lambdas introduce two scopes instead of one. All captured # definitions for that lambda have to be in the nested scope, # even lambda function arguments, because that nested scope # needs to be different for each invocation of the lambda. assert(!scope.hasCapturedDefinitions) # Implement the lambda interface with the right type parameters var interfaceType = _interfaceTypeForLambdaType(function.resolvedType) var interfaceFunction = interfaceType.symbol.asObjectSymbol.functions.first assert(interfaceFunction.name == "run") object.implements = [Node.createType(interfaceType)] object.interfaceTypes = [interfaceType] interfaceFunction.implementations ?= [] interfaceFunction.implementations.append(function) } case .LOOP { # Store the environment instance in a variable var variable = _createVariable(.VARIABLE_LOCAL, scope.enclosingFunction.scope.generateName("env"), object.resolvedType) variable.value = constructorCall scope.environmentVariable = variable # Define the variable at the top of the function body var variables = Node.createVariables.appendChild(Node.createVariable(variable)) var node = scope.node var block = node.kind == .FOR ? node.forBlock : node.kind == .FOREACH ? node.foreachBlock : node.kind == .WHILE ? node.whileBlock : null block.prependChild(variables) # Assign captured loop variables var previous = variables for definition in scope.definitions { if definition.isCaptured && definition.symbol.isLoopVariable { var assignment = _createAssignment(variable, definition.member, definition.symbol) block.insertChildAfter(previous, assignment) previous = assignment } } } default { assert(false) } } # These will be referenced later scope.environmentObject = object scope.environmentConstructor = constructor scope.environmentConstructorCall = constructorCall } # Mutate the parent scope pointer to skip past irrelevant scopes # (those without environments). This means everything necessary to # access captured symbols can be found on the environment associated # with the parent scope without needing to look at grandparent scopes. # # All parent scopes that need environments should already have them # because scopes are iterated over using a pre-order traversal. while scope.parent != null && scope.parent.environmentObject == null { assert(!scope.parent.hasCapturedDefinitions && scope.parent.kind != .LAMBDA) scope.parent = scope.parent.parent } } # Make sure each environment has a copy of each parent environment that it or its children needs for scope in _scopes { var object = scope.environmentObject var constructor = scope.environmentConstructor var constructorCall = scope.environmentConstructorCall if object != null { for copy in scope.copies { var name = object.scope.generateName(copy.scope.kind == .LAMBDA ? "lambda" : "env") var member = _createInstanceVariable(name, copy.scope.environmentObject.resolvedType, object) var argument = _createVariable(.VARIABLE_ARGUMENT, name, member.resolvedType) copy.member = member constructor.arguments.append(argument) constructor.resolvedType.argumentTypes.append(argument.resolvedType) constructor.block.appendChild(_createAssignment(constructor.this, member, argument)) constructorCall.appendChild(scope.parent.createReferenceToScope(copy.scope)) } } } for scope in _scopes { # Replace variable definitions of captured symbols with assignments to their environment if scope.hasCapturedDefinitions { for definition in scope.definitions { if definition.isCaptured && definition.node != null { assert(definition.node.kind == .VARIABLE) assert(definition.node.parent.kind == .VARIABLES) definition.node.extractVariableFromVariables definition.node.parent.replaceWith(Node.createExpression(Node.createBinary(.ASSIGN, Node.createMemberReference(Node.createSymbolReference(scope.environmentVariable), definition.member), definition.symbol.value.remove).withType(definition.member.resolvedType))) } } } # Replace all references to captured variables with a member access from the appropriate environment for use in scope.uses { if use.definition.isCaptured { use.node.become(Node.createMemberReference( scope.createReferenceToScope(use.definition.scope), use.definition.member)) } } } } def _visitObject(symbol ObjectSymbol) { for object in symbol.objects { _visitObject(object) } for function in symbol.functions { _visitFunction(function) } for variable in symbol.variables { _visitVariable(variable) } } def _visitFunction(symbol FunctionSymbol) { if symbol.block != null { _enclosingFunction = symbol var scope = _pushScope(.FUNCTION, symbol.block, null) if symbol.this != null { scope.recordDefinition(symbol.this, null) } for argument in symbol.arguments { scope.recordDefinition(argument, null) } _visit(symbol.block) _stack.removeLast _enclosingFunction = null } } def _visitVariable(symbol VariableSymbol) { if symbol.value != null { _visit(symbol.value) } } def _visit(node Node) { var kind = node.kind var symbol = node.symbol var oldEnclosingFunction = _enclosingFunction if kind == .LAMBDA { _enclosingFunction = symbol.asFunctionSymbol var lambdaScope = _pushScope(.LAMBDA, node, _stack.isEmpty ? null : _stack.last) var scope = _pushScope(.FUNCTION, node.lambdaBlock, lambdaScope) for argument in symbol.asFunctionSymbol.arguments { scope.recordDefinition(argument, null) } } else if kind == .FOREACH { # Visit loop header _visit(node.foreachValue) # Visit loop body var scope = _pushScope(.LOOP, node, _stack.last) scope.recordDefinition(symbol.asVariableSymbol, null) _visit(node.foreachBlock) _stack.removeLast return } else if kind == .FOR || kind == .WHILE { _pushScope(.LOOP, node, _stack.last) } else if kind == .VARIABLE { _stack.last.recordDefinition(symbol.asVariableSymbol, node) } else if kind == .CATCH { # TODO } else if kind == .CALL { _calls.append(node) } else if kind == .NAME && symbol != null && (symbol.kind == .VARIABLE_ARGUMENT || symbol.kind == .VARIABLE_LOCAL) { _stack.last.recordUse(symbol.asVariableSymbol, node) } for child = node.firstChild; child != null; child = child.nextSibling { _visit(child) } if kind == .LAMBDA { _stack.removeLast _stack.removeLast _enclosingFunction = oldEnclosingFunction } else if kind.isLoop { _stack.removeLast } } def _pushScope(kind CaptureKind, node Node, parent Scope) Scope { var scope = Scope.new(kind, node, _enclosingFunction, parent) _scopes.append(scope) _stack.append(scope) return scope } def _createObject(kind SymbolKind, name string, parent ObjectSymbol) ObjectSymbol { var object = ObjectSymbol.new(kind, parent.scope.generateName(name)) object.scope = ObjectScope.new(parent.scope, object) object.resolvedType = Type.new(.SYMBOL, object) object.state = .INITIALIZED object.parent = parent parent.objects.append(object) return object } def _ensureSkewNamespaceExists { if _skewNamespace == null { var symbol = _global.scope.find("Skew", .NORMAL) # Did the user's code define the namespace? if symbol != null && symbol.kind.isObject { _skewNamespace = symbol.asObjectSymbol } # It's missing or there's a conflict, define one ourselves else { _skewNamespace = _createObject(.OBJECT_NAMESPACE, "Skew", _global) _skewNamespace.flags |= .IS_IMPORTED } } } def _createInterface(count int, hasReturnType bool) ObjectSymbol { var key = count << 1 | hasReturnType as int var object = _interfaces.get(key, null) if object == null { _ensureSkewNamespaceExists object = _createObject(.OBJECT_INTERFACE, (hasReturnType ? "Fn" : "FnVoid") + count.toString, _skewNamespace) object.flags |= .IS_IMPORTED _interfaces[key] = object var function = _createFunction(object, .FUNCTION_INSTANCE, "run", .ABSTRACT) function.flags |= .IS_IMPORTED function.resolvedType.argumentTypes = [] if hasReturnType { var returnType = _createParameter(object, "R").resolvedType function.resolvedType.returnType = returnType function.returnType = Node.createType(returnType) } for i in 0..count { var parameter = _createParameter(object, "A\(i + 1)") function.arguments.append(_createVariable(.VARIABLE_ARGUMENT, "a\(i + 1)", parameter.resolvedType)) function.resolvedType.argumentTypes.append(parameter.resolvedType) } } return object } def _interfaceTypeForLambdaType(lambdaType Type) Type { var interface = _createInterface(lambdaType.argumentTypes.count, lambdaType.returnType != null) var interfaceType = interface.resolvedType var substitutions List = [] if lambdaType.returnType != null { substitutions.append(lambdaType.returnType) } substitutions.append(lambdaType.argumentTypes) if !substitutions.isEmpty { interfaceType = _cache.substitute(interfaceType, _cache.createEnvironment(interface.parameters, substitutions)) } return interfaceType } } namespace Converter { def _generateEnvironmentName(scope Scope) string { var name = "" var root = scope while root.parent != null { root = root.parent } for symbol Symbol = root.enclosingFunction; symbol != null && symbol.kind != .OBJECT_GLOBAL; symbol = symbol.parent { if symbol.kind != .OBJECT_GLOBAL && !Renaming.isInvalidIdentifier(symbol.name) { name = withUppercaseFirstLetter(symbol.name) + name } } name += scope.kind == .LAMBDA ? "Lambda" : "Env" return name } def _createConstructor(object ObjectSymbol) FunctionSymbol { var function = _createFunction(object, .FUNCTION_CONSTRUCTOR, "new", .IMPLEMENTED) function.resolvedType.returnType = object.resolvedType function.returnType = Node.createType(object.resolvedType) return function } enum Body { ABSTRACT IMPLEMENTED } def _createFunction(object ObjectSymbol, kind SymbolKind, name string, body Body) FunctionSymbol { var function = FunctionSymbol.new(kind, name) function.scope = FunctionScope.new(object.scope, function) function.resolvedType = Type.new(.SYMBOL, function) function.resolvedType.argumentTypes = [] function.state = .INITIALIZED function.parent = object if body == .IMPLEMENTED { function.block = Node.createBlock function.this = _createVariable(.VARIABLE_LOCAL, "self", object.resolvedType) } object.functions.append(function) return function } def _createInstanceVariable(name string, type Type, object ObjectSymbol) VariableSymbol { var variable = _createVariable(.VARIABLE_INSTANCE, name, type) variable.parent = object object.variables.append(variable) return variable } def _createVariable(kind SymbolKind, name string, type Type) VariableSymbol { var variable = VariableSymbol.new(kind, name) variable.initializeWithType(type) return variable } def _createParameter(parent ObjectSymbol, name string) ParameterSymbol { var parameter = ParameterSymbol.new(.PARAMETER_OBJECT, name) parameter.resolvedType = Type.new(.SYMBOL, parameter) parameter.state = .INITIALIZED parent.parameters ?= [] parent.parameters.append(parameter) return parameter } def _createAssignment(object VariableSymbol, member VariableSymbol, variable VariableSymbol) Node { return Node.createExpression(Node.createBinary(.ASSIGN, Node.createMemberReference(Node.createSymbolReference(object), member), Node.createSymbolReference(variable)).withType(member.resolvedType)) } } } ================================================ FILE: src/middle/library.sk ================================================ namespace Skew { const NATIVE_LIBRARY = " const RELEASE = false const ASSERTS = !RELEASE enum Target { NONE CPLUSPLUS CSHARP JAVASCRIPT } const TARGET Target = .NONE def @alwaysinline def @deprecated def @deprecated(message string) def @entry def @export def @import def @neverinline def @prefer def @rename(name string) def @skip def @spreads @spreads { def @using(name string) # For use with C# def @include(name string) # For use with C++ } @import if TARGET == .NONE @skip if !ASSERTS def assert(truth bool) @import if TARGET == .NONE namespace Math { @prefer def abs(x double) double def abs(x int) int def acos(x double) double def asin(x double) double def atan(x double) double def atan2(x double, y double) double def sin(x double) double def cos(x double) double def tan(x double) double def floor(x double) double def ceil(x double) double def round(x double) double def exp(x double) double def log(x double) double def pow(x double, y double) double def random double def randomInRange(min double, max double) double def randomInRange(minInclusive int, maxExclusive int) int def sqrt(x double) double @prefer { def max(x double, y double) double def min(x double, y double) double def max(x double, y double, z double) double def min(x double, y double, z double) double def max(x double, y double, z double, w double) double def min(x double, y double, z double, w double) double def clamp(x double, min double, max double) double } def max(x int, y int) int def min(x int, y int) int def max(x int, y int, z int) int def min(x int, y int, z int) int def max(x int, y int, z int, w int) int def min(x int, y int, z int, w int) int def clamp(x int, min int, max int) int def E double { return 2.718281828459045 } def INFINITY double { return 1 / 0.0 } def NAN double { return 0 / 0.0 } def PI double { return 3.141592653589793 } def SQRT_2 double { return 2 ** 0.5 } } @import class bool { def ! bool def toString string } @import class int { def + int def - int def ~ int def +(x int) int def -(x int) int def *(x int) int def /(x int) int def %(x int) int def **(x int) int def <=>(x int) int def <<(x int) int def >>(x int) int def >>>(x int) int def &(x int) int def |(x int) int def ^(x int) int def %%(x int) int { return ((self % x) + x) % x } def <<=(x int) int def >>=(x int) int def &=(x int) int def |=(x int) int def ^=(x int) int if TARGET != .CSHARP && TARGET != .CPLUSPLUS { def >>>=(x int) } if TARGET != .JAVASCRIPT { def ++ int def -- int def %=(x int) int def +=(x int) int def -=(x int) int def *=(x int) int def /=(x int) int } def toString string } namespace int { def MIN int { return -0x7FFFFFFF - 1 } def MAX int { return 0x7FFFFFFF } } @import class double { def + double def ++ double def - double def -- double def *(x double) double def +(x double) double def -(x double) double def /(x double) double def **(x double) double def <=>(x double) int def %%(x double) double { return self - Math.floor(self / x) * x } def *=(x double) double def +=(x double) double def -=(x double) double def /=(x double) double def isFinite bool def isNaN bool def toString string } @import class string { def +(x string) string def +=(x string) string def <=>(x string) int def count int def in(x string) bool def indexOf(x string) int def lastIndexOf(x string) int def startsWith(x string) bool def endsWith(x string) bool def [](x int) int def get(x int) string def slice(start int) string def slice(start int, end int) string def codePoints List def codeUnits List def split(x string) List def join(x List) string def repeat(x int) string def replaceAll(before string, after string) string def toLowerCase string def toUpperCase string def toString string { return self } } namespace string { def fromCodePoint(x int) string def fromCodePoints(x List) string def fromCodeUnit(x int) string def fromCodeUnits(x List) string } @import if TARGET == .NONE class StringBuilder { def new def append(x string) def toString string } @import class List { def new def [...](x T) List def [](x int) T def []=(x int, y T) T def count int def isEmpty bool def resize(count int, defaultValue T) @prefer def append(x T) def append(x List) def appendOne(x T) @prefer def prepend(x T) def prepend(x List) @prefer def insert(x int, value T) def insert(x int, values List) def removeAll(x T) def removeAt(x int) def removeDuplicates def removeFirst def removeIf(x fn(T) bool) def removeLast def removeOne(x T) def removeRange(start int, end int) def takeFirst T def takeLast T def takeAt(x int) T def takeRange(start int, end int) List def first T def first=(x T) T { return self[0] = x } def last T def last=(x T) T { return self[count - 1] = x } def in(x T) bool def indexOf(x T) int def lastIndexOf(x T) int def all(x fn(T) bool) bool def any(x fn(T) bool) bool def clone List def each(x fn(T)) def equals(x List) bool def filter(x fn(T) bool) List def map(x fn(T) R) List def reverse def shuffle def slice(start int) List def slice(start int, end int) List def sort(x fn(T, T) int) def swap(x int, y int) } @import class StringMap { def new def {...}(key string, value T) StringMap def [](key string) T def []=(key string, value T) T def count int def isEmpty bool def keys List def values List def clone StringMap def each(x fn(string, T)) def get(key string, defaultValue T) T def in(key string) bool def remove(key string) } @import class IntMap { def new def {...}(key int, value T) IntMap def [](key int) T def []=(key int, value T) T def count int def isEmpty bool def keys List def values List def clone IntMap def each(x fn(int, T)) def get(key int, defaultValue T) T def in(key int) bool def remove(key int) } class Box { var value T } ################################################################################ # Implementations class int { def **(x int) int { var y = self var z = x < 0 ? 0 : 1 while x > 0 { if (x & 1) != 0 { z *= y } x >>= 1 y *= y } return z } def <=>(x int) int { return ((x < self) as int) - ((x > self) as int) } } class double { @alwaysinline def **(x double) double { return Math.pow(self, x) } def <=>(x double) int { return ((x < self) as int) - ((x > self) as int) } } class List { def resize(count int, defaultValue T) { assert(count >= 0) while self.count < count { append(defaultValue) } while self.count > count { removeLast } } def removeAll(value T) { var index = 0 # Remove elements in place for i in 0..count { if self[i] != value { if index < i { self[index] = self[i] } index++ } } # Shrink the array to the correct size while index < count { removeLast } } def removeDuplicates { var index = 0 # Remove elements in place for i in 0..count { var found = false var value = self[i] for j in 0..i { if value == self[j] { found = true break } } if !found { if index < i { self[index] = self[i] } index++ } } # Shrink the array to the correct size while index < count { removeLast } } def shuffle { var n = count for i in 0..n - 1 { swap(i, i + ((Math.random * (n - i)) as int)) } } def swap(i int, j int) { assert(0 <= i && i < count) assert(0 <= j && j < count) var temp = self[i] self[i] = self[j] self[j] = temp } } namespace Math { def randomInRange(min double, max double) double { assert(min <= max) var value = min + (max - min) * random assert(min <= value && value <= max) return value } def randomInRange(minInclusive int, maxExclusive int) int { assert(minInclusive <= maxExclusive) var value = minInclusive + ((maxExclusive - minInclusive) * random) as int assert(minInclusive <= value && (minInclusive != maxExclusive ? value < maxExclusive : value == maxExclusive)) return value } if TARGET != .JAVASCRIPT { def max(x double, y double, z double) double { return max(max(x, y), z) } def min(x double, y double, z double) double { return min(min(x, y), z) } def max(x int, y int, z int) int { return max(max(x, y), z) } def min(x int, y int, z int) int { return min(min(x, y), z) } def max(x double, y double, z double, w double) double { return max(max(x, y), max(z, w)) } def min(x double, y double, z double, w double) double { return min(min(x, y), min(z, w)) } def max(x int, y int, z int, w int) int { return max(max(x, y), max(z, w)) } def min(x int, y int, z int, w int) int { return min(min(x, y), min(z, w)) } } def clamp(x double, min double, max double) double { return x < min ? min : x > max ? max : x } def clamp(x int, min int, max int) int { return x < min ? min : x > max ? max : x } } " } ================================================ FILE: src/middle/librarycpp.sk ================================================ namespace Skew { const NATIVE_LIBRARY_CPP = " @import { def __doubleToString(x double) string def __intToString(x int) string @rename(\"std::isnan\") def __doubleIsNaN(x double) bool @rename(\"std::isfinite\") def __doubleIsFinite(x double) bool } class bool { def toString string { return self ? \"true\" : \"false\" } } class int { def toString string { return __intToString(self) } def >>>(x int) int { return (self as dynamic.unsigned >> x) as int } } class double { def toString string { return __doubleToString(self) } def isNaN bool { return __doubleIsNaN(self) } def isFinite bool { return __doubleIsFinite(self) } } class string { @rename(\"compare\") def <=>(x string) int @rename(\"contains\") def in(x string) bool } @rename(\"Skew::List\") class List { @rename(\"contains\") def in(x T) bool } @rename(\"Skew::StringMap\") class StringMap { @rename(\"contains\") def in(x string) bool } @rename(\"Skew::IntMap\") class IntMap { @rename(\"contains\") def in(x int) bool } @import @rename(\"Skew::StringBuilder\") class StringBuilder { } @import @rename(\"Skew::Math\") namespace Math { } @import def assert(truth bool) " } ================================================ FILE: src/middle/librarycs.sk ================================================ namespace Skew { const NATIVE_LIBRARY_CS = " @using(\"System.Diagnostics\") def assert(truth bool) { dynamic.Debug.Assert(truth) } @using(\"System\") var __random dynamic.Random = null @using(\"System\") @import namespace Math { @rename(\"Abs\") if TARGET == .CSHARP def abs(x double) double @rename(\"Abs\") if TARGET == .CSHARP def abs(x int) int @rename(\"Acos\") if TARGET == .CSHARP def acos(x double) double @rename(\"Asin\") if TARGET == .CSHARP def asin(x double) double @rename(\"Atan\") if TARGET == .CSHARP def atan(x double) double @rename(\"Atan2\") if TARGET == .CSHARP def atan2(x double, y double) double @rename(\"Sin\") if TARGET == .CSHARP def sin(x double) double @rename(\"Cos\") if TARGET == .CSHARP def cos(x double) double @rename(\"Tan\") if TARGET == .CSHARP def tan(x double) double @rename(\"Floor\") if TARGET == .CSHARP def floor(x double) double @rename(\"Ceiling\") if TARGET == .CSHARP def ceil(x double) double @rename(\"Round\") if TARGET == .CSHARP def round(x double) double @rename(\"Exp\") if TARGET == .CSHARP def exp(x double) double @rename(\"Log\") if TARGET == .CSHARP def log(x double) double @rename(\"Pow\") if TARGET == .CSHARP def pow(x double, y double) double @rename(\"Sqrt\") if TARGET == .CSHARP def sqrt(x double) double @rename(\"Max\") if TARGET == .CSHARP def max(x double, y double) double @rename(\"Max\") if TARGET == .CSHARP def max(x int, y int) int @rename(\"Min\") if TARGET == .CSHARP def min(x double, y double) double @rename(\"Min\") if TARGET == .CSHARP def min(x int, y int) int def random double { __random ?= dynamic.Random.new() return __random.NextDouble() } } class double { def isFinite bool { return !isNaN && !dynamic.double.IsInfinity(self) } def isNaN bool { return dynamic.double.IsNaN(self) } } @using(\"System.Text\") @import class StringBuilder { @rename(\"Append\") def append(x string) @rename(\"ToString\") def toString string } class bool { @rename(\"ToString\") def toString string { return self ? \"true\" : \"false\" } } class int { @rename(\"ToString\") def toString string def >>>(x int) int { return dynamic.unchecked(self as dynamic.uint >> x) as int } } class double { @rename(\"ToString\") def toString string } class string { @rename(\"CompareTo\") def <=>(x string) int @rename(\"StartsWith\") def startsWith(x string) bool @rename(\"EndsWith\") def endsWith(x string) bool @rename(\"Contains\") def in(x string) bool @rename(\"IndexOf\") def indexOf(x string) int @rename(\"LastIndexOf\") def lastIndexOf(x string) int @rename(\"Replace\") def replaceAll(before string, after string) string @rename(\"Substring\") { def slice(start int) string def slice(start int, end int) string } @rename(\"ToLower\") def toLowerCase string @rename(\"ToUpper\") def toUpperCase string def count int { return (self as dynamic).Length } def get(index int) string { return fromCodeUnit(self[index]) } def repeat(times int) string { var result = \"\" for i in 0..times { result += self } return result } @using(\"System.Linq\") @using(\"System\") def split(separator string) List { var separators = [separator] return dynamic.Enumerable.ToList((self as dynamic).Split(dynamic.Enumerable.ToArray(separators as dynamic), dynamic.StringSplitOptions.None)) } def join(parts List) string { return dynamic.string.Join(self, parts) } def slice(start int, end int) string { return (self as dynamic).Substring(start, end - start) } def codeUnits List { var result List = [] for i in 0..count { result.append(self[i]) } return result } } namespace string { def fromCodeUnit(codeUnit int) string { return dynamic.string.new(codeUnit as dynamic.char, 1) } def fromCodeUnits(codeUnits List) string { var builder = StringBuilder.new for codeUnit in codeUnits { builder.append(codeUnit as dynamic.char) } return builder.toString } } @using(\"System.Collections.Generic\") class List { @rename(\"Contains\") def in(x T) bool @rename(\"Add\") def append(value T) @rename(\"AddRange\") def append(value List) def sort(x fn(T, T) int) { # C# doesn't allow an anonymous function to be passed directly (self as dynamic).Sort((a T, b T) => x(a, b)) } @rename(\"Reverse\") def reverse @rename(\"RemoveAll\") def removeIf(x fn(T) bool) @rename(\"RemoveAt\") def removeAt(x int) @rename(\"Remove\") def removeOne(x T) @rename(\"TrueForAll\") def all(x fn(T) bool) bool @rename(\"ForEach\") def each(x fn(T)) @rename(\"FindAll\") def filter(x fn(T) bool) List @rename(\"ConvertAll\") def map(x fn(T) R) List @rename(\"IndexOf\") def indexOf(x T) int @rename(\"LastIndexOf\") def lastIndexOf(x T) int @rename(\"Insert\") def insert(x int, value T) @rename(\"InsertRange\") def insert(x int, value List) def appendOne(x T) { if !(x in self) { append(x) } } def removeRange(start int, end int) { (self as dynamic).RemoveRange(start, end - start) } @using(\"System.Linq\") { @rename(\"SequenceEqual\") def equals(x List) bool @rename(\"First\") def first T @rename(\"Last\") def last T } def any(callback fn(T) bool) bool { return !all(x => !callback(x)) } def isEmpty bool { return count == 0 } def count int { return (self as dynamic).Count } def prepend(value T) { insert(0, value) } def prepend(values List) { var count = values.count for i in 0..count { prepend(values[count - i - 1]) } } def removeFirst { removeAt(0) } def removeLast { removeAt(count - 1) } def takeFirst T { var value = first removeFirst return value } def takeLast T { var value = last removeLast return value } def takeAt(x int) T { var value = self[x] removeAt(x) return value } def takeRange(start int, end int) List { var value = slice(start, end) removeRange(start, end) return value } def slice(start int) List { return slice(start, count) } def slice(start int, end int) List { return (self as dynamic).GetRange(start, end - start) } def clone List { var clone = new clone.append(self) return clone } } @using(\"System.Collections.Generic\") @rename(\"Dictionary\") class StringMap { def count int { return (self as dynamic).Count } @rename(\"ContainsKey\") def in(key string) bool @rename(\"Remove\") def remove(key string) def isEmpty bool { return count == 0 } def {...}(key string, value T) StringMap { (self as dynamic).Add(key, value) return self } def get(key string, value T) T { return key in self ? self[key] : value } def keys List { return dynamic.System.Linq.Enumerable.ToList((self as dynamic).Keys) } def values List { return dynamic.System.Linq.Enumerable.ToList((self as dynamic).Values) } def clone StringMap { var clone = new for key in keys { clone[key] = self[key] } return clone } def each(x fn(string, T)) { for pair in self as dynamic { x(pair.Key, pair.Value) } } } @using(\"System.Collections.Generic\") @rename(\"Dictionary\") class IntMap { def count int { return (self as dynamic).Count } @rename(\"ContainsKey\") def in(key int) bool @rename(\"Remove\") def remove(key int) def isEmpty bool { return count == 0 } def {...}(key int, value T) IntMap { (self as dynamic).Add(key, value) return self } def get(key int, value T) T { return key in self ? self[key] : value } def keys List { return dynamic.System.Linq.Enumerable.ToList((self as dynamic).Keys) } def values List { return dynamic.System.Linq.Enumerable.ToList((self as dynamic).Values) } def clone IntMap { var clone = new for key in keys { clone[key] = self[key] } return clone } def each(x fn(int, T)) { for pair in self as dynamic { x(pair.Key, pair.Value) } } } " } ================================================ FILE: src/middle/libraryjs.sk ================================================ namespace Skew { const NATIVE_LIBRARY_JS = " const __create fn(dynamic) dynamic = dynamic.Object.create ? dynamic.Object.create : prototype => { return {\"__proto__\": prototype} } const __extends = (derived dynamic, base dynamic) => { derived.prototype = __create(base.prototype) derived.prototype.constructor = derived } const __imul fn(int, int) int = dynamic.Math.imul ? dynamic.Math.imul : (a, b) => { return ((a as dynamic) * (b >>> 16) << 16) + (a as dynamic) * (b & 65535) | 0 } const __prototype dynamic const __isInt = (value dynamic) => value == (value | 0) const __isBool = (value dynamic) => value == !!value const __isDouble = (value dynamic) => dynamic.typeof(value) == \"number\" const __isString = (value dynamic) => dynamic.typeof(value) == \"string\" const __asString = (value dynamic) => value == null ? value : value + \"\" def assert(truth bool) { if !truth { throw dynamic.Error(\"Assertion failed\") } } # Override this to true to remove asserts from many of the functions below so that they may be inlined const JS_INLINE_NATIVE = false @import namespace Math {} @rename(\"boolean\") class bool {} @rename(\"number\") class int {} @rename(\"number\") class double { @alwaysinline def isFinite bool { return dynamic.isFinite(self) } @alwaysinline def isNaN bool { return dynamic.isNaN(self) } } class string { def <=>(x string) int { return ((x as dynamic < self) as int) - ((x as dynamic > self) as int) } if JS_INLINE_NATIVE { @alwaysinline def slice(start int) string { return (self as dynamic).slice(start) } } else { def slice(start int) string { assert(0 <= start && start <= count) return (self as dynamic).slice(start) } } if JS_INLINE_NATIVE { @alwaysinline def slice(start int, end int) string { return (self as dynamic).slice(start, end) } } else { def slice(start int, end int) string { assert(0 <= start && start <= end && end <= count) return (self as dynamic).slice(start, end) } } @alwaysinline def startsWith(text string) bool { return (self as dynamic).startsWith(text) } @alwaysinline def endsWith(text string) bool { return (self as dynamic).endsWith(text) } @alwaysinline def replaceAll(before string, after string) string { return after.join(self.split(before)) } @alwaysinline def in(value string) bool { return indexOf(value) != -1 } @alwaysinline def count int { return (self as dynamic).length } if JS_INLINE_NATIVE { @alwaysinline def [](index int) int { return (self as dynamic).charCodeAt(index) } } else { def [](index int) int { assert(0 <= index && index < count) return (self as dynamic).charCodeAt(index) } } if JS_INLINE_NATIVE { @alwaysinline def get(index int) string { return (self as dynamic)[index] } } else { def get(index int) string { assert(0 <= index && index < count) return (self as dynamic)[index] } } def repeat(times int) string { var result = \"\" for i in 0..times { result += self } return result } @alwaysinline def join(parts List) string { return (parts as dynamic).join(self) } def codeUnits List { var result List = [] for i in 0..count { result.append(self[i]) } return result } } namespace string { @alwaysinline def fromCodeUnit(codeUnit int) string { return dynamic.String.fromCharCode(codeUnit) } def fromCodeUnits(codeUnits List) string { var result = \"\" for codeUnit in codeUnits { result += string.fromCodeUnit(codeUnit) } return result } } class StringBuilder { var buffer = \"\" def new { } @alwaysinline def append(x string) { buffer += x } @alwaysinline def toString string { return buffer } } @rename(\"Array\") class List { @rename(\"unshift\") def prepend(x T) @rename(\"push\") def append(x T) @rename(\"every\") def all(x fn(T) bool) bool @rename(\"some\") def any(x fn(T) bool) bool @rename(\"slice\") def clone List @rename(\"forEach\") def each(x fn(T)) if JS_INLINE_NATIVE { @alwaysinline def slice(start int) List { return (self as dynamic).slice(start) } } else { def slice(start int) List { assert(0 <= start && start <= count) return (self as dynamic).slice(start) } } if JS_INLINE_NATIVE { @alwaysinline def slice(start int, end int) List { return (self as dynamic).slice(start, end) } } else { def slice(start int, end int) List { assert(0 <= start && start <= end && end <= count) return (self as dynamic).slice(start, end) } } if JS_INLINE_NATIVE { @alwaysinline def [](index int) T { return (self as dynamic)[index] } } else { def [](index int) T { assert(0 <= index && index < count) return (self as dynamic)[index] } } if JS_INLINE_NATIVE { @alwaysinline def []=(index int, value T) T { return (self as dynamic)[index] = value } } else { def []=(index int, value T) T { assert(0 <= index && index < count) return (self as dynamic)[index] = value } } @alwaysinline def in(value T) bool { return indexOf(value) != -1 } @alwaysinline def isEmpty bool { return count == 0 } @alwaysinline def count int { return (self as dynamic).length } if JS_INLINE_NATIVE { @alwaysinline def first T { return self[0] } } else { def first T { assert(!isEmpty) return self[0] } } def last T { assert(!isEmpty) return self[count - 1] } def prepend(values List) { assert(values != self) var count = values.count for i in 0..count { prepend(values[count - i - 1]) } } def append(values List) { assert(values != self) for value in values { append(value) } } def insert(index int, values List) { assert(values != self) for value in values { insert(index, value) index++ } } def insert(index int, value T) { assert(0 <= index && index <= count) (self as dynamic).splice(index, 0, value) } def removeFirst { assert(!isEmpty) (self as dynamic).shift() } if JS_INLINE_NATIVE { @alwaysinline def takeFirst T { return (self as dynamic).shift() } } else { def takeFirst T { assert(!isEmpty) return (self as dynamic).shift() } } def removeLast { assert(!isEmpty) (self as dynamic).pop() } if JS_INLINE_NATIVE { @alwaysinline def takeLast T { return (self as dynamic).pop() } } else { def takeLast T { assert(!isEmpty) return (self as dynamic).pop() } } def removeAt(index int) { assert(0 <= index && index < count) (self as dynamic).splice(index, 1) } if JS_INLINE_NATIVE { @alwaysinline def takeAt(index int) T { return (self as dynamic).splice(index, 1)[0] } } else { def takeAt(index int) T { assert(0 <= index && index < count) return (self as dynamic).splice(index, 1)[0] } } def takeRange(start int, end int) List { assert(0 <= start && start <= end && end <= count) return (self as dynamic).splice(start, end - start) } def appendOne(value T) { if !(value in self) { append(value) } } def removeOne(value T) { var index = indexOf(value) if index >= 0 { removeAt(index) } } def removeRange(start int, end int) { assert(0 <= start && start <= end && end <= count) (self as dynamic).splice(start, end - start) } def removeIf(callback fn(T) bool) { var index = 0 # Remove elements in place for i in 0..count { if !callback(self[i]) { if index < i { self[index] = self[i] } index++ } } # Shrink the array to the correct size while index < count { removeLast } } def equals(other List) bool { if count != other.count { return false } for i in 0..count { if self[i] != other[i] { return false } } return true } } namespace List { @alwaysinline def new List { return [] as dynamic } } namespace StringMap { @alwaysinline def new StringMap { return dynamic.Map.new } } class StringMap { if JS_INLINE_NATIVE { @alwaysinline def [](key string) T { return (self as dynamic).get(key) } } else { def [](key string) T { assert(key in self) return (self as dynamic).get(key) } } def []=(key string, value T) T { (self as dynamic).set(key, value) return value } def {...}(key string, value T) StringMap { (self as dynamic).set(key, value) return self } @alwaysinline def in(key string) bool { return (self as dynamic).has(key) } @alwaysinline def count int { return (self as dynamic).size } @alwaysinline def isEmpty bool { return count == 0 } def get(key string, defaultValue T) T { const value = (self as dynamic).get(key) return value != dynamic.void(0) ? value : defaultValue # Compare against undefined so the key is only hashed once for speed } @alwaysinline def keys List { return dynamic.Array.from((self as dynamic).keys()) } @alwaysinline def values List { return dynamic.Array.from((self as dynamic).values()) } @alwaysinline def clone StringMap { return dynamic.Map.new(self) } @alwaysinline def remove(key string) { (self as dynamic).delete(key) } def each(x fn(string, T)) { (self as dynamic).forEach((value, key) => { x(key, value) }) } } namespace IntMap { @alwaysinline def new IntMap { return dynamic.Map.new } } class IntMap { if JS_INLINE_NATIVE { @alwaysinline def [](key int) T { return (self as dynamic).get(key) } } else { def [](key int) T { assert(key in self) return (self as dynamic).get(key) } } def []=(key int, value T) T { (self as dynamic).set(key, value) return value } def {...}(key int, value T) IntMap { (self as dynamic).set(key, value) return self } @alwaysinline def in(key int) bool { return (self as dynamic).has(key) } @alwaysinline def count int { return (self as dynamic).size } @alwaysinline def isEmpty bool { return count == 0 } def get(key int, defaultValue T) T { const value = (self as dynamic).get(key) return value != dynamic.void(0) ? value : defaultValue # Compare against undefined so the key is only hashed once for speed } @alwaysinline def keys List { return dynamic.Array.from((self as dynamic).keys()) } @alwaysinline def values List { return dynamic.Array.from((self as dynamic).values()) } @alwaysinline def clone IntMap { return dynamic.Map.new(self) } @alwaysinline def remove(key int) { (self as dynamic).delete(key) } def each(x fn(int, T)) { (self as dynamic).forEach((value, key) => { x(key, value) }) } } " } ================================================ FILE: src/middle/merging.sk ================================================ namespace Skew { enum PassKind { MERGING } class MergingPass : Pass { over kind PassKind { return .MERGING } over run(context PassContext) { Merging.mergeObject(context.log, null, context.global, context.global) } } } namespace Skew.Merging { def mergeObject(log Log, parent ObjectSymbol, target ObjectSymbol, symbol ObjectSymbol) { target.scope = ObjectScope.new(parent?.scope, target) symbol.scope = target.scope symbol.parent = parent # This is a heuristic for getting the range to be from the primary definition if symbol.kind == target.kind && symbol.variables.count > target.variables.count { target.range = symbol.range } if symbol.parameters != null { for parameter in symbol.parameters { parameter.scope = parent.scope parameter.parent = target # Type parameters cannot merge with any members var other = target.members.get(parameter.name, null) if other != null { log.semanticErrorDuplicateSymbol(parameter.range, parameter.name, other.range) continue } target.members[parameter.name] = parameter } } mergeObjects(log, target, symbol.objects) mergeFunctions(log, target, symbol.functions, .NORMAL) mergeVariables(log, target, symbol.variables) } def mergeObjects(log Log, parent ObjectSymbol, children List) { var members = parent.members children.removeIf(child => { var other = members.get(child.name, null) # Simple case: no merging if other == null { members[child.name] = child mergeObject(log, parent, child, child) return false } # Can only merge with another of the same kind or with a namespace if other.kind == .OBJECT_NAMESPACE { var swap = other.range other.range = child.range child.range = swap other.kind = child.kind } else if child.kind == .OBJECT_NAMESPACE { child.kind = other.kind } else if child.kind != other.kind { log.semanticErrorDuplicateSymbol(child.range, child.name, other.range) return true } # Classes can only have one base type var object = other.asObjectSymbol if child.extends != null { if object.extends != null { log.semanticErrorDuplicateBaseType(child.extends.range, child.name, object.extends.range) return true } object.extends = child.extends } # Merge base interfaces if child.implements != null { if object.implements != null { object.implements.append(child.implements) } else { object.implements = child.implements } } # Cannot merge two objects that both have type parameters if child.parameters != null && object.parameters != null { log.semanticErrorDuplicateTypeParameters(rangeOfParameters(child.parameters), child.name, rangeOfParameters(object.parameters)) return true } # Merge "child" into "other" mergeObject(log, parent, object, child) object.mergeInformationFrom(child) object.objects.append(child.objects) object.functions.append(child.functions) object.variables.append(child.variables) if child.parameters != null { object.parameters = child.parameters } if child.guards != null { object.guards ?= [] for guard in child.guards { for g = guard; g != null; g = g.elseGuard { g.parent = object g.contents.parent = object } object.guards.append(guard) } } return true }) } enum MergeBehavior { NORMAL INTO_DERIVED_CLASS } def mergeFunctions(log Log, parent ObjectSymbol, children List, behavior MergeBehavior) { var members = parent.members for child in children { var other = members.get(child.name, null) # Create a scope for this function's type parameters if behavior == .NORMAL { var scope = FunctionScope.new(parent.scope, child) child.scope = scope child.parent = parent if child.parameters != null { for parameter in child.parameters { parameter.scope = scope parameter.parent = child # Type parameters cannot merge with other parameters on this function var previous = scope.parameters.get(parameter.name, null) if previous != null { log.semanticErrorDuplicateSymbol(parameter.range, parameter.name, previous.range) continue } scope.parameters[parameter.name] = parameter } } } # Simple case: no merging if other == null { members[child.name] = child continue } var childKind = overloadedKind(child.kind) var otherKind = overloadedKind(other.kind) # Merge with another symbol of the same overloaded group type if childKind != otherKind || !childKind.isOverloadedFunction { if behavior == .NORMAL { log.semanticErrorDuplicateSymbol(child.range, child.name, other.range) } else { log.semanticErrorBadOverride(other.range, other.name, parent.baseType, child.range) } continue } # Merge with a group of overloaded functions if other.kind.isOverloadedFunction { other.asOverloadedFunctionSymbol.symbols.append(child) if behavior == .NORMAL { child.overloaded = other.asOverloadedFunctionSymbol } continue } # Create an overload group var overloaded = OverloadedFunctionSymbol.new(childKind, child.name, [other.asFunctionSymbol, child]) members[child.name] = overloaded other.asFunctionSymbol.overloaded = overloaded if behavior == .NORMAL { child.overloaded = overloaded } overloaded.scope = parent.scope overloaded.parent = parent } } def overloadedKind(kind SymbolKind) SymbolKind { return kind == .FUNCTION_CONSTRUCTOR || kind == .FUNCTION_GLOBAL ? .OVERLOADED_GLOBAL : kind == .FUNCTION_ANNOTATION ? .OVERLOADED_ANNOTATION : kind == .FUNCTION_INSTANCE ? .OVERLOADED_INSTANCE : kind } def mergeVariables(log Log, parent ObjectSymbol, children List) { var members = parent.members for child in children { var other = members.get(child.name, null) child.scope = VariableScope.new(parent.scope, child) child.parent = parent # Variables never merge if other != null { log.semanticErrorDuplicateSymbol(child.range, child.name, other.range) continue } members[child.name] = child } } def rangeOfParameters(parameters List) Range { return Range.span(parameters.first.range, parameters.last.range) } } ================================================ FILE: src/middle/motion.sk ================================================ namespace Skew { enum PassKind { MOTION } class MotionPass : Pass { over kind PassKind { return .MOTION } over run(context PassContext) { var motionContext = Motion.Context.new Motion.symbolMotion(context.global, context.options, motionContext) motionContext.finish } } } namespace Skew.Motion { def symbolMotion(symbol ObjectSymbol, options CompilerOptions, context Context) { # Move non-imported objects off imported objects symbol.objects.removeIf(object => { symbolMotion(object, options, context) if symbol.isImported && !object.isImported || !options.target.supportsNestedTypes && !symbol.kind.isNamespaceOrGlobal { context.moveSymbolIntoNewNamespace(object) return true } return false }) # Move global functions with implementations off of imported objects and interfaces symbol.functions.removeIf(function => { if function.kind == .FUNCTION_GLOBAL && (symbol.isImported && !function.isImported || symbol.kind == .OBJECT_INTERFACE) { context.moveSymbolIntoNewNamespace(function) return true } return false }) # Move stuff off of enums and flags if symbol.kind.isEnumOrFlags { symbol.objects.each(object => context.moveSymbolIntoNewNamespace(object)) symbol.functions.each(function => context.moveSymbolIntoNewNamespace(function)) symbol.variables.removeIf(variable => { if variable.kind != .VARIABLE_ENUM_OR_FLAGS { context.moveSymbolIntoNewNamespace(variable) return true } return false }) symbol.objects = [] symbol.functions = [] } # Move variables off of interfaces else if symbol.kind == .OBJECT_INTERFACE { symbol.variables.each(variable => context.moveSymbolIntoNewNamespace(variable)) symbol.variables = [] } } class Context { var _namespaces = IntMap.new # Avoid mutation during iteration def finish { var values = _namespaces.values values.sort(Symbol.SORT_OBJECTS_BY_ID) # Sort so the order is deterministic for object in values { object.parent.asObjectSymbol.objects.append(object) } } def moveSymbolIntoNewNamespace(symbol Symbol) { var parent = symbol.parent var namespace = _namespaces.get(parent.id, null) var object = namespace?.asObjectSymbol # Create a parallel namespace next to the parent if namespace == null { var common = parent.parent.asObjectSymbol var name = "in_" + parent.name var candidate = common.members.get(name, null) if candidate != null && candidate.kind == .OBJECT_NAMESPACE { object = candidate.asObjectSymbol } else { object = ObjectSymbol.new(.OBJECT_NAMESPACE, common.scope.generateName(name)) object.range = parent.range object.resolvedType = Type.new(.SYMBOL, object) object.state = .INITIALIZED object.scope = ObjectScope.new(common.scope, object) object.parent = common common.members[name] = object _namespaces[parent.id] = object } } # Move this function into that parallel namespace symbol.parent = object if symbol.kind.isObject { object.objects.append(symbol.asObjectSymbol) } else if symbol.kind.isFunction { object.functions.append(symbol.asFunctionSymbol) # Inflate functions with type parameters from the parent (TODO: Need to inflate call sites too) if parent.asObjectSymbol.parameters != null { var function = symbol.asFunctionSymbol function.parameters ?= [] function.parameters.prepend(parent.asObjectSymbol.parameters) } } else if symbol.kind.isVariable { object.variables.append(symbol.asVariableSymbol) } } } } ================================================ FILE: src/middle/renaming.sk ================================================ namespace Skew { enum PassKind { RENAMING } class RenamingPass : Pass { over kind PassKind { return .RENAMING } over run(context PassContext) { Renaming.renameGlobal(context.log, context.global) } } } namespace Skew.Renaming { def renameGlobal(log Log, global ObjectSymbol) { # Collect all functions var functions List = [] collectFunctionAndRenameObjectsAndVariables(global, functions) # Compute naming groups var labels = UnionFind.new.allocate(functions.count) var groups List> = [] var firstScopeForObject = IntMap.new for i in 0..functions.count { functions[i].namingGroup = i groups.append(null) } for function in functions { if function.overridden != null { labels.union(function.namingGroup, function.overridden.namingGroup) } if function.implementations != null { for implementation in function.implementations { labels.union(function.namingGroup, implementation.namingGroup) } } } for function in functions { var label = labels.find(function.namingGroup) var group = groups[label] function.namingGroup = label if group == null { group = [] groups[label] = group } else { assert(function.name == group.first.name) } group.append(function) # Certain parent objects such as namespaces may have multiple scopes. # However, we want to resolve name collisions in the same scope to detect # collisions across all scopes. Do this by using the first scope. if !(function.parent.id in firstScopeForObject) { firstScopeForObject[function.parent.id] = function.scope.parent } } # Rename stuff for group in groups { if group == null { continue } var isImportedOrExported = false var shouldRename = false var isInvalid = false var rename string = null for function in group { if function.isImportedOrExported { isImportedOrExported = true } # Make sure there isn't more than one renamed symbol if function.rename != null { if rename != null && rename != function.rename { log.semanticErrorDuplicateRename(function.range, function.name, rename, function.rename) } rename = function.rename } # Rename functions with unusual names and make sure overloaded functions have unique names if !shouldRename { if isInvalidIdentifier(function.name) { isInvalid = true shouldRename = true } else if function.overloaded != null && function.overloaded.symbols.count > 1 { shouldRename = true } } } # Bake in the rename annotation now if rename != null { for function in group { function.flags |= .IS_RENAMED function.name = rename function.rename = null } continue } # One function with a pinned name causes the whole group to avoid renaming if !shouldRename || isImportedOrExported && !isInvalid { continue } var first = group.first var arguments = first.arguments.count var count = 0 var start = first.name if (arguments == 0 || arguments == 1 && first.kind == .FUNCTION_GLOBAL) && start in unaryPrefixes { start = unaryPrefixes[start] } else if start in prefixes { start = prefixes[start] } else { if start.startsWith("@") { start = start.slice(1) } if isInvalidIdentifier(start) { start = generateValidIdentifier(start) } } # Generate a new name var name = start while group.any(function => firstScopeForObject[function.parent.id].isNameUsed(name)) { count++ name = start + count.toString } for function in group { firstScopeForObject[function.parent.id].reserveName(name, null) function.name = name } } } def collectFunctionAndRenameObjectsAndVariables(symbol ObjectSymbol, functions List) { for object in symbol.objects { if object.rename != null { object.name = object.rename object.rename = null } collectFunctionAndRenameObjectsAndVariables(object, functions) } for function in symbol.functions { functions.append(function) } for variable in symbol.variables { if variable.rename != null { variable.name = variable.rename variable.rename = null } } } def isAlpha(c int) bool { return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_' } def isNumber(c int) bool { return c >= '0' && c <= '9' } def isInvalidIdentifier(name string) bool { for i in 0..name.count { var c = name[i] if !isAlpha(c) && (i == 0 || !isNumber(c)) { return true } } return false } def generateValidIdentifier(name string) string { var text = "" for i in 0..name.count { var c = name[i] if isAlpha(c) || isNumber(c) { text += name.get(i) } } if text != "" && name.endsWith("=") { return "set" + withUppercaseFirstLetter(text) } return text == "" || !isAlpha(text[0]) ? "_" + text : text } const unaryPrefixes = { "!": "not", "+": "positive", "++": "increment", "-": "negative", "--": "decrement", "~": "complement", } const prefixes = { # Binary operators "%": "remainder", "%%": "modulus", "&": "and", "*": "multiply", "**": "power", "+": "add", "-": "subtract", "/": "divide", "<<": "leftShift", "<=>": "compare", ">>": "rightShift", ">>>": "unsignedRightShift", "^": "xor", "in": "contains", "|": "or", # Binary assignment operators "%%=": "modulusUpdate", "%=": "remainderUpdate", "&=": "andUpdate", "**=": "powerUpdate", "*=": "multiplyUpdate", "+=": "addUpdate", "-=": "subtractUpdate", "/=": "divideUpdate", "<<=": "leftShiftUpdate", ">>=": "rightShiftUpdate", "^=": "xorUpdate", "|=": "orUpdate", # Index operators "[]": "get", "[]=": "set", # Initializer operators "<>...": "append", "[...]": "append", "[new]": "new", "{...}": "insert", "{new}": "new", } } ================================================ FILE: src/middle/resolving.sk ================================================ namespace Skew { enum PassKind { RESOLVING } class ResolvingPass : Pass { over kind PassKind { return .RESOLVING } over run(context PassContext) { context.cache.loadGlobals(context.log, context.global) Resolving.Resolver.new(context.global, context.options, context.options.defines.clone, context.cache, context.log).resolve # The tree isn't fully resolved for speed reasons if code completion is requested if context.options.completionContext == null { context.isResolvePassComplete = true } } } } namespace Skew.Resolving { enum ConversionKind { IMPLICIT EXPLICIT } enum SymbolStatistic { READ WRITE } class LocalVariableStatistics { var symbol VariableSymbol var readCount = 0 var writeCount = 0 } namespace LocalVariableStatistics { const SORT_BY_ID = (a LocalVariableStatistics, b LocalVariableStatistics) => a.symbol.id <=> b.symbol.id } class Resolver { const _global ObjectSymbol const _options CompilerOptions const _defines StringMap const _cache TypeCache const _log Log const _foreachLoops List = [] const _localVariableStatistics IntMap = {} const _controlFlow = ControlFlowAnalyzer.new const _generatedGlobalVariables List = [] var _constantFolder Folding.ConstantFolder = null var _isMergingGuards = true def resolve { _constantFolder = Folding.ConstantFolder.new(_cache, _options, symbol => _initializeSymbol(symbol)) _initializeGlobals _iterativelyMergeGuards _resolveGlobal _removeObsoleteFunctions(_global) _global.variables.insert(0, _generatedGlobalVariables) } # Put the guts of the function inside another function because V8 doesn't # optimize functions with try-catch statements def _initializeSymbolSwitch(symbol Symbol) { switch symbol.kind { case .OBJECT_CLASS, .OBJECT_ENUM, .OBJECT_FLAGS, .OBJECT_GLOBAL, .OBJECT_INTERFACE, .OBJECT_NAMESPACE, .OBJECT_WRAPPED { _initializeObject(symbol.asObjectSymbol) } case .FUNCTION_ANNOTATION, .FUNCTION_CONSTRUCTOR, .FUNCTION_GLOBAL, .FUNCTION_INSTANCE, .FUNCTION_LOCAL { _initializeFunction(symbol.asFunctionSymbol) } case .VARIABLE_ARGUMENT, .VARIABLE_ENUM_OR_FLAGS, .VARIABLE_GLOBAL, .VARIABLE_INSTANCE, .VARIABLE_LOCAL { _initializeVariable(symbol.asVariableSymbol) } case .PARAMETER_FUNCTION, .PARAMETER_OBJECT { _initializeParameter(symbol.asParameterSymbol) } case .OVERLOADED_ANNOTATION, .OVERLOADED_GLOBAL, .OVERLOADED_INSTANCE { _initializeOverloadedFunction(symbol.asOverloadedFunctionSymbol) } default { assert(false) } } } def _initializeSymbol(symbol Symbol) { # The scope should have been set by the merging pass (or by this pass for local variables) assert(symbol.scope != null) # Only initialize the symbol once if symbol.state == .UNINITIALIZED { symbol.state = .INITIALIZING try { _initializeSymbolSwitch(symbol) } # If guard merging failed, reset the type so we'll try again next time catch failure GuardMergingFailure { symbol.state = .UNINITIALIZED throw failure } assert(symbol.resolvedType != null) symbol.state = .INITIALIZED if symbol.kind.isFunction { var function = symbol.asFunctionSymbol var overloaded = function.overloaded # After initializing a function symbol, ensure the entire overload set is initialized if overloaded != null && overloaded.state == .UNINITIALIZED { _initializeSymbol(overloaded) } } } # Detect cyclic symbol references such as "foo foo;" else if symbol.state == .INITIALIZING { _log.semanticErrorCyclicDeclaration(symbol.range, symbol.name) symbol.resolvedType = .DYNAMIC } } def _validateEntryPoint(symbol FunctionSymbol) { # Detect duplicate entry points if _cache.entryPointSymbol != null { _log.semanticErrorDuplicateEntryPoint(symbol.range, _cache.entryPointSymbol.range) return } _cache.entryPointSymbol = symbol # Only recognize a few entry point types var type = symbol.resolvedType if type != .DYNAMIC { var argumentTypes = type.argumentTypes # The argument list must be empty or one argument of type "List" if argumentTypes.count > 1 || argumentTypes.count == 1 && argumentTypes.first != _cache.createListType(_cache.stringType) { _log.semanticErrorInvalidEntryPointArguments(Range.span(symbol.arguments.first.range, symbol.arguments.last.type.range), symbol.name) } # The return type must be nothing or "int" else if type.returnType != null && type.returnType != _cache.intType { _log.semanticErrorInvalidEntryPointReturnType(symbol.returnType.range, symbol.name) } } } def _resolveDefines(symbol VariableSymbol) { var key = symbol.fullName var define = _defines.get(key, null) if define == null { return } # Remove the define so we can tell what defines weren't used later on _defines.remove(key) var type = symbol.resolvedType var range = define.value var value = range.toString var node Node = null # Special-case booleans if type == _cache.boolType { if value == "true" || value == "false" { node = Node.createBool(value == "true") } } # Special-case doubles else if type == _cache.doubleType { var number = parseDoubleLiteral(value) if !number.isNaN { node = Node.createDouble(number) } } # Special-case strings else if type == _cache.stringType { node = Node.createString(value) } # Special-case enums else if type.isEnumOrFlags { node = Node.createDot(null, value) } # Integers can also apply to doubles if node == null && _cache.isNumeric(type) { var box = Parsing.parseIntLiteral(_log, range) if box != null { node = Node.createInt(box.value) } } # Stop if anything failed above if node == null { _log.semanticErrorInvalidDefine(range, value, type, key) return } _resolveAsParameterizedExpressionWithConversion(node.withRange(range), _global.scope, type) symbol.value = node } def _resolveAnnotations(symbol Symbol) { var parent = symbol.parent var annotations = symbol.annotations # The import/export annotations are inherited, except import isn't inherited for implemented functions if parent != null { symbol.flags |= parent.flags & (symbol.kind.isFunction && symbol.asFunctionSymbol.block != null ? .IS_EXPORTED : .IS_IMPORTED | .IS_EXPORTED) } # Resolve annotations on this symbol after annotation inheritance. Don't # use removeIf() since this annotation list may be shared elsewhere. if annotations != null { symbol.annotations = annotations.filter(annotation => _resolveAnnotation(annotation, symbol)) } # Protected access used to be an annotation. It's now indicated with just # a leading underscore. if symbol.name.startsWith("_") && !symbol.kind.isLocal { symbol.flags |= .IS_PROTECTED } } def _resolveParameters(parameters List) { if parameters != null { for parameter in parameters { _resolveParameter(parameter) } } } def _initializeParameter(symbol ParameterSymbol) { symbol.resolvedType ?= Type.new(.SYMBOL, symbol) _resolveAnnotations(symbol) } def _resolveParameter(symbol ParameterSymbol) { _initializeSymbol(symbol) } def _initializeObject(symbol ObjectSymbol) { var kind = symbol.kind var extends = symbol.extends var implements = symbol.implements symbol.resolvedType ?= Type.new(.SYMBOL, symbol) _resolveParameters(symbol.parameters) # Resolve the base type (only for classes and wrapped types) if extends != null { _resolveAsParameterizedType(extends, symbol.scope) var baseType = extends.resolvedType if kind == .OBJECT_WRAPPED { symbol.wrappedType = baseType symbol.resolvedType.environment = baseType.environment # Don't lose the type parameters from the base type } else if kind != .OBJECT_CLASS || (baseType != .DYNAMIC && (!baseType.isClass || baseType.symbol.isValueType)) { _log.semanticErrorInvalidExtends(extends.range, baseType) } else if baseType != .DYNAMIC { symbol.baseType = baseType symbol.baseClass = baseType.symbol.asObjectSymbol symbol.resolvedType.environment = baseType.environment # Don't lose the type parameters from the base type # Copy members from the base type var functions List = [] var members = symbol.baseClass.members.values members.sort(Symbol.SORT_BY_ID) # Sort so the order is deterministic for member in members { var memberKind = member.kind # Separate out functions if memberKind.isFunction { if memberKind != .FUNCTION_CONSTRUCTOR { functions.append(member.asFunctionSymbol) } } # Include overloaded functions individually else if memberKind.isOverloadedFunction { for function in member.asOverloadedFunctionSymbol.symbols { if function.kind != .FUNCTION_CONSTRUCTOR { functions.append(function) } } } # Other kinds else if !memberKind.isParameter { var other = symbol.members.get(member.name, null) if other != null { _log.semanticErrorBadOverride(other.range, other.name, baseType, member.range) } else { symbol.members[member.name] = member } } } Merging.mergeFunctions(_log, symbol, functions, .INTO_DERIVED_CLASS) } } # Wrapped types without something to wrap don't make sense else if kind == .OBJECT_WRAPPED { _log.semanticErrorMissingWrappedType(symbol.range, symbol.fullName) # Make sure to fill out the wrapped type anyway so code that tries to # access it doesn't crash. The dynamic type should ignore further errors. symbol.wrappedType = .DYNAMIC } # Resolve the base interface types if implements != null { symbol.interfaceTypes = [] for i in 0..implements.count { var type = implements[i] _resolveAsParameterizedType(type, symbol.scope) # Ignore the dynamic type, which will be from errors and dynamic expressions used for exports var interfaceType = type.resolvedType if interfaceType == .DYNAMIC { continue } # Only classes can derive from interfaces if kind != .OBJECT_CLASS || !interfaceType.isInterface { _log.semanticErrorInvalidImplements(type.range, interfaceType) continue } # An interface can only be implemented once for j in 0..i { var other = implements[j] if other.resolvedType == interfaceType { _log.semanticErrorDuplicateImplements(type.range, interfaceType, other.range) break } } symbol.interfaceTypes.append(interfaceType) } } # Assign values for all enums and flags before they are initialized if kind.isEnumOrFlags { var nextValue = 0 for variable in symbol.variables { if variable.kind == .VARIABLE_ENUM_OR_FLAGS { if nextValue >= 32 && kind == .OBJECT_FLAGS { _log.semanticErrorTooManyFlags(variable.range, symbol.name) } variable.value = Node.createInt(kind == .OBJECT_FLAGS ? 1 << nextValue : nextValue).withType(symbol.resolvedType).withRange(variable.range) nextValue++ } } symbol.flags |= .IS_VALUE_TYPE } _resolveAnnotations(symbol) # Create a default constructor if one doesn't exist var constructor = symbol.members.get("new", null) if kind == .OBJECT_CLASS && !symbol.isImported && constructor == null { var baseConstructor = symbol.baseClass?.members.get("new", null) # Unwrap the overload group if present if baseConstructor != null && baseConstructor.kind == .OVERLOADED_GLOBAL { var overloaded = baseConstructor.asOverloadedFunctionSymbol for overload in overloaded.symbols { if overload.kind == .FUNCTION_CONSTRUCTOR { if baseConstructor.kind == .FUNCTION_CONSTRUCTOR { baseConstructor = null # Signal that there isn't a single base constructor break } baseConstructor = overload } } } # A default constructor can only be created if the base class has a single constructor if symbol.baseClass == null || baseConstructor != null && baseConstructor.kind == .FUNCTION_CONSTRUCTOR { var generated = FunctionSymbol.new(.FUNCTION_CONSTRUCTOR, "new") generated.scope = FunctionScope.new(symbol.scope, generated) generated.flags |= .IS_AUTOMATICALLY_GENERATED generated.parent = symbol generated.range = symbol.range generated.overridden = baseConstructor?.asFunctionSymbol symbol.functions.append(generated) symbol.members[generated.name] = generated } } # Create a default toString if one doesn't exist if kind == .OBJECT_ENUM && !symbol.isImported && !("toString" in symbol.members) { var generated = FunctionSymbol.new(.FUNCTION_INSTANCE, "toString") generated.scope = FunctionScope.new(symbol.scope, generated) generated.flags |= .IS_AUTOMATICALLY_GENERATED | .IS_INLINING_FORCED _options.isAlwaysInlinePresent = true generated.parent = symbol generated.range = symbol.range symbol.functions.append(generated) symbol.members[generated.name] = generated } } def _checkInterfacesAndAbstractStatus(object ObjectSymbol, function FunctionSymbol) { assert(function.kind == .FUNCTION_INSTANCE) assert(function.state == .INITIALIZED) if !object.isAbstract && !function.isImported && !function.isObsolete && function.block == null { object.isAbstractBecauseOf = function } } def _checkInterfacesAndAbstractStatus(symbol ObjectSymbol) { assert(symbol.state == .INITIALIZED) if symbol.hasCheckedInterfacesAndAbstractStatus || symbol.kind != .OBJECT_CLASS { return } symbol.hasCheckedInterfacesAndAbstractStatus = true # Check to see if this class is abstract (has unimplemented members) var members = symbol.members.values members.sort(Symbol.SORT_BY_ID) # Sort so the order is deterministic for member in members { if member.kind == .OVERLOADED_INSTANCE { _initializeSymbol(member) for function in member.asOverloadedFunctionSymbol.symbols { _checkInterfacesAndAbstractStatus(symbol, function) } } else if member.kind == .FUNCTION_INSTANCE { _initializeSymbol(member) _checkInterfacesAndAbstractStatus(symbol, member.asFunctionSymbol) } if symbol.isAbstract { break } } # Check interfaces for missing implementations if symbol.interfaceTypes != null { for interfaceType in symbol.interfaceTypes { for function in interfaceType.symbol.asObjectSymbol.functions { if function.kind != .FUNCTION_INSTANCE || function.block != null { continue } _initializeSymbol(function) var member = symbol.members.get(function.name, null) var match FunctionSymbol = null var equivalence = TypeCache.Equivalence.NOT_EQUIVALENT # Search for a matching function if member != null { _initializeSymbol(member) if member.kind == .OVERLOADED_INSTANCE { for other in member.asOverloadedFunctionSymbol.symbols { equivalence = _cache.areFunctionSymbolsEquivalent(function, interfaceType.environment, other, null) if equivalence != .NOT_EQUIVALENT { match = other break } } } else if member.kind == .FUNCTION_INSTANCE { equivalence = _cache.areFunctionSymbolsEquivalent(function, interfaceType.environment, member.asFunctionSymbol, null) if equivalence != .NOT_EQUIVALENT { match = member.asFunctionSymbol } } } # Validate use of the interface if match == null { _log.semanticErrorBadInterfaceImplementation(symbol.range, symbol.resolvedType, interfaceType, function.name, function.range) } else if equivalence == .EQUIVALENT_EXCEPT_RETURN_TYPE { var returnType = function.resolvedType.returnType if returnType != null { returnType = _cache.substitute(returnType, interfaceType.environment) } _log.semanticErrorBadInterfaceImplementationReturnType(match.range, match.name, match.resolvedType.returnType, _cache.substituteFunctionParameters(returnType, match, function), interfaceType, function.range) } else { function.implementations ?= [] function.implementations.append(match) } } } } } def _initializeGlobals { _initializeSymbol(_cache.boolType.symbol) _initializeSymbol(_cache.doubleType.symbol) _initializeSymbol(_cache.intMapType.symbol) _initializeSymbol(_cache.intType.symbol) _initializeSymbol(_cache.listType.symbol) _initializeSymbol(_cache.stringMapType.symbol) _initializeSymbol(_cache.stringType.symbol) } def _resolveGlobal { _resolveObject(_global) _scanLocalVariables _convertForeachLoops _discardUnusedDefines } # An obsolete function is one without an implementation that was dropped in # favor of one with an implementation: # # namespace Foo { # def foo {} # # # This will be marked as obsolete # def foo # } # def _removeObsoleteFunctions(symbol ObjectSymbol) { for object in symbol.objects { _removeObsoleteFunctions(object) } symbol.functions.removeIf(function => function.isObsolete) } def _iterativelyMergeGuards { var guards List # Iterate until a fixed point is reached while true { guards = [] _scanForGuards(_global, guards) if guards.isEmpty { break } # Each iteration must remove at least one guard to continue if !_processGuards(guards) { break } } _isMergingGuards = false # All remaining guards are errors for guard in guards { var count = _log.errorCount _resolveAsParameterizedExpressionWithConversion(guard.test, guard.parent.scope, _cache.boolType) if _log.errorCount == count { _log.semanticErrorExpectedConstant(guard.test.range) } } } def _scanForGuards(symbol ObjectSymbol, guards List) { if symbol.guards != null { guards.append(symbol.guards) } for object in symbol.objects { _scanForGuards(object, guards) } } if TARGET == .CSHARP { class GuardMergingFailure : dynamic.System.Exception { } } else { class GuardMergingFailure { } } def _reportGuardMergingFailure(node Node) { if _isMergingGuards { throw GuardMergingFailure.new } } def _attemptToResolveGuardConstant(node Node, scope Scope) bool { assert(scope != null) try { _resolveAsParameterizedExpressionWithConversion(node, scope, _cache.boolType) _constantFolder.foldConstants(node) return true } catch failure GuardMergingFailure { } return false } def _processGuards(guards List) bool { var wasGuardRemoved = false for guard in guards { var test = guard.test var parent = guard.parent # If it's not a constant, we'll just try again in the next iteration if !_attemptToResolveGuardConstant(test, parent.scope) { continue } if test.isBool { parent.guards.removeOne(guard) wasGuardRemoved = true if test.isTrue { _mergeGuardIntoObject(guard, parent) } else { var elseGuard = guard.elseGuard if elseGuard != null { if elseGuard.test != null { elseGuard.parent = parent parent.guards.append(elseGuard) } else { _mergeGuardIntoObject(elseGuard, parent) } } } } } return wasGuardRemoved } def _mergeGuardIntoObject(guard Guard, object ObjectSymbol) { var symbol = guard.contents Merging.mergeObjects(_log, object, symbol.objects) Merging.mergeFunctions(_log, object, symbol.functions, .NORMAL) Merging.mergeVariables(_log, object, symbol.variables) object.objects.append(symbol.objects) object.functions.append(symbol.functions) object.variables.append(symbol.variables) # Handle nested guard clauses like this: # # if true { # if true { # var foo = 0 # } # } # if symbol.guards != null { for nested in symbol.guards { object.guards.append(nested) for g = nested; g != null; g = g.elseGuard { g.parent = object g.contents.parent = object } } } } # Foreach loops are converted to for loops after everything is resolved # because that process needs to generate symbol names and it's much easier # to generate non-conflicting symbol names after all local variables have # been defined. def _convertForeachLoops { for node in _foreachLoops { var symbol = node.symbol.asVariableSymbol var scope = symbol.scope.findEnclosingFunctionOrLambda # Generate names at the function level to avoid conflicts with local scopes var value = node.foreachValue var block = node.foreachBlock # Handle "for i in 0..10" if value.kind == .PAIR { var first = value.firstValue var second = value.secondValue symbol.flags &= ~.IS_CONST symbol.value = first.remove var setup = Node.createVariables.appendChild(Node.createVariable(symbol)) var symbolName = Node.createSymbolReference(symbol) var update = Node.createUnary(.PREFIX_INCREMENT, symbolName) var test Node # Special-case constant iteration limits to generate simpler code if second.kind == .CONSTANT || second.kind == .NAME && second.symbol != null && second.symbol.isConst { test = Node.createBinary(.LESS_THAN, symbolName.clone, second.remove) } # Otherwise, save the iteration limit in case it changes during iteration else { var count = VariableSymbol.new(.VARIABLE_LOCAL, scope.generateName("count")) count.initializeWithType(_cache.intType) count.value = second.remove setup.appendChild(Node.createVariable(count)) test = Node.createBinary(.LESS_THAN, symbolName.clone, Node.createSymbolReference(count)) } # Use a C-style for loop to implement this foreach loop node.become(Node.createFor(setup, test, update, block.remove).withComments(node.comments).withRange(node.range)) # Make sure the new expressions are resolved _resolveNode(test, symbol.scope, null) _resolveNode(update, symbol.scope, null) } else if _cache.isList(value.resolvedType) && !_options.target.supportsListForeach { # Create the index variable var index = VariableSymbol.new(.VARIABLE_LOCAL, scope.generateName("i")) index.initializeWithType(_cache.intType) index.value = _cache.createInt(0) var setup = Node.createVariables.appendChild(Node.createVariable(index)) var indexName = Node.createSymbolReference(index) # Create the list variable var list VariableSymbol = null if value.kind == .NAME && value.symbol != null && value.symbol.kind.isVariable && value.symbol.isConst { list = value.symbol.asVariableSymbol } else { list = VariableSymbol.new(.VARIABLE_LOCAL, scope.generateName("list")) list.initializeWithType(value.resolvedType) list.value = value.remove setup.appendChild(Node.createVariable(list)) } var listName = Node.createSymbolReference(list) # Create the count variable var count = VariableSymbol.new(.VARIABLE_LOCAL, scope.generateName("count")) count.initializeWithType(_cache.intType) count.value = Node.createDot(listName, "count") setup.appendChild(Node.createVariable(count)) var countName = Node.createSymbolReference(count) # Move the loop variable into the loop body symbol.value = Node.createIndex(listName.clone, indexName) block.prependChild(Node.createVariables.appendChild(Node.createVariable(symbol))) # Use a C-style for loop to implement this foreach loop var test = Node.createBinary(.LESS_THAN, indexName.clone, countName) var update = Node.createUnary(.PREFIX_INCREMENT, indexName.clone) node.become(Node.createFor(setup, test, update, block.remove).withComments(node.comments).withRange(node.range)) # Make sure the new expressions are resolved _resolveNode(symbol.value, symbol.scope, null) _resolveNode(count.value, symbol.scope, null) _resolveNode(test, symbol.scope, null) _resolveNode(update, symbol.scope, null) } } } def _scanLocalVariables { var values = _localVariableStatistics.values values.sort(LocalVariableStatistics.SORT_BY_ID) # Sort so the order is deterministic for info in values { var symbol = info.symbol # Variables that are never re-assigned can safely be considered constants for constant folding if info.writeCount == 0 && _options.foldAllConstants { symbol.flags |= .IS_CONST } # Unused local variables can safely be removed, but don't warn about "for i in 0..10 {}" if info.readCount == 0 && !symbol.isLoopVariable && symbol.kind == .VARIABLE_LOCAL { _log.semanticWarningUnreadLocalVariable(symbol.range, symbol.name) } # Rename local variables that conflict var scope = symbol.scope while scope.kind == .LOCAL { scope = scope.parent } if scope.used != null && scope.used.get(symbol.name, null) != symbol { symbol.name = scope.generateName(symbol.name) } } } def _discardUnusedDefines { var keys = _defines.keys keys.sort((a, b) => a <=> b) # Sort so the order is deterministic for key in keys { _log.semanticErrorInvalidDefine(_defines[key].name, key) } } def _resolveObject(symbol ObjectSymbol) { _initializeSymbol(symbol) for object in symbol.objects { _resolveObject(object) } for function in symbol.functions { _resolveFunction(function) } for variable in symbol.variables { _resolveVariable(variable) } _checkInterfacesAndAbstractStatus(symbol) } def _initializeFunction(symbol FunctionSymbol) { symbol.resolvedType ?= Type.new(.SYMBOL, symbol) # Referencing a normal variable instead of a special node kind for "this" # makes many things much easier including lambda capture and devirtualization if symbol.kind == .FUNCTION_INSTANCE || symbol.kind == .FUNCTION_CONSTRUCTOR { var this = VariableSymbol.new(.VARIABLE_ARGUMENT, "self") this.initializeWithType(_cache.parameterize(symbol.parent.resolvedType)) this.flags |= .IS_CONST symbol.this = this } # Lazily-initialize automatically generated functions if symbol.isAutomaticallyGenerated { if symbol.kind == .FUNCTION_CONSTRUCTOR { assert(symbol.name == "new") _automaticallyGenerateClassConstructor(symbol) } else if symbol.kind == .FUNCTION_INSTANCE { assert(symbol.name == "toString") _automaticallyGenerateEnumToString(symbol) } } _resolveParameters(symbol.parameters) # Resolve the argument variables symbol.resolvedType.argumentTypes = [] for argument in symbol.arguments { argument.scope = symbol.scope _resolveVariable(argument) symbol.resolvedType.argumentTypes.append(argument.resolvedType) } symbol.argumentOnlyType = _cache.createLambdaType(symbol.resolvedType.argumentTypes, null) # Resolve the return type if present (no return type means "void") var returnType Type = null if symbol.returnType != null { if symbol.kind == .FUNCTION_CONSTRUCTOR { _log.semanticErrorConstructorReturnType(symbol.returnType.range) } # Explicitly handle a "void" return type for better error messages else if symbol.returnType.kind == .NAME && symbol.returnType.asString == "void" && symbol.scope.find("void", .NORMAL) == null { _log.semanticErrorVoidReturnType(symbol.returnType.range) } else { _resolveAsParameterizedType(symbol.returnType, symbol.scope) returnType = symbol.returnType.resolvedType } } # Constructors always return the type they construct if symbol.kind == .FUNCTION_CONSTRUCTOR { returnType = _cache.parameterize(symbol.parent.resolvedType) symbol.returnType = Node.createType(returnType) } # The "<=>" operator must return a numeric value for comparison with zero var count = symbol.arguments.count if symbol.name == "<=>" { if returnType == null || returnType != _cache.intType { _log.semanticErrorComparisonOperatorNotInt(symbol.returnType != null ? symbol.returnType.range : symbol.range) returnType = .DYNAMIC } else if count != 1 { _log.semanticErrorWrongArgumentCount(symbol.range, symbol.name, 1) } } # Setters must have one argument else if symbol.isSetter && count != 1 { _log.semanticErrorWrongArgumentCount(symbol.range, symbol.name, 1) symbol.flags &= ~.IS_SETTER } # Validate argument count else { var argumentCount = argumentCountForOperator(symbol.name) if argumentCount != null && !(count in argumentCount) { _log.semanticErrorWrongArgumentCountRange(symbol.range, symbol.name, argumentCount) } # Enforce that the initializer constructor operators take lists of # values to avoid confusing error messages inside the code generated # for initializer expressions else if symbol.name == "{new}" || symbol.name == "[new]" { for argument in symbol.arguments { if argument.resolvedType != .DYNAMIC && !_cache.isList(argument.resolvedType) { _log.semanticErrorExpectedList(argument.range, argument.name, argument.resolvedType) } } } } symbol.resolvedType.returnType = returnType _resolveAnnotations(symbol) # Validate the entry point after this symbol has a type if symbol.isEntryPoint { _validateEntryPoint(symbol) } } def _automaticallyGenerateClassConstructor(symbol FunctionSymbol) { # Create the function body var block = Node.createBlock symbol.block = block # Mirror the base constructor's arguments if symbol.overridden != null { _initializeSymbol(symbol.overridden) var arguments = symbol.overridden.arguments var base = Node.createSuper.withRange(symbol.overridden.range) if arguments.isEmpty { block.appendChild(Node.createExpression(base)) } else { var call = Node.createCall(base) for variable in arguments { var argument = VariableSymbol.new(.VARIABLE_ARGUMENT, variable.name) argument.range = variable.range argument.initializeWithType(variable.resolvedType) symbol.arguments.append(argument) call.appendChild(Node.createSymbolReference(argument)) } block.prependChild(Node.createExpression(call)) } } # Add an argument for every uninitialized variable var parent = symbol.parent.asObjectSymbol _initializeSymbol(parent) for variable in parent.variables { if variable.kind == .VARIABLE_INSTANCE { _initializeSymbol(variable) if variable.value == null { var argument = VariableSymbol.new(.VARIABLE_ARGUMENT, variable.name) argument.initializeWithType(variable.resolvedType) argument.range = variable.range symbol.arguments.append(argument) block.appendChild(Node.createExpression(Node.createBinary(.ASSIGN, Node.createMemberReference(Node.createSymbolReference(symbol.this), variable), Node.createSymbolReference(argument)).withRange(variable.range))) } else { block.appendChild(Node.createExpression(Node.createBinary(.ASSIGN, Node.createMemberReference(Node.createSymbolReference(symbol.this), variable), variable.value.clone).withRange(variable.range))) } } } # Make constructors without arguments into getters if symbol.arguments.isEmpty { symbol.flags |= .IS_GETTER } } def _automaticallyGenerateEnumToString(symbol FunctionSymbol) { var parent = symbol.parent.asObjectSymbol var names = Node.createList _initializeSymbol(parent) symbol.returnType = Node.createType(_cache.stringType) symbol.flags |= .IS_GETTER # TypeScript has special enum-to-string support that we can use instead if _options.target is TypeScriptTarget { const target = Node.createName(parent.name).withSymbol(parent).withType(.DYNAMIC) symbol.block = Node.createBlock.appendChild(Node.createReturn(Node.createIndex(target, Node.createName("self")))) return } for variable in parent.variables { if variable.kind == .VARIABLE_ENUM_OR_FLAGS { assert(variable.value.asInt == names.childCount) names.appendChild(Node.createString(variable.name)) } } var strings = VariableSymbol.new(.VARIABLE_GLOBAL, parent.scope.generateName("_strings")) strings.range = parent.range strings.initializeWithType(_cache.createListType(_cache.stringType)) strings.value = names strings.flags |= .IS_PROTECTED | .IS_CONST | .IS_AUTOMATICALLY_GENERATED strings.parent = parent strings.scope = parent.scope parent.variables.append(strings) _resolveAsParameterizedExpressionWithConversion(strings.value, strings.scope, strings.resolvedType) symbol.block = Node.createBlock.appendChild(Node.createReturn(Node.createIndex(Node.createSymbolReference(strings), Node.createName("self")))) } def _resolveFunction(symbol FunctionSymbol) { _initializeSymbol(symbol) # Validate use of "def" vs "over" if !symbol.isObsolete { if symbol.overridden != null && symbol.kind == .FUNCTION_INSTANCE { if !symbol.isOver { _log.semanticErrorModifierMissingOverride(symbol.range, symbol.name, symbol.overridden.range) } } else { if symbol.isOver { _log.semanticErrorModifierUnusedOverride(symbol.range, symbol.name) } } } var scope = LocalScope.new(symbol.scope, .NORMAL) if symbol.this != null { scope.define(symbol.this, _log) } # Default values for argument variables aren't resolved with this local # scope since they are evaluated at the call site, not inside the # function body, and shouldn't have access to other arguments for argument in symbol.arguments { scope.define(argument, _log) _localVariableStatistics[argument.id] = LocalVariableStatistics.new(argument) } # The function is considered abstract if the body is missing var block = symbol.block if block != null { var firstStatement = block.firstChild if firstStatement != null && firstStatement.isSuperCallStatement { firstStatement = firstStatement.nextSibling } # User-specified constructors have variable initializers automatically inserted if symbol.kind == .FUNCTION_CONSTRUCTOR && !symbol.isAutomaticallyGenerated { for variable in symbol.parent.asObjectSymbol.variables { if variable.kind == .VARIABLE_INSTANCE { _resolveVariable(variable) # Attempt to create a default value if absent. Right now this # avoids the problem of initializing type parameters: # # class Foo { # var foo T # def new {} # def use T { return foo } # } # # This should be fixed at some point. if variable.value == null && !variable.resolvedType.isParameter { variable.value = _createDefaultValueForType(variable.resolvedType, variable.range) } if variable.value != null { block.insertChildBefore(firstStatement, Node.createExpression(Node.createBinary(.ASSIGN, Node.createMemberReference(Node.createSymbolReference(symbol.this), variable), variable.value.clone))) } } } } # Skip resolving irrelevant function bodies to speed up code completion var context = _options.completionContext if context != null && block.range != null && block.range.source != context.source { return } _resolveNode(block, scope, null) # Missing a return statement is an error if symbol.kind != .FUNCTION_CONSTRUCTOR { var returnType = symbol.resolvedType.returnType if returnType != null && !symbol.isDynamicLambda && block.hasControlFlowAtEnd { _log.semanticErrorMissingReturn(symbol.range, symbol.name, returnType) } } # Derived class constructors must start with a call to "super" else if symbol.parent.asObjectSymbol.baseClass != null { var first = block.firstChild if first == null || !first.isSuperCallStatement { _log.semanticErrorMissingSuper(firstStatement == null ? symbol.range : firstStatement.range) } } } # Global functions and functions on non-virtual types can't be abstract else if !symbol.isImported && !symbol.isObsolete && (symbol.kind == .FUNCTION_GLOBAL || symbol.kind == .FUNCTION_CONSTRUCTOR || symbol.kind == .FUNCTION_INSTANCE && symbol.parent.kind != .OBJECT_CLASS && symbol.parent.kind != .OBJECT_INTERFACE) { _log.semanticErrorUnimplementedFunction(symbol.range, symbol.name) } } def _recordStatistic(symbol Symbol, statistic SymbolStatistic) { if symbol != null && (symbol.kind == .VARIABLE_LOCAL || symbol.kind == .VARIABLE_ARGUMENT) { var info = _localVariableStatistics.get(symbol.id, null) if info != null { switch statistic { case .READ { info.readCount++ } case .WRITE { info.writeCount++ } } } } } def _initializeVariable(symbol VariableSymbol) { var value = symbol.value # Normal variables may omit the initializer if the type is present if symbol.type != null { _resolveAsParameterizedType(symbol.type, symbol.scope) symbol.resolvedType = symbol.type.resolvedType symbol.state = .INITIALIZED # Resolve the constant now so initialized constants always have a value if symbol.isConst && value != null { _resolveAsParameterizedExpressionWithConversion(value, symbol.scope, symbol.resolvedType) } } # Enums take their type from their parent else if symbol.kind == .VARIABLE_ENUM_OR_FLAGS { symbol.resolvedType = symbol.parent.resolvedType } # Implicitly-typed variables take their type from their initializer else if value != null { _resolveAsParameterizedExpression(value, symbol.scope) var type = value.resolvedType symbol.resolvedType = type # Forbid certain types if !_isValidVariableType(type) { _log.semanticErrorBadImplicitVariableType(symbol.range, type) symbol.resolvedType = .DYNAMIC } } # Use a different error for constants which must have a type and lambda arguments which cannot have an initializer else if symbol.isConst || symbol.scope.kind == .FUNCTION && symbol.scope.asFunctionScope.symbol.kind == .FUNCTION_LOCAL { _log.semanticErrorVarMissingType(symbol.range, symbol.name) symbol.resolvedType = .DYNAMIC } # Variables without a type are an error else { _log.semanticErrorVarMissingValue(symbol.range, symbol.name) symbol.resolvedType = .DYNAMIC } # Make sure the symbol has a type node symbol.type ?= Node.createType(symbol.resolvedType) _resolveDefines(symbol) _resolveAnnotations(symbol) # Run post-annotation checks if symbol.resolvedType != .DYNAMIC && symbol.isConst && !symbol.isImported && value == null && symbol.kind != .VARIABLE_ENUM_OR_FLAGS && symbol.kind != .VARIABLE_INSTANCE { _log.semanticErrorConstMissingValue(symbol.range, symbol.name) } } def _resolveVariable(symbol VariableSymbol) { _initializeSymbol(symbol) if symbol.value != null { _resolveAsParameterizedExpressionWithConversion(symbol.value, symbol.scope, symbol.resolvedType) } # Default-initialize variables else if symbol.kind != .VARIABLE_ARGUMENT && symbol.kind != .VARIABLE_INSTANCE && symbol.kind != .VARIABLE_ENUM_OR_FLAGS { symbol.value = _createDefaultValueForType(symbol.resolvedType, symbol.range) } } def _createDefaultValueForType(type Type, range Range) Node { var unwrapped = _cache.unwrappedType(type) if unwrapped == _cache.intType { return Node.createInt(0).withType(type) } if unwrapped == _cache.doubleType { return Node.createDouble(0).withType(type) } if unwrapped == _cache.boolType { return Node.createBool(false).withType(type) } if unwrapped.isEnumOrFlags { return Node.createCast(_cache.createInt(0), Node.createType(type)).withType(type) } if unwrapped.isParameter { _log.semanticErrorNoDefaultValue(range, type) return null } assert(unwrapped.isReference) return Node.createNull.withType(type) } def _initializeOverloadedFunction(symbol OverloadedFunctionSymbol) { var symbols = symbol.symbols symbol.resolvedType ?= Type.new(.SYMBOL, symbol) # Ensure no two overloads have the same argument types var i = 0 while i < symbols.count { var function = symbols[i] _initializeSymbol(function) symbol.flags |= function.flags & .IS_SETTER var equivalence TypeCache.Equivalence = .NOT_EQUIVALENT var index = -1 for j in 0..i { equivalence = _cache.areFunctionSymbolsEquivalent(function, null, symbols[j], null) if equivalence != .NOT_EQUIVALENT { index = j break } } if index == -1 { i++ continue } var other = symbols[index] var parent = symbol.parent.asObjectSymbol var isFromSameObject = function.parent == other.parent var areReturnTypesDifferent = equivalence == .EQUIVALENT_EXCEPT_RETURN_TYPE && (isFromSameObject || symbol.kind == .OVERLOADED_INSTANCE) # Symbols should be in the base type chain assert(parent.isSameOrHasBaseClass(function.parent)) assert(parent.isSameOrHasBaseClass(other.parent)) # Forbid overloading by return type if !isFromSameObject && areReturnTypesDifferent { var derived = function.parent == parent ? function : other var base = derived == function ? other : function _log.semanticErrorBadOverrideReturnType(derived.range, derived.name, parent.baseType, base.range) if isFromSameObject { function.flags |= .IS_OBSOLETE } } # Allow duplicate function declarations with the same type to merge # as long as there are not two declarations that provide implementations. # Mark the obsolete function as obsolete instead of removing it so it # doesn't potentially mess up iteration in a parent call stack. else if areReturnTypesDifferent || isFromSameObject && function.block != null && other.block != null { _log.semanticErrorDuplicateOverload(function.range, symbol.name, other.range) if isFromSameObject { function.flags |= .IS_OBSOLETE } } # Keep "function" else if isFromSameObject ? function.block != null : function.parent.asObjectSymbol.hasBaseClass(other.parent) { if function.parent == parent && other.parent == parent { function.mergeInformationFrom(other) function.flags |= function.block != null ? other.flags & ~.IS_IMPORTED : other.flags other.flags |= .IS_OBSOLETE } else if !isFromSameObject { function.overridden = other } symbols[index] = function } # Keep "other" else { if function.parent == parent && other.parent == parent { other.flags |= other.block != null ? function.flags & ~.IS_IMPORTED : function.flags other.mergeInformationFrom(function) function.flags |= .IS_OBSOLETE } else if !isFromSameObject { other.overridden = function } } # Remove the symbol after the merge symbols.removeAt(i) } } # Put the guts of the function inside another function because V8 doesn't # optimize functions with try-catch statements def _resolveNodeSwitch(node Node, scope Scope, context Type) { switch node.kind { case .BLOCK { _resolveBlock(node, scope) } case .PAIR { _resolvePair(node, scope, context) } # Statements case .BREAK, .CONTINUE { _resolveJump(node, scope) } case .COMMENT_BLOCK {} case .EXPRESSION { _resolveExpression(node, scope) } case .FOR { _resolveFor(node, scope) } case .FOREACH { _resolveForeach(node, scope) } case .IF { _resolveIf(node, scope) } case .RETURN { _resolveReturn(node, scope) } case .SWITCH { _resolveSwitch(node, scope) } case .THROW { _resolveThrow(node, scope) } case .TRY { _resolveTry(node, scope) } case .VARIABLE { _resolveVariable(node, scope) } case .VARIABLES { _resolveVariables(node, scope) } case .WHILE { _resolveWhile(node, scope) } # Expressions case .ASSIGN_INDEX { _resolveOperatorOverload(node, scope, context) } case .CALL { _resolveCall(node, scope, context) } case .CAST { _resolveCast(node, scope, context) } case .COMPLEMENT, .NEGATIVE, .NOT, .POSITIVE, .POSTFIX_DECREMENT, .POSTFIX_INCREMENT, .PREFIX_DECREMENT, .PREFIX_INCREMENT { _resolveOperatorOverload(node, scope, context) } case .CONSTANT { _resolveConstant(node, scope, context) } case .DOT { _resolveDot(node, scope, context) } case .HOOK { _resolveHook(node, scope, context) } case .INDEX { _resolveOperatorOverload(node, scope, context) } case .INITIALIZER_LIST, .INITIALIZER_MAP { _resolveInitializer(node, scope, context) } case .LAMBDA { _resolveLambda(node, scope, context) } case .LAMBDA_TYPE { _resolveLambdaType(node, scope) } case .NAME { _resolveName(node, scope) } case .NULL { node.resolvedType = .NULL } case .NULL_DOT { _resolveNullDot(node, scope) } case .PARAMETERIZE { _resolveParameterize(node, scope) } case .PARSE_ERROR { node.resolvedType = .DYNAMIC } case .SEQUENCE { _resolveSequence(node, scope, context) } case .STRING_INTERPOLATION { _resolveStringInterpolation(node, scope) } case .SUPER { _resolveSuper(node, scope) } case .TYPE {} case .TYPE_CHECK { _resolveTypeCheck(node, scope) } case .XML { _resolveXML(node, scope) } default { if node.kind.isBinary { _resolveBinary(node, scope, context) } else { assert(false) } } } } def _resolveNode(node Node, scope Scope, context Type) { if node.resolvedType != null { return # Only resolve once } node.resolvedType = .DYNAMIC try { _resolveNodeSwitch(node, scope, context) } # If guard merging failed, reset the type so we'll try again next time catch failure GuardMergingFailure { node.resolvedType = null throw failure } assert(node.resolvedType != null) } def _resolveAsParameterizedType(node Node, scope Scope) { assert(node.kind.isExpression) node.flags |= .SHOULD_EXPECT_TYPE _resolveNode(node, scope, null) _checkIsType(node) _checkIsParameterized(node) } def _resolveAsParameterizedExpression(node Node, scope Scope) { assert(node.kind.isExpression) _resolveNode(node, scope, null) _checkIsInstance(node) _checkIsParameterized(node) } def _resolveAsParameterizedExpressionWithTypeContext(node Node, scope Scope, type Type) { assert(node.kind.isExpression) _resolveNode(node, scope, type) _checkIsInstance(node) _checkIsParameterized(node) } def _resolveAsParameterizedExpressionWithConversion(node Node, scope Scope, type Type) { _resolveAsParameterizedExpressionWithTypeContext(node, scope, type) _checkConversion(node, type, .IMPLICIT) } def _resolveChildrenAsParameterizedExpressions(node Node, scope Scope) { for child = node.firstChild; child != null; child = child.nextSibling { _resolveAsParameterizedExpression(child, scope) } } def _resolveChildrenAsParameterizedExpressionsWithDynamicTypeContext(node Node, scope Scope) { for child = node.firstChild; child != null; child = child.nextSibling { _resolveAsParameterizedExpressionWithTypeContext(child, scope, .DYNAMIC) } } def _checkUnusedExpression(node Node) { var kind = node.kind if kind == .HOOK { _checkUnusedExpression(node.hookTrue) _checkUnusedExpression(node.hookFalse) } else if node.range != null && node.resolvedType != .DYNAMIC && kind != .CALL && !kind.isAssign { _log.semanticWarningUnusedExpression(node.range) } } def _checkIsInstance(node Node) { if node.resolvedType != .DYNAMIC && node.isType { _log.semanticErrorUnexpectedType(node.range, node.resolvedType) node.resolvedType = .DYNAMIC } } def _checkIsType(node Node) { if node.resolvedType != .DYNAMIC && !node.isType { _log.semanticErrorUnexpectedExpression(node.range, node.resolvedType) node.resolvedType = .DYNAMIC } } def _checkIsParameterized(node Node) { if node.resolvedType.parameters != null && !node.resolvedType.isParameterized { _log.semanticErrorUnparameterizedType(node.range, node.resolvedType) node.resolvedType = .DYNAMIC } } def _checkStorage(node Node, scope Scope) { var symbol = node.symbol # Only allow storage to variables if node.kind != .NAME && node.kind != .DOT && (node.kind != .INDEX || node.resolvedType != .DYNAMIC) || symbol != null && !symbol.kind.isVariable { _log.semanticErrorBadStorage(node.range) } # Forbid storage to constants else if symbol != null && symbol.isConst { var function = scope.findEnclosingFunction # Allow assignments to constants inside constructors if function == null || function.symbol.kind != .FUNCTION_CONSTRUCTOR || function.symbol.parent != symbol.parent || symbol.kind != .VARIABLE_INSTANCE { _log.semanticErrorStorageToConstSymbol(node.internalRangeOrRange, symbol.name) } } } def _checkAccess(node Node, range Range, scope Scope) { var symbol = node.symbol if symbol == null { return } # Check access control if symbol.isProtected { while scope != null { if scope.kind == .OBJECT { var object = scope.asObjectScope.symbol if object.isSameOrHasBaseClass(symbol.parent) { return } } scope = scope.parent } _log.semanticErrorAccessViolation(range, symbol.name) } # Deprecation annotations optionally provide a warning message if symbol.isDeprecated { for annotation in symbol.annotations { if annotation.symbol != null && annotation.symbol.fullName == "@deprecated" { var value = annotation.annotationValue if value.kind == .CALL && value.hasTwoChildren { var last = value.lastChild if last.kind == .CONSTANT && last.content.kind == .STRING { _log.append(_log.newWarning(range, last.content.asString)) return } } } } _log.semanticWarningDeprecatedUsage(range, symbol.name) } } def _checkConversion(node Node, to Type, kind ConversionKind) { var from = node.resolvedType assert(from != null) assert(to != null) # The "dynamic" type is a hole in the type system if from == .DYNAMIC || to == .DYNAMIC { return } # No conversion is needed for identical types if from == to { return } # The implicit conversion must be valid if kind == .IMPLICIT && !_cache.canImplicitlyConvert(from, to) || kind == .EXPLICIT && !_cache.canExplicitlyConvert(from, to) { _log.semanticErrorIncompatibleTypes(node.range, from, to, _cache.canExplicitlyConvert(from, to)) node.resolvedType = .DYNAMIC return } # Make the implicit conversion explicit for convenience later on if kind == .IMPLICIT { node.become(Node.createCast(node.cloneAndStealChildren, Node.createType(to)).withType(to).withRange(node.range)) } } def _resolveAnnotation(node Node, symbol Symbol) bool { var value = node.annotationValue var test = node.annotationTest _resolveNode(value, symbol.scope, null) if test != null { _resolveAsParameterizedExpressionWithConversion(test, symbol.scope, _cache.boolType) } # Terminate early when there were errors if value.symbol == null { return false } # Make sure annotations have the arguments they need if value.kind != .CALL { _log.semanticErrorArgumentCount(value.range, value.symbol.resolvedType.argumentTypes.count, 0, value.symbol.name, value.symbol.range) return false } # Ensure all arguments are constants var isValid = true for child = value.callValue.nextSibling; child != null; child = child.nextSibling { isValid = isValid && _recursivelyResolveAsConstant(child) } if !isValid { return false } # Only store symbols for annotations with the correct arguments for ease of use node.symbol = value.symbol # Apply built-in annotation logic var flag = _annotationSymbolFlags.get(value.symbol.fullName, 0) if flag != 0 { switch flag { case .IS_DEPRECATED {} case .IS_ENTRY_POINT { isValid = symbol.kind == .FUNCTION_GLOBAL } case .IS_EXPORTED { isValid = !symbol.isImported } case .IS_IMPORTED { isValid = !symbol.isExported && (!symbol.kind.isFunction || symbol.asFunctionSymbol.block == null) } case .IS_INLINING_FORCED, .IS_INLINING_PREVENTED, .IS_PREFERRED { isValid = symbol.kind.isFunction } case .IS_RENAMED {} case .IS_SKIPPED { isValid = symbol.kind.isFunction && symbol.resolvedType.returnType == null } case .SHOULD_SPREAD { isValid = symbol.kind == .FUNCTION_ANNOTATION } } if flag == .IS_INLINING_FORCED { _options.isAlwaysInlinePresent = true } if !isValid { _log.semanticErrorInvalidAnnotation(value.range, value.symbol.name, symbol.name) return false } # Don't add an annotation when the test expression is false if test != null && _recursivelyResolveAsConstant(test) && test.isFalse { return false } # Only warn about duplicate annotations after checking the test expression if (symbol.flags & flag) != 0 { if (symbol.parent.flags & flag & (.IS_IMPORTED | .IS_EXPORTED)) != 0 { _log.semanticWarningRedundantAnnotation(value.range, value.symbol.name, symbol.name, symbol.parent.name) } else { _log.semanticWarningDuplicateAnnotation(value.range, value.symbol.name, symbol.name) } } symbol.flags |= flag # Store the new name for later if flag == .IS_RENAMED && value.hasTwoChildren { symbol.rename = value.lastChild.asString } } return true } def _recursivelyResolveAsConstant(node Node) bool { _constantFolder.foldConstants(node) if node.kind != .CONSTANT { _log.semanticErrorExpectedConstant(node.range) return false } return true } def _resolveBlock(node Node, scope Scope) { assert(node.kind == .BLOCK) _controlFlow.pushBlock(node) for child = node.firstChild, next Node = null; child != null; child = next { var prev = child.previousSibling next = child.nextSibling # There is a well-known ambiguity in languages like JavaScript where # a return statement followed by a newline and a value can either be # parsed as a single return statement with a value or as two # statements, a return statement without a value and an expression # statement. Luckily, we're better off than JavaScript since we know # the type of the function. Parse a single statement in a non-void # function but two statements in a void function. if child.kind == .RETURN && next != null && child.returnValue == null && next.kind == .EXPRESSION { var function = scope.findEnclosingFunctionOrLambda.symbol if function.kind != .FUNCTION_CONSTRUCTOR && function.resolvedType.returnType != null { var statement = next.remove var value = statement.expressionValue.remove child.appendChild(value) var trailing = Comment.lastTrailingComment(statement.comments) var notTrailing = Comment.withoutLastTrailingComment(statement.comments) if trailing != null { child.comments = Comment.concat(child.comments, [trailing]) } value.comments = Comment.concat(notTrailing, value.comments) next = child.nextSibling assert(child.returnValue != null) } } _resolveNode(child, scope, null) # Visit control flow from the previous node to the next node, which # should handle the case where this node was replaced with something for n = prev != null ? prev.nextSibling : node.firstChild; n != next; n = n.nextSibling { _controlFlow.visitStatementInPostOrder(n) } # Stop now if the child was removed if child.parent == null { continue } # The "@skip" annotation removes function calls after type checking if child.kind == .EXPRESSION { var value = child.expressionValue if value.kind == .CALL && value.symbol != null && value.symbol.isSkipped { child.remove } } } _controlFlow.popBlock(node) } def _resolvePair(node Node, scope Scope, context Type) { # Allow resolving a pair with a type context of "dynamic" to # deliberately silence errors around needing type context if context == .DYNAMIC { _resolveAsParameterizedExpressionWithConversion(node.firstValue, scope, context) _resolveAsParameterizedExpressionWithConversion(node.secondValue, scope, context) return } _resolveAsParameterizedExpression(node.firstValue, scope) _resolveAsParameterizedExpression(node.secondValue, scope) } def _resolveJump(node Node, scope Scope) { if scope.findEnclosingLoop == null { _log.semanticErrorBadJump(node.range, node.kind == .BREAK ? "break" : "continue") } } def _resolveExpressionOrImplicitReturn(node Node, value Node, scope Scope) { var hook = _sinkNullDotIntoHook(value, scope, null) # Turn top-level "?." expressions into if statements if hook != null { var test = hook.hookTest var yes = hook.hookTrue var block = Node.createBlock.appendChild(Node.createExpression(yes.remove).withRange(yes.range)).withRange(yes.range) node.become(Node.createIf(test.remove, block, null).withRange(node.range).withComments(node.comments)) _resolveNode(node, scope, null) } # Turn top-level "?=" expressions into if statements else if value.kind == .ASSIGN_NULL { var left = value.binaryLeft var right = value.binaryRight _resolveAsParameterizedExpressionWithTypeContext(left, scope, null) _checkStorage(left, scope) var test = Node.createBinary(.EQUAL, _extractExpressionForAssignment(left, scope), Node.createNull).withRange(left.range) var assign = Node.createBinary(.ASSIGN, left.remove, right.remove).withRange(node.range).withFlags(.WAS_ASSIGN_NULL) var block = Node.createBlock.appendChild(Node.createExpression(assign).withRange(node.range)).withRange(node.range) node.become(Node.createIf(test, block, null).withRange(node.range).withComments(node.comments)) _resolveNode(node, scope, null) } # Normal expression statement else { _resolveAsParameterizedExpression(value, scope) } } def _resolveExpression(node Node, scope Scope) { var value = node.expressionValue _resolveExpressionOrImplicitReturn(node, value, scope) # Only continue this didn't get turned into an if statement due to a top-level "?." or "?=" expression if node.kind == .EXPRESSION { _checkUnusedExpression(value) } } def _resolveFor(node Node, scope Scope) { var setup = node.forSetup var update = node.forUpdate scope = LocalScope.new(scope, .LOOP) if setup.kind == .VARIABLES { _resolveNode(setup, scope, null) # All for loop variables must have the same type. This is a requirement # for one-to-one code emission in the languages we want to target. var type = setup.firstChild.symbol.resolvedType for child = setup.firstChild.nextSibling; child != null; child = child.nextSibling { var symbol = child.symbol if symbol.resolvedType != type { _log.semanticErrorForLoopDifferentType(symbol.range, symbol.name, symbol.resolvedType, type) break } } } else { _resolveAsParameterizedExpression(setup, scope) } _resolveAsParameterizedExpressionWithConversion(node.forTest, scope, _cache.boolType) _resolveAsParameterizedExpression(update, scope) if update.kind == .SEQUENCE { for child = update.firstChild; child != null; child = child.nextSibling { _checkUnusedExpression(child) } } _resolveBlock(node.forBlock, scope) } def _resolveForeach(node Node, scope Scope) { var type Type = .DYNAMIC scope = LocalScope.new(scope, .LOOP) var value = node.foreachValue _resolveAsParameterizedExpression(value, scope) # Support "for i in 0..10" if value.kind == .PAIR { var first = value.firstValue var second = value.secondValue type = _cache.intType _checkConversion(first, _cache.intType, .IMPLICIT) _checkConversion(second, _cache.intType, .IMPLICIT) # The ".." syntax only counts up, unlike CoffeeScript if first.isInt && second.isInt && first.asInt >= second.asInt { _log.semanticWarningEmptyRange(value.range) } } # Support "for i in [1, 2, 3]" else if _cache.isList(value.resolvedType) { type = value.resolvedType.substitutions[0] } # Anything else is an error else if value.resolvedType != .DYNAMIC { _log.semanticErrorBadForValue(value.range, value.resolvedType) } # Special-case symbol initialization with the type var symbol = node.symbol.asVariableSymbol scope.asLocalScope.define(symbol, _log) _localVariableStatistics[symbol.id] = LocalVariableStatistics.new(symbol) symbol.initializeWithType(type) symbol.flags |= .IS_CONST | .IS_LOOP_VARIABLE _resolveBlock(node.foreachBlock, scope) # Collect foreach loops and convert them in another pass _foreachLoops.append(node) } def _resolveIf(node Node, scope Scope) { var test = node.ifTest var ifFalse = node.ifFalse _resolveAsParameterizedExpressionWithConversion(test, scope, _cache.boolType) _resolveBlock(node.ifTrue, LocalScope.new(scope, .NORMAL)) if ifFalse != null { _resolveBlock(ifFalse, LocalScope.new(scope, .NORMAL)) } } def _resolveReturn(node Node, scope Scope) { var value = node.returnValue var function = scope.findEnclosingFunctionOrLambda.symbol var returnType = function.kind != .FUNCTION_CONSTRUCTOR ? function.resolvedType.returnType : null # Check for a returned value if value == null { if returnType != null && !function.isDynamicLambda { _log.semanticErrorExpectedReturnValue(node.range, returnType) } return } # Check the type of the returned value if returnType != null { _resolveAsParameterizedExpressionWithConversion(value, scope, returnType) if function.shouldInferReturnType && _isCallReturningVoid(value) { node.kind = .EXPRESSION } return } # If there's no return type, still check for other errors if node.isImplicitReturn { _resolveExpressionOrImplicitReturn(node, value, scope) # Stop now if this got turned into an if statement due to a top-level "?." or "?=" expression if node.kind != .RETURN { return } } else { _resolveAsParameterizedExpression(value, scope) } # Lambdas without a return type or an explicit "return" statement get special treatment if !node.isImplicitReturn { _log.semanticErrorUnexpectedReturnValue(value.range) return } # Check for a return value of type "void" if !function.shouldInferReturnType || _isCallReturningVoid(value) { _checkUnusedExpression(value) node.kind = .EXPRESSION return } # Check for an invalid return type var type = value.resolvedType if !_isValidVariableType(type) { _log.semanticErrorBadReturnType(value.range, type) node.kind = .EXPRESSION return } # Mutate the return type to the type from the returned value function.returnType = Node.createType(type) function.resolvedType.returnType = type } def _resolveSwitch(node Node, scope Scope) { var duplicateCases IntMap = {} var mustEnsureConstantIntegers = _options.target.requiresIntegerSwitchStatements var allValuesAreIntegers = true var value = node.switchValue _resolveAsParameterizedExpression(value, scope) for child = value.nextSibling; child != null; child = child.nextSibling { var block = child.caseBlock # Resolve all case values for caseValue = child.firstChild; caseValue != block; caseValue = caseValue.nextSibling { _resolveAsParameterizedExpressionWithConversion(caseValue, scope, value.resolvedType) var symbol = caseValue.symbol var integer = 0 # Check for a constant variable, which may just be read-only with a # value determined at runtime if symbol != null && (mustEnsureConstantIntegers ? symbol.kind == .VARIABLE_ENUM_OR_FLAGS : symbol.kind.isVariable && symbol.isConst) { var constant = _constantFolder.constantForSymbol(symbol.asVariableSymbol) if constant == null || constant.kind != .INT { allValuesAreIntegers = false continue } integer = constant.asInt } # Fall back to the constant folder only as a last resort because it # mutates the syntax tree and harms readability else { _constantFolder.foldConstants(caseValue) if !caseValue.isInt { allValuesAreIntegers = false continue } integer = caseValue.asInt } # Duplicate case detection var previous = duplicateCases.get(integer, null) if previous != null { _log.semanticErrorDuplicateCase(caseValue.range, previous) } else { duplicateCases[integer] = caseValue.range } } # The default case must be last, makes changing into an if chain easier later if child.hasOneChild && child.nextSibling != null { _log.semanticErrorDefaultCaseNotLast(child.range) } _resolveBlock(block, LocalScope.new(scope, .NORMAL)) } # Fall back to an if statement if the case values aren't compile-time # integer constants, which is requried by many language targets if !allValuesAreIntegers && mustEnsureConstantIntegers { _convertSwitchToIfChain(node, scope) } } def _resolveThrow(node Node, scope Scope) { var value = node.throwValue _resolveAsParameterizedExpression(value, scope) } def _resolveVariable(node Node, scope Scope) { var symbol = node.symbol.asVariableSymbol scope.asLocalScope.define(symbol, _log) _localVariableStatistics[symbol.id] = LocalVariableStatistics.new(symbol) _resolveVariable(symbol) # Make sure to parent any created values under the variable node if !node.hasChildren && symbol.value != null { node.appendChild(symbol.value) } } def _resolveVariables(node Node, scope Scope) { for child = node.firstChild; child != null; child = child.nextSibling { _resolveVariable(child, scope) } } def _resolveTry(node Node, scope Scope) { var tryBlock = node.tryBlock var finallyBlock = node.finallyBlock _resolveBlock(tryBlock, LocalScope.new(scope, .NORMAL)) # Bare try statements catch all thrown values if node.hasOneChild { node.appendChild(Node.createCatch(null, Node.createBlock)) } # Check catch statements for child = tryBlock.nextSibling; child != finallyBlock; child = child.nextSibling { var childScope = LocalScope.new(scope, .NORMAL) if child.symbol != null { var symbol = child.symbol.asVariableSymbol childScope.define(symbol, _log) _resolveVariable(symbol) } _resolveBlock(child.catchBlock, childScope) } # Check finally block if finallyBlock != null { _resolveBlock(finallyBlock, LocalScope.new(scope, .NORMAL)) } } def _resolveWhile(node Node, scope Scope) { _resolveAsParameterizedExpressionWithConversion(node.whileTest, scope, _cache.boolType) _resolveBlock(node.whileBlock, LocalScope.new(scope, .LOOP)) } def _resolveCall(node Node, scope Scope, context Type) { var hook = _sinkNullDotIntoHook(node, scope, context) if hook != null { node.become(hook) _resolveAsParameterizedExpressionWithTypeContext(node, scope, context) return } var value = node.callValue # Take argument types from call argument values for immediately-invoked # function expressions: # # var foo = ((a, b) => a + b)(1, 2) # var bar int = ((a, b) => { return a + b })(1, 2) # if value.kind == .LAMBDA { var symbol = value.symbol.asFunctionSymbol var arguments = symbol.arguments if node.childCount == arguments.count + 1 { var child = value.nextSibling for i in 0..arguments.count { var argument = arguments[i] if argument.type == null { _resolveAsParameterizedExpression(child, scope) argument.type = Node.createType(child.resolvedType) } child = child.nextSibling } if context != null && symbol.returnType == null { symbol.returnType = Node.createType(context) } } } _resolveAsParameterizedExpression(value, scope) var type = value.resolvedType switch type.kind { # Each function has its own type for simplicity case .SYMBOL { if _resolveSymbolCall(node, scope, type) { return } } # Lambda types look like "fn(int, int) int" case .LAMBDA { if _resolveFunctionCall(node, scope, type) { return } } # Can't call other types (the null type, for example) default { if type != .DYNAMIC { _log.semanticErrorInvalidCall(node.internalRangeOrRange, value.resolvedType) } } } # If there was an error, resolve the arguments to check for further # errors but use a dynamic type context to avoid introducing errors for child = value.nextSibling; child != null; child = child.nextSibling { _resolveAsParameterizedExpressionWithConversion(child, scope, .DYNAMIC) } } def _resolveSymbolCall(node Node, scope Scope, type Type) bool { var symbol = type.symbol # Getters are called implicitly, so explicitly calling one is an error. # This error prevents a getter returning a lambda which is then called. # To overcome this, wrap the call in parentheses: # # def foo fn() # # def bar { # foo() # Error # (foo)() # Correct # } # if symbol.isGetter && _isCallValue(node) && !node.callValue.isInsideParentheses { if symbol.resolvedType.returnType != null && symbol.resolvedType.returnType.kind == .LAMBDA { _log.semanticErrorGetterRequiresWrap(node.range, symbol.name, symbol.range) } else { _log.semanticErrorGetterCalledTwice(node.parent.internalRangeOrRange, symbol.name, symbol.range) } _resolveChildrenAsParameterizedExpressionsWithDynamicTypeContext(node, scope) return false } # Check for calling a function directly if symbol.kind.isFunction { return _resolveFunctionCall(node, scope, type) } # Check for calling a set of functions, must not be ambiguous if symbol.kind.isOverloadedFunction { return _resolveOverloadedFunctionCall(node, scope, type) } # Can't call other symbols _log.semanticErrorInvalidCall(node.internalRangeOrRange, node.callValue.resolvedType) return false } def _resolveFunctionCall(node Node, scope Scope, type Type) bool { var function = type.symbol?.asFunctionSymbol var expected = type.argumentTypes.count var count = node.childCount - 1 node.symbol = function # Use the return type even if there were errors if type.returnType != null { node.resolvedType = type.returnType } # There is no "void" type, so make sure this return value isn't used else if _isExpressionUsed(node) { if function != null { _log.semanticErrorUseOfVoidFunction(node.range, function.name, function.range) } else { _log.semanticErrorUseOfVoidLambda(node.range) } } # Check argument count if expected != count { _log.semanticErrorArgumentCount(node.internalRangeOrRange, expected, count, function?.name, function?.range) _resolveChildrenAsParameterizedExpressionsWithDynamicTypeContext(node, scope) return false } # Check argument types var value = node.firstChild var child = value.nextSibling for argumentType in type.argumentTypes { _resolveAsParameterizedExpressionWithConversion(child, scope, argumentType) child = child.nextSibling } # Forbid constructing an abstract type if function != null && function.kind == .FUNCTION_CONSTRUCTOR && value.kind != .SUPER { _checkInterfacesAndAbstractStatus(function.parent.asObjectSymbol) var reason = function.parent.asObjectSymbol.isAbstractBecauseOf if reason != null { _log.semanticErrorAbstractNew(node.internalRangeOrRange, function.parent.resolvedType, reason.range, reason.name) } } # Replace overloaded symbols with the chosen overload if value.kind == .PARAMETERIZE { value = value.parameterizeValue } if function != null && value.symbol != null && value.symbol.kind.isOverloadedFunction && function in value.symbol.asOverloadedFunctionSymbol.symbols { value.symbol = function value.resolvedType = type } return true } def _resolveOverloadedFunction(range Range, node Node, scope Scope, symbolType Type) Type { var overloaded = symbolType.symbol.asOverloadedFunctionSymbol var firstArgument = node.firstChild.nextSibling var count = node.childCount - 1 var candidates List = [] # Filter by argument length and substitute using the current type environment for symbol in overloaded.symbols { if symbol.arguments.count == count || overloaded.symbols.count == 1 { candidates.append(_cache.substitute(symbol.resolvedType, symbolType.environment)) } } # Check for matches if candidates.isEmpty { _log.semanticErrorNoMatchingOverload(range, overloaded.name, count, null) return null } # Check for an unambiguous match if candidates.count == 1 { return candidates[0] } # First filter by syntactic structure impossibilities. This helps break # the chicken-and-egg problem of needing to resolve argument types to # get a match and needing a match to resolve argument types. For example, # a list literal needs type context to resolve correctly. var index = 0 while index < candidates.count { var child = firstArgument for type in candidates[index].argumentTypes { var kind = child.kind if kind == .NULL && !type.isReference || kind == .INITIALIZER_LIST && _findMember(type, "[new]") == null && _findMember(type, "[...]") == null || kind == .INITIALIZER_MAP && _findMember(type, "{new}") == null && _findMember(type, "{...}") == null || kind == .LAMBDA && (type.kind != .LAMBDA || type.argumentTypes.count != child.symbol.asFunctionSymbol.arguments.count) { candidates.removeAt(index) index-- break } child = child.nextSibling } index++ } # Check for an unambiguous match if candidates.count == 1 { return candidates[0] } # If that still didn't work, resolve the arguments without type context for child = firstArgument; child != null; child = child.nextSibling { _resolveAsParameterizedExpression(child, scope) } # Try again, this time discarding all implicit conversion failures index = 0 while index < candidates.count { var child = firstArgument for type in candidates[index].argumentTypes { if !_cache.canImplicitlyConvert(child.resolvedType, type) { candidates.removeAt(index) index-- break } child = child.nextSibling } index++ } # Check for an unambiguous match if candidates.count == 1 { return candidates[0] } # Extract argument types for an error if there is one var childTypes List = [] for child = firstArgument; child != null; child = child.nextSibling { childTypes.append(child.resolvedType) } # Give up without a match if candidates.isEmpty { _log.semanticErrorNoMatchingOverload(range, overloaded.name, count, childTypes) return null } # If that still didn't work, try type equality for type in candidates { var isMatch = true for i in 0..count { if childTypes[i] != type.argumentTypes[i] { isMatch = false break } } if isMatch { return type } } # If that still didn't work, try picking the preferred overload var firstPreferred Type = null var secondPreferred Type = null for type in candidates { if type.symbol.isPreferred { secondPreferred = firstPreferred firstPreferred = type } } # Check for a single preferred overload if firstPreferred != null && secondPreferred == null { return firstPreferred } # Give up since the overload is ambiguous _log.semanticErrorAmbiguousOverload(range, overloaded.name, count, childTypes) return null } def _resolveOverloadedFunctionCall(node Node, scope Scope, type Type) bool { var match = _resolveOverloadedFunction(node.callValue.range, node, scope, type) if match != null && _resolveFunctionCall(node, scope, match) { _checkAccess(node, node.callValue.internalRangeOrRange, scope) return true } return false } def _resolveCast(node Node, scope Scope, context Type) { var value = node.castValue var type = node.castType var neededTypeContext = _needsTypeContext(value) _resolveAsParameterizedType(type, scope) _resolveAsParameterizedExpressionWithTypeContext(value, scope, type.resolvedType) _checkConversion(value, type.resolvedType, .EXPLICIT) node.resolvedType = type.resolvedType # Warn about unnecessary casts var range = node.internalRangeOrRange if range != null && type.resolvedType != .DYNAMIC && value.resolvedType != .DYNAMIC && !neededTypeContext && ( value.resolvedType == type.resolvedType || context == type.resolvedType && _cache.canImplicitlyConvert(value.resolvedType, type.resolvedType)) { _log.semanticWarningExtraCast(Range.span(range, type.range), value.resolvedType, type.resolvedType) } } def _resolveConstant(node Node, scope Scope, context Type) { switch node.content.kind { case .BOOL { node.resolvedType = _cache.boolType } case .DOUBLE { node.resolvedType = _cache.doubleType } case .STRING { node.resolvedType = _cache.stringType } # The literal "0" represents the empty set for "flags" types case .INT { node.resolvedType = context != null && context.isFlags && node.asInt == 0 ? context : _cache.intType } default { assert(false) } } } def _findMember(type Type, name string) Symbol { if type.kind == .SYMBOL { var symbol = type.symbol if symbol.kind.isObject { var member = symbol.asObjectSymbol.members.get(name, null) if member != null { _initializeSymbol(member) return member } } } return null } def _sinkNullDotIntoHook(node Node, scope Scope, context Type) Node { var nullDot = node # Search down the chain of dot accesses and calls for "?." expression while true { if nullDot.kind == .DOT && nullDot.dotTarget != null { nullDot = nullDot.dotTarget } else if nullDot.kind == .CALL { nullDot = nullDot.callValue } else { break } } # Stop if this isn't a "?." expression after all if nullDot.kind != .NULL_DOT { return null } # Wrap everything in a null check var target = nullDot.dotTarget.remove _resolveAsParameterizedExpression(target, scope) var test = Node.createBinary(.NOT_EQUAL, _extractExpression(target, scope), Node.createNull.withRange(nullDot.internalRange)).withRange(target.range) var dot = Node.createDot(target, nullDot.asString).withRange(nullDot.range).withInternalRange(nullDot.internalRange) var hook = Node.createHook(test, dot, Node.createNull.withRange(nullDot.internalRangeOrRange)).withRange(nullDot.range) nullDot.become(hook.hookTrue.clone) node.resolvedType = null # This is necessary to trigger the resolve below hook.hookTrue.become(node.cloneAndStealChildren) return hook } enum CompletionCheck { NORMAL INSTANCE_ONLY GLOBAL_ONLY } def _checkForMemberCompletions(type Type, range Range, name string, check CompletionCheck) { assert(type != null) var completionContext = _options.completionContext if completionContext != null && range != null && range.source == completionContext.source && range.touches(completionContext.index) && type.kind == .SYMBOL && type.symbol.kind.isObject { var prefix = name.slice(0, completionContext.index - range.start) var object = type.symbol.asObjectSymbol _initializeSymbol(object) completionContext.range = range for member in object.members.values { var isOnInstances = member.kind.isOnInstances if (check == .INSTANCE_ONLY ? isOnInstances : check == .GLOBAL_ONLY ? !isOnInstances : true) && _matchCompletion(member, prefix) { _initializeSymbol(member) completionContext.addCompletion(member) } } } } def _checkForScopeCompletions(scope Scope, range Range, name string, thisObject ObjectSymbol) { var completionContext = _options.completionContext if completionContext != null && range != null && range.source == completionContext.source && range.touches(completionContext.index) { var prefix = name.slice(0, completionContext.index - range.start) completionContext.range = range while scope != null { switch scope.kind { case .OBJECT { var object = scope.asObjectScope.symbol for symbol in object.members.values { if _matchCompletion(symbol, prefix) && (!symbol.kind.isOnInstances || object == thisObject) { _initializeSymbol(symbol) completionContext.addCompletion(symbol) } } } case .FUNCTION { for symbol in scope.asFunctionScope.parameters.values { if _matchCompletion(symbol, prefix) { _initializeSymbol(symbol) completionContext.addCompletion(symbol) } } } case .LOCAL { for symbol in scope.asLocalScope.locals.values { if _matchCompletion(symbol, prefix) { _initializeSymbol(symbol) completionContext.addCompletion(symbol) } } } } scope = scope.parent } } } def _resolveDot(node Node, scope Scope, context Type) { var hook = _sinkNullDotIntoHook(node, scope, context) if hook != null { node.become(hook) _resolveAsParameterizedExpressionWithTypeContext(node, scope, context) return } var target = node.dotTarget var name = node.asString # Resolve the target if present if target != null { _resolveNode(target, scope, null) # Support IDE code completion _checkForMemberCompletions(target.resolvedType, node.internalRange, name, target.isType ? .GLOBAL_ONLY : .INSTANCE_ONLY) } # Ignore parse errors (the syntax tree is kept around for code completion) if name == "" { return } # Infer the target from the type context if it's omitted if target == null { if context == null { _log.semanticErrorMissingDotContext(node.range, name) return } target = Node.createType(context) node.appendChild(target) assert(node.dotTarget == target) # Support IDE code completion _checkForMemberCompletions(target.resolvedType, node.internalRange, name, target.isType ? .GLOBAL_ONLY : .INSTANCE_ONLY) } # Search for a setter first, then search for a normal member var symbol Symbol = null if _shouldCheckForSetter(node) { symbol = _findMember(target.resolvedType, name + "=") } if symbol == null { symbol = _findMember(target.resolvedType, name) if symbol == null { # Symbol lookup failure if target.resolvedType != .DYNAMIC { var type = target.resolvedType var correction = type.kind != .SYMBOL || !type.symbol.kind.isObject ? null : type.symbol.asObjectSymbol.scope.findWithFuzzyMatching(name, target.isType ? .GLOBAL_ONLY : .INSTANCE_ONLY, .SELF_ONLY) _reportGuardMergingFailure(node) _log.semanticErrorUnknownMemberSymbol(node.internalRangeOrRange, name, target.resolvedType, correction?.name, correction?.range) } # Dynamic symbol access else { # Make sure to warn when accessing a symbol at statement level without using it if node.parent != null && node.parent.kind == .EXPRESSION { _log.semanticWarningUnusedExpression(node.range) } # "dynamic.foo" => "foo" if target.kind == .TYPE { node.kind = .NAME node.removeChildren } # "Foo.new" => "Foo.new()" # "Foo.new()" => "Foo.new()" else if name == "new" && !_isCallValue(node) { node.become(Node.createCall(node.cloneAndStealChildren).withType(.DYNAMIC).withRange(node.range)) } } return } } # Forbid referencing a base class global or constructor function from a derived class if _isBaseGlobalReference(target.resolvedType.symbol, symbol) { _log.semanticErrorUnknownMemberSymbol(node.range, name, target.resolvedType, symbol.fullName, symbol.range) return } var isType = target.isType var needsType = !symbol.kind.isOnInstances # Make sure the global/instance context matches the intended usage if isType { if !needsType { _log.semanticErrorMemberUnexpectedInstance(node.internalRangeOrRange, symbol.name) } else if symbol.kind.isFunctionOrOverloadedFunction { _checkIsParameterized(target) } else if target.resolvedType.isParameterized { _log.semanticErrorParameterizedType(target.range, target.resolvedType) } } else if needsType { _log.semanticErrorMemberUnexpectedGlobal(node.internalRangeOrRange, symbol.name) } # Always access referenced globals directly if !_options.stopAfterResolve && symbol.kind.isGlobalReference { node.kind = .NAME node.removeChildren } node.symbol = symbol node.resolvedType = _cache.substitute(symbol.resolvedType, target.resolvedType.environment) _automaticallyCallGetter(node, scope) } def _resolveHook(node Node, scope Scope, context Type) { _resolveAsParameterizedExpressionWithConversion(node.hookTest, scope, _cache.boolType) var trueValue = node.hookTrue var falseValue = node.hookFalse # Use the type context from the parent if context != null { _resolveAsParameterizedExpressionWithConversion(trueValue, scope, context) _resolveAsParameterizedExpressionWithConversion(falseValue, scope, context) node.resolvedType = context } # Find the common type from both branches else { _resolveAsParameterizedExpression(trueValue, scope) _resolveAsParameterizedExpression(falseValue, scope) var commonType = _cache.commonImplicitType(trueValue.resolvedType, falseValue.resolvedType) # Insert casts if needed since some targets can't perform this type inference if commonType != null { _checkConversion(trueValue, commonType, .IMPLICIT) _checkConversion(falseValue, commonType, .IMPLICIT) node.resolvedType = commonType } else { _log.semanticErrorNoCommonType(Range.span(trueValue.range, falseValue.range), trueValue.resolvedType, falseValue.resolvedType) } } # Check for likely bugs where both branches look the same if trueValue.looksTheSameAs(falseValue) { _log.semanticWarningIdenticalOperands(Range.span(trueValue.range, falseValue.range), node.wasNullJoin ? "??" : ":") } } def _resolveInitializer(node Node, scope Scope, context Type) { # Make sure to resolve the children even if the initializer is invalid if context != null { if context == .DYNAMIC || !_resolveInitializerWithContext(node, scope, context) { _resolveChildrenAsParameterizedExpressionsWithDynamicTypeContext(node, scope) } return } # First pass: only children with type context, second pass: all children for pass in 0..2 { switch node.kind { case .INITIALIZER_LIST { var type Type = null # Resolve all children for this pass for child = node.firstChild; child != null; child = child.nextSibling { if pass != 0 || !_needsTypeContext(child) { _resolveAsParameterizedExpression(child, scope) type = _mergeCommonType(type, child) } } # Resolve remaining children using the type context if valid if type != null && _isValidVariableType(type) { _resolveInitializerWithContext(node, scope, _cache.createListType(type)) return } } case .INITIALIZER_MAP { var keyType Type = null var valueType Type = null # Resolve all children for this pass for child = node.firstChild; child != null; child = child.nextSibling { var key = child.firstValue var value = child.secondValue if pass != 0 || !_needsTypeContext(key) { _resolveAsParameterizedExpression(key, scope) keyType = _mergeCommonType(keyType, key) } if pass != 0 || !_needsTypeContext(value) { _resolveAsParameterizedExpression(value, scope) valueType = _mergeCommonType(valueType, value) } } # Resolve remaining children using the type context if valid if keyType != null && valueType != null && _isValidVariableType(valueType) { assert(!_cache.isEquivalentToInt(keyType) || !_cache.isEquivalentToString(keyType)) if _cache.isEquivalentToInt(keyType) { _resolveInitializerWithContext(node, scope, _cache.createIntMapType(valueType)) return } if _cache.isEquivalentToString(keyType) { _resolveInitializerWithContext(node, scope, _cache.createStringMapType(valueType)) return } } } } } _log.semanticErrorInitializerTypeInferenceFailed(node.range) _resolveChildrenAsParameterizedExpressionsWithDynamicTypeContext(node, scope) } def _resolveInitializerWithContext(node Node, scope Scope, context Type) bool { var isList = node.kind == .INITIALIZER_LIST var new = _findMember(context, isList ? "[new]" : "{new}") var add = _findMember(context, isList ? "[...]" : "{...}") # Special-case imported literals to prevent an infinite loop for list literals if add != null && add.isImported { var function = add.asFunctionSymbol if function.arguments.count == (isList ? 1 : 2) { var functionType = _cache.substitute(function.resolvedType, context.environment) for child = node.firstChild; child != null; child = child.nextSibling { if child.kind == .PAIR { _resolveAsParameterizedExpressionWithConversion(child.firstValue, scope, functionType.argumentTypes[0]) _resolveAsParameterizedExpressionWithConversion(child.secondValue, scope, functionType.argumentTypes[1]) child.resolvedType = .DYNAMIC } else { _resolveAsParameterizedExpressionWithConversion(child, scope, functionType.argumentTypes[0]) } } node.resolvedType = context return true } } # Use simple call chaining when there's an add operator present if add != null { var type = Node.createType(context).withRange(node.range).withFlags(.IS_IGNORED_BY_IDE) var chain = Node.createDot(type, new != null ? new.name : "new").withRange(node.range).withFlags(.IS_IGNORED_BY_IDE) while node.hasChildren { var child = node.firstChild.remove var dot = Node.createDot(chain, add.name).withRange(child.range).withFlags(.IS_IGNORED_BY_IDE) chain = Node.createCall(dot).withRange(child.range).withFlags(.IS_IGNORED_BY_IDE) if child.kind == .PAIR { chain.appendChildrenFrom(child) } else { chain.appendChild(child) } } node.become(chain) _resolveAsParameterizedExpressionWithConversion(node, scope, context) return true } # Make sure there's a constructor to call if new == null { # Avoid emitting an extra error when the constructor doesn't have the right type: # # def main Foo { # return [] # } # # class Foo { # def [new](x int) {} # } # if !node.isInitializerExpansion { _log.semanticErrorInitializerTypeInferenceFailed(node.range) } return false } # Avoid infinite expansion if node.isInitializerExpansion { _log.semanticErrorInitializerRecursiveExpansion(node.range, new.range) return false } var dot = Node.createDot(Node.createType(context).withRange(node.range), new.name).withRange(node.range) # Call the initializer constructor if node.kind == .INITIALIZER_MAP { var firstValues = Node.createList.withFlags(.IS_INITIALIZER_EXPANSION).withRange(node.range) var secondValues = Node.createList.withFlags(.IS_INITIALIZER_EXPANSION).withRange(node.range) for child = node.firstChild; child != null; child = child.nextSibling { var first = child.firstValue var second = child.secondValue firstValues.appendChild(first.remove) secondValues.appendChild(second.remove) } node.become(Node.createCall(dot).withRange(node.range).appendChild(firstValues).appendChild(secondValues)) } else { var values = Node.createList.withFlags(.IS_INITIALIZER_EXPANSION).withRange(node.range) node.become(Node.createCall(dot).withRange(node.range).appendChild(values.appendChildrenFrom(node))) } _resolveAsParameterizedExpressionWithConversion(node, scope, context) return true } def _mergeCommonType(commonType Type, child Node) Type { if commonType == null || child.resolvedType == .DYNAMIC { return child.resolvedType } var result = _cache.commonImplicitType(commonType, child.resolvedType) if result != null { return result } _log.semanticErrorNoCommonType(child.range, commonType, child.resolvedType) return .DYNAMIC } def _resolveLambda(node Node, scope Scope, context Type) { var symbol = node.symbol.asFunctionSymbol symbol.scope = FunctionScope.new(scope, symbol) # Use type context to implicitly set missing types if context != null && context.kind == .LAMBDA { # Copy over the argument types if they line up if context.argumentTypes.count == symbol.arguments.count { for i in 0..symbol.arguments.count { symbol.arguments[i].type ?= Node.createType(context.argumentTypes[i]) } } # Copy over the return type if symbol.returnType == null && context.returnType != null { symbol.returnType = Node.createType(context.returnType) } } else { # Only infer non-void return types if there's no type context if symbol.returnType == null { symbol.flags |= .SHOULD_INFER_RETURN_TYPE } # If there's dynamic type context, treat all arguments as dynamic if context == .DYNAMIC { for argument in symbol.arguments { argument.type ?= Node.createType(.DYNAMIC) } symbol.returnType ?= Node.createType(.DYNAMIC) symbol.flags |= .IS_DYNAMIC_LAMBDA } } _resolveFunction(symbol) # Use a LambdaType instead of a SymbolType for the node var argumentTypes List = [] var returnType = symbol.returnType for argument in symbol.arguments { argumentTypes.append(argument.resolvedType) } node.resolvedType = _cache.createLambdaType(argumentTypes, returnType?.resolvedType) } def _resolveLambdaType(node Node, scope Scope) { var lambdaReturnType = node.lambdaReturnType var argumentTypes List = [] var returnType Type = null for child = node.firstChild; child != lambdaReturnType; child = child.nextSibling { _resolveAsParameterizedType(child, scope) argumentTypes.append(child.resolvedType) } # An empty return type is signaled by the type "null" if lambdaReturnType.kind != .TYPE || lambdaReturnType.resolvedType != .NULL { _resolveAsParameterizedType(lambdaReturnType, scope) returnType = lambdaReturnType.resolvedType } node.resolvedType = _cache.createLambdaType(argumentTypes, returnType) } def _resolveName(node Node, scope Scope) { var enclosingFunction = scope.findEnclosingFunction var thisVariable = enclosingFunction?.symbol.this var name = node.asString # Support IDE code completion _checkForScopeCompletions(scope, node.range, name, thisVariable != null ? enclosingFunction.symbol.parent.asObjectSymbol : null) if thisVariable != null { _checkForMemberCompletions(thisVariable.resolvedType, node.range, name, .NORMAL) } var symbol = scope.find(name, _shouldCheckForSetter(node) ? .ALSO_CHECK_FOR_SETTER : .NORMAL) if symbol == null { _reportGuardMergingFailure(node) if name == "this" && thisVariable != null { _log.semanticErrorUndeclaredSelfSymbol(node.range, name) } else { var correction = scope.findWithFuzzyMatching(name, node.shouldExpectType ? .TYPE_ONLY : thisVariable != null ? .EVERYTHING : .GLOBAL_ONLY, .SELF_AND_PARENTS) _log.semanticErrorUndeclaredSymbol(node.range, name, correction == null ? null : enclosingFunction != null && _isBaseGlobalReference(enclosingFunction.symbol.parent, correction) ? correction.fullName : correction.name, correction?.range) } return } _initializeSymbol(symbol) # Track reads and writes of local variables for later use if node.isAssignTarget { _recordStatistic(symbol, .WRITE) # Also track reads for assignments if _isExpressionUsed(node.parent) { _recordStatistic(symbol, .READ) } } else { _recordStatistic(symbol, .READ) } # Forbid referencing a base class global or constructor function from a derived class if enclosingFunction != null && _isBaseGlobalReference(enclosingFunction.symbol.parent, symbol) { _log.semanticErrorUndeclaredSymbol(node.range, name, symbol.fullName, symbol.range) return } # Automatically insert "self." before instance symbols var resolvedType = symbol.resolvedType if symbol.kind.isOnInstances { if thisVariable != null && enclosingFunction.symbol.parent.asObjectSymbol.isSameOrHasBaseClass(symbol.parent) { node.become(Node.createDot(Node.createSymbolReference(thisVariable), name).withRange(node.range).withInternalRange(node.range)) resolvedType = _cache.substitute(resolvedType, thisVariable.resolvedType.environment) } else { _log.semanticErrorMemberUnexpectedInstance(node.range, symbol.name) } } # Type parameters for objects may only be used in certain circumstances else if symbol.kind == .PARAMETER_OBJECT { var parent = scope var isValid = false while parent != null { switch parent.kind { case .OBJECT { isValid = parent.asObjectScope.symbol == symbol.parent break } case .FUNCTION { var function = parent.asFunctionScope.symbol if function.kind != .FUNCTION_LOCAL { isValid = function.parent == symbol.parent break } } case .VARIABLE { var variable = parent.asVariableScope.symbol isValid = variable.kind == .VARIABLE_INSTANCE && variable.parent == symbol.parent break } } parent = parent.parent } if !isValid { _log.semanticErrorMemberUnexpectedTypeParameter(node.range, symbol.name) } } node.symbol = symbol node.resolvedType = resolvedType _automaticallyCallGetter(node, scope) } def _resolveNullDot(node Node, scope Scope) { node.become(_sinkNullDotIntoHook(node, scope, null)) _resolveAsParameterizedExpression(node, scope) } def _resolveParameterize(node Node, scope Scope) { var value = node.parameterizeValue _resolveNode(value, scope, null) # Resolve parameter types var substitutions List = [] var count = 0 for child = value.nextSibling; child != null; child = child.nextSibling { _resolveAsParameterizedType(child, scope) substitutions.append(child.resolvedType) count++ } var type = value.resolvedType var parameters = type.parameters # If this is an overloaded symbol, try to pick an overload just using the parameter count if parameters == null && type.kind == .SYMBOL && type.symbol.kind.isOverloadedFunction { var match FunctionSymbol = null for candidate in type.symbol.asOverloadedFunctionSymbol.symbols { if candidate.parameters != null && candidate.parameters.count == count { if match != null { match = null break } match = candidate } } if match != null { type = _cache.substitute(match.resolvedType, type.environment) parameters = type.parameters } } # Check for type parameters if parameters == null || type.isParameterized { if type != .DYNAMIC { _log.semanticErrorCannotParameterize(node.range, type) } value.resolvedType = .DYNAMIC return } # Check parameter count var expected = parameters.count if count != expected { _log.semanticErrorParameterCount(node.internalRangeOrRange, expected, count) value.resolvedType = .DYNAMIC return } # Make sure all parameters have types for parameter in parameters { _initializeSymbol(parameter) } # Include the symbol for use with Node.isType node.resolvedType = _cache.substitute(type, _cache.mergeEnvironments(type.environment, _cache.createEnvironment(parameters, substitutions), null)) node.symbol = value.symbol } def _resolveSequence(node Node, scope Scope, context Type) { for child = node.firstChild; child != null; child = child.nextSibling { _resolveAsParameterizedExpressionWithTypeContext(child, scope, child.nextSibling == null ? context : null) } if node.hasChildren { node.resolvedType = node.lastChild.resolvedType } } def _resolveStringInterpolation(node Node, scope Scope) { assert(node.childCount % 2 == 1) _resolveChildrenAsParameterizedExpressions(node, scope) # TypeScript supports string interpolation natively if _options.target is TypeScriptTarget { for child = node.firstChild; child != null; child = child.nextSibling { if child.resolvedType != .DYNAMIC && child.resolvedType != _cache.stringType { var temp = Node.createNull child.replaceWith(temp) child = Node.createDot(child, "toString").withRange(child.range).withFlags(.IS_IGNORED_BY_IDE) temp.replaceWith(child) } _resolveAsParameterizedExpressionWithConversion(child, scope, _cache.stringType) } node.resolvedType = _cache.stringType return } # Convert the string interpolation into a series of string concatenations var joined Node = null while node.hasChildren { var child = node.firstChild.remove if child.isString && child.asString == "" { continue } else if child.resolvedType != .DYNAMIC && child.resolvedType != _cache.stringType { child = Node.createDot(child, "toString").withRange(child.range).withFlags(.IS_IGNORED_BY_IDE) _resolveAsParameterizedExpressionWithConversion(child, scope, _cache.stringType) } joined = joined != null ? Node.createBinary(.ADD, joined, child).withRange(Range.span(joined.range, child.range)).withFlags(.IS_IGNORED_BY_IDE) : child _resolveAsParameterizedExpressionWithConversion(joined, scope, _cache.stringType) } node.become(joined != null ? joined : Node.createString("")) _resolveAsParameterizedExpressionWithConversion(node, scope, _cache.stringType) } def _resolveSuper(node Node, scope Scope) { var function = scope.findEnclosingFunction var symbol = function?.symbol var baseType = symbol?.parent.asObjectSymbol.baseType var overridden = baseType == null ? null : _findMember(baseType, symbol.name) if overridden == null { _log.semanticErrorBadSuper(node.range) return } # Calling a static method doesn't need special handling if overridden.kind == .FUNCTION_GLOBAL { node.kind = .NAME } node.resolvedType = overridden.resolvedType node.symbol = overridden _automaticallyCallGetter(node, scope) } def _resolveTypeCheck(node Node, scope Scope) { var value = node.typeCheckValue var type = node.typeCheckType _resolveAsParameterizedExpression(value, scope) _resolveAsParameterizedType(type, scope) _checkConversion(value, type.resolvedType, .EXPLICIT) node.resolvedType = _cache.boolType # Type checks don't work against interfaces if type.resolvedType.isInterface { _log.semanticWarningBadTypeCheck(type.range, type.resolvedType) } # Warn about unnecessary type checks else if value.resolvedType != .DYNAMIC && _cache.canImplicitlyConvert(value.resolvedType, type.resolvedType) && (type.resolvedType != .DYNAMIC || type.kind == .TYPE) { _log.semanticWarningExtraTypeCheck(node.range, value.resolvedType, type.resolvedType) } } def _resolveXML(node Node, scope Scope) { var tag = node.xmlTag var attributes = node.xmlAttributes var children = node.xmlChildren var closingTag = node.xmlClosingTag?.remove var initialErrorCount = _log.errorCount _resolveAsParameterizedType(tag, scope) # Make sure there's a constructor to call if _findMember(tag.resolvedType, "new") == null { attributes.removeChildren children.removeChildren attributes.resolvedType = .DYNAMIC # Only report an error if there isn't one already if _log.errorCount == initialErrorCount { _log.semanticErrorXMLCannotConstruct(node.range, tag.resolvedType) } return } # Call the constructor var value = Node.createDot(tag.clone, "new").withRange(node.range).withFlags(.IS_IGNORED_BY_IDE) var needsSequence = attributes.hasChildren || children.hasChildren var result = value _resolveAsParameterizedExpression(value, scope) if needsSequence { result = Node.createSequence.withRange(node.range).appendChild(_extractExpression(value, scope).withFlags(.IS_IGNORED_BY_IDE)) } # Assign to attributes if necessary while attributes.hasChildren { var child = attributes.firstChild.remove var name = child.binaryLeft while name.kind == .DOT { name = name.dotTarget } assert(name.kind == .NAME) name.replaceWith(Node.createDot(value.clone, name.asString).withRange(name.range)) result.appendChild(child) } # Make sure there's an append function to call if needed if children.hasChildren && _findMember(tag.resolvedType, "<>...") == null { _log.semanticErrorXMLMissingAppend(children.firstChild.range, tag.resolvedType) children.removeChildren } # Append children else { # Don't need a closure if all children are expressions var isJustExpressions = true for child = children.firstChild; child != null; child = child.nextSibling { if child.kind != .EXPRESSION { isJustExpressions = false break } } # All expression statements get passed as arguments to "<>..." _recursivelyReplaceExpressionsInXML(children, value) # Add to the sequence if isJustExpressions { for child = children.firstChild; child != null; child = child.nextSibling { result.appendChild(child.expressionValue.remove) } } # Wrap in a closure else { var symbol = FunctionSymbol.new(.FUNCTION_LOCAL, "") symbol.range = children.range symbol.block = children.remove result.appendChild(Node.createCall(Node.createLambda(symbol).withRange(symbol.range)).withRange(symbol.range)) } } # Resolve the closing tag for IDE tooltips if closingTag != null { _resolveAsParameterizedType(closingTag, scope) value = Node.createCast(value, closingTag) } # Resolve the value node.become(needsSequence ? result.appendChild(value) : value) _resolveAsParameterizedExpression(node, scope) } def _recursivelyReplaceExpressionsInXML(node Node, reference Node) { assert(node.kind == .BLOCK) for child = node.firstChild; child != null; child = child.nextSibling { switch child.kind { case .EXPRESSION { child.appendChild(Node.createCall(Node.createDot(reference.clone, "<>...").withRange(child.range).withFlags(.IS_IGNORED_BY_IDE)) .appendChild(child.expressionValue.remove).withRange(child.range)) } case .FOR { _recursivelyReplaceExpressionsInXML(child.forBlock, reference) } case .FOREACH { _recursivelyReplaceExpressionsInXML(child.foreachBlock, reference) } case .IF { _recursivelyReplaceExpressionsInXML(child.ifTrue, reference) if child.ifFalse != null { _recursivelyReplaceExpressionsInXML(child.ifFalse, reference) } } case .SWITCH { for nested = child.switchValue.nextSibling; nested != null; nested = nested.nextSibling { _recursivelyReplaceExpressionsInXML(nested.caseBlock, reference) } } case .TRY { var tryBlock = child.tryBlock var finallyBlock = child.finallyBlock _recursivelyReplaceExpressionsInXML(tryBlock, reference) for nested = tryBlock.nextSibling; nested != finallyBlock; nested = nested.nextSibling { _recursivelyReplaceExpressionsInXML(nested.catchBlock, reference) } if finallyBlock != null { _recursivelyReplaceExpressionsInXML(finallyBlock, reference) } } case .WHILE { _recursivelyReplaceExpressionsInXML(child.whileBlock, reference) } } } } def _resolveBinary(node Node, scope Scope, context Type) { var kind = node.kind var left = node.binaryLeft var right = node.binaryRight # Special-case the "??" operator if kind == .NULL_JOIN { _resolveAsParameterizedExpressionWithTypeContext(left, scope, context) _resolveAsParameterizedExpressionWithTypeContext(right, scope, context ?? left.resolvedType) var test = Node.createBinary(.NOT_EQUAL, _extractExpressionForAssignment(left, scope), Node.createNull).withRange(left.range) node.become(Node.createHook(test, left.remove, right.remove).withRange(node.range).withFlags(.WAS_NULL_JOIN)) _resolveAsParameterizedExpressionWithTypeContext(node, scope, context) return } # Special-case the "?=" operator if kind == .ASSIGN_NULL { _resolveAsParameterizedExpressionWithTypeContext(left, scope, context) _checkStorage(left, scope) var test = Node.createBinary(.NOT_EQUAL, _extractExpressionForAssignment(left, scope), Node.createNull).withRange(left.range) var assign = Node.createBinary(.ASSIGN, left.remove, right.remove).withRange(node.range).withFlags(.WAS_ASSIGN_NULL) node.become(Node.createHook(test, left.clone, assign).withRange(node.range)) _resolveAsParameterizedExpressionWithTypeContext(node, scope, context) return } # Special-case the equality operators if kind == .EQUAL || kind == .NOT_EQUAL { if _needsTypeContext(left) { _resolveAsParameterizedExpression(right, scope) _resolveAsParameterizedExpressionWithTypeContext(left, scope, right.resolvedType) } else if _needsTypeContext(right) { _resolveAsParameterizedExpression(left, scope) _resolveAsParameterizedExpressionWithTypeContext(right, scope, left.resolvedType) } else { _resolveAsParameterizedExpression(left, scope) _resolveAsParameterizedExpression(right, scope) } # Check for likely bugs "x == x" or "x != x", except when this is used to test for NaN if left.looksTheSameAs(right) && left.hasNoSideEffects && right.hasNoSideEffects && !_cache.isEquivalentToDouble(left.resolvedType) && left.resolvedType != .DYNAMIC { _log.semanticWarningIdenticalOperands(node.range, kind == .EQUAL ? "==" : "!=") } # The two types must be compatible var commonType = _cache.commonImplicitType(left.resolvedType, right.resolvedType) if commonType == null { _log.semanticErrorNoCommonType(node.range, left.resolvedType, right.resolvedType) } else { node.resolvedType = _cache.boolType # Make sure type casts are inserted _checkConversion(left, commonType, .IMPLICIT) _checkConversion(right, commonType, .IMPLICIT) } return } # Special-case assignment since it's not overridable if kind == .ASSIGN { _resolveAsParameterizedExpression(left, scope) # Automatically call setters if left.symbol != null && left.symbol.isSetter { node.become(Node.createCall(left.remove).withRange(node.range).withInternalRange(right.range).appendChild(right.remove)) _resolveAsParameterizedExpression(node, scope) } # Resolve the right side using type context from the left side else { _resolveAsParameterizedExpressionWithConversion(right, scope, left.resolvedType) node.resolvedType = left.resolvedType _checkStorage(left, scope) # Check for likely bugs "x = x" if left.looksTheSameAs(right) && left.hasNoSideEffects && right.hasNoSideEffects { _log.semanticWarningIdenticalOperands(node.range, node.wasAssignNull ? "?=" : "=") } # Check for likely bugs "x = y" instead of "x == y" based on type context else if node.internalRange != null && context != null && context != .DYNAMIC && left.resolvedType == .DYNAMIC && right.resolvedType != .DYNAMIC && !_cache.canImplicitlyConvert(right.resolvedType, context) { _log.semanticWarningSuspiciousAssignmentLocation(node.internalRangeOrRange) } # Check for likely bugs "x = y" instead of "x == y" based on expression context else if node.internalRange != null && node.parent != null { var parent = node.parent var parentKind = parent.kind if parentKind == .IF || parentKind == .WHILE || parentKind == .LOGICAL_AND || parentKind == .LOGICAL_OR || parentKind == .NOT || parentKind == .RETURN && !parent.isImplicitReturn || parentKind == .HOOK && node == parent.hookTest { _log.semanticWarningSuspiciousAssignmentLocation(node.internalRangeOrRange) } } } return } # Special-case short-circuit logical operators since they aren't overridable if kind == .LOGICAL_AND || kind == .LOGICAL_OR { _resolveAsParameterizedExpressionWithConversion(left, scope, _cache.boolType) _resolveAsParameterizedExpressionWithConversion(right, scope, _cache.boolType) node.resolvedType = _cache.boolType # Check for likely bugs "x && x" or "x || x" if left.looksTheSameAs(right) && left.hasNoSideEffects && right.hasNoSideEffects && (!left.isBool || !right.isBool) { _log.semanticWarningIdenticalOperands(node.range, kind == .LOGICAL_AND ? "&&" : "||") } return } _resolveOperatorOverload(node, scope, context) } def _generateReference(scope Scope, type Type) Node { var enclosingFunction = scope.findEnclosingFunctionOrLambda var symbol VariableSymbol = null # Add a local variable if enclosingFunction != null { var block = enclosingFunction.symbol.block # Make sure the call to "super" is still the first statement var after = block.firstChild if after.isSuperCallStatement { after = after.nextSibling } # Add the new variable to the top of the function symbol = VariableSymbol.new(.VARIABLE_LOCAL, enclosingFunction.generateName("ref")) block.insertChildBefore(after, Node.createVariables.appendChild(Node.createVariable(symbol))) } # Otherwise, add a global variable else { symbol = VariableSymbol.new(.VARIABLE_GLOBAL, _global.scope.generateName("ref")) symbol.parent = _global _generatedGlobalVariables.append(symbol) } # Force-initialize the symbol symbol.initializeWithType(type) return Node.createSymbolReference(symbol) } def _extractExpression(node Node, scope Scope) Node { assert(node.resolvedType != null) if node.kind == .NAME || node.kind == .CONSTANT { return node.clone } # Replace the original expression with a reference var reference = _generateReference(scope, node.resolvedType).withRange(node.range).withFlags(.IS_IGNORED_BY_IDE) var setup = node.cloneAndStealChildren node.become(reference) return Node.createBinary(.ASSIGN, reference, setup).withType(node.resolvedType).withRange(node.range) } # Expressions with side effects must be stored to temporary variables # if they need to be duplicated in an expression. This does the variable # allocation and storage and returns a partial assigment. # # Examples: # # "a" stays "a" and returns "a" # "a.b" stays "a.b" and returns "a.b" # "a[0]" stays "a[0]" and returns "a[0]" # "a().b" becomes "ref.b" and returns "(ref = a()).b" # "a()[0]" becomes "ref[0]" and returns "(ref = a())[0]" # "a()[b()]" becomes "ref[ref2]" and returns "(ref = a())[ref2 = b()]" # def _extractExpressionForAssignment(node Node, scope Scope) Node { assert(node.resolvedType != null) # Handle dot expressions if node.kind == .DOT && node.symbol != null { return Node.createDot(_extractExpression(node.dotTarget, scope), node.asString).withSymbol(node.symbol) .withType(node.resolvedType).withRange(node.range).withInternalRange(node.internalRange) } # Handle index expressions if node.kind == .INDEX { var left = _extractExpression(node.indexLeft, scope) return Node.createIndex(left, _extractExpression(node.indexRight, scope)).withRange(node.range) } # Handle name expressions if node.kind == .NAME { return node.clone } # Handle everything else return _extractExpression(node, scope) } def _resolveOperatorOverload(node Node, scope Scope, context Type) { # The order of operands are reversed for the "in" operator var kind = node.kind var reverseBinaryOrder = kind == .IN var first = node.firstChild var second = first.nextSibling var target = reverseBinaryOrder ? second : first var other = kind.isBinary ? reverseBinaryOrder ? first : second : null var isBitOperation = kind.isBitOperation var bitContext = isBitOperation && context != null && context.isFlags ? context : null # Allow "foo in [.FOO, .BAR]" if kind == .IN && target.kind == .INITIALIZER_LIST && !_needsTypeContext(other) { _resolveAsParameterizedExpression(other, scope) _resolveAsParameterizedExpressionWithTypeContext(target, scope, other.resolvedType != .DYNAMIC ? _cache.createListType(other.resolvedType) : null) } # Resolve just the target since the other arguments may need type context from overload resolution else { _resolveAsParameterizedExpressionWithTypeContext(target, scope, bitContext) } # Warn about shifting by 0 in the original source code, since that doesn't # do anything when the arguments are integers and so is likely a mistake if kind.isShift && _cache.isEquivalentToInt(target.resolvedType) && other.isInt && other.asInt == 0 { _log.semanticWarningShiftByZero(node.range) } # Can't do overload resolution on the dynamic type var type = target.resolvedType if type == .DYNAMIC { if kind.isAssign && kind != .ASSIGN_INDEX { _checkStorage(target, scope) } _resolveChildrenAsParameterizedExpressions(node, scope) return } # Check if the operator can be overridden at all var info = operatorInfo[kind] if info.kind != .OVERRIDABLE { _log.semanticErrorUnknownMemberSymbol(node.internalRangeOrRange, info.text, type, null, null) _resolveChildrenAsParameterizedExpressions(node, scope) return } # Numeric conversions var enumFlagsType Type = null # Binary operations if other != null { # Assignment operations aren't symmetric if !kind.isBinaryAssign { if type == _cache.intType { _resolveAsParameterizedExpression(other, scope) # Auto-convert doubles to ints if other.resolvedType == _cache.doubleType { _checkConversion(target, _cache.doubleType, .IMPLICIT) type = _cache.doubleType } } # Check if the target is an enum else if type.isEnumOrFlags { _resolveAsParameterizedExpressionWithTypeContext(other, scope, bitContext ?? ((isBitOperation || kind == .IN) && type.isFlags ? type : null)) # Auto-convert enums to ints when both operands can be converted if _cache.isNumeric(other.resolvedType) { type = _cache.commonImplicitType(type, other.resolvedType) assert(type != null) if type.isEnumOrFlags { if type.isFlags { enumFlagsType = type } type = _cache.intType } _checkConversion(target, type, .IMPLICIT) _checkConversion(other, type, .IMPLICIT) } } } # Allow certain operations on "flags" types else if isBitOperation && type.isFlags { _resolveAsParameterizedExpressionWithTypeContext(other, scope, type) enumFlagsType = type type = _cache.intType _checkConversion(other, type, .IMPLICIT) } } # Allow "~x" on "flags" types else if kind == .COMPLEMENT && type.isEnumOrFlags { if type.isFlags { enumFlagsType = type } type = _cache.intType _checkConversion(target, type, .IMPLICIT) } # Find the operator method var isComparison = kind.isBinaryComparison var name = isComparison ? "<=>" : info.text var symbol = _findMember(type, name) var extracted Node = null var wasUnaryPostfix = false # Convert operators like "+=" to a "+" inside a "=" if symbol == null && info.assignKind != .NULL { symbol = _findMember(type, operatorInfo[info.assignKind].text) if symbol != null { extracted = _extractExpressionForAssignment(target, scope) if kind == .PREFIX_INCREMENT || kind == .PREFIX_DECREMENT || kind == .POSTFIX_INCREMENT || kind == .POSTFIX_DECREMENT { node.appendChild(_cache.createInt(1).withRange(node.internalRangeOrRange)) node.internalRange = null # This no longer makes sense } wasUnaryPostfix = kind.isUnaryPostfix && _isExpressionUsed(node) kind = info.assignKind node.kind = kind } } # Special-case the "in" operator on "flags" types if symbol == null && kind == .IN && enumFlagsType != null { node.become(Node.createBinary(.NOT_EQUAL, Node.createBinary(.BITWISE_AND, other.remove, target.remove).withRange(node.range), _cache.createInt(0)).withRange(node.range)) _resolveAsParameterizedExpression(node, scope) return } # Fail if the operator wasn't found if symbol == null { _log.semanticErrorUnknownMemberSymbol(node.internalRangeOrRange, name, type, null, null) _resolveChildrenAsParameterizedExpressions(node, scope) return } var symbolType = _cache.substitute(symbol.resolvedType, type.environment) # Resolve the overload now so the symbol's properties can be inspected if symbol.kind.isOverloadedFunction { if reverseBinaryOrder { first.swapWith(second) } symbolType = _resolveOverloadedFunction(node.internalRangeOrRange, node, scope, symbolType) if reverseBinaryOrder { first.swapWith(second) } if symbolType == null { _resolveChildrenAsParameterizedExpressions(node, scope) return } symbol = symbolType.symbol } var isRawImport = symbol.isImported && !symbol.isRenamed node.symbol = symbol _checkAccess(node, node.internalRangeOrRange, scope) # Check for a valid storage location for imported operators if kind.isAssign && kind != .ASSIGN_INDEX && symbol.isImported && extracted == null { _checkStorage(target, scope) } # "<", ">", "<=", or ">=" if isComparison && (isRawImport || type == _cache.intType || type == _cache.doubleType) { _resolveChildrenAsParameterizedExpressions(node, scope) node.resolvedType = _cache.boolType node.symbol = null } # Don't replace the operator with a call if it's just used for type checking else if isRawImport { if reverseBinaryOrder { first.swapWith(second) } if !_resolveFunctionCall(node, scope, symbolType) { _resolveChildrenAsParameterizedExpressions(node, scope) } if reverseBinaryOrder { first.swapWith(second) } # Handle "flags" types if isBitOperation && enumFlagsType != null { node.resolvedType = enumFlagsType } } else { # Resolve the method call if reverseBinaryOrder { first.swapWith(second) } node.prependChild(Node.createMemberReference(target.remove, symbol).withRange(node.internalRangeOrRange)) # Implement the logic for the "<=>" operator if isComparison { var call = Node.new(.CALL).appendChildrenFrom(node).withRange(node.range) node.appendChild(call) node.appendChild(_cache.createInt(0)) node.resolvedType = _cache.boolType _resolveFunctionCall(call, scope, symbolType) } # All other operators are just normal method calls else { node.kind = .CALL _resolveFunctionCall(node, scope, symbolType) } } if extracted != null { # The expression used to initialize the assignment must return a value if symbolType.returnType == null { _log.semanticErrorUseOfVoidFunction(node.range, symbol.name, symbol.range) } # Wrap everything in an assignment if the assignment target was extracted _promoteToAssignment(node, extracted) _resolveAsParameterizedExpression(node, scope) # Handle custom unary postfix operators if wasUnaryPostfix { node.become(Node.createBinary(kind, node.cloneAndStealChildren, _cache.createInt(-1).withRange(node.internalRangeOrRange)).withRange(node.range)) _resolveAsParameterizedExpression(node, scope) } } # Handle custom unary assignment operators else if kind.isUnaryAssign && !isRawImport { # "foo(x++)" => "foo((ref = x, x = ref.increment(), ref))" if kind.isUnaryPostfix && _isExpressionUsed(node) { var reference = _generateReference(scope, target.resolvedType).withRange(target.range) var original = _extractExpressionForAssignment(target, scope) target.replaceWith(reference) _promoteToAssignment(node, target) node.become(Node.createSequence .appendChild(Node.createBinary(.ASSIGN, reference.clone, original).withRange(node.range)) .appendChild(node.cloneAndStealChildren) .appendChild(reference.clone) .withRange(node.range)) _resolveAsParameterizedExpression(node, scope) } # "foo(++x)" => "foo(x = x.increment())" else { _promoteToAssignment(node, _extractExpressionForAssignment(target, scope)) _resolveAsParameterizedExpression(node, scope) } } } def _promoteToAssignment(node Node, extracted Node) { assert(extracted.parent == null) if extracted.kind == .INDEX { extracted.kind = .ASSIGN_INDEX extracted.appendChild(node.cloneAndStealChildren) node.become(extracted) } else { node.become(Node.createBinary(.ASSIGN, extracted, node.cloneAndStealChildren).withRange(node.range)) } } def _automaticallyCallGetter(node Node, scope Scope) bool { var symbol = node.symbol if symbol == null { return false } var kind = symbol.kind var parent = node.parent # Never call a getter if type parameters are present if parent != null && parent.kind == .PARAMETERIZE && _isCallValue(parent) { return false } # The check for getters is complicated by overloaded functions if !symbol.isGetter && kind.isOverloadedFunction && (!_isCallValue(node) || parent.hasOneChild) { var overloaded = symbol.asOverloadedFunctionSymbol for getter in overloaded.symbols { # Just return the first getter assuming errors for duplicate getters # were already logged when the overloaded symbol was initialized if getter.isGetter { node.resolvedType = _cache.substitute(getter.resolvedType, node.resolvedType.environment) node.symbol = getter symbol = getter break } } } _checkAccess(node, node.internalRangeOrRange, scope) # Automatically wrap the getter in a call expression if symbol.isGetter { node.become(Node.createCall(node.cloneAndStealChildren).withRange(node.range)) _resolveAsParameterizedExpression(node, scope) return true } # Forbid bare function references if !symbol.isSetter && node.resolvedType != .DYNAMIC && kind.isFunctionOrOverloadedFunction && kind != .FUNCTION_ANNOTATION && !_isCallValue(node) && (parent == null || parent.kind != .PARAMETERIZE || !_isCallValue(parent)) { var lower = 0x7FFFFFFF var upper = -1 if kind.isFunction { lower = upper = symbol.asFunctionSymbol.arguments.count } else { for function in symbol.asOverloadedFunctionSymbol.symbols { var count = function.arguments.count if count < lower { lower = count } if count > upper { upper = count } } } _log.semanticErrorMustCallFunction(node.internalRangeOrRange, symbol.name, lower, upper) node.resolvedType = .DYNAMIC } return false } def _convertSwitchToIfChain(node Node, scope Scope) { var variable = VariableSymbol.new(.VARIABLE_LOCAL, scope.generateName("value")) var value = node.switchValue.remove var block Node = null # Stash the variable being switched over so it's only evaluated once variable.initializeWithType(value.resolvedType) variable.value = value node.parent.insertChildBefore(node, Node.createVariables.appendChild(Node.createVariable(variable))) # Build the chain in reverse starting with the last case for child = node.lastChild; child != null; child = child.previousSibling { var caseBlock = child.caseBlock.remove var test Node = null # Combine adjacent cases in a "||" chain while child.hasChildren { var caseValue = Node.createBinary(.EQUAL, Node.createSymbolReference(variable), child.firstChild.remove).withType(_cache.boolType) test = test != null ? Node.createBinary(.LOGICAL_OR, test, caseValue).withType(_cache.boolType) : caseValue } # Chain if-else statements together block = test != null ? Node.createBlock.appendChild(Node.createIf(test, caseBlock, block)) : caseBlock } # Replace the switch statement with the if chain if block != null { node.replaceWithChildrenFrom(block) } else { node.remove } } } namespace Resolver { const _annotationSymbolFlags StringMap = { "@alwaysinline": .IS_INLINING_FORCED, "@deprecated": .IS_DEPRECATED, "@entry": .IS_ENTRY_POINT, "@export": .IS_EXPORTED, "@import": .IS_IMPORTED, "@neverinline": .IS_INLINING_PREVENTED, "@prefer": .IS_PREFERRED, "@rename": .IS_RENAMED, "@skip": .IS_SKIPPED, "@spreads": .SHOULD_SPREAD, } def _shouldCheckForSetter(node Node) bool { return node.parent != null && node.parent.kind == .ASSIGN && node == node.parent.binaryLeft } def _isExpressionUsed(node Node) bool { # Check for a null parent to handle variable initializers var parent = node.parent return parent == null || parent.kind != .EXPRESSION && !parent.isImplicitReturn && (parent.kind != .ANNOTATION || node != parent.annotationValue) && (parent.kind != .FOR || node != parent.forUpdate) && parent.kind != .SEQUENCE } def _isValidVariableType(type Type) bool { return type != .NULL && (type.kind != .SYMBOL || !type.symbol.kind.isFunctionOrOverloadedFunction) } def _isBaseGlobalReference(parent Symbol, member Symbol) bool { return parent != null && parent.kind == .OBJECT_CLASS && member.kind.isGlobalReference && member.parent != parent && member.parent.kind == .OBJECT_CLASS && parent.asObjectSymbol.hasBaseClass(member.parent) } def _isCallValue(node Node) bool { var parent = node.parent return parent != null && parent.kind == .CALL && node == parent.callValue } def _isCallReturningVoid(node Node) bool { return node.kind == .CALL && ( node.symbol != null && node.symbol.resolvedType.returnType == null || node.callValue.resolvedType.kind == .LAMBDA && node.callValue.resolvedType.returnType == null) } def _needsTypeContext(node Node) bool { return node.kind == .DOT && node.dotTarget == null || node.kind == .HOOK && _needsTypeContext(node.hookTrue) && _needsTypeContext(node.hookFalse) || node.kind.isInitializer } def _ensureFunctionIsOverloaded(symbol FunctionSymbol) { if symbol.overloaded == null { var overloaded = OverloadedFunctionSymbol.new(Merging.overloadedKind(symbol.kind), symbol.name, [symbol]) overloaded.parent = symbol.parent overloaded.scope = overloaded.parent.scope symbol.overloaded = overloaded } } def _matchCompletion(symbol Symbol, prefix string) bool { if symbol.state == .INITIALIZING { return false } var name = symbol.name.toLowerCase if name[0] == '_' && prefix[0] != '_' { name = name.slice(1) } return name.startsWith(prefix.toLowerCase) } } } ================================================ FILE: src/middle/scope.sk ================================================ namespace Skew { enum ScopeKind { FUNCTION LOCAL OBJECT VARIABLE } enum ScopeSearch { NORMAL ALSO_CHECK_FOR_SETTER } enum FuzzyScopeSearch { SELF_ONLY SELF_AND_PARENTS } class Scope { var parent Scope var used StringMap = null var _enclosingFunctionOrLambda FunctionScope = null var _enclosingFunction FunctionScope = null var _enclosingLoop LocalScope = null def kind ScopeKind def _find(name string) Symbol { return null } def _findWithFuzzyMatching(matcher FuzzySymbolMatcher) {} # Need to check for a setter at the same time as for a normal symbol # because the one in the closer scope must be picked. If both are in # the same scope, pick the setter. def find(name string, search ScopeSearch) Symbol { var symbol Symbol = null var setterName = search == .ALSO_CHECK_FOR_SETTER ? name + "=" : null for scope = self; scope != null && symbol == null; scope = scope.parent { if setterName != null { symbol = scope._find(setterName) } symbol ?= scope._find(name) } return symbol } def findWithFuzzyMatching(name string, kind FuzzySymbolKind, search FuzzyScopeSearch) Symbol { var matcher = FuzzySymbolMatcher.new(name, kind) for scope = self; scope != null; scope = scope.parent { scope._findWithFuzzyMatching(matcher) if search == .SELF_ONLY { break } } return matcher.bestSoFar } def asObjectScope ObjectScope { assert(kind == .OBJECT) return self as ObjectScope } def asFunctionScope FunctionScope { assert(kind == .FUNCTION) return self as FunctionScope } def asVariableScope VariableScope { assert(kind == .VARIABLE) return self as VariableScope } def asLocalScope LocalScope { assert(kind == .LOCAL) return self as LocalScope } def findEnclosingFunctionOrLambda FunctionScope { if _enclosingFunctionOrLambda != null { return _enclosingFunctionOrLambda } var scope = self while scope != null { if scope.kind == .FUNCTION { _enclosingFunctionOrLambda = scope.asFunctionScope return _enclosingFunctionOrLambda } scope = scope.parent } return null } def findEnclosingFunction FunctionScope { if _enclosingFunction != null { return _enclosingFunction } var scope Scope = findEnclosingFunctionOrLambda while scope != null { if scope.kind == .FUNCTION && scope.asFunctionScope.symbol.kind != .FUNCTION_LOCAL { _enclosingFunction = scope.asFunctionScope return _enclosingFunction } scope = scope.parent } return null } def findEnclosingLoop LocalScope { if _enclosingLoop != null { return _enclosingLoop } var scope = self while scope != null && scope.kind == .LOCAL { if scope.asLocalScope.type == .LOOP { _enclosingLoop = scope.asLocalScope return _enclosingLoop } scope = scope.parent } return null } def generateName(prefix string) string { var count = 0 var name = prefix while isNameUsed(name) { name = prefix + (++count).toString } reserveName(name, null) return name } def reserveName(name string, symbol VariableSymbol) { used ?= {} if !(name in used) { used[name] = symbol } } def isNameUsed(name string) bool { if find(name, .NORMAL) != null { return true } for scope = self; scope != null; scope = scope.parent { if scope.used != null && name in scope.used { return true } } return false } } class ObjectScope : Scope { var symbol ObjectSymbol over kind ScopeKind { return .OBJECT } over _find(name string) Symbol { return symbol.members.get(name, null) } over _findWithFuzzyMatching(matcher FuzzySymbolMatcher) { symbol.members.each((name, member) => matcher.include(member)) } } class FunctionScope : Scope { var symbol FunctionSymbol var parameters StringMap = {} over kind ScopeKind { return .FUNCTION } over _find(name string) Symbol { return parameters.get(name, null) } over _findWithFuzzyMatching(matcher FuzzySymbolMatcher) { parameters.each((name, parameter) => matcher.include(parameter)) } } class VariableScope : Scope { var symbol VariableSymbol over kind ScopeKind { return .VARIABLE } } enum LocalType { LOOP NORMAL } class LocalScope : Scope { var locals StringMap = {} var type LocalType over kind ScopeKind { return .LOCAL } over _find(name string) Symbol { return locals.get(name, null) } over _findWithFuzzyMatching(matcher FuzzySymbolMatcher) { locals.each((name, local) => matcher.include(local)) } def define(symbol VariableSymbol, log Log) { symbol.scope = self # Check for duplicates var other = locals.get(symbol.name, null) if other != null { log.semanticErrorDuplicateSymbol(symbol.range, symbol.name, other.range) return } # Check for shadowing var scope = parent while scope.kind == .LOCAL { var local = scope.asLocalScope.locals.get(symbol.name, null) if local != null { log.semanticErrorShadowedSymbol(symbol.range, symbol.name, local.range) return } scope = scope.parent } scope.reserveName(symbol.name, symbol) locals[symbol.name] = symbol } } } ================================================ FILE: src/middle/shaking.sk ================================================ namespace Skew { enum ShakingMode { USE_TYPES IGNORE_TYPES } # Remove all code that isn't reachable from the entry point or from an # imported or exported symbol. This is called tree shaking here but is also # known as dead code elimination. Tree shaking is perhaps a better name # because this pass doesn't remove dead code inside functions. def shakingPass(global ObjectSymbol, entryPoint FunctionSymbol, mode ShakingMode) { var graph = UsageGraph.new(global, mode) var symbols List = [] Shaking.collectExportedSymbols(global, symbols, entryPoint) var usages = graph.usagesForSymbols(symbols) if usages != null { Shaking.removeUnusedSymbols(global, usages) } } # This stores a mapping from every symbol to its immediate dependencies and # uses that to provide a mapping from a subset of symbols to their complete # dependencies. This is useful for dead code elimination. class UsageGraph { var _mode ShakingMode var context Symbol = null var _currentUsages IntMap = null var _overridesForSymbol IntMap> = {} var _usages IntMap> = {} def new(global ObjectSymbol, mode ShakingMode) { _mode = mode _visitObject(global) _changeContext(null) } def usagesForSymbols(symbols List) IntMap { var overridesToCheck IntMap> = {} var combinedUsages IntMap = {} var stack List = [] stack.append(symbols) # Iterate until a fixed point is reached while !stack.isEmpty { var symbol = stack.takeLast if !(symbol.id in combinedUsages) { combinedUsages[symbol.id] = symbol var symbolUsages = _usages.get(symbol.id, null) if symbolUsages != null { stack.append(symbolUsages) } # Handle function overrides if symbol.kind.isFunction { var overridden = symbol.asFunctionSymbol.overridden var symbolOverrides = _overridesForSymbol.get(symbol.id, null) # Automatically include all overridden functions in case the use # of this type is polymorphic, which is a conservative estimate if overridden != null { stack.append(overridden) } # Check function overrides too if symbolOverrides != null { for override in symbolOverrides { var key = override.parent.id # Queue this override immediately if the parent type is used if key in combinedUsages { stack.append(override) } # Otherwise, remember this override for later if the parent type ends up being used else { var overrides = overridesToCheck.get(key, null) if overrides == null { overrides = [] overridesToCheck[key] = overrides } overrides.append(override) } } } } # Handle overrides dependent on this type else if symbol.kind.isType { var overrides = overridesToCheck.get(symbol.id, null) if overrides != null { stack.append(overrides) } } } } return combinedUsages } def _changeContext(symbol Symbol) { if context != null { var values = _currentUsages.values values.sort(Symbol.SORT_BY_ID) # Sort so the order is deterministic _usages[context.id] = values } _currentUsages = {} if symbol != null { _includeSymbol(symbol) _currentUsages[symbol.id] = symbol } context = symbol } def _recordOverride(base FunctionSymbol, derived FunctionSymbol) { var overrides = _overridesForSymbol.get(base.id, null) if overrides == null { overrides = [] _overridesForSymbol[base.id] = overrides } overrides.append(derived) } def _recordUsage(symbol Symbol) { _includeSymbol(symbol) if !symbol.kind.isLocal { _currentUsages[symbol.id] = symbol } } def _visitObject(symbol ObjectSymbol) { for object in symbol.objects { _changeContext(object) _recordUsage(symbol) # Always pull the base class in if object.baseClass != null { _recordUsage(object.baseClass) } # Only pull interfaces in for typed targets (interfaces disappear entirely for untyped targets) if _mode != .IGNORE_TYPES && object.interfaceTypes != null { for type in object.interfaceTypes { if type.symbol != null { _recordUsage(type.symbol) } } } # If an imported type is used, automatically assume all functions and # variables for that type are used too if object.isImported { for function in object.functions { _recordUsage(function) } for variable in object.functions { _recordUsage(variable) } } _visitObject(object) } for function in symbol.functions { _changeContext(function) # Instance functions shouldn't cause their instance type to be emitted for dynamically-typed targets if _mode != .IGNORE_TYPES || function.kind != .FUNCTION_INSTANCE { _recordUsage(symbol) } _visitFunction(function) } for variable in symbol.variables { _changeContext(variable) # Instance variables shouldn't require the class to be present because # accessing an instance variable already requires a constructed instance if variable.kind != .VARIABLE_INSTANCE { _recordUsage(symbol) } _visitVariable(variable) } } def _visitFunction(symbol FunctionSymbol) { for argument in symbol.arguments { _visitVariable(argument) } _visitType(symbol.resolvedType.returnType) _visitNode(symbol.block) # Remember which functions are overridden for later if symbol.overridden != null { _recordOverride(symbol.overridden, symbol) } # Remember which functions are overridden for later if symbol.implementations != null { for function in symbol.implementations { _recordOverride(symbol, function) _recordOverride(function, symbol) } } } def _visitVariable(symbol VariableSymbol) { _visitType(symbol.resolvedType) _visitNode(symbol.value) } def _visitNode(node Node) { if node == null { return } if node.kind == .CAST { _visitNode(node.castValue) _visitType(node.castType.resolvedType) } # This is necessary to preserve the types of constant-folded enums in typed languages else if node.kind == .CONSTANT && _mode == .USE_TYPES { _visitType(node.resolvedType) } else { for child = node.firstChild; child != null; child = child.nextSibling { _visitNode(child) } } if node.symbol != null { _recordUsage(node.symbol) } switch node.kind { # The function block is a child node and has already been visited case .LAMBDA { var function = node.symbol.asFunctionSymbol for argument in function.arguments { _visitVariable(argument) } _visitType(function.resolvedType.returnType) } # The variable value is a child node and has already been visited case .VARIABLE { _visitType(node.symbol.asVariableSymbol.resolvedType) } } } def _visitType(type Type) { if _mode == .USE_TYPES && type != null && type.symbol != null { _recordUsage(type.symbol) # This should be a tree too, so infinite loops should not happen if type.isParameterized { for substitution in type.substitutions { _visitType(substitution) } } } } } } namespace Skew.Shaking { def collectExportedSymbols(symbol ObjectSymbol, symbols List, entryPoint FunctionSymbol) { for object in symbol.objects { collectExportedSymbols(object, symbols, entryPoint) if object.isExported { symbols.append(object) } } for function in symbol.functions { if function.isExported || function == entryPoint { symbols.append(function) } } for variable in symbol.variables { if variable.isExported { symbols.append(variable) } } } def removeUnusedSymbols(symbol ObjectSymbol, usages IntMap) { symbol.objects.removeIf(object => !(object.id in usages)) symbol.functions.removeIf(function => !(function.id in usages)) symbol.variables.removeIf(variable => !(variable.id in usages)) for object in symbol.objects { removeUnusedSymbols(object, usages) } } } # Only enable toString in debug mode because of tracking overhead if !RELEASE { class Skew.UsageGraph { var _allSymbols IntMap = {} def _includeSymbol(symbol Symbol) { _allSymbols[symbol.id] = symbol } def toString string { var symbols = _allSymbols.values symbols.sort(Symbol.SORT_BY_ID) # Sort so the order is deterministic var text = "" for symbol in symbols { var implies = _usages.get(symbol.id, null) if text != "" { text += "\n" } text += symbol.fullName + " => [" + (implies != null ? ", ".join(implies.map(s => s.fullName)) : "") + "]" } return text } } } else { class Skew.UsageGraph { def _includeSymbol(symbol Symbol) { } } } ================================================ FILE: src/middle/type.sk ================================================ namespace Skew { enum TypeKind { LAMBDA SPECIAL SYMBOL } class Type { const id = _createID var kind TypeKind var symbol Symbol var environment Environment = null var substitutions List = null var argumentTypes List = null var returnType Type = null var substitutionCache IntMap = null # Maps a type environment id to this type in that environment def parameters List { return symbol == null ? null : symbol.kind.isObject ? symbol.asObjectSymbol.parameters : symbol.kind.isFunction ? symbol.asFunctionSymbol.parameters : null } def isParameterized bool { return substitutions != null } def isWrapped bool { return symbol != null && symbol.kind == .OBJECT_WRAPPED } def isClass bool { return symbol != null && symbol.kind == .OBJECT_CLASS } def isInterface bool { return symbol != null && symbol.kind == .OBJECT_INTERFACE } def isEnumOrFlags bool { return symbol != null && symbol.kind.isEnumOrFlags } def isFlags bool { return symbol != null && symbol.kind == .OBJECT_FLAGS } def isParameter bool { return symbol != null && symbol.kind.isParameter } # Type parameters are not guaranteed to be nullable since generics are # implemented through type erasure and the substituted type may be "int" def isReference bool { return symbol == null || !symbol.isValueType && !symbol.kind.isParameter && (symbol.kind != .OBJECT_WRAPPED || symbol.asObjectSymbol.wrappedType.isReference) } def toString string { if kind == .SYMBOL { if isParameterized { var name = symbol.fullName + "<" for i in 0..substitutions.count { if i != 0 { name += ", " } name += substitutions[i].toString } return name + ">" } return symbol.fullName } if kind == .LAMBDA { var result = "fn(" for i in 0..argumentTypes.count { if i != 0 { result += ", " } result += argumentTypes[i].toString } return result + (returnType != null ? ") \(returnType)" : ")") } return self == DYNAMIC ? "dynamic" : "null" } def baseType Type { return isClass ? symbol.asObjectSymbol.baseType : null } def interfaceTypes List { return isClass ? symbol.asObjectSymbol.interfaceTypes : null } } namespace Type { var DYNAMIC = Type.new(.SPECIAL, null) var NULL = Type.new(.SPECIAL, null) def _createID int { return ++_nextID } var _nextID = 0 } class Environment { const id = _createID const parameters List const substitutions List const mergeCache IntMap = null # This is just for debugging def toString string { var text = "(" for i in 0..parameters.count { if i != 0 { text += ", " } text += "\(parameters[i].name) => \(substitutions[i])" } return text + ")" } } namespace Environment { def _createID int { return ++_nextID } var _nextID = 0 } } ================================================ FILE: src/middle/typecache.sk ================================================ namespace Skew { class TypeCache { var boolType Type = null var boxType Type = null var doubleType Type = null var intMapType Type = null var intType Type = null var listType Type = null var mathType Type = null var stringMapType Type = null var stringType Type = null var boolToStringSymbol Symbol = null var doublePowerSymbol Symbol = null var doubleToStringSymbol Symbol = null var intPowerSymbol Symbol = null var intToStringSymbol Symbol = null var mathPowSymbol Symbol = null var stringCountSymbol Symbol = null var stringFromCodePointsSymbol Symbol = null var stringFromCodePointSymbol Symbol = null var stringFromCodeUnitsSymbol Symbol = null var stringFromCodeUnitSymbol Symbol = null var entryPointSymbol FunctionSymbol = null const _environments = IntMap>.new const _lambdaTypes = IntMap>.new const _parameters List = [] def loadGlobals(log Log, global ObjectSymbol) { boolType = _loadGlobalObject(global, "bool", .OBJECT_CLASS, .IS_VALUE_TYPE) boxType = _loadGlobalObject(global, "Box", .OBJECT_CLASS, 0) doubleType = _loadGlobalObject(global, "double", .OBJECT_CLASS, .IS_VALUE_TYPE) intMapType = _loadGlobalObject(global, "IntMap", .OBJECT_CLASS, 0) intType = _loadGlobalObject(global, "int", .OBJECT_CLASS, .IS_VALUE_TYPE) listType = _loadGlobalObject(global, "List", .OBJECT_CLASS, 0) mathType = _loadGlobalObject(global, "Math", .OBJECT_NAMESPACE, 0) stringMapType = _loadGlobalObject(global, "StringMap", .OBJECT_CLASS, 0) stringType = _loadGlobalObject(global, "string", .OBJECT_CLASS, 0) boolToStringSymbol = _loadInstanceFunction(boolType, "toString") doublePowerSymbol = _loadInstanceFunction(doubleType, "**") doubleToStringSymbol = _loadInstanceFunction(doubleType, "toString") intPowerSymbol = _loadInstanceFunction(intType, "**") intToStringSymbol = _loadInstanceFunction(intType, "toString") mathPowSymbol = _loadGlobalFunction(mathType, "pow") stringCountSymbol = _loadInstanceFunction(stringType, "count") stringFromCodePointsSymbol = _loadGlobalFunction(stringType, "fromCodePoints") stringFromCodePointSymbol = _loadGlobalFunction(stringType, "fromCodePoint") stringFromCodeUnitsSymbol = _loadGlobalFunction(stringType, "fromCodeUnits") stringFromCodeUnitSymbol = _loadGlobalFunction(stringType, "fromCodeUnit") } def isEquivalentToBool(type Type) bool { return unwrappedType(type) == boolType } def isEquivalentToInt(type Type) bool { return isInteger(unwrappedType(type)) } def isEquivalentToDouble(type Type) bool { return unwrappedType(type) == doubleType } def isEquivalentToString(type Type) bool { return unwrappedType(type) == stringType } def isInteger(type Type) bool { return type == intType || type.isEnumOrFlags } def isNumeric(type Type) bool { return isInteger(type) || type == doubleType } def isBox(type Type) bool { return type.symbol == boxType.symbol } def isList(type Type) bool { return type.symbol == listType.symbol } def isIntMap(type Type) bool { return type.symbol == intMapType.symbol } def isStringMap(type Type) bool { return type.symbol == stringMapType.symbol } def isBaseType(derived Type, base Type) bool { if derived.isClass && base.isClass { while true { var baseType = derived.baseType if baseType == null { break } derived = substitute(baseType, derived.environment) if derived == base { return true } } } return false } def isImplementedInterface(classType Type, interfaceType Type) bool { if classType.isClass && interfaceType.isInterface { while classType != null { var interfaceTypes = classType.interfaceTypes if interfaceTypes != null { for type in interfaceTypes { if substitute(type, classType.environment) == interfaceType { return true } } } var baseType = classType.baseType if baseType == null { break } classType = substitute(baseType, classType.environment) } } return false } def unwrappedType(type Type) Type { if type.isWrapped { var inner = type.symbol.asObjectSymbol.wrappedType if inner != null { return unwrappedType(substitute(inner, type.environment)) } } return type } def canImplicitlyConvert(from Type, to Type) bool { if from == to { return true } if from == .DYNAMIC || to == .DYNAMIC { return true } if from == .NULL && to.isReference { return true } if from == intType && to == doubleType { return true } if isBaseType(from, to) { return true } if isImplementedInterface(from, to) { return true } if from.isEnumOrFlags && !to.isEnumOrFlags && isNumeric(to) { return true } return false } def canExplicitlyConvert(from Type, to Type) bool { from = unwrappedType(from) to = unwrappedType(to) if canImplicitlyConvert(from, to) { return true } if _canCastToNumeric(from) && _canCastToNumeric(to) { return true } if isBaseType(to, from) { return true } if isImplementedInterface(to, from) { return true } if to.isEnumOrFlags && isNumeric(from) { return true } return false } def commonImplicitType(left Type, right Type) Type { # Short-circuit early for identical types if left == right { return left } # Dynamic is a hole in the type system if left == .DYNAMIC || right == .DYNAMIC { return .DYNAMIC } # Check implicit conversions if canImplicitlyConvert(left, right) { return right } if canImplicitlyConvert(right, left) { return left } # Implement common implicit types for numeric types if isNumeric(left) && isNumeric(right) { return isInteger(left) && isInteger(right) ? intType : doubleType } # Check for a common base class if left.isClass && right.isClass { return _commonBaseType(left, right) } return null } def createBool(value bool) Node { return Node.createBool(value).withType(boolType) } def createInt(value int) Node { return Node.createInt(value).withType(intType) } def createDouble(value double) Node { return Node.createDouble(value).withType(doubleType) } def createString(value string) Node { return Node.createString(value).withType(stringType) } def createListType(itemType Type) Type { return substitute(listType, createEnvironment(listType.parameters, [itemType])) } def createIntMapType(valueType Type) Type { return substitute(intMapType, createEnvironment(intMapType.parameters, [valueType])) } def createStringMapType(valueType Type) Type { return substitute(stringMapType, createEnvironment(stringMapType.parameters, [valueType])) } def createEnvironment(parameters List, substitutions List) Environment { assert(parameters.count == substitutions.count) # Hash the inputs var hash = _hashTypes(_hashParameters(parameters), substitutions) var bucket = _environments.get(hash, null) # Check existing environments in the bucket for a match if bucket != null { for existing in bucket { if parameters.equals(existing.parameters) && substitutions.equals(existing.substitutions) { return existing } } } # Make a new bucket else { bucket = [] _environments[hash] = bucket } # Make a new environment var environment = Environment.new(parameters, substitutions) bucket.append(environment) return environment } def createLambdaType(argumentTypes List, returnType Type) Type { assert(returnType != .NULL) # This is used as a sentinel on LAMBDA_TYPE nodes var hash = _hashTypes(returnType != null ? returnType.id : -1, argumentTypes) var bucket = _lambdaTypes.get(hash, null) # Check existing types in the bucket for a match if bucket != null { for existing in bucket { if argumentTypes.equals(existing.argumentTypes) && returnType == existing.returnType { return existing } } } # Make a new bucket else { bucket = [] _lambdaTypes[hash] = bucket } # Make a new lambda type var type = Type.new(.LAMBDA, null) type.argumentTypes = argumentTypes.clone # Make a copy in case the caller mutates this later type.returnType = returnType bucket.append(type) return type } def mergeEnvironments(a Environment, b Environment, restrictions List) Environment { if a == null { return b } if b == null { return a } var parameters = a.parameters.clone var substitutions = substituteAll(a.substitutions, b) for i in 0..b.parameters.count { var parameter = b.parameters[i] var substitution = b.substitutions[i] if !(parameter in parameters) && (restrictions == null || parameter in restrictions) { parameters.append(parameter) substitutions.append(substitution) } } return createEnvironment(parameters, substitutions) } def parameterize(type Type) Type { var parameters = type.parameters if parameters == null { return type } assert(!type.isParameterized) var substitutions List = [] for parameter in parameters { substitutions.append(parameter.resolvedType) } return substitute(type, createEnvironment(parameters, substitutions)) } def substituteAll(types List, environment Environment) List { var substitutions List = [] for type in types { substitutions.append(substitute(type, environment)) } return substitutions } def substitute(type Type, environment Environment) Type { var existing = type.environment if environment == null || environment == existing { return type } # Merge the type environments (this matters for nested generics). For # object types, limit the parameters in the environment to just those # on this type and the base type. var parameters = type.parameters if existing != null { environment = mergeEnvironments(existing, environment, type.kind == .SYMBOL && type.symbol.kind.isFunctionOrOverloadedFunction ? null : parameters) } # Check to see if this has been computed before var rootType = type.kind == .SYMBOL ? type.symbol.resolvedType : type rootType.substitutionCache ?= {} var substituted = rootType.substitutionCache.get(environment.id, null) if substituted != null { return substituted } substituted = type if type.kind == .LAMBDA { var argumentTypes List = [] var returnType Type = null # Substitute function arguments for argumentType in type.argumentTypes { argumentTypes.append(substitute(argumentType, environment)) } # Substitute return type if type.returnType != null { returnType = substitute(type.returnType, environment) } substituted = createLambdaType(argumentTypes, returnType) } else if type.kind == .SYMBOL { var symbol = type.symbol # Parameters just need simple substitution if symbol.kind.isParameter { var index = environment.parameters.indexOf(symbol.asParameterSymbol) if index != -1 { substituted = environment.substitutions[index] } } # Symbols with type parameters are more complicated else { # Overloaded functions are also included even though they don't have # type parameters because the type environment needs to be bundled # for later substitution into individual matched overloads if parameters != null || symbol.kind.isFunctionOrOverloadedFunction { substituted = Type.new(.SYMBOL, symbol) substituted.environment = environment # Generate type substitutions if parameters != null { var found = true for parameter in parameters { found = parameter in environment.parameters if !found { break } } if found { substituted.substitutions = [] for parameter in parameters { substituted.substitutions.append(substitute(parameter.resolvedType, environment)) } } } # Substitute function arguments if type.argumentTypes != null { substituted.argumentTypes = [] for argumentType in type.argumentTypes { substituted.argumentTypes.append(substitute(argumentType, environment)) } } # Substitute return type if type.returnType != null { substituted.returnType = substitute(type.returnType, environment) } } } } rootType.substitutionCache[environment.id] = substituted return substituted } # Substitute the type parameters from one function into the other def substituteFunctionParameters(type Type, from FunctionSymbol, to FunctionSymbol) Type { if from.parameters != null && to.parameters != null && from.parameters.count == to.parameters.count { var substitutions List = [] for parameter in from.parameters { substitutions.append(parameter.resolvedType) } type = substitute(type, createEnvironment(to.parameters, substitutions)) } return type } enum Equivalence { EQUIVALENT EQUIVALENT_EXCEPT_RETURN_TYPE NOT_EQUIVALENT } def areFunctionSymbolsEquivalent(left FunctionSymbol, leftEnvironment Environment, right FunctionSymbol, rightEnvironment Environment) Equivalence { var leftType = left.resolvedType var rightType = right.resolvedType var leftReturn = leftType.returnType var rightReturn = rightType.returnType # Account for return types of functions from generic base types if leftReturn != null { leftReturn = substitute(leftReturn, leftEnvironment) } if rightReturn != null { rightReturn = substitute(rightReturn, rightEnvironment) } # Overloading by return type is not allowed, so only compare argument types if substitute(left.argumentOnlyType, leftEnvironment) == substitute(right.argumentOnlyType, rightEnvironment) { return leftReturn == rightReturn ? .EQUIVALENT : .EQUIVALENT_EXCEPT_RETURN_TYPE } # For generic functions, substitute dummy type parameters into both # functions and then compare. For example, these are equivalent: # # def foo(bar X) # def foo(baz Y) # if left.parameters != null && right.parameters != null { var leftArguments = leftType.argumentTypes var rightArguments = rightType.argumentTypes var argumentCount = leftArguments.count var parameterCount = left.parameters.count if argumentCount == rightArguments.count && parameterCount == right.parameters.count { # Generate enough dummy type parameters for i in _parameters.count..parameterCount { var symbol = ParameterSymbol.new(.PARAMETER_FUNCTION, "T\(i)") symbol.resolvedType = Type.new(.SYMBOL, symbol) symbol.state = .INITIALIZED _parameters.append(symbol.resolvedType) } # Substitute the same type parameters into both functions var parameters = _parameters.count == parameterCount ? _parameters : _parameters.slice(0, parameterCount) var leftParametersEnvironment = createEnvironment(left.parameters, parameters) var rightParametersEnvironment = createEnvironment(right.parameters, parameters) # Compare each argument for i in 0..argumentCount { if substitute(substitute(leftArguments[i], leftEnvironment), leftParametersEnvironment) != substitute(substitute(rightArguments[i], rightEnvironment), rightParametersEnvironment) { return .NOT_EQUIVALENT } } return leftReturn == null && rightReturn == null || leftReturn != null && rightReturn != null && substitute(leftReturn, leftParametersEnvironment) == substitute(rightReturn, rightParametersEnvironment) ? .EQUIVALENT : .EQUIVALENT_EXCEPT_RETURN_TYPE } } return .NOT_EQUIVALENT } def _canCastToNumeric(type Type) bool { return type == intType || type == doubleType || type == boolType } } namespace TypeCache { def _loadGlobalObject(global ObjectSymbol, name string, kind SymbolKind, flags SymbolFlags) Type { assert(kind.isObject) var symbol = global.members.get(name, null) assert(symbol != null) assert(symbol.kind == kind) var type = Type.new(.SYMBOL, symbol.asObjectSymbol) symbol.resolvedType = type symbol.flags |= flags return type } def _loadInstanceFunction(type Type, name string) Symbol { var symbol = type.symbol.asObjectSymbol.members.get(name, null) assert(symbol != null) assert(symbol.kind == .FUNCTION_INSTANCE || symbol.kind == .OVERLOADED_INSTANCE) return symbol } def _loadGlobalFunction(type Type, name string) Symbol { var symbol = type.symbol.asObjectSymbol.members.get(name, null) assert(symbol != null) assert(symbol.kind == .FUNCTION_GLOBAL || symbol.kind == .OVERLOADED_GLOBAL) return symbol } def _hashParameters(parameters List) int { var hash = 0 for parameter in parameters { hash = hashCombine(hash, parameter.id) } return hash } def _hashTypes(hash int, types List) int { for type in types { hash = hashCombine(hash, type.id) } return hash } def _commonBaseType(left Type, right Type) Type { var a = left while a != null { var b = right while b != null { if a == b { return a } b = b.baseType } a = a.baseType } return null } } } ================================================ FILE: src/middle/unicode.sk ================================================ namespace Skew { const UNICODE_LIBRARY = " namespace Unicode { enum Encoding { UTF8 UTF16 UTF32 } const STRING_ENCODING Encoding = TARGET == .CPLUSPLUS ? .UTF8 : TARGET == .CSHARP || TARGET == .JAVASCRIPT ? .UTF16 : .UTF32 class StringIterator { var value = \"\" var index = 0 var stop = 0 def reset(text string, start int) StringIterator { value = text index = start stop = text.count return self } def countCodePointsUntil(stop int) int { var count = 0 while index < stop && nextCodePoint >= 0 { count++ } return count } def previousCodePoint int def nextCodePoint int if STRING_ENCODING == .UTF8 { def previousCodePoint int { if index <= 0 { return -1 } var a = value[--index] if (a & 0xC0) != 0x80 { return a } if index <= 0 { return -1 } var b = value[--index] if (b & 0xC0) != 0x80 { return ((b & 0x1F) << 6) | (a & 0x3F) } if index <= 0 { return -1 } var c = value[--index] if (c & 0xC0) != 0x80 { return ((c & 0x0F) << 12) | ((b & 0x3F) << 6) | (a & 0x3F) } if index >= stop { return -1 } var d = value[--index] return ((d & 0x07) << 18) | ((c & 0x3F) << 12) | ((b & 0x3F) << 6) | (a & 0x3F) } def nextCodePoint int { if index >= stop { return -1 } var a = value[index++] if a < 0xC0 { return a } if index >= stop { return -1 } var b = value[index++] if a < 0xE0 { return ((a & 0x1F) << 6) | (b & 0x3F) } if index >= stop { return -1 } var c = value[index++] if a < 0xF0 { return ((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F) } if index >= stop { return -1 } var d = value[index++] return ((a & 0x07) << 18) | ((b & 0x3F) << 12) | ((c & 0x3F) << 6) | (d & 0x3F) } } else if STRING_ENCODING == .UTF16 { def previousCodePoint int { if index <= 0 { return -1 } var a = value[--index] if (a & 0xFC00) != 0xDC00 { return a } if index <= 0 { return -1 } var b = value[--index] return (b << 10) + a + (0x10000 - (0xD800 << 10) - 0xDC00) } def nextCodePoint int { if index >= stop { return -1 } var a = value[index++] if (a & 0xFC00) != 0xD800 { return a } if index >= stop { return -1 } var b = value[index++] return (a << 10) + b + (0x10000 - (0xD800 << 10) - 0xDC00) } } else { def previousCodePoint int { if index <= 0 { return -1 } return value[--index] } def nextCodePoint int { if index >= stop { return -1 } return value[index++] } } } namespace StringIterator { const INSTANCE = StringIterator.new } def codeUnitCountForCodePoints(codePoints List, encoding Encoding) int { var count = 0 switch encoding { case .UTF8 { for codePoint in codePoints { if codePoint < 0x80 { count++ } else if codePoint < 0x800 { count += 2 } else if codePoint < 0x10000 { count += 3 } else { count += 4 } } } case .UTF16 { for codePoint in codePoints { if codePoint < 0x10000 { count++ } else { count += 2 } } } case .UTF32 { count = codePoints.count } } return count } } class string { if Unicode.STRING_ENCODING == .UTF32 { def codePoints List { return codeUnits } } else { def codePoints List { var codePoints List = [] var instance = Unicode.StringIterator.INSTANCE instance.reset(self, 0) while true { var codePoint = instance.nextCodePoint if codePoint < 0 { return codePoints } codePoints.append(codePoint) } } } } namespace string { def fromCodePoints(codePoints List) string { var builder = StringBuilder.new for codePoint in codePoints { builder.append(fromCodePoint(codePoint)) } return builder.toString } if Unicode.STRING_ENCODING == .UTF8 { def fromCodePoint(codePoint int) string { return codePoint < 0x80 ? fromCodeUnit(codePoint) : ( codePoint < 0x800 ? fromCodeUnit(((codePoint >> 6) & 0x1F) | 0xC0) : ( codePoint < 0x10000 ? fromCodeUnit(((codePoint >> 12) & 0x0F) | 0xE0) : ( fromCodeUnit(((codePoint >> 18) & 0x07) | 0xF0) ) + fromCodeUnit(((codePoint >> 12) & 0x3F) | 0x80) ) + fromCodeUnit(((codePoint >> 6) & 0x3F) | 0x80) ) + fromCodeUnit((codePoint & 0x3F) | 0x80) } } else if Unicode.STRING_ENCODING == .UTF16 { def fromCodePoint(codePoint int) string { return codePoint < 0x10000 ? fromCodeUnit(codePoint) : fromCodeUnit(((codePoint - 0x10000) >> 10) + 0xD800) + fromCodeUnit(((codePoint - 0x10000) & ((1 << 10) - 1)) + 0xDC00) } } else { def fromCodePoint(codePoint int) string { return fromCodeUnit(codePoint) } } } " } ================================================ FILE: tests/cplusplus.sk ================================================ namespace Skew.Tests { def testCPlusPlus { # Test entry point test(" @entry def test {} ", " void main() { } ").cpp # Test entry point test(" @entry def test int { return 0 } ", " int main() { return 0; } ").cpp # Test entry point test(" @entry def test(x List) { } ", " void main(int argc, char** argv) { Skew::List *x = new Skew::List(); if (*argv++) { while (*argv) { x->append(*argv++); } } } ").cpp # Test entry point test(" @entry def test(x List) int { return x.count } ", " int main(int argc, char** argv) { Skew::List *x = new Skew::List(); if (*argv++) { while (*argv) { x->append(*argv++); } } return x->count(); } ").cpp # Test entry point inside a namespace test(" namespace ns { @entry def test(x List) int { test([]) return x.count } } ", " int main(int argc, char** argv) { Skew::List *x = new Skew::List(); if (*argv++) { while (*argv) { x->append(*argv++); } } main(new Skew::List()); return x->count(); } ").cpp # Test entry point name collisions test(" @entry def main(x List) int { var argc = 0 var argv = 0 return argc + argv } ", " int main(int argc1, char** argv1) { Skew::List *x = new Skew::List(); if (*argv1++) { while (*argv1) { x->append(*argv1++); } } int argc = 0; int argv = 0; return argc + argv; } ").cpp # Basic class hierarchy test(" @export class Foo { const instanceVariable1 int const instanceVariable2 = 0 def instanceMethod {} def instanceMethod2 {} } namespace Foo { const staticVariable = 0 } @export class Bar : Foo { const instanceVariable3 int const instanceVariable4 = 0 over instanceMethod { super } def instanceMethod3 {} } namespace Bar { const staticVariable2 = 0 } ", " struct Foo; struct Bar; struct Foo : virtual Skew::Object { virtual void instanceMethod(); void instanceMethod2(); Foo(int instanceVariable1); int instanceVariable1; int instanceVariable2; static int staticVariable; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct Bar : Foo { virtual void instanceMethod() override; void instanceMethod3(); Bar(int instanceVariable1, int instanceVariable3); int instanceVariable3; int instanceVariable4; static int staticVariable2; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; int Foo::staticVariable = 0; int Bar::staticVariable2 = 0; void Foo::instanceMethod() { } void Foo::instanceMethod2() { } Foo::Foo(int instanceVariable1) { this->instanceVariable1 = instanceVariable1; this->instanceVariable2 = 0; } #ifdef SKEW_GC_MARK_AND_SWEEP void Foo::__gc_mark() { } #endif void Bar::instanceMethod() { Foo::instanceMethod(); } void Bar::instanceMethod3() { } Bar::Bar(int instanceVariable1, int instanceVariable3) : Foo::Foo(instanceVariable1) { this->instanceVariable3 = instanceVariable3; this->instanceVariable4 = 0; } #ifdef SKEW_GC_MARK_AND_SWEEP void Bar::__gc_mark() { Foo::__gc_mark(); } #endif ").cpp # Basic interface usage test(" @export class Foo :: Bar, Baz { def instanceMethod {} def instanceMethod(x int) {} def instanceMethod2 {} } interface Bar { def instanceMethod def instanceMethod(x int) } interface Baz { def instanceMethod def instanceMethod2 } ", " struct Bar; struct Baz; struct Foo; struct Bar : virtual Skew::Object { virtual void instanceMethod() = 0; virtual void instanceMethod(int x) = 0; }; struct Baz : virtual Skew::Object { virtual void instanceMethod() = 0; virtual void instanceMethod2() = 0; }; struct Foo : Bar, Baz { virtual void instanceMethod(); virtual void instanceMethod(int x); virtual void instanceMethod2(); Foo(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; void Foo::instanceMethod() { } void Foo::instanceMethod(int x) { } void Foo::instanceMethod2() { } Foo::Foo() { } #ifdef SKEW_GC_MARK_AND_SWEEP void Foo::__gc_mark() { } #endif ").cpp # Interface usage with tree shaking test(" class Foo :: Bar, Baz { def instanceMethod {} def instanceMethod(x int) {} def instanceMethod2 {} } interface Bar { def instanceMethod def instanceMethod(x int) } interface Baz { def instanceMethod def instanceMethod2 } @export def test { var foo = Foo.new foo.instanceMethod } ", " struct Bar; struct Baz; struct Foo; struct Bar : virtual Skew::Object { virtual void instanceMethod1() = 0; }; struct Baz : virtual Skew::Object { virtual void instanceMethod1() = 0; }; struct Foo : Bar, Baz { virtual void instanceMethod1(); Foo(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; void test() { Foo *foo = new Foo(); foo->instanceMethod1(); } void Foo::instanceMethod1() { } Foo::Foo() { } #ifdef SKEW_GC_MARK_AND_SWEEP void Foo::__gc_mark() { } #endif ").cpp # Type wrapping test(" type Foo : double { def scaleBy(scale Foo) Foo { return ((self as double) * (scale as double)) as Foo } } namespace Foo { const FOO = 0.5 as Foo } @export def test(x double) Foo { return (x as Foo).scaleBy(Foo.FOO) } ", " namespace Foo { double scaleBy(double self, double scale); extern double FOO; } namespace Foo { double FOO = 0.5; } double test(double x) { return Foo::scaleBy(x, Foo::FOO); } double Foo::scaleBy(double self, double scale) { return self * scale; } ").cpp # Casting between enums and integers must be explicit test(" enum Foo { FOO } @export def test Foo { var x = Foo.FOO return ((x as int) * 1) as Foo } ", " enum struct Foo { FOO = 0, }; Foo test() { Foo x = Foo::FOO; return (Foo)((int)x * 1); } ").cpp # Lists and maps test(" @export def foo { var x = [1, 2, 3] var y = {1: 2, 3: 4} var z = {\"1\": 2, \"3\": 4} } ", " #include void foo() { Skew::List *x = new Skew::List({1, 2, 3}); Skew::IntMap *y = new Skew::IntMap({std::make_pair(1, 2), std::make_pair(3, 4)}); Skew::StringMap *z = new Skew::StringMap({std::make_pair(\"1\"_s, 2), std::make_pair(\"3\"_s, 4)}); } ").cpp # Test math constants test(" @export def main { dynamic.foo(Math.NAN, Math.INFINITY, -Math.INFINITY) dynamic.foo(Math.NAN.toString, Math.INFINITY.toString, (-Math.INFINITY).toString) } ", " void main() { foo(0.0 / 0.0, 1.0 / 0.0, -(1.0 / 0.0)); foo(__doubleToString(0.0 / 0.0), __doubleToString(1.0 / 0.0), __doubleToString(-(1.0 / 0.0))); } ").cpp.inlineAllFunctions # Test math constants test(" @export def main { dynamic.foo(Math.NAN, Math.INFINITY, -Math.INFINITY) dynamic.foo(Math.NAN.toString, Math.INFINITY.toString, (-Math.INFINITY).toString) } ", " #include void main() { foo(NAN, INFINITY, -INFINITY); foo(__doubleToString(NAN), __doubleToString(INFINITY), __doubleToString(-INFINITY)); } ").cpp.inlineAllFunctions.foldAllConstants # Test math toString test(" @export def main { dynamic.foo(0.toString, 1.0.toString, (-1.0).toString, 0.5.toString, (-0.5).toString) } ", " void main() { foo(__intToString(0), __doubleToString(1.0), __doubleToString(-1.0), __doubleToString(0.5), __doubleToString(-0.5)); } ").cpp.inlineAllFunctions # Double literals must be emitted with a decimal point test(" @export def main(x double) { x = 1.0 / 2.0 x = 1e100 / 2e100 x = 1e-100 / 2e-100 x = 1.5 / 2.5 x = 1.5e100 / 2.5e100 x = 1.5e-100 / 2.5e-100 } ", " void main(double x) { x = 1.0 / 2.0; x = 1.0e+100 / 2.0e+100; x = 1.0e-100 / 2.0e-100; x = 1.5 / 2.5; x = 1.5e+100 / 2.5e+100; x = 1.5e-100 / 2.5e-100; } ").cpp # Check for a crash when converting switch statements to if chains test(" @export def main { switch \"a\" { case \"b\" {} case \"c\" {} default {} } } ", " void main() { Skew::string value = \"a\"_s; if (value == \"b\"_s) { } else if (value == \"c\"_s) { } else { } } ").cpp # Check different integer types test(" enum Foo { A, B def foo {} } flags Bar { C, D def foo {} } type Baz : int { def foo {} } namespace Baz { const X = 0 as Baz } @export def test int { var a = Foo.A var c = Bar.C var x = 0 as Baz a.foo c.foo x.foo return a + c + x as int + Foo.B + Bar.D + Baz.X as int } ", " enum struct Foo { A = 0, B = 1, }; namespace Bar { enum { C = 1, D = 2, }; } namespace Baz { void foo(int self); extern int X; } namespace in_Foo { void foo(Foo self); } namespace in_Bar { void foo(int self); } namespace Baz { int X = 0; } int test() { Foo a = Foo::A; int c = Bar::C; int x = 0; in_Foo::foo(a); in_Bar::foo(c); Baz::foo(x); return (int)a + c + x + (int)Foo::B + Bar::D + Baz::X; } void Baz::foo(int self) { } void in_Foo::foo(Foo self) { } void in_Bar::foo(int self) { } ").cpp # Check code generation for flags types test(" flags Foo { X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X27 X28 X29 X30 X31 } @export def test { if !((.X0 | .X1) in (Foo.X30 | .X31)) { var x = Foo.X0 x = .X1 | .X2 x &= ~.X3 } } ", " namespace Foo { enum { X0 = 1, X1 = 2, X2 = 4, X3 = 8, X30 = 1073741824, X31 = -2147483648, }; } void test() { if (!(((Foo::X0 | Foo::X1) & (Foo::X30 | Foo::X31)) != 0)) { int x = Foo::X0; x = Foo::X1 | Foo::X2; x &= ~Foo::X3; } } ").cpp # Check dynamic types test(" @export def test(foo dynamic.Foo) dynamic.Bar { return foo as dynamic.Bar + 0 as dynamic.Int + 0 as dynamic } ", " Bar test(Foo foo) { return (Bar)foo + (Int)0 + 0; } ").cpp # Check bit shifts test(" @export def test(x int) { x = x << x x = x >> x x = x >>> x x <<= x x >>= x x >>>= x } ", " namespace in_int { int unsignedRightShift(int self, int x); } void test(int x) { x = x << x; x = x >> x; x = in_int::unsignedRightShift(x, x); x <<= x; x >>= x; x = in_int::unsignedRightShift(x, x); } int in_int::unsignedRightShift(int self, int x) { return (int)((unsigned)self >> x); } ").cpp # Test lambda conversion with scope capture and currying test(" @export class Foo { var value = 0 def test(x int) int { var y = 0 var f = (a int) => => (b int) => => value + x + y + a + b return f(1)()(2)() } } ", " struct Foo; struct FooTestEnv; struct FooTestLambda; struct FooTestEnv1; struct FooTestLambda1; struct FooTestLambda2; struct FooTestEnv2; struct FooTestLambda3; struct Foo : virtual Skew::Object { int test(int x); Foo(); int value; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct FooTestEnv : virtual Skew::Object { FooTestEnv(); Foo *self; int x; int y; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct FooTestLambda : Skew::Fn1 *, int> *> *, int> { FooTestLambda(FooTestEnv *env); virtual Skew::Fn0 *, int> *> *run(int a); FooTestEnv *env; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct FooTestEnv1 : virtual Skew::Object { FooTestEnv1(FooTestEnv *env); int a; FooTestEnv *env; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct FooTestLambda1 : Skew::Fn0 *, int> *> { FooTestLambda1(FooTestEnv *env, FooTestEnv1 *env1); virtual Skew::Fn1 *, int> *run(); FooTestEnv *env; FooTestEnv1 *env1; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct FooTestLambda2 : Skew::Fn1 *, int> { FooTestLambda2(FooTestEnv *env, FooTestEnv1 *env1); virtual Skew::Fn0 *run(int b); FooTestEnv *env; FooTestEnv1 *env1; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct FooTestEnv2 : virtual Skew::Object { FooTestEnv2(FooTestEnv *env, FooTestEnv1 *env1); int b; FooTestEnv *env; FooTestEnv1 *env1; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct FooTestLambda3 : Skew::Fn0 { FooTestLambda3(FooTestEnv *env, FooTestEnv1 *env1, FooTestEnv2 *env2); virtual int run(); FooTestEnv *env; FooTestEnv1 *env1; FooTestEnv2 *env2; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; int Foo::test(int x) { FooTestEnv *env = new FooTestEnv(); env->self = this; env->x = x; env->y = 0; Skew::Fn1 *, int> *> *, int> *f = new FooTestLambda(env); return f->run(1)->run()->run(2)->run(); } Foo::Foo() { this->value = 0; } #ifdef SKEW_GC_MARK_AND_SWEEP void Foo::__gc_mark() { } #endif FooTestEnv::FooTestEnv() { } #ifdef SKEW_GC_MARK_AND_SWEEP void FooTestEnv::__gc_mark() { Skew::GC::mark(self); } #endif FooTestLambda::FooTestLambda(FooTestEnv *env) { this->env = env; } Skew::Fn0 *, int> *> *FooTestLambda::run(int a) { FooTestEnv1 *env1 = new FooTestEnv1(this->env); env1->a = a; return new FooTestLambda1(env1->env, env1); } #ifdef SKEW_GC_MARK_AND_SWEEP void FooTestLambda::__gc_mark() { Skew::GC::mark(env); } #endif FooTestEnv1::FooTestEnv1(FooTestEnv *env) { this->env = env; } #ifdef SKEW_GC_MARK_AND_SWEEP void FooTestEnv1::__gc_mark() { Skew::GC::mark(env); } #endif FooTestLambda1::FooTestLambda1(FooTestEnv *env, FooTestEnv1 *env1) { this->env = env; this->env1 = env1; } Skew::Fn1 *, int> *FooTestLambda1::run() { return new FooTestLambda2(this->env, this->env1); } #ifdef SKEW_GC_MARK_AND_SWEEP void FooTestLambda1::__gc_mark() { Skew::GC::mark(env); Skew::GC::mark(env1); } #endif FooTestLambda2::FooTestLambda2(FooTestEnv *env, FooTestEnv1 *env1) { this->env = env; this->env1 = env1; } Skew::Fn0 *FooTestLambda2::run(int b) { FooTestEnv2 *env2 = new FooTestEnv2(this->env, this->env1); env2->b = b; return new FooTestLambda3(env2->env, env2->env1, env2); } #ifdef SKEW_GC_MARK_AND_SWEEP void FooTestLambda2::__gc_mark() { Skew::GC::mark(env); Skew::GC::mark(env1); } #endif FooTestEnv2::FooTestEnv2(FooTestEnv *env, FooTestEnv1 *env1) { this->env = env; this->env1 = env1; } #ifdef SKEW_GC_MARK_AND_SWEEP void FooTestEnv2::__gc_mark() { Skew::GC::mark(env); Skew::GC::mark(env1); } #endif FooTestLambda3::FooTestLambda3(FooTestEnv *env, FooTestEnv1 *env1, FooTestEnv2 *env2) { this->env = env; this->env1 = env1; this->env2 = env2; } int FooTestLambda3::run() { return this->env->self->value + this->env->x + this->env->y + this->env1->a + this->env2->b; } #ifdef SKEW_GC_MARK_AND_SWEEP void FooTestLambda3::__gc_mark() { Skew::GC::mark(env); Skew::GC::mark(env1); Skew::GC::mark(env2); } #endif ").cpp # Test lambda conversion at global scope test(" var f = (x int) => (y int) => x + y @entry def test int { return f(1)(2) } ", " struct Lambda; struct LambdaRunEnv; struct LambdaRunLambda; struct Lambda : Skew::Fn1 *, int> { Lambda(); virtual Skew::Fn1 *run(int x); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct LambdaRunEnv : virtual Skew::Object { LambdaRunEnv(); int x; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct LambdaRunLambda : Skew::Fn1 { LambdaRunLambda(LambdaRunEnv *env); virtual int run(int y); LambdaRunEnv *env; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; Skew::Root *, int>> f = new Lambda(); int main() { return f->run(1)->run(2); } Lambda::Lambda() { } Skew::Fn1 *Lambda::run(int x) { LambdaRunEnv *env = new LambdaRunEnv(); env->x = x; return new LambdaRunLambda(env); } #ifdef SKEW_GC_MARK_AND_SWEEP void Lambda::__gc_mark() { } #endif LambdaRunEnv::LambdaRunEnv() { } #ifdef SKEW_GC_MARK_AND_SWEEP void LambdaRunEnv::__gc_mark() { } #endif LambdaRunLambda::LambdaRunLambda(LambdaRunEnv *env) { this->env = env; } int LambdaRunLambda::run(int y) { return this->env->x + y; } #ifdef SKEW_GC_MARK_AND_SWEEP void LambdaRunLambda::__gc_mark() { Skew::GC::mark(env); } #endif ").cpp # Check for a crash with nested loop handling test(" @export def test { var foo = 0 var bar = () int => { while true { for baz in [1] { return foo + baz } } } bar() } ", " struct TestEnv; struct TestLambda; struct TestEnv : virtual Skew::Object { TestEnv(); int foo; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct TestLambda : Skew::Fn0 { TestLambda(TestEnv *env); virtual int run(); TestEnv *env; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; void test() { TestEnv *env = new TestEnv(); env->foo = 0; Skew::Fn0 *bar = new TestLambda(env); bar->run(); } TestEnv::TestEnv() { } #ifdef SKEW_GC_MARK_AND_SWEEP void TestEnv::__gc_mark() { } #endif TestLambda::TestLambda(TestEnv *env) { this->env = env; } int TestLambda::run() { while (true) { for (int baz : *new Skew::List({1})) { return this->env->foo + baz; } } } #ifdef SKEW_GC_MARK_AND_SWEEP void TestLambda::__gc_mark() { Skew::GC::mark(env); } #endif ").cpp # Check that recursive lambdas work test(" @export def main { var f fn() f = => f() f() } ", " struct MainEnv; struct MainLambda; struct MainEnv : virtual Skew::Object { MainEnv(); Skew::FnVoid0 *f; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct MainLambda : Skew::FnVoid0 { MainLambda(MainEnv *env); virtual void run(); MainEnv *env; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; void main() { MainEnv *env = new MainEnv(); env->f = nullptr; env->f = new MainLambda(env); env->f->run(); } MainEnv::MainEnv() { } #ifdef SKEW_GC_MARK_AND_SWEEP void MainEnv::__gc_mark() { Skew::GC::mark(f); } #endif MainLambda::MainLambda(MainEnv *env) { this->env = env; } void MainLambda::run() { this->env->f->run(); } #ifdef SKEW_GC_MARK_AND_SWEEP void MainLambda::__gc_mark() { Skew::GC::mark(env); } #endif ").cpp # Check for a crash in captured variable substitution test(" @export def main { var x = [1] var f = => x for y in x {} f() } ", " struct MainEnv; struct MainLambda; struct MainEnv : virtual Skew::Object { MainEnv(); Skew::List *x; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct MainLambda : Skew::Fn0 *> { MainLambda(MainEnv *env); virtual Skew::List *run(); MainEnv *env; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; void main() { MainEnv *env = new MainEnv(); env->x = new Skew::List({1}); Skew::Fn0 *> *f = new MainLambda(env); for (int y : *env->x) { } f->run(); } MainEnv::MainEnv() { } #ifdef SKEW_GC_MARK_AND_SWEEP void MainEnv::__gc_mark() { Skew::GC::mark(x); } #endif MainLambda::MainLambda(MainEnv *env) { this->env = env; } Skew::List *MainLambda::run() { return this->env->x; } #ifdef SKEW_GC_MARK_AND_SWEEP void MainLambda::__gc_mark() { Skew::GC::mark(env); } #endif ").cpp # Test break statements inside a switch test(" @export def test(x int, y bool) { while true { switch x { case 0 { if y { break } } } } } ", " void test(int x, bool y) { while (true) { switch (x) { case 0: { if (y) { goto label; } break; } } } label:; } ").cpp # Avoid emitting an empty anonymous enum to avoid a clang warning test(" @export { flags Foo { FOO # Comment 1 # Comment 2 BAR BAZ } flags Bar {} } ", " namespace Foo { enum { // Comment 1 FOO = 1, // Comment 2 BAR = 2, BAZ = 4, }; } namespace Bar { } ").cpp # Check for accidental mutation of argument types during globalization test(" class Foo { def test(foo Foo) {} } def foo { var visit fn(Foo) visit = foo => visit(foo) } @export def test { Foo.new.test(null) foo } ", " struct Foo; struct FooEnv; struct FooLambda; struct Foo : virtual Skew::Object { static void test(Foo *self, Foo *foo); Foo(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct FooEnv : virtual Skew::Object { FooEnv(); Skew::FnVoid1 *visit; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct FooLambda : Skew::FnVoid1 { FooLambda(FooEnv *env); virtual void run(Foo *foo); FooEnv *env; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; void foo() { FooEnv *env = new FooEnv(); env->visit = nullptr; env->visit = new FooLambda(env); } void test() { Foo::test(new Foo(), (Foo *)nullptr); foo(); } void Foo::test(Foo *self, Foo *foo) { } Foo::Foo() { } #ifdef SKEW_GC_MARK_AND_SWEEP void Foo::__gc_mark() { } #endif FooEnv::FooEnv() { } #ifdef SKEW_GC_MARK_AND_SWEEP void FooEnv::__gc_mark() { Skew::GC::mark(visit); } #endif FooLambda::FooLambda(FooEnv *env) { this->env = env; } void FooLambda::run(Foo *foo) { this->env->visit->run(foo); } #ifdef SKEW_GC_MARK_AND_SWEEP void FooLambda::__gc_mark() { Skew::GC::mark(env); } #endif ").cpp.globalizeAllFunctions # Test dead code elimination and imports test(" @import { var a fn() def b(x fn(int)) } var c fn(int, int) def d(x fn(int, int, int)) {} @export def test { a = => {} b(x => {}) c = (x, y) => {} d((x, y, z) => {}) } ", " struct TestLambda; struct TestLambda1; struct TestLambda2; struct TestLambda3; struct TestLambda : Skew::FnVoid0 { TestLambda(); virtual void run(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct TestLambda1 : Skew::FnVoid1 { TestLambda1(); virtual void run(int x); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct TestLambda2 : Skew::FnVoid2 { TestLambda2(); virtual void run(int x, int y); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct TestLambda3 : Skew::FnVoid3 { TestLambda3(); virtual void run(int x, int y, int z); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; Skew::Root> c = nullptr; void d(Skew::FnVoid3 *x) { } void test() { a = new TestLambda(); b(new TestLambda1()); c = new TestLambda2(); d(new TestLambda3()); } TestLambda::TestLambda() { } void TestLambda::run() { } #ifdef SKEW_GC_MARK_AND_SWEEP void TestLambda::__gc_mark() { } #endif TestLambda1::TestLambda1() { } void TestLambda1::run(int x) { } #ifdef SKEW_GC_MARK_AND_SWEEP void TestLambda1::__gc_mark() { } #endif TestLambda2::TestLambda2() { } void TestLambda2::run(int x, int y) { } #ifdef SKEW_GC_MARK_AND_SWEEP void TestLambda2::__gc_mark() { } #endif TestLambda3::TestLambda3() { } void TestLambda3::run(int x, int y, int z) { } #ifdef SKEW_GC_MARK_AND_SWEEP void TestLambda3::__gc_mark() { } #endif ").cpp # Test lambda conversion in loops test(" @export def test(list List) { (=> list)() for i in list { (=> list.append(i))() } } ", " struct TestEnv; struct TestLambda; struct TestEnv1; struct TestLambda1; struct TestEnv : virtual Skew::Object { TestEnv(); Skew::List *list; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct TestLambda : Skew::Fn0 *> { TestLambda(TestEnv *env); virtual Skew::List *run(); TestEnv *env; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct TestEnv1 : virtual Skew::Object { TestEnv1(TestEnv *env); int i; TestEnv *env; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct TestLambda1 : Skew::FnVoid0 { TestLambda1(TestEnv *env, TestEnv1 *env1); virtual void run(); TestEnv *env; TestEnv1 *env1; #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; void test(Skew::List *list) { TestEnv *env = new TestEnv(); env->list = list; (new TestLambda(env))->run(); for (int i : *env->list) { TestEnv1 *env1 = new TestEnv1(env); env1->i = i; (new TestLambda1(env1->env, env1))->run(); } } TestEnv::TestEnv() { } #ifdef SKEW_GC_MARK_AND_SWEEP void TestEnv::__gc_mark() { Skew::GC::mark(list); } #endif TestLambda::TestLambda(TestEnv *env) { this->env = env; } Skew::List *TestLambda::run() { return this->env->list; } #ifdef SKEW_GC_MARK_AND_SWEEP void TestLambda::__gc_mark() { Skew::GC::mark(env); } #endif TestEnv1::TestEnv1(TestEnv *env) { this->env = env; } #ifdef SKEW_GC_MARK_AND_SWEEP void TestEnv1::__gc_mark() { Skew::GC::mark(env); } #endif TestLambda1::TestLambda1(TestEnv *env, TestEnv1 *env1) { this->env = env; this->env1 = env1; } void TestLambda1::run() { this->env->list->append(this->env1->i); } #ifdef SKEW_GC_MARK_AND_SWEEP void TestLambda1::__gc_mark() { Skew::GC::mark(env); Skew::GC::mark(env1); } #endif ").cpp # Constructors on dynamic types shouldn't need parentheses test(" @export def test { dynamic.Foo.new dynamic.Foo.new() dynamic.Foo.new.test dynamic.Foo.new().test dynamic.Foo.new.test dynamic.Foo.new().test var a = dynamic.Foo.new var b = dynamic.Foo.new() var c = dynamic.Foo.new.test var d = dynamic.Foo.new().test var e = dynamic.Foo.new.test var f = dynamic.Foo.new().test } ", " void test() { new Foo(); new Foo(); (new Foo())->test; (new Foo())->test; (new Foo())->test; (new Foo())->test; void *a = new Foo(); void *b = new Foo(); void *c = (new Foo())->test; void *d = (new Foo())->test; void *e = (new Foo())->test; void *f = (new Foo())->test; } ").cpp # Check preserving enum types for constant-folded enum values test(" enum Foo { FOO BAR } def foo(x Foo) { bar(x) } @import def bar(x int) @export def test(x bool) { foo(x ? .BAR : .FOO) } ", " enum struct Foo { }; void test(bool x) { bar((int)(x ? (Foo)1 : (Foo)0)); } ").cpp.foldAllConstants.inlineAllFunctions # Test parentheses generation to avoid C++ compiler warnings test(" @export def test(x int, y int) { x = x == 0 && y == 1 || x == 2 ? 1 : 0 x = x & y | x # ^ x = x + y ^ x x = x - y ^ x x = x * y ^ x x = x / y ^ x # | x = x + y | x x = x - y | x x = x * y | x x = x / y | x # & x = x + y & x x = x - y & x x = x * y & x x = x / y & x # << x = x + y << x x = x - y << x x = x * y << x x = x / y << x # >> x = x + y >> x x = x - y >> x x = x * y >> x x = x / y >> x } ", " void test(int x, int y) { x = (x == 0 && y == 1) || x == 2 ? 1 : 0; x = (x & y) | x; // ^ x = (x + y) ^ x; x = (x - y) ^ x; x = x * y ^ x; x = x / y ^ x; // | x = (x + y) | x; x = (x - y) | x; x = x * y | x; x = x / y | x; // & x = (x + y) & x; x = (x - y) & x; x = x * y & x; x = x / y & x; // << x = (x + y) << x; x = (x - y) << x; x = x * y << x; x = x / y << x; // >> x = (x + y) >> x; x = (x - y) >> x; x = x * y >> x; x = x / y >> x; } ").cpp # Check forward-declaration of global variables test(" namespace Foo { const FOO = 0 } const foo = Foo.FOO @export def test { foo } ", " namespace Foo { extern int FOO; } int foo = Foo::FOO; namespace Foo { int FOO = 0; } void test() { foo; } ").cpp # Check that exporting wrapped types with constructors is feasible test(" @export type Foo : int { def foo int { return self as int * 2 } } namespace Foo { def new(x int) Foo { return (x / 2) as Foo } } @export def test { Foo.new(0).foo } ", " namespace Foo { int foo(int self); int _new(int x); } void test() { Foo::foo(Foo::_new(0)); } int Foo::foo(int self) { return self * 2; } int Foo::_new(int x) { return x / 2; } ").cpp # Check for a validation error with types inside exported classes test(" @export class Foo { enum Bar { BAZ } } @export def test { Foo.Bar.BAZ } ", " struct Foo; namespace in_Foo { enum struct Bar { BAZ = 0, }; } struct Foo : virtual Skew::Object { Foo(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; namespace in_Bar { Skew::string toString(in_Foo::Bar self); extern Skew::Root> _strings; } namespace in_Foo { } namespace in_Bar { Skew::Root> _strings = new Skew::List({\"BAZ\"_s}); } void test() { in_Foo::Bar::BAZ; } Foo::Foo() { } #ifdef SKEW_GC_MARK_AND_SWEEP void Foo::__gc_mark() { } #endif Skew::string in_Bar::toString(in_Foo::Bar self) { return (*in_Bar::_strings.get())[(int)self]; } ").cpp # Check wrapped types with type parameters test(" type Foo : List { def size int { return (self as List).count } } @entry def main { (List.new as Foo).size } ", " namespace Foo { } namespace in_Foo { template int size(Skew::List *self); } void main() { in_Foo::size(new Skew::List()); } template int in_Foo::size(Skew::List *self) { return self->count(); } ").cpp # Check the "is" operator test(" class Foo {} class Bar : Foo {} @entry def main { Foo.new is Bar == true || true == Foo.new is Bar } ", " struct Foo; struct Bar; struct Foo : virtual Skew::Object { Foo(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; struct Bar : Foo { Bar(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; void main() { dynamic_cast(new Foo()) != nullptr == true || true == (dynamic_cast(new Foo()) != nullptr); } Foo::Foo() { } #ifdef SKEW_GC_MARK_AND_SWEEP void Foo::__gc_mark() { } #endif Bar::Bar() : Foo::Foo() { } #ifdef SKEW_GC_MARK_AND_SWEEP void Bar::__gc_mark() { Foo::__gc_mark(); } #endif ").cpp # Need to emit ".get()" for global variables inside roots test(" class Foo {} var foo = Foo.new @entry def main { var x = Foo.new x = foo ?? x foo = x ?? foo } ", " struct Foo; struct Foo : virtual Skew::Object { Foo(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; Skew::Root foo = new Foo(); void main() { Foo *x = new Foo(); x = foo.get() != (Foo *)nullptr ? foo.get() : x; foo = x != (Foo *)nullptr ? x : foo.get(); } Foo::Foo() { } #ifdef SKEW_GC_MARK_AND_SWEEP void Foo::__gc_mark() { } #endif ").cpp # Make sure to fully specify the base class in __gc_mark() for derived classes to avoid infinite recursion test(" class A.Foo {} @export class B.Foo : A.Foo {} ", " namespace A { struct Foo; } namespace B { struct Foo; } namespace A { struct Foo : virtual Skew::Object { Foo(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; } namespace B { struct Foo : A::Foo { Foo(); #ifdef SKEW_GC_MARK_AND_SWEEP virtual void __gc_mark() override; #endif }; } A::Foo::Foo() { } #ifdef SKEW_GC_MARK_AND_SWEEP void A::Foo::__gc_mark() { } #endif B::Foo::Foo() : A::Foo::Foo() { } #ifdef SKEW_GC_MARK_AND_SWEEP void B::Foo::__gc_mark() { A::Foo::__gc_mark(); } #endif ").cpp # Check block comments before a statement test(" @entry def main { # This is # a block # comment return } ", " void main() { // This is // a block // comment return; } ").cpp # Check trailing block comments test(" @entry def main { # This is # a block # comment } ", " void main() { // This is // a block // comment } ").cpp } } ================================================ FILE: tests/csharp.sk ================================================ namespace Skew.Tests { def testCSharp { # Test entry point test(" @entry def test {} ", " public class Globals { public static void Main() { } } ").csharp # Test entry point test(" @entry def test int { return 0 } ", " public class Globals { public static int Main() { return 0; } } ").csharp # Test entry point test(" @entry def test(x List) { } ", " using System.Collections.Generic; public class Globals { public static void Main(string[] x1) { List x = new List(x1); } } ").csharp.inlineAllFunctions # Test entry point test(" @entry def test(x List) int { return x.count } ", " using System.Collections.Generic; public class Globals { public static int Main(string[] x1) { List x = new List(x1); return x.Count; } } ").csharp.inlineAllFunctions # Test entry point inside a namespace test(" namespace ns { @entry def test(x List) int { test([]) return x.count } } ", " using System.Collections.Generic; public class ns { public static int Main(string[] x1) { List x = new List(x1); ns.Main(new List()); return x.Count; } } ").csharp.inlineAllFunctions # Basic class hierarchy test(" @export class Foo { const instanceVariable1 int const instanceVariable2 = 0 def instanceMethod {} def instanceMethod2 {} } namespace Foo { const staticVariable = 0 } @export class Bar : Foo { const instanceVariable3 int const instanceVariable4 = 0 over instanceMethod { super } def instanceMethod3 {} } namespace Bar { const staticVariable2 = 0 } ", " public class Foo { public int instanceVariable1; public int instanceVariable2 = 0; public static int staticVariable = 0; public virtual void instanceMethod() { } public void instanceMethod2() { } public Foo(int instanceVariable1) { this.instanceVariable1 = instanceVariable1; this.instanceVariable2 = 0; } } public class Bar : Foo { public int instanceVariable3; public int instanceVariable4 = 0; public static int staticVariable2 = 0; public override void instanceMethod() { base.instanceMethod(); } public void instanceMethod3() { } public Bar(int instanceVariable1, int instanceVariable3) : base(instanceVariable1) { this.instanceVariable3 = instanceVariable3; this.instanceVariable4 = 0; } } ").csharp # Basic interface usage test(" @export class Foo :: Bar, Baz { def instanceMethod {} def instanceMethod(x int) {} def instanceMethod2 {} } interface Bar { def instanceMethod def instanceMethod(x int) } interface Baz { def instanceMethod def instanceMethod2 } ", " public class Foo : Bar, Baz { public virtual void instanceMethod() { } public virtual void instanceMethod(int x) { } public virtual void instanceMethod2() { } public Foo() { } } public interface Bar { void instanceMethod(); void instanceMethod(int x); } public interface Baz { void instanceMethod(); void instanceMethod2(); } ").csharp # Interface usage with tree shaking test(" class Foo :: Bar, Baz { def instanceMethod {} def instanceMethod(x int) {} def instanceMethod2 {} } interface Bar { def instanceMethod def instanceMethod(x int) } interface Baz { def instanceMethod def instanceMethod2 } @export def test { var foo = Foo.new foo.instanceMethod } ", " public class Foo : Bar, Baz { public virtual void instanceMethod1() { } public Foo() { } } public interface Bar { void instanceMethod1(); } public interface Baz { void instanceMethod1(); } public class Globals { public static void test() { Foo foo = new Foo(); foo.instanceMethod1(); } } ").csharp # Type wrapping test(" type Foo : double { def scaleBy(scale Foo) Foo { return ((self as double) * (scale as double)) as Foo } } namespace Foo { const FOO = 0.5 as Foo } @export def test(x double) Foo { return (x as Foo).scaleBy(Foo.FOO) } ", " public static class Foo { public static double FOO = 0.5; public static double scaleBy(double self, double scale) { return self * scale; } } public class Globals { public static double test(double x) { return Foo.scaleBy(x, Foo.FOO); } } ").csharp # Casting between enums and integers must be explicit test(" enum Foo { FOO } @export def test Foo { var x = Foo.FOO return ((x as int) * 1) as Foo } ", " public enum Foo { FOO = 0, } public class Globals { public static Foo test() { Foo x = Foo.FOO; return (Foo)((int)x * 1); } } ").csharp # Lists and maps test(" @export def foo { var x = [1, 2, 3] var y = {1: 2, 3: 4} var z = {\"1\": 2, \"3\": 4} } ", " using System.Collections.Generic; public class in_StringMap { public static Dictionary insert(Dictionary self, string key, T value) { self.Add(key, value); return self; } } public class in_IntMap { public static Dictionary insert(Dictionary self, int key, T value) { self.Add(key, value); return self; } } public class Globals { public static void foo() { List x = new List { 1, 2, 3 }; Dictionary y = in_IntMap.insert(in_IntMap.insert(new Dictionary(), 1, 2), 3, 4); Dictionary z = in_StringMap.insert(in_StringMap.insert(new Dictionary(), \"1\", 2), \"3\", 4); } } ").csharp # Inheriting from a dynamically-specified type test(" @export class Error : dynamic.System.Exception { } ", " public class Error : System.Exception { public Error() { } } ").csharp # Test math constants test(" @export def main { dynamic.foo(Math.NAN, Math.INFINITY, -Math.INFINITY) dynamic.foo(Math.NAN.toString, Math.INFINITY.toString, (-Math.INFINITY).toString) } ", " public class Globals { public static void main() { foo(0.0 / 0.0, 1.0 / 0.0, -(1.0 / 0.0)); foo((0.0 / 0.0).ToString(), (1.0 / 0.0).ToString(), (-(1.0 / 0.0)).ToString()); } } ").csharp.inlineAllFunctions # Test math constants test(" @export def main { dynamic.foo(Math.NAN, Math.INFINITY, -Math.INFINITY) dynamic.foo(Math.NAN.toString, Math.INFINITY.toString, (-Math.INFINITY).toString) } ", " using System; public class Globals { public static void main() { foo(Double.NaN, Double.PositiveInfinity, Double.NegativeInfinity); foo(Double.NaN.ToString(), Double.PositiveInfinity.ToString(), Double.NegativeInfinity.ToString()); } } ").csharp.inlineAllFunctions.foldAllConstants # Test math toString test(" @export def main { dynamic.foo(0.toString, 1.0.toString, (-1.0).toString, 0.5.toString, (-0.5).toString) } ", " public class Globals { public static void main() { foo(0.ToString(), 1.0.ToString(), (-1.0).ToString(), 0.5.ToString(), (-0.5).ToString()); } } ").csharp # Test math toString test(" @export def main { dynamic.foo(0.toString, 1.0.toString, (-1.0).toString, 0.5.toString, (-0.5).toString) } ", " public class Globals { public static void main() { foo(\"0\", 1.0.ToString(), (-1.0).ToString(), 0.5.ToString(), (-0.5).ToString()); } } ").csharp.foldAllConstants # Double literals must be emitted with a decimal point test(" @export def main(x double) { x = 1.0 / 2.0 x = 1e100 / 2e100 x = 1e-100 / 2e-100 x = 1.5 / 2.5 x = 1.5e100 / 2.5e100 x = 1.5e-100 / 2.5e-100 } ", " public class Globals { public static void main(double x) { x = 1.0 / 2.0; x = 1.0e+100 / 2.0e+100; x = 1.0e-100 / 2.0e-100; x = 1.5 / 2.5; x = 1.5e+100 / 2.5e+100; x = 1.5e-100 / 2.5e-100; } } ").csharp # Check for a crash when converting switch statements to if chains test(" @export def main { switch \"a\" { case \"b\" {} case \"c\" {} default {} } } ", " public class Globals { public static void main() { string value = \"a\"; if (value == \"b\") { } else if (value == \"c\") { } else { } } } ").csharp # Check different integer types test(" enum Foo { A, B def foo {} } flags Bar { C, D def foo {} } type Baz : int { def foo {} } namespace Baz { const X = 0 as Baz } @export def test int { var a = Foo.A var c = Bar.C var x = 0 as Baz a.foo c.foo x.foo return a + c + x as int + Foo.B + Bar.D + Baz.X as int } ", " public enum Foo { A = 0, B = 1, } public static class Bar { public const int C = 1; public const int D = 2; } public static class Baz { public static int X = 0; public static void foo(int self) { } } public class in_Foo { public static void foo(Foo self) { } } public class in_Bar { public static void foo(int self) { } } public class Globals { public static int test() { Foo a = Foo.A; int c = Bar.C; int x = 0; in_Foo.foo(a); in_Bar.foo(c); Baz.foo(x); return (int)a + c + x + (int)Foo.B + Bar.D + Baz.X; } } ").csharp # Check code generation for flags types test(" flags Foo { X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X27 X28 X29 X30 X31 } @export def test { if !((.X0 | .X1) in (Foo.X30 | .X31)) { var x = Foo.X0 x = .X1 | .X2 x &= ~.X3 } } ", " public static class Foo { public const int X0 = 1; public const int X1 = 2; public const int X2 = 4; public const int X3 = 8; public const int X30 = 1073741824; public const int X31 = -2147483648; } public class Globals { public static void test() { if (!(((Foo.X0 | Foo.X1) & (Foo.X30 | Foo.X31)) != 0)) { int x = Foo.X0; x = Foo.X1 | Foo.X2; x &= ~Foo.X3; } } } ").csharp # Check dynamic types test(" @export def test(foo dynamic.Foo) dynamic.Bar { return foo as dynamic.Bar + 0 as dynamic.Int + 0 as dynamic } ", " public class Globals { public static Bar test(Foo foo) { return (Bar)foo + (Int)0 + 0; } } ").csharp # Check bit shifts test(" @export def test(x int) { x = x << x x = x >> x x = x >>> x x <<= x x >>= x x >>>= x } ", " public class in_int { public static int unsignedRightShift(int self, int x) { return (int)unchecked((uint)self >> x); } } public class Globals { public static void test(int x) { x = x << x; x = x >> x; x = in_int.unsignedRightShift(x, x); x <<= x; x >>= x; x = in_int.unsignedRightShift(x, x); } } ").csharp # Test lambda code generation test(" @export def test { var a = => {} var b = (x int) => {} var c = (x int, y int) => x == y a() b(1) c(2, 3) } ", " public class Globals { public static void test() { System.Action a = () => { }; System.Action b = (int x) => { }; System.Func c = (int x, int y) => { return x == y; }; a(); b(1); c(2, 3); } } ").csharp # Test lambda conversion with scope capture and currying test(" @export class Foo { var value = 0 def test(x int) int { var y = 0 var f = (a int) => => (b int) => => value + x + y + a + b return f(1)()(2)() } } ", " public class Foo { public int value = 0; public int test(int x) { int y = 0; System.Func>>> f = (int a) => { return () => { return (int b) => { return () => { return this.value + x + y + a + b; }; }; }; }; return f(1)()(2)(); } public Foo() { this.value = 0; } } ").csharp # Test break statements inside a switch test(" @export def test(x int, y bool) { while true { switch x { case 0 { if y { break } } } } } ", " public class Globals { public static void test(int x, bool y) { while (true) { switch (x) { case 0: { if (y) { goto label; } break; } } } label:; } } ").csharp # Constructors on dynamic types shouldn't need parentheses test(" @export def test { dynamic.Foo.new dynamic.Foo.new() dynamic.Foo.new.test dynamic.Foo.new().test dynamic.Foo.new.test dynamic.Foo.new().test var a = dynamic.Foo.new var b = dynamic.Foo.new() var c = dynamic.Foo.new.test var d = dynamic.Foo.new().test var e = dynamic.Foo.new.test var f = dynamic.Foo.new().test } ", " public class Globals { public static void test() { new Foo(); new Foo(); new Foo().test; new Foo().test; new Foo().test; new Foo().test; dynamic a = new Foo(); dynamic b = new Foo(); dynamic c = new Foo().test; dynamic d = new Foo().test; dynamic e = new Foo().test; dynamic f = new Foo().test; } } ").csharp # Check preserving enum types for constant-folded enum values test(" enum Foo { FOO BAR } def foo(x Foo) { bar(x) } @import def bar(x int) @export def test(x bool) { foo(x ? .BAR : .FOO) } ", " public enum Foo { } public class Globals { public static void test(bool x) { bar((int)(x ? (Foo)1 : (Foo)0)); } } ").csharp.foldAllConstants.inlineAllFunctions # Check wrapped types with type parameters test(" type Foo : List { def size int { return (self as List).count } } @entry def main { (List.new as Foo).size } ", " using System.Collections.Generic; public static class Foo { } public class in_List { public static int count(List self) { return self.Count; } } public class in_Foo { public static int size(List self) { return in_List.count(self); } } public class Globals { public static void Main() { in_Foo.size(new List()); } } ").csharp # Check the "is" operator test(" class Foo {} class Bar : Foo {} @entry def main { Foo.new is Bar == true || true == Foo.new is Bar } ", " public class Foo { public Foo() { } } public class Bar : Foo { public Bar() : base() { } } public class Globals { public static void Main() { new Foo() is Bar == true || true == new Foo() is Bar; } } ").csharp # Check block comments before a statement test(" @entry def main { # This is # a block # comment return } ", " public class Globals { public static void Main() { // This is // a block // comment return; } } ").csharp # Check trailing block comments test(" @entry def main { # This is # a block # comment } ", " public class Globals { public static void Main() { // This is // a block // comment } } ").csharp # CFG must-return check on a switch statement converted to an if-else chain test(" @export def test(x string) int { switch x { case \"y\" { return 1 } default { throw x } } } ", " public class Globals { public static int test(string x) { string value = x; if (value == \"y\") { return 1; } else { throw x; } } } ").csharp } } ================================================ FILE: tests/formatting.sk ================================================ namespace Skew.Tests { def testFormatting { testFormat(" 01234abcde56789 ", " 01234abcde56789 ~~~~~ ", 5, 10, 15) testFormat(" abcde0123456789 ", " abcde01... ~~~~~ ", 0, 5, 10) testFormat(" 0123456789abcde ", " ...89abcde ~~~~~ ", 10, 15, 10) testFormat(" 01234abcde56789 ", " ...abcd... ~~~~ ", 5, 10, 10) testFormat(" 01234abcde56789 ", " ...abcde... ~~~~~ ", 5, 10, 11) testFormat(" abcdefghij0123456789klmnopqrst ", " ...ij0123456789kl... ~~~~~~~~~~ ", 10, 20, 20) testFormat(" 0123456789 ", " 01234... ~~~~ ", 1, 7, 8) testFormat(" 0123456789 ", " 01234... ~~~ ", 2, 6, 8) testFormat(" 0123456789 ", " ...34... ~~ ", 3, 5, 8) testFormat(" 01234💩💩💩💩💩01234 ", " 01234💩💩💩💩💩01234 ~~~~~ ", "01234".count, "01234💩💩💩💩💩".count, 20) testFormat(" 💩💩💩💩💩01234012340123401234 ", " 💩💩💩💩💩01... ~~~~~ ", 0, "💩💩💩💩💩".count, 10) testFormat(" 01234012340123401234💩💩💩💩💩 ", " ...34💩💩💩💩💩 ~~~~~ ", "01234012340123401234".count, "01234012340123401234💩💩💩💩💩".count, 10) testFormat(" 😄😉😍😗😜😔😞😢😭😪😥😰😅😓😩😨😱😠😡😎 ", " 😄😉😍😗😜😔😞... ~~~~~ ", 0, "😄😉😍😗😜".count, 10) testFormat(" 😄😉😍😗😜😔😞😢😭😪😥😰😅😓😩😨😱😠😡😎 ", " ...😓😩😨😱😠😡😎 ~~~~~ ", "😄😉😍😗😜😔😞😢😭😪😥😰😅😓😩".count, "😄😉😍😗😜😔😞😢😭😪😥😰😅😓😩😨😱😠😡😎".count, 10) testFormat(" 😄😉😍😗😜😔😞😢😭😪😥😰😅😓😩😨😱😠😡😎 ", " ...😔😞😢😭... ~~~~ ", "😄😉😍😗😜".count, "😄😉😍😗😜😔😞😢😭😪😥😰😅😓😩".count, 10) testFormat(" \ta\tab\tabc\tabcd\tabcde\tabcdef\tabcdefg\tabcdefgh\tx ", " a ab abc abcd abcde abcdef abcdefg abcdefgh x ~~~~~~~~ ", 0, 1, 0) testFormat(" \t0\t1\t2\t3\t4 ", " ... 2 3 4 ~~~~~~~ ", 8, 9, 22) } } ================================================ FILE: tests/ide.sk ================================================ namespace Skew.Tests { def testIDE { # Tooltips: basic test of globals and formatting testIDE(" def @annotation ( x int ) # Comment for annotation def globalFunction ( x int ) bool { return false } # Comment for globalFunction # Comment for globalVariable var globalVariable = 0 # Comment 2 for globalVariable ", (ide, expect) => { expect("", ide.tooltipQuery(0, 3)) expect("# Comment for annotation\ndef @annotation(x int)", ide.tooltipQuery(0, 4)) expect("# Comment for annotation\ndef @annotation(x int)", ide.tooltipQuery(0, 15)) expect("", ide.tooltipQuery(0, 16)) expect("", ide.tooltipQuery(1, 3)) expect("# Comment for globalFunction\ndef globalFunction(x int) bool", ide.tooltipQuery(1, 4)) expect("# Comment for globalFunction\ndef globalFunction(x int) bool", ide.tooltipQuery(1, 18)) expect("", ide.tooltipQuery(1, 19)) expect("", ide.tooltipQuery(1, 20)) expect("var x int", ide.tooltipQuery(1, 21)) expect("var x int", ide.tooltipQuery(1, 22)) expect("class int", ide.tooltipQuery(1, 23)) expect("class int", ide.tooltipQuery(1, 26)) expect("", ide.tooltipQuery(1, 27)) expect("", ide.tooltipQuery(1, 28)) expect("class bool", ide.tooltipQuery(1, 29)) expect("class bool", ide.tooltipQuery(1, 33)) expect("", ide.tooltipQuery(1, 34)) expect("", ide.tooltipQuery(3, 3)) expect("# Comment for globalVariable\n# Comment 2 for globalVariable\nvar globalVariable int", ide.tooltipQuery(3, 4)) expect("# Comment for globalVariable\n# Comment 2 for globalVariable\nvar globalVariable int", ide.tooltipQuery(3, 18)) expect("", ide.tooltipQuery(3, 19)) }) # Tooltips: test object types testIDE(" class Derived : Base :: I { const foo Foo = .FOO # Comment for foo over instanceFunction {} } class Base { def instanceFunction {} } interface I { } enum Foo { FOO # Comment for FOO } ", (ide, expect) => { expect("", ide.tooltipQuery(0, 5)) expect("class Derived : Base :: I", ide.tooltipQuery(0, 6)) expect("class Derived : Base :: I", ide.tooltipQuery(0, 13)) expect("", ide.tooltipQuery(0, 14)) expect("", ide.tooltipQuery(0, 15)) expect("class Base", ide.tooltipQuery(0, 16)) expect("class Base", ide.tooltipQuery(0, 20)) expect("", ide.tooltipQuery(0, 21)) expect("", ide.tooltipQuery(0, 23)) expect("interface I", ide.tooltipQuery(0, 24)) expect("interface I", ide.tooltipQuery(0, 25)) expect("", ide.tooltipQuery(0, 26)) expect("", ide.tooltipQuery(1, 7)) expect("# Comment for foo\nconst foo Foo", ide.tooltipQuery(1, 8)) expect("# Comment for foo\nconst foo Foo", ide.tooltipQuery(1, 11)) expect("enum Foo", ide.tooltipQuery(1, 12)) expect("enum Foo", ide.tooltipQuery(1, 15)) expect("", ide.tooltipQuery(1, 16)) expect("", ide.tooltipQuery(1, 18)) expect("# Comment for FOO\nconst FOO Foo = 0", ide.tooltipQuery(1, 19)) expect("# Comment for FOO\nconst FOO Foo = 0", ide.tooltipQuery(1, 22)) expect("", ide.tooltipQuery(1, 23)) expect("", ide.tooltipQuery(2, 6)) expect("over instanceFunction", ide.tooltipQuery(2, 7)) expect("over instanceFunction", ide.tooltipQuery(2, 23)) expect("", ide.tooltipQuery(2, 24)) expect("", ide.tooltipQuery(6, 5)) expect("def instanceFunction", ide.tooltipQuery(6, 6)) expect("def instanceFunction", ide.tooltipQuery(6, 22)) expect("", ide.tooltipQuery(6, 23)) expect("", ide.tooltipQuery(13, 1)) expect("# Comment for FOO\nconst FOO Foo = 0", ide.tooltipQuery(13, 2)) expect("# Comment for FOO\nconst FOO Foo = 0", ide.tooltipQuery(13, 5)) expect("", ide.tooltipQuery(13, 6)) }) # Tooltips: test flags testIDE(" flags Flags { FOO # FOO } var f Flags = .FOO | .FOO # f ", (ide, expect) => { expect("", ide.tooltipQuery(0, 5)) expect("flags Flags", ide.tooltipQuery(0, 6)) expect("flags Flags", ide.tooltipQuery(0, 11)) expect("", ide.tooltipQuery(0, 12)) expect("", ide.tooltipQuery(1, 1)) expect("# FOO\nconst FOO Flags = 1", ide.tooltipQuery(1, 2)) expect("# FOO\nconst FOO Flags = 1", ide.tooltipQuery(1, 5)) expect("", ide.tooltipQuery(1, 6)) expect("", ide.tooltipQuery(4, 3)) expect("# f\nvar f Flags", ide.tooltipQuery(4, 4)) expect("# f\nvar f Flags", ide.tooltipQuery(4, 5)) expect("flags Flags", ide.tooltipQuery(4, 6)) expect("flags Flags", ide.tooltipQuery(4, 11)) expect("", ide.tooltipQuery(4, 12)) expect("", ide.tooltipQuery(4, 14)) expect("# FOO\nconst FOO Flags = 1", ide.tooltipQuery(4, 15)) expect("# FOO\nconst FOO Flags = 1", ide.tooltipQuery(4, 18)) expect("def |(x int) int", ide.tooltipQuery(4, 19)) expect("def |(x int) int", ide.tooltipQuery(4, 20)) expect("", ide.tooltipQuery(4, 21)) expect("# FOO\nconst FOO Flags = 1", ide.tooltipQuery(4, 22)) expect("# FOO\nconst FOO Flags = 1", ide.tooltipQuery(4, 25)) expect("", ide.tooltipQuery(4, 26)) }) # Tooltips: test generics testIDE(" def test(foo List>, bar X) X { foo.append(null) return null in foo ? foo.first.bar : bar } class Foo { var bar Y } ", (ide, expect) => { expect("", ide.tooltipQuery(0, 3)) expect("def test(foo List>, bar X) X", ide.tooltipQuery(0, 4)) expect("def test(foo List>, bar X) X", ide.tooltipQuery(0, 8)) expect("X", ide.tooltipQuery(0, 9)) expect("X", ide.tooltipQuery(0, 10)) expect("", ide.tooltipQuery(0, 11)) expect("var foo List>", ide.tooltipQuery(0, 12)) expect("var foo List>", ide.tooltipQuery(0, 15)) expect("class List", ide.tooltipQuery(0, 16)) expect("class List", ide.tooltipQuery(0, 20)) expect("class Foo", ide.tooltipQuery(0, 21)) expect("class Foo", ide.tooltipQuery(0, 24)) expect("X", ide.tooltipQuery(0, 25)) expect("X", ide.tooltipQuery(0, 26)) expect("", ide.tooltipQuery(0, 27)) expect("", ide.tooltipQuery(1, 1)) expect("var foo List>", ide.tooltipQuery(1, 2)) expect("var foo List>", ide.tooltipQuery(1, 5)) expect("def append(x Foo)", ide.tooltipQuery(1, 6)) expect("def append(x Foo)", ide.tooltipQuery(1, 12)) expect("", ide.tooltipQuery(1, 13)) expect("", ide.tooltipQuery(2, 13)) expect("def in(x T) bool", ide.tooltipQuery(2, 14)) expect("def in(x T) bool", ide.tooltipQuery(2, 16)) expect("var foo List>", ide.tooltipQuery(2, 17)) expect("var foo List>", ide.tooltipQuery(2, 20)) expect("", ide.tooltipQuery(2, 21)) expect("", ide.tooltipQuery(2, 22)) expect("var foo List>", ide.tooltipQuery(2, 23)) expect("var foo List>", ide.tooltipQuery(2, 26)) expect("def first Foo", ide.tooltipQuery(2, 27)) expect("def first Foo", ide.tooltipQuery(2, 32)) expect("var bar X", ide.tooltipQuery(2, 33)) expect("var bar X", ide.tooltipQuery(2, 36)) expect("", ide.tooltipQuery(2, 37)) expect("", ide.tooltipQuery(5, 5)) expect("class Foo", ide.tooltipQuery(5, 6)) expect("class Foo", ide.tooltipQuery(5, 9)) expect("Y", ide.tooltipQuery(5, 10)) expect("Y", ide.tooltipQuery(5, 11)) expect("", ide.tooltipQuery(5, 12)) }) # Tooltips: test wrapped types, casts, and self testIDE(" type Foo : int { def foo Foo { return self as int as Foo # } } ", (ide, expect) => { expect("", ide.tooltipQuery(0, 4)) expect("type Foo = int", ide.tooltipQuery(0, 5)) expect("type Foo = int", ide.tooltipQuery(0, 8)) expect("", ide.tooltipQuery(0, 9)) expect("", ide.tooltipQuery(0, 10)) expect("class int", ide.tooltipQuery(0, 11)) expect("class int", ide.tooltipQuery(0, 14)) expect("", ide.tooltipQuery(0, 15)) expect("", ide.tooltipQuery(1, 5)) expect("def foo Foo", ide.tooltipQuery(1, 6)) expect("def foo Foo", ide.tooltipQuery(1, 9)) expect("type Foo = int", ide.tooltipQuery(1, 10)) expect("type Foo = int", ide.tooltipQuery(1, 13)) expect("", ide.tooltipQuery(1, 14)) expect("", ide.tooltipQuery(2, 10)) expect("const self Foo", ide.tooltipQuery(2, 11)) expect("const self Foo", ide.tooltipQuery(2, 15)) expect("", ide.tooltipQuery(2, 16)) expect("", ide.tooltipQuery(2, 18)) expect("class int", ide.tooltipQuery(2, 19)) expect("class int", ide.tooltipQuery(2, 22)) expect("", ide.tooltipQuery(2, 23)) expect("", ide.tooltipQuery(2, 25)) expect("type Foo = int", ide.tooltipQuery(2, 26)) expect("type Foo = int", ide.tooltipQuery(2, 29)) expect("", ide.tooltipQuery(2, 30)) }) # Tooltips: test function merging testIDE(" def foo # def foo {} def bar {} def bar # ", (ide, expect) => { expect("", ide.tooltipQuery(0, 3)) expect("#\ndef foo", ide.tooltipQuery(0, 4)) expect("#\ndef foo", ide.tooltipQuery(0, 7)) expect("", ide.tooltipQuery(0, 8)) expect("", ide.tooltipQuery(1, 3)) expect("#\ndef foo", ide.tooltipQuery(1, 4)) expect("#\ndef foo", ide.tooltipQuery(1, 7)) expect("", ide.tooltipQuery(1, 8)) expect("", ide.tooltipQuery(2, 3)) expect("#\ndef bar", ide.tooltipQuery(2, 4)) expect("#\ndef bar", ide.tooltipQuery(2, 7)) expect("", ide.tooltipQuery(2, 8)) expect("", ide.tooltipQuery(3, 3)) expect("#\ndef bar", ide.tooltipQuery(3, 4)) expect("#\ndef bar", ide.tooltipQuery(3, 7)) expect("", ide.tooltipQuery(3, 8)) }) # Tooltips: test object merging testIDE(" namespace Foo {} class Foo {} class Bar {} namespace Bar {} ", (ide, expect) => { expect("", ide.tooltipQuery(0, 9)) expect("class Foo", ide.tooltipQuery(0, 10)) expect("class Foo", ide.tooltipQuery(0, 13)) expect("", ide.tooltipQuery(0, 14)) expect("", ide.tooltipQuery(1, 5)) expect("class Foo", ide.tooltipQuery(1, 6)) expect("class Foo", ide.tooltipQuery(1, 9)) expect("", ide.tooltipQuery(1, 10)) expect("", ide.tooltipQuery(2, 5)) expect("class Bar", ide.tooltipQuery(2, 6)) expect("class Bar", ide.tooltipQuery(2, 9)) expect("", ide.tooltipQuery(2, 10)) expect("", ide.tooltipQuery(3, 9)) expect("class Bar", ide.tooltipQuery(3, 10)) expect("class Bar", ide.tooltipQuery(3, 13)) expect("", ide.tooltipQuery(3, 14)) }) # Tooltips: test rewriting operators testIDE(" def test(foo Foo) { foo = foo?.bar ?? foo # foo = foo.bar?.bar ?? foo # } class Foo { var bar Foo } ", (ide, expect) => { expect("", ide.tooltipQuery(1, 1)) expect("var foo Foo", ide.tooltipQuery(1, 2)) expect("var foo Foo", ide.tooltipQuery(1, 5)) expect("", ide.tooltipQuery(1, 6)) expect("", ide.tooltipQuery(1, 7)) expect("var foo Foo", ide.tooltipQuery(1, 8)) expect("var foo Foo", ide.tooltipQuery(1, 11)) expect("", ide.tooltipQuery(1, 12)) expect("var bar Foo", ide.tooltipQuery(1, 13)) expect("var bar Foo", ide.tooltipQuery(1, 16)) expect("", ide.tooltipQuery(1, 17)) expect("", ide.tooltipQuery(1, 19)) expect("var foo Foo", ide.tooltipQuery(1, 20)) expect("var foo Foo", ide.tooltipQuery(1, 23)) expect("", ide.tooltipQuery(1, 24)) expect("", ide.tooltipQuery(2, 1)) expect("var foo Foo", ide.tooltipQuery(2, 2)) expect("var foo Foo", ide.tooltipQuery(2, 5)) expect("", ide.tooltipQuery(2, 6)) expect("", ide.tooltipQuery(2, 7)) expect("var foo Foo", ide.tooltipQuery(2, 8)) expect("var foo Foo", ide.tooltipQuery(2, 11)) expect("var bar Foo", ide.tooltipQuery(2, 12)) expect("var bar Foo", ide.tooltipQuery(2, 15)) expect("", ide.tooltipQuery(2, 16)) expect("var bar Foo", ide.tooltipQuery(2, 17)) expect("var bar Foo", ide.tooltipQuery(2, 20)) expect("", ide.tooltipQuery(2, 21)) expect("", ide.tooltipQuery(2, 23)) expect("var foo Foo", ide.tooltipQuery(2, 24)) expect("var foo Foo", ide.tooltipQuery(2, 27)) expect("", ide.tooltipQuery(2, 28)) }) # Tooltips: test string interpolation testIDE(" def test string { return \"\\(test) + \\(test)\" } ", (ide, expect) => { expect("", ide.tooltipQuery(1, 11)) expect("def test string", ide.tooltipQuery(1, 12)) expect("def test string", ide.tooltipQuery(1, 16)) expect("", ide.tooltipQuery(1, 17)) expect("", ide.tooltipQuery(1, 21)) expect("def test string", ide.tooltipQuery(1, 22)) expect("def test string", ide.tooltipQuery(1, 26)) expect("", ide.tooltipQuery(1, 27)) }) # Tooltips: test XML literals testIDE(" class Foo.Bar { var foo Foo.Bar = def <>...(x Foo.Bar) {} } ", (ide, expect) => { expect("", ide.tooltipQuery(1, 5)) expect("var foo Foo.Bar", ide.tooltipQuery(1, 6)) expect("var foo Foo.Bar", ide.tooltipQuery(1, 9)) expect("namespace Foo", ide.tooltipQuery(1, 10)) expect("namespace Foo", ide.tooltipQuery(1, 13)) expect("class Bar", ide.tooltipQuery(1, 14)) expect("class Bar", ide.tooltipQuery(1, 17)) expect("", ide.tooltipQuery(1, 18)) expect("", ide.tooltipQuery(1, 20)) expect("namespace Foo", ide.tooltipQuery(1, 21)) expect("namespace Foo", ide.tooltipQuery(1, 24)) expect("class Bar", ide.tooltipQuery(1, 25)) expect("class Bar", ide.tooltipQuery(1, 28)) expect("var foo Foo.Bar", ide.tooltipQuery(1, 29)) expect("var foo Foo.Bar", ide.tooltipQuery(1, 32)) expect("", ide.tooltipQuery(1, 33)) expect("", ide.tooltipQuery(1, 38)) expect("namespace Foo", ide.tooltipQuery(1, 39)) expect("namespace Foo", ide.tooltipQuery(1, 42)) expect("class Bar", ide.tooltipQuery(1, 43)) expect("class Bar", ide.tooltipQuery(1, 46)) expect("var foo Foo.Bar", ide.tooltipQuery(1, 47)) expect("var foo Foo.Bar", ide.tooltipQuery(1, 50)) expect("", ide.tooltipQuery(1, 51)) expect("", ide.tooltipQuery(1, 58)) expect("namespace Foo", ide.tooltipQuery(1, 59)) expect("namespace Foo", ide.tooltipQuery(1, 62)) expect("class Bar", ide.tooltipQuery(1, 63)) expect("class Bar", ide.tooltipQuery(1, 66)) expect("", ide.tooltipQuery(1, 67)) }) # Definitions: test object merging testIDE(" namespace Foo {} class Foo {} class Bar {} namespace Bar {} def test(foo Foo, bar Bar) {} ", (ide, expect) => { expect(":2:7", ide.definitionQuery(4, 13)) expect(":3:7", ide.definitionQuery(4, 22)) }) # Definitions: test function merging testIDE(" def foo def foo {} def bar {} def bar def test { foo bar } ", (ide, expect) => { expect(":2:5", ide.definitionQuery(5, 2)) expect(":3:5", ide.definitionQuery(6, 2)) }) # Renaming: test function arguments testIDE(" var x = 0 def foo(x int) { var y = x x += x var z = (x bool) => x ? y : -y z(x == y) } ", (ide, expect) => { expect(":2:9, :3:11, :4:3, :4:8, :6:5", ide.renameQuery(3, 2)) }) # Renaming: test local variables testIDE(" var x = 0 def foo { var x = 0 x += x var z = (x bool) => x ? 1 : -1 z(x == 1) } ", (ide, expect) => { expect(":3:7, :4:3, :4:8, :6:5", ide.renameQuery(3, 2)) }) # Renaming: test instance variables testIDE(" var x = 0 class Foo { var x = 0 def foo { x += x var z = (x bool) => x ? 1 : -1 z(x == 1) } } ", (ide, expect) => { expect(":3:7, :5:5, :5:10, :7:7", ide.renameQuery(4, 4)) }) # Renaming: test global variables testIDE(" var x = 0 namespace Foo { var x = 0 def foo { x += x var z = (x bool) => x ? 1 : -1 z(x == 1) } } ", (ide, expect) => { expect(":3:7, :5:5, :5:10, :7:7", ide.renameQuery(4, 4)) }) # Renaming: test instance functions without arguments testIDE(" def foo { foo } class Foo { def foo { foo var bar = (foo int) int => { new.foo return foo } bar(0) } } ", (ide, expect) => { expect(":5:7, :6:5, :8:11", ide.renameQuery(5, 4)) }) # Renaming: test instance functions with arguments testIDE(" def foo(x int) { foo(x) } class Foo { def foo(x int) { foo(x) var bar = (foo int) int => { new.foo(x) return foo } bar(0) } } ", (ide, expect) => { expect(":5:7, :6:5, :8:11", ide.renameQuery(5, 4)) }) # Renaming: test instance functions without arguments testIDE(" def foo { foo } namespace Foo { def foo { foo var bar = (foo int) int => { Foo.foo return foo } bar(0) } } ", (ide, expect) => { expect(":5:7, :6:5, :8:11", ide.renameQuery(5, 4)) }) # Renaming: test instance functions with arguments testIDE(" def foo(x int) { foo(x) } namespace Foo { def foo(x int) { foo(x) var bar = (foo int) int => { Foo.foo(x) return foo } bar(0) } } ", (ide, expect) => { expect(":5:7, :6:5, :8:11", ide.renameQuery(5, 4)) }) # Renaming: test classes testIDE(" class Foo.Bar {} namespace Foo.Bar { def new Bar { var bar Bar = var baz Baz.Bar = null baz = baz ?? Baz.Bar.new return bar } } class Baz.Bar {} ", (ide, expect) => { expect(":1:11, :2:15, :3:11, :4:13, :4:20", ide.renameQuery(2, 10)) }) # Renaming: test overridden instance methods testIDE(" class Foo :: IFoo { def foo {} def foo(x int) {} } class Bar : Foo { over foo {} over foo(x int) {} } class Baz : Bar { over foo {} over foo(x int) {} } class Nope { def foo {} def foo(x int) {} } interface IFoo { def foo def foo(x int) } ", (ide, expect) => { expect(":2:7, :6:8, :10:8, :18:7", ide.renameQuery(5, 7)) }) # Renaming: test overloaded interface methods testIDE(" interface Foo { def foo def foo(x int) {} } class Bar :: Foo { def foo {} def foo(x int) {} } class Baz :: Foo { def foo {} def foo(x int) {} } class Nope { def foo {} def foo(x int) {} } ", (ide, expect) => { expect(":2:7, :6:7, :10:7", ide.renameQuery(1, 6)) }) # Completion: variable type testCompletion(" var x i var y = 0 ", 0, 7, " :1:7: error: \"i\" is not declared var x i ^ :1:7: completions: var x i ^ [IntMap] # \"class\" [int] # \"class\" ") # Completion: variable inside instance function testCompletion(" class Foo { var bar = 0 def baz { ba } } ", 4, 6, " :5:5: error: \"ba\" is not declared, did you mean \"bar\"? ba ~~ :2:7: note: \"bar\" is defined here var bar = 0 ~~~ :5:5: completions: ba ~~ [bar] # \"int\" [baz] # \"()\" ") # Completion: global inside instance function testCompletion(" class Foo { var bar = 0 def baz { n } } namespace Foo { var nox = 0 } ", 4, 5, " :5:5: error: \"n\" is not declared n ^ :5:5: completions: n ^ [new] # \"() Foo\" [nox] # \"int\" ") # Completion: instance inside global function testCompletion(" class Foo { var bar = 0 } namespace Foo { def foo { ba } var baz = 0 } ", 6, 6, " :7:5: error: \"ba\" is not declared, did you mean \"baz\"? ba ~~ :10:7: note: \"baz\" is defined here var baz = 0 ~~~ :7:5: completions: ba ~~ [baz] # \"int\" ") # Completion: multi-level instance check testCompletion(" class Foo { var bar = 0 class Bar { var baz = 0 def foo { ba } } namespace Bar { var ban = 0 } } namespace Foo { var bax = 0 } var bat = 0 ", 7, 8, " :8:7: error: \"ba\" is not declared, did you mean \"bar\"? ba ~~ :2:7: note: \"bar\" is defined here var bar = 0 ~~~ :8:7: completions: ba ~~ [Bar] # \"class\" [ban] # \"int\" [bat] # \"int\" [bax] # \"int\" [baz] # \"int\" ") # Completion: empty dot access testCompletion(" def main(foo Foo) { foo. } class Foo { var foo = 0 var bar = 0 var baz = 0 } ", 1, 6, " :2:7: error: Expected identifier but found newline foo. ^ :2:7: completions: foo. ^ [bar] # \"int\" [baz] # \"int\" [foo] # \"int\" ") # Completion: partial dot access testCompletion(" def main(foo Foo) { foo.bar } class Foo { var foo = 0 var bar = 0 var baz = 0 } ", 1, 6, " :2:3: warning: Unused expression foo.bar ~~~~~~~ :2:7: completions: foo.bar ~~~ [bar] # \"int\" [baz] # \"int\" [foo] # \"int\" ") # Completion: partial dot access testCompletion(" def main(foo Foo) { foo.bar } class Foo { var foo = 0 var bar = 0 var baz = 0 } ", 1, 8, " :2:3: warning: Unused expression foo.bar ~~~~~~~ :2:7: completions: foo.bar ~~~ [bar] # \"int\" [baz] # \"int\" ") # Completion: dot access testCompletion(" def main(foo Foo) { foo.bar } class Foo { var foo = 0 var bar = 0 var baz = 0 } ", 1, 9, " :2:3: warning: Unused expression foo.bar ~~~~~~~ :2:7: completions: foo.bar ~~~ [bar] # \"int\" ") # Completion: global dot access testCompletion(" def main { Foo. } class Foo { var foo = 0 var bar = 0 } namespace Foo { var baz = 0 } ", 1, 6, " :2:7: error: Expected identifier but found newline Foo. ^ :2:7: completions: Foo. ^ [baz] # \"int\" [new] # \"() Foo\" ") # Completion: comments testCompletion(" # This is the # first paragraph # of text. # # This is the # second paragraph # of text. def main { ma } ", 8, 4, " :9:3: error: \"ma\" is not declared ma ~~ :9:3: completions: ma ~~ [Math] # \"namespace\" [main] # \"()\" # This is the # first paragraph # of text. # # This is the # second paragraph # of text. ") # Completion: class object testCompletion(" class Foo {} var foo F ", 1, 9, " :2:9: error: \"F\" is not declared var foo F ^ :2:9: completions: var foo F ^ [Foo] # \"class\" ") # Completion: interface object testCompletion(" interface Foo {} var foo F ", 1, 9, " :2:9: error: \"F\" is not declared var foo F ^ :2:9: completions: var foo F ^ [Foo] # \"interface\" ") # Completion: namespace object testCompletion(" namespace Foo {} var foo F ", 1, 9, " :2:9: error: \"F\" is not declared var foo F ^ :2:9: completions: var foo F ^ [Foo] # \"namespace\" ") # Completion: enum object testCompletion(" enum Foo {} var foo F ", 1, 9, " :2:9: error: \"F\" is not declared var foo F ^ :2:9: completions: var foo F ^ [Foo] # \"enum\" ") # Completion: flags object testCompletion(" flags Foo {} var foo F ", 1, 9, " :2:9: error: \"F\" is not declared var foo F ^ :2:9: completions: var foo F ^ [Foo] # \"flags\" ") # Completion: wrapped object testCompletion(" type Foo : int {} var foo F ", 1, 9, " :2:9: error: \"F\" is not declared var foo F ^ :2:9: completions: var foo F ^ [Foo] # \"type\" ") # Completion: enum with inferred target testCompletion(" enum Foo { FOO } var foo Foo = .F ", 1, 15, " :2:16: error: \"F\" is not declared on type \"Foo\" var foo Foo = .F ^ :2:16: completions: var foo Foo = .F ^ [FOO] # \"Foo\" ") # Completion: inside partial if statement testCompletion(" def foo { if f } ", 1, 6, " :3:1: error: Expected \"{\" but found \"}\" } ^ :2:6: error: \"f\" is not declared if f ^ :2:6: completions: if f ^ [foo] # \"()\" ") # Signatures: test basic signature queries testIDE(" def test(foo int, bar bool) double { return test(foo, bar) } ", (ide, expect) => { expect("", ide.signatureQuery(1, 12)) expect("def test([foo int], bar bool) double", ide.signatureQuery(1, 13)) expect("def test([foo int], bar bool) double", ide.signatureQuery(1, 17)) expect("def test(foo int, [bar bool]) double", ide.signatureQuery(1, 18)) expect("def test(foo int, [bar bool]) double", ide.signatureQuery(1, 23)) }) } } ================================================ FILE: tests/javascript.mangle.sk ================================================ namespace Skew.Tests { def testJavaScriptMangle { # Test falsy values test(" class Foo { } @entry def main { var i int var s string var d double var b bool var f Foo var y dynamic if i != 0 { dynamic.foo() } if s != \"\" { dynamic.foo() } if s != null { dynamic.foo() } if d != 0.0 { dynamic.foo() } if b != false { dynamic.foo() } if f != null { dynamic.foo() } if y != 0 { dynamic.foo() } if y != \"\" { dynamic.foo() } if y != 0.0 { dynamic.foo() } if y != false { dynamic.foo() } if y != null { dynamic.foo() } } ", " (function() { function g() { var c = 0, b = null, d = 0, e = !1, f = null, a = null; c && foo(), b != '' && foo(), b != null && foo(), d != 0 && foo(), e != !1 && foo(), f && foo(), a !== 0 && foo(), a !== '' && foo(), a !== 0 && foo(), a !== !1 && foo(), a !== null && foo(); } g(); })(); ").jsMangle # Test default values test(" @entry def main { var i int var s string var d double var b bool var f Foo var r = i.toString + d.toString + b.toString + s + f.toString } class Foo { def toString string } ", " (function() { function e() { var a = null, b = null, d = '0' + 0 + 'false' + a + b.c(); } e(); })(); ").jsMangle.foldAllConstants # Test associative operator rotation test(" def foo { dynamic.test(dynamic.a + (dynamic.b + dynamic.c)) dynamic.test(dynamic.a - (dynamic.b - dynamic.c)) dynamic.test(dynamic.a * (dynamic.b * dynamic.c)) dynamic.test(dynamic.a / (dynamic.b / dynamic.c)) dynamic.test(dynamic.a & (dynamic.b & dynamic.c)) dynamic.test(dynamic.a | (dynamic.b | dynamic.c)) dynamic.test(dynamic.a ^ (dynamic.b ^ dynamic.c)) dynamic.test(dynamic.a && (dynamic.b && dynamic.c)) dynamic.test(dynamic.a || (dynamic.b || dynamic.c)) } @entry def bar { dynamic.test(dynamic.a & ((dynamic.b | dynamic.c) & (dynamic.d & (dynamic.e & dynamic.f)))) foo } ", " (function() { function a() { test(a + (b + c)), test(a - (b - c)), test(a * (b * c)), test(a / (b / c)), test(a & b & c), test(a | b | c), test(a ^ b ^ c), test(a && b && c), test(a || b || c); } function b() { test(a & (b | c) & d & e & f), a(); } b(); })(); ").jsMangle # Test if statement folding test(" @entry def main { if dynamic.a() {} if dynamic.b() { dynamic.c() } if dynamic.d() {} else { dynamic.e() } if dynamic.f() { dynamic.g() } else { dynamic.h() } } ", " (function() { function a() { a(), b() && c(), d() || e(), f() ? g() : h(); } a(); })(); ").jsMangle.foldAllConstants # Test if statement return folding test(" def foo bool { if dynamic.a { return true } if dynamic.b && dynamic.c { return true } if dynamic.d { return true } if dynamic.e && dynamic.f { return true } return false } def bar bool { if dynamic.a { return true } else if dynamic.b && dynamic.c { return true } else if dynamic.d { return true } else if dynamic.e && dynamic.f { return true } else { return false } } def baz bool { if dynamic.a || dynamic.b { if dynamic.c || dynamic.d { return true } } return false } @entry def main { foo bar baz } ", " (function() { function a() { return a || b && c || d || e && f ? !0 : !1; } function b() { return a || b && c || d || e && f ? !0 : !1; } function c() { return (a || b) && (c || d) ? !0 : !1; } function d() { a(), b(), c(); } d(); })(); ").jsMangle # More tests for if statement return folding test(" def foo(x dynamic) dynamic { x.foo(1) if x.y { x.foo(2) if x.y { x.foo(0) return x } } x.foo(3) if x.y { x.foo(4) if x.y { x.foo(0) return x } } return 0 } def bar(x double) int { if x < 0 { return 0 } if x > 1 { return 1 } return 2 } @entry def main { foo(0) bar(0) } ", " (function() { function b(a) { return (a.foo(1), a.y && (a.foo(2), a.y)) || (a.foo(3), a.y && (a.foo(4), a.y)) ? (a.foo(0), a) : 0; } function c(a) { return a < 0 ? 0 : a > 1 ? 1 : 2; } function d() { b(0), c(0); } d(); })(); ").jsMangle # Test integer comparison special cases test(" def foo(bar int, baz fn()) { if bar < 1 { baz() } if bar <= 1 { baz() } if bar > 1 { baz() } if bar >= 1 { baz() } if bar == 1 { baz() } if bar != 1 { baz() } } def bar(foo int, baz fn()) { if 1 < foo { baz() } if 1 <= foo { baz() } if 1 > foo { baz() } if 1 >= foo { baz() } if 1 == foo { baz() } if 1 != foo { baz() } } @entry def main { foo(0, null) bar(0, null) } ", " (function() { function c(a, b) { a < 1 && b(), a < 2 && b(), a > 1 && b(), a > 0 && b(), a ^ 1 || b(), a ^ 1 && b(); } function d(a, b) { 1 < a && b(), 0 < a && b(), 1 > a && b(), 2 > a && b(), 1 ^ a || b(), 1 ^ a && b(); } function e() { c(0, null), d(0, null); } e(); })(); ").jsMangle # Test enum comparison special cases test(" def foo(bar Foo, baz fn()) { if bar < Foo.BAR { baz() } if bar <= Foo.BAR { baz() } if bar > Foo.BAR { baz() } if bar >= Foo.BAR { baz() } if bar == Foo.FOO { baz() } if bar != Foo.FOO { baz() } if bar == Foo.BAR { baz() } if bar != Foo.BAR { baz() } } def bar(foo Foo, baz fn()) { if Foo.BAR < foo { baz() } if Foo.BAR <= foo { baz() } if Foo.BAR > foo { baz() } if Foo.BAR >= foo { baz() } if Foo.FOO == foo { baz() } if Foo.FOO != foo { baz() } if Foo.BAR == foo { baz() } if Foo.BAR != foo { baz() } } @entry def main { foo(.FOO, null) bar(.BAR, null) } enum Foo { FOO BAR } ", " (function() { function c(a, b) { a < 1 && b(), a < 2 && b(), a > 1 && b(), a > 0 && b(), a || b(), a && b(), a ^ 1 || b(), a ^ 1 && b(); } function d(a, b) { 1 < a && b(), 0 < a && b(), 1 > a && b(), 2 > a && b(), a || b(), a && b(), 1 ^ a || b(), 1 ^ a && b(); } function e() { c(0, null), d(1, null); } e(); })(); ").jsMangle.foldAllConstants # Doubles should not trigger integer comparison special cases test(" def foo(bar double, baz fn()) { if bar < 1 { baz() } if bar <= 1 { baz() } if bar > 1 { baz() } if bar >= 1 { baz() } if bar == 1 { baz() } if bar != 1 { baz() } } def bar(foo double, baz fn()) { if 1 < foo { baz() } if 1 <= foo { baz() } if 1 > foo { baz() } if 1 >= foo { baz() } if 1 == foo { baz() } if 1 != foo { baz() } } @entry def main { foo(0, null) bar(0, null) } ", " (function() { function c(a, b) { a < 1 && b(), a <= 1 && b(), a > 1 && b(), a >= 1 && b(), a == 1 && b(), a != 1 && b(); } function d(a, b) { 1 < a && b(), 1 <= a && b(), 1 > a && b(), 1 >= a && b(), 1 == a && b(), 1 != a && b(); } function e() { c(0, null), d(0, null); } e(); })(); ").jsMangle # Test a special case for comparison with -1 test(" def foo(bar string) { if \"foo\" in bar { dynamic.bar() } } @entry def main { foo(null) } ", " (function() { function a(b) { ~b.indexOf('foo') && bar(); } function c() { a(null); } c(); })(); ").jsMangle.inlineAllFunctions.foldAllConstants # Test index to member conversions test(" def foo(map StringMap) { dynamic.test(map[\"x\"]) dynamic.test(map[\"_\"]) dynamic.test(map[\"0\"]) dynamic.test(map[\"x0\"]) dynamic.test(map[\"if\"]) } def bar(map StringMap) { map[\"x\"] = dynamic.test() map[\"_\"] = dynamic.test() map[\"0\"] = dynamic.test() map[\"x0\"] = dynamic.test() map[\"if\"] = dynamic.test() } @entry def main { foo(null) bar(null) } ", " (function() { function d(a) { test(a.get('x')), test(a.get('_')), test(a.get('0')), test(a.get('x0')), test(a.get('if')); } function e(a) { b(a, 'x', test()), b(a, '_', test()), b(a, '0', test()), b(a, 'x0', test()), b(a, 'if', test()); } function g() { d(null), e(null); } function b(f, a, c) { return f.set(a, c), c; } g(); })(); ").jsMangle.inlineAllFunctions.foldAllConstants # Test dead code elimination with constants test(" enum Foo { FOO BAR BAZ } const foo = Foo.BAZ @entry def bar { if foo == .FOO { dynamic.test(\"FOO\") } else if foo == .BAR { dynamic.test(\"BAR\") } else if foo == .BAZ { dynamic.test(\"BAZ\") } else { dynamic.test(\"FAIL\") } } ", " (function() { function a() { test('BAZ'); } a(); })(); ").jsMangle.foldAllConstants # Test return statement collapsing test(" @entry def foo { if dynamic.x { dynamic.y() return } } ", " (function() { function a() { if (x) { y(); } } a(); })(); ").jsMangle # Test return statement collapsing test(" @entry def foo { if dynamic.x { return } dynamic.y() dynamic.y() } ", " (function() { function a() { x || (y(), y()); } a(); })(); ").jsMangle # Test return statement collapsing test(" @entry def foo { if dynamic.x { return } dynamic.y() if dynamic.x { return } dynamic.y() } ", " (function() { function a() { x || (y(), x || y()); } a(); })(); ").jsMangle # Test return statement collapsing test(" @entry def foo { if dynamic.x { if dynamic.y { return } dynamic.z() dynamic.z() } } ", " (function() { function a() { x && (y || (z(), z())); } a(); })(); ").jsMangle # Test return statement collapsing test(" @entry def foo { if dynamic.x { if dynamic.y { return } dynamic.z() dynamic.z() } dynamic.z() } ", " (function() { function a() { if (x) { if (y) { return; } z(), z(); } z(); } a(); })(); ").jsMangle # Test return statement collapsing test(" @entry def foo { while dynamic.x { if dynamic.y { return } dynamic.z() dynamic.z() } } ", " (function() { function a() { for (; x;) { if (y) { return; } z(), z(); } } a(); })(); ").jsMangle # Test return statement collapsing test(" def foo { dynamic.a() dynamic.b() if dynamic.c() { if dynamic.d() { return } dynamic.e() dynamic.f() } } @entry def bar { dynamic.a() dynamic.b() if dynamic.c() { if dynamic.d() { return } dynamic.e() dynamic.f() } foo } ", " (function() { function a() { a(), b(), c() && (d() || (e(), f())); } function b() { if (a(), b(), c()) { if (d()) { return; } e(), f(); } a(); } b(); })(); ").jsMangle # Test continue statement collapsing test(" @entry def foo { while dynamic.x { if dynamic.y { dynamic.z() continue } } } ", " (function() { function a() { for (; x;) { if (y) { z(); } } } a(); })(); ").jsMangle # Test continue statement collapsing test(" @entry def foo { while dynamic.x { if dynamic.y { continue } dynamic.z() dynamic.z() } } ", " (function() { function a() { for (; x;) { y || (z(), z()); } } a(); })(); ").jsMangle # Test continue statement collapsing test(" @entry def foo { while dynamic.x { if dynamic.y { continue } dynamic.z() if dynamic.y { continue } dynamic.z() } } ", " (function() { function a() { for (; x;) { y || (z(), y || z()); } } a(); })(); ").jsMangle # Test continue statement collapsing test(" @entry def foo { while dynamic.x { if dynamic.y { dynamic.z() if dynamic.y { continue } dynamic.z() dynamic.z() } } } ", " (function() { function a() { for (; x;) { y && (z(), y || (z(), z())); } } a(); })(); ").jsMangle # Test continue statement collapsing test(" @entry def foo { while dynamic.x { if dynamic.y { dynamic.z() if dynamic.y { continue } dynamic.z() dynamic.z() } dynamic.z() } } ", " (function() { function a() { for (; x;) { if (y) { if (z(), y) { continue; } z(), z(); } z(); } } a(); })(); ").jsMangle # Test continue statement collapsing test(" @entry def foo { while dynamic.x { if dynamic.y { dynamic.z() continue } dynamic.z() dynamic.z() } } ", " (function() { function a() { for (; x;) { y ? z() : (z(), z()); } } a(); })(); ").jsMangle # Test continue statement collapsing test(" def foo { while dynamic.x { dynamic.a() dynamic.b() if dynamic.c() { if dynamic.d() { continue } dynamic.e() dynamic.f() } } } @entry def bar { while dynamic.x { dynamic.a() dynamic.b() if dynamic.c() { if dynamic.d() { continue } dynamic.e() dynamic.f() } foo } } ", " (function() { function a() { for (; x;) { a(), b(), c() && (d() || (e(), f())); } } function b() { for (; x;) { if (a(), b(), c()) { if (d()) { continue; } e(), f(); } a(); } } b(); })(); ").jsMangle # Test mangling the "self" variable test(" @export class Foo { var x = 100 def foo fn() int { var y = dynamic.get() return => x + y } def bar fn() int { var y = dynamic.get() while true { return => x + y } } def baz fn() int { while true { return => x } } def test fn() int { return => x } } ", " (function(d) { var c; d.Foo = function() { this.x = 100; }; c = d.Foo.prototype; c.foo = function() { var b = this, a = get(); return function() { return b.x + a; }; }; c.bar = function() { for (var b = this, a = get();;) { return function() { return b.x + a; }; } }; c.baz = function() { for (var a = this;;) { return function() { return a.x; }; } }; c.test = function() { var a = this; return function() { return a.x; }; }; })(this); ").jsMangle # Test a loop special case test(" def foo { while true { dynamic.a() if dynamic.b() { break } } } def bar { while dynamic.a() { dynamic.b() if dynamic.c() { break } } } @entry def main { foo bar } ", " (function() { function a() { for (; a(), !b();) { } } function b() { for (; a() && (b(), !c());) { } } function c() { a(), b(); } c(); })(); ").jsMangle # Test mangling the name of catch variables test(" @entry def foo { try { } catch foo dynamic { } } ", " (function() { function b() { try { } catch (a) { } } b(); })(); ").jsMangle test(" @entry def foo { while true { switch dynamic.x { case 0 { break } case 1 { switch dynamic.y { case 0 { break } } } } } for i = 0; i < 10; i++ { switch dynamic.x { case 0 { break } case 1 { switch dynamic.y { case 0 { break } } } } } for i in 0..10 { switch dynamic.x { case 0 { break } case 1 { switch dynamic.y { case 0 { break } } } } } } ", " (function() { function c() { d: for (;;) { switch (x) { case 0: { break d; } case 1: { if (y === 0) { break d; } break; } } } e: for (var a = 0; a < 10; a = a + 1 | 0) { switch (x) { case 0: { break e; } case 1: { if (y === 0) { break e; } break; } } } f: for (var b = 0; b < 10; b = b + 1 | 0) { switch (x) { case 0: { break f; } case 1: { if (y === 0) { break f; } break; } } } } c(); })(); ").jsMangle # Test moving the default case outside the switch statement test(" @entry def foo { switch dynamic.a { case 0 { return } default { dynamic.b1() dynamic.b2() } } switch dynamic.a { case 0 { dynamic.c() } default { dynamic.d1() dynamic.d2() } } dynamic.e() } ", " (function() { function a() { a !== 0 && (b1(), b2(), a === 0 ? c() : (d1(), d2()), e()); } a(); })(); ").jsMangle # Test inline identifiers in object literals test(" @export def foo dynamic { return {\"a\": 0, \"0\": 0, \" \": 0} } ", " (function(a) { a.foo = function() { return {a: 0, '0': 0, ' ': 0}; }; })(this); ").jsMangle # Test sequence hook rotation test(" @export def foo dynamic { return () bool => { dynamic.x() if dynamic.q { dynamic.x() return true } return false } } ", " (function(a) { a.foo = function() { return function() { return x(), q ? (x(), !0) : !1; }; }; })(this); ").jsMangle # Test toString() removal test(" @export def foo(x int, f Foo, b Bar) { var z = Baz.new dynamic.t(\"\" + x.toString) dynamic.t(dynamic.q + x.toString) dynamic.t(x.toString + \"\") dynamic.t(x.toString + x.toString) dynamic.t(f.toString + \"\") dynamic.t(b.toString(0) + \"\") dynamic.t(z.toString + \"\") } @import class Foo { def toString string } @import class Bar { def toString(x int) string } class Baz { def toString string { return \"\" } } ", " (function(g) { function f() { } f.prototype.c = function() { return ''; }; g.foo = function(a, d, e) { var b = new f; t('' + a), t(q + a.toString()), t(a + ''), t(a.toString() + a), t(d + ''), t(e.toString(0) + ''), t(b.c() + ''); }; })(this); ").jsMangle # Make sure folding doesn't leave extra variables in this case test(" @export def foo int { var a = 0, b = 0 var c = 0 return a + b + c } ", " (function(a) { a.foo = function() { return 0; }; })(this); ").jsMangle.foldAllConstants # Make sure special runtime function names and definitions are mangled test(" class Foo {} class Bar : Foo {} @entry def main(args List) int { Bar.new return args.count * 100 } ", " (function() { var c = Object.create || function(a) { return {__proto__: a}; }; function d(a, b) { a.prototype = c(b.prototype), a.prototype.constructor = a; } var e = Math.imul || function(a, b) { return (a * (b >>> 16) << 16) + a * (b & 65535) | 0; }; function h(a) { return new g, e(a.length, 100); } function f() { } function g() { f.call(this); } d(g, f); process.exit(h(process.argv.slice(2))); })(); ").jsMangle # Make sure casting doesn't cause the enum declaration to be emitted test(" enum Foo { FOO } @entry def main int { return 0 as Foo } ", " (function() { function a() { return 0; } process.exit(a()); })(); ").jsMangle # Make sure constant folding folds string lengths test(" @entry def foo int { return \"abc\".count } ", " (function() { function a() { return 3; } process.exit(a()); })(); ").jsMangle.foldAllConstants # Lock down the name generation algorithm test(" @import { def x int def y } @entry def main { var a0 = x, a1 = x, a2 = x, a3 = x, a4 = x, a5 = x, a6 = x, a7 = x, a8 = x, a9 = x y var b0 = x, b1 = x, b2 = x, b3 = x, b4 = x, b5 = x, b6 = x, b7 = x, b8 = x, b9 = x y var c0 = x, c1 = x, c2 = x, c3 = x, c4 = x, c5 = x, c6 = x, c7 = x, c8 = x, c9 = x y var d0 = x, d1 = x, d2 = x, d3 = x, d4 = x, d5 = x, d6 = x, d7 = x, d8 = x, d9 = x y var e0 = x, e1 = x, e2 = x, e3 = x, e4 = x, e5 = x, e6 = x, e7 = x, e8 = x, e9 = x y var f0 = x, f1 = x, f2 = x, f3 = x, f4 = x, f5 = x, f6 = x, f7 = x, f8 = x, f9 = x y var g0 = x, g1 = x, g2 = x, g3 = x, g4 = x, g5 = x, g6 = x, g7 = x, g8 = x, g9 = x y var h0 = x, h1 = x, h2 = x, h3 = x, h4 = x, h5 = x, h6 = x, h7 = x, h8 = x, h9 = x y var i0 = x, i1 = x, i2 = x, i3 = x, i4 = x, i5 = x, i6 = x, i7 = x, i8 = x, i9 = x y var j0 = x, j1 = x, j2 = x, j3 = x, j4 = x, j5 = x, j6 = x, j7 = x, j8 = x, j9 = x y var k0 = x, k1 = x, k2 = x, k3 = x, k4 = x, k5 = x, k6 = x, k7 = x, k8 = x, k9 = x } ", " (function() { function eb() { var a = x(), b = x(), c = x(), d = x(), e = x(), f = x(), g = x(), h = x(), i = x(), j = x(); y(); var k = x(), l = x(), m = x(), n = x(), o = x(), p = x(), q = x(), r = x(), s = x(), t = x(); y(); var u = x(), v = x(), w = x(), z = x(), A = x(), B = x(), C = x(), D = x(), E = x(), F = x(); y(); var G = x(), H = x(), I = x(), J = x(), K = x(), L = x(), M = x(), N = x(), O = x(), P = x(); y(); var Q = x(), R = x(), S = x(), T = x(), U = x(), V = x(), W = x(), X = x(), Y = x(), Z = x(); y(); var _ = x(), $ = x(), aa = x(), ba = x(), ca = x(), da = x(), ea = x(), fa = x(), ga = x(), ha = x(); y(); var ia = x(), ja = x(), ka = x(), la = x(), ma = x(), na = x(), oa = x(), pa = x(), qa = x(), ra = x(); y(); var sa = x(), ta = x(), ua = x(), va = x(), wa = x(), xa = x(), ya = x(), za = x(), Aa = x(), Ba = x(); y(); var Ca = x(), Da = x(), Ea = x(), Fa = x(), Ga = x(), Ha = x(), Ia = x(), Ja = x(), Ka = x(), La = x(); y(); var Ma = x(), Na = x(), Oa = x(), Pa = x(), Qa = x(), Ra = x(), Sa = x(), Ta = x(), Ua = x(), Va = x(); y(); var Wa = x(), Xa = x(), Ya = x(), Za = x(), _a = x(), $a = x(), ab = x(), bb = x(), cb = x(), db = x(); } eb(); })(); ").jsMangle # Make sure it's possible to not clobber over jQuery test(" @import { def x int def y @rename(\"$\") var jQuery dynamic } @entry def main { var a0 = x, a1 = x, a2 = x, a3 = x, a4 = x, a5 = x, a6 = x, a7 = x, a8 = x, a9 = x y var b0 = x, b1 = x, b2 = x, b3 = x, b4 = x, b5 = x, b6 = x, b7 = x, b8 = x, b9 = x y var c0 = x, c1 = x, c2 = x, c3 = x, c4 = x, c5 = x, c6 = x, c7 = x, c8 = x, c9 = x y var d0 = x, d1 = x, d2 = x, d3 = x, d4 = x, d5 = x, d6 = x, d7 = x, d8 = x, d9 = x y var e0 = x, e1 = x, e2 = x, e3 = x, e4 = x, e5 = x, e6 = x, e7 = x, e8 = x, e9 = x y var f0 = x, f1 = x, f2 = x, f3 = x, f4 = x, f5 = x, f6 = x, f7 = x, f8 = x, f9 = x jQuery() } ", " (function() { function ja() { var a = x(), b = x(), c = x(), d = x(), e = x(), f = x(), g = x(), h = x(), i = x(), j = x(); y(); var k = x(), l = x(), m = x(), n = x(), o = x(), p = x(), q = x(), r = x(), s = x(), t = x(); y(); var u = x(), v = x(), w = x(), z = x(), A = x(), B = x(), C = x(), D = x(), E = x(), F = x(); y(); var G = x(), H = x(), I = x(), J = x(), K = x(), L = x(), M = x(), N = x(), O = x(), P = x(); y(); var Q = x(), R = x(), S = x(), T = x(), U = x(), V = x(), W = x(), X = x(), Y = x(), Z = x(); y(); var _ = x(), aa = x(), ba = x(), ca = x(), da = x(), ea = x(), fa = x(), ga = x(), ha = x(), ia = x(); $(); } ja(); })(); ").jsMangle # Make sure inlined helper functions are also constant folded test(" def bar(a int, b int) int { return 'A' | a << 8 | 'B' << 16 | b << 24 } @entry def main int { const a = 1 const b = 2 const foo = bar(a, b) return foo } ", " (function() { function a() { return 37880129; } process.exit(a()); })(); ").jsMangle.foldAllConstants.inlineAllFunctions # This tests a bug where overloaded imported constructors were emitted incorrectly test(" @entry def main { foo(Foo.new([])) } def foo(bar Foo) Foo { return Foo.new(0) } @import class Foo { def new(length int) def new(array List) } ", " (function() { function c() { a(new Foo([])); } function a(b) { return new Foo(0); } c(); })(); ").jsMangle # Make sure the 32-bit integer negation special-case is handled correctly test(" def foo List { return [0x7FFFFFFF, -0x7FFFFFFF, 0x80000000, -0x80000000] } @entry def main { foo } ", " (function() { function a() { return [2147483647, -2147483647, 1 << 31, -(1 << 31) | 0]; } function b() { a(); } b(); })(); ").jsMangle # Make sure the 32-bit integer negation special-case is handled correctly test(" def foo List { return [0x7FFFFFFF, -0x7FFFFFFF, 0x80000000, -0x80000000] } @entry def main { foo } ", " (function() { function a() { return [2147483647, -2147483647, 1 << 31, 1 << 31]; } function b() { a(); } b(); })(); ").jsMangle.foldAllConstants # Test left shift substitution test(" def foo List { return [ 16384, 131072, 1073741824, -2147483648, 0xFF000, 0xFF0000, 0xFF00000, 0xFF000000, ] } @entry def main { foo } ", " (function() { function a() { return [16384, 1 << 17, 1 << 30, 1 << 31, 1044480, 255 << 16, 255 << 20, 255 << 24]; } function b() { a(); } b(); })(); ").jsMangle.foldAllConstants # Wrapping shouldn't introduce casts test(" type Foo = int @entry def main int { return 0 as Foo as int as Foo as int } ", " (function() { function a() { return 0; } process.exit(a()); })(); ").jsMangle # Wrapping should work with constant folding test(" @entry def main { dynamic.foo((0 as Foo) == (1 as Foo)) dynamic.foo((0 as int) == (1 as int)) dynamic.foo(0 == 1) } type Foo = int ", " (function() { function a() { foo(!1), foo(!1), foo(!1); } a(); })(); ").jsMangle.foldAllConstants # Check that empty switch statements are removed test(" @entry def main { switch dynamic.x { default { dynamic.y() } } } ", " (function() { function a() { y(); } a(); })(); ").jsMangle # Check that empty switch statements are removed test(" @entry def main { switch dynamic.x() { default { dynamic.y() } } } ", " (function() { function a() { x(), y(); } a(); })(); ").jsMangle # Check that switch statements have a single element turn into if statements, which are then further optimized test(" @entry def main { switch dynamic.x { case X { dynamic.foo() } } switch dynamic.y { case Y { dynamic.bar() } } } type Foo = int const X = 0 const Y = 0 as Foo ", " (function() { function a() { x === 0 && foo(), y === 0 && bar(); } a(); })(); ").jsMangle.foldAllConstants # Check that switch statements that become single-element after other optimizations turn into if statements test(" @entry def main { switch dynamic.x { case 0 { return } default { dynamic.y() } } } ", " (function() { function a() { x !== 0 && y(); } a(); })(); ").jsMangle # Check that double-element switch statements turn into if statements and are optimized further test(" @entry def main { switch dynamic.x { case 0 { dynamic.y() } default { dynamic.z() } } } ", " (function() { function a() { x === 0 ? y() : z(); } a(); })(); ").jsMangle # Check that pass-through switch statements are optimized test(" enum Foo { FOO BAR BAZ } def a(x int) Foo { switch x { case 0 { return .FOO } case 1 { return .BAR } case 2 { return .BAZ } default { return .FOO } } } def b(x int) Foo { switch x { case 1 { return .FOO } case 2 { return .BAR } case 3 { return .BAZ } default { return .FOO } } } def c(x int) Foo { switch x { case 0 { return .BAR } case 1 { return .BAZ } default { return .FOO } } } def d(x Foo) int { switch x { case .FOO { return 0 } case .BAR { return 1 } case .BAZ { return 2 } default { return 0 } } } def e(x Foo) int { switch x { case .FOO { return 1 } case .BAR { return 2 } case .BAZ { return 3 } default { return 0 } } } def f(x Foo) int { switch x { case .BAR { return 0 } case .BAZ { return 1 } default { return 0 } } } def g(x int) Foo { switch x { case 1 { return .BAR } case 0 { return .FOO } default { return .FOO } } } def h(x int) Foo { switch x { case 0 { return .BAR } case 1 { return .FOO } default { return .FOO } } } def i(x int) Foo { switch x { case 0 { return .FOO } case 2 { return .BAZ } default { return .FOO } } } @entry def main { a(0) b(0) c(0) d(.FOO) e(.BAR) f(.BAZ) g(0) h(0) i(0) } ", " (function() { function b(a) { return a > -1 && a < 3 ? a : 0; } function c(a) { return a > 0 && a < 4 ? a - 1 | 0 : 0; } function d(a) { return a > -1 && a < 2 ? a + 1 | 0 : 0; } function e(a) { return a > -1 && a < 3 ? a : 0; } function f(a) { return a > -1 && a < 3 ? a + 1 | 0 : 0; } function g(a) { return a > 0 && a < 3 ? a - 1 | 0 : 0; } function h(a) { return a > -1 && a < 2 ? a : 0; } function i(a) { switch (a) { case 0: { return 1; } case 1: { return 0; } } return 0; } function j(a) { switch (a) { case 0: case 2: { return a; } } return 0; } function k() { b(0), c(0), d(0), e(0), f(1), g(2), h(0), i(0), j(0); } k(); })(); ").jsMangle.foldAllConstants # Check parentheses omission for object creation test(" @entry def main { dynamic.Foo.new() dynamic.Foo.new(100) dynamic.Foo.new().foo dynamic.Foo.new(100).foo } ", " (function() { function a() { new Foo, new Foo(100), new Foo().foo, new Foo(100).foo; } a(); })(); ").jsMangle # Test equivalence of function and lambda folding test(" def foo(x int) { if x == 0 { return } dynamic.use(x) } var bar = (x int) => { if x == 0 { return } dynamic.use(x) } @entry def main { foo(0) bar(0) } ", " (function() { function b(a) { a && use(a); } function d() { b(0), c(0); } var c = function(a) { a && use(a); }; d(); })(); ").jsMangle test(" class Class {} enum Enum {} type Type = int @entry def main { dynamic.foo(dynamic.bar is dynamic) dynamic.foo(dynamic.bar is fn()) dynamic.foo(dynamic.bar is bool) dynamic.foo(dynamic.bar is double) dynamic.foo(dynamic.bar is Enum) dynamic.foo(dynamic.bar is int) dynamic.foo(dynamic.bar is Type) dynamic.foo(dynamic.bar is string) dynamic.foo(dynamic.bar is Class) dynamic.foo(dynamic.bar is dynamic.Dynamic) } ", " (function() { var b; function f(a) { return a === (a | 0); } function g(a) { return a === !!a; } function h(a) { return typeof a === 'number'; } function i(a) { return typeof a === 'string'; } function j() { foo(bar), foo(bar instanceof Function), foo(g(bar)), foo(h(bar)), foo(f(bar)), foo(f(bar)), foo(f(bar)), foo(i(bar)), foo(bar instanceof c), foo(bar instanceof Dynamic); } var c = {}; var d = {}; var e = {}; j(); })(); ").jsMangle # Test default values for wrapped types test(" @entry def main { var a Bool var b Int var c Double var d String var e Bar } type Bool = bool type Int = int type Double = double type String = string type Bar = Foo enum Foo {} ", " (function() { function f() { var a = !1, b = 0, c = 0, d = null, e = 0; } f(); })(); ").jsMangle # This used to be a crash during globalization test(" class Foo { def foo(foo fn(Foo) T) T { return foo(self) } } @entry def main { var foo fn(Foo) dynamic = bar => bar.foo(foo) } ", " (function() { function d() { var a = function(b) { return c(b, a); }; } function c(b, a) { return a(b); } d(); })(); ").jsMangle.globalizeAllFunctions test(" class Foo {} namespace Foo { def foo(self Foo, baz fn(Foo) T) T { return baz(self) } } @entry def main { var foo fn(Foo) dynamic = x => Foo.foo(x, foo) foo(Foo.new) } ", " (function() { function d() { var a = function(b) { return a(b); }; a(new c); } function c() { } d(); })(); ").jsMangle.inlineAllFunctions # There used to be a bug where generic inlineable functions were still emitted test(" class Foo { var bar List = [] def foo(baz fn(Foo) T) List { return bar.map(baz) } } @entry def main { var foo fn(Foo) dynamic = bar => bar.foo(foo) foo(Foo.new) } ", " (function() { function e() { var a = function(c) { return c.a.map(a); }; a(new d); } function d() { this.a = []; } e(); })(); ").jsMangle.inlineAllFunctions.globalizeAllFunctions # Make sure wrapped types are unwrapped before using "instanceof" test(" class Foo {} type Bar = Foo @entry def main { var foo = Foo.new var bar = foo is Foo || foo is Bar } ", " (function() { function e() { var a = new b, d = a instanceof b || a instanceof b; } function b() { } var c = {}; e(); })(); ").jsMangle # Basic usage of interfaces test(" interface IFoo { def foo } class Foo :: IFoo { def foo {} } @entry def main { var foo = Foo.new var ifoo = foo as IFoo foo.foo ifoo.foo } ", " (function() { function e() { var b = new d, c = b; b.a(), c.a(); } function d() { } d.prototype.a = function() { }; e(); })(); ").jsMangle # Basic usage of interfaces test(" interface IFoo { def foo } class Foo :: IFoo { def foo {} } class Bar :: IFoo { def foo {} } @entry def main { var foo = Foo.new var bar = Foo.new var ifoo = foo as IFoo foo.foo bar.foo ifoo.foo ifoo = bar ifoo.foo } ", " (function() { function f() { var c = new e, d = new e, b = c; c.a(), d.a(), b.a(), b = d, b.a(); } function e() { } e.prototype.a = function() { }; f(); })(); ").jsMangle # Interface removal with globalization test(" interface IFoo { def foo } class Foo :: IFoo { def foo {} } @entry def main { var foo = Foo.new var ifoo = foo as IFoo foo.foo ifoo.foo } ", " (function() { function e() { var b = new d, c = b; a(b), a(c); } function a(b) { } function d() { } e(); })(); ").jsMangle.globalizeAllFunctions # Interface removal should not trigger even with globalization test(" interface IFoo { def foo } class Foo :: IFoo { def foo {} } class Bar :: IFoo { def foo {} } @entry def main { var foo = Foo.new var bar = Bar.new var ifoo = foo as IFoo foo.foo bar.foo ifoo.foo ifoo = bar ifoo.foo } ", " (function() { function g() { var c = new e, d = new f, b = c; c.a(), d.a(), b.a(), b = d, b.a(); } function e() { } e.prototype.a = function() { }; function f() { } f.prototype.a = function() { }; g(); })(); ").jsMangle.globalizeAllFunctions # Check overloaded functions when an interface is present test(" interface I { def foo(x int) # I.foo(x int) def foo(x bool) # I.foo(x bool) } class Foo :: I { def foo(x int) {} # Foo.foo(x int) def foo(x bool) {} # Foo.foo(x bool) } class Bar :: I { def foo(x string) {} # Bar.foo(x string) def foo(x int) {} # Bar.foo(x int) def foo(x bool) {} # Bar.foo(x bool) } @entry def main { var foo = Foo.new var bar = Bar.new foo.foo(0) foo.foo(false) bar.foo(0) bar.foo(false) } ", " (function() { var e; function h() { var c = new f, d = new g; c.a(0), c.b(!1), d.a(0), d.b(!1); } function f() { } e = f.prototype; // Foo.foo(x int) e.a = function(c) { }; // Foo.foo(x bool) e.b = function(c) { }; function g() { } e = g.prototype; // Bar.foo(x int) e.a = function(c) { }; // Bar.foo(x bool) e.b = function(c) { }; h(); })(); ").jsMangle # Check shortening of long reciprocals test(" @entry def foo { var x = 0 var y = 0.3333333333333333 + x * 1.3333333333333333 } ", " (function() { function c() { var a = 0, b = 1 / 3 + a * (4 / 3); } c(); })(); ").jsMangle # Check inlining of instance constants test(" class Foo { const foo = 1 } @import class Bar { const bar = 2 } def test(foo Foo, bar Bar) int { return foo.foo + bar.bar } @entry def main { test(null, null) } ", " (function() { function b(a, c) { return a.a + 2 | 0; } function d() { b(null, null); } d(); })(); ").jsMangle.foldAllConstants # Check lambda cloning when mangling is active test(" class Foo { const f = (x int) => x def new {} def new(x int) {} } @entry def main { Foo.new Foo.new(0) } ", " (function() { function e() { new d, new d.b(0); } function d() { this.a = function(a) { return a; }; } d.b = function(a) { this.a = function(c) { return c; }; }; d.b.prototype = d.prototype; e(); })(); ").jsMangle # Basic constant folding check test(" class Foo { const foo = 0 } @entry def main { Foo.new } ", " (function() { function c() { new b; } function b() { this.a = 0; } c(); })(); ").jsMangle.foldAllConstants # Check constant folding when the constant is defined in the constructor test(" class Foo { const foo int def new { foo = 1 } } @entry def main int { var x = Foo.new return x.foo } ", " (function() { function c() { var a = new b; return a.a; } function b() { this.a = 1; } process.exit(c()); })(); ").jsMangle.foldAllConstants # Check mangling with typed catch blocks test(" class Foo { } @entry def main { try { throw Foo.new } catch e Foo { throw e } } ", " (function() { function c() { try { throw new b; } catch (a) { if (a instanceof b) { throw a; } else { throw a; } } } function b() { } c(); })(); ").jsMangle # Check mangling of global variables test(" @export def main double { return a + ns1.b + c + x + ns2.y + z } var a = 0.0 namespace ns1 { var b = 0.0 } var c = 0.0 @export var x = 0.0 @export namespace ns2 { var y = 0.0 } var z = 0.0 ", " (function(f) { f.main = function() { return a + b + c + f.x + f.ns2.y + d; }; f.ns2 = {}; f.x = 0; f.ns2.y = 0; var a = 0, c = 0, d = 0, b = 0; })(this); ").jsMangle # Check mangling of hook expressions containing assignments test(" class Foo { var y Foo = null } @export def test(x Foo) Foo { return (x = x.y) != null ? x : Foo.new } ", " (function(c) { function b() { this.a = null; } c.test = function(a) { return (a = a.a) || new b; }; })(this); ").jsMangle # Interface removal with an instance variable shouldn't prevent dead code stripping a class constructor test(" interface IFoo { def foo } class Foo :: IFoo { const _foo fn(Foo) def foo { _foo(self) } } @export def test(foo Foo) { foo.foo } ", " (function(c) { function b(a) { a.a(a); } c.test = function(a) { b(a); }; })(this); ").jsMangle.globalizeAllFunctions # Check for a crash due to a cast with a resolved type of null after inlining test(" class Foo {} namespace Foo { def new(x bool) Foo { return x as dynamic } } @entry def test { var foo = Foo.new(false) } ", " (function() { function b() { var a = !1; } b(); })(); ").jsMangle.inlineAllFunctions # Check for prototype caching for secondary constructors test(" class Foo { def new {} def new(x int) {} def foo {} } class Bar { def new {} def foo {} } class Baz { def new {} def new(x int) {} } @entry def main { Foo.new.foo Foo.new(0).foo Bar.new.foo Baz.new Baz.new(0) } ", " (function() { var e; function j() { new g().a(), new g.b(0).a(), new h().c(), new i, new i.d(0); } function g() { } e = g.prototype; g.b = function(f) { }; g.b.prototype = e; e.a = function() { }; function h() { } h.prototype.c = function() { }; function i() { } i.d = function(f) { }; i.d.prototype = i.prototype; j(); })(); ").jsMangle # Test assignment collapsing test(" def foo(a int, b int, c int, f fn() int) { a = 1 b = 1 c = 1 a = 1 b = 2 c = 3 a = f() b = f() c = f() } def bar(a dynamic, f fn() int) { a[0] = 1 a[1] = 1 a[2] = 1 a[0] = 1 a[1] = 2 a[2] = 3 a[0] = f() a[1] = f() a[2] = f() } def baz(a dynamic, f fn() int) { a.x = 1 a[1] = 1 a[\"y\"] = 1 a.x = 1 a[1] = 2 a[\"y\"] = 3 a.x = f() a[1] = f() a[\"y\"] = f() } @entry def main { foo(0, 0, 0, null) bar(0, null) baz(0, null) } ", " (function() { function e(a, b, c, d) { a = 1, b = 1, c = 1, a = 1, b = 2, c = 3, a = d(), b = d(), c = d(); } function f(a, b) { a[0] = 1, a[1] = 1, a[2] = 1, a[0] = 1, a[1] = 2, a[2] = 3, a[0] = b(), a[1] = b(), a[2] = b(); } function g(a, b) { a.x = 1, a[1] = 1, a.y = 1, a.x = 1, a[1] = 2, a.y = 3, a.x = b(), a[1] = b(), a.y = b(); } function h() { e(0, 0, 0, null), f(0, null), g(0, null); } h(); })(); ").jsMangle # Test that the number shorterer that turns "33554432" into "1 << 25" doesn't cause a crash for enum values test(" flags Foo { A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z } @entry def test int { return Foo.Z } ", " (function() { function c() { return b.a; } var b = { a: 1 << 25 }; process.exit(c()); })(); ").jsMangle # Check that int casts aren't inserted when != turns into ^ test(" @entry def test { var a = 0, b = 0, c = 0, d = 0 while a - b != c - d {} while ((a - b) ^ (c - d)) != 0 {} while (a - b | 0) != (c - d | 0) {} while (a - b | 1) != (c - d | 1) {} } ", " (function() { function e() { for (var a = 0, b = 0, c = 0, d = 0; a - b ^ c - d;) { } for (; a - b ^ c - d;) { } for (; a - b ^ c - d;) { } for (; (a - b | 1) ^ (c - d | 1);) { } } e(); })(); ").jsMangle # Check mangled map constructors test(" @entry def test { var a = {1: 2, 3: 4} var b = {\"1\": 2, \"3\": 4} } ", " (function() { function f() { var d = c(c(new Map, 1, 2), 3, 4), e = b(b(new Map, '1', 2), '3', 4); } function b(a, d, e) { return a.set(d, e), a; } function c(a, d, e) { return a.set(d, e), a; } f(); })(); ").jsMangle.inlineAllFunctions # Check export syntax test(" @export { class Class { def foo { new } } enum Enum { def toString { Namespace.foo } } namespace Namespace { def foo { (0 as Enum).toString } } def Function {} var Variable = 0 } ", " (function(c) { var b = {}; b.toString = function(a) { c.Namespace.foo(); }; c.Function = function() { }; c.Class = function() { }; c.Class.prototype.foo = function() { new c.Class; }; c.Enum = {}; c.Namespace = {}; c.Namespace.foo = function() { b.toString(0); }; c.Variable = 0; })(this); ").jsMangle # Check parenthesis removal for new expressions in cast expressions test(" @export def foo dynamic { return (dynamic.Foo.new as dynamic).foo } ", " (function(a) { a.foo = function() { return new Foo().foo; }; })(this); ").jsMangle # Check parenthesis removal for new expressions in parameterized expressions test(" @export def foo dynamic { return dynamic.Foo.new.foo } ", " (function(c) { var a; c.foo = function() { return new Foo().foo; }; })(this); ").jsMangle test(" @entry def main int { var x dynamic = 1 x = x + 1 x = x + 1 return x } ", " (function() { function b() { var a = 1; return a = a + 1, a = a + 1, a; } process.exit(b()); })(); ").jsMangle test(" @entry def main int { var x = 1 x++ x++ return x } ", " (function() { function b() { var a = 1; return a = a + 1 | 0, a = a + 1 | 0, a; } process.exit(b()); })(); ").jsMangle test(" class Foo { var value double } @export def main double { var x = Foo.new(1) var y = x x.value = y.value + 1 x.value = y.value + 1 return x.value } ", " (function(d) { function c(a) { this.a = a; } d.main = function() { var a = new c(1), b = a; return a.a = b.a + 1, a.a = b.a + 1, a.a; }; })(this); ").jsMangle test(" @entry def main int { var x dynamic = [1] var y = x x[0] = y[0] + 1 x[0] = y[0] + 1 return x[0] } ", " (function() { function c() { var a = [1], b = a; return a[0] = b[0] + 1, a[0] = b[0] + 1, a[0]; } process.exit(c()); })(); ").jsMangle # Check block comments before a statement test(" @entry def main { # This is # a block # comment return } ", " (function() { function a() { // This is // a block // comment return; } a(); })(); ").jsMangle # Check trailing block comments test(" @entry def main { # This is # a block # comment } ", " (function() { function a() { } a(); })(); ").jsMangle # Check block comments in else statements test(" @export def main(x bool) { if x { x = false } else { # This is # a block # comment } } ", " (function(b) { b.main = function(a) { a && (a = !1); }; })(this); ").jsMangle } } ================================================ FILE: tests/javascript.minify.sk ================================================ namespace Skew.Tests { def testJavaScriptMinify { # Strings should be emitted using the shortest representation test(" @entry def main { dynamic.a(\"'\" + \"\\\"\") } ", " (function(){function main(){a(\"'\"+'\"')}main()})(); ").jsMinify # Check for various whitespace issues test(" def foo dynamic { dynamic.a(dynamic.b + +dynamic.c) dynamic.a(dynamic.b + -dynamic.c) dynamic.a(dynamic.b - +dynamic.c) dynamic.a(dynamic.b - -dynamic.c) return \"\" return dynamic.a return dynamic.a + dynamic.b return -dynamic.a return ~dynamic.a return !dynamic.a return [] return {} } @entry def main { foo } ", " (function(){function foo(){a(b+ +c);a(b+-c);a(b-+c);a(b- -c);return'';return a;return a+b;return-a;return~a;return!a;return[];return{}}function main(){foo()}main()})(); ").jsMinify # Check for whitespace with numeric literals test(" def foo dynamic { return -1 } def bar dynamic { return -1.5 } def baz dynamic { return 0 } @entry def main { foo bar baz } ", " (function(){function foo(){return-1}function bar(){return-1.5}function baz(){return 0}function main(){foo();bar();baz()}main()})(); ").jsMinify.foldAllConstants # Check for omitting the leading zero test(" @entry def foo { var x = [-1.5, -1.0, -0.5, -0.0, 0.0, 0.5, 1.0, 1.5] } ", " (function(){function foo(){var x=[-1.5,-1,-.5,0,0,.5,1,1.5]}foo()})(); ").jsMinify.foldAllConstants # There is no "dangling if" ambiguity here test(" @entry def main { if dynamic.a() { if dynamic.b() { var c = dynamic.d() dynamic.e(c, c) } else { var f = dynamic.g() dynamic.h(f, f) } } else { dynamic.i() } } ", " (function(){function main(){if(a()){if(b()){var c=d();e(c,c)}else{var f=g();h(f,f)}}else i()}main()})(); ").jsMinify # This must be careful about the "dangling if" ambiguity test(" @entry def main { if dynamic.a() { if dynamic.b() { if dynamic.c() { var d = dynamic.e() dynamic.f(d, d) } else { var g = dynamic.h() dynamic.i(g, g) } } } else { dynamic.j() } } ", " (function(){function main(){if(a()){if(b())if(c()){var d=e();f(d,d)}else{var g=h();i(g,g)}}else j()}main()})(); ").jsMinify # Another "dangling if" variant test(" @entry def main { if dynamic.a() { if dynamic.b() { var c = dynamic.d() dynamic.e(c, c) } else if dynamic.f() { var g = dynamic.h() dynamic.i(g, g) } } else { var j = dynamic.k() dynamic.l(j, j) } } ", " (function(){function main(){if(a()){if(b()){var c=d();e(c,c)}else if(f()){var g=h();i(g,g)}}else{var j=k();l(j,j)}}main()})(); ").jsMinify # A tricky "dangling if" issue test(" @entry def main { if dynamic.a() { while true { if dynamic.b() { break } } } else { dynamic.c() } } ", " (function(){function main(){if(a()){while(true)if(b())break}else c()}main()})(); ").jsMinify # Check that minification doesn't remove constants in switch statements unless necessary test(" @export const C = 2 @export def main(x int, y Foo) { switch x { case C { dynamic.a() } case C + 3 { dynamic.a() } } switch y { case .FOO { dynamic.b() } } } enum Foo { FOO } ", " (function(exports){var Foo={FOO:0};exports.main=function(x,y){switch(x){case exports.C:a();break;case 5:a();break}switch(y){case Foo.FOO:b();break}};exports.C=2})(this); ").jsMinify test(" def foo string { return 0.toString } @entry def main { foo } ", " (function(){function foo(){return (0).toString()}function main(){foo()}main()})(); ").jsMinify # Make sure null is allowed to convert to string test(" def foo string { return null } @entry def main { foo } ", " (function(){function foo(){return null}function main(){foo()}main()})(); ").jsMinify # Avoid a space after "case" if possible test(" @entry def main { var x = \"\" switch x { case \"x\" {} case x {} } } ", " (function(){function main(){var x='';switch(x){case'x':break;case x:break}}main()})(); ").jsMinify # Check complex post-keyword space removal test(" @entry def main int { var x = 0 if x == -1 { dynamic.foo() } else { dynamic.bar() } return 0 } ", " (function(){function b(){var a=0;return~a?bar():foo(),0}process.exit(b())})(); ").jsMinify.jsMangle # Check nested negation minification test(" @entry def main { var x = 0.0 var y = x - (-x) * 2 + x + (-x) * 2 } ", " (function(){function main(){var x=0;var y=x- -x*2+x+-x*2}main()})(); ").jsMinify # Check nested negation minification test(" @entry def main { var x = dynamic.z as double var y = x - (-2) * 2 + x + (-2) * 2 } ", " (function(){function main(){var x=+z;var y=x- -4+x+-4}main()})(); ").jsMinify.foldAllConstants # Remove the space before the "in" operator test(" @entry def test { dynamic.foo(dynamic.x in dynamic.y) dynamic.foo(\"x\" in dynamic.y) } ", " (function(){function test(){foo(x in y);foo('x'in y)}test()})(); ").jsMinify # Check block comments before a statement test(" @entry def main { # This is # a block # comment return } ", " (function(){function main(){return}main()})(); ").jsMinify # Check trailing block comments test(" @entry def main { # This is # a block # comment } ", " (function(){function main(){}main()})(); ").jsMinify # Check block comments in else statements test(" @export def main(x bool) { if x { x = false } else { # This is # a block # comment } } ", " (function(exports){exports.main=function(x){if(x)x=false;else{}}})(this); ").jsMinify } } ================================================ FILE: tests/javascript.sk ================================================ namespace Skew.Tests { def testJavaScript { # Check special cases for keyword operators test(" @entry def main { dynamic.foo(dynamic.void(0)) dynamic.foo(dynamic.typeof(0)) dynamic.foo(dynamic.typeof(0).length) dynamic.foo(dynamic.delete(dynamic.foo.bar)) } ", " (function() { function main() { foo(void 0); foo(typeof 0); foo((typeof 0).length); foo(delete foo.bar); } main(); })(); ").js # Check primitive casting for dynamic values test(" @entry def test { dynamic.foo(dynamic.x as bool) dynamic.foo(dynamic.x as int) dynamic.foo(dynamic.x as double) dynamic.foo(dynamic.x as string) } ", " (function() { function __asString(value) { return value === null ? value : value + ''; } function test() { foo(!!x); foo(x | 0); foo(+x); foo(__asString(x)); } test(); })(); ").js # Dynamic base classes should still work test(" @export class DerivedClass : dynamic.BaseClass {} ", " (function(exports) { var __create = Object.create ? Object.create : function(prototype) { return {'__proto__': prototype}; }; function __extends(derived, base) { derived.prototype = __create(base.prototype); derived.prototype.constructor = derived; } exports.DerivedClass = function() { }; __extends(exports.DerivedClass, BaseClass); })(this); ").js # Make sure classes with unused constructors are still emitted test(" class A.B.C {} namespace A.B.C { def foo {} } namespace D.E.F { def foo {} } @entry def main { A.B.C.foo D.E.F.foo } ", " (function() { function main() { A.B.C.foo(); D.E.F.foo(); } var A = {}; A.B = {}; A.B.C = {}; A.B.C.foo = function() { }; var D = {}; D.E = {}; D.E.F = {}; D.E.F.foo = function() { }; main(); })(); ").js # Check renaming across inherited scopes test(" class Foo { def foo(x int) {} # Foo.foo(x int) def foo(x bool) {} # Foo.foo(x bool) } class Bar : Foo { def foo1(x string) {} # Bar.foo1(x string) over foo(x int) {} # Bar.foo(x int) } @entry def main { var foo = Foo.new var bar = Bar.new foo.foo(0) foo.foo(false) bar.foo(0) bar.foo1(null) } ", " (function() { var __create = Object.create ? Object.create : function(prototype) { return {'__proto__': prototype}; }; function __extends(derived, base) { derived.prototype = __create(base.prototype); derived.prototype.constructor = derived; } function main() { var foo = new Foo(); var bar = new Bar(); foo.foo2(0); foo.foo1(false); bar.foo2(0); bar.foo1(null); } function Foo() { } // Foo.foo(x int) Foo.prototype.foo2 = function(x) { }; // Foo.foo(x bool) Foo.prototype.foo1 = function(x) { }; function Bar() { Foo.call(this); } __extends(Bar, Foo); // Bar.foo1(x string) Bar.prototype.foo1 = function(x) { }; // Bar.foo(x int) Bar.prototype.foo2 = function(x) { }; main(); })(); ").js # Check overloaded functions with identical names across types test(" class Foo { def foo(x int) {} # Foo.foo(x int) def foo(x bool) {} # Foo.foo(x bool) } class Bar { def foo(x string) {} # Bar.foo(x string) def foo(x int) {} # Bar.foo(x int) def foo(x bool) {} # Bar.foo(x bool) } @entry def main { var foo = Foo.new var bar = Bar.new foo.foo(0) foo.foo(false) bar.foo(0) bar.foo(false) } ", " (function() { function main() { var foo = new Foo(); var bar = new Bar(); foo.foo1(0); foo.foo2(false); bar.foo2(0); bar.foo3(false); } function Foo() { } // Foo.foo(x int) Foo.prototype.foo1 = function(x) { }; // Foo.foo(x bool) Foo.prototype.foo2 = function(x) { }; function Bar() { } // Bar.foo(x int) Bar.prototype.foo2 = function(x) { }; // Bar.foo(x bool) Bar.prototype.foo3 = function(x) { }; main(); })(); ").js # Check overloaded functions when an interface is present test(" interface I { def foo(x int) # I.foo(x int) def foo(x bool) # I.foo(x bool) } class Foo :: I { def foo(x int) {} # Foo.foo(x int) def foo(x bool) {} # Foo.foo(x bool) } class Bar :: I { def foo(x string) {} # Bar.foo(x string) def foo(x int) {} # Bar.foo(x int) def foo(x bool) {} # Bar.foo(x bool) } @entry def main { var foo = Foo.new var bar = Bar.new foo.foo(0) foo.foo(false) bar.foo(0) bar.foo(false) } ", " (function() { function main() { var foo = new Foo(); var bar = new Bar(); foo.foo2(0); foo.foo3(false); bar.foo2(0); bar.foo3(false); } function Foo() { } // Foo.foo(x int) Foo.prototype.foo2 = function(x) { }; // Foo.foo(x bool) Foo.prototype.foo3 = function(x) { }; function Bar() { } // Bar.foo(x int) Bar.prototype.foo2 = function(x) { }; // Bar.foo(x bool) Bar.prototype.foo3 = function(x) { }; main(); })(); ").js.globalizeAllFunctions # Interface removal with globalization test(" interface IFoo { def foo } namespace IFoo { const FOO = 0 } class Foo :: IFoo { def foo {} } @entry def main int { var foo = Foo.new var ifoo = foo as IFoo foo.foo ifoo.foo return IFoo.FOO } ", " (function() { function main() { var foo = new Foo(); var ifoo = foo; Foo.foo(foo); Foo.foo(ifoo); return Foo.FOO; } function Foo() { } Foo.foo = function(self) { }; Foo.FOO = 0; process.exit(main()); })(); ").js.globalizeAllFunctions # Interface removal should not trigger even with globalization test(" interface IFoo { def foo } namespace IFoo { const FOO = 0 } class Foo :: IFoo { def foo {} } class Bar :: IFoo { def foo {} } @entry def main int { var foo = Foo.new var bar = Bar.new var ifoo = foo as IFoo foo.foo bar.foo ifoo.foo ifoo = bar ifoo.foo return IFoo.FOO } ", " (function() { function main() { var foo = new Foo(); var bar = new Bar(); var ifoo = foo; foo.foo(); bar.foo(); ifoo.foo(); ifoo = bar; ifoo.foo(); return in_IFoo.FOO; } function Foo() { } Foo.prototype.foo = function() { }; function Bar() { } Bar.prototype.foo = function() { }; var in_IFoo = {}; in_IFoo.FOO = 0; process.exit(main()); })(); ").js.globalizeAllFunctions # Check interface removal with an inlineable function test(" @entry def main int { var ifoo IFoo = Foo.new(0) return ifoo.x } class Foo :: IFoo { var _x int def x int { return _x } } interface IFoo { def x int } ", " (function() { function main() { var ifoo = new Foo(0); return ifoo.x(); } function Foo(_x) { this._x = _x; } Foo.prototype.x = function() { return this._x; }; process.exit(main()); })(); ").js # Check interface removal with an inlineable function test(" @entry def main int { var ifoo IFoo = Foo.new(0) return ifoo.x } class Foo :: IFoo { var _x int def x int { return _x } } interface IFoo { def x int } ", " (function() { function main() { var ifoo = new Foo(0); return Foo.x(ifoo); } function Foo(_x) { this._x = _x; } Foo.x = function(self) { return self._x; }; process.exit(main()); })(); ").js.globalizeAllFunctions # Check interface removal with an inlineable function test(" @entry def main int { var ifoo IFoo = Foo.new(0) return ifoo.x } class Foo :: IFoo { var _x int def x int { return _x } } interface IFoo { def x int } ", " (function() { function main() { var ifoo = new Foo(0); return ifoo._x; } function Foo(_x) { this._x = _x; } process.exit(main()); })(); ").js.globalizeAllFunctions.inlineAllFunctions # Conflicting rename annotations should be an error test(" class Foo { @rename(\"a\") def foo } class Bar : Foo { @rename(\"b\") over foo } ", " :8:8: error: Cannot rename \"foo\" to both \"a\" and \"b\" over foo ~~~ ").js # Check map literal inlining test(" @entry def main { dynamic.bar(foo(foo([], 1, 2), \"3\", \"4\")) } def foo(map dynamic, key dynamic, value dynamic) dynamic { map[key] = value return map } ", " (function() { function main() { bar(foo(foo([], 1, 2), '3', '4')); } function foo(map, key, value) { map[key] = value; return map; } main(); })(); ").js.inlineAllFunctions # Check map literal inlining test(" @entry def main { dynamic.bar(foo(foo({}, 1, 2), \"3\", \"4\")) } def foo(map dynamic, key dynamic, value dynamic) dynamic { map[key] = value return map } ", " (function() { function main() { bar({1: 2, '3': '4'}); } main(); })(); ").js.inlineAllFunctions # Check map literal inlining test(" def foo(map IntMap, key int, value T) IntMap { map[key] = value return map } @entry def main { dynamic.bar(foo(foo({}, 1, 2), 3, 4)) } ", " (function() { function foo(map, key, value) { in_IntMap.set(map, key, value); return map; } function main() { bar(foo(foo(new Map(), 1, 2), 3, 4)); } var in_IntMap = {}; in_IntMap.set = function(self, key, value) { self.set(key, value); return value; }; main(); })(); ").js.inlineAllFunctions # Check map literal inlining test(" @entry def main { var x = 0 dynamic.bar(foo(foo(foo(foo({}, 1, 2), 3, 4), x, 5), 6, 7)) } def foo(map dynamic, key int, value int) dynamic { map[key] = value return map } ", " (function() { function main() { var x = 0; bar(foo(foo({1: 2, 3: 4}, x, 5), 6, 7)); } function foo(map, key, value) { map[key] = value; return map; } main(); })(); ").js.inlineAllFunctions # Check map literal inlining test(" @entry def main { var x = 0 dynamic.bar({1: 2, 3: 4, x: 5, 6: 7} as Foo) } @import class Foo { def []=(key int, value int) { (self as dynamic)[key] = value } def {...}(key int, value int) Foo { self[key] = value return self } } namespace Foo { def new Foo { return {} as dynamic } } ", " (function() { function main() { var x = 0; bar(in_Foo.insert(in_Foo.insert({1: 2, 3: 4}, x, 5), 6, 7)); } var in_Foo = {}; in_Foo.insert = function(self, key, value) { self[key] = value; return self; }; main(); })(); ").js.inlineAllFunctions # Check list literal inlining test(" @entry def main { dynamic.bar(foo(foo([], 1), 2)) } def foo(list dynamic, value dynamic) dynamic { list.push(value) return list } ", " (function() { function main() { bar([1, 2]); } main(); })(); ").js.inlineAllFunctions # Check list literal inlining test(" @entry def main { dynamic.bar(foo(foo([], 1), 2)) } def foo(list dynamic, value dynamic) dynamic { list.unshift(value) return list } ", " (function() { function main() { bar(foo(foo([], 1), 2)); } function foo(list, value) { list.unshift(value); return list; } main(); })(); ").js.inlineAllFunctions # Check list literal inlining test(" @entry def main { dynamic.bar(foo(foo({}, 1), 2)) } def foo(list dynamic, value dynamic) dynamic { list.push(value) return list } ", " (function() { function main() { bar(foo(foo({}, 1), 2)); } function foo(list, value) { list.push(value); return list; } main(); })(); ").js.inlineAllFunctions # Check list literal inlining test(" @entry def main { var x = 0 dynamic.bar([1, \"2\", x, false] as Foo) } @import class Foo { def append(value dynamic) { (self as dynamic).push(value) } def [...](value dynamic) Foo { append(value) return self } } namespace Foo { def new Foo { return [] as dynamic } } ", " (function() { function main() { var x = 0; bar([1, '2', x, false]); } main(); })(); ").js.inlineAllFunctions # Check that order doesn't matter when inlining list literals test(" @import class Foo { def [...](value int) Foo { (self as dynamic).push(value) return self } def [...](value bool) Foo { (self as dynamic).push(value) return self } } namespace Foo { def [new] Foo { return [] as dynamic } } @entry def main { [1, false] as Foo } ", " (function() { function main() { [1, false]; } main(); })(); ").js.inlineAllFunctions # Check that order doesn't matter when inlining list literals test(" @import class Foo { def [...](value bool) Foo { (self as dynamic).push(value) return self } def [...](value int) Foo { (self as dynamic).push(value) return self } } namespace Foo { def [new] Foo { return [] as dynamic } } @entry def main { [1, false] as Foo } ", " (function() { function main() { [1, false]; } main(); })(); ").js.inlineAllFunctions # Check for a bug where merged functions weren't emitted sometimes test(" class Bar : Foo { } class Foo { def foo def foo {} } @entry def main { Foo.new.foo } ", " (function() { function main() { new Foo().foo(); } function Foo() { } Foo.prototype.foo = function() { }; main(); })(); ").js # Check for a bug where merged functions weren't emitted sometimes test(" class Foo { def foo def foo {} } class Bar : Foo { } @entry def main { Foo.new.foo } ", " (function() { function main() { new Foo().foo(); } function Foo() { } Foo.prototype.foo = function() { }; main(); })(); ").js # Implemented instance functions should be pulled off of interfaces test(" class Foo :: IFoo { def foo {} } interface IFoo { def bar {} } @entry def main { var foo = Foo.new var ifoo = foo as IFoo foo.foo ifoo.bar } ", " (function() { function main() { var foo = new Foo(); var ifoo = foo; foo.foo(); in_IFoo.bar(ifoo); } function Foo() { } Foo.prototype.foo = function() { }; var in_IFoo = {}; in_IFoo.bar = function(self) { }; main(); })(); ").js # Check folding of the "**" operator test(" @entry def main { var x dynamic = [ 2 ** 2, 2 ** 3, 3 ** 2, 2 ** 0, 2 ** -2, 0.25 ** 0.5, Math.pow(0.25, 0.5), ] } ", " (function() { function main() { var x = [4, 8, 9, 1, 0, 0.5, 0.5]; } main(); })(); ").js.foldAllConstants # Make sure variable initialization works with multiple user-defined constructors test(" class Foo { var foo = \"\" var bar = [1, 2, 3] def new {} def new(x int) {} } @entry def main { Foo.new Foo.new(1) } ", " (function() { function main() { new Foo(); new Foo.new2(1); } function Foo() { this.foo = ''; this.bar = [1, 2, 3]; } Foo.new2 = function(x) { this.foo = ''; this.bar = [1, 2, 3]; }; Foo.new2.prototype = Foo.prototype; main(); })(); ").js # Make sure constant folding of compile-time guards doesn't change code emission of the variables involved test(" enum Foo { FOO } if BAR == 0 { @export def main {} } @export const BAR = Foo.FOO ", " (function(exports) { var Foo = { FOO: 0 }; exports.main = function() { }; exports.BAR = Foo.FOO; })(this); ").js # Check for integer casting test(" @entry def main { var x = 0 dynamic.foo(x + 1) dynamic.foo(x - 1) dynamic.foo(x * 2) dynamic.foo(x / 2) dynamic.foo(x % 2) dynamic.foo(x % 0) dynamic.foo(x % x) dynamic.foo(x << 1) dynamic.foo(x >> 1) dynamic.foo(x >>> 1) dynamic.foo(x >>> 0) dynamic.foo(x >>> x) } ", " (function() { var __imul = Math.imul ? Math.imul : function(a, b) { return (a * (b >>> 16) << 16) + a * (b & 65535) | 0; }; function main() { var x = 0; foo(x + 1 | 0); foo(x - 1 | 0); foo(__imul(x, 2)); foo(x / 2 | 0); foo(x % 2); foo(x % 0 | 0); foo(x % x | 0); foo(x << 1); foo(x >> 1); foo(x >>> 1); foo(x >>> 0 | 0); foo(x >>> x | 0); } main(); })(); ").js # This caused an assert to fail during inlining test(" interface Foo { def foo int } class Bar :: Foo { def foo int { return 0 } } class Baz { def foo(foo Foo) int { return false ? 0 : foo.foo } } @entry def main { Baz.new.foo(null) } ", " (function() { function main() { Baz.foo(new Baz(), null); } var Bar = {}; Bar.foo = function(self) { return 0; }; function Baz() { } Baz.foo = function(self, foo) { return Bar.foo(foo); }; main(); })(); ").js.inlineAllFunctions.foldAllConstants.globalizeAllFunctions # Check a case of assignment folding test(" @export def main(x int, y int) int { x = 0 x = 1 return x + y } ", " (function(exports) { exports.main = function(x, y) { x = 1; return x + y | 0; }; })(this); ").js.foldAllConstants # Check a case of assignment folding test(" @export def main(x int, y int) int { x = 0 x = x + 1 return x + y } ", " (function(exports) { exports.main = function(x, y) { x = 0; x = x + 1 | 0; return x + y | 0; }; })(this); ").js.foldAllConstants # Check a case of assignment folding test(" @export def main(x int, y int) int { x = 0 y = 0 x = 1 return x + y } ", " (function(exports) { exports.main = function(x, y) { y = 0; x = 1; return x + y | 0; }; })(this); ").js.foldAllConstants # Check a case of assignment folding test(" @export def main(x int, y int) int { x = 0 dynamic.foo() x = 1 return x + y } ", " (function(exports) { exports.main = function(x, y) { x = 0; foo(); x = 1; return x + y | 0; }; })(this); ").js.foldAllConstants # Check a case of assignment folding test(" @export def main(x int, y int) int { x = 0 y = x x = 1 return x + y } ", " (function(exports) { exports.main = function(x, y) { x = 0; y = x; x = 1; return x + y | 0; }; })(this); ").js.foldAllConstants # Check a case of assignment folding test(" @export def main(x int, y int) int { x = 0 y = dynamic.foo ? x : x + 1 x = 1 return x + y } ", " (function(exports) { exports.main = function(x, y) { x = 0; y = foo ? x : x + 1 | 0; x = 1; return x + y | 0; }; })(this); ").js.foldAllConstants # Check a case of assignment folding test(" class Foo { var x = 0 } @entry def main int { var foo = Foo.new foo.x = 1 foo.x = 2 return foo.x } ", " (function() { function main() { var foo = new Foo(); foo.x = 2; return foo.x; } function Foo() { this.x = 0; } process.exit(main()); })(); ").js.foldAllConstants # Check a case of assignment folding test(" class Foo { var x = 0 def foo {} } @entry def main int { var foo = Foo.new var y Foo = null var z = 0 foo.x = 1 y = foo z = y.x foo.x = 2 return foo.x } ", " (function() { function main() { var foo = new Foo(); var y = null; var z = 0; foo.x = 1; y = foo; z = y.x; foo.x = 2; return foo.x; } function Foo() { this.x = 0; } process.exit(main()); })(); ").js.foldAllConstants # Check a case of assignment folding test(" class Foo { var x = 0 def foo int { return x } } @entry def main int { var foo = Foo.new foo.x = foo.foo + 1 foo.x = foo.foo + 2 return foo.x } ", " (function() { function main() { var foo = new Foo(); foo.x = foo.foo() + 1 | 0; foo.x = foo.foo() + 2 | 0; return foo.x; } function Foo() { this.x = 0; } Foo.prototype.foo = function() { return this.x; }; process.exit(main()); })(); ").js.foldAllConstants # Check a case of assignment folding test(" class Foo { var x = 0 def foo {} } @entry def main int { var foo = Foo.new var bar = Foo.new foo.x = 1 bar.x = 2 foo.x = 3 return foo.x } ", " (function() { function main() { var foo = new Foo(); var bar = new Foo(); bar.x = 2; foo.x = 3; return foo.x; } function Foo() { this.x = 0; } process.exit(main()); })(); ").js.foldAllConstants # Check constant folding of switch statements test(" @export def main string { switch 0 { case 0 { return \"0\" } case 1 { return \"1\" } default { return null } } } ", " (function(exports) { exports.main = function() { return '0'; }; })(this); ").js.foldAllConstants # Check constant folding of switch statements test(" @export def main string { switch 2 { case 0 { return \"0\" } case 1 { return \"1\" } default { return null } } } ", " (function(exports) { exports.main = function() { return null; }; })(this); ").js.foldAllConstants # Check constant folding of switch statements test(" @export def main(x int) string { switch 0 { case 0 { return \"0\" } case x { return \"1\" } default { return null } } } ", " (function(exports) { exports.main = function(x) { return '0'; }; })(this); ").js.foldAllConstants # Check constant folding of switch statements test(" @export def main(x int) string { switch 1 { case x { return \"0\" } case 1 { return \"1\" } case 2 { return \"2\" } default { return null } } } ", " (function(exports) { exports.main = function(x) { switch (1) { case x: { return '0'; } case 1: { return '1'; } default: { return null; } } }; })(this); ").js.foldAllConstants # Check IIFE parentheses test(" @entry def main { (=> dynamic.this)().foo[\"bar\"] = (=> dynamic.this)().foo[\"bar\"] } ", " (function() { function main() { (function() { return this; })().foo['bar'] = function() { return this; }().foo['bar']; } main(); })(); ").js.foldAllConstants # Check constant folding of a bitwise "and" nested between two shifts test(" @export def main(x int) int { x = (((x >> 8) & 255) << 8) + (((x >>> 8) & 255) << 8) x = (((x >> 7) & 255) << 8) + (((x >>> 7) & 255) << 8) x = (((x >> 8) & 255) << 7) + (((x >>> 8) & 255) << 7) return x } ", " (function(exports) { exports.main = function(x) { x = (x & 65280) + (x & 65280) | 0; x = (x << 1 & 65280) + (x << 1 & 65280) | 0; x = (x >> 1 & 32640) + (x >>> 1 & 32640) | 0; return x; }; })(this); ").js.foldAllConstants # Check constant folding of two adjacent bitwise "and" operations inside a bitwise "or" test(" @export def main(x int) int { return x & 17 | 257 & x } ", " (function(exports) { exports.main = function(x) { return x & 273; }; })(this); ").js.foldAllConstants # Check constant folding of a single "and" inside a bitwise "or" test(" @export def main(x int) int { x = (x & 0xFF000000) | 0xFFFFFF x = (x & 0x7F000000) | 0xFFFFFF x = 0xFFFFFF | (0xFF0000FF & x) x = 0xFFFFFF | (0x7F0000FF & x) return x } ", " (function(exports) { exports.main = function(x) { x = x | 16777215; x = x & 2130706432 | 16777215; x = x | 16777215; x = x & 2130706687 | 16777215; return x; }; })(this); ").js.foldAllConstants # Check constant folding of identity operations test(" @export def main(x int, y fn() int) { # Multiplication (be careful about expressions with side effects and about NaN for dynamic values) dynamic.foo(x * 0) dynamic.foo(x * 1) dynamic.foo(x * 2) dynamic.foo(y() * 0) dynamic.foo(y() * 1) dynamic.foo(y() * 2) dynamic.foo(dynamic.z * 0) dynamic.foo(dynamic.z * 1) dynamic.foo(dynamic.z * 2) # Bitwise operations with an integer reference dynamic.foo(x & 0) dynamic.foo(x | 0) dynamic.foo(x & ~0) dynamic.foo(x | ~0) dynamic.foo(x << 0) dynamic.foo(x >> 0) dynamic.foo(x >>> 0) # Bitwise operations with an integer expression with side effects dynamic.foo(y() & 0) dynamic.foo(y() | 0) dynamic.foo(y() & ~0) dynamic.foo(y() | ~0) # Bitwise operations with a dynamically typed expression with side effects, may be a non-integer dynamic.foo(dynamic.z() & 0) dynamic.foo(dynamic.z() | 0) dynamic.foo(dynamic.z() & ~0) dynamic.foo(dynamic.z() | ~0) dynamic.foo(dynamic.z() << 0) dynamic.foo(dynamic.z() >> 0) dynamic.foo(dynamic.z() >>> 0) } ", " (function(exports) { var __imul = Math.imul ? Math.imul : function(a, b) { return (a * (b >>> 16) << 16) + a * (b & 65535) | 0; }; exports.main = function(x, y) { // Multiplication (be careful about expressions with side effects and about NaN for dynamic values) foo(0); foo(x); foo(x << 1); foo(__imul(y(), 0)); foo(y()); foo(y() << 1); foo(z * 0); foo(z); foo(z * 2); // Bitwise operations with an integer reference foo(0); foo(x); foo(x); foo(-1); foo(x); foo(x); foo(x); // Bitwise operations with an integer expression with side effects foo(y() & 0); foo(y()); foo(y()); foo(y() | -1); // Bitwise operations with a dynamically typed expression with side effects, may be a non-integer foo(z() & 0); foo(z() | 0); foo(z() & -1); foo(z() | -1); foo(z() << 0); foo(z() >> 0); foo(z() >>> 0); }; })(this); ").js.foldAllConstants.inlineAllFunctions # Check constant folding of bitwise operations and inlining test(" type Color : int { def r int { return (self as int) & 255 } def g int { return ((self as int) >> 8) & 255 } def b int { return ((self as int) >> 16) & 255 } def a int { return (self as int) >>> 24 } def opaque Color { return new(r, g, b, 255) } } namespace Color { def new(r int, g int, b int, a int) Color { return (r | g << 8 | b << 16 | a << 24) as Color } } @export def isOrange(color Color) bool { return color.opaque == Color.new(255, 127, 0, 255) } ", " (function(exports) { var Color = {}; Color.opaque = function(self) { return self | -16777216; }; exports.isOrange = function(color) { return Color.opaque(color) == -16744449; }; })(this); ").js.foldAllConstants.inlineAllFunctions # Check that compile-time if statements work test(" @export { if FOO == .FOO { const FOO_YES = true } else { const FOO_NO = false } if FOO == .BAR { const BAR_YES = true } else { const BAR_NO = false } } const FOO = Foo.FOO enum Foo { FOO BAR } ", " (function(exports) { exports.FOO_YES = true; exports.BAR_NO = false; })(this); ").js # Check that nested compile-time if statements work test(" if true { @export { if FOO == .FOO { const FOO_YES = true } else { const FOO_NO = false } if FOO == .BAR { const BAR_YES = true } else { const BAR_NO = false } } } const FOO = Foo.FOO enum Foo { FOO BAR } ", " (function(exports) { exports.FOO_YES = true; exports.BAR_NO = false; })(this); ").js # Check constant folding of unicode string factory functions test(" @export def main string { return string.fromCodePoint('😄') + string.fromCodeUnit('x') + string.fromCodePoints(['💾', '💿']) + string.fromCodeUnits(['y', 'z']) } ", " (function(exports) { exports.main = function() { return '😄x💾💿yz'; }; })(this); ").js.foldAllConstants # Check constant folding of unicode string factory functions test(" @export def main(x int) string { return string.fromCodePoint(x) + string.fromCodeUnit(x) + string.fromCodePoints(['x', x]) + string.fromCodeUnits([x, x]) } ", " (function(exports) { function assert(truth) { if (!truth) { throw Error('Assertion failed'); } } function StringBuilder() { this.buffer = ''; } var in_List = {}; in_List.get = function(self, index) { assert(0 <= index && index < self.length); return self[index]; }; var in_string = {}; in_string.fromCodePoints = function(codePoints) { var builder = new StringBuilder(); for (var i = 0, count1 = codePoints.length; i < count1; i = i + 1 | 0) { var codePoint = in_List.get(codePoints, i); builder.buffer += in_string.fromCodePoint(codePoint); } return builder.buffer; }; in_string.fromCodeUnits = function(codeUnits) { var result = ''; for (var i = 0, count1 = codeUnits.length; i < count1; i = i + 1 | 0) { var codeUnit = in_List.get(codeUnits, i); result += String.fromCharCode(codeUnit); } return result; }; in_string.fromCodePoint = function(codePoint) { return codePoint < 65536 ? String.fromCharCode(codePoint) : String.fromCharCode((codePoint - 65536 >> 10) + 55296 | 0) + String.fromCharCode((codePoint - 65536 & 1023) + 56320 | 0); }; exports.main = function(x) { return in_string.fromCodePoint(x) + String.fromCharCode(x) + in_string.fromCodePoints([120, x]) + in_string.fromCodeUnits([x, x]); }; })(this); ").js.foldAllConstants.inlineAllFunctions # Check that iterating over a local constant doesn't generate an extra variable test(" @export def main { var foo = [0, 1, 2] var bar = [0, 1, 2] bar = [] for i in foo {} for i in bar {} } ", " (function(exports) { function assert(truth) { if (!truth) { throw Error('Assertion failed'); } } var in_List = {}; in_List.get = function(self, index) { assert(0 <= index && index < self.length); return self[index]; }; exports.main = function() { var foo = [0, 1, 2]; var bar = [0, 1, 2]; bar = []; for (var i2 = 0, count = foo.length; i2 < count; i2 = i2 + 1 | 0) { var i = in_List.get(foo, i2); } for (var i3 = 0, list = bar, count1 = list.length; i3 < count1; i3 = i3 + 1 | 0) { var i1 = in_List.get(list, i3); } }; })(this); ").js.foldAllConstants.inlineAllFunctions # Check for a bug with bounded for loops test(" @export def main { var foo = 0 var bar = 5 for i in foo..bar {} } ", " (function(exports) { exports.main = function() { for (var i = 0; i < 5; i = i + 1 | 0) { } }; })(this); ").js.foldAllConstants # Test @neverinline test(" def foo { dynamic.Foo() } @neverinline def bar { dynamic.Bar() } @entry def main { foo bar } ", " (function() { function bar() { Bar(); } function main() { Foo(); bar(); } main(); })(); ").js.inlineAllFunctions # Test @alwaysinline test(" def foo { dynamic.Foo() } @alwaysinline def bar { dynamic.Bar() } class Foo { def foo { dynamic.Foo(self) } @alwaysinline def bar { dynamic.Bar(self) } } @entry def main { foo bar Foo.new.foo Foo.new.bar } ", " (function() { function foo() { Foo(); } function main() { foo(); Bar(); new Foo().foo(); Bar(new Foo()); } function Foo() { } Foo.prototype.foo = function() { Foo(this); }; main(); })(); ").js # Test obscure bug with super class calls test(" class Baz : Bar { } @entry def main { Baz2.new.foo(0) } class Bar : Foo { over foo(bar int) {} } class Foo { def foo(bar int) {} } class Baz2 : Bar { over foo(bar int) { super(bar) } } ", " (function() { var __create = Object.create ? Object.create : function(prototype) { return {'__proto__': prototype}; }; function __extends(derived, base) { derived.prototype = __create(base.prototype); derived.prototype.constructor = derived; } function main() { new Baz2().foo(0); } function Foo() { } Foo.prototype.foo = function(bar) { }; function Bar() { Foo.call(this); } __extends(Bar, Foo); Bar.prototype.foo = function(bar) { }; function Baz2() { Bar.call(this); } __extends(Baz2, Bar); Baz2.prototype.foo = function(bar) { Bar.prototype.foo.call(this, bar); }; main(); })(); ").js # Test obscure bug with super class calls test(" @entry def main { Baz2.new.foo(0) } class Foo { def foo(bar int) {} } class Bar : Foo { over foo(bar int) {} } class Baz : Bar { } class Baz2 : Bar { over foo(bar int) { super(bar) } } ", " (function() { var __create = Object.create ? Object.create : function(prototype) { return {'__proto__': prototype}; }; function __extends(derived, base) { derived.prototype = __create(base.prototype); derived.prototype.constructor = derived; } function main() { new Baz2().foo(0); } function Foo() { } Foo.prototype.foo = function(bar) { }; function Bar() { Foo.call(this); } __extends(Bar, Foo); Bar.prototype.foo = function(bar) { }; function Baz2() { Bar.call(this); } __extends(Baz2, Bar); Baz2.prototype.foo = function(bar) { Bar.prototype.foo.call(this, bar); }; main(); })(); ").js # Test math constants test(" @export def main { dynamic.foo(Math.NAN, Math.INFINITY, -Math.INFINITY) dynamic.foo(Math.NAN.toString, Math.INFINITY.toString, (-Math.INFINITY).toString) } ", " (function(exports) { exports.main = function() { foo(0 / 0, 1 / 0, -(1 / 0)); foo((0 / 0).toString(), (1 / 0).toString(), (-(1 / 0)).toString()); }; })(this); ").js.inlineAllFunctions # Test math constants test(" @export def main { dynamic.foo(Math.NAN, Math.INFINITY, -Math.INFINITY) dynamic.foo(Math.NAN.toString, Math.INFINITY.toString, (-Math.INFINITY).toString) } ", " (function(exports) { exports.main = function() { foo(NaN, Infinity, -Infinity); foo(NaN.toString(), Infinity.toString(), (-Infinity).toString()); }; })(this); ").js.inlineAllFunctions.foldAllConstants # Test math toString test(" @export def main { dynamic.foo(0.toString, 1.0.toString, (-1.0).toString, 0.5.toString, (-0.5).toString) } ", " (function(exports) { exports.main = function() { foo((0).toString(), (1).toString(), (-1).toString(), (0.5).toString(), (-0.5).toString()); }; })(this); ").js # Test math toString test(" @export def main { dynamic.foo(0.toString, 1.0.toString, (-1.0).toString, 0.5.toString, (-0.5).toString) } ", " (function(exports) { exports.main = function() { foo('0', (1).toString(), (-1).toString(), (0.5).toString(), (-0.5).toString()); }; })(this); ").js.foldAllConstants # Double literals must be emitted with the right format test(" @export def main(x double) { x = 1.0 / 2.0 x = 1e100 / 2e100 x = 1e-100 / 2e-100 x = 1.5 / 2.5 x = 1.5e100 / 2.5e100 x = 1.5e-100 / 2.5e-100 } ", " (function(exports) { exports.main = function(x) { x = 1 / 2; x = 1e+100 / 2e+100; x = 1e-100 / 2e-100; x = 1.5 / 2.5; x = 1.5e+100 / 2.5e+100; x = 1.5e-100 / 2.5e-100; }; })(this); ").js # Test folding of nested shifts test(" @export def main(x int) { dynamic.foo(x * 32 * 64) dynamic.foo(x * 32.0 * 64) dynamic.foo(x * 32 * 64.0) dynamic.foo(x << 5 << 6) dynamic.foo(x >> 5 >> 6) dynamic.foo(x >>> 5 >>> 6) dynamic.foo(x << 5 >> 6) dynamic.foo(x << 5 >>> 6) dynamic.foo(x >> 5 >>> 6) dynamic.foo(x >>> 5 >> 6) } ", " (function(exports) { exports.main = function(x) { foo(x << 11); foo(x * 32 * 64); foo((x << 5) * 64); foo(x << 11); foo(x >> 11); foo(x >>> 11); foo(x << 5 >> 6); foo(x << 5 >>> 6); foo(x >> 5 >>> 6); foo(x >>> 5 >> 6); }; })(this); ").js.foldAllConstants # Test renaming with symbol merging test(" @import class Foo { def new def foo(x int) } class Foo { @rename(\"bar\") def foo(x int) } @export def main { Foo.new.foo(0) } ", " (function(exports) { exports.main = function() { new Foo().bar(0); }; })(this); ").js # Test renaming with symbol merging test(" @import class Foo { def new @rename(\"bar\") def foo(x int) } class Foo { def foo(x int) } @export def main { Foo.new.foo(0) } ", " (function(exports) { exports.main = function() { new Foo().bar(0); }; })(this); ").js # Test renaming with symbol merging and generics test(" @import class Foo { def new @rename(\"baz\") def foo(x Y) def foo(x Z) def bar(x Y) @rename(\"baz\") def bar(x Z) } @export def main { Foo.new.foo(0) Foo.new.bar(0) } ", " (function(exports) { exports.main = function() { new Foo().baz(0); new Foo().baz(0); }; })(this); ").js # Test imported auto-implemented operators test(" @import class Foo { def *(x int) Foo } @export def main(foo Foo) { foo *= 2 } ", " (function(exports) { exports.main = function(foo) { foo = foo * 2; }; })(this); ").js.inlineAllFunctions # Test side-effect preservation for auto-implemented operators test(" class Foo { var x = 0.0 var y = 0 var z = [0.0] } def bar Foo { return Foo.new } @export def main(foo Foo) { foo.x **= 2 foo.z[0] **= 2 foo.z[foo.y] **= 2 bar.x **= 2 bar.z[0] **= 2 bar.z[bar.y] **= 2 } ", " (function(exports) { exports.main = function(foo) { var ref6; var ref5; var ref4; var ref3; var ref2; var ref1; var ref; foo.x = Math.pow(foo.x, 2); (ref = foo.z)[0] = Math.pow(ref[0], 2); (ref1 = foo.z)[ref2 = foo.y] = Math.pow(ref1[ref2], 2); (ref3 = new Foo()).x = Math.pow(ref3.x, 2); (ref4 = new Foo().z)[0] = Math.pow(ref4[0], 2); (ref5 = new Foo().z)[ref6 = new Foo().y] = Math.pow(ref5[ref6], 2); }; function Foo() { this.x = 0; this.y = 0; this.z = [0]; } })(this); ").js.inlineAllFunctions.skip # Skipped because rewriting assignment operators doesn't work with non-imported functions yet # Test the null join operator test(" class Foo { var x Foo = null } def bar Foo { return Foo.new } @export def main(foo Foo) { foo ?? bar foo.x ?? foo.x bar.x ?? bar.x bar.x ?? bar ?? foo.x ?? foo } ", " (function(exports) { function bar() { return new Foo(); } function Foo() { this.x = null; } exports.main = function(foo) { var ref2; var ref1; var ref; foo != null ? foo : bar(); foo.x != null ? foo.x : foo.x; (ref = bar()).x != null ? ref.x : bar().x; (ref2 = bar()).x != null ? ref2.x : (ref1 = bar()) != null ? ref1 : foo.x != null ? foo.x : foo; }; })(this); ").js # Test the null dot operator test(" class Foo { var a Foo = null def b Foo { return self } var c fn() Foo = => null def d(x int) Foo { return self } } def bar Foo { return Foo.new } @export def main(foo Foo) { dynamic.use(foo?.a.a) dynamic.use(foo?.b.b) dynamic.use(foo?.c().c()) dynamic.use(foo?.d(0).d(0)) dynamic.use(foo?.a?.a) dynamic.use(foo?.b?.b) dynamic.use(foo?.c()?.c()) dynamic.use(foo?.d(0)?.d(0)) dynamic.use(bar?.a.a) dynamic.use(bar?.b.b) dynamic.use(bar?.c().c()) dynamic.use(bar?.d(0).d(0)) dynamic.use(bar?.a?.a) dynamic.use(bar?.b?.b) dynamic.use(bar?.c()?.c()) dynamic.use(bar?.d(0)?.d(0)) } ", " (function(exports) { function bar() { return new Foo(); } function Foo() { this.a = null; this.c = function() { return null; }; } Foo.prototype.b = function() { return this; }; Foo.prototype.d = function(x) { return this; }; exports.main = function(foo) { var ref15; var ref14; var ref13; var ref12; var ref11; var ref10; var ref9; var ref8; var ref7; var ref6; var ref5; var ref4; var ref3; var ref2; var ref1; var ref; use(foo != null ? foo.a.a : null); use(foo != null ? foo.b().b() : null); use(foo != null ? foo.c().c() : null); use(foo != null ? foo.d(0).d(0) : null); use((ref = foo != null ? foo.a : null) != null ? ref.a : null); use((ref1 = foo != null ? foo.b() : null) != null ? ref1.b() : null); use((ref2 = foo != null ? foo.c() : null) != null ? ref2.c() : null); use((ref3 = foo != null ? foo.d(0) : null) != null ? ref3.d(0) : null); use((ref4 = bar()) != null ? ref4.a.a : null); use((ref5 = bar()) != null ? ref5.b().b() : null); use((ref6 = bar()) != null ? ref6.c().c() : null); use((ref7 = bar()) != null ? ref7.d(0).d(0) : null); use((ref9 = (ref8 = bar()) != null ? ref8.a : null) != null ? ref9.a : null); use((ref11 = (ref10 = bar()) != null ? ref10.b() : null) != null ? ref11.b() : null); use((ref13 = (ref12 = bar()) != null ? ref12.c() : null) != null ? ref13.c() : null); use((ref15 = (ref14 = bar()) != null ? ref14.d(0) : null) != null ? ref15.d(0) : null); }; })(this); ").js # Test top-level use of the null dot operator test(" class Foo { var a Foo = null def b Foo { return self } var c fn() Foo = => null def d(x int) Foo { return self } } def bar Foo { return Foo.new } @export def main(foo Foo) { foo?.a.a foo?.b.b foo?.c().c() foo?.d(0).d(0) foo?.a?.a foo?.b?.b foo?.c()?.c() foo?.d(0)?.d(0) bar?.a.a bar?.b.b bar?.c().c() bar?.d(0).d(0) bar?.a?.a bar?.b?.b bar?.c()?.c() bar?.d(0)?.d(0) } ", " (function(exports) { function bar() { return new Foo(); } function Foo() { this.a = null; this.c = function() { return null; }; } Foo.prototype.b = function() { return this; }; Foo.prototype.d = function(x) { return this; }; exports.main = function(foo) { var ref15; var ref14; var ref13; var ref12; var ref11; var ref10; var ref9; var ref8; var ref7; var ref6; var ref5; var ref4; var ref3; var ref2; var ref1; var ref; if (foo != null) { foo.a.a; } if (foo != null) { foo.b().b(); } if (foo != null) { foo.c().c(); } if (foo != null) { foo.d(0).d(0); } if ((ref = foo != null ? foo.a : null) != null) { ref.a; } if ((ref1 = foo != null ? foo.b() : null) != null) { ref1.b(); } if ((ref2 = foo != null ? foo.c() : null) != null) { ref2.c(); } if ((ref3 = foo != null ? foo.d(0) : null) != null) { ref3.d(0); } if ((ref4 = bar()) != null) { ref4.a.a; } if ((ref5 = bar()) != null) { ref5.b().b(); } if ((ref6 = bar()) != null) { ref6.c().c(); } if ((ref7 = bar()) != null) { ref7.d(0).d(0); } if ((ref9 = (ref8 = bar()) != null ? ref8.a : null) != null) { ref9.a; } if ((ref11 = (ref10 = bar()) != null ? ref10.b() : null) != null) { ref11.b(); } if ((ref13 = (ref12 = bar()) != null ? ref12.c() : null) != null) { ref13.c(); } if ((ref15 = (ref14 = bar()) != null ? ref14.d(0) : null) != null) { ref15.d(0); } }; })(this); ").js # Test unary assignment operators test(" class Foo { var x = 0 } def foo Foo { return Foo.new } @export def main { var x = 0 var y = 0.0 x++ ++x x-- --x y++ ++y y-- --y foo.x++ ++foo.x foo.x-- --foo.x dynamic.use(x++) dynamic.use(++x) dynamic.use(x--) dynamic.use(--x) dynamic.use(y++) dynamic.use(++y) dynamic.use(y--) dynamic.use(--y) dynamic.use(foo.x++) dynamic.use(++foo.x) dynamic.use(foo.x--) dynamic.use(--foo.x) } ", " (function(exports) { function foo() { return new Foo(); } function Foo() { this.x = 0; } exports.main = function() { var ref7; var ref6; var ref5; var ref4; var ref3; var ref2; var ref1; var ref; var x = 0; var y = 0; x = x + 1 | 0; x = x + 1 | 0; x = x - 1 | 0; x = x - 1 | 0; y++; ++y; y--; --y; (ref = foo()).x = ref.x + 1 | 0; (ref1 = foo()).x = ref1.x + 1 | 0; (ref2 = foo()).x = ref2.x - 1 | 0; (ref3 = foo()).x = ref3.x - 1 | 0; use((x = x + 1 | 0) - 1 | 0); use(x = x + 1 | 0); use((x = x - 1 | 0) + 1 | 0); use(x = x - 1 | 0); use(y++); use(++y); use(y--); use(--y); use(((ref4 = foo()).x = ref4.x + 1 | 0) - 1 | 0); use((ref5 = foo()).x = ref5.x + 1 | 0); use(((ref6 = foo()).x = ref6.x - 1 | 0) + 1 | 0); use((ref7 = foo()).x = ref7.x - 1 | 0); }; })(this); ").js.foldAllConstants # Test custom unary assignment operators test(" class Foo { var foo Foo = null var bar List = [] def ++ Foo { return self } } def bar Foo { return Foo.new } def baz int { return 0 } @export def main { var foo = Foo.new foo++ ++foo bar.foo++ ++bar.foo bar.bar[baz]++ ++bar.bar[baz] dynamic.use(foo++) dynamic.use(++foo) dynamic.use(bar.foo++) dynamic.use(++bar.foo) dynamic.use(bar.bar[baz]++) dynamic.use(++bar.bar[baz]) } ", " (function(exports) { function bar() { return new Foo(); } function baz() { return 0; } function Foo() { this.foo = null; this.bar = []; } Foo.prototype.increment = function() { return this; }; exports.main = function() { var ref14; var ref13; var ref12; var ref11; var ref10; var ref9; var ref8; var ref7; var ref6; var ref5; var ref4; var ref3; var ref2; var ref1; var ref; var foo = new Foo(); foo = foo.increment(); foo = foo.increment(); (ref = bar()).foo = ref.foo.increment(); (ref1 = bar()).foo = ref1.foo.increment(); (ref2 = bar().bar)[ref3 = baz()] = ref2[ref3].increment(); (ref4 = bar().bar)[ref5 = baz()] = ref4[ref5].increment(); use((ref6 = foo, foo = ref6.increment(), ref6)); use(foo = foo.increment()); use((ref7 = (ref8 = bar()).foo, ref8.foo = ref7.increment(), ref7)); use((ref9 = bar()).foo = ref9.foo.increment()); use((ref10 = (ref11 = bar().bar)[ref12 = baz()], ref11[ref12] = ref10.increment(), ref10)); use((ref13 = bar().bar)[ref14 = baz()] = ref13[ref14].increment()); }; })(this); ").js.skip # Skipped because rewriting assignment operators doesn't work with non-imported functions yet # Test null assignment operator test(" class Foo { var x Foo = null } def foo Foo { return Foo.new } def bar(foo Foo) { } @export def main(baz Foo) { baz ?? foo baz ?= foo baz.x ?? foo baz.x ?= foo foo.x ?? foo foo.x ?= foo bar(baz ?? foo) bar(baz ?= foo) bar(baz.x ?? foo) bar(baz.x ?= foo) bar(foo.x ?? foo) bar(foo.x ?= foo) } ", " (function(exports) { function foo() { return new Foo(); } function bar(foo) { } function Foo() { this.x = null; } exports.main = function(baz) { var ref3; var ref2; var ref1; var ref; baz != null ? baz : foo(); if (baz == null) { baz = foo(); } baz.x != null ? baz.x : foo(); if (baz.x == null) { baz.x = foo(); } (ref = foo()).x != null ? ref.x : foo(); if ((ref1 = foo()).x == null) { ref1.x = foo(); } bar(baz != null ? baz : foo()); bar(baz != null ? baz : baz = foo()); bar(baz.x != null ? baz.x : foo()); bar(baz.x != null ? baz.x : baz.x = foo()); bar((ref2 = foo()).x != null ? ref2.x : foo()); bar((ref3 = foo()).x != null ? ref3.x : ref3.x = foo()); }; })(this); ").js # Test null assignment operator with dynamic values test(" @export def main { dynamic.use(dynamic.foo[dynamic.bar] ?? \"1\") dynamic.use(dynamic.foo[dynamic.bar] ?= \"2\") dynamic.use(dynamic.foo()[dynamic.bar()] ?? \"3\") dynamic.use(dynamic.foo()[dynamic.bar()] ?= \"4\") } ", " (function(exports) { exports.main = function() { var ref3; var ref2; var ref1; var ref; use(foo[bar] !== null ? foo[bar] : '1'); use(foo[bar] !== null ? foo[bar] : foo[bar] = '2'); use((ref = foo())[ref1 = bar()] !== null ? ref[ref1] : '3'); use((ref2 = foo())[ref3 = bar()] !== null ? ref2[ref3] : ref2[ref3] = '4'); }; })(this); ").js # Test "?=" and "?." with comments when converted to if statements test(" class Foo { var foo Foo } @export def main(foo Foo) { foo ?= foo # This comment should be kept foo?.foo # This comment should be kept too } ", " (function(exports) { exports.main = function(foo) { // This comment should be kept if (foo == null) { foo = foo; } // This comment should be kept too if (foo != null) { foo.foo; } }; })(this); ").js # Test string interpolation empty string omission test(" @export { var a = \"\\(1)\" var b = \"a\\(1)\" var c = \"\\(1)b\" var d = \"a\\(1)b\" var e = \"\\(\"\")\\(\"\")\" var f = \"\\(\"\")\\(\"\")\\(1)\\(\"\")\\(\"\")\" } ", " (function(exports) { exports.a = (1).toString(); exports.b = 'a' + (1).toString(); exports.c = (1).toString() + 'b'; exports.d = 'a' + (1).toString() + 'b'; exports.e = ''; exports.f = (1).toString(); })(this); ").js # Test string interpolation toString insertion test(" @import { def x int def y dynamic } @export var z = \"\\(x)\\(y)\" ", " (function(exports) { exports.z = x().toString() + y(); })(this); ").js # Test nested string interpolation test(" var x = => \"\" @export var y = \"\\(1)\\(\"a\")\\( \"\\(x())\\((x()))\\( \"\\(\"b\" + x() + \"c\")\\((x() + \"d\" + x()))\" )\" )\" ", " (function(exports) { var x = function() { return ''; }; exports.y = (1).toString() + 'a' + (x() + x() + ('b' + x() + 'c' + (x() + 'd' + x()))); })(this); ").js # Test string interpolation order without constant folding test(" @import def x string @export { var y = x + \"\\(x)\\(x)\" var z = \"a\\(x)b\" + \"c\\(x)d\" } ", " (function(exports) { exports.y = x() + (x() + x()); exports.z = 'a' + x() + 'b' + ('c' + x() + 'd'); })(this); ").js # Test string interpolation order with constant folding test(" @import def x string @export { var y = x + \"\\(x)\\(x)\" var z = \"a\\(x)b\" + \"c\\(x)d\" } ", " (function(exports) { exports.y = x() + x() + x(); exports.z = 'a' + x() + 'bc' + x() + 'd'; })(this); ").js.foldAllConstants # Check assignment operators inside global variable initializers test(" class Foo { var y Foo = null var z = 0 } var x = Foo.new @export var y = x.y.z += 2 ", " (function(exports) { function Foo() { this.y = null; this.z = 0; } var ref; var x = new Foo(); exports.y = (ref = x.y).z = ref.z + 2 | 0; })(this); ").js # Check for a crash when inlining an XML-style literal that calls a global function test(" @export var foo = class Foo { def new(x int) {} } namespace Foo { def new Foo { return Foo.new(0) } } ", " (function(exports) { function Foo(x) { } exports.foo = new Foo(0); })(this); ").js.inlineAllFunctions # Check correct XML literal output for variable assignments test(" @export var foo = foo=1>/> class Foo { var foo = 0 def <>...(x Foo) Foo { return self } def bar=(x Foo) {} } ", " (function(exports) { function Foo() { this.foo = 0; } Foo.prototype.append = function(x) { return this; }; Foo.prototype.setBar = function(x) { }; var ref; var ref1; var ref2; exports.foo = (ref = new Foo(), ref.foo = 0, ref.setBar((ref1 = new Foo(), ref1.setBar(new Foo()), ref1.foo = 1, ref1.append((ref2 = new Foo(), ref2.foo = 2, ref2)), ref1)), ref); })(this); ").js # Check correct XML literal output for generated closures test(" @export var foo = for i in 0..5 { } class Foo { var foo = 0 def <>...(x Foo) {} } ", " (function(exports) { function Foo() { this.foo = 0; } Foo.prototype.append = function(x) { }; var ref; exports.foo = (ref = new Foo(), (function() { var ref1; for (var i = 0; i < 5; i = i + 1 | 0) { ref.append((ref1 = new Foo(), ref1.foo = i, ref1)); } })(), ref); })(this); ").js # Unused interface implementations should not be emitted test(" interface IFoo { def foo } class Foo :: IFoo { const _foo fn(Foo) def foo { _foo(self) } } @export def test(foo Foo) { foo.foo } ", " (function(exports) { exports.test = function(foo) { foo.foo(); }; })(this); ").js # Check code generation of contextual keywords test(" class Foo { var class = 0 var def = 0 var enum = 0 var interface = 0 var namespace = 0 var over = 0 } @export def test Foo { var class = 0 var def = 0 var enum = 0 var interface = 0 var namespace = 0 var over = 0 return Foo.new } ", " (function(exports) { function Foo() { this.$class = 0; this.def = 0; this.$enum = 0; this.$interface = 0; this.namespace = 0; this.over = 0; } exports.test = function() { var $class = 0; var def = 0; var $enum = 0; var $interface = 0; var namespace = 0; var over = 0; return new Foo(); }; })(this); ").js # Check for a crash due to a variable value changing unexpectedly after inlining test(" def foo(x bool) dynamic { return x } @entry def test { var bar = foo(false) } ", " (function() { function test() { var bar = false; } test(); })(); ").js.inlineAllFunctions # Check code generation for flags types test(" flags Foo { X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X27 X28 X29 X30 X31 } @entry def test { if (.X0 | .X1) in (Foo.X30 | .X31) { var x = Foo.X0 x = .X1 | .X2 x &= ~.X3 } } ", " (function() { function test() { if (((Foo.X0 | Foo.X1) & (Foo.X30 | Foo.X31)) != 0) { var x = Foo.X0; x = Foo.X1 | Foo.X2; x &= ~Foo.X3; } } var Foo = { X0: 1, X1: 2, X2: 4, X3: 8, X30: 1073741824, X31: -2147483648 }; test(); })(); ").js # Make sure lambda functions with dynamic type context don't return values that are known to be undefined test(" @entry def test { var a = => {} var b = => a() var c dynamic = => b() var d dynamic = => c() } ", " (function() { function test() { var a = function() { }; var b = function() { a(); }; var c = function() { b(); }; var d = function() { return c(); }; } test(); })(); ").js # Check for constant folding a subtraction identity test(" @export def test(x int, y double) { dynamic.foo(0 + x) dynamic.foo(0 - x) dynamic.foo(0 + y) dynamic.foo(0 - y) dynamic.bar(x + 0) dynamic.bar(x - 0) dynamic.bar(y + 0) dynamic.bar(y - 0) } ", " (function(exports) { exports.test = function(x, y) { foo(x); foo(-x | 0); foo(y); foo(-y); bar(x); bar(x); bar(y); bar(y); }; })(this); ").js.foldAllConstants # There's no way operator overloading will work with JavaScript, but make sure it at least generates valid JavaScript test(" @export class Foo { def + { +new } } ", " (function(exports) { exports.Foo = function() { }; exports.Foo.prototype.positive = function() { new exports.Foo().positive(); }; })(this); ").js # Check for parentheses in a stupid JavaScript corner case test(" @export def test(z dynamic) { for x = 0 in z; x; x = !x {} for x2 in z {} var x3 = 0 in z } ", " (function(exports) { exports.test = function(z) { for (var x = (0 in z); x; x = !x) { } for (var x2 in z) { } var x3 = 0 in z; }; })(this); ").js # The null dot operator shouldn't require a value inside a lambda expression test(" @export def test(x Foo, y Foo) { x?.z (=> x?.z)() x ?= y (=> x ?= y)() } class Foo { def z {} } ", " (function(exports) { exports.test = function(x, y) { if (x != null) { x.z(); } (function() { if (x != null) { x.z(); } })(); if (x == null) { x = y; } (function() { if (x == null) { x = y; } })(); }; })(this); ").js # Constructors on dynamic types shouldn't need parentheses test(" @entry def test { dynamic.Foo.new dynamic.Foo.new() dynamic.Foo.new.test dynamic.Foo.new().test dynamic.Foo.new.test dynamic.Foo.new().test var a = dynamic.Foo.new var b = dynamic.Foo.new() var c = dynamic.Foo.new.test var d = dynamic.Foo.new().test var e = dynamic.Foo.new.test var f = dynamic.Foo.new().test } ", " (function() { function test() { new Foo(); new Foo(); new Foo().test; new Foo().test; new Foo().test; new Foo().test; var a = new Foo(); var b = new Foo(); var c = new Foo().test; var d = new Foo().test; var e = new Foo().test; var f = new Foo().test; } test(); })(); ").js test(" @export var foo = [ 0, 0x7FFFFFFF, -0x7FFFFFFF, 0x80000000, ] ", " (function(exports) { exports.foo = [0, 2147483647, -2147483647, -2147483648]; })(this); ").js # Check export syntax test(" @export { class Class { def foo { new } } enum Enum { def toString { Namespace.foo } } namespace Namespace { def foo { (0 as Enum).toString } } def Function {} var Variable = 0 } ", " (function(exports) { var in_Enum = {}; in_Enum.toString = function(self) { exports.Namespace.foo(); }; exports.Function = function() { }; exports.Class = function() { }; exports.Class.prototype.foo = function() { new exports.Class(); }; exports.Enum = {}; exports.Namespace = {}; exports.Namespace.foo = function() { in_Enum.toString(0); }; exports.Variable = 0; })(this); ").js # Check export name collisions test(" @export var exports = 0 ", " (function(exports1) { exports1.exports = 0; })(this); ").js # Make sure not to overwrite the in_IntMap namespace when generating sibling namespaces in separate passes test(" @entry def main { var x = IntMap.new var y = {0: 1} } ", " (function() { function main() { var x = new Map(); var y = in_IntMap.insert(new Map(), 0, 1); } var in_IntMap = {}; in_IntMap.insert = function(self, key, value) { self.set(key, value); return self; }; main(); })(); ").js # Check multiline comments (removed) test(" @entry def main int { ## return 0 ## ### return 1 ### ### return 2 ### foo ### foo return 3 ### } ", " (function() { function main() { /// return 0; /// //// return 1; //// //// return 2; //// foo //// foo return 3; //// } process.exit(main()); })(); ").js # Check block comments before a statement test(" @entry def main { # This is # a block # comment return } ", " (function() { function main() { // This is // a block // comment return; } main(); })(); ").js # Check trailing block comments test(" @entry def main { # This is # a block # comment } ", " (function() { function main() { // This is // a block // comment } main(); })(); ").js # Check block comments in else statements test(" @export def main(x bool) { if x { x = false } else { # This is # a block # comment } } ", " (function(exports) { exports.main = function(x) { if (x) { x = false; } else { // This is // a block // comment } }; })(this); ").js # Comments in comma-separated lists test(" @entry def main { var x = [ # leading # comment 0, 1, # trailing comment # leading and 2 # trailing comment ] } ", " (function() { function main() { var x = [ // leading // comment 0, // trailing comment 1, // leading and // trailing comment 2 ]; } main(); })(); ").js # Comments on case values test(" @export def main(x int) { switch x { # before case 0, # 0 1, # 1 2 # 2 {} } } ", " (function(exports) { exports.main = function(x) { switch (x) { // before // 0 case 0: // 1 case 1: // 2 case 2: { break; } } }; })(this); ").js # Comments in binary expressions test(" @entry def main int { return # 1 1 + # 2 2 + # 3 3 } ", " (function() { function main() { return (1 + // 2 2 | 0) + // 3 3 | 0; } process.exit(main()); })(); ").js # Comments in hook expressions test(" @export def main(x bool) int { return # x x ? # 1 1 : # 2 2 } ", " (function(exports) { exports.main = function(x) { return ( // x x ? // 1 1 : // 2 2); }; })(this); ").js # Avoid name collisions across namespaces test(" namespace A { def foo { foo(1) } def foo(x int) {} } namespace B { def foo { foo(1) } } namespace B { def foo(x int) {} } @entry def main { A.foo B.foo } ", " (function() { function main() { A.foo1(); B.foo1(); } var A = {}; A.foo1 = function() { A.foo2(1); }; A.foo2 = function(x) { }; var B = {}; B.foo1 = function() { B.foo2(1); }; B.foo2 = function(x) { }; main(); })(); ").js } } ================================================ FILE: tests/library.sk ================================================ namespace Skew.Tests { def testLibrary { testExpect("sanity check", => toString([1, 2, 3]), "[1, 2, 3]") testExpect("sanity check", => toString(["1", "2", "3"]), "[1, 2, 3]") testExpect("sanity check", => toString({1: 0.5, 2: -0.5}), "{1: 0.5, 2: -0.5}") testExpect("sanity check", => toString({"a": 0.5, "b": -0.5}), "{a: 0.5, b: -0.5}") # Verify lambda environment capture testExpect("sanity check", () string => { var x = (y int) => => y var a = x(1) var b = x(2) return (a() + b()).toString }, "3") testExpect("sanity check", () string => { var x = (y int) => => => => y var a = x(1) var b = x(2) return (a()()() + b()()()).toString }, "3") ################################################################################ # Math testExpect("Math.abs(2.5)", => Math.abs(2.5), 2.5) testExpect("Math.abs(-2.5)", => Math.abs(-2.5), 2.5) testExpect("Math.abs(2)", => Math.abs(2), 2) testExpect("Math.abs(-2)", => Math.abs(-2), 2) testExpect("Math.acos(-1)", => Math.acos(-1), Math.PI) testExpect("Math.acos(0)", => Math.acos(0), Math.PI / 2) testExpect("Math.acos(1)", => Math.acos(1), 0) testExpect("Math.asin(-1)", => Math.asin(-1), -Math.PI / 2) testExpect("Math.asin(0)", => Math.asin(0), 0) testExpect("Math.asin(1)", => Math.asin(1), Math.PI / 2) testExpect("Math.atan(0)", => Math.atan(0), 0) testExpect("Math.atan2(0, 1)", => Math.atan2(0, 1), 0) testExpect("Math.atan2(1, 0)", => Math.atan2(1, 0), Math.PI / 2) testExpect("Math.atan2(0, -1)", => Math.atan2(0, -1), Math.PI) testExpect("Math.atan2(-1, 0)", => Math.atan2(-1, 0), -Math.PI / 2) testExpect("Math.sin(0)", => Math.sin(0), 0) testExpect("Math.cos(0)", => Math.cos(0), 1) testExpect("Math.tan(0)", => Math.tan(0), 0) testExpect("Math.floor(1.5)", => Math.floor(1.5), 1) testExpect("Math.floor(-1.5)", => Math.floor(-1.5), -2) testExpect("Math.ceil(1.5)", => Math.ceil(1.5), 2) testExpect("Math.ceil(-1.5)", => Math.ceil(-1.5), -1) testExpect("Math.round(1.25)", => Math.round(1.25), 1) testExpect("Math.round(-1.25)", => Math.round(-1.25), -1) testExpect("Math.round(1.75)", => Math.round(1.75), 2) testExpect("Math.round(-1.75)", => Math.round(-1.75), -2) testExpect("Math.exp(0)", => Math.exp(0), 1) testExpect("Math.exp(1)", => Math.exp(1), Math.E) testExpect("Math.log(1)", => Math.log(1), 0) testExpect("Math.log(Math.E)", => Math.log(Math.E), 1) testExpect("Math.pow(2, 3)", => Math.pow(2, 3), 8) testExpect("Math.sqrt(4)", => Math.sqrt(4), 2) testExpect("0 <= Math.random < 1", () bool => { for i in 0..1000 { var value = Math.random if value < 0 || value >= 1 { return false } } return true }, true) testExpect("a <= Math.randomInRange(a, b) <= b", () bool => { for i in 0..1000 { var a = Math.random var b = a + Math.random var value = Math.randomInRange(a, b) if value < a || value > b { return false } } return true }, true) testExpect("a <= Math.randomInRange(a, b) < b", () bool => { for i in 0..1000 { var a = (Math.random * 100) as int var b = a + (Math.random * 100) as int var value int = Math.randomInRange(a, b) if value < a || (a != b ? value >= b : value != b) { return false } } return true }, true) testExpect("Math.max(-2.0, 3.0)", => Math.max(-2.0, 3.0), 3) testExpect("Math.min(-2.0, 3.0)", => Math.min(-2.0, 3.0), -2) testExpect("Math.max(3.0, -2.0)", => Math.max(3.0, -2.0), 3) testExpect("Math.min(3.0, -2.0)", => Math.min(3.0, -2.0), -2) testExpect("Math.max(-2, 3)", => Math.max(-2, 3), 3) testExpect("Math.min(-2, 3)", => Math.min(-2, 3), -2) testExpect("Math.max(3, -2)", => Math.max(3, -2), 3) testExpect("Math.min(3, -2)", => Math.min(3, -2), -2) testExpect("Math.max(-2.0, 0.0, 3.0)", => Math.max(-2.0, 0.0, 3.0), 3) testExpect("Math.min(-2.0, 0.0, 3.0)", => Math.min(-2.0, 0.0, 3.0), -2) testExpect("Math.max(3.0, -2.0, 0.0)", => Math.max(3.0, -2.0, 0.0), 3) testExpect("Math.min(3.0, -2.0, 0.0)", => Math.min(3.0, -2.0, 0.0), -2) testExpect("Math.max(0.0, 3.0, -2.0)", => Math.max(0.0, 3.0, -2.0), 3) testExpect("Math.min(0.0, 3.0, -2.0)", => Math.min(0.0, 3.0, -2.0), -2) testExpect("Math.max(-2, 0, 3)", => Math.max(-2, 0, 3), 3) testExpect("Math.min(-2, 0, 3)", => Math.min(-2, 0, 3), -2) testExpect("Math.max(3, -2, 0)", => Math.max(3, -2, 0), 3) testExpect("Math.min(3, -2, 0)", => Math.min(3, -2, 0), -2) testExpect("Math.max(0, 3, -2)", => Math.max(0, 3, -2), 3) testExpect("Math.min(0, 3, -2)", => Math.min(0, 3, -2), -2) testExpect("Math.max(-1.0, 2.0, -3.0, 4.0)", => Math.max(-1.0, 2.0, -3.0, 4.0), 4) testExpect("Math.min(-1.0, 2.0, -3.0, 4.0)", => Math.min(-1.0, 2.0, -3.0, 4.0), -3) testExpect("Math.max(4.0, -1.0, 2.0, -3.0)", => Math.max(4.0, -1.0, 2.0, -3.0), 4) testExpect("Math.min(4.0, -1.0, 2.0, -3.0)", => Math.min(4.0, -1.0, 2.0, -3.0), -3) testExpect("Math.max(-3.0, 4.0, -1.0, 2.0)", => Math.max(-3.0, 4.0, -1.0, 2.0), 4) testExpect("Math.min(-3.0, 4.0, -1.0, 2.0)", => Math.min(-3.0, 4.0, -1.0, 2.0), -3) testExpect("Math.max(2.0, -3.0, 4.0, -1.0)", => Math.max(2.0, -3.0, 4.0, -1.0), 4) testExpect("Math.min(2.0, -3.0, 4.0, -1.0)", => Math.min(2.0, -3.0, 4.0, -1.0), -3) testExpect("Math.max(-1, 2, -3, 4)", => Math.max(-1, 2, -3, 4), 4) testExpect("Math.min(-1, 2, -3, 4)", => Math.min(-1, 2, -3, 4), -3) testExpect("Math.max(4, -1, 2, -3)", => Math.max(4, -1, 2, -3), 4) testExpect("Math.min(4, -1, 2, -3)", => Math.min(4, -1, 2, -3), -3) testExpect("Math.max(-3, 4, -1, 2)", => Math.max(-3, 4, -1, 2), 4) testExpect("Math.min(-3, 4, -1, 2)", => Math.min(-3, 4, -1, 2), -3) testExpect("Math.max(2, -3, 4, -1)", => Math.max(2, -3, 4, -1), 4) testExpect("Math.min(2, -3, 4, -1)", => Math.min(2, -3, 4, -1), -3) testExpect("Math.clamp(-3.0, -1.0, 2.0)", => Math.clamp(-3.0, -1.0, 2.0), -1) testExpect("Math.clamp(3.0, -1.0, 2.0)", => Math.clamp(3.0, -1.0, 2.0), 2) testExpect("Math.clamp(-3, -1, 2)", => Math.clamp(-3, -1, 2), -1) testExpect("Math.clamp(3, -1, 2)", => Math.clamp(3, -1, 2), 2) testExpect("Math.E", => Math.E, 2.718281828459045) testExpect("Math.INFINITY", => Math.INFINITY, 1 / 0.0) testExpect("Math.NAN", => Math.NAN, 0 / 0.0) testExpect("Math.PI", => Math.PI, 3.141592653589793) testExpect("Math.SQRT_2", => Math.SQRT_2, 1.4142135623730951) ################################################################################ # bool # Unary testExpect("!false", => !false, true) testExpect("!true", => !true, false) # Binary testExpect("false && false", => false && false, false) testExpect("false && true", => false && true, false) testExpect("true && false", => true && false, false) testExpect("true && true", => true && true, true) testExpect("false || false", => false || false, false) testExpect("false || true", => false || true, true) testExpect("true || false", => true || false, true) testExpect("true || true", => true || true, true) # Short-circuit testExpect("true || (=> { throw null })()", => (true || (=> { throw null })()).toString, "true") testExpect("false && (=> { throw null })()", => (false && (=> { throw null })()).toString, "false") # toString testExpect("false.toString", => false.toString, "false") testExpect("true.toString", => true.toString, "true") ################################################################################ # int # Literals testExpect("'a'", => 'a', 97) testExpect("0b101", => 0b101, 5) testExpect("-0b101", => -0b101, -5) testExpect("0o123", => 0o123, 83) testExpect("-0o123", => -0o123, -83) testExpect("0x123", => 0x123, 291) testExpect("-0x123", => -0x123, -291) # Unary testExpect("+2", => +2, 2) testExpect("3++", () int => { var x = 3 x++ return x }, 4) testExpect("++3", () int => { var x = 3 ++x return x }, 4) testExpect("-2", => -2, -2) testExpect("3--", () int => { var x = 3 x-- return x }, 2) testExpect("--3", () int => { var x = 3 --x return x }, 2) testExpect("~2", => ~2, -3) # Binary testExpect("5 + 3", => 5 + 3, 8) testExpect("5 - 3", => 5 - 3, 2) testExpect("5 * 3", => 5 * 3, 15) testExpect("5 / 3", => 5 / 3, 1) testExpect("5 % 3", => 5 % 3, 2) testExpect("-5 % 3", => -5 % 3, -2) testExpect("5 %% 3", => 5 %% 3, 2) testExpect("-5 %% 3", => -5 %% 3, 1) testExpect("5 %% -3", => 5 %% -3, -1) testExpect("-5 %% -3", => -5 %% -3, -2) testExpect("5 << 3", => 5 << 3, 40) testExpect("5 >> 1", => 5 >> 1, 2) testExpect("-5 >> 1", => -5 >> 1, -3) testExpect("5 >>> 1", => 5 >>> 1, 2) testExpect("-5 >>> 1", => -5 >>> 1, 0x7FFFFFFD) testExpect("2 ** 3", => 2 ** 3, 8) testExpect("44 | 33", => 44 | 33, 45) testExpect("44 & 33", => 44 & 33, 32) testExpect("44 ^ 33", => 44 ^ 33, 13) testExpect("-44 | 33", => -44 | 33, -11) testExpect("-44 & 33", => -44 & 33, 0) testExpect("-44 ^ 33", => -44 ^ 33, -11) testExpect("-44 | -33", => -44 | -33, -33) testExpect("-44 & -33", => -44 & -33, -44) testExpect("-44 ^ -33", => -44 ^ -33, 11) testExpect("2 <=> 5", => 2 <=> 5, -1) testExpect("2 <=> 2", => 2 <=> 2, 0) testExpect("5 <=> 2", => 5 <=> 2, 1) testExpect("-0x7FFFFFFF <=> 0x7FFFFFFF", => -0x7FFFFFFF <=> 0x7FFFFFFF, -1) # 32-bit integer multiplication var imul = (a int, b int) => a * b testExpect("0x12345678 * 0x87654321", => imul(0x12345678, 0x87654321), 0x70B88D78) testExpect("0x12345678 * -0x87654321", => imul(0x12345678, -0x87654321), -0x70B88D78) testExpect("-0xDEADF00D * -0xCAFEBABE", => imul(-0xDEADF00D, -0xCAFEBABE), 0x14679BA6) # Binary assignment testExpect("5 += 3", () int => { var x = 5 x += 3 return x }, 8) testExpect("5 -= 3", () int => { var x = 5 x -= 3 return x }, 2) testExpect("5 *= 3", () int => { var x = 5 x *= 3 return x }, 15) testExpect("5 /= 3", () int => { var x = 5 x /= 3 return x }, 1) testExpect("5 %= 3", () int => { var x = 5 x %= 3 return x }, 2) testExpect("-5 %%= 3", () int => { var x = -5 x %%= 3 return x }, 1) testExpect("2 **= 3", () int => { var x = 2 x **= 3 return x }, 8) testExpect("44 |= 33", () int => { var x = 44 x |= 33 return x }, 45) testExpect("44 &= 33", () int => { var x = 44 x &= 33 return x }, 32) testExpect("44 ^= 33", () int => { var x = 44 x ^= 33 return x }, 13) testExpect("5 <<= 3", () int => { var x = 5 x <<= 3 return x }, 40) testExpect("5 >>= 1", () int => { var x = 5 x >>= 1 return x }, 2) testExpect("-5 >>= 1", () int => { var x = -5 x >>= 1 return x }, -3) testExpect("5 >>>= 1", () int => { var x = 5 x >>>= 1 return x }, 2) testExpect("-5 >>>= 1", () int => { var x = -5 x >>>= 1 return x }, 0x7FFFFFFD) testExpect("int.MIN", => int.MIN, -0x7FFFFFFF - 1) testExpect("int.MAX", => int.MAX, 0x7FFFFFFF) ################################################################################ # double # Unary testExpect("+2.0", => +2.0, 2.0) testExpect("x++", () double => { var x = 3.5 x++ return x }, 4.5) testExpect("++x", () double => { var x = 3.5 ++x return x }, 4.5) testExpect("-2.0", => -2.0, -2.0) testExpect("x--", () double => { var x = 3.5 x-- return x }, 2.5) testExpect("--x", () double => { var x = 3.5 --x return x }, 2.5) # Binary testExpect("5.5 + 3.0", => 5.5 + 3.0, 8.5) testExpect("5.5 - 3.0", => 5.5 - 3.0, 2.5) testExpect("5.5 * 3.0", => 5.5 * 3.0, 16.5) testExpect("5.0 / 3.0", => 5.0 / 3.0, 1.6666666666666667) testExpect("5.0 %% 3.5", => 5.0 %% 3.5, 1.5) testExpect("-5.0 %% 3.5", => -5.0 %% 3.5, 2) testExpect("5.0 %% -3.5", => 5.0 %% -3.5, -2) testExpect("-5.0 %% -3.5", => -5.0 %% -3.5, -1.5) testExpect("2.0 ** 3.0", => 2.0 ** 3.0, 8) testExpect("2.0 ** 0.5", => 2.0 ** 0.5, 1.4142135623730951) testExpect("2.0 <=> 5.5", => 2.0 <=> 5.5, -1) testExpect("2.0 <=> 2.0", => 2.0 <=> 2.0, 0) testExpect("5.5 <=> 2.0", => 5.5 <=> 2.0, 1) # Binary assignment testExpect("5.5 += 3.0", () double => { var x = 5.5 x += 3.0 return x }, 8.5) testExpect("5.5 -= 3.0", () double => { var x = 5.5 x -= 3.0 return x }, 2.5) testExpect("5.5 *= 3.0", () double => { var x = 5.5 x *= 3.0 return x }, 16.5) testExpect("5.0 /= 3.0", () double => { var x = 5.0 x /= 3.0 return x }, 1.6666666666666667) testExpect("-5.0 %%= 3.5", () double => { var x = -5.0 x %%= 3.5 return x }, 2) testExpect("2.0 **= 3.0", () double => { var x = 2.0 x **= 3 return x }, 8) # isFinite testExpect("0.0.isFinite", => 0.0.isFinite, true) testExpect("Math.NAN.isFinite", => Math.NAN.isFinite, false) testExpect("Math.INFINITY.isFinite", => Math.INFINITY.isFinite, false) testExpect("(-Math.INFINITY).isFinite", => (-Math.INFINITY).isFinite, false) # isNaN testExpect("0.0.isNaN", => 0.0.isNaN, false) testExpect("Math.NAN.isNaN", => Math.NAN.isNaN, true) testExpect("Math.INFINITY.isNaN", => Math.INFINITY.isNaN, false) testExpect("(-Math.INFINITY).isNaN", => (-Math.INFINITY).isNaN, false) ################################################################################ # string # Literals testExpect("", => "", "") testExpect("\\0", => "\0", "\0") testExpect("\\x00", => "\x00", "\0") testExpect("\\01", => "\01", "\0" + "1") testExpect("\\x001", => "\x001", "\0" + "1") # Binary testExpect("\"a\\0b\" + \"x\\0y\"", => "a\0b" + "x\0y", "a\0bx\0y") testExpect("\"a\\0b\" += \"x\\0y\"", () string => { var x = "a\0b" x += "x\0y" return x }, "a\0bx\0y") testExpect("\"\\0a\" <=> \"\\0x\"", => "\0a" <=> "\0x", -1) testExpect("\"\\0a\" <=> \"\\0a\"", => "\0a" <=> "\0a", 0) testExpect("\"\\0x\" <=> \"\\0a\"", => "\0x" <=> "\0a", 1) # count testExpect("\"a\\0b\".count", => "a\0b".count, 3) # in testExpect("\"a\\0b\" in \"a\\0\"", => "a\0b" in "a\0", false) testExpect("\"a\\0b\" in \"a\\0b\"", => "a\0b" in "a\0b", true) testExpect("\"a\\0b\" in \"a\\0bc\"", => "a\0b" in "a\0bc", true) testExpect("\"a\\0b\" in \" a\\0b\"", => "a\0b" in " a\0b", true) testExpect("\"a\\0b\" in \" a\\0bc\"", => "a\0b" in " a\0bc", true) # indexOf testExpect("\"a\\0\".indexOf(\"a\\0b\")", => "a\0".indexOf("a\0b"), -1) testExpect("\"a\\0b\".indexOf(\"a\\0b\")", => "a\0b".indexOf("a\0b"), 0) testExpect("\" a\\0b \".indexOf(\"a\\0b\")", => " a\0b ".indexOf("a\0b"), 1) testExpect("\" a\\0b a\\0b \".indexOf(\"a\\0b\")", => " a\0b a\0b ".indexOf("a\0b"), 1) # lastIndexOf testExpect("\"a\\0\".lastIndexOf(\"a\\0b\")", => "a\0".lastIndexOf("a\0b"), -1) testExpect("\"a\\0b\".lastIndexOf(\"a\\0b\")", => "a\0b".lastIndexOf("a\0b"), 0) testExpect("\" a\\0b \".lastIndexOf(\"a\\0b\")", => " a\0b ".lastIndexOf("a\0b"), 1) testExpect("\" a\\0b a\\0b \".lastIndexOf(\"a\\0b\")", => " a\0b a\0b ".lastIndexOf("a\0b"), 5) # startsWith testExpect("\"a\\0\".startsWith(\"a\\0b\")", => "a\0".startsWith("a\0b"), false) testExpect("\"a\\0b\".startsWith(\"a\\0b\")", => "a\0b".startsWith("a\0b"), true) testExpect("\"a\\0bc\".startsWith(\"a\\0b\")", => "a\0bc".startsWith("a\0b"), true) testExpect("\" a\\0b\".startsWith(\"a\\0b\")", => " a\0b".startsWith("a\0b"), false) # endsWith testExpect("\"a\\0\".endsWith(\"a\\0b\")", => "a\0".endsWith("a\0b"), false) testExpect("\"a\\0b\".endsWith(\"a\\0b\")", => "a\0b".endsWith("a\0b"), true) testExpect("\"a\\0bc\".endsWith(\"a\\0b\")", => "a\0bc".endsWith("a\0b"), false) testExpect("\" a\\0b\".endsWith(\"a\\0b\")", => " a\0b".endsWith("a\0b"), true) # [] testExpect("\"a\\0b\"[0]", => "a\0b"[0], 'a') testExpect("\"a\\0b\"[1]", => "a\0b"[1], '\0') testExpect("\"a\\0b\"[2]", => "a\0b"[2], 'b') # get testExpect("\"a\\0b\".get(0)", => "a\0b".get(0), "a") testExpect("\"a\\0b\".get(1)", => "a\0b".get(1), "\0") testExpect("\"a\\0b\".get(2)", => "a\0b".get(2), "b") # slice testExpect("\"a\\0b\".slice(1)", => "a\0b".slice(1), "\0b") testExpect("\"a\\0b\".slice(1, 2)", => "a\0b".slice(1, 2), "\0") # Unicode testExpect("\"a\\0b\".codePoints", => "a\0b".codePoints, ['a', '\0', 'b']) testExpect("\"a\\0b\".codeUnits", => "a\0b".codeUnits, ['a', '\0', 'b']) testExpect("string.fromCodePoint('\\0')", => string.fromCodePoint('\0'), "\0") testExpect("string.fromCodePoint('a')", => string.fromCodePoint('a'), "a") testExpect("string.fromCodeUnit('\\0')", => string.fromCodeUnit('\0'), "\0") testExpect("string.fromCodeUnit('a')", => string.fromCodeUnit('a'), "a") testExpect("string.fromCodePoints(['a', '\\0', 'b'])", => string.fromCodePoints(['a', '\0', 'b']), "a\0b") testExpect("string.fromCodeUnits(['a', '\\0', 'b'])", => string.fromCodeUnits(['a', '\0', 'b']), "a\0b") # Other testExpect("\"a \\0 b\".split(\" \")", => "a \0 b".split(" "), ["a", "\0", "b"]) testExpect("\" a \\0 b \".split(\" \")", => " a \0 b ".split(" "), ["", "a", "\0", "b", ""]) testExpect("\" \".join([\"a\", \"\\0\", \"b\"])", => " ".join(["a", "\0", "b"]), "a \0 b") testExpect("\" \".join([\"\", \"a\", \"\\0\", \"b\", \"\"])", => " ".join(["", "a", "\0", "b", ""]), " a \0 b ") testExpect("\"a\\0b\".repeat(3)", => "a\0b".repeat(3), "a\0ba\0ba\0b") testExpect("\"a\\0b\\0c\".replaceAll(\"\\0\", \"\\0\\0\")", => "a\0b\0c".replaceAll("\0", "\0\0"), "a\0\0b\0\0c") testExpect("\"a\\0B\\0c\\0D\".toUpperCase", => "a\0B\0c\0D".toUpperCase, "A\0B\0C\0D") testExpect("\"a\\0B\\0c\\0D\".toLowerCase", => "a\0B\0c\0D".toLowerCase, "a\0b\0c\0d") testExpect("\"a\\0B\\0c\\0D\".toString", => "a\0B\0c\0D".toString, "a\0B\0c\0D") ################################################################################ # List # new testExpect("[] as List", => [] as List, [] as List) testExpect("List.new", => List.new, [] as List) testExpect("[1, 2, 3]", => [1, 2, 3], [1, 2, 3]) # [] and []= testExpect("[1, 2, 3][1]", => [1, 2, 3][1], 2) testExpect("[1, 2, 3][1] = 4", () List => { var x = [1, 2, 3] x[1] = 4 return x }, [1, 4, 3]) testExpect("[1, 2, 3][1] = 4", => [1, 2, 3][1] = 4, 4) # count testExpect("[1, 2, 3].count", => [1, 2, 3].count, 3) # isEmpty testExpect("List.new.isEmpty", => List.new.isEmpty, true) testExpect("[1, 2, 3].isEmpty", => [1, 2, 3].isEmpty, false) # resize testExpect("[1, 2, 3].resize(5, -1)", () List => { var x = [1, 2, 3] x.resize(5, -1) return x }, [1, 2, 3, -1, -1]) testExpect("[1, 2, 3].resize(1, -1)", () List => { var x = [1, 2, 3] x.resize(1, -1) return x }, [1]) # append testExpect("[1, 2, 3].append(4)", () List => { var x = [1, 2, 3] x.append(4) return x }, [1, 2, 3, 4]) testExpect("[1, 2, 3].append([4, 5])", () List => { var x = [1, 2, 3] x.append([4, 5]) return x }, [1, 2, 3, 4, 5]) testExpect("[1, 2, 3].appendOne(4)", () List => { var x = [1, 2, 3] x.appendOne(4) return x }, [1, 2, 3, 4]) testExpect("[1, 2, 3].appendOne(2)", () List => { var x = [1, 2, 3] x.appendOne(2) return x }, [1, 2, 3]) # prepend testExpect("[1, 2, 3].prepend(4)", () List => { var x = [1, 2, 3] x.prepend(4) return x }, [4, 1, 2, 3]) testExpect("[1, 2, 3].prepend([4, 5])", () List => { var x = [1, 2, 3] x.prepend([4, 5]) return x }, [4, 5, 1, 2, 3]) # insert testExpect("[1, 2, 3].insert(1, 4)", () List => { var x = [1, 2, 3] x.insert(1, 4) return x }, [1, 4, 2, 3]) testExpect("[1, 2, 3].insert(1, [4, 5])", () List => { var x = [1, 2, 3] x.insert(1, [4, 5]) return x }, [1, 4, 5, 2, 3]) testExpect("[1, 2, 3].insert(3, 4)", () List => { var x = [1, 2, 3] x.insert(3, 4) return x }, [1, 2, 3, 4]) testExpect("[1, 2, 3].insert(3, [4, 5])", () List => { var x = [1, 2, 3] x.insert(3, [4, 5]) return x }, [1, 2, 3, 4, 5]) # remove testExpect("[1, 2, 1, 3, 1].removeAll(1)", () List => { var x = [1, 2, 1, 3, 1] x.removeAll(1) return x }, [2, 3]) testExpect("[1, 2, 1, 3, 1].removeAll(0)", () List => { var x = [1, 2, 1, 3, 1] x.removeAll(0) return x }, [1, 2, 1, 3, 1]) testExpect("[1, 2, 3].removeAt(1)", () List => { var x = [1, 2, 3] x.removeAt(1) return x }, [1, 3]) testExpect("[1, 2, 1, 3, 1].removeDuplicates", () List => { var x = [1, 2, 1, 3, 1] x.removeDuplicates return x }, [1, 2, 3]) testExpect("[1, 2, 3].removeFirst", () List => { var x = [1, 2, 3] x.removeFirst return x }, [2, 3]) testExpect("[1, 2, 1, 3, 1].removeIf(y => y == 1)", () List => { var x = [1, 2, 1, 3, 1] x.removeIf(y => y == 1) return x }, [2, 3]) testExpect("[1, 2, 3].removeLast", () List => { var x = [1, 2, 3] x.removeLast return x }, [1, 2]) testExpect("[1, 2, 1, 3, 1].removeOne(1)", () List => { var x = [1, 2, 1, 3, 1] x.removeOne(1) return x }, [2, 1, 3, 1]) testExpect("[1, 2, 1, 3, 1].removeOne(0)", () List => { var x = [1, 2, 1, 3, 1] x.removeOne(0) return x }, [1, 2, 1, 3, 1]) testExpect("[1, 2, 1, 3, 1].removeRange(1, 4)", () List => { var x = [1, 2, 1, 3, 1] x.removeRange(1, 4) return x }, [1, 1]) # take testExpect("[1, 2, 3].takeFirst", => [1, 2, 3].takeFirst, 1) testExpect("[1, 2, 3].takeLast", => [1, 2, 3].takeLast, 3) testExpect("[1, 2, 3].takeAt(1)", => [1, 2, 3].takeAt(1), 2) testExpect("[1, 2, 3, 4, 5].takeRange(1, 4)", => [1, 2, 3, 4, 5].takeRange(1, 4), [2, 3, 4]) testExpect("[1, 2, 3].takeFirst", () List => { var x = [1, 2, 3] x.takeFirst return x }, [2, 3]) testExpect("[1, 2, 3].takeLast", () List => { var x = [1, 2, 3] x.takeLast return x }, [1, 2]) testExpect("[1, 2, 3].takeAt(1)", () List => { var x = [1, 2, 3] x.takeAt(1) return x }, [1, 3]) testExpect("[1, 2, 3, 4, 5].takeRange(1, 4)", () List => { var x = [1, 2, 3, 4, 5] x.takeRange(1, 4) return x }, [1, 5]) # first testExpect("[1, 2, 3].first", => [1, 2, 3].first, 1) testExpect("[1, 2, 3].first = 4", () List => { var x = [1, 2, 3] x.first = 4 return x }, [4, 2, 3]) testExpect("[1, 2, 3].first = 4", => [1, 2, 3].first = 4, 4) # last testExpect("[1, 2, 3].last", => [1, 2, 3].last, 3) testExpect("[1, 2, 3].last = 4", () List => { var x = [1, 2, 3] x.last = 4 return x }, [1, 2, 4]) testExpect("[1, 2, 3].last = 4", => [1, 2, 3].last = 4, 4) # in testExpect("0 in [1, 2, 3]", => 0 in [1, 2, 3], false) testExpect("1 in [1, 2, 3]", => 1 in [1, 2, 3], true) # indexOf testExpect("[1, 2, 3, 2, 1].indexOf(0)", => [1, 2, 3, 2, 1].indexOf(0), -1) testExpect("[1, 2, 3, 2, 1].indexOf(2)", => [1, 2, 3, 2, 1].indexOf(2), 1) # lastIndexOf testExpect("[1, 2, 3, 2, 1].lastIndexOf(0)", => [1, 2, 3, 2, 1].lastIndexOf(0), -1) testExpect("[1, 2, 3, 2, 1].lastIndexOf(2)", => [1, 2, 3, 2, 1].lastIndexOf(2), 3) # all testExpect("[1, 2, 3].all(x => x < 0)", => [1, 2, 3].all(x => x < 0), false) testExpect("[1, 2, 3].all(x => x > 0)", => [1, 2, 3].all(x => x > 0), true) testExpect("[1, 2, 3].all(x => x > 1)", => [1, 2, 3].all(x => x > 1), false) # any testExpect("[1, 2, 3].any(x => x < 0)", => [1, 2, 3].any(x => x < 0), false) testExpect("[1, 2, 3].any(x => x > 0)", => [1, 2, 3].any(x => x > 0), true) testExpect("[1, 2, 3].any(x => x > 1)", => [1, 2, 3].any(x => x > 1), true) # clone testExpect("[1, 2, 3].clone", => [1, 2, 3].clone, [1, 2, 3]) testExpect("(x => x != x.clone)([1, 2, 3])", => (x => x != x.clone)([1, 2, 3]), true) # each testExpect("[1, 2, 3].each(x => y += x)", () int => { var y = 0 [1, 2, 3].each(x => y += x) return y }, 6) # equals testExpect("[1, 2, 3].equals([1, 2, 3])", => [1, 2, 3].equals([1, 2, 3]), true) testExpect("[1, 2, 3].equals([3, 2, 1])", => [1, 2, 3].equals([3, 2, 1]), false) testExpect("[1, 2, 3, 4].equals([1, 2, 3])", => [1, 2, 3, 4].equals([1, 2, 3]), false) testExpect("[1, 2, 3].equals([1, 2, 3, 4])", => [1, 2, 3].equals([1, 2, 3, 4]), false) # filter testExpect("[1, 2, 3, 2, 1].filter(x => x != 2)", => [1, 2, 3, 2, 1].filter(x => x != 2), [1, 3, 1]) testExpect("(x => x != x.filter(y => true))([1, 2, 3])", => (x => x != x.filter(y => true))([1, 2, 3]), true) # map testExpect("[1, 2, 3, 2, 1].map(x => x + 1)", => [1, 2, 3, 2, 1].map(x => x + 1), [2, 3, 4, 3, 2]) testExpect("(x => x != x.map(y => y))([1, 2, 3])", => (x => x != x.map(y => y))([1, 2, 3]), true) # reverse testExpect("[1, 2, 3].reverse", () List => { var x = [1, 2, 3] x.reverse return x }, [3, 2, 1]) # shuffle testExpect("[1, 2, 3, 4, 5].shuffle", () int => { var x = [1, 2, 3, 4, 5] var y = 0 x.shuffle x.each(z => y += z) return y }, 1 + 2 + 3 + 4 + 5) # slice testExpect("[1, 2, 3].slice(1)", => [1, 2, 3].slice(1), [2, 3]) testExpect("[1, 2, 3].slice(1, 2)", => [1, 2, 3].slice(1, 2), [2]) # sort testExpect("[2, 1, 3].sort((a, b) => a <=> b)", () List => { var x = [2, 1, 3] x.sort((a, b) => a <=> b) return x }, [1, 2, 3]) # swap testExpect("[1, 2, 3].swap(0, 2)", () List => { var x = [1, 2, 3] x.swap(0, 2) return x }, [3, 2, 1]) ################################################################################ # IntMap # new testExpect("{} as IntMap", => ({}) as IntMap, {} as IntMap) testExpect("IntMap.new", => IntMap.new, {} as IntMap) testExpect("{1: 0.5, 2: -0.5}", => ({1: 0.5, 2: -0.5}), {1: 0.5, 2: -0.5}) # [] and []= testExpect("{1: 0.5}[1]", => ({1: 0.5})[1], 0.5) testExpect("{1: 0.5}[1] = -0.5", () IntMap => { var x = {1: 0.5} x[1] = -0.5 return x }, {1: -0.5}) testExpect("{1: 0.5}[1] = -0.5", => ({1: 0.5})[1] = -0.5, -0.5) # count testExpect("{1: 0.5, 2: -0.5}.count", => ({1: 0.5, 2: -0.5}).count, 2) # isEmpty testExpect("IntMap.new.isEmpty", => IntMap.new.isEmpty, true) testExpect("{1: 0.5, 2: -0.5}.isEmpty", => ({1: 0.5, 2: -0.5}).isEmpty, false) # keys testExpect("{1: 0.5, 2: -0.5}.keys", () List => { var x = {1: 0.5, 2: -0.5}.keys x.sort((a, b) => a <=> b) # Sort so the order is deterministic return x }, [1, 2]) # values testExpect("{1: 0.5, 2: -0.5}.values", () List => { var x = {1: 0.5, 2: -0.5}.values x.sort((a, b) => a <=> b) # Sort so the order is deterministic return x }, [-0.5, 0.5]) # clone testExpect("{1: 0.5, 2: -0.5}.clone", => ({1: 0.5, 2: -0.5}).clone, {1: 0.5, 2: -0.5}) testExpect("(x => x != x.clone)({1: 0.5, 2: -0.5})", => (x => x != x.clone)({1: 0.5, 2: -0.5}), true) # each testExpect("{1: 0.5, 2: -0.5}.each((k, v) => x += k)", () int => { var x = 0 {1: 0.5, 2: -0.5}.each((k, v) => x += k) return x }, 3) testExpect("{1: 0.5, 2: -1.0}.each((k, v) => x += v)", () double => { var x = 0.0 {1: 0.5, 2: -1.0}.each((k, v) => x += v) return x }, -0.5) # get testExpect("{1: 0.5, 2: -0.5}.get(1, -1)", => ({1: 0.5, 2: -0.5}).get(1, -1), 0.5) testExpect("{1: 0.5, 2: -0.5}.get(0, -1)", => ({1: 0.5, 2: -0.5}).get(0, -1), -1) # in testExpect("0 in {1: 0.5, 2: -0.5}", => 0 in {1: 0.5, 2: -0.5}, false) testExpect("1 in {1: 0.5, 2: -0.5}", => 1 in {1: 0.5, 2: -0.5}, true) # remove testExpect("{1: 0.5, 2: -0.5}.remove(1)", () IntMap => { var x = {1: 0.5, 2: -0.5} x.remove(1) return x }, {2: -0.5}) testExpect("{1: 0.5, 2: -0.5}.remove(0)", () IntMap => { var x = {1: 0.5, 2: -0.5} x.remove(0) return x }, {1: 0.5, 2: -0.5}) ################################################################################ # StringMap # new testExpect("{} as StringMap", => ({}) as StringMap, {} as StringMap) testExpect("StringMap.new", => StringMap.new, {} as StringMap) testExpect("{\"\": 0.5, \"\\0\": -0.5}", => ({"": 0.5, "\0": -0.5}), {"": 0.5, "\0": -0.5}) # [] and []= testExpect("{\"\": 0.5}[\"\"]", => ({"": 0.5})[""], 0.5) testExpect("{\"\": 0.5}[\"\"] = -0.5", () StringMap => { var x = {"": 0.5} x[""] = -0.5 return x }, {"": -0.5}) testExpect("{\"\": 0.5}[\"\"] = -0.5", => ({"": 0.5})[""] = -0.5, -0.5) # count testExpect("{\"\": 0.5, \"\\0\": -0.5}.count", => ({"": 0.5, "\0": -0.5}).count, 2) # isEmpty testExpect("StringMap.new.isEmpty", => StringMap.new.isEmpty, true) testExpect("{\"\": 0.5, \"\\0\": -0.5}.isEmpty", => ({"": 0.5, "\0": -0.5}).isEmpty, false) # keys testExpect("{\"\": 0.5, \"\\0\": -0.5}.keys", () List => { var x = {"": 0.5, "\0": -0.5}.keys x.sort((a, b) => a <=> b) # Sort so the order is deterministic return x }, ["", "\0"]) # values testExpect("{\"\": 0.5, \"\\0\": -0.5}.values", () List => { var x = {"": 0.5, "\0": -0.5}.values x.sort((a, b) => a <=> b) # Sort so the order is deterministic return x }, [-0.5, 0.5]) # clone testExpect("{\"\": 0.5, \"\\0\": -0.5}.clone", => ({"": 0.5, "\0": -0.5}).clone, {"": 0.5, "\0": -0.5}) testExpect("(x => x != x.clone)({\"\": 0.5, \"\\0\": -0.5})", => (x => x != x.clone)({"": 0.5, "\0": -0.5}), true) # each testExpect("{\"\": 0.5, \"\\0\": -0.5, \"a\": 0}.each((k, v) => x += k)", () bool => { var x = "" {"": 0.5, "\0": -0.5, "a": 0}.each((k, v) => x += k) return x == "a\0" || x == "\0a" }, true) testExpect("{\"\": 0.5, \"\\0\": -1.0}.each((k, v) => x += v)", () double => { var x = 0.0 {"": 0.5, "\0": -1.0}.each((k, v) => x += v) return x }, -0.5) # get testExpect("{\"\": 0.5, \"\\0\": -0.5}.get(\"\\0\", -1)", => ({"": 0.5, "\0": -0.5}).get("\0", -1), -0.5) testExpect("{\"\": 0.5, \"\\0\": -0.5}.get(\"\", -1)", => ({"": 0.5, "\0": -0.5}).get("", -1), 0.5) testExpect("{\"\": 0.5, \"\\0\": -0.5}.get(\"x\", -1)", => ({"": 0.5, "\0": -0.5}).get("x", -1), -1) # in testExpect("\"x\" in {\"\": 0.5, \"\\0\": -0.5}", => "x" in {"": 0.5, "\0": -0.5}, false) testExpect("\"\" in {\"\": 0.5, \"\\0\": -0.5}", => "" in {"": 0.5, "\0": -0.5}, true) testExpect("\"\\0\" in {\"\": 0.5, \"\\0\": -0.5}", => "\0" in {"": 0.5, "\0": -0.5}, true) # remove testExpect("{\"\": 0.5, \"\\0\": -0.5}.remove(\"\")", () StringMap => { var x = {"": 0.5, "\0": -0.5} x.remove("") return x }, {"\0": -0.5}) testExpect("{\"\": 0.5, \"\\0\": -0.5}.remove(\"x\")", () StringMap => { var x = {"": 0.5, "\0": -0.5} x.remove("x") return x }, {"": 0.5, "\0": -0.5}) ################################################################################ # StringBuilder testExpect("StringBuilder 1", () string => { var builder = StringBuilder.new builder.append("abc") builder.append("\0") builder.append("def") builder.append("") builder.append("xyz") return builder.toString }, "abc\0defxyz") testExpect("StringBuilder 2", () string => { var builder = StringBuilder.new builder.append("abc") builder.append("\0") builder.toString # Calling append after toString must still append builder.append("def") builder.append("") builder.append("xyz") return builder.toString }, "abc\0defxyz") } } ================================================ FILE: tests/node.sk ================================================ enum Skew.NodeKind { A B C D E F G } namespace Skew.Tests { def testNode { var visit fn(Node) visit = node => { assert(node.previousSibling == null || node.previousSibling.nextSibling == node) assert(node.nextSibling == null || node.nextSibling.previousSibling == node) assert(node.firstChild == null || node.firstChild.previousSibling == null) assert(node.lastChild == null || node.lastChild.nextSibling == null) for child = node.firstChild; child != null; child = child.nextSibling { assert(child.parent == node) visit(child) } } var verify = (root Node) string => { if root != null { visit(root) } return toString(root) } ################################################################################ # lastChild/previousSibling test("previousSibling", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).previousSibling), "null") }) test("lastChild", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).lastChild), "[C]") }) test("lastChild.lastChild", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).lastChild.lastChild), "null") }) test("lastChild.previousSibling", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).lastChild.previousSibling), "[B]") }) test("lastChild.previousSibling.previousSibling", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).lastChild.previousSibling.previousSibling), "null") }) ################################################################################ # firstChild/nextSibling test("nextSibling", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).nextSibling), "null") }) test("firstChild", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).firstChild), "[B]") }) test("firstChild.firstChild", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).firstChild.firstChild), "null") }) test("firstChild.nextSibling", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).firstChild.nextSibling), "[C]") }) test("firstChild.nextSibling.nextSibling", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).firstChild.nextSibling.nextSibling), "null") }) ################################################################################ # prependChild test("prependChild 1", expect => { expect(verify(Node.new(.A).prependChild(Node.new(.B))), "[A, [B]]") }) test("prependChild 2", expect => { expect(verify(Node.new(.A).prependChild(Node.new(.B)).prependChild(Node.new(.C))), "[A, [C], [B]]") }) ################################################################################ # appendChild test("appendChild 1", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B))), "[A, [B]]") }) test("appendChild 2", expect => { expect(verify(Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C))), "[A, [B], [C]]") }) ################################################################################ # appendChildrenFrom test("appendChildrenFrom 1", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) var D = Node.new(.D) expect((ABC == ABC.appendChildrenFrom(D)).toString, "true") expect(verify(ABC), "[A, [B], [C]]") expect(verify(D), "[D]") }) test("appendChildrenFrom 2", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) var DEF = Node.new(.D).appendChild(Node.new(.E)).appendChild(Node.new(.F)) expect((ABC == ABC.appendChildrenFrom(DEF)).toString, "true") expect(verify(ABC), "[A, [B], [C], [E], [F]]") expect(verify(DEF), "[D]") }) ################################################################################ # insertChildBefore test("insertChildBefore 1", expect => { var A = Node.new(.A) var B = Node.new(.B) expect((A == A.insertChildBefore(null, null)).toString, "true") expect(verify(A), "[A]") expect(verify(B), "[B]") }) test("insertChildBefore 2", expect => { var A = Node.new(.A) var B = Node.new(.B) expect((A == A.insertChildBefore(null, B)).toString, "true") expect(verify(A), "[A, [B]]") expect(verify(B), "[B]") }) test("insertChildBefore 3", expect => { var AB = Node.new(.A).appendChild(Node.new(.B)) var C = Node.new(.C) expect((AB == AB.insertChildBefore(null, C)).toString, "true") expect(verify(AB), "[A, [B], [C]]") expect(verify(C), "[C]") }) test("insertChildBefore 4", expect => { var AB = Node.new(.A).appendChild(Node.new(.B)) var C = Node.new(.C) expect((AB == AB.insertChildBefore(AB.firstChild, C)).toString, "true") expect(verify(AB), "[A, [C], [B]]") expect(verify(C), "[C]") }) ################################################################################ # insertChildAfter test("insertChildAfter 1", expect => { var A = Node.new(.A) var B = Node.new(.B) expect((A == A.insertChildAfter(null, null)).toString, "true") expect(verify(A), "[A]") expect(verify(B), "[B]") }) test("insertChildAfter 2", expect => { var A = Node.new(.A) var B = Node.new(.B) expect((A == A.insertChildAfter(null, B)).toString, "true") expect(verify(A), "[A, [B]]") expect(verify(B), "[B]") }) test("insertChildAfter 3", expect => { var AB = Node.new(.A).appendChild(Node.new(.B)) var C = Node.new(.C) expect((AB == AB.insertChildAfter(null, C)).toString, "true") expect(verify(AB), "[A, [C], [B]]") expect(verify(C), "[C]") }) test("insertChildAfter 4", expect => { var AB = Node.new(.A).appendChild(Node.new(.B)) var C = Node.new(.C) expect((AB == AB.insertChildAfter(AB.firstChild, C)).toString, "true") expect(verify(AB), "[A, [B], [C]]") expect(verify(C), "[C]") }) ################################################################################ # insertChildrenAfterFrom test("insertChildrenAfterFrom 1", expect => { var A = Node.new(.A) var BCD = Node.new(.B).appendChild(Node.new(.C)).appendChild(Node.new(.D)) A.insertChildrenAfterFrom(BCD, null) expect(verify(A), "[A, [C], [D]]") expect(verify(BCD), "[B]") }) test("insertChildrenAfterFrom 2", expect => { var AB = Node.new(.A).appendChild(Node.new(.B)) var CDE = Node.new(.C).appendChild(Node.new(.D)).appendChild(Node.new(.E)) AB.insertChildrenAfterFrom(CDE, null) expect(verify(AB), "[A, [D], [E], [B]]") expect(verify(CDE), "[C]") }) test("insertChildrenAfterFrom 3", expect => { var AB = Node.new(.A).appendChild(Node.new(.B)) var CDE = Node.new(.C).appendChild(Node.new(.D)).appendChild(Node.new(.E)) AB.insertChildrenAfterFrom(CDE, AB.firstChild) expect(verify(AB), "[A, [B], [D], [E]]") expect(verify(CDE), "[C]") }) ################################################################################ # remove test("remove 1", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) var B = ABC.firstChild.remove expect(verify(B), "[B]") expect(verify(ABC), "[A, [C]]") }) test("remove 2", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) var C = ABC.lastChild.remove expect(verify(C), "[C]") expect(verify(ABC), "[A, [B]]") }) ################################################################################ # removeChildren test("removeChildren 1", expect => { var A = Node.new(.A) A.removeChildren expect(verify(A), "[A]") }) test("removeChildren 2", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) ABC.removeChildren expect(verify(ABC), "[A]") }) ################################################################################ # replaceWith test("replaceWith 1", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) var D = Node.new(.D) var B = ABC.firstChild.replaceWith(D) expect(verify(ABC), "[A, [D], [C]]") expect(verify(D), "[D]") expect(verify(B), "[B]") }) test("replaceWith 2", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) var D = Node.new(.D) var C = ABC.lastChild.replaceWith(D) expect(verify(ABC), "[A, [B], [D]]") expect(verify(D), "[D]") expect(verify(C), "[C]") }) test("replaceWith 3", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) var DEF = Node.new(.D).appendChild(Node.new(.E)).appendChild(Node.new(.F)) var B = ABC.firstChild.replaceWith(DEF) expect(verify(ABC), "[A, [D, [E], [F]], [C]]") expect(verify(DEF), "[D, [E], [F]]") expect(verify(B), "[B]") }) test("replaceWith 4", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) var DEF = Node.new(.D).appendChild(Node.new(.E)).appendChild(Node.new(.F)) var C = ABC.lastChild.replaceWith(DEF) expect(verify(ABC), "[A, [B], [D, [E], [F]]]") expect(verify(DEF), "[D, [E], [F]]") expect(verify(C), "[C]") }) ################################################################################ # replaceWithChildrenFrom test("replaceWithChildrenFrom 1", expect => { var ABCD = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).appendChild(Node.new(.D)) var EFG = Node.new(.E).appendChild(Node.new(.F)).appendChild(Node.new(.G)) var B = ABCD.firstChild.replaceWithChildrenFrom(EFG) expect(verify(ABCD), "[A, [F], [G], [C], [D]]") expect(verify(EFG), "[E]") expect(verify(B), "[B]") }) test("replaceWithChildrenFrom 2", expect => { var ABCD = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).appendChild(Node.new(.D)) var EFG = Node.new(.E).appendChild(Node.new(.F)).appendChild(Node.new(.G)) var C = ABCD.firstChild.nextSibling.replaceWithChildrenFrom(EFG) expect(verify(ABCD), "[A, [B], [F], [G], [D]]") expect(verify(EFG), "[E]") expect(verify(C), "[C]") }) test("replaceWithChildrenFrom 3", expect => { var ABCD = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).appendChild(Node.new(.D)) var EFG = Node.new(.E).appendChild(Node.new(.F)).appendChild(Node.new(.G)) var D = ABCD.lastChild.replaceWithChildrenFrom(EFG) expect(verify(ABCD), "[A, [B], [C], [F], [G]]") expect(verify(EFG), "[E]") expect(verify(D), "[D]") }) test("replaceWithChildrenFrom 4", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) var D = Node.new(.D) var B = ABC.firstChild.replaceWithChildrenFrom(D) expect(verify(ABC), "[A, [C]]") expect(verify(D), "[D]") expect(verify(B), "[B]") }) test("replaceWithChildrenFrom 5", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) var D = Node.new(.D) var C = ABC.lastChild.replaceWithChildrenFrom(D) expect(verify(ABC), "[A, [B]]") expect(verify(D), "[D]") expect(verify(C), "[C]") }) ################################################################################ # swapWith test("swapWith 1", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) ABC.firstChild.swapWith(ABC.lastChild) expect(verify(ABC), "[A, [C], [B]]") }) test("swapWith 2", expect => { var ABC = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)) ABC.lastChild.swapWith(ABC.firstChild) expect(verify(ABC), "[A, [C], [B]]") }) test("swapWith 3", expect => { var ABCD = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).appendChild(Node.new(.D)) ABCD.firstChild.swapWith(ABCD.lastChild) expect(verify(ABCD), "[A, [D], [C], [B]]") }) test("swapWith 4", expect => { var ABCD = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).appendChild(Node.new(.D)) ABCD.lastChild.swapWith(ABCD.firstChild) expect(verify(ABCD), "[A, [D], [C], [B]]") }) test("swapWith 5", expect => { var ABCD = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).appendChild(Node.new(.D)) ABCD.firstChild.swapWith(ABCD.firstChild.nextSibling) expect(verify(ABCD), "[A, [C], [B], [D]]") }) test("swapWith 6", expect => { var ABCD = Node.new(.A).appendChild(Node.new(.B)).appendChild(Node.new(.C)).appendChild(Node.new(.D)) ABCD.lastChild.swapWith(ABCD.lastChild.previousSibling) expect(verify(ABCD), "[A, [B], [D], [C]]") }) } } ================================================ FILE: tests/other.sk ================================================ namespace Skew.Tests { def testLevenshteinEditDistance { var check = (a string, b string, expected double) => { testExpect("caseAwareLevenshteinEditDistance(\(a), \(b))", => caseAwareLevenshteinEditDistance(a, b), expected) testExpect("caseAwareLevenshteinEditDistance(\(b), \(a))", => caseAwareLevenshteinEditDistance(b, a), expected) } check("", "", 0) check("x", "", 1) check("xy", "", 2) check("xyz", "", 3) check("x", "x", 0) check("xy", "x", 1) check("xyz", "x", 2) check("x", "z", 1) check("xy", "z", 2) check("xyz", "z", 2) check("xyz", "yz", 1) check("xyz", "xz", 1) check("xyz", "xy", 1) check("xyz", "Xyz", 0.5) check("xyz", "xYz", 0.5) check("xyz", "xyZ", 0.5) check("xyz", "Xyz", 0.5) check("xyz", "XYz", 1) check("xyz", "XYZ", 1.5) check("xyz", "1xyz", 1) check("xyz", "x1yz", 1) check("xyz", "xy1z", 1) check("xyz", "xyz1", 1) check("xxx", "x", 2) check("xxx", "xx", 1) check("xxx", "xxx", 0) check("xxx", "xxxx", 1) check("xxx", "xxxxx", 2) check("1xy2xy3xy4", "xyxyxy", 4) check("1xy2xy3xy4", "4xy3xy2xy1", 4) check("programming", "language", 9) check("testing", "(TeStInG)", 4) } def testQuoteReplacement { var check = (input string, expected string) => { testExpect("replaceSingleQuotesWithDoubleQuotes(\(quoteString(input, .DOUBLE, .NORMAL))) == \(quoteString(expected, .DOUBLE, .NORMAL))", => replaceSingleQuotesWithDoubleQuotes(input), expected) } check("''", "\"\"") check("'abc'", "\"abc\"") # Translate quotes check("'abc\\'xyz'", "\"abc'xyz\"") check("'abc\"xyz'", "\"abc\\\"xyz\"") check("'abc\\\"xyz'", "\"abc\\\"xyz\"") # Don't translate other escape sequences check("'abc\nxyz'", "\"abc\nxyz\"") check("'abc\\nxyz'", "\"abc\\nxyz\"") check("'abc\\x20xyz'", "\"abc\\x20xyz\"") } def testRanges { testExpect("Range('abc', 0, 3).toString", => Range.new(Source.new("", "abc"), 0, 3).toString, "abc") testExpect("Range('abc', 1, 2).toString", => Range.new(Source.new("", "abc"), 1, 2).toString, "b") testExpect("Range('abc', 0, 3).rangeIncludingLeftWhitespace.toString", => Range.new(Source.new("", "abc"), 0, 3).rangeIncludingLeftWhitespace.toString, "abc") testExpect("Range('abc', 0, 3).rangeIncludingRightWhitespace.toString", => Range.new(Source.new("", "abc"), 0, 3).rangeIncludingRightWhitespace.toString, "abc") testExpect("Range('abc', 0, 3).rangeIncludingLeftWhitespace.toString", => Range.new(Source.new("", "abc"), 0, 3).rangeIncludingLeftWhitespace.toString, "abc") testExpect("Range('abc', 1, 2).rangeIncludingRightWhitespace.toString", => Range.new(Source.new("", "abc"), 1, 2).rangeIncludingRightWhitespace.toString, "b") testExpect("Range(' abc ', 1, 4).rangeIncludingLeftWhitespace.toString", => Range.new(Source.new("", " abc "), 1, 4).rangeIncludingLeftWhitespace.toString, " abc") testExpect("Range(' abc ', 1, 4).rangeIncludingRightWhitespace.toString", => Range.new(Source.new("", " abc "), 1, 4).rangeIncludingRightWhitespace.toString, "abc ") testExpect("Range('[ abc ]', 2, 5).rangeIncludingLeftWhitespace.toString", => Range.new(Source.new("", "[ abc ]"), 2, 5).rangeIncludingLeftWhitespace.toString, " abc") testExpect("Range('[ abc ]', 2, 5).rangeIncludingRightWhitespace.toString", => Range.new(Source.new("", "[ abc ]"), 2, 5).rangeIncludingRightWhitespace.toString, "abc ") testExpect("Range('[ abc ]', 3, 6).rangeIncludingLeftWhitespace.toString", => Range.new(Source.new("", "[ abc ]"), 3, 6).rangeIncludingLeftWhitespace.toString, " abc") testExpect("Range('[ abc ]', 3, 6).rangeIncludingRightWhitespace.toString", => Range.new(Source.new("", "[ abc ]"), 3, 6).rangeIncludingRightWhitespace.toString, "abc ") } class Foo {} class Bar : Foo {} class Baz : Bar {} def testRuntime { testExpect("Foo.new is Foo", => Foo.new is Foo, true) testExpect("Bar.new is Bar", => Bar.new is Bar, true) testExpect("Foo.new is Bar", => Foo.new is Bar, false) testExpect("Bar.new is Foo", => Bar.new is Foo, true) testExpect("Bar.new is Baz", => Bar.new is Baz, false) } } ================================================ FILE: tests/parsing.sk ================================================ namespace Skew.Tests { def testParsing { test(" var x = '' var y = '1' var z = '12' ", " :1:9: error: Use double quotes for strings (single quotes are for character literals) var x = '' ~~ :3:9: error: Use double quotes for strings (single quotes are for character literals) var z = '12' ~~~~ :1:9: fix: Replace single quotes with double quotes var x = '' ~~ [\"\"] :3:9: fix: Replace single quotes with double quotes var z = '12' ~~~~ [\"12\"] ") test(" var foo = [ 0x7FFFFFFF, -0x7FFFFFFF, 0x80000000, -0x80000000, 0xFFFFFFFF, -0xFFFFFFFF, 0x100000000, -0x100000000, 0xFFFFFFFFFFFFFFFF, -0xFFFFFFFFFFFFFFFF, ] var bar = [ 2147483647, -2147483647, 2147483648, -2147483648, 2147483648, -2147483648, 4294967295, -4294967295, 4294967296, -4294967296, 12345678901234567890, -12345678901234567890, ] ", " :8:3: error: Integer literal is too big to fit in 32 bits 0x100000000, ~~~~~~~~~~~ :9:4: error: Integer literal is too big to fit in 32 bits -0x100000000, ~~~~~~~~~~~ :10:3: error: Integer literal is too big to fit in 32 bits 0xFFFFFFFFFFFFFFFF, ~~~~~~~~~~~~~~~~~~ :11:4: error: Integer literal is too big to fit in 32 bits -0xFFFFFFFFFFFFFFFF, ~~~~~~~~~~~~~~~~~~ :22:3: error: Integer literal is too big to fit in 32 bits 4294967296, ~~~~~~~~~~ :23:4: error: Integer literal is too big to fit in 32 bits -4294967296, ~~~~~~~~~~ :24:3: error: Integer literal is too big to fit in 32 bits 12345678901234567890, ~~~~~~~~~~~~~~~~~~~~ :25:4: error: Integer literal is too big to fit in 32 bits -12345678901234567890, ~~~~~~~~~~~~~~~~~~~~ ") test(" def foo { 0; var x = 0; ; if true {}; return; } ", " :2:4: error: Expected newline but found \";\" 0; ^ :3:12: error: Expected newline but found \";\" var x = 0; ^ :4:3: error: Unexpected \";\" ; ^ :5:13: error: Expected newline but found \";\" if true {}; ^ :6:9: error: Expected newline but found \";\" return; ^ :2:3: warning: Unused expression 0; ^ :3:7: warning: Local variable \"x\" is never read var x = 0; ^ :2:4: fix: Remove \";\" 0; ^ [] :3:12: fix: Remove \";\" var x = 0; ^ [] :5:13: fix: Remove \";\" if true {}; ^ [] :6:9: fix: Remove \";\" return; ^ [] ") test(" var x = 0; var y = 0; ", " :1:10: error: Expected newline but found \";\" var x = 0; ^ :2:10: error: Expected newline but found \";\" var y = 0; ^ :1:10: fix: Remove \";\" var x = 0; ^ [] :2:10: fix: Remove \";\" var y = 0; ^ [] ") test(" def main { while true {} else {} } ", " :2:17: error: Unexpected \"else\" while true {} else {} ~~~~ ") test(" var x = 0b2 ", " :1:10: error: Expected newline but found identifier var x = 0b2 ~~ ") test(" var x = 0b02 ", " :1:12: error: Expected newline but found integer var x = 0b02 ^ ") test(" var x = 0o8 ", " :1:10: error: Expected newline but found identifier var x = 0o8 ~~ ") test(" var x = 0o08 ", " :1:12: error: Expected newline but found integer var x = 0o08 ^ ") test(" var x = 0xG ", " :1:10: error: Expected newline but found identifier var x = 0xG ~~ ") test(" var x = 0x0G ", " :1:12: error: Expected newline but found identifier var x = 0x0G ^ ") test(" var ns.foo int ", " :1:7: error: Expected newline but found \".\" var ns.foo int ^ :1:5: error: The implicitly typed variable \"ns\" must be initialized var ns.foo int ~~ ") test(" def ns.foo int ", " :1:7: error: Expected newline but found \".\" def ns.foo int ^ :1:5: error: Non-imported function \"ns\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) def ns.foo int ~~ ") test(" def main { var foo = dynamic } ", " :2:20: error: Expected \".\" but found newline var foo = dynamic ^ :2:7: warning: Local variable \"foo\" is never read var foo = dynamic ~~~ ") # Test splitting the ">>" token test(" var x List> ", " :1:16: error: Expected newline but found \">\" var x List> ^ ") # Test splitting the ">=" token test(" var x List== ", " :1:17: error: Unexpected \"=\" var x List== ^ ") # Test splitting the ">>>" token test(" var x List>> ", " :1:16: error: Expected newline but found \">>\" var x List>> ~~ ") # Test splitting the ">>=" token test(" var x List>= ", " :1:16: error: Expected newline but found \">=\" var x List>= ~~ ") # Test splitting the ">>>=" token test(" var x List>>= ", " :1:16: error: Expected newline but found \">>=\" var x List>>= ~~~ ") # Test a string interpolation error case test(" var x = \"\\()\" ", " :1:12: error: Unexpected string interpolation var x = \"\\()\" ~~ ") # Test a string interpolation error case test(" var x = \"\\(1)\\()\" ", " :1:16: error: Unexpected string interpolation var x = \"\\(1)\\()\" ~~ ") # Test a string interpolation error case test(" var x = \"\\([)\\(])\" ", " :1:14: error: Syntax error \"\\\" var x = \"\\([)\\(])\" ^ :1:13: error: Unexpected \")\" var x = \"\\([)\\(])\" ^ ") # Test a string interpolation error case test(" var x = \"\\({)\\(})\" ", " :1:14: error: Syntax error \"\\\" var x = \"\\({)\\(})\" ^ :1:13: error: Unexpected \")\" var x = \"\\({)\\(})\" ^ ") # Test a string interpolation error case test(" var x = \"\\(()\\())\" ", " :1:14: error: Syntax error \"\\\" var x = \"\\(()\\())\" ^ :1:15: error: Expected \"=>\" but found end of input var x = \"\\(()\\())\" ^ ") # Test XML tag mismatch test(" var foo = ", " :1:18: error: Expected \"Foo\" but found \"Foo.Bar\" in XML literal var foo = ~~~~~~~ :1:12: note: Attempted to match opening tag here var foo = ~~~ :1:12: error: \"Foo\" is not declared var foo = ~~~ ") # Test XML literals test(" var foo = ", " :1:12: error: Expected identifier but found \"dynamic\" var foo = ~~~~~~~ ") # Test XML attribute precedence test(" var foo = ", " :6:11: error: Expected \">\" but found \"+\" foo=bar + bar ^ ") # Test parsing XML being typed test(" class Foo { var foo Foo = < def <>...(x Foo) {} } ", " :2:22: error: Unexpected \"<<\" var foo Foo = < ~~ :2:24: error: Expected newline but found \"/\" var foo Foo = < ^ :2:25: error: Expected newline but found identifier var foo Foo = < ~~~ :2:28: error: Expected newline but found \">\" var foo Foo = < ^ :3:7: error: Expected newline but found \"<>...\" def <>...(x Foo) {} ~~~~~~~~ :3:15: error: Expected newline but found \"(\" def <>...(x Foo) {} ^ :3:23: error: Expected \"=>\" but found \"{\" def <>...(x Foo) {} ^ :4:1: error: Expected \":3:3: error: Unexpected \"var\" var x = 0 ~~~ :5:3: error: Unexpected \"const\" const y = 0 ~~~~~ :7:3: error: Unexpected \"while\" while true {} ~~~~~ :9:3: error: Unexpected \"for\" for i in 0..5 {} ~~~ :11:3: error: Unexpected \"if\" if true {} ~~ :13:3: error: Unexpected \"else\" else {} ~~~~ :15:3: error: Unexpected \"return\" return ~~~~~~ :17:3: error: Unexpected \"break\" break ~~~~~ :19:3: error: Unexpected \"continue\" continue ~~~~~~~~ :21:3: error: Unexpected \"try\" try {} ~~~ :23:3: error: Unexpected \"catch\" catch e dynamic {} ~~~~~ :25:3: error: Unexpected \"finally\" finally {} ~~~~~~~ :2:3: error: \"foo\" is not declared foo( ~~~ :4:3: error: \"foo\" is not declared foo( ~~~ :6:3: error: \"foo\" is not declared foo( ~~~ :8:3: error: \"foo\" is not declared foo( ~~~ :10:3: error: \"foo\" is not declared foo( ~~~ :12:3: error: \"foo\" is not declared foo( ~~~ :14:3: error: \"foo\" is not declared foo( ~~~ :16:3: error: \"foo\" is not declared foo( ~~~ :17:3: error: Cannot use \"break\" outside a loop break ~~~~~ :18:3: error: \"foo\" is not declared foo( ~~~ :19:3: error: Cannot use \"continue\" outside a loop continue ~~~~~~~~ :20:3: error: \"foo\" is not declared foo( ~~~ :22:3: error: \"foo\" is not declared foo( ~~~ :24:3: error: \"foo\" is not declared foo( ~~~ :3:7: warning: Local variable \"x\" is never read var x = 0 ^ :5:9: warning: Local variable \"y\" is never read const y = 0 ^ ") # Test parsing recovery after "var" and "const" test(" def test { var var x = 0 const const y = 0 } ", " :2:6: error: Expected identifier but found newline var ^ :4:8: error: Expected identifier but found newline const ^ :3:7: warning: Local variable \"x\" is never read var x = 0 ^ :5:9: warning: Local variable \"y\" is never read const y = 0 ^ ") # Test partial statement presence during parsing recovery test(" @export def test { var x = [ var y = x # This should not be a reference error about \"x\" } ", " :4:3: error: Unexpected \"var\" var y = x # This should not be a reference error about \"x\" ~~~ :4:7: warning: Local variable \"y\" is never read var y = x # This should not be a reference error about \"x\" ^ ") # This should not infinite loop test(" def test { f( } ", " :3:1: error: Unexpected \"}\" } ^ :2:3: error: \"f\" is not declared f( ^ ") # Check for a special error message when attempting to use C-style variable declarations (complex cases aren't handled but are still tested) test(" def test { # Bad int a = 0 List b = [] fn(int) int c = x => x dynamic d = null # Good var w int = 0 var x List = [] var y fn(int) int = x => x var z dynamic = null } ", " :3:3: error: Declare variables using \"var\" and put the type after the variable name int a = 0 ~~~ :4:3: error: Declare variables using \"var\" and put the type after the variable name List b = [] ~~~~~~~~~ :5:11: error: Expected newline but found identifier fn(int) int c = x => x ~~~ :5:11: error: Declare variables using \"var\" and put the type after the variable name fn(int) int c = x => x ~~~ :6:11: error: Expected \".\" but found identifier dynamic d = null ^ :5:3: error: \"fn\" is not declared fn(int) int c = x => x ~~ :5:6: error: Unexpected type \"int\" fn(int) int c = x => x ~~~ :5:19: error: Unable to determine the type of \"x\" fn(int) int c = x => x ^ :5:19: error: Cannot convert from type \"fn(dynamic) dynamic\" to type \"int\" fn(int) int c = x => x ~~~~~~ :6:11: error: \"d\" is not declared dynamic d = null ^ :3:7: warning: Local variable \"a\" is never read int a = 0 ^ :4:13: warning: Local variable \"b\" is never read List b = [] ^ :5:15: warning: Local variable \"c\" is never read fn(int) int c = x => x ^ :9:7: warning: Local variable \"w\" is never read var w int = 0 ^ :10:7: warning: Local variable \"x\" is never read var x List = [] ^ :11:7: warning: Local variable \"y\" is never read var y fn(int) int = x => x ^ :12:7: warning: Local variable \"z\" is never read var z dynamic = null ^ :3:3: fix: Declare \"a\" correctly int a = 0 ~~~~~ [var a int] :4:3: fix: Declare \"b\" correctly List b = [] ~~~~~~~~~~~ [var b List] :5:11: fix: Declare \"c\" correctly fn(int) int c = x => x ~~~~~ [var c int] ") # Check for issues with parsing type parameters and string interpolation test(" var a = \"\\(x > y)\" var b = \"\\(x < y)\" var c = \"\\(x <= y)\" var d = \"\\(x >= y)\" var e = \"\\(x <=> y)\" ", " :1:12: error: \"x\" is not declared var a = \"\\(x > y)\" ^ :1:16: error: \"y\" is not declared var a = \"\\(x > y)\" ^ :2:12: error: \"x\" is not declared var b = \"\\(x < y)\" ^ :2:16: error: \"y\" is not declared var b = \"\\(x < y)\" ^ :3:12: error: \"x\" is not declared var c = \"\\(x <= y)\" ^ :3:17: error: \"y\" is not declared var c = \"\\(x <= y)\" ^ :4:12: error: \"x\" is not declared var d = \"\\(x >= y)\" ^ :4:17: error: \"y\" is not declared var d = \"\\(x >= y)\" ^ :5:12: error: \"x\" is not declared var e = \"\\(x <=> y)\" ^ :5:18: error: \"y\" is not declared var e = \"\\(x <=> y)\" ^ ") # Check that comma separated variables are parsed correctly test(" var a int, b int var c = 1, d = 2 const e = 3, f = 4 class Foo { var a int, b int var c = 1, d = 2 const e = 3, f = 4 } def test { var a int, b int var c = 1, d = 2 const e = 3, f = 4 } ", " :12:7: warning: Local variable \"a\" is never read var a int, b int ^ :12:14: warning: Local variable \"b\" is never read var a int, b int ^ :13:7: warning: Local variable \"c\" is never read var c = 1, d = 2 ^ :13:14: warning: Local variable \"d\" is never read var c = 1, d = 2 ^ :14:9: warning: Local variable \"e\" is never read const e = 3, f = 4 ^ :14:16: warning: Local variable \"f\" is never read const e = 3, f = 4 ^ ") # Check that using braces on the next line works test(" class Foo : int { def test { if true { } else if false { } else { } while true { } for x = 0; x < 1; x++ { } for x in 1..2 { } switch 0 { case 0 { } case 1, 2 { } default { } } try { } catch e dynamic { } finally { } => { } } } ", " :1:13: error: Cannot extend type \"int\" class Foo : int ~~~ :57:5: error: Cannot infer a type for this literal { ^ :56:5: warning: Unused expression => ~~ ") # Check for a crash due to a type comparison on a dot expression with an implicit target test(" def test { switch 0 { case .X { case .Y {} } } ", " :4:5: error: Unexpected \"case\" case .Y {} ~~~~ :4:10: error: Expected newline but found \".\" case .Y {} ^ :4:13: error: Expected newline but found \"{\" case .Y {} ^ :6:2: error: Expected newline but found end of input } ^ ") # Allow commas between enums and allow enums between other declarations test(" enum Foo { def x {} A, B X, Y def y {} } ", " ") # Forbid invalid enum names test(" enum Foo { def, A } ", " :2:6: error: Expected identifier but found \",\" def, A ^ ") # Forbid invalid enum names test(" enum Foo { A, def } ", " :2:6: error: Expected newline but found identifier A, def ~~~ ") # Warn about extra commas test(" enum Foo { A, B, X, Y, } enum Bar { A, B, } ", " :2:7: warning: Unnecessary comma A, B, ^ :3:7: warning: Unnecessary comma X, Y, ^ :5:16: warning: Unnecessary comma enum Bar { A, B, } ^ :2:7: fix: Remove comma A, B, ^ [] :3:7: fix: Remove comma X, Y, ^ [] :5:16: fix: Remove comma enum Bar { A, B, } ^ [] ") # Test comment removal inside expressions test(" def test { 0 .foo 0 # x .foo 0 # x .foo 0 # x # x .foo true ? 0 : 0 true # x ? 0 # x : 0 true # x ? 0 # x : 0 true # x # x ? 0 # x # x : 0 foo( 0 , 1 ) foo( 0 # x , 1 # x ) foo( 0 # x , 1 # x ) foo( 0 # x # x , 1 # x # x ) [ null , 1 ] [ null # x , 1 # x ] [ null # x , 1 # x ] [ null # x # x , 1 # x # x ] } ", " :3:6: error: \"foo\" is not declared on type \"int\" .foo ~~~ :6:6: error: \"foo\" is not declared on type \"int\" .foo ~~~ :10:6: error: \"foo\" is not declared on type \"int\" .foo ~~~ :14:6: error: \"foo\" is not declared on type \"int\" .foo ~~~ :17:7: warning: Both sides of \":\" are identical, is this a bug? ? 0 ^ :17:7: warning: Unused expression ? 0 ^ :18:7: warning: Unused expression : 0 ^ :21:7: warning: Both sides of \":\" are identical, is this a bug? ? 0 # x ~~~~~ :21:7: warning: Unused expression ? 0 # x ^ :22:7: warning: Unused expression : 0 ^ :26:7: warning: Both sides of \":\" are identical, is this a bug? ? 0 ^ :26:7: warning: Unused expression ? 0 ^ :28:7: warning: Unused expression : 0 ^ :32:7: warning: Both sides of \":\" are identical, is this a bug? ? 0 # x ~~~~~ :32:7: warning: Unused expression ? 0 # x ^ :34:7: warning: Unused expression : 0 ^ :36:3: error: \"foo\" is not declared foo( ~~~ :41:3: error: \"foo\" is not declared foo( ~~~ :46:3: error: \"foo\" is not declared foo( ~~~ :53:3: error: \"foo\" is not declared foo( ~~~ :62:7: error: No common type for \"null\" and \"int\" , 1 ^ :60:3: warning: Unused expression [ ^ :67:7: error: No common type for \"null\" and \"int\" , 1 # x ^ :65:3: warning: Unused expression [ ^ :73:7: error: No common type for \"null\" and \"int\" , 1 ^ :70:3: warning: Unused expression [ ^ :80:7: error: No common type for \"null\" and \"int\" , 1 # x ^ :77:3: warning: Unused expression [ ^ ") # Check comment corrections test(" var x int = false //this is a test var y int = false // this is also a test ////////// ", " :1:19: error: Comments start with \"#\" instead of \"//\" var x int = false //this is a test ~~~~~~~~~~~~~~~~ :2:19: error: Comments start with \"#\" instead of \"//\" var y int = false // this is also a test ~~~~~~~~~~~~~~~~~~~~~~ :3:1: error: Comments start with \"#\" instead of \"//\" ////////// ~~~~~~~~~~ :1:13: error: Cannot convert from type \"bool\" to type \"int\" without a cast var x int = false //this is a test ~~~~~ :2:13: error: Cannot convert from type \"bool\" to type \"int\" without a cast var y int = false // this is also a test ~~~~~ :1:19: fix: Replace \"//\" with \"#\" var x int = false //this is a test ~~~~~~~~~~~~~~~~ [#this is a test] :2:19: fix: Replace \"//\" with \"#\" var y int = false // this is also a test ~~~~~~~~~~~~~~~~~~~~~~ [# this is also a test] :3:1: fix: Replace \"//\" with \"#\" ////////// ~~~~~~~~~~ [#########] ") # Check == and != corrections test(" var x = 0 === 1 var y = 0 !== 1 ", " :1:11: error: Use the \"==\" operator instead var x = 0 === 1 ~~~ :2:11: error: Use the \"!=\" operator instead var y = 0 !== 1 ~~~ :1:11: fix: Replace with \"==\" var x = 0 === 1 ~~~ [==] :2:11: fix: Replace with \"!=\" var y = 0 !== 1 ~~~ [!=] ") # Check List corrections test(" var a int[] = [] var b Foo.Bar.Baz[] = [] var c Foo[] = [] var d fn() int[] = null ", " :1:7: error: The array type is \"List\" var a int[] = [] ~~~~~ :2:7: error: The array type is \"List\" var b Foo.Bar.Baz[] = [] ~~~~~~~~~~~~~ :3:7: error: The array type is \"List\" var c Foo[] = [] ~~~~~~~~~~~~~~~~~~ :4:12: error: The array type is \"List\" var d fn() int[] = null ~~~~~ :1:7: fix: Replace with \"List\" var a int[] = [] ~~~~~ [List] :2:7: fix: Replace with \"List\" var b Foo.Bar.Baz[] = [] ~~~~~~~~~~~~~ [List] :3:7: fix: Replace with \"List>\" var c Foo[] = [] ~~~~~~~~~~~~~~~~~~ [List>] :4:12: fix: Replace with \"List\" var d fn() int[] = null ~~~~~ [List] ") # Check parentheses corrections on statements test(" def test { while (true) {} for (i in 0..5) {} for (i in [1, 2, 3]) {} for (i = 0; i < 10; i++) {} for (var i = 0; i < 10; i++) {} for var i = 0; i < 10; i++ {} if (true) {} else if (true) {} while(true) {} for(i in 0..5) {} for(i in [1, 2, 3]) {} for(i = 0; i < 10; i++) {} for(var i = 0; i < 10; i++) {} if(true) {} else if(true) {} } ", " :2:9: warning: Unnecessary parentheses while (true) {} ~~~~~~ :3:7: warning: Unnecessary parentheses for (i in 0..5) {} ~~~~~~~~~~~ :4:7: warning: Unnecessary parentheses for (i in [1, 2, 3]) {} ~~~~~~~~~~~~~~~~ :5:7: warning: Unnecessary parentheses for (i = 0; i < 10; i++) {} ~~~~~~~~~~~~~~~~~~~~ :6:8: error: The \"var\" keyword is unnecessary here since for loops automatically declare their variables for (var i = 0; i < 10; i++) {} ~~~ :6:7: warning: Unnecessary parentheses for (var i = 0; i < 10; i++) {} ~~~~~~~~~~~~~~~~~~~~~~~~ :7:7: error: The \"var\" keyword is unnecessary here since for loops automatically declare their variables for var i = 0; i < 10; i++ {} ~~~ :8:6: warning: Unnecessary parentheses if (true) {} else if (true) {} ~~~~~~ :8:24: warning: Unnecessary parentheses if (true) {} else if (true) {} ~~~~~~ :10:8: warning: Unnecessary parentheses while(true) {} ~~~~~~ :11:6: warning: Unnecessary parentheses for(i in 0..5) {} ~~~~~~~~~~~ :12:6: warning: Unnecessary parentheses for(i in [1, 2, 3]) {} ~~~~~~~~~~~~~~~~ :13:6: warning: Unnecessary parentheses for(i = 0; i < 10; i++) {} ~~~~~~~~~~~~~~~~~~~~ :14:7: error: The \"var\" keyword is unnecessary here since for loops automatically declare their variables for(var i = 0; i < 10; i++) {} ~~~ :14:6: warning: Unnecessary parentheses for(var i = 0; i < 10; i++) {} ~~~~~~~~~~~~~~~~~~~~~~~~ :15:5: warning: Unnecessary parentheses if(true) {} else if(true) {} ~~~~~~ :15:22: warning: Unnecessary parentheses if(true) {} else if(true) {} ~~~~~~ :2:9: fix: Remove parentheses while (true) {} ~~~~~~ [true] :3:7: fix: Remove parentheses for (i in 0..5) {} ~~~~~~~~~~~ [i in 0..5] :4:7: fix: Remove parentheses for (i in [1, 2, 3]) {} ~~~~~~~~~~~~~~~~ [i in [1, 2, 3]] :5:7: fix: Remove parentheses for (i = 0; i < 10; i++) {} ~~~~~~~~~~~~~~~~~~~~ [i = 0; i < 10; i++] :6:8: fix: Remove \"var\" for (var i = 0; i < 10; i++) {} ~~~~ [] :6:7: fix: Remove parentheses for (var i = 0; i < 10; i++) {} ~~~~~~~~~~~~~~~~~~~~~~~~ [var i = 0; i < 10; i++] :7:7: fix: Remove \"var\" for var i = 0; i < 10; i++ {} ~~~~ [] :8:6: fix: Remove parentheses if (true) {} else if (true) {} ~~~~~~ [true] :8:24: fix: Remove parentheses if (true) {} else if (true) {} ~~~~~~ [true] :10:8: fix: Remove parentheses while(true) {} ~~~~~~ [ true] :11:6: fix: Remove parentheses for(i in 0..5) {} ~~~~~~~~~~~ [ i in 0..5] :12:6: fix: Remove parentheses for(i in [1, 2, 3]) {} ~~~~~~~~~~~~~~~~ [ i in [1, 2, 3]] :13:6: fix: Remove parentheses for(i = 0; i < 10; i++) {} ~~~~~~~~~~~~~~~~~~~~ [ i = 0; i < 10; i++] :14:7: fix: Remove \"var\" for(var i = 0; i < 10; i++) {} ~~~~ [] :14:6: fix: Remove parentheses for(var i = 0; i < 10; i++) {} ~~~~~~~~~~~~~~~~~~~~~~~~ [ var i = 0; i < 10; i++] :15:5: fix: Remove parentheses if(true) {} else if(true) {} ~~~~~~ [ true] :15:22: fix: Remove parentheses if(true) {} else if(true) {} ~~~~~~ [ true] ") # Check for colon-before-type corrections test(" var a: int, b: int def c(d: int, e: int): int { var f: int, g: int var h = (i: int, j: int) => {} } ", " :1:6: error: Do not use a colon before a type expression var a: int, b: int ^ :1:14: error: Do not use a colon before a type expression var a: int, b: int ^ :3:8: error: Do not use a colon before a type expression def c(d: int, e: int): int { ^ :3:16: error: Do not use a colon before a type expression def c(d: int, e: int): int { ^ :3:22: error: Do not use a colon before a type expression def c(d: int, e: int): int { ^ :4:8: error: Do not use a colon before a type expression var f: int, g: int ^ :4:16: error: Do not use a colon before a type expression var f: int, g: int ^ :5:13: error: Do not use a colon before a type expression var h = (i: int, j: int) => {} ^ :5:21: error: Do not use a colon before a type expression var h = (i: int, j: int) => {} ^ :3:5: error: All control paths for \"c\" must return a value of type \"int\" def c(d: int, e: int): int { ^ :4:7: warning: Local variable \"f\" is never read var f: int, g: int ^ :4:15: warning: Local variable \"g\" is never read var f: int, g: int ^ :5:7: warning: Local variable \"h\" is never read var h = (i: int, j: int) => {} ^ :1:6: fix: Remove the colon var a: int, b: int ^ [] :1:14: fix: Remove the colon var a: int, b: int ^ [] :3:8: fix: Remove the colon def c(d: int, e: int): int { ^ [] :3:16: fix: Remove the colon def c(d: int, e: int): int { ^ [] :3:22: fix: Remove the colon def c(d: int, e: int): int { ^ [] :4:8: fix: Remove the colon var f: int, g: int ^ [] :4:16: fix: Remove the colon var f: int, g: int ^ [] :5:13: fix: Remove the colon var h = (i: int, j: int) => {} ^ [] :5:21: fix: Remove the colon var h = (i: int, j: int) => {} ^ [] ") # Check for colon-before-type corrections test(" def test { var a = new Foo var b = new Foo().foo var c = new Foo.Bar.Baz().foo } ", " :2:11: error: There is no \"new\" operator, use \"Foo.new\" instead var a = new Foo ~~~~~~~ :3:11: error: There is no \"new\" operator, use \"Foo.new\" instead var b = new Foo().foo ~~~~~~~ :4:11: error: There is no \"new\" operator, use \"Foo.Bar.Baz.new\" instead var c = new Foo.Bar.Baz().foo ~~~~~~~~~~~~~~~~~~ :2:7: warning: Local variable \"a\" is never read var a = new Foo ^ :3:7: warning: Local variable \"b\" is never read var b = new Foo().foo ^ :4:7: warning: Local variable \"c\" is never read var c = new Foo.Bar.Baz().foo ^ :2:11: fix: Replace with \"Foo.new\" var a = new Foo ~~~~~~~ [Foo.new] :3:11: fix: Replace with \"Foo.new\" var b = new Foo().foo ~~~~~~~ [Foo.new] :4:11: fix: Replace with \"Foo.Bar.Baz.new\" var c = new Foo.Bar.Baz().foo ~~~~~~~~~~~~~~~~~~ [Foo.Bar.Baz.new] ") # Check for Java-style class keyword corrections test(" class Foo extends Bar implements Baz {} ", " :1:11: error: Use \":\" instead of \"extends\" to indicate a base class class Foo extends Bar implements Baz {} ~~~~~~~ :1:23: error: Use \"::\" instead of \"implements\" to indicate implemented interfaces class Foo extends Bar implements Baz {} ~~~~~~~~~~ :1:19: error: \"Bar\" is not declared class Foo extends Bar implements Baz {} ~~~ :1:34: error: \"Baz\" is not declared class Foo extends Bar implements Baz {} ~~~ :1:11: fix: Replace \"extends\" with \":\" class Foo extends Bar implements Baz {} ~~~~~~~ [:] :1:23: fix: Replace \"implements\" with \"::\" class Foo extends Bar implements Baz {} ~~~~~~~~~~ [::] ") # Check for TypeScript-style variable and function declarations test(" class Foo { a = 0 b: int c = 0 d() {} e(): int {} f() {} } ", " :2:3: error: Use \"var\" before variable declarations a = 0 ^ :3:3: error: Use \"var\" before variable declarations b: int ^ :3:4: error: Do not use a colon before a type expression b: int ^ :4:3: error: Use \"var\" before variable declarations c = 0 ^ :6:3: error: Use \"def\" before function declarations d() {} ^ :6:4: error: Functions without arguments do not use parentheses d() {} ~~ :7:3: error: Use \"def\" before function declarations e(): int {} ^ :7:4: error: Functions without arguments do not use parentheses e(): int {} ~~ :7:6: error: Do not use a colon before a type expression e(): int {} ^ :8:3: error: Use \"def\" before function declarations f() {} ^ :8:4: error: Functions without arguments do not use parentheses f() {} ~~ :7:3: error: All control paths for \"e\" must return a value of type \"int\" e(): int {} ^ :2:3: fix: Insert \"var\" a = 0 ^ [var a] :3:3: fix: Insert \"var\" b: int ^ [var b] :3:4: fix: Remove the colon b: int ^ [] :4:3: fix: Insert \"var\" c = 0 ^ [var c] :6:3: fix: Insert \"def\" d() {} ^ [def d] :6:4: fix: Remove parentheses d() {} ~~ [] :7:3: fix: Insert \"def\" e(): int {} ^ [def e] :7:4: fix: Remove parentheses e(): int {} ~~ [] :7:6: fix: Remove the colon e(): int {} ^ [] :8:3: fix: Insert \"def\" f() {} ^ [def f] :8:4: fix: Remove parentheses f() {} ~~ [] ") # Don't stop the whole parse if a function argument has invalid syntax test(" def x(a b c) {} def y(a b c) {} ", " :1:11: error: Expected \",\" but found identifier def x(a b c) {} ^ :2:11: error: Expected \",\" but found identifier def y(a b c) {} ^ :1:9: error: \"b\" is not declared def x(a b c) {} ^ :2:9: error: \"b\" is not declared def y(a b c) {} ^ ") # Skip over top-level identifiers test(" class Foo { nonsense var a = 0 static var b = 0 public c int = 0 private d: int = 0 protected e() {} var nonsense = 0 var static = 0 var public = 0 var private = 0 var protected = 0 } ", " :2:3: error: Unexpected identifier nonsense var a = 0 ~~~~~~~~ :3:3: error: There is no \"static\" keyword (declare this symbol in a namespace called \"Foo\" instead) static var b = 0 ~~~~~~ :4:3: error: There is no \"public\" keyword public c int = 0 ~~~~~~ :4:10: error: Unexpected identifier public c int = 0 ^ :4:12: error: Use \"var\" before variable declarations public c int = 0 ~~~ :5:3: error: There is no \"private\" keyword (to give something protected access, use a name starting with \"_\" instead) private d: int = 0 ~~~~~~~ :5:11: error: Use \"var\" before variable declarations private d: int = 0 ^ :5:12: error: Do not use a colon before a type expression private d: int = 0 ^ :6:3: error: There is no \"protected\" keyword (to give something protected access, use a name starting with \"_\" instead) protected e() {} ~~~~~~~~~ :6:13: error: Use \"def\" before function declarations protected e() {} ^ :6:14: error: Functions without arguments do not use parentheses protected e() {} ~~ :5:14: error: Cannot access instance member \"int\" from a global context private d: int = 0 ~~~ :5:14: error: Unexpected expression of type \"int\" private d: int = 0 ~~~ :4:12: fix: Insert \"var\" public c int = 0 ~~~ [var int] :5:11: fix: Insert \"var\" private d: int = 0 ^ [var d] :5:12: fix: Remove the colon private d: int = 0 ^ [] :6:13: fix: Insert \"def\" protected e() {} ^ [def e] :6:14: fix: Remove parentheses protected e() {} ~~ [] ") # Check for parser recovery on a C-style switch statement test(" def test { switch 0 { case 1: x = 0; y = 0; break; case 2: case 3: x = 0; y = 0; break; case 4: case 5: x = 0; y = 0; break; default: x = 0; y = 0; } } ", " :3:18: error: Expected newline but found \";\" case 1: x = 0; y = 0; break; ^ :3:25: error: Expected newline but found \";\" case 1: x = 0; y = 0; break; ^ :3:32: error: Expected newline but found \";\" case 1: x = 0; y = 0; break; ^ :3:11: error: Surround the body of case and default statements with \"{\" and \"}\" instead of \":\" and \"break\" case 1: x = 0; y = 0; break; ^ :4:11: error: Use a comma between multiple values in a case statement (example: \"case 1, 2, 3 { ... }\") case 2: case 3: x = 0; y = 0; break; ~~~~~~ :4:26: error: Expected newline but found \";\" case 2: case 3: x = 0; y = 0; break; ^ :4:33: error: Expected newline but found \";\" case 2: case 3: x = 0; y = 0; break; ^ :4:40: error: Expected newline but found \";\" case 2: case 3: x = 0; y = 0; break; ^ :4:19: error: Surround the body of case and default statements with \"{\" and \"}\" instead of \":\" and \"break\" case 2: case 3: x = 0; y = 0; break; ^ :5:11: error: Use a comma between multiple values in a case statement (example: \"case 1, 2, 3 { ... }\") case 4: ^ :7:12: error: Expected newline but found \";\" x = 0; ^ :8:12: error: Expected newline but found \";\" y = 0; ^ :9:12: error: Expected newline but found \";\" break; ^ :6:11: error: Surround the body of case and default statements with \"{\" and \"}\" instead of \":\" and \"break\" case 5: ^ :11:12: error: Expected newline but found \";\" x = 0; ^ :12:12: error: Expected newline but found \";\" y = 0; ^ :10:12: error: Surround the body of case and default statements with \"{\" and \"}\" instead of \":\" and \"break\" default: ^ :3:13: error: \"x\" is not declared case 1: x = 0; y = 0; break; ^ :3:20: error: \"y\" is not declared case 1: x = 0; y = 0; break; ^ :4:21: error: \"x\" is not declared case 2: case 3: x = 0; y = 0; break; ^ :4:28: error: \"y\" is not declared case 2: case 3: x = 0; y = 0; break; ^ :7:7: error: \"x\" is not declared x = 0; ^ :8:7: error: \"y\" is not declared y = 0; ^ :11:7: error: \"x\" is not declared x = 0; ^ :12:7: error: \"y\" is not declared y = 0; ^ :3:18: fix: Remove \";\" case 1: x = 0; y = 0; break; ^ [] :3:25: fix: Remove \";\" case 1: x = 0; y = 0; break; ^ [] :3:32: fix: Remove \";\" case 1: x = 0; y = 0; break; ^ [] :3:11: fix: Replace \":\" and \"break\" with \"{\" and \"}\" case 1: x = 0; y = 0; break; ~~~~~~~~~~~~~~~~~~~~~~ [ { x = 0; y = 0; }] :4:11: fix: Replace this with a comma case 2: case 3: x = 0; y = 0; break; ~~~~~~ [,] :4:26: fix: Remove \";\" case 2: case 3: x = 0; y = 0; break; ^ [] :4:33: fix: Remove \";\" case 2: case 3: x = 0; y = 0; break; ^ [] :4:40: fix: Remove \";\" case 2: case 3: x = 0; y = 0; break; ^ [] :4:19: fix: Replace \":\" and \"break\" with \"{\" and \"}\" case 2: case 3: x = 0; y = 0; break; ~~~~~~~~~~~~~~~~~~~~~~ [ { x = 0; y = 0; }] :5:11: fix: Replace this with a comma case 4: ^ [,] :7:12: fix: Remove \";\" x = 0; ^ [] :8:12: fix: Remove \";\" y = 0; ^ [] :9:12: fix: Remove \";\" break; ^ [] :6:11: fix: Replace \":\" and \"break\" with \"{\" and \"}\" case 5: ^ [ { x = 0; y = 0; }] :11:12: fix: Remove \";\" x = 0; ^ [] :12:12: fix: Remove \";\" y = 0; ^ [] ") # Check for an infinite loop due to a parse error test(" def test { switch 0 { case 1: blah; case 2: case 3: blah; } } ", " :3:17: error: Expected newline but found \";\" case 1: blah; ^ :3:11: error: Surround the body of case and default statements with \"{\" and \"}\" instead of \":\" and \"break\" case 1: blah; ^ :4:11: error: Use a comma between multiple values in a case statement (example: \"case 1, 2, 3 { ... }\") case 2: case 3: blah; ~~~~~~ :4:25: error: Expected newline but found \";\" case 2: case 3: blah; ^ :4:19: error: Surround the body of case and default statements with \"{\" and \"}\" instead of \":\" and \"break\" case 2: case 3: blah; ^ :3:13: error: \"blah\" is not declared case 1: blah; ~~~~ :4:21: error: \"blah\" is not declared case 2: case 3: blah; ~~~~ :3:17: fix: Remove \";\" case 1: blah; ^ [] :4:11: fix: Replace this with a comma case 2: case 3: blah; ~~~~~~ [,] :4:25: fix: Remove \";\" case 2: case 3: blah; ^ [] ") } } ================================================ FILE: tests/simple.sk ================================================ namespace Skew.Tests { def testSimple { test(" var foo Foo = null ", " :1:9: error: \"Foo\" is not declared var foo Foo = null ~~~ ") test(" def main List { return [ 0, 0b10, 0o76543210, 0xFEDBCA98, 0.5, 1e100, 1e+100, 1e-100, 0.5e100, 0.5e+100, 0.5e-100, 0.toString, 0.5.toString, 1e100.toString, 1e+100.toString, 1e-100.toString, 0.5e100.toString, 0.5e+100.toString, 0.5e-100.toString, 01, ] } ", " :22:5: warning: Number interpreted as decimal (use the prefix \"0o\" for octal numbers) 01, ~~ :14:5: error: Cannot convert from type \"string\" to type \"double\" 0.toString, ~~~~~~~~~~ :15:5: error: Cannot convert from type \"string\" to type \"double\" 0.5.toString, ~~~~~~~~~~~~ :16:5: error: Cannot convert from type \"string\" to type \"double\" 1e100.toString, ~~~~~~~~~~~~~~ :17:5: error: Cannot convert from type \"string\" to type \"double\" 1e+100.toString, ~~~~~~~~~~~~~~~ :18:5: error: Cannot convert from type \"string\" to type \"double\" 1e-100.toString, ~~~~~~~~~~~~~~~ :19:5: error: Cannot convert from type \"string\" to type \"double\" 0.5e100.toString, ~~~~~~~~~~~~~~~~ :20:5: error: Cannot convert from type \"string\" to type \"double\" 0.5e+100.toString, ~~~~~~~~~~~~~~~~~ :21:5: error: Cannot convert from type \"string\" to type \"double\" 0.5e-100.toString, ~~~~~~~~~~~~~~~~~ :22:5: fix: Remove the leading zeros to avoid confusion 01, ~~ [1] :22:5: fix: Add the prefix \"0o\" to interpret the number as octal 01, ~~ [0o1] ") test(" class Foo { def foo T } class Bar { def bar T } def main { var x Foo>> var a bool = x var b bool = x.foo var c bool = x.foo.bar var d bool = x.foo.bar.foo } ", " :11:16: error: Cannot convert from type \"Foo>>\" to type \"bool\" var a bool = x ^ :12:16: error: Cannot convert from type \"Bar>\" to type \"bool\" var b bool = x.foo ~~~~~ :13:16: error: Cannot convert from type \"Foo\" to type \"bool\" var c bool = x.foo.bar ~~~~~~~~~ :14:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast var d bool = x.foo.bar.foo ~~~~~~~~~~~~~ :11:7: warning: Local variable \"a\" is never read var a bool = x ^ :12:7: warning: Local variable \"b\" is never read var b bool = x.foo ^ :13:7: warning: Local variable \"c\" is never read var c bool = x.foo.bar ^ :14:7: warning: Local variable \"d\" is never read var d bool = x.foo.bar.foo ^ ") test(" class Foo { def new {} } namespace Foo { def new(x int) Foo { return new } } ", " ") test(" class Foo { def size int def pop T def shift T def push(value T) def unshift(value T) def map(callback fn(T) R) Foo def filter(callback fn(T) bool) Foo } def main { var x Foo x.push(x.size) x.filter(1.0) x.map(1.0) x.map(1.0) x.filter(x => x + 100) x.map(x => x + 100) var y int = x.map(1.0) } ", " :14:12: error: Cannot convert from type \"double\" to type \"fn(int) bool\" x.filter(1.0) ~~~ :15:3: error: Cannot use unparameterized type \"Foo.map\" here x.map(1.0) ~~~~~ :16:17: error: Cannot convert from type \"double\" to type \"fn(int) string\" x.map(1.0) ~~~ :17:17: error: Cannot convert from type \"int\" to type \"bool\" without a cast x.filter(x => x + 100) ~~~~~~~ :18:3: error: Cannot use unparameterized type \"Foo.map\" here x.map(x => x + 100) ~~~~~ :19:29: error: Cannot convert from type \"double\" to type \"fn(int) string\" var y int = x.map(1.0) ~~~ :19:15: error: Cannot convert from type \"Foo\" to type \"int\" var y int = x.map(1.0) ~~~~~~~~~~~~~~~~~~ :19:7: warning: Local variable \"y\" is never read var y int = x.map(1.0) ^ ") test(" class Foo { def new {} def foo(t T) Foo { return self } } def main { Foo.new.foo(0).foo(0.0) Foo.new.foo(0).foo(0.0) } ", " :7:3: error: Cannot use unparameterized type \"Foo.foo\" here Foo.new.foo(0).foo(0.0) ~~~~~~~~~~~ :8:21: error: Cannot convert from type \"int\" to type \"bool\" without a cast Foo.new.foo(0).foo(0.0) ^ :8:33: error: Cannot convert from type \"double\" to type \"int\" without a cast Foo.new.foo(0).foo(0.0) ~~~ ") test(" class Foo { def new {} def foo Foo { return self } def bar {} } def main { var x int = => Foo.new var y int = => Foo.new.foo var z int = => Foo.new.bar } ", " :8:15: error: Cannot convert from type \"fn() Foo\" to type \"int\" var x int = => Foo.new ~~~~~~~~~~ :9:15: error: Cannot convert from type \"fn() Foo\" to type \"int\" var y int = => Foo.new.foo ~~~~~~~~~~~~~~ :10:15: error: Cannot convert from type \"fn()\" to type \"int\" var z int = => Foo.new.bar ~~~~~~~~~~~~~~ :8:7: warning: Local variable \"x\" is never read var x int = => Foo.new ^ :9:7: warning: Local variable \"y\" is never read var y int = => Foo.new.foo ^ :10:7: warning: Local variable \"z\" is never read var z int = => Foo.new.bar ^ ") test(" def main(x int) { main x x + 1 x ? x : x } ", " :2:3: error: The function \"main\" takes 1 argument and must be called main ~~~~ :3:3: warning: Unused expression x ^ :4:3: warning: Unused expression x + 1 ~~~~~ :5:3: error: Cannot convert from type \"int\" to type \"bool\" without a cast x ? x : x ^ :5:7: warning: Both sides of \":\" are identical, is this a bug? x ? x : x ~~~~~ :5:7: warning: Unused expression x ? x : x ^ :5:11: warning: Unused expression x ? x : x ^ ") # Check unused expression warnings for lambdas that return nothing test(" def main { bar(x => foo(x)) bar(x => x + 1) bar(x => x) } def foo(x int) int { return 0 } def bar(x fn(int)) { } ", " :3:12: warning: Unused expression bar(x => x + 1) ~~~~~ :4:12: warning: Unused expression bar(x => x) ^ ") # Test return statement checks in the presence of the dynamic type test(" def main { x => x => {} => { return } => { return 0 } var a dynamic = x => x var b dynamic = => {} var c dynamic = => { return } var d dynamic = => { return 0 } var e fn() dynamic = x => x var f fn() dynamic = => {} var g fn() dynamic = => { return } var h fn() dynamic = => { return 0 } } def a dynamic {} def b dynamic { return } def c dynamic { return 0 } ", " :2:3: error: Unable to determine the type of \"x\" x => x ^ :2:3: warning: Unused expression x => x ~~~~~~ :3:3: warning: Unused expression => {} ~~~~~ :4:3: warning: Unused expression => { return } ~~~~~~~~~~~~~ :5:15: error: Cannot return a value inside a function without a return type => { return 0 } ^ :5:3: warning: Unused expression => { return 0 } ~~~~~~~~~~~~~~~ :10:24: error: Unable to determine the type of \"x\" var e fn() dynamic = x => x ^ :10:24: error: Cannot convert from type \"fn(dynamic) dynamic\" to type \"fn() dynamic\" var e fn() dynamic = x => x ~~~~~~ :11:24: error: All control paths for \"\" must return a value of type \"dynamic\" var f fn() dynamic = => {} ~~~~~ :12:29: error: Must return a value of type \"dynamic\" var g fn() dynamic = => { return } ~~~~~~ :16:5: error: All control paths for \"a\" must return a value of type \"dynamic\" def a dynamic {} ^ :17:17: error: Must return a value of type \"dynamic\" def b dynamic { return } ~~~~~~ :6:7: warning: Local variable \"a\" is never read var a dynamic = x => x ^ :7:7: warning: Local variable \"b\" is never read var b dynamic = => {} ^ :8:7: warning: Local variable \"c\" is never read var c dynamic = => { return } ^ :9:7: warning: Local variable \"d\" is never read var d dynamic = => { return 0 } ^ :10:7: warning: Local variable \"e\" is never read var e fn() dynamic = x => x ^ :11:7: warning: Local variable \"f\" is never read var f fn() dynamic = => {} ^ :12:7: warning: Local variable \"g\" is never read var g fn() dynamic = => { return } ^ :13:7: warning: Local variable \"h\" is never read var h fn() dynamic = => { return 0 } ^ ") test(" def main { var x = null var y = => null } ", " :2:7: error: Implicitly typed variables cannot be of type \"null\" var x = null ^ :3:14: error: Cannot create a function with a return type of \"null\" var y = => null ~~~~ :2:7: warning: Local variable \"x\" is never read var x = null ^ :3:7: warning: Local variable \"y\" is never read var y = => null ^ ") test(" def main { var a = => 0 var b = => {} var c = () => 0 var d = () => {} var e = () int => 0 var f = () int => { return 0 } var g fn(int) = x => 0 var h fn(int) = x => {} var i fn(int) int = (x) => 0 var j fn(int) int = (x) => { return 0 } var k = (x int) => 0 var l = (x int) => {} var m = (x int) int => 0 var n = (x int) int => { return 0 } } ", " :8:24: warning: Unused expression var g fn(int) = x => 0 ^ :2:7: warning: Local variable \"a\" is never read var a = => 0 ^ :3:7: warning: Local variable \"b\" is never read var b = => {} ^ :4:7: warning: Local variable \"c\" is never read var c = () => 0 ^ :5:7: warning: Local variable \"d\" is never read var d = () => {} ^ :6:7: warning: Local variable \"e\" is never read var e = () int => 0 ^ :7:7: warning: Local variable \"f\" is never read var f = () int => { return 0 } ^ :8:7: warning: Local variable \"g\" is never read var g fn(int) = x => 0 ^ :9:7: warning: Local variable \"h\" is never read var h fn(int) = x => {} ^ :10:7: warning: Local variable \"i\" is never read var i fn(int) int = (x) => 0 ^ :11:7: warning: Local variable \"j\" is never read var j fn(int) int = (x) => { return 0 } ^ :12:7: warning: Local variable \"k\" is never read var k = (x int) => 0 ^ :13:7: warning: Local variable \"l\" is never read var l = (x int) => {} ^ :14:7: warning: Local variable \"m\" is never read var m = (x int) int => 0 ^ :15:7: warning: Local variable \"n\" is never read var n = (x int) int => { return 0 } ^ ") test(" class Foo { def new {} } def main { var foo Foo = Foo.new var bar Foo = Foo.new } ", " :7:23: error: Cannot convert from type \"Foo\" to type \"Foo\" var bar Foo = Foo.new ~~~~~~~~~~~~ :6:7: warning: Local variable \"foo\" is never read var foo Foo = Foo.new ~~~ :7:7: warning: Local variable \"bar\" is never read var bar Foo = Foo.new ~~~ ") test(" class Foo { def in(x int) bool } def main(foo Foo) { false in foo 0 in foo } ", " :6:3: error: Cannot convert from type \"bool\" to type \"int\" without a cast false in foo ~~~~~ ") test(" namespace foo { namespace bar { def @baz } } @foo.bar.bax @foo.bar.baz def main {} ", " :7:10: error: \"@bax\" is not declared on type \"foo.bar\", did you mean \"@baz\"? @foo.bar.bax ~~~ :3:9: note: \"@baz\" is defined here def @baz ~~~~ :7:10: fix: Replace with \"@baz\" @foo.bar.bax ~~~ [@baz] ") test(" def @foo(x int) def @bar @foo @foo(1, 2) @bar(1) def main {} ", " :4:1: error: Expected 1 argument but found 0 arguments when calling \"@foo\" @foo ~~~~ :1:5: note: The function declaration is here def @foo(x int) ~~~~ :5:5: error: Expected 1 argument but found 2 arguments when calling \"@foo\" @foo(1, 2) ~~~~~~ :1:5: note: The function declaration is here def @foo(x int) ~~~~ :6:5: error: Cannot call the value returned from the function \"@bar\" (this function was called automatically because it takes no arguments) @bar(1) ~~~ :2:5: note: The function declaration is here def @bar ~~~~ ") # These shouldn't cause missing newline errors test(" def @foo class foo { def - def new {} } class bar { def - @foo def new {} } class baz { def - # foo def new {} } def main bool { return 1 - 1 } ", " :23:10: error: Cannot convert from type \"int\" to type \"bool\" without a cast return 1 - ~~~ ") test(" class Foo { def foo { var self int } def bar(self int) { } def baz { if true { var self int } } } ", " :3:9: error: \"self\" is already declared var self int ~~~~ :6:11: error: \"self\" is already declared def bar(self int) { ~~~~ :11:11: error: \"self\" shadows a previous declaration var self int ~~~~ :3:9: warning: Local variable \"self\" is never read var self int ~~~~ :11:11: warning: Local variable \"self\" is never read var self int ~~~~ ") test(" namespace a { class Foo.Bar {} class Foo.Baz {} class Foo {} def main(foo Foo.Bar, bar Foo.Baz) {} } namespace b { class Foo { class Bar {} class Baz {} } def main(foo Foo.Bar, bar Foo.Baz) {} } ", " ") test(" class ns1.Foo {} var ns1 int var ns2 int class ns2.Foo {} ", " :2:5: error: \"ns1\" is already declared var ns1 int ~~~ :1:7: note: The previous declaration is here class ns1.Foo {} ~~~ :3:5: error: \"ns2\" is already declared var ns2 int ~~~ :4:7: note: The previous declaration is here class ns2.Foo {} ~~~ ") test(" @skip var a int @skip { var x int var y int } @skip @skip def b {} @skip @skip { def c {} } ", " :10:1: warning: Duplicate annotation \"@skip\" on \"b\" @skip ~~~~~ :14:1: warning: Duplicate annotation \"@skip\" on \"c\" @skip { ~~~~~ :1:1: error: Cannot use the annotation \"@skip\" on \"a\" @skip ~~~~~ :4:1: error: Cannot use the annotation \"@skip\" on \"x\" @skip { ~~~~~ :4:1: error: Cannot use the annotation \"@skip\" on \"y\" @skip { ~~~~~ ") test(" class Foo { const a const b int const c = 0 const d bool = 0 } @import class Bar { const a const b int const c = 0 const d bool = 0 } const a const b int const c = 0 const d bool = 0 @import { const w const x int const y = 0 const z bool = 0 } ", " :2:9: error: Unable to determine the type of \"a\" const a ^ :5:18: error: Cannot convert from type \"int\" to type \"bool\" without a cast const d bool = 0 ^ :10:9: error: Unable to determine the type of \"a\" const a ^ :13:18: error: Cannot convert from type \"int\" to type \"bool\" without a cast const d bool = 0 ^ :16:7: error: Unable to determine the type of \"a\" const a ^ :17:7: error: The constant \"b\" must be initialized const b int ^ :19:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast const d bool = 0 ^ :22:9: error: Unable to determine the type of \"w\" const w ^ :25:18: error: Cannot convert from type \"int\" to type \"bool\" without a cast const z bool = 0 ^ ") test(" const a = 0 var b = 0 class Foo { const x = 0 } def main(foo Foo) { const y = 0 a = 0 b = 0 foo.x = 0 y = 0 } ", " :10:3: error: Cannot store to constant symbol \"a\" a = 0 ^ :12:7: error: Cannot store to constant symbol \"x\" foo.x = 0 ^ :13:3: error: Cannot store to constant symbol \"y\" y = 0 ^ :9:9: warning: Local variable \"y\" is never read const y = 0 ^ ") test(" class _Foo { var _x int var _bar _Bar def _foo { _x = 0 _foo var _y = => _x } class _Bar {} } def _main(_foo _Foo) { _foo._x = 0 _foo._foo var _x = => _foo._x var _y = => _foo._foo var _z _Foo._Bar } ", " :15:8: error: Cannot access protected symbol \"_x\" here _foo._x = 0 ~~ :16:8: error: Cannot access protected symbol \"_foo\" here _foo._foo ~~~~ :17:20: error: Cannot access protected symbol \"_x\" here var _x = => _foo._x ~~ :18:20: error: Cannot access protected symbol \"_foo\" here var _y = => _foo._foo ~~~~ :19:15: error: Cannot access protected symbol \"_Bar\" here var _z _Foo._Bar ~~~~ :8:9: warning: Local variable \"_y\" is never read var _y = => _x ~~ :17:7: warning: Local variable \"_x\" is never read var _x = => _foo._x ~~ :18:7: warning: Local variable \"_y\" is never read var _y = => _foo._foo ~~ :19:7: warning: Local variable \"_z\" is never read var _z _Foo._Bar ~~ ") test(" class bool { def +(x int, y int) def -(x int, y int) {} } def main { false + false false - false } ", " :2:7: error: Expected \"+\" to take at most 1 argument def +(x int, y int) ^ :3:7: error: Expected \"-\" to take at most 1 argument def -(x int, y int) {} ^ :7:9: error: Expected 2 arguments but found 1 argument when calling \"+\" false + false ^ :2:7: note: The function declaration is here def +(x int, y int) ^ :8:9: error: Expected 2 arguments but found 1 argument when calling \"-\" false - false ^ :3:7: note: The function declaration is here def -(x int, y int) {} ^ ") test(" class bool { def +(x bool) def -(x bool) {} } def main { false + false + false false - false - false } ", " :7:3: error: The function \"+\" does not return a value false + false + false ~~~~~~~~~~~~~ :2:7: note: The function declaration is here def +(x bool) ^ :8:3: error: The function \"-\" does not return a value false - false - false ~~~~~~~~~~~~~ :3:7: note: The function declaration is here def -(x bool) {} ^ ") test(" def main { 0 + 0 0 + 0.5 0.5 + 0 0.5 + 0.5 } ", " :2:3: warning: Unused expression 0 + 0 ~~~~~ :3:3: warning: Unused expression 0 + 0.5 ~~~~~~~ :4:3: warning: Unused expression 0.5 + 0 ~~~~~~~ :5:3: warning: Unused expression 0.5 + 0.5 ~~~~~~~~~ ") test(" class Foo { def * def -(x int, y int) def <=> int def [...] def [] def []= def foo= def !(x int) def {...} } ", " :2:7: error: Expected \"*\" to take 1 argument def * ^ :3:7: error: Expected \"-\" to take at most 1 argument def -(x int, y int) ^ :4:7: error: Expected \"<=>\" to take 1 argument def <=> int ~~~ :5:7: error: Expected \"[...]\" to take 1 argument def [...] ~~~~~ :6:7: error: Expected \"[]\" to take 1 argument def [] ~~ :7:7: error: Expected \"[]=\" to take 2 arguments def []= ~~~ :8:7: error: Expected \"foo=\" to take 1 argument def foo= ~~~~ :9:7: error: Expected \"!\" to take 0 arguments def !(x int) ^ :10:7: error: Expected \"{...}\" to take 2 arguments def {...} ~~~~~ ") test(" def main(x List) { var y bool = x[false] } ", " :2:18: error: Cannot convert from type \"bool\" to type \"int\" without a cast var y bool = x[false] ~~~~~ :2:16: error: Cannot convert from type \"double\" to type \"bool\" without a cast var y bool = x[false] ~~~~~~~~ :2:7: warning: Local variable \"y\" is never read var y bool = x[false] ^ ") test(" def main(i int) { for i in false..false { i = false } for i in List.new { i = false } for i in 0 { i = false } for j in 0..10 {} for j in 10..0 {} } ", " :2:12: error: Cannot convert from type \"bool\" to type \"int\" without a cast for i in false..false { ~~~~~ :2:19: error: Cannot convert from type \"bool\" to type \"int\" without a cast for i in false..false { ~~~~~ :2:7: error: \"i\" shadows a previous declaration for i in false..false { ^ :1:10: note: The previous declaration is here def main(i int) { ^ :3:9: error: Cannot convert from type \"bool\" to type \"int\" without a cast i = false ~~~~~ :5:7: error: \"i\" shadows a previous declaration for i in List.new { ^ :1:10: note: The previous declaration is here def main(i int) { ^ :6:9: error: Cannot convert from type \"bool\" to type \"int\" without a cast i = false ~~~~~ :8:12: error: Cannot iterate over type \"int\" for i in 0 { ^ :8:7: error: \"i\" shadows a previous declaration for i in 0 { ^ :1:10: note: The previous declaration is here def main(i int) { ^ :9:9: error: Cannot convert from type \"bool\" to type \"int\" without a cast i = false ~~~~~ :12:12: warning: This range is empty for j in 10..0 {} ~~~~~ ") test(" def main { var foo = true ? false : 0 var bar bool = true ? 0 : 0.5 } ", " :2:20: error: No common type for \"bool\" and \"int\" var foo = true ? false : 0 ~~~~~~~~~ :3:25: error: Cannot convert from type \"int\" to type \"bool\" without a cast var bar bool = true ? 0 : 0.5 ^ :3:29: error: Cannot convert from type \"double\" to type \"bool\" without a cast var bar bool = true ? 0 : 0.5 ~~~ :2:7: warning: Local variable \"foo\" is never read var foo = true ? false : 0 ~~~ :3:7: warning: Local variable \"bar\" is never read var bar bool = true ? 0 : 0.5 ~~~ ") test(" def main { var a int = [false] var b List = [false] var c List> = [[false]] var d int = {1: 2} var e IntMap = {\"a\": \"b\"} var f StringMap = {1: 2} [false, 0] [0, 0.5] - 1 [null, [1], [0.5]] [null] [] {0: 1} - 1 {\"a\": 1} - 1 {0.5: 1} - 1 [foo] } def foo(x int) { } ", " :2:15: error: Cannot infer a type for this literal var a int = [false] ~~~~~~~ :3:22: error: Cannot convert from type \"bool\" to type \"int\" without a cast var b List = [false] ~~~~~ :4:29: error: Cannot convert from type \"bool\" to type \"int\" without a cast var c List> = [[false]] ~~~~~ :5:15: error: Cannot infer a type for this literal var d int = {1: 2} ~~~~~~ :6:25: error: Cannot convert from type \"string\" to type \"int\" var e IntMap = {\"a\": \"b\"} ~~~ :6:30: error: Cannot convert from type \"string\" to type \"bool\" var e IntMap = {\"a\": \"b\"} ~~~ :7:28: error: Cannot convert from type \"int\" to type \"string\" var f StringMap = {1: 2} ^ :7:31: error: Cannot convert from type \"int\" to type \"bool\" without a cast var f StringMap = {1: 2} ^ :8:11: error: No common type for \"bool\" and \"int\" [false, 0] ^ :8:3: warning: Unused expression [false, 0] ~~~~~~~~~~ :9:12: error: \"-\" is not declared on type \"List\" [0, 0.5] - 1 ^ :10:15: error: No common type for \"List\" and \"List\" [null, [1], [0.5]] ~~~~~ :10:3: warning: Unused expression [null, [1], [0.5]] ~~~~~~~~~~~~~~~~~~ :11:3: error: Cannot infer a type for this literal [null] ~~~~~~ :12:3: error: Cannot infer a type for this literal [] ~~ :13:10: error: \"-\" is not declared on type \"IntMap\" {0: 1} - 1 ^ :14:12: error: \"-\" is not declared on type \"StringMap\" {\"a\": 1} - 1 ^ :15:3: error: Cannot infer a type for this literal {0.5: 1} - 1 ~~~~~~~~ :16:4: error: The function \"foo\" takes 1 argument and must be called [foo] ~~~ :16:3: warning: Unused expression [foo] ~~~~~ :2:7: warning: Local variable \"a\" is never read var a int = [false] ^ :3:7: warning: Local variable \"b\" is never read var b List = [false] ^ :4:7: warning: Local variable \"c\" is never read var c List> = [[false]] ^ :5:7: warning: Local variable \"d\" is never read var d int = {1: 2} ^ :6:7: warning: Local variable \"e\" is never read var e IntMap = {\"a\": \"b\"} ^ :7:7: warning: Local variable \"f\" is never read var f StringMap = {1: 2} ^ ") test(" class A {} class B { def [...] {} } class C { def new {} def [...] {} } class D { def new {} def [...](x bool) D { return self } } def main { var a A = [1, 2] var b B = [1, 2] var c C = [1, 2] var d D = [1, 2] } ", " :4:7: error: Expected \"[...]\" to take 1 argument def [...] {} ~~~~~ :9:7: error: Expected \"[...]\" to take 1 argument def [...] {} ~~~~~ :18:13: error: Cannot infer a type for this literal var a A = [1, 2] ~~~~~~ :19:14: error: The function \"[...]\" does not return a value var b B = [1, 2] ^ :4:7: note: The function declaration is here def [...] {} ~~~~~ :19:14: error: Expected 0 arguments but found 1 argument when calling \"[...]\" var b B = [1, 2] ^ :4:7: note: The function declaration is here def [...] {} ~~~~~ :20:14: error: The function \"[...]\" does not return a value var c C = [1, 2] ^ :9:7: note: The function declaration is here def [...] {} ~~~~~ :20:14: error: Expected 0 arguments but found 1 argument when calling \"[...]\" var c C = [1, 2] ^ :9:7: note: The function declaration is here def [...] {} ~~~~~ :21:14: error: Cannot convert from type \"int\" to type \"bool\" without a cast var d D = [1, 2] ^ :21:17: error: Cannot convert from type \"int\" to type \"bool\" without a cast var d D = [1, 2] ^ :18:7: warning: Local variable \"a\" is never read var a A = [1, 2] ^ :19:7: warning: Local variable \"b\" is never read var b B = [1, 2] ^ :20:7: warning: Local variable \"c\" is never read var c C = [1, 2] ^ :21:7: warning: Local variable \"d\" is never read var d D = [1, 2] ^ ") test(" class Foo { def [new](x int, y int, z int) int {} def {new}(x int, y int, z int) int {} } ", " :2:34: error: Constructors cannot have a return type def [new](x int, y int, z int) int {} ~~~ :2:7: error: Expected \"[new]\" to take at most 1 argument def [new](x int, y int, z int) int {} ~~~~~ :3:34: error: Constructors cannot have a return type def {new}(x int, y int, z int) int {} ~~~ :3:7: error: Expected \"{new}\" to take either 0 or 2 arguments def {new}(x int, y int, z int) int {} ~~~~~ :2:33: fix: Remove the return type def [new](x int, y int, z int) int {} ~~~~ [] :3:33: fix: Remove the return type def {new}(x int, y int, z int) int {} ~~~~ [] ") test(" class Foo { def foo int { return Bar.new.foo } def bar List { return Bar.new.foo } } class Bar { def new {} def foo List { return null } } ", " :3:12: error: Cannot convert from type \"List\" to type \"int\" return Bar.new.foo ~~~~~~~~~~~~~~~~ ") test(" class Foo { def foo int { return 0 } def foo double { return 0 } def bar(x int) {} def bar(x double) {} def bar(x int) {} } ", " :3:7: error: Duplicate overloaded function \"foo\" def foo double { return 0 } ~~~ :2:7: note: The previous declaration is here def foo int { return 0 } ~~~ :6:7: error: Duplicate overloaded function \"bar\" def bar(x int) {} ~~~ :4:7: note: The previous declaration is here def bar(x int) {} ~~~ ") test(" class Bar : Foo { over foo } class Foo { def foo } ", " ") test(" class Foo { def foo } class Bar : Foo { over foo } ", " ") test(" class Bar : Foo { over foo } class Foo { def foo {} def foo {} } ", " :7:7: error: Duplicate overloaded function \"foo\" def foo {} ~~~ :6:7: note: The previous declaration is here def foo {} ~~~ ") test(" class Foo { def foo {} def foo {} } class Bar : Foo { over foo } ", " :3:7: error: Duplicate overloaded function \"foo\" def foo {} ~~~ :2:7: note: The previous declaration is here def foo {} ~~~ ") test(" class Bar : Foo { over foo {} over foo {} } class Foo { def foo } ", " :3:8: error: Duplicate overloaded function \"foo\" over foo {} ~~~ :2:8: note: The previous declaration is here over foo {} ~~~ ") test(" class Foo { def foo } class Bar : Foo { over foo {} over foo {} } ", " :7:8: error: Duplicate overloaded function \"foo\" over foo {} ~~~ :6:8: note: The previous declaration is here over foo {} ~~~ ") test(" class Bar : Foo { over foo {} over foo {} } class Foo { def foo {} def foo {} } ", " :3:8: error: Duplicate overloaded function \"foo\" over foo {} ~~~ :2:8: note: The previous declaration is here over foo {} ~~~ :8:7: error: Duplicate overloaded function \"foo\" def foo {} ~~~ :7:7: note: The previous declaration is here def foo {} ~~~ ") test(" class Foo { def foo {} def foo {} } class Bar : Foo { over foo {} over foo {} } ", " :3:7: error: Duplicate overloaded function \"foo\" def foo {} ~~~ :2:7: note: The previous declaration is here def foo {} ~~~ :8:8: error: Duplicate overloaded function \"foo\" over foo {} ~~~ :7:8: note: The previous declaration is here over foo {} ~~~ ") test(" def bar(x int) int { return 0 } def bar(x double) double { return 0 } def bar(x List) List { return null } def main { var w bool = bar(null) var x bool = bar(0) var y bool = bar(0.5) var z bool = bar([]) } ", " :6:16: error: Cannot convert from type \"List\" to type \"bool\" var w bool = bar(null) ~~~~~~~~~ :7:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast var x bool = bar(0) ~~~~~~ :8:16: error: Cannot convert from type \"double\" to type \"bool\" without a cast var y bool = bar(0.5) ~~~~~~~~ :9:16: error: Cannot convert from type \"List\" to type \"bool\" var z bool = bar([]) ~~~~~~~ :6:7: warning: Local variable \"w\" is never read var w bool = bar(null) ^ :7:7: warning: Local variable \"x\" is never read var x bool = bar(0) ^ :8:7: warning: Local variable \"y\" is never read var y bool = bar(0.5) ^ :9:7: warning: Local variable \"z\" is never read var z bool = bar([]) ^ ") test(" namespace Foo { def [new](x List) {} } class Foo {} def main(foo Foo) { foo = [] } ", " :8:9: error: The function \"[new]\" does not return a value foo = [] ~~ :2:7: note: The function declaration is here def [new](x List) {} ~~~~~ ") test(" namespace Foo { def foo {} var bar = 0 } class Foo { def new {} def ifoo {} var ibar = 0 } def main(foo Foo) { foo.new Foo.new Foo.new foo.foo Foo.foo Foo.foo foo.bar = 0 Foo.bar = 0 Foo.bar = 0 foo.ifoo Foo.ifoo Foo.ifoo foo.ibar = 0 Foo.ibar = 0 Foo.ibar = 0 } ", " :13:7: error: Cannot access global member \"new\" from an instance context foo.new ~~~ :14:3: error: Cannot use unparameterized type \"Foo\" here Foo.new ~~~ :17:7: error: Cannot access global member \"foo\" from an instance context foo.foo ~~~ :18:3: error: Cannot use unparameterized type \"Foo\" here Foo.foo ~~~ :21:7: error: Cannot access global member \"bar\" from an instance context foo.bar = 0 ~~~ :23:3: error: Cannot use parameterized type \"Foo\" here Foo.bar = 0 ~~~~~~~~ :26:7: error: Cannot access instance member \"ifoo\" from a global context Foo.ifoo ~~~~ :27:12: error: Cannot access instance member \"ifoo\" from a global context Foo.ifoo ~~~~ :30:7: error: Cannot access instance member \"ibar\" from a global context Foo.ibar = 0 ~~~~ :31:12: error: Cannot access instance member \"ibar\" from a global context Foo.ibar = 0 ~~~~ ") test(" class Foo { class Bar { def bar int { return foo + baz } } def baz int { return 0 } var foo = 0 } ", " :2:36: error: Cannot access instance member \"foo\" from a global context class Bar { def bar int { return foo + baz } } ~~~ :2:42: error: Cannot access instance member \"baz\" from a global context class Bar { def bar int { return foo + baz } } ~~~ ") test(" class Foo { class Bar {} } namespace Foo { namespace Bar { def bar int { return foo + baz } } def baz int { return 0 } var foo = 0 } ", " ") test(" class Foo : int {} class Bar : fn() {} class Baz : Baz {} ", " :1:13: error: Cannot extend type \"int\" class Foo : int {} ~~~ :2:13: error: Cannot extend type \"fn()\" class Bar : fn() {} ~~~~ :3:7: error: Cyclic declaration of \"Baz\" class Baz : Baz {} ~~~ ") test(" class Foo { def foo {} } class Bar : Foo { def bar {} } class Baz : Foo { def baz {} } def main(foo Foo, bar Bar, baz Baz) { foo.foo foo.bar bar.foo bar.bar baz.foo baz.bar } ", " :7:7: error: \"bar\" is not declared on type \"Foo\" foo.bar ~~~ :11:7: error: \"bar\" is not declared on type \"Baz\", did you mean \"baz\"? baz.bar ~~~ :3:23: note: \"baz\" is defined here class Baz : Foo { def baz {} } ~~~ :11:7: fix: Replace with \"baz\" baz.bar ~~~ [baz] ") test(" class Foo { def new {} } class Bar : Foo { def new { super } } class Baz : Foo { def new { super } } def main { var a Foo = Foo.new var b Foo = Bar.new var c Foo = Baz.new var d Bar = Foo.new var e Bar = Bar.new var f Bar = Baz.new } ", " :9:15: error: Cannot convert from type \"Foo\" to type \"Bar\" without a cast var d Bar = Foo.new ~~~~~~~ :11:15: error: Cannot convert from type \"Baz\" to type \"Bar\" var f Bar = Baz.new ~~~~~~~ :6:7: warning: Local variable \"a\" is never read var a Foo = Foo.new ^ :7:7: warning: Local variable \"b\" is never read var b Foo = Bar.new ^ :8:7: warning: Local variable \"c\" is never read var c Foo = Baz.new ^ :9:7: warning: Local variable \"d\" is never read var d Bar = Foo.new ^ :10:7: warning: Local variable \"e\" is never read var e Bar = Bar.new ^ :11:7: warning: Local variable \"f\" is never read var f Bar = Baz.new ^ ") test(" class Foo { def new {} } class Bar : Foo { def new { super } } class Baz : Foo { def new { super } } def main { var foo = true ? Bar.new : Baz.new var bar int = foo } ", " :7:17: error: Cannot convert from type \"Foo\" to type \"int\" var bar int = foo ~~~ :7:7: warning: Local variable \"bar\" is never read var bar int = foo ~~~ ") test(" class Foo { def new {} } class Bar : Foo { def new { super } } class Baz : Foo { def new { super } } def main { var foo = true ? Bar.new : Baz.new var bar int = foo } ", " :6:20: error: No common type for \"Bar\" and \"Baz\" var foo = true ? Bar.new : Baz.new ~~~~~~~~~~~~~~~~~ :7:7: warning: Local variable \"bar\" is never read var bar int = foo ~~~ ") test(" class Foo { def foo(a A) Foo { return self } def bar(b B) A { return b as dynamic } } class Bar : Foo { } class Baz : Bar { def new { super } } def foo string { return Baz.new.foo(0.5).bar(null) } ", " :14:22: error: Cannot convert from type \"double\" to type \"int\" without a cast return Baz.new.foo(0.5).bar(null) ~~~ :14:31: error: Cannot convert from type \"null\" to type \"bool\" return Baz.new.foo(0.5).bar(null) ~~~~ :14:10: error: Cannot convert from type \"int\" to type \"string\" return Baz.new.foo(0.5).bar(null) ~~~~~~~~~~~~~~~~~~~~~~~~~~ ") test(" var a = 0 # a var b = 0 # b # comment var c = 0 # c # comment var d = 0 # d ", " ") test(" class Foo { def foo(x T) def foo(x int) } def foo(foo Foo) { foo.foo(0) foo.foo(0.5) foo.foo(false) } ", " :8:3: error: No overload of \"foo\" was found that takes 1 argument of type double foo.foo(0.5) ~~~~~~~ ") test(" class Foo { def +=(x int) def +=(x List) } def foo(foo Foo) { foo += false foo += [] foo += [false] foo += {} foo += {false: false} } ", " :7:7: error: No overload of \"+=\" was found that takes 1 argument of type bool foo += false ~~ :9:11: error: Cannot convert from type \"bool\" to type \"int\" without a cast foo += [false] ~~~~~ :10:10: error: Cannot infer a type for this literal foo += {} ~~ :10:7: error: No overload of \"+=\" was found that takes 1 argument of type dynamic foo += {} ~~ :11:10: error: Cannot infer a type for this literal foo += {false: false} ~~~~~~~~~~~~~~ :11:7: error: No overload of \"+=\" was found that takes 1 argument of type dynamic foo += {false: false} ~~ ") test(" class Foo { def +=(x dynamic) def +=(x List) } def foo(foo Foo) { foo += false foo += [false] } ", " :8:11: error: Cannot convert from type \"bool\" to type \"int\" without a cast foo += [false] ~~~~~ ") test(" class Foo { def +=(x dynamic) def +=(x double) } def foo(foo Foo) { foo += 0 } ", " :7:7: error: Multiple matching overloads of \"+=\" were found that can take 1 argument of type int foo += 0 ~~ ") test(" class Foo { def +=(x dynamic) int @prefer def +=(x double) double } def foo(foo Foo) { var bar bool = foo += 0 } ", " :8:18: error: Cannot convert from type \"double\" to type \"bool\" without a cast var bar bool = foo += 0 ~~~~~~~~ :8:7: warning: Local variable \"bar\" is never read var bar bool = foo += 0 ~~~ ") test(" class Foo { def new {} def <=>(x Foo) int { return 0 } } var foo int = Foo.new < Foo.new var bar bool = Foo.new <=> Foo.new var baz int = 1 < 2 ", " :6:15: error: Cannot convert from type \"bool\" to type \"int\" without a cast var foo int = Foo.new < Foo.new ~~~~~~~~~~~~~~~~~ :7:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast var bar bool = Foo.new <=> Foo.new ~~~~~~~~~~~~~~~~~~~ :8:15: error: Cannot convert from type \"bool\" to type \"int\" without a cast var baz int = 1 < 2 ~~~~~ ") test(" def main(x List>) { var foo = [false] in x } ", " :2:14: error: Cannot convert from type \"bool\" to type \"int\" without a cast var foo = [false] in x ~~~~~ :2:7: warning: Local variable \"foo\" is never read var foo = [false] in x ~~~ ") test(" def main { for i in 0..10 { 0 += 1 0 = 1 i += 1 i = 1 } } ", " :3:5: error: Cannot store to this location 0 += 1 ^ :4:5: error: Cannot store to this location 0 = 1 ^ :5:5: error: Cannot store to constant symbol \"i\" i += 1 ^ :6:5: error: Cannot store to constant symbol \"i\" i = 1 ^ ") test(" def main { for i in 0..10 { 0 += 1 0 = 1 i += 1 i = 1 } } ", " :3:5: error: Cannot store to this location 0 += 1 ^ :4:5: error: Cannot store to this location 0 = 1 ^ :5:5: error: Cannot store to constant symbol \"i\" i += 1 ^ :6:5: error: Cannot store to constant symbol \"i\" i = 1 ^ ") test(" class Foo { def foo(x int) } class Bar : Foo { def foo(x bool) } def main(x Bar) { x.foo(0) x.foo(0.5) x.foo(false) } ", " :11:3: error: No overload of \"foo\" was found that takes 1 argument of type double x.foo(0.5) ~~~~~ ") test(" class Foo { def foo(x string) def foo(x int) } class Bar : Foo { def foo(x bool) } def main(x Bar) { x.foo(0) x.foo(\"\") x.foo(0.5) x.foo(false) } ", " :13:3: error: No overload of \"foo\" was found that takes 1 argument of type double x.foo(0.5) ~~~~~ ") test(" class Foo { def foo(x int) } class Bar : Foo { def foo(x string) def foo(x bool) } def main(x Bar) { x.foo(0) x.foo(\"\") x.foo(0.5) x.foo(false) } ", " :13:3: error: No overload of \"foo\" was found that takes 1 argument of type double x.foo(0.5) ~~~~~ ") test(" class Foo { def foo(x Foo) def foo(x int) } class Bar : Foo { def foo(x string) def foo(x bool) } def main(x Bar) { x.foo(0) x.foo(\"\") x.foo(0.5) x.foo(null) x.foo(false) } ", " :14:3: error: No overload of \"foo\" was found that takes 1 argument of type double x.foo(0.5) ~~~~~ :15:3: error: Multiple matching overloads of \"foo\" were found that can take 1 argument of type null x.foo(null) ~~~~~ ") test(" class Foo { var foo int } class Bar : Foo { def foo } ", " :6:7: error: \"foo\" overrides another declaration with the same name in base type \"Foo\" def foo ~~~ :2:7: note: The overridden declaration is here var foo int ~~~ ") test(" class Foo { def foo } class Bar : Foo { var foo int } ", " :6:7: error: \"foo\" overrides another declaration with the same name in base type \"Foo\" var foo int ~~~ :2:7: note: The overridden declaration is here def foo ~~~ ") test(" class Foo { class foo {} } class Bar : Foo { def foo } ", " :6:7: error: \"foo\" overrides another declaration with the same name in base type \"Foo\" def foo ~~~ :2:9: note: The overridden declaration is here class foo {} ~~~ ") test(" class Foo { def foo } class Bar : Foo { class foo {} } ", " :6:9: error: \"foo\" overrides another declaration with the same name in base type \"Foo\" class foo {} ~~~ :2:7: note: The overridden declaration is here def foo ~~~ ") test(" class Foo { } namespace Foo { def foo {} } class Bar : Foo { def foo } ", " :9:7: error: \"foo\" overrides another declaration with the same name in base type \"Foo\" def foo ~~~ :5:7: note: The overridden declaration is here def foo {} ~~~ ") test(" class Foo { def foo } class Bar : Foo { } namespace Bar { def foo {} } ", " :9:7: error: \"foo\" overrides another declaration with the same name in base type \"Foo\" def foo {} ~~~ :2:7: note: The overridden declaration is here def foo ~~~ ") test(" class Foo { def new {} def foo } class Bar : Foo { def new { super } over foo } namespace Foo { def bar {} } namespace Bar { def bar {} } ", " ") test(" class Foo { def foo int } class Bar : Foo { def foo bool } ", " :6:7: error: \"foo\" overrides another function with the same name and argument types but a different return type in base type \"Foo\" def foo bool ~~~ :2:7: note: The overridden function is here def foo int ~~~ ") test(" class Foo { def foo int } class Bar : Foo { def foo bool def bar int } ", " :6:7: error: \"foo\" overrides another function with the same name and argument types but a different return type in base type \"Foo\" def foo bool ~~~ :2:7: note: The overridden function is here def foo int ~~~ ") test(" class Foo { def foo int def bar int } class Bar : Foo { def foo bool } ", " :7:7: error: \"foo\" overrides another function with the same name and argument types but a different return type in base type \"Foo\" def foo bool ~~~ :2:7: note: The overridden function is here def foo int ~~~ ") test(" class Foo { def foo int def bar bool } class Bar : Foo { def foo bool def bar int } ", " :7:7: error: \"foo\" overrides another function with the same name and argument types but a different return type in base type \"Foo\" def foo bool ~~~ :2:7: note: The overridden function is here def foo int ~~~ :8:7: error: \"bar\" overrides another function with the same name and argument types but a different return type in base type \"Foo\" def bar int ~~~ :3:7: note: The overridden function is here def bar bool ~~~ ") test(" interface Foo { def foo(x A) B } class Bar :: Foo { def foo(x int) double { return 0 } } class Bar2 :: Foo { def foo(x double) int { return 0 } } ", " :11:7: error: Type \"Bar2\" is missing an implementation of function \"foo\" from interface \"Foo\" class Bar2 :: Foo { ~~~~ :2:7: note: The function declaration is here def foo(x A) B ~~~ ") test(" interface Foo { def foo(x A, y B) B } class Bar :: Foo { def foo(x int, y T) T { return y } } class Bar2 :: Foo { def foo(x double, y T) int { return 0 } } class Bar3 :: Foo { def foo(x int, y T) int { return 0 } } ", " :11:7: error: Type \"Bar2\" is missing an implementation of function \"foo\" from interface \"Foo\" class Bar2 :: Foo { ~~~~ :2:7: note: The function declaration is here def foo(x A, y B) B ~~~ :18:7: error: Function \"foo\" has unexpected return type \"int\", expected return type \"T\" to match the function with the same name and argument types from interface \"Foo\" def foo(x int, y T) int { ~~~ :2:7: note: The function declaration is here def foo(x A, y B) B ~~~ ") test(" var x = super def foo { super } class Foo { def new { super } def foo { super } } namespace Foo { def bar { super } } class Bar : Foo { def new { super } over foo { super } } namespace Bar { def bar { super } } ", " :5:13: error: Cannot use \"super\" here def new { super } ~~~~~ :6:13: error: Cannot use \"super\" here def foo { super } ~~~~~ :10:13: error: Cannot use \"super\" here def bar { super } ~~~~~ :2:11: error: Cannot use \"super\" here def foo { super } ~~~~~ :1:9: error: Cannot use \"super\" here var x = super ~~~~~ ") test(" class Foo { def new(x int) {} def foo(x int) {} } namespace Foo { def bar(x int) {} } class Bar : Foo { def new(x int) { super() } over foo(x int) { super() } } namespace Bar { def bar(x int) { super() } } ", " :11:25: error: Expected 1 argument but found 0 arguments when calling \"new\" def new(x int) { super() } ~~ :2:7: note: The function declaration is here def new(x int) {} ~~~ :12:26: error: Expected 1 argument but found 0 arguments when calling \"foo\" over foo(x int) { super() } ~~ :3:7: note: The function declaration is here def foo(x int) {} ~~~ :16:25: error: Expected 1 argument but found 0 arguments when calling \"bar\" def bar(x int) { super() } ~~ :7:7: note: The function declaration is here def bar(x int) {} ~~~ ") test(" class Foo { def new(x int) {} } class Bar : Foo { def new(x bool) { super(x) } } ", " :7:11: error: Cannot convert from type \"bool\" to type \"int\" without a cast super(x) ^ ") test(" class Foo { def new(x int) {} } class Bar : Foo { def new { super(1, 2) } } ", " :7:10: error: Expected 1 argument but found 2 arguments when calling \"new\" super(1, 2) ~~~~~~ :2:7: note: The function declaration is here def new(x int) {} ~~~ ") test(" class Foo { def new(x int) {} def new(x int, y int) {} } class Bar : Foo { def new(z int) { super(false) } def new(y int, z int) { super(false, y) } def new(x int, y int, z int) { super(x, y, z) } } ", " :8:11: error: Cannot convert from type \"bool\" to type \"int\" without a cast super(false) ~~~~~ :12:11: error: Cannot convert from type \"bool\" to type \"int\" without a cast super(false, y) ~~~~~ :16:5: error: No overload of \"new\" was found that takes 3 arguments super(x, y, z) ~~~~~ ") test(" class Foo { def new(x int) { super } } ", " :3:5: error: Cannot use \"super\" here super ~~~~~ ") test(" class Foo { def foo(x int) {} def foo {} } class Bar : Foo { over foo { super(false) } over foo(x int) { super } def foo(x int, y int) { super(x, y) } } ", " :7:20: error: Cannot convert from type \"bool\" to type \"int\" without a cast over foo { super(false) } ~~~~~ :9:27: error: No overload of \"foo\" was found that takes 2 arguments def foo(x int, y int) { super(x, y) } ~~~~~ ") test(" enum Foo { FOO BAR } def main { var foo int = Foo.FOO == .BAR var bar int = .FOO == Foo.BAR var baz int = .FOO == .BAR } ", " :7:17: error: Cannot convert from type \"bool\" to type \"int\" without a cast var foo int = Foo.FOO == .BAR ~~~~~~~~~~~~~~~ :8:17: error: Cannot convert from type \"bool\" to type \"int\" without a cast var bar int = .FOO == Foo.BAR ~~~~~~~~~~~~~~~ :9:25: error: Cannot access \"BAR\" without type context var baz int = .FOO == .BAR ~~~~ :9:17: error: Cannot convert from type \"bool\" to type \"int\" without a cast var baz int = .FOO == .BAR ~~~~~~~~~~~~ :7:7: warning: Local variable \"foo\" is never read var foo int = Foo.FOO == .BAR ~~~ :8:7: warning: Local variable \"bar\" is never read var bar int = .FOO == Foo.BAR ~~~ :9:7: warning: Local variable \"baz\" is never read var baz int = .FOO == .BAR ~~~ ") test(" enum Foo { var foo def bar def new } namespace Foo { var foo2 def bar2 } ", " :2:3: error: Cannot use this declaration here var foo ~~~~~~~ :4:3: error: Cannot use this declaration here def new ~~~~~~~ :3:7: error: Non-imported function \"bar\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) def bar ~~~ :4:7: error: Non-imported function \"new\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) def new ~~~ :9:7: error: Non-imported function \"bar2\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) def bar2 ~~~~ :2:7: error: The implicitly typed variable \"foo\" must be initialized var foo ~~~ :8:7: error: The implicitly typed variable \"foo2\" must be initialized var foo2 ~~~~ ") test(" enum Foo { FOO } def main { var a bool = Foo.FOO as string var b bool = Foo.FOO as int var c bool = Foo.FOO as double var d bool = \"FOO\" as Foo var e bool = 0 as Foo var f bool = 0.0 as Foo var g bool = Foo.FOO.toString var h int = Foo.FOO } ", " :6:16: error: Cannot convert from type \"Foo\" to type \"string\" var a bool = Foo.FOO as string ~~~~~~~ :6:16: error: Cannot convert from type \"string\" to type \"bool\" var a bool = Foo.FOO as string ~~~~~~~~~~~~~~~~~ :7:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast var b bool = Foo.FOO as int ~~~~~~~~~~~~~~ :8:16: error: Cannot convert from type \"double\" to type \"bool\" without a cast var c bool = Foo.FOO as double ~~~~~~~~~~~~~~~~~ :9:16: error: Cannot convert from type \"string\" to type \"Foo\" var d bool = \"FOO\" as Foo ~~~~~ :9:16: error: Cannot convert from type \"Foo\" to type \"bool\" var d bool = \"FOO\" as Foo ~~~~~~~~~~~~ :10:16: error: Cannot convert from type \"Foo\" to type \"bool\" var e bool = 0 as Foo ~~~~~~~~ :11:16: error: Cannot convert from type \"Foo\" to type \"bool\" var f bool = 0.0 as Foo ~~~~~~~~~~ :12:16: error: Cannot convert from type \"string\" to type \"bool\" var g bool = Foo.FOO.toString ~~~~~~~~~~~~~~~~ :6:7: warning: Local variable \"a\" is never read var a bool = Foo.FOO as string ^ :7:7: warning: Local variable \"b\" is never read var b bool = Foo.FOO as int ^ :8:7: warning: Local variable \"c\" is never read var c bool = Foo.FOO as double ^ :9:7: warning: Local variable \"d\" is never read var d bool = \"FOO\" as Foo ^ :10:7: warning: Local variable \"e\" is never read var e bool = 0 as Foo ^ :11:7: warning: Local variable \"f\" is never read var f bool = 0.0 as Foo ^ :12:7: warning: Local variable \"g\" is never read var g bool = Foo.FOO.toString ^ :13:7: warning: Local variable \"h\" is never read var h int = Foo.FOO ^ ") test(" def main { if true {} else if true {} else {} # A if true {} # AA # B else if true {} # BB # C else {} # CC } ", " ") test(" def main { while true { break continue } break continue if true { break continue } while true { if true { break continue } } } ", " :7:3: error: Cannot use \"break\" outside a loop break ~~~~~ :8:3: error: Cannot use \"continue\" outside a loop continue ~~~~~~~~ :11:5: error: Cannot use \"break\" outside a loop break ~~~~~ :12:5: error: Cannot use \"continue\" outside a loop continue ~~~~~~~~ ") test(" def main { while true { var x = => { break continue } } } ", " :4:7: error: Cannot use \"break\" outside a loop break ~~~~~ :5:7: error: Cannot use \"continue\" outside a loop continue ~~~~~~~~ :3:9: warning: Local variable \"x\" is never read var x = => { ^ ") test(" enum Foo { FOO BAR } def main { switch Foo.FOO { case .FOO, .BAR, .BAZ {} case 0 {} default {} } } ", " :8:23: error: \"BAZ\" is not declared on type \"Foo\", did you mean \"BAR\"? case .FOO, .BAR, .BAZ {} ~~~ :3:3: note: \"BAR\" is defined here BAR ~~~ :9:10: error: Cannot convert from type \"int\" to type \"Foo\" without a cast case 0 {} ^ :9:10: error: Duplicate case value case 0 {} ^ :8:10: note: The first occurrence is here case .FOO, .BAR, .BAZ {} ~~~~ :8:23: fix: Replace with \"BAR\" case .FOO, .BAR, .BAZ {} ~~~ [BAR] ") test(" def main(x Foo) { switch 0 { case 0, 1, 2 {} case 0, 2 {} case 1 {} } switch x { case .FOO, .BAR, .BAZ {} case .FOO, .BAZ {} case .BAR {} } } enum Foo { FOO BAR BAZ } ", " :4:10: error: Duplicate case value case 0, 2 {} ^ :3:10: note: The first occurrence is here case 0, 1, 2 {} ^ :4:13: error: Duplicate case value case 0, 2 {} ^ :3:16: note: The first occurrence is here case 0, 1, 2 {} ^ :5:10: error: Duplicate case value case 1 {} ^ :3:13: note: The first occurrence is here case 0, 1, 2 {} ^ :10:10: error: Duplicate case value case .FOO, .BAZ {} ~~~~ :9:10: note: The first occurrence is here case .FOO, .BAR, .BAZ {} ~~~~ :10:16: error: Duplicate case value case .FOO, .BAZ {} ~~~~ :9:22: note: The first occurrence is here case .FOO, .BAR, .BAZ {} ~~~~ :11:10: error: Duplicate case value case .BAR {} ~~~~ :9:16: note: The first occurrence is here case .FOO, .BAR, .BAZ {} ~~~~ ") test(" def foo(x double, y double) double { return 0 } def foo(x int, y int) int { return 0 } @prefer def bar(x double, y double) double { return 0 } def bar(x int, y int) int { return 0 } def main { var a bool = foo(1, 2) var b bool = foo(1, 2.0) var c bool = foo(1, 2 as dynamic) var x bool = bar(1, 2) var y bool = bar(1, 2.0) var z bool = bar(1, 2 as dynamic) } ", " :9:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast var a bool = foo(1, 2) ~~~~~~~~~ :10:16: error: Cannot convert from type \"double\" to type \"bool\" without a cast var b bool = foo(1, 2.0) ~~~~~~~~~~~ :11:16: error: Multiple matching overloads of \"foo\" were found that can take 2 arguments of types int and dynamic var c bool = foo(1, 2 as dynamic) ~~~ :13:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast var x bool = bar(1, 2) ~~~~~~~~~ :14:16: error: Cannot convert from type \"double\" to type \"bool\" without a cast var y bool = bar(1, 2.0) ~~~~~~~~~~~ :15:16: error: Cannot convert from type \"double\" to type \"bool\" without a cast var z bool = bar(1, 2 as dynamic) ~~~~~~~~~~~~~~~~~~~~ :9:7: warning: Local variable \"a\" is never read var a bool = foo(1, 2) ^ :10:7: warning: Local variable \"b\" is never read var b bool = foo(1, 2.0) ^ :11:7: warning: Local variable \"c\" is never read var c bool = foo(1, 2 as dynamic) ^ :13:7: warning: Local variable \"x\" is never read var x bool = bar(1, 2) ^ :14:7: warning: Local variable \"y\" is never read var y bool = bar(1, 2.0) ^ :15:7: warning: Local variable \"z\" is never read var z bool = bar(1, 2 as dynamic) ^ ") test(" class Foo { var a bool var b = 0 } class Bar : Foo { var c double var d = \"\" } def main { var foo int = Foo.new(0) var bar int = Bar.new(0, 1) } ", " :12:25: error: Cannot convert from type \"int\" to type \"bool\" without a cast var foo int = Foo.new(0) ^ :12:17: error: Cannot convert from type \"Foo\" to type \"int\" var foo int = Foo.new(0) ~~~~~~~~~~ :13:25: error: Cannot convert from type \"int\" to type \"bool\" without a cast var bar int = Bar.new(0, 1) ^ :13:17: error: Cannot convert from type \"Bar\" to type \"int\" var bar int = Bar.new(0, 1) ~~~~~~~~~~~~~ :12:7: warning: Local variable \"foo\" is never read var foo int = Foo.new(0) ~~~ :13:7: warning: Local variable \"bar\" is never read var bar int = Bar.new(0, 1) ~~~ ") test(" class Foo { const x int def new { x = 100 } } class Bar : Foo { const y int def new { super x = 0 y = 100 } } ", " :12:5: error: Cannot store to constant symbol \"x\" x = 0 ^ ") test(" @import class Foo { def new def foo { new } } @import class Bar : Foo { over foo { new } } @import class Baz : Foo { def new over foo { new } } def main { Foo.new Bar.new Baz.new } ", " :9:14: error: \"new\" is not declared over foo { new } ~~~ :20:7: error: \"new\" is not declared on type \"Bar\" Bar.new ~~~ ") test(" class Foo { var x int } class Bar : Foo { var y int } def main { Foo.new(0) Bar.new(0) Bar.new(0, 1) } ", " :11:10: error: Expected 2 arguments but found 1 argument when calling \"new\" Bar.new(0) ~~~ :5:7: note: The function declaration is here class Bar : Foo { ~~~ ") test(" class Foo {} namespace Foo { const x = 0 } def main { Foo.new } ", " ") test(" class Foo { var foo = Foo.new } class Bar { } namespace Bar { var foo = Bar.new } ", " :1:7: error: Cyclic declaration of \"new\" class Foo { ~~~ ") test(" class Foo { } class Bar : Foo { } class Baz : Foo { } def main { Foo.new Bar.new Baz.new } ", " ") test(" class Foo {} class Bar : Foo {} def main { var a Foo = null as Foo var b = null as Bar var c = a as Foo var d = a as Bar var e int = 0.5 as int var f double = 0 as double } ", " :5:20: warning: Unnecessary cast from type \"null\" to type \"Foo\" var a Foo = null as Foo ~~~~~~ :7:13: warning: Unnecessary cast from type \"Foo\" to type \"Foo\" var c = a as Foo ~~~~~~ :10:20: warning: Unnecessary cast from type \"int\" to type \"double\" var f double = 0 as double ~~~~~~~~~ :6:7: warning: Local variable \"b\" is never read var b = null as Bar ^ :7:7: warning: Local variable \"c\" is never read var c = a as Foo ^ :8:7: warning: Local variable \"d\" is never read var d = a as Bar ^ :9:7: warning: Local variable \"e\" is never read var e int = 0.5 as int ^ :10:7: warning: Local variable \"f\" is never read var f double = 0 as double ^ :5:19: fix: Remove the cast var a Foo = null as Foo ~~~~~~~ [] :7:12: fix: Remove the cast var c = a as Foo ~~~~~~~ [] :10:19: fix: Remove the cast var f double = 0 as double ~~~~~~~~~~ [] ") test(" @import def foo def foo ", " ") test(" def foo @import def foo ", " ") test(" def foo {} def foo ", " ") test(" def foo def foo {} ", " ") test(" def foo def foo {} def foo ", " ") test(" def foo {} def foo def foo {} def foo ", " :3:5: error: Duplicate overloaded function \"foo\" def foo {} ~~~ :1:5: note: The previous declaration is here def foo {} ~~~ ") test(" def foo {} def foo {} ", " :2:5: error: Duplicate overloaded function \"foo\" def foo {} ~~~ :1:5: note: The previous declaration is here def foo {} ~~~ ") test(" def foo int def foo double { return 0 } ", " :2:5: error: Duplicate overloaded function \"foo\" def foo double { return 0 } ~~~ :1:5: note: The previous declaration is here def foo int ~~~ :1:5: error: Non-imported function \"foo\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) def foo int ~~~ ") test(" def foo(x int) def foo(x double) def foo(x int) {} def foo(x double) {} def main { foo(null as dynamic) } ", " :8:3: error: Multiple matching overloads of \"foo\" were found that can take 1 argument of type dynamic foo(null as dynamic) ~~~ ") test(" @prefer def foo(x int) def foo(x double) def foo(x int) {} def foo(x double) {} def main { foo(null as dynamic) } ", " ") test(" def foo(x int) {} def foo(x double) {} @prefer def foo(x int) def foo(x double) def main { foo(null as dynamic) } ", " ") test(" class Foo { def new(x int) {} def foo(x int) {} def foo(x double) {} } def main { Foo.new Foo.new(0).foo } ", " :8:7: error: The function \"new\" takes 1 argument and must be called Foo.new ~~~ :9:14: error: The function \"foo\" takes 1 argument and must be called Foo.new(0).foo ~~~ ") test(" class Foo { } namespace Foo { def new(x int) Foo { return null } } def main { var foo = Foo.new } ", " :11:17: error: The function \"new\" takes 1 argument and must be called var foo = Foo.new ~~~ :11:7: warning: Local variable \"foo\" is never read var foo = Foo.new ~~~ ") test(" class Foo { var foo = 0 var bar = foo } ", " :3:13: error: Cannot access instance member \"foo\" from a global context var bar = foo ~~~ ") test(" class Foo { var foo T class Bar { var foo T } namespace Bar { var bar T } } namespace Foo { var bar T } ", " :5:13: error: Cannot access type parameter \"T\" here var foo T ^ :9:13: error: Cannot access type parameter \"T\" here var bar T ^ :9:9: error: Cannot construct a default value of type \"T\" var bar T ~~~ :14:11: error: Cannot access type parameter \"T\" here var bar T ^ :14:7: error: Cannot construct a default value of type \"T\" var bar T ~~~ ") test(" # Nested uses of T should be fine inside instance functions class Foo { def new { var x = (a T) => { var y = (b T) => {} } } } namespace Foo { var x = (a T) => { var y = (b T) => {} } } ", " :11:14: error: Cannot access type parameter \"T\" here var x = (a T) => { ^ :12:16: error: Cannot access type parameter \"T\" here var y = (b T) => {} ^ :4:9: warning: Local variable \"x\" is never read var x = (a T) => { ^ :5:11: warning: Local variable \"y\" is never read var y = (b T) => {} ^ :12:9: warning: Local variable \"y\" is never read var y = (b T) => {} ^ ") test(" class Foo { } namespace Foo { def new(x T) T { var y Foo return x } def foo { var y Foo } } ", " :6:9: warning: Local variable \"y\" is never read var y Foo ^ :11:9: warning: Local variable \"y\" is never read var y Foo ^ ") test(" class Foo { } namespace Foo { def foo {} def bar T { return dynamic.t } } def main { Foo.foo Foo.foo Foo.bar Foo.bar } ", " :10:3: error: Cannot use unparameterized type \"Foo\" here Foo.foo ~~~ :12:3: error: Cannot use unparameterized type \"Foo\" here Foo.bar ~~~ ") test(" class Foo { } class Foo { } ", " :4:11: error: \"Foo\" already has type parameters class Foo { ~~~~ :1:11: note: Type parameters were previously declared here class Foo { ~~~~~~~ ") test(" class Foo { def [new](t List) {} } class Bar { def [new](t T) {} } def main { var foo Foo = [0] var bar Foo = [0] } ", " :6:13: error: Expected argument \"t\" to be of type \"List\" instead of type \"T\" def [new](t T) {} ^ :10:7: warning: Local variable \"foo\" is never read var foo Foo = [0] ~~~ :11:7: warning: Local variable \"bar\" is never read var bar Foo = [0] ~~~ ") test(" class Foo { def {new}(k List, v List) {} } class Bar { def {new}(k K, v V) {} } def main { var foo Foo = {1: false} var bar Foo = {1: false} } ", " :6:13: error: Expected argument \"k\" to be of type \"List\" instead of type \"K\" def {new}(k K, v V) {} ^ :6:18: error: Expected argument \"v\" to be of type \"List\" instead of type \"V\" def {new}(k K, v V) {} ^ :10:7: warning: Local variable \"foo\" is never read var foo Foo = {1: false} ~~~ :11:7: warning: Local variable \"bar\" is never read var bar Foo = {1: false} ~~~ ") test(" class Foo { def []=(key K, value V) {} } namespace Foo { def {new}(keys List, values List) Foo { var foo = Foo.new for i in 0..keys.count { foo[keys[i]] = values[i] } return foo } } def main { var foo Foo = {false: 1} } ", " :16:29: error: Cannot convert from type \"bool\" to type \"int\" without a cast var foo Foo = {false: 1} ~~~~~ :16:36: error: Cannot convert from type \"int\" to type \"bool\" without a cast var foo Foo = {false: 1} ^ :16:7: warning: Local variable \"foo\" is never read var foo Foo = {false: 1} ~~~ ") test(" class Foo { def new {} def {...}(x int, y T) Foo { return self } } class Bar { def {...}(x int, y T) Bar { return self } } namespace Bar { def new Bar { return null } } def main { var foo Foo = {0: 1} var bar Bar = {0: 1} } ", " :15:7: warning: Local variable \"foo\" is never read var foo Foo = {0: 1} ~~~ :16:7: warning: Local variable \"bar\" is never read var bar Bar = {0: 1} ~~~ ") # Check list literal error messages test(" def main Foo { return [] } class Foo { def [new](x int) {} } ", " :6:13: error: Expected argument \"x\" to be of type \"List\" instead of type \"int\" def [new](x int) {} ^ ") # Check map literal error messages test(" def main Foo { return {} } class Foo { def {new}(x int, y int) {} } ", " :6:13: error: Expected argument \"x\" to be of type \"List\" instead of type \"int\" def {new}(x int, y int) {} ^ :6:20: error: Expected argument \"y\" to be of type \"List\" instead of type \"int\" def {new}(x int, y int) {} ^ ") # Check for stack overflow due to infinite list literal recursion test(" def main Foo { return [] } class Foo { def [new](x Foo) {} } ", " :6:13: error: Expected argument \"x\" to be of type \"List\" instead of type \"Foo\" def [new](x Foo) {} ^ :2:10: error: Attempting to resolve this literal led to recursive expansion return [] ~~ :6:7: note: The constructor that was called recursively is here def [new](x Foo) {} ~~~~~ ") test(" class Foo { var ivar = 0 def ifun {} } class Bar : Foo {} class Baz : Bar { over ifun {} } namespace Foo { var gvar = 0 def gfun {} } namespace Bar {} namespace Baz { def gfun {} } def main { Foo.new.ivar = 0 Bar.new.ivar = 0 Baz.new.ivar = 0 Foo.new.ifun Bar.new.ifun Baz.new.ifun Foo.gvar = 0 Bar.gvar = 0 Baz.gvar = 0 Foo.gfun Bar.gfun Baz.gfun } ", " :29:3: error: \"gvar\" is not declared on type \"Bar\", did you mean \"Foo.gvar\"? Bar.gvar = 0 ~~~~~~~~ :11:7: note: \"Foo.gvar\" is defined here var gvar = 0 ~~~~ :30:3: error: \"gvar\" is not declared on type \"Baz\", did you mean \"Foo.gvar\"? Baz.gvar = 0 ~~~~~~~~ :11:7: note: \"Foo.gvar\" is defined here var gvar = 0 ~~~~ :33:3: error: \"gfun\" is not declared on type \"Bar\", did you mean \"Foo.gfun\"? Bar.gfun ~~~~~~~~ :12:7: note: \"Foo.gfun\" is defined here def gfun {} ~~~~ :29:3: fix: Replace with \"Foo.gvar\" Bar.gvar = 0 ~~~~~~~~ [Foo.gvar] :30:3: fix: Replace with \"Foo.gvar\" Baz.gvar = 0 ~~~~~~~~ [Foo.gvar] :33:3: fix: Replace with \"Foo.gfun\" Bar.gfun ~~~~~~~~ [Foo.gfun] ") test(" def main { var bar = dynamic.foo } ", " :2:7: warning: Local variable \"bar\" is never read var bar = dynamic.foo ~~~ ") test(" class Bar : Foo { var y int } class Foo { var x int } def main { Bar.new(0, 1) # This used to fail when Bar came before Foo since Foo.new wasn't initialized first and so had no arguments } ", " ") test(" enum Foo { FOO BAR } def main { if Foo.FOO in [.FOO, .BAR] {} if .FOO in [Foo.FOO, Foo.BAR] {} } ", " ") test(" def main { var a = [.FOO, .BAR] var b = [Foo.FOO, .BAR] var c = [.FOO, Foo.BAR] var d = [Foo.FOO, Foo.BAR] } enum Foo { FOO BAR } ", " :2:12: error: Cannot access \"FOO\" without type context var a = [.FOO, .BAR] ~~~~ :2:18: error: Cannot access \"BAR\" without type context var a = [.FOO, .BAR] ~~~~ :2:7: warning: Local variable \"a\" is never read var a = [.FOO, .BAR] ^ :3:7: warning: Local variable \"b\" is never read var b = [Foo.FOO, .BAR] ^ :4:7: warning: Local variable \"c\" is never read var c = [.FOO, Foo.BAR] ^ :5:7: warning: Local variable \"d\" is never read var d = [Foo.FOO, Foo.BAR] ^ ") test(" def main(x int) { switch x { case 0 { var foo = 0 } case 1 { var foo = false } } } ", " :3:18: warning: Local variable \"foo\" is never read case 0 { var foo = 0 } ~~~ :4:18: warning: Local variable \"foo\" is never read case 1 { var foo = false } ~~~ ") # This tests a special case in the globalizing pass for super calls test(" @import class Foo { def foo {} def foo(x int) {} } class Bar : Foo { over foo { super } over foo(x int) { super(x) } } ", " ") test(" class Foo { @deprecated { def foo def bar(x int) def baz def baz(x int) } } def main(foo Foo) { foo.foo foo.bar(0) foo.baz foo.baz(0) } ", " :11:7: warning: Use of deprecated symbol \"foo\" foo.foo ~~~ :12:7: warning: Use of deprecated symbol \"bar\" foo.bar(0) ~~~ :13:7: warning: Use of deprecated symbol \"baz\" foo.baz ~~~ :14:7: warning: Use of deprecated symbol \"baz\" foo.baz(0) ~~~ ") test(" class Foo { @deprecated { def + def +(x int) def -(x int) def -(x bool) } } def main(foo Foo) { +foo foo + 0 foo - 0 foo - false } ", " :11:3: warning: Use of deprecated symbol \"+\" +foo ^ :12:7: warning: Use of deprecated symbol \"+\" foo + 0 ^ :13:7: warning: Use of deprecated symbol \"-\" foo - 0 ^ :14:7: warning: Use of deprecated symbol \"-\" foo - false ^ ") test(" class Foo { @deprecated def foo {} } class Bar : Foo { over foo { super } } ", " :7:14: warning: Use of deprecated symbol \"foo\" over foo { super } ~~~~~ ") test(" @deprecated class Foo {} def main { Foo.new } ", " :5:3: warning: Use of deprecated symbol \"Foo\" Foo.new ~~~ ") test(" @deprecated(\"Why are you using this?\") def test {} def main { test } ", " :5:3: warning: Why are you using this? test ~~~~ ") test(" class Foo { def ++ } def main(x Foo) { var foo = ++x var bar = x++ } ", " :6:13: error: The function \"++\" does not return a value var foo = ++x ~~~ :2:7: note: The function declaration is here def ++ ~~ :7:13: error: The function \"++\" does not return a value var bar = x++ ~~~ :2:7: note: The function declaration is here def ++ ~~ :6:7: warning: Local variable \"foo\" is never read var foo = ++x ~~~ :7:7: warning: Local variable \"bar\" is never read var bar = x++ ~~~ ") test(" def foo int {} ", " :1:5: error: All control paths for \"foo\" must return a value of type \"int\" def foo int {} ~~~ ") test(" def foo(x bool) int { if x { return 0 } } ", " :1:5: error: All control paths for \"foo\" must return a value of type \"int\" def foo(x bool) int { ~~~ ") test(" def foo(x bool) int { if x { } else { return 0 } } ", " :1:5: error: All control paths for \"foo\" must return a value of type \"int\" def foo(x bool) int { ~~~ ") test(" def foo(x bool) int { if x { return 0 } else { return 0 } } ", " ") test(" def foo int { if false { } if true { } } ", " :1:5: error: All control paths for \"foo\" must return a value of type \"int\" def foo int { ~~~ ") test(" def foo int { if true { return 0 } } ", " ") test(" def foo int { if false { } else { return 0 } } ", " ") test(" def foo int { while true {} } ", " ") test(" def foo int { while true { return 0 } } ", " ") test(" def foo int { while true { break } } ", " :1:5: error: All control paths for \"foo\" must return a value of type \"int\" def foo int { ~~~ ") test(" def foo int { for i = 0; true; i = i + 1 {} } ", " ") test(" def foo int { for i = 0; true; i = i + 1 { return 0 } } ", " ") test(" def foo int { for i = 0; true; i = i + 1 { break } } ", " :1:5: error: All control paths for \"foo\" must return a value of type \"int\" def foo int { ~~~ ") test(" def foo int { switch dynamic.bar { case 0 { return 0 } default { return 0 } } } ", " ") test(" def foo int { switch dynamic.bar { case 0 { return 0 } default {} } } ", " :1:5: error: All control paths for \"foo\" must return a value of type \"int\" def foo int { ~~~ ") test(" def foo int { switch dynamic.bar { case 0 { return 0 } } } ", " :1:5: error: All control paths for \"foo\" must return a value of type \"int\" def foo int { ~~~ ") test(" def main { 0-- 0 += 1 [1][2]=3 } ", " :2:3: error: Cannot store to this location 0-- ^ :3:3: error: Cannot store to this location 0 += 1 ^ ") test(" class Foo { class Bar { def foo Foo def bar Bar } } ", " ") test(" var a = [ # comment ] var b = [ 100 # comment ] var c = [ # comment 100 ] var d = [ 100, # comment ] var e = [ # comment 100, ] var f = [ # comment 100, # comment ] var g = [ 100, # comment 100, ] ", " :1:9: error: Cannot infer a type for this literal var a = [ ^ ") test(" def main { try {} catch x dynamic {} catch x dynamic {} } ", " ") test(" if true { if true { var foo = false } } def main int { return foo } ", " :8:10: error: Cannot convert from type \"bool\" to type \"int\" without a cast return foo ~~~ ") test(" if true { if false { } else { var foo = false } } def main int { return foo } ", " :9:10: error: Cannot convert from type \"bool\" to type \"int\" without a cast return foo ~~~ ") test(" if true { if false { var foo = 0.0 } else { var foo = false } } def main int { return foo } ", " :12:10: error: Cannot convert from type \"bool\" to type \"int\" without a cast return foo ~~~ ") test(" if true { if false { var foo = 0.0 } else if true { var foo = false } } def main int { return foo } ", " :10:10: error: Cannot convert from type \"bool\" to type \"int\" without a cast return foo ~~~ ") test(" if true { if false { var foo = 0.0 } else if false { var foo = \"\" } else { var foo = false } } def main int { return foo } ", " :12:10: error: Cannot convert from type \"bool\" to type \"int\" without a cast return foo ~~~ ") test(" class Foo { def foo } def main { Foo.new } ", " :6:3: error: Cannot construct abstract type \"Foo\" Foo.new ~~~~~~~ :2:7: note: The type \"Foo\" is abstract due to member \"foo\" def foo ~~~ ") test(" class Foo { def foo } class Bar : Foo { } def main { Bar.new } ", " :9:3: error: Cannot construct abstract type \"Bar\" Bar.new ~~~~~~~ :2:7: note: The type \"Bar\" is abstract due to member \"foo\" def foo ~~~ ") test(" class Foo { def foo } class Bar : Foo { } class Baz : Foo { } def main { Baz.new } ", " :12:3: error: Cannot construct abstract type \"Baz\" Baz.new ~~~~~~~ :2:7: note: The type \"Baz\" is abstract due to member \"foo\" def foo ~~~ ") test(" class Foo { def foo } class Bar : Foo { def foo(x int) } class Baz : Bar { } def main { Baz.new } ", " :13:3: error: Cannot construct abstract type \"Baz\" Baz.new ~~~~~~~ :6:7: note: The type \"Baz\" is abstract due to member \"foo\" def foo(x int) ~~~ ") test(" class Foo { def foo } class Bar : Foo { def foo(x int) {} } class Baz : Bar { } def main { Baz.new } ", " :13:3: error: Cannot construct abstract type \"Baz\" Baz.new ~~~~~~~ :2:7: note: The type \"Baz\" is abstract due to member \"foo\" def foo ~~~ ") test(" class Foo { def foo {} } class Bar : Foo { def foo(x int) } class Baz : Bar { } def main { Baz.new } ", " :13:3: error: Cannot construct abstract type \"Baz\" Baz.new ~~~~~~~ :6:7: note: The type \"Baz\" is abstract due to member \"foo\" def foo(x int) ~~~ ") test(" class Foo { def foo } class Bar : Foo { over foo {} } def main { Bar.new } ", " ") test(" class Foo { } class Bar : Foo { def foo } def main { Foo.new } ", " ") test(" class Foo { } class Bar : Foo { def foo } def main { Bar.new } ", " :9:3: error: Cannot construct abstract type \"Bar\" Bar.new ~~~~~~~ :5:7: note: The type \"Bar\" is abstract due to member \"foo\" def foo ~~~ ") test(" class Foo { def foo T { var bar T return bar } } ", " :3:9: error: Cannot construct a default value of type \"T\" var bar T ~~~ ") test(" def main { List.new.sort((a, b) => a <=> b) List.new.sort((a, b) => a <=> b) List.new.sort((a, b) => a <=> b) List.new.sort((a, b) => a <=> b) } ", " :2:35: error: \"<=>\" is not declared on type \"bool\" List.new.sort((a, b) => a <=> b) ~~~ ") test(" def foo fn() { return null } def bar {} def main { foo() (foo)() bar() bar(1) bar } ", " :5:3: error: Wrap calls to the function \"foo\" in parentheses to call the returned lambda foo() ~~~ :1:5: note: The function declaration is here def foo fn() { return null } ~~~ :7:6: error: Cannot call the value returned from the function \"bar\" (this function was called automatically because it takes no arguments) bar() ~~ :2:5: note: The function declaration is here def bar {} ~~~ :8:6: error: Cannot call the value returned from the function \"bar\" (this function was called automatically because it takes no arguments) bar(1) ~~~ :2:5: note: The function declaration is here def bar {} ~~~ :7:6: fix: Remove the unnecessary \"()\" bar() ~~ [] ") test(" def main { ((a, b) => a + b)(1, false) var foo bool = ((a, b) => a + b)(1, 2) var bar bool = ((a, b) => { return a + b })(1, 2) } ", " :2:18: error: Cannot convert from type \"bool\" to type \"int\" without a cast ((a, b) => a + b)(1, false) ^ :3:29: error: Cannot convert from type \"int\" to type \"bool\" without a cast var foo bool = ((a, b) => a + b)(1, 2) ~~~~~ :4:38: error: Cannot convert from type \"int\" to type \"bool\" without a cast var bar bool = ((a, b) => { return a + b })(1, 2) ~~~~~ :3:7: warning: Local variable \"foo\" is never read var foo bool = ((a, b) => a + b)(1, 2) ~~~ :4:7: warning: Local variable \"bar\" is never read var bar bool = ((a, b) => { return a + b })(1, 2) ~~~ ") test(" class Foo { def foo=(x int) {} } def main { Foo.new.foo = false } ", " :6:17: error: Cannot convert from type \"bool\" to type \"int\" without a cast Foo.new.foo = false ~~~~~ ") # Make sure the uncalled function error doesn't trigger for overloaded setter calls test(" class Foo { def foo=(x int) {} def foo=(x double) {} } def main { Foo.new.foo = 0 } ", " ") test(" def main { for i = 0, b = false; true; 0, 1 {} } ", " :2:14: error: Expected loop variable \"b\" to be of type \"int\" instead of type \"bool\" for i = 0, b = false; true; 0, 1 {} ^ :2:31: warning: Unused expression for i = 0, b = false; true; 0, 1 {} ^ :2:34: warning: Unused expression for i = 0, b = false; true; 0, 1 {} ^ :2:7: warning: Local variable \"i\" is never read for i = 0, b = false; true; 0, 1 {} ^ :2:14: warning: Local variable \"b\" is never read for i = 0, b = false; true; 0, 1 {} ^ ") test(" type Foo = int type Foo = int type Foo : int {} ", " :2:12: error: \"Foo\" already has a base type type Foo = int ~~~ :1:12: note: The previous base type is here type Foo = int ~~~ :3:12: error: \"Foo\" already has a base type type Foo : int {} ~~~ :1:12: note: The previous base type is here type Foo = int ~~~ ") test(" class Foo { def foo {} } type Bar = Foo type Bar { def bar {} } type Baz : Foo { def bar {} } def main { var foo = Foo.new var bar = foo as Bar var baz = foo as Baz foo.foo foo.bar bar.foo bar.bar baz.foo baz.bar } ", " :20:7: error: \"bar\" is not declared on type \"Foo\" foo.bar ~~~ :21:7: error: \"foo\" is not declared on type \"Bar\" bar.foo ~~~ :23:7: error: \"foo\" is not declared on type \"Baz\" baz.foo ~~~ ") test(" type Foo { } ", " :1:6: error: Missing base type for wrapped type \"Foo\" type Foo { ~~~ ") test(" class Foo : Bar {} type Bar = int ", " :1:13: error: Cannot extend type \"Bar\" class Foo : Bar {} ~~~ ") test(" var foo int type Foo = fn() type Bar = foo type Baz = dynamic type Bat = Foo def main { var foo Foo = => {} var foo2 Foo = => {} as Foo var bat Bat = foo var bat2 Bat = foo as Bat } ", " :3:12: error: Unexpected expression of type \"int\" type Bar = foo ~~~ :8:17: error: Cannot convert from type \"fn()\" to type \"Foo\" without a cast var foo Foo = => {} ~~~~~ :10:17: error: Cannot convert from type \"Foo\" to type \"Bat\" without a cast var bat Bat = foo ~~~ :9:7: warning: Local variable \"foo2\" is never read var foo2 Foo = => {} as Foo ~~~~ :10:7: warning: Local variable \"bat\" is never read var bat Bat = foo ~~~ :11:7: warning: Local variable \"bat2\" is never read var bat2 Bat = foo as Bat ~~~~ ") test(" type Foo : int { var foo def bar def new } namespace Foo { var foo2 def bar2 } ", " :2:3: error: Cannot use this declaration here var foo ~~~~~~~ :4:3: error: Cannot use this declaration here def new ~~~~~~~ :3:7: error: Non-imported function \"bar\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) def bar ~~~ :4:7: error: Non-imported function \"new\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) def new ~~~ :9:7: error: Non-imported function \"bar2\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) def bar2 ~~~~ :2:7: error: The implicitly typed variable \"foo\" must be initialized var foo ~~~ :8:7: error: The implicitly typed variable \"foo2\" must be initialized var foo2 ~~~~ ") test(" class Foo { } class Bar : Foo { } def main { var foo = Foo.new var bar = foo as Bar var baz = foo as Bar var bat = bar as Bar } ", " :9:13: error: Cannot convert from type \"Foo\" to type \"Bar\" var bar = foo as Bar ~~~ :11:13: error: Cannot convert from type \"Bar\" to type \"Bar\" var bat = bar as Bar ~~~ :10:7: warning: Local variable \"baz\" is never read var baz = foo as Bar ~~~ :11:7: warning: Local variable \"bat\" is never read var bat = bar as Bar ~~~ ") test(" class Foo { } type Bar : Foo { } def main { var foo = Foo.new var bar = foo as Bar var baz = foo as Bar var bat = bar as Bar } ", " :9:13: error: Cannot convert from type \"Foo\" to type \"Bar\" var bar = foo as Bar ~~~ :11:13: error: Cannot convert from type \"Bar\" to type \"Bar\" var bat = bar as Bar ~~~ :10:7: warning: Local variable \"baz\" is never read var baz = foo as Bar ~~~ :11:7: warning: Local variable \"bat\" is never read var bat = bar as Bar ~~~ ") test(" type Foo = Bar type Bar = Foo ", " :1:6: error: Cyclic declaration of \"Foo\" type Foo = Bar ~~~ ") test(" type Foo {} def main { var foo = 0 as Foo var bar = null as Foo } ", " :1:6: error: Missing base type for wrapped type \"Foo\" type Foo {} ~~~ :4:7: warning: Local variable \"foo\" is never read var foo = 0 as Foo ~~~ :5:7: warning: Local variable \"bar\" is never read var bar = null as Foo ~~~ ") test(" type Foo : List { def +=(foo int) { (self as List).append(foo) } } namespace Foo { def new Foo { return List.new as Foo } } def foo Foo { return Foo.new } def main { foo += 0 foo += false } ", " :19:10: error: Cannot convert from type \"bool\" to type \"int\" without a cast foo += false ~~~~~ ") test(" class Foo :: Baz {} class Bar : Foo {} interface Baz {} @export def main { dynamic.foo(Foo.new is dynamic) dynamic.foo(Foo.new is dynamic.Test) dynamic.foo(Foo.new is Foo) dynamic.foo(Foo.new is Bar) dynamic.foo(Foo.new is Baz) dynamic.foo(Bar.new is Foo) dynamic.foo(Bar.new is Bar) dynamic.foo(Bar.new is Baz) } ", " :7:15: warning: Unnecessary type check, type \"Foo\" is always type \"dynamic\" dynamic.foo(Foo.new is dynamic) ~~~~~~~~~~~~~~~~~~ :9:15: warning: Unnecessary type check, type \"Foo\" is always type \"Foo\" dynamic.foo(Foo.new is Foo) ~~~~~~~~~~~~~~ :11:26: error: Cannot check against interface type \"Baz\" dynamic.foo(Foo.new is Baz) ~~~ :12:15: warning: Unnecessary type check, type \"Bar\" is always type \"Foo\" dynamic.foo(Bar.new is Foo) ~~~~~~~~~~~~~~ :13:15: warning: Unnecessary type check, type \"Bar\" is always type \"Bar\" dynamic.foo(Bar.new is Bar) ~~~~~~~~~~~~~~ :14:26: error: Cannot check against interface type \"Baz\" dynamic.foo(Bar.new is Baz) ~~~ ") test(" var a Bool = null var b Int = null var c Double = null var d String = null var e Bar = null type Bool = bool type Int = int type Double = double type String = string type Bar = Foo enum Foo {} ", " :1:14: error: Cannot convert from type \"null\" to type \"Bool\" var a Bool = null ~~~~ :2:13: error: Cannot convert from type \"null\" to type \"Int\" var b Int = null ~~~~ :3:16: error: Cannot convert from type \"null\" to type \"Double\" var c Double = null ~~~~ :5:13: error: Cannot convert from type \"null\" to type \"Bar\" var e Bar = null ~~~~ ") test(" interface I {} class C :: I {} interface I2 :: I {} namespace N :: I {} type T : C :: I {} enum E :: I {} ", " :3:17: error: Cannot implement type \"I\" interface I2 :: I {} ^ :4:16: error: Cannot implement type \"I\" namespace N :: I {} ^ :5:15: error: Cannot implement type \"I\" type T : C :: I {} ^ :6:11: error: Cannot implement type \"I\" enum E :: I {} ^ ") test(" interface I {} class C :: I, I2, I, I2 {} interface I2 {} ", " :2:19: error: Duplicate implemented type \"I\" class C :: I, I2, I, I2 {} ^ :2:12: note: The first occurrence is here class C :: I, I2, I, I2 {} ^ :2:22: error: Duplicate implemented type \"I2\" class C :: I, I2, I, I2 {} ~~ :2:15: note: The first occurrence is here class C :: I, I2, I, I2 {} ~~ ") test(" class Foo :: IFoo {} interface IFoo {} interface IBar {} def main(foo Foo, ifoo IFoo, ibar IBar) { foo = ifoo foo = ibar ifoo = foo ifoo = ibar ibar = foo ibar = ifoo } ", " :6:9: error: Cannot convert from type \"IFoo\" to type \"Foo\" without a cast foo = ifoo ~~~~ :7:9: error: Cannot convert from type \"IBar\" to type \"Foo\" foo = ibar ~~~~ :9:10: error: Cannot convert from type \"IBar\" to type \"IFoo\" ifoo = ibar ~~~~ :10:10: error: Cannot convert from type \"Foo\" to type \"IBar\" ibar = foo ~~~ :11:10: error: Cannot convert from type \"IFoo\" to type \"IBar\" ibar = ifoo ~~~~ ") test(" class Foo :: IFoo {} interface IFoo {} def main(foo Foo, ifoo_int IFoo, ifoo_bool IFoo) { foo = ifoo_int foo = ifoo_bool ifoo_int = foo ifoo_int = ifoo_bool ifoo_bool = foo ifoo_bool = ifoo_int } ", " :5:9: error: Cannot convert from type \"IFoo\" to type \"Foo\" without a cast foo = ifoo_int ~~~~~~~~ :6:9: error: Cannot convert from type \"IFoo\" to type \"Foo\" foo = ifoo_bool ~~~~~~~~~ :8:14: error: Cannot convert from type \"IFoo\" to type \"IFoo\" ifoo_int = ifoo_bool ~~~~~~~~~ :9:15: error: Cannot convert from type \"Foo\" to type \"IFoo\" ifoo_bool = foo ~~~ :10:15: error: Cannot convert from type \"IFoo\" to type \"IFoo\" ifoo_bool = ifoo_int ~~~~~~~~ ") test(" class Foo :: IFoo {} interface IFoo {} def main(foo_int Foo, ifoo_int IFoo, ifoo_bool IFoo) { foo_int = ifoo_int foo_int = ifoo_bool ifoo_int = foo_int ifoo_int = ifoo_bool ifoo_bool = foo_int ifoo_bool = ifoo_int } ", " :5:13: error: Cannot convert from type \"IFoo\" to type \"Foo\" without a cast foo_int = ifoo_int ~~~~~~~~ :6:13: error: Cannot convert from type \"IFoo\" to type \"Foo\" foo_int = ifoo_bool ~~~~~~~~~ :8:14: error: Cannot convert from type \"IFoo\" to type \"IFoo\" ifoo_int = ifoo_bool ~~~~~~~~~ :9:15: error: Cannot convert from type \"Foo\" to type \"IFoo\" ifoo_bool = foo_int ~~~~~~~ :10:15: error: Cannot convert from type \"IFoo\" to type \"IFoo\" ifoo_bool = ifoo_int ~~~~~~~~ ") test(" class Foo :: IFoo {} interface IFoo { def foo } ", " :1:7: error: Type \"Foo\" is missing an implementation of function \"foo\" from interface \"IFoo\" class Foo :: IFoo {} ~~~ :2:22: note: The function declaration is here interface IFoo { def foo } ~~~ ") test(" class Foo :: IFoo { def foo int } interface IFoo { def foo bool } ", " :1:25: error: Function \"foo\" has unexpected return type \"int\", expected return type \"bool\" to match the function with the same name and argument types from interface \"IFoo\" class Foo :: IFoo { def foo int } ~~~ :2:22: note: The function declaration is here interface IFoo { def foo bool } ~~~ ") test(" class Foo :: IFoo { def foo } interface IFoo { def foo bool } ", " :1:25: error: Expected the return type of function \"foo\" to match the function with the same name and argument types from interface \"IFoo\" class Foo :: IFoo { def foo } ~~~ :2:22: note: The function declaration is here interface IFoo { def foo bool } ~~~ ") test(" class Foo :: IFoo { def foo int } interface IFoo { def foo } ", " :1:25: error: Expected the return type of function \"foo\" to match the function with the same name and argument types from interface \"IFoo\" class Foo :: IFoo { def foo int } ~~~ :2:22: note: The function declaration is here interface IFoo { def foo } ~~~ ") test(" class Foo :: IFoo { var foo int } interface IFoo { def foo bool } ", " :1:7: error: Type \"Foo\" is missing an implementation of function \"foo\" from interface \"IFoo\" class Foo :: IFoo { var foo int } ~~~ :2:22: note: The function declaration is here interface IFoo { def foo bool } ~~~ ") test(" class Foo { over foo } ", " :2:8: error: \"foo\" is declared using \"over\" instead of \"def\" but does not override anything over foo ~~~ ") test(" class Foo { def foo } class Bar : Foo { def foo } ", " :6:7: error: \"foo\" overrides another symbol with the same name but is declared using \"def\" instead of \"over\" def foo ~~~ :2:7: note: The overridden declaration is here def foo ~~~ ") test(" class Foo { def new {} } class Bar : Foo { def new {} } ", " :6:7: error: Constructors for derived types must start with a call to \"super\" def new {} ~~~ ") test(" class Foo { def new {} } class Bar : Foo { def new { super } } ", " ") test(" class Foo { def new(x int) {} } class Bar : Foo { def new { super } } ", " :6:13: error: The function \"new\" takes 1 argument and must be called def new { super } ~~~~~ ") test(" class Foo { def new(x int) {} } class Bar : Foo { def new { super(1) } } ", " ") # Parser whitespace test test(" var x = 0 ? # Comment 1 : # Comment 2 var y = z( # Comment ) ", " :1:9: error: Cannot convert from type \"int\" to type \"bool\" without a cast var x = 0 ? ^ :6:9: error: \"z\" is not declared var y = z( ^ ") # Parser whitespace test test(" def main { if 0 { } else { } } ", " :2:6: error: Cannot convert from type \"int\" to type \"bool\" without a cast if 0 ^ ") # Parser whitespace test test(" def main( a int, b int ) int { } ", " :1:5: error: All control paths for \"main\" must return a value of type \"int\" def main( ~~~~ ") # Parser whitespace test test(" var foo = a .b .c ", " :1:11: error: \"a\" is not declared var foo = a ^ ") # Test for an operator overload resolution bug with integer promotion to double test(" def foo(count int) { count + 0.0 count += 0.0 } ", " :2:3: warning: Unused expression count + 0.0 ~~~~~~~~~~~ :3:12: error: Cannot convert from type \"double\" to type \"int\" without a cast count += 0.0 ~~~ ") # Test for enum promotion to integer for comparison operators test(" enum Foo {} enum Bar {} def foo(foo Foo, bar Bar) bool { return (foo < 0 || foo > 0 || foo <= 0 || foo >= 0 || foo == 0 || foo != 0) && (0 < foo || 0 > foo || 0 <= foo || 0 >= foo || 0 == foo || 0 != foo) && (foo < 0.0 || foo > 0.0 || foo <= 0.0 || foo >= 0.0 || foo == 0.0 || foo != 0.0) && (0.0 < foo || 0.0 > foo || 0.0 <= foo || 0.0 >= foo || 0.0 == foo || 0.0 != foo) && (foo < foo || foo > foo || foo <= foo || foo >= foo || foo == foo || foo != foo) && (foo < bar || foo > bar || foo <= bar || foo >= bar || foo == bar || foo != bar) && (foo < false || foo > false || foo <= false || foo >= false || foo == false || foo != false) && (false < foo || false > foo || false <= foo || false >= foo || false == foo || false != foo) } ", " :12:60: warning: Both sides of \"==\" are identical, is this a bug? (foo < foo || foo > foo || foo <= foo || foo >= foo || foo == foo || foo != foo) && ~~~~~~~~~~ :12:74: warning: Both sides of \"!=\" are identical, is this a bug? (foo < foo || foo > foo || foo <= foo || foo >= foo || foo == foo || foo != foo) && ~~~~~~~~~~ :15:10: error: \"<=>\" is not declared on type \"Foo\" (foo < false || foo > false || foo <= false || foo >= false || foo == false || foo != false) && ^ :15:25: error: \"<=>\" is not declared on type \"Foo\" (foo < false || foo > false || foo <= false || foo >= false || foo == false || foo != false) && ^ :15:40: error: \"<=>\" is not declared on type \"Foo\" (foo < false || foo > false || foo <= false || foo >= false || foo == false || foo != false) && ~~ :15:56: error: \"<=>\" is not declared on type \"Foo\" (foo < false || foo > false || foo <= false || foo >= false || foo == false || foo != false) && ~~ :15:68: error: No common type for \"Foo\" and \"bool\" (foo < false || foo > false || foo <= false || foo >= false || foo == false || foo != false) && ~~~~~~~~~~~~ :15:84: error: No common type for \"Foo\" and \"bool\" (foo < false || foo > false || foo <= false || foo >= false || foo == false || foo != false) && ~~~~~~~~~~~~ :16:12: error: \"<=>\" is not declared on type \"bool\" (false < foo || false > foo || false <= foo || false >= foo || false == foo || false != foo) ^ :16:27: error: \"<=>\" is not declared on type \"bool\" (false < foo || false > foo || false <= foo || false >= foo || false == foo || false != foo) ^ :16:42: error: \"<=>\" is not declared on type \"bool\" (false < foo || false > foo || false <= foo || false >= foo || false == foo || false != foo) ~~ :16:58: error: \"<=>\" is not declared on type \"bool\" (false < foo || false > foo || false <= foo || false >= foo || false == foo || false != foo) ~~ :16:68: error: No common type for \"bool\" and \"Foo\" (false < foo || false > foo || false <= foo || false >= foo || false == foo || false != foo) ~~~~~~~~~~~~ :16:84: error: No common type for \"bool\" and \"Foo\" (false < foo || false > foo || false <= foo || false >= foo || false == foo || false != foo) ~~~~~~~~~~~~ ") # Test for enum promotion to integer for other operators test(" enum Foo {} enum Bar {} def foo(foo Foo, bar Bar) List { return [ foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ] } ", " :6:5: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~ :6:14: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~ :6:23: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~ :6:32: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~ :6:41: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~ :6:50: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~ :6:59: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~ :6:68: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~ :6:77: warning: Shifting an integer by zero doesn't do anything, is this a bug? foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~~ :6:77: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~~ :6:87: warning: Shifting an integer by zero doesn't do anything, is this a bug? foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~~ :6:87: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~~ :6:97: warning: Shifting an integer by zero doesn't do anything, is this a bug? foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~~~ :6:97: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~~~ :6:108: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0, foo - 0, foo * 0, foo / 0, foo % 0, foo & 0, foo | 0, foo ^ 0, foo << 0, foo >> 0, foo >>> 0, foo <=> 0, ~~~~~~~~~ :7:5: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~ :7:14: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~ :7:23: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~ :7:32: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~ :7:41: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~ :7:50: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~ :7:59: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~ :7:68: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~ :7:77: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~~ :7:87: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~~ :7:97: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~~~ :7:108: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 + foo, 0 - foo, 0 * foo, 0 / foo, 0 % foo, 0 & foo, 0 | foo, 0 ^ foo, 0 << foo, 0 >> foo, 0 >>> foo, 0 <=> foo, ~~~~~~~~~ :9:5: error: Cannot convert from type \"double\" to type \"bool\" without a cast foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ~~~~~~~~~ :9:16: error: Cannot convert from type \"double\" to type \"bool\" without a cast foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ~~~~~~~~~ :9:27: error: Cannot convert from type \"double\" to type \"bool\" without a cast foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ~~~~~~~~~ :9:38: error: Cannot convert from type \"double\" to type \"bool\" without a cast foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ~~~~~~~~~ :9:53: error: \"%\" is not declared on type \"double\" foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ^ :9:64: error: \"&\" is not declared on type \"double\" foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ^ :9:75: error: \"|\" is not declared on type \"double\" foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ^ :9:86: error: \"^\" is not declared on type \"double\" foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ^ :9:97: error: \"<<\" is not declared on type \"double\" foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ~~ :9:109: error: \">>\" is not declared on type \"double\" foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ~~ :9:121: error: \">>>\" is not declared on type \"double\" foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ~~~ :9:130: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + 0.0, foo - 0.0, foo * 0.0, foo / 0.0, foo % 0.0, foo & 0.0, foo | 0.0, foo ^ 0.0, foo << 0.0, foo >> 0.0, foo >>> 0.0, foo <=> 0.0, ~~~~~~~~~~~ :10:5: error: Cannot convert from type \"double\" to type \"bool\" without a cast 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ~~~~~~~~~ :10:16: error: Cannot convert from type \"double\" to type \"bool\" without a cast 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ~~~~~~~~~ :10:27: error: Cannot convert from type \"double\" to type \"bool\" without a cast 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ~~~~~~~~~ :10:38: error: Cannot convert from type \"double\" to type \"bool\" without a cast 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ~~~~~~~~~ :10:53: error: \"%\" is not declared on type \"double\" 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ^ :10:64: error: \"&\" is not declared on type \"double\" 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ^ :10:75: error: \"|\" is not declared on type \"double\" 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ^ :10:86: error: \"^\" is not declared on type \"double\" 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ^ :10:97: error: \"<<\" is not declared on type \"double\" 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ~~ :10:109: error: \">>\" is not declared on type \"double\" 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ~~ :10:121: error: \">>>\" is not declared on type \"double\" 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ~~~ :10:130: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0.0 + foo, 0.0 - foo, 0.0 * foo, 0.0 / foo, 0.0 % foo, 0.0 & foo, 0.0 | foo, 0.0 ^ foo, 0.0 << foo, 0.0 >> foo, 0.0 >>> foo, 0.0 <=> foo, ~~~~~~~~~~~ :12:5: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :12:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :12:27: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :12:38: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :12:49: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :12:60: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :12:71: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :12:82: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :12:93: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~~ :12:105: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~~ :12:117: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~~~ :12:130: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~~~ :13:5: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :13:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :13:27: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :13:38: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :13:49: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :13:60: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :13:71: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :13:82: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~ :13:93: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~~ :13:105: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~~ :13:117: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~~~ :13:130: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + foo, foo - foo, foo * foo, foo / foo, foo % foo, foo & foo, foo | foo, foo ^ foo, foo << foo, foo >> foo, foo >>> foo, foo <=> foo, ~~~~~~~~~~~ :15:5: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~ :15:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~ :15:27: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~ :15:38: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~ :15:49: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~ :15:60: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~ :15:71: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~ :15:82: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~ :15:93: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~~ :15:105: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~~ :15:117: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~~~ :15:130: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo + bar, foo - bar, foo * bar, foo / bar, foo % bar, foo & bar, foo | bar, foo ^ bar, foo << bar, foo >> bar, foo >>> bar, foo <=> bar, ~~~~~~~~~~~ :16:5: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~ :16:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~ :16:27: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~ :16:38: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~ :16:49: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~ :16:60: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~ :16:71: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~ :16:82: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~ :16:93: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~~ :16:105: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~~ :16:117: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~~~ :16:130: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar + foo, bar - foo, bar * foo, bar / foo, bar % foo, bar & foo, bar | foo, bar ^ foo, bar << foo, bar >> foo, bar >>> foo, bar <=> foo, ~~~~~~~~~~~ ") # Test for recursive propagation of dynamic type context for list literals test(" const foo dynamic = [1, [1, null]] ", " ") # Test for recursive propagation of dynamic type context for map literals test(" const foo dynamic = { \"a\": 1, \"b\": { \"c\": 1, \"d\": null } } ", " ") # Check that enums work with IntMap type inference test(" enum Foo { FOO } def test bool { var foo = {Foo.FOO: \"\"} return foo } ", " :7:10: error: Cannot convert from type \"IntMap\" to type \"bool\" return foo ~~~ ") # Check for a crash due to a bug with nested guards test(" if true { if FOO == .BAR { } } enum Foo { FOO } const FOO = Foo.FOO ", " :2:14: error: \"BAR\" is not declared on type \"Foo\" if FOO == .BAR { ~~~ ") # Check for a crash due to a bug with nested guards test(" if true { if FOO == .BAR { } else { } } const FOO = Foo.FOO ", " :7:13: error: \"Foo\" is not declared const FOO = Foo.FOO ~~~ ") # Check cyclic declarations involving lambdas test(" # Cyclic declaration const a = => a() const b = (=> b() + 1)() const c = => { c() } # Not cyclic declarations const d fn() = => d() const e fn() = => { e() } ", " :2:7: error: Cyclic declaration of \"a\" const a = => a() ^ :3:7: error: Cyclic declaration of \"b\" const b = (=> b() + 1)() ^ :4:7: error: Cyclic declaration of \"c\" const c = => { c() } ^ ") # Check equality operator warnings meant to prevent bugs, but don't warn on values that may be NaN test(" def main(i int, d double, y dynamic, f fn() int, foo Foo) bool { return i + 2 == i + 2 && i + 2 != i + 2 || d + 2 == d + 2 && d + 2 != d + 2 || y + 2 == y + 2 && y + 2 != y + 2 || f() + 2 == f() + 2 && f() + 2 != f() + 2 || foo.i == foo.i && foo.i != foo.i || foo.d == foo.d && foo.d != foo.d || foo.y == foo.y && foo.y != foo.y || foo.f() == foo.f() && foo.f() != foo.f() } class Foo { var i int var d double var y dynamic var f fn() int } ", " :3:5: warning: Both sides of \"==\" are identical, is this a bug? i + 2 == i + 2 && i + 2 != i + 2 || ~~~~~~~~~~~~~~ :3:23: warning: Both sides of \"!=\" are identical, is this a bug? i + 2 == i + 2 && i + 2 != i + 2 || ~~~~~~~~~~~~~~ :7:5: warning: Both sides of \"==\" are identical, is this a bug? foo.i == foo.i && foo.i != foo.i || ~~~~~~~~~~~~~~ :7:23: warning: Both sides of \"!=\" are identical, is this a bug? foo.i == foo.i && foo.i != foo.i || ~~~~~~~~~~~~~~ ") # Check logical boolean operator warnings meant to prevent bugs test(" def main int { return (1 + 2 == 3 && 1 + 2 == 3 ? 1 : 0) + (1 + 2 == 3 || 1 + 2 == 3 ? 1 : 0) + (dynamic.foo && dynamic.foo ? 1 : 0) + (dynamic.foo || dynamic.foo ? 1 : 0) + (dynamic.bar() && dynamic.bar() ? 1 : 0) + (dynamic.bar() || dynamic.bar() ? 1 : 0) + (true ? 0 : 0) + (true ? dynamic.bar() : dynamic.bar()) } ", " :3:6: warning: Both sides of \"&&\" are identical, is this a bug? (1 + 2 == 3 && 1 + 2 == 3 ? 1 : 0) + ~~~~~~~~~~~~~~~~~~~~~~~~ :4:6: warning: Both sides of \"||\" are identical, is this a bug? (1 + 2 == 3 || 1 + 2 == 3 ? 1 : 0) + ~~~~~~~~~~~~~~~~~~~~~~~~ :5:6: warning: Both sides of \"&&\" are identical, is this a bug? (dynamic.foo && dynamic.foo ? 1 : 0) + ~~~~~~~~~~~~~~~~~~~~~~~~~~ :6:6: warning: Both sides of \"||\" are identical, is this a bug? (dynamic.foo || dynamic.foo ? 1 : 0) + ~~~~~~~~~~~~~~~~~~~~~~~~~~ :9:13: warning: Both sides of \":\" are identical, is this a bug? (true ? 0 : 0) + ~~~~~ :10:13: warning: Both sides of \":\" are identical, is this a bug? (true ? dynamic.bar() : dynamic.bar()) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ") # Make sure redundant inherited import/export annotations have appropriate warnings test(" @import class Foo { def foo } class Foo { @import def foo(x int) } @export class Bar { def foo {} } class Bar { @export def foo(x int) {} } @rename(\"Baz\") class Baz { def foo {} } class Baz { @rename(\"foo\") def foo(x int) {} } ", " :7:3: warning: Redundant annotation \"@import\" on \"foo\" is already inherited from type \"Foo\" @import ~~~~~~~ :17:3: warning: Redundant annotation \"@export\" on \"foo\" is already inherited from type \"Bar\" @export ~~~~~~~ ") test(" class Foo { def foo int { return this + that } } namespace Foo { def bar int { return this + that } } ", " :3:12: error: \"this\" is not declared (use \"self\" to refer to the object instance) return this + that ~~~~ :3:19: error: \"that\" is not declared return this + that ~~~~ :9:12: error: \"this\" is not declared return this + that ~~~~ :9:19: error: \"that\" is not declared return this + that ~~~~ :3:12: fix: Replace \"this\" with \"self\" return this + that ~~~~ [self] ") test(" def foo(x T) { foo } def foo { foo(0) } def bar(x T) { bar(0, 1) } def bar(x bool, y bool) { bar(0) } ", " :2:21: error: Cannot convert from type \"int\" to type \"bool\" without a cast def foo { foo(0) } ^ :4:23: error: Cannot convert from type \"int\" to type \"bool\" without a cast def bar(x T) { bar(0, 1) } ^ :4:26: error: Cannot convert from type \"int\" to type \"bool\" without a cast def bar(x T) { bar(0, 1) } ^ :5:37: error: Cannot convert from type \"int\" to type \"bool\" without a cast def bar(x bool, y bool) { bar(0) } ^ ") test(" def foo(x T) {} def main { foo } ", " :2:12: error: The function \"foo\" takes 1 argument and must be called def main { foo } ~~~ ") # Make sure overload filtering takes into account lambda arity test(" class Foo { def foo(arg fn(int)) def foo(arg fn()) def bar(arg fn(int)) def baz(arg fn()) } def main(foo Foo) { foo.foo(x => {}) foo.foo(=> {}) foo.bar(x => {}) foo.bar(=> {}) foo.baz(x => {}) foo.baz(=> {}) } ", " :12:11: error: Cannot convert from type \"fn()\" to type \"fn(int)\" foo.bar(=> {}) ~~~~~ :13:11: error: Unable to determine the type of \"x\" foo.baz(x => {}) ^ :13:11: error: Cannot convert from type \"fn(dynamic)\" to type \"fn()\" foo.baz(x => {}) ~~~~~~~ ") # Make sure type context is propagated from implicit "self" references test(" class Foo { def foo(arg T) } class Bar : Foo { def bar { foo(false) self.foo(false) } } ", " :7:9: error: Cannot convert from type \"bool\" to type \"int\" without a cast foo(false) ~~~~~ :8:14: error: Cannot convert from type \"bool\" to type \"int\" without a cast self.foo(false) ~~~~~ ") # Check for a crash when initializing an overloaded function test(" class Baz : Bar { } class Foo { def foo bool } class Bar : Foo { over foo bool { return 0 } } ", " :10:12: error: Cannot convert from type \"int\" to type \"bool\" without a cast return 0 ^ ") # Check for a crash when initializing an overloaded function test(" class Baz : Bar { } class Bar : Foo { over foo bool { return 0 } } class Foo { def foo bool } ", " :6:12: error: Cannot convert from type \"int\" to type \"bool\" without a cast return 0 ^ ") # Check for suspicious assignment warnings test(" class Foo { var z int } def foo Foo { return null } def main(x int, y Foo) { x = x y.z = y.z foo.z = foo.z } ", " :10:3: warning: Both sides of \"=\" are identical, is this a bug? x = x ~~~~~ :11:3: warning: Both sides of \"=\" are identical, is this a bug? y.z = y.z ~~~~~~~~~ ") # Check for suspicious assignment placement warnings test(" def main(x dynamic, y dynamic) dynamic { var d1 dynamic = x = 1 var d2 dynamic = x = false var d3 dynamic = x = y var b1 bool = x = 1 var b2 bool = x = false var b3 bool = x = y var i1 int = x = 1 var i2 int = x = false var i3 int = x = y if x = 1 {} if x = false {} if x = y {} if !(x = 1) {} if !(x = false) {} if !(x = y) {} if (x = 1) || (x = 2) {} if (x = true) || (x = false) {} if (x = y) || (y = x) {} while x = 1 {} while x = false {} while x = y {} x = (x = 1) ? x = 0 : x = 1 x = (x = false) ? x = 0 : x = 1 x = (x = y) ? x = 0 : x = 1 x = y = 1 x = y = true x = y = x return x = 1 return x = true return x = y return x++ return ++x (=> x = 1)() (=> x = true)() (=> x = y)() } ", " :6:19: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? var b1 bool = x = 1 ^ :11:18: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? var i2 int = x = false ^ :14:8: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if x = 1 {} ^ :15:8: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if x = false {} ^ :16:8: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if x = y {} ^ :18:10: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if !(x = 1) {} ^ :19:10: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if !(x = false) {} ^ :20:10: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if !(x = y) {} ^ :22:9: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if (x = 1) || (x = 2) {} ^ :22:20: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if (x = 1) || (x = 2) {} ^ :23:9: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if (x = true) || (x = false) {} ^ :23:23: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if (x = true) || (x = false) {} ^ :24:9: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if (x = y) || (y = x) {} ^ :24:20: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? if (x = y) || (y = x) {} ^ :26:11: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? while x = 1 {} ^ :27:11: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? while x = false {} ^ :28:11: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? while x = y {} ^ :30:10: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? x = (x = 1) ? x = 0 : x = 1 ^ :31:10: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? x = (x = false) ? x = 0 : x = 1 ^ :32:10: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? x = (x = y) ? x = 0 : x = 1 ^ :38:12: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? return x = 1 ^ :39:12: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? return x = true ^ :40:12: warning: Use of \"=\" here looks like a bug, did you mean to use \"==\"? return x = y ^ :2:7: warning: Local variable \"d1\" is never read var d1 dynamic = x = 1 ~~ :3:7: warning: Local variable \"d2\" is never read var d2 dynamic = x = false ~~ :4:7: warning: Local variable \"d3\" is never read var d3 dynamic = x = y ~~ :6:7: warning: Local variable \"b1\" is never read var b1 bool = x = 1 ~~ :7:7: warning: Local variable \"b2\" is never read var b2 bool = x = false ~~ :8:7: warning: Local variable \"b3\" is never read var b3 bool = x = y ~~ :10:7: warning: Local variable \"i1\" is never read var i1 int = x = 1 ~~ :11:7: warning: Local variable \"i2\" is never read var i2 int = x = false ~~ :12:7: warning: Local variable \"i3\" is never read var i3 int = x = y ~~ ") # Check math functions with dynamic inputs, overloads shouldn't be ambiguous test(" def main(x dynamic, y dynamic) { Math.abs(x) Math.acos(x) Math.asin(x) Math.atan(x) Math.atan2(x, y) Math.ceil(x) Math.cos(x) Math.exp(x) Math.floor(x) Math.log(x) Math.max(x, y) Math.min(x, y) Math.pow(x, y) Math.round(x) Math.sin(x) Math.sqrt(x) Math.tan(x) } ", " ") # Check storage with dynamic variables test(" def main(x dynamic, y string, z int) { x = x x += x x[0] += z x++ ++x y = y y += y y[0] += z z++ ++z } ", " :2:3: warning: Both sides of \"=\" are identical, is this a bug? x = x ~~~~~ :7:3: warning: Both sides of \"=\" are identical, is this a bug? y = y ~~~~~ :9:3: error: Cannot store to this location y[0] += z ~~~~ ") # Check setter scope lookup test(" class Foo { var foo = 0 var bar = 0 def test { foo = false bar = false } } def foo=(x string) {} def bar double { return 0 } def bar=(x string) {} var _foo = 0 var _bar = 0 class Bar { def test { foo = false bar = false _foo = false _bar = false } def _foo=(x string) {} def _bar double { return 0 } def _bar=(x string) {} } class Baz : Bar { over test { _foo = false _bar = false } } ", " :5:11: error: Cannot convert from type \"bool\" to type \"int\" without a cast foo = false ~~~~~ :6:11: error: Cannot convert from type \"bool\" to type \"int\" without a cast bar = false ~~~~~ :19:11: error: Cannot convert from type \"bool\" to type \"string\" foo = false ~~~~~ :20:11: error: Cannot convert from type \"bool\" to type \"string\" bar = false ~~~~~ :21:12: error: Cannot convert from type \"bool\" to type \"string\" _foo = false ~~~~~ :22:12: error: Cannot convert from type \"bool\" to type \"string\" _bar = false ~~~~~ :32:12: error: Cannot convert from type \"bool\" to type \"string\" _foo = false ~~~~~ :33:12: error: Cannot convert from type \"bool\" to type \"string\" _bar = false ~~~~~ ") # Forbid default values in function arguments test(" def foo(x bool = true) {} ", " :1:16: error: Optional arguments aren't supported yet def foo(x bool = true) {} ~~~~~~ ") # Forbid default values in function arguments test(" var foo = (x bool = true) => {} ", " :1:19: error: Optional arguments aren't supported yet var foo = (x bool = true) => {} ~~~~~~ ") # Merging shouldn't be allowed between enums and flags test(" enum Foo { A, B } flags Foo { C, D } flags Bar { A, B } enum Bar { C, D } ", " :2:7: error: \"Foo\" is already declared flags Foo { C, D } ~~~ :1:6: note: The previous declaration is here enum Foo { A, B } ~~~ :4:6: error: \"Bar\" is already declared enum Bar { C, D } ~~~ :3:7: note: The previous declaration is here flags Bar { A, B } ~~~ ") # Check for bugs with symbol merging and generics test(" class Foo { def foo(cb fn(X, Y) Y) Foo def foo(cb fn(X, Z) Z) Foo } def main(x Foo) { x.foo((x, y) => x) } ", " :7:25: error: Cannot convert from type \"int\" to type \"bool\" without a cast x.foo((x, y) => x) ^ ") # Check for bugs with symbol merging and generics test(" class Foo { def foo(x Y) def foo(x Z) Foo def bar(x Y) Foo def bar(x Z) } ", " :3:7: error: Duplicate overloaded function \"foo\" def foo(x Z) Foo ~~~ :2:7: note: The previous declaration is here def foo(x Y) ~~~ :6:7: error: Duplicate overloaded function \"bar\" def bar(x Z) ~~~ :5:7: note: The previous declaration is here def bar(x Y) Foo ~~~ ") # Test storage check for auto-implemented operators test(" @import class Foo { def *(x int) Foo def foo Foo } def main(foo Foo) { foo.foo *= 2 } ", " :8:3: error: Cannot store to this location foo.foo *= 2 ~~~~~~~ ") # Test storage check for auto-implemented operators test(" @import class Foo { def new def *(x int) Foo } def main { const foo = Foo.new foo *= 2 } ", " :9:3: error: Cannot store to constant symbol \"foo\" foo *= 2 ~~~ :8:9: warning: Local variable \"foo\" is never read const foo = Foo.new ~~~ ") # Test auto-implemented index operator test(" @import class Foo { } def main(foo Foo) { foo[0] *= 2 } ", " :6:6: error: \"[]\" is not declared on type \"Foo\" foo[0] *= 2 ~~~ ") # Test auto-implemented index operator test(" @import class Foo { def [](x int) int } def main(foo Foo) { foo[0] *= 2 } ", " :7:3: error: Cannot store to this location foo[0] *= 2 ~~~~~~ ") # Test auto-implemented index operator test(" @import class Foo { def *(x int) int def [](x int) Foo def []=(x int, y bool) } def main(foo Foo) { foo[0] *= 2 } ", " :9:3: error: Cannot convert from type \"int\" to type \"bool\" without a cast foo[0] *= 2 ~~~~~~~~~~~ ") # Test auto-implemented index operator test(" @import class Foo { def *(x int) def [](x int) Foo def []=(x int, y bool) } def main(foo Foo) { foo[0] *= 2 } ", " :9:3: error: The function \"*\" does not return a value foo[0] *= 2 ~~~~~~~~~~~ :3:7: note: The function declaration is here def *(x int) ^ ") # Test auto-implemented unary assignment operators test(" class Foo { def +(x int) bool def -(x int) string } class Bar { def +(x bool) Bar def -(x string) Bar } def main(foo Foo, bar Bar) { foo++ foo-- bar++ bar-- } ", " :12:3: error: Cannot convert from type \"bool\" to type \"Foo\" foo++ ~~~~~ :13:3: error: Cannot convert from type \"string\" to type \"Foo\" foo-- ~~~~~ :14:6: error: Cannot convert from type \"int\" to type \"bool\" without a cast bar++ ~~ :15:6: error: Cannot convert from type \"int\" to type \"string\" bar-- ~~ ") # Test that assignment operators don't trigger "local variable is never read" warnings test(" class Foo { } def test { var a = Foo.new var b = Foo.new a = null a = b = null } ", " :5:7: warning: Local variable \"a\" is never read var a = Foo.new ^ ") # Test that auto-implemented assignment operators don't trigger "local variable is never read" warnings test(" class Foo { def +(x int) Foo { return self } } def test { var a = Foo.new var b = Foo.new a += 1 a = b += 1 } ", " :6:7: warning: Local variable \"a\" is never read var a = Foo.new ^ ") # Test the null join operator on primitive types test(" def main(foo int) { foo = foo ?? 0 } ", " :2:9: error: No common type for \"int\" and \"null\" foo = foo ?? 0 ~~~ ") # Test the null join operator test(" class Foo { } def main(foo Foo) Foo { return foo ?? Foo.new } ", " ") # Test type context for the null join operator test(" class Foo { } def main(foo Foo) int { return foo ?? Foo.new } ", " :5:10: error: Cannot convert from type \"Foo\" to type \"int\" return foo ?? Foo.new ~~~ :5:17: error: Cannot convert from type \"Foo\" to type \"int\" return foo ?? Foo.new ~~~~~~~ ") # Test warnings for the null join operator test(" class Foo { var x Foo = null } def main(foo Foo) { foo ?? foo foo ?? foo.x foo.x ?? foo foo.x ?? foo.x } ", " :6:3: warning: Both sides of \"??\" are identical, is this a bug? foo ?? foo ~~~~~~~~~~ :6:3: warning: Unused expression foo ?? foo ~~~ :6:10: warning: Unused expression foo ?? foo ~~~ :7:3: warning: Unused expression foo ?? foo.x ~~~ :7:10: warning: Unused expression foo ?? foo.x ~~~~~ :8:3: warning: Unused expression foo.x ?? foo ~~~~~ :8:12: warning: Unused expression foo.x ?? foo ~~~ :9:3: warning: Both sides of \"??\" are identical, is this a bug? foo.x ?? foo.x ~~~~~~~~~~~~~~ :9:3: warning: Unused expression foo.x ?? foo.x ~~~~~ :9:12: warning: Unused expression foo.x ?? foo.x ~~~~~ ") # Test warnings for the null dot operator test(" class Foo { var a Foo = null def b Foo { return self } def d(x int) Foo { return self } var c fn() Foo = => null } def main(foo Foo) { foo?.a?.a foo?.b?.b foo?.c()?.c() foo?.d(0)?.d(0) } ", " :9:3: warning: Unused expression foo?.a?.a ~~~~~~~~~ ") # Test warnings for the null assignment operator test(" class Foo { var x Foo = null } def main(foo Foo) { foo ?= foo foo ?= foo.x foo.x ?= foo foo.x ?= foo.x main(foo ?= foo) main(foo ?= foo.x) main(foo.x ?= foo) main(foo.x ?= foo.x) } ", " :6:3: warning: Both sides of \"?=\" are identical, is this a bug? foo ?= foo ~~~~~~~~~~ :9:3: warning: Both sides of \"?=\" are identical, is this a bug? foo.x ?= foo.x ~~~~~~~~~~~~~~ :11:8: warning: Both sides of \"?=\" are identical, is this a bug? main(foo ?= foo) ~~~~~~~~~~ :14:8: warning: Both sides of \"?=\" are identical, is this a bug? main(foo.x ?= foo.x) ~~~~~~~~~~~~~~ ") # Test top-level use of the null dot operator test(" class Foo { def foo {} def bar int { return 0 } def baz string { return null } } def main(foo Foo) { foo?.foo foo?.bar foo?.baz } ", " ") # Test unary increment operators on strings test(" def main { var x = \"x\" x[0]++ x[0]-- ++x[0] --x[0] } ", " :3:3: error: Cannot store to this location x[0]++ ~~~~ :4:3: error: Cannot store to this location x[0]-- ~~~~ :5:5: error: Cannot store to this location ++x[0] ~~~~ :6:5: error: Cannot store to this location --x[0] ~~~~ ") # Test null assignment operator test(" class Foo { } def foo Foo { return Foo.new } def main { foo ?= foo var bar = foo ?= foo } ", " :9:3: error: Cannot store to this location foo ?= foo ~~~ :10:13: error: Cannot store to this location var bar = foo ?= foo ~~~ :10:7: warning: Local variable \"bar\" is never read var bar = foo ?= foo ~~~ ") # Test string interpolation on different types test(" class NoToString {} class WrongToString { def toString NoToString { return null } } class CorrectToString { def toString string { return null } } var x = \"\\(NoToString.new)\" var y = \"\\(WrongToString.new)\" var z = \"\\(CorrectToString.new)\" ", " :5:12: error: \"toString\" is not declared on type \"NoToString\" var x = \"\\(NoToString.new)\" ~~~~~~~~~~~~~~ :6:12: error: Cannot convert from type \"NoToString\" to type \"string\" var y = \"\\(WrongToString.new)\" ~~~~~~~~~~~~~~~~~ ") # Test error messages about forbidden operator customizations test(" class Foo { def ==(x bool) def !=(x bool) def <(x bool) def >(x bool) def <=(x bool) def >=(x bool) def &&(x bool) def ||(x bool) def =(x bool) } ", " :2:7: error: The \"==\" operator is not customizable because that wouldn't work with generics, which are implemented with type erasure def ==(x bool) ~~ :3:7: error: The \"!=\" operator is not customizable because that wouldn't work with generics, which are implemented with type erasure def !=(x bool) ~~ :4:7: error: The \"<\" operator is not customizable because it's automatically implemented using the \"<=>\" operator (customize the \"<=>\" operator instead) def <(x bool) ^ :5:7: error: The \">\" operator is not customizable because it's automatically implemented using the \"<=>\" operator (customize the \"<=>\" operator instead) def >(x bool) ^ :6:7: error: The \"<=\" operator is not customizable because it's automatically implemented using the \"<=>\" operator (customize the \"<=>\" operator instead) def <=(x bool) ~~ :7:7: error: The \">=\" operator is not customizable because it's automatically implemented using the \"<=>\" operator (customize the \"<=>\" operator instead) def >=(x bool) ~~ :8:7: error: The \"&&\" operator is not customizable because of its special short-circuit evaluation behavior def &&(x bool) ~~ :9:7: error: The \"||\" operator is not customizable because of its special short-circuit evaluation behavior def ||(x bool) ~~ :10:7: error: The \"=\" operator is not customizable because value types are not supported by the language def =(x bool) ^ ") # Test the argument count error for incorrect implicit calls test(" class Foo { def foo(x int) def bar(x int) def bar(x int) {} def baz(x int) def baz(x double) {} def baz2(x int, y int, z int) def baz2(x double, y double) {} def baz3(x int, y int) def baz3(x double, y double, z double) {} } def main(foo Foo) { foo.foo foo.bar foo.baz foo.baz2 foo.baz3 } ", " :14:7: error: The function \"foo\" takes 1 argument and must be called foo.foo ~~~ :15:7: error: The function \"bar\" takes 1 argument and must be called foo.bar ~~~ :16:7: error: The function \"baz\" takes 1 argument and must be called foo.baz ~~~ :17:7: error: The function \"baz2\" takes between 2 and 3 arguments and must be called foo.baz2 ~~~~ :18:7: error: The function \"baz3\" takes between 2 and 3 arguments and must be called foo.baz3 ~~~~ ") # Test XML literals test(" var foo = var foo2 = var bar = var bar2 = var baz = var baz2 = var i = var nope = class Foo { class Bar { } } @import var Nope dynamic ", " :5:16: error: \"Baz\" is not declared on type \"Foo\", did you mean \"Bar\"? var baz = ~~~ :11:9: note: \"Bar\" is defined here class Bar { ~~~ :6:17: error: \"Baz\" is not declared on type \"Foo\", did you mean \"Bar\"? var baz2 = ~~~ :11:9: note: \"Bar\" is defined here class Bar { ~~~ :7:9: error: Cannot construct type \"int\" var i = ~~~~~~ :8:12: error: Cannot construct type \"dynamic\" var nope = ~~~~~~~ :5:16: fix: Replace with \"Bar\" var baz = ~~~ [Bar] :6:17: fix: Replace with \"Bar\" var baz2 = ~~~ [Bar] ") # Test child element function error test(" var foo = var foo2 = 1 var bar = var bar2 = 1 class Foo { } class Bar { def <>...(x int) {} } ", " :2:17: error: Implement a function called \"<>...\" on type \"Foo\" to add support for child elements var foo2 = 1 ^ ") # Test XML literal return types test(" var foo = .foo var foo2 = .foo var foo3 = .foo var foo4 = .foo class Foo { def <>...(x Foo) {} } ", " :1:18: error: \"foo\" is not declared on type \"Foo\" var foo = .foo ~~~ :2:24: error: \"foo\" is not declared on type \"Foo\" var foo2 = .foo ~~~ :3:30: error: \"foo\" is not declared on type \"Foo\" var foo3 = .foo ~~~ :4:17: error: \"foo\" is not declared on type \"Foo\" var foo4 = .foo ~~~ :4:25: error: \"foo\" is not declared on type \"Foo\" var foo4 = .foo ~~~ ") # Test multi-line XML literals test(" var foo = < Foo/> ", " :1:12: error: Expected identifier but found newline var foo = < ^ ") # Test multi-line XML literals test(" var foo = ", " :1:18: error: Expected identifier but found newline var foo = class Foo { def <>...(x Foo) {} } ", " ") # Test XML literal with child comment test(" var foo = null # Comment ", " :2:4: error: \"Foo\" is not declared ~~~ ") # Test XML literal with child comment test(" var foo = null # Comment ", " :2:4: error: \"Foo\" is not declared ~~~ ") # Test XML literal with child comment test(" var foo = # Comment null ", " :2:4: error: \"Foo\" is not declared ~~~ ") # Test XML children precedence test(" var foo = bar bar.bar ++bar bar++ bar + bar (bar + bar) bar +bar # This is the second of two expressions, not a single expression if true { bar + bar } var bar = 0 class Foo { def <>...(x Foo) {} } ", " :8:10: warning: Unnecessary parentheses (bar + bar) ~~~~~~~~~~~ :3:10: error: Cannot convert from type \"int\" to type \"Foo\" bar ~~~ :4:14: error: \"bar\" is not declared on type \"int\" bar.bar ~~~ :5:10: error: Cannot convert from type \"int\" to type \"Foo\" ++bar ~~~~~ :6:10: error: Cannot convert from type \"int\" to type \"Foo\" bar++ ~~~~~ :7:10: error: Cannot convert from type \"int\" to type \"Foo\" bar + bar ~~~~~~~~~ :8:10: error: Cannot convert from type \"int\" to type \"Foo\" (bar + bar) ~~~~~~~~~~~ :10:7: error: Cannot convert from type \"int\" to type \"Foo\" bar ~~~ :11:7: error: Cannot convert from type \"int\" to type \"Foo\" +bar # This is the second of two expressions, not a single expression ~~~~ :13:20: error: Cannot convert from type \"int\" to type \"Foo\" if true { bar + bar } ~~~~~~~~~ :8:10: fix: Remove parentheses (bar + bar) ~~~~~~~~~~~ [ bar + bar ] ") # Test XML nested children test(" var foo = if true { 0 } else if true { 1 } else { 2 } for i = 0; i < 5; i++ { i } for i in [0, 1, 2, 3, 4] { i } for i in 0..5 { i } while Math.random < 0.5 { 0 } switch (Math.random * 3) as int { case 0 { 0 } case 1, 2 { 1 } default { 2 } } try { 0 } catch e dynamic { 1 } finally { 2 } (=> { 0 })() class Foo { def <>...(x bool) {} } ", " :3:15: error: Cannot convert from type \"int\" to type \"bool\" without a cast if true { 0 } ^ :4:20: error: Cannot convert from type \"int\" to type \"bool\" without a cast else if true { 1 } ^ :5:12: error: Cannot convert from type \"int\" to type \"bool\" without a cast else { 2 } ^ :7:7: error: Cannot convert from type \"int\" to type \"bool\" without a cast i ^ :10:7: error: Cannot convert from type \"int\" to type \"bool\" without a cast i ^ :13:7: error: Cannot convert from type \"int\" to type \"bool\" without a cast i ^ :16:7: error: Cannot convert from type \"int\" to type \"bool\" without a cast 0 ^ :19:16: error: Cannot convert from type \"int\" to type \"bool\" without a cast case 0 { 0 } ^ :20:19: error: Cannot convert from type \"int\" to type \"bool\" without a cast case 1, 2 { 1 } ^ :21:17: error: Cannot convert from type \"int\" to type \"bool\" without a cast default { 2 } ^ :23:11: error: Cannot convert from type \"int\" to type \"bool\" without a cast try { 0 } ^ :24:23: error: Cannot convert from type \"int\" to type \"bool\" without a cast catch e dynamic { 1 } ^ :25:15: error: Cannot convert from type \"int\" to type \"bool\" without a cast finally { 2 } ^ :26:11: warning: Unused expression (=> { 0 })() ^ :26:6: error: All control paths for \"\" must return a value of type \"bool\" (=> { 0 })() ~~~~~~~~ ") # Test XML attribute type checking test(" var foo = class Foo { var foo = 0 var bar Bar = .FOO def baz=(x int) {} } enum Bar { FOO BAR } ", " :2:7: error: Cannot convert from type \"bool\" to type \"int\" without a cast foo=false ~~~~~ :3:7: error: Cannot convert from type \"bool\" to type \"Bar\" bar=false ~~~~~ :4:7: error: Cannot convert from type \"bool\" to type \"int\" without a cast baz=false ~~~~~ :5:3: error: \"nope\" is not declared on type \"Foo\" nope=0 ~~~~ ") # Test complex XML-like attributes test(" var foo = class Foo { var foo Foo = null var bar Foo = null var baz = 0 } ", " :2:11: error: Cannot convert from type \"int\" to type \"Foo\" foo.bar=100 ~~~ :3:8: error: Cannot convert from type \"bool\" to type \"int\" without a cast baz+=false ~~~~~ ") # Check that comments are allowed in different places in a call expression test(" def main { foo( # 1 2, # 3 4 # 5 , 6 # 7 ) } ", " :2:3: error: \"foo\" is not declared foo( ~~~ ") # Check return statements with a newline between the keyword and the value test(" def foo int { return false } def foo2 int { return } def bar { return false } def bar2 { return } ", " :3:3: error: Cannot convert from type \"bool\" to type \"int\" without a cast false ~~~~~ :7:3: error: Must return a value of type \"int\" return ~~~~~~ :12:3: warning: Unused expression false ~~~~~ ") # Check parsing of contextual keywords test(" class Foo { var class = 0 var def = 0 var enum = 0 var interface = 0 var namespace = 0 var over = 0 } var foo = def bar { var class = 2 var def = 2 var enum = 2 var interface = 2 var namespace = 2 var over = 2 class = 3 def = 3 enum = 3 interface = 3 namespace = 3 over = 3 } ", " :20:7: warning: Local variable \"class\" is never read var class = 2 ~~~~~ :21:7: warning: Local variable \"def\" is never read var def = 2 ~~~ :22:7: warning: Local variable \"enum\" is never read var enum = 2 ~~~~ :23:7: warning: Local variable \"interface\" is never read var interface = 2 ~~~~~~~~~ :24:7: warning: Local variable \"namespace\" is never read var namespace = 2 ~~~~~~~~~ :25:7: warning: Local variable \"over\" is never read var over = 2 ~~~~ ") # The keyword "var" is a token, not a contextual keyword test(" var var = 0 ", " :1:5: error: Expected identifier but found \"var\" var var = 0 ~~~ ") # The keyword "const" is a token, not a contextual keyword test(" var const = 0 ", " :1:5: error: Expected identifier but found \"const\" var const = 0 ~~~~~ ") # Check for flags overflow test(" flags Foo { X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X27 X28 X29 X30 X31 X32 X33 } flags Bar { X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 } flags Bar { X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 } flags Bar { X20 X21 X22 X23 X24 X25 X26 X27 X28 X29 } flags Bar { X30 X31 X32 X33 } ", " :37:3: error: The type \"Foo\" cannot have more than 32 flags X32 ~~~ :38:3: error: The type \"Foo\" cannot have more than 32 flags X33 ~~~ :83:3: error: The type \"Bar\" cannot have more than 32 flags X32 ~~~ :84:3: error: The type \"Bar\" cannot have more than 32 flags X33 ~~~ ") # Check for flags type conversions test(" flags Foo { FOO BAR } @export def test List { var foo = Foo.FOO foo |= 0 foo |= ~0 foo |= 1 foo |= .FOO foo |= ~.FOO foo &= ~.FOO foo ^= ~.FOO foo += ~.FOO return [ 0, ~0, 1, -1, ~.FOO, .FOO in Foo.BAR, .FOO & .BAR, .FOO | .BAR, .FOO ^ .BAR, foo |= ~.FOO, foo &= ~.FOO, foo ^= ~.FOO, foo += ~.FOO, ] } ", " :16:7: error: \"+=\" is not declared on type \"Foo\" foo += ~.FOO ~~ :16:11: error: Cannot access \"FOO\" without type context foo += ~.FOO ~~~~ :20:5: error: Cannot convert from type \"int\" to type \"Foo\" without a cast 1, ^ :21:5: error: Cannot convert from type \"int\" to type \"Foo\" without a cast -1, ~~ :23:5: error: Cannot convert from type \"bool\" to type \"Foo\" .FOO in Foo.BAR, ~~~~~~~~~~~~~~~ :30:9: error: \"+=\" is not declared on type \"Foo\" foo += ~.FOO, ~~ :30:13: error: Cannot access \"FOO\" without type context foo += ~.FOO, ~~~~ ") # Check for flags-style enum type conversions test(" enum Foo { FOO BAR } @export def test List { var foo = Foo.FOO foo |= 0 foo |= ~0 foo |= 1 foo |= .FOO foo |= ~.FOO foo &= ~.FOO foo ^= ~.FOO foo += ~.FOO return [ 0, ~0, 1, -1, ~.FOO, .FOO in Foo.BAR, .FOO & .BAR, .FOO | .BAR, .FOO ^ .BAR, foo |= ~.FOO, foo &= ~.FOO, foo ^= ~.FOO, foo += ~.FOO, ] } ", " :9:7: error: \"|=\" is not declared on type \"Foo\" foo |= 0 ~~ :10:7: error: \"|=\" is not declared on type \"Foo\" foo |= ~0 ~~ :11:7: error: \"|=\" is not declared on type \"Foo\" foo |= 1 ~~ :12:7: error: \"|=\" is not declared on type \"Foo\" foo |= .FOO ~~ :12:10: error: Cannot access \"FOO\" without type context foo |= .FOO ~~~~ :13:7: error: \"|=\" is not declared on type \"Foo\" foo |= ~.FOO ~~ :13:11: error: Cannot access \"FOO\" without type context foo |= ~.FOO ~~~~ :14:7: error: \"&=\" is not declared on type \"Foo\" foo &= ~.FOO ~~ :14:11: error: Cannot access \"FOO\" without type context foo &= ~.FOO ~~~~ :15:7: error: \"^=\" is not declared on type \"Foo\" foo ^= ~.FOO ~~ :15:11: error: Cannot access \"FOO\" without type context foo ^= ~.FOO ~~~~ :16:7: error: \"+=\" is not declared on type \"Foo\" foo += ~.FOO ~~ :16:11: error: Cannot access \"FOO\" without type context foo += ~.FOO ~~~~ :18:5: error: Cannot convert from type \"int\" to type \"Foo\" without a cast 0, ^ :19:5: error: Cannot convert from type \"int\" to type \"Foo\" without a cast ~0, ~~ :20:5: error: Cannot convert from type \"int\" to type \"Foo\" without a cast 1, ^ :21:5: error: Cannot convert from type \"int\" to type \"Foo\" without a cast -1, ~~ :22:6: error: Cannot access \"FOO\" without type context ~.FOO, ~~~~ :23:5: error: Cannot access \"FOO\" without type context .FOO in Foo.BAR, ~~~~ :23:10: error: \"in\" is not declared on type \"Foo\" .FOO in Foo.BAR, ~~ :24:5: error: Cannot access \"FOO\" without type context .FOO & .BAR, ~~~~ :24:12: error: Cannot access \"BAR\" without type context .FOO & .BAR, ~~~~ :25:5: error: Cannot access \"FOO\" without type context .FOO | .BAR, ~~~~ :25:12: error: Cannot access \"BAR\" without type context .FOO | .BAR, ~~~~ :26:5: error: Cannot access \"FOO\" without type context .FOO ^ .BAR, ~~~~ :26:12: error: Cannot access \"BAR\" without type context .FOO ^ .BAR, ~~~~ :27:9: error: \"|=\" is not declared on type \"Foo\" foo |= ~.FOO, ~~ :27:13: error: Cannot access \"FOO\" without type context foo |= ~.FOO, ~~~~ :28:9: error: \"&=\" is not declared on type \"Foo\" foo &= ~.FOO, ~~ :28:13: error: Cannot access \"FOO\" without type context foo &= ~.FOO, ~~~~ :29:9: error: \"^=\" is not declared on type \"Foo\" foo ^= ~.FOO, ~~ :29:13: error: Cannot access \"FOO\" without type context foo ^= ~.FOO, ~~~~ :30:9: error: \"+=\" is not declared on type \"Foo\" foo += ~.FOO, ~~ :30:13: error: Cannot access \"FOO\" without type context foo += ~.FOO, ~~~~ ") # Test for a crash due to a missing symbol initialization test(" namespace Baz { def test { Bar.new } } class Bar :: Foo { def foo def bar } interface Foo { def foo def bar } ", " :3:5: error: Cannot construct abstract type \"Bar\" Bar.new ~~~~~~~ :8:7: note: The type \"Bar\" is abstract due to member \"foo\" def foo ~~~ ") # The null dot operator shouldn't require a value inside a lambda expression test(" def test(x Foo, y Foo) { x?.z (=> x?.z)() (() int => x?.z)() x ?= y (=> x ?= y)() (() int => x ?= y)() } class Foo { def z {} } ", " :4:14: error: The function \"z\" does not return a value (() int => x?.z)() ~~~~ :12:7: note: The function declaration is here def z {} ^ :8:14: error: Cannot convert from type \"Foo\" to type \"int\" (() int => x ?= y)() ^ :8:14: error: Cannot convert from type \"Foo\" to type \"int\" (() int => x ?= y)() ~~~~~~ ") # Check member typo corrections test(" def test(x Foo) { x.string x.toStrong x.tostring x.to_string x.to x.blah x.blong x.tostr x.toStr x.to_str x.bloblobloblob } class Foo { def toString string { return \"\" } } ", " :2:5: error: \"string\" is not declared on type \"Foo\", did you mean \"toString\"? x.string ~~~~~~ :17:7: note: \"toString\" is defined here def toString string { ~~~~~~~~ :3:5: error: \"toStrong\" is not declared on type \"Foo\", did you mean \"toString\"? x.toStrong ~~~~~~~~ :17:7: note: \"toString\" is defined here def toString string { ~~~~~~~~ :4:5: error: \"tostring\" is not declared on type \"Foo\", did you mean \"toString\"? x.tostring ~~~~~~~~ :17:7: note: \"toString\" is defined here def toString string { ~~~~~~~~ :5:5: error: \"to_string\" is not declared on type \"Foo\", did you mean \"toString\"? x.to_string ~~~~~~~~~ :17:7: note: \"toString\" is defined here def toString string { ~~~~~~~~ :7:5: error: \"to\" is not declared on type \"Foo\" x.to ~~ :8:5: error: \"blah\" is not declared on type \"Foo\" x.blah ~~~~ :9:5: error: \"blong\" is not declared on type \"Foo\" x.blong ~~~~~ :10:5: error: \"tostr\" is not declared on type \"Foo\" x.tostr ~~~~~ :11:5: error: \"toStr\" is not declared on type \"Foo\" x.toStr ~~~~~ :12:5: error: \"to_str\" is not declared on type \"Foo\" x.to_str ~~~~~~ :13:5: error: \"bloblobloblob\" is not declared on type \"Foo\" x.bloblobloblob ~~~~~~~~~~~~~ :2:5: fix: Replace with \"toString\" x.string ~~~~~~ [toString] :3:5: fix: Replace with \"toString\" x.toStrong ~~~~~~~~ [toString] :4:5: fix: Replace with \"toString\" x.tostring ~~~~~~~~ [toString] :5:5: fix: Replace with \"toString\" x.to_string ~~~~~~~~~ [toString] ") # Avoid typo corrections for mismatched static/instance contexts test(" def test(x Foo) { x.bar Foo.Foo } class Foo { var foo = 0 namespace Bar { } } ", " :2:5: error: \"bar\" is not declared on type \"Foo\" x.bar ~~~ :3:7: error: \"Foo\" is not declared on type \"Foo\" Foo.Foo ~~~ ") # Type corrections for global names from the derived class test(" class Foo : Bar { def test { abc xyz abb xyy } } class Bar { } namespace Bar { var abc int def xyz {} } ", " :3:5: error: \"abc\" is not declared, did you mean \"Bar.abc\"? abc ~~~ :14:7: note: \"Bar.abc\" is defined here var abc int ~~~ :4:5: error: \"xyz\" is not declared, did you mean \"Bar.xyz\"? xyz ~~~ :15:7: note: \"Bar.xyz\" is defined here def xyz {} ~~~ :5:5: error: \"abb\" is not declared, did you mean \"Bar.abc\"? abb ~~~ :14:7: note: \"Bar.abc\" is defined here var abc int ~~~ :6:5: error: \"xyy\" is not declared, did you mean \"Bar.xyz\"? xyy ~~~ :15:7: note: \"Bar.xyz\" is defined here def xyz {} ~~~ :3:5: fix: Replace with \"Bar.abc\" abc ~~~ [Bar.abc] :4:5: fix: Replace with \"Bar.xyz\" xyz ~~~ [Bar.xyz] :5:5: fix: Replace with \"Bar.abc\" abb ~~~ [Bar.abc] :6:5: fix: Replace with \"Bar.xyz\" xyy ~~~ [Bar.xyz] ") # Check name typo corrections test(" class Foo { def test { var x Foot = Foot.new var y tset # This shouldn't match because it's a type context Foot tset # This should match because this is an instance context } } namespace Foo { def foo { var x Foot = Foot.new var y tset # This shouldn't match because it's a type context Foot tset # This shouldn't match because this isn't an instance context } } ", " :3:11: error: \"Foot\" is not declared, did you mean \"Foo\"? var x Foot = Foot.new ~~~~ :1:7: note: \"Foo\" is defined here class Foo { ~~~ :3:18: error: \"Foot\" is not declared, did you mean \"Foo\"? var x Foot = Foot.new ~~~~ :1:7: note: \"Foo\" is defined here class Foo { ~~~ :4:11: error: \"tset\" is not declared var y tset # This shouldn't match because it's a type context ~~~~ :5:5: error: \"Foot\" is not declared, did you mean \"Foo\"? Foot ~~~~ :1:7: note: \"Foo\" is defined here class Foo { ~~~ :6:5: error: \"tset\" is not declared, did you mean \"test\"? tset # This should match because this is an instance context ~~~~ :2:7: note: \"test\" is defined here def test { ~~~~ :12:11: error: \"Foot\" is not declared, did you mean \"Foo\"? var x Foot = Foot.new ~~~~ :1:7: note: \"Foo\" is defined here class Foo { ~~~ :12:18: error: \"Foot\" is not declared, did you mean \"Foo\"? var x Foot = Foot.new ~~~~ :1:7: note: \"Foo\" is defined here class Foo { ~~~ :13:11: error: \"tset\" is not declared var y tset # This shouldn't match because it's a type context ~~~~ :14:5: error: \"Foot\" is not declared, did you mean \"Foo\"? Foot ~~~~ :1:7: note: \"Foo\" is defined here class Foo { ~~~ :15:5: error: \"tset\" is not declared tset # This shouldn't match because this isn't an instance context ~~~~ :3:9: warning: Local variable \"x\" is never read var x Foot = Foot.new ^ :4:9: warning: Local variable \"y\" is never read var y tset # This shouldn't match because it's a type context ^ :12:9: warning: Local variable \"x\" is never read var x Foot = Foot.new ^ :13:9: warning: Local variable \"y\" is never read var y tset # This shouldn't match because it's a type context ^ :3:11: fix: Replace with \"Foo\" var x Foot = Foot.new ~~~~ [Foo] :3:18: fix: Replace with \"Foo\" var x Foot = Foot.new ~~~~ [Foo] :5:5: fix: Replace with \"Foo\" Foot ~~~~ [Foo] :6:5: fix: Replace with \"test\" tset # This should match because this is an instance context ~~~~ [test] :12:11: fix: Replace with \"Foo\" var x Foot = Foot.new ~~~~ [Foo] :12:18: fix: Replace with \"Foo\" var x Foot = Foot.new ~~~~ [Foo] :14:5: fix: Replace with \"Foo\" Foot ~~~~ [Foo] ") # Type corrections shouldn't suggest symbols that are currently being initialized test(" var foo = Bar var bar = Bar var fooo = Fooo ", " :1:11: error: \"Bar\" is not declared, did you mean \"bar\"? var foo = Bar ~~~ :2:5: note: \"bar\" is defined here var bar = Bar ~~~ :2:11: error: \"Bar\" is not declared var bar = Bar ~~~ :3:12: error: \"Fooo\" is not declared, did you mean \"foo\"? var fooo = Fooo ~~~~ :1:5: note: \"foo\" is defined here var foo = Bar ~~~ :1:11: fix: Replace with \"bar\" var foo = Bar ~~~ [bar] :3:12: fix: Replace with \"foo\" var fooo = Fooo ~~~~ [foo] ") # Check case-sensitive typo corrections test(" var Foo = 0 var FOo = 0 var bar = 0 var a = foo var b = FOO var c = fOO var d = foO var e = BAR ", " :4:9: error: \"foo\" is not declared, did you mean \"Foo\"? var a = foo ~~~ :1:5: note: \"Foo\" is defined here var Foo = 0 ~~~ :5:9: error: \"FOO\" is not declared, did you mean \"FOo\"? var b = FOO ~~~ :2:5: note: \"FOo\" is defined here var FOo = 0 ~~~ :6:9: error: \"fOO\" is not declared, did you mean \"FOo\"? var c = fOO ~~~ :2:5: note: \"FOo\" is defined here var FOo = 0 ~~~ :7:9: error: \"foO\" is not declared, did you mean \"Foo\"? var d = foO ~~~ :1:5: note: \"Foo\" is defined here var Foo = 0 ~~~ :8:9: error: \"BAR\" is not declared, did you mean \"bar\"? var e = BAR ~~~ :3:5: note: \"bar\" is defined here var bar = 0 ~~~ :4:9: fix: Replace with \"Foo\" var a = foo ~~~ [Foo] :5:9: fix: Replace with \"FOo\" var b = FOO ~~~ [FOo] :6:9: fix: Replace with \"FOo\" var c = fOO ~~~ [FOo] :7:9: fix: Replace with \"Foo\" var d = foO ~~~ [Foo] :8:9: fix: Replace with \"bar\" var e = BAR ~~~ [bar] ") # Check the "void" correction test(" def foo void {} def bar(x int) void {} ", " :1:9: error: There is no explicit \"void\" return type (to indicate that there's nothing to return, just don't put a return type) def foo void {} ~~~~ :2:16: error: There is no explicit \"void\" return type (to indicate that there's nothing to return, just don't put a return type) def bar(x int) void {} ~~~~ :1:8: fix: Remove \"void\" def foo void {} ~~~~~ [] :2:15: fix: Remove \"void\" def bar(x int) void {} ~~~~~ [] ") # Check the "void" correction test(" class void {} def foo void { return null } def bar(x int) void { return null } ", " ") # Check for type context with the "??" operator test(" class Foo {} namespace Foo { const FOO = new } def test(foo Foo) { (foo ?? .FOO) + 2 } ", " :8:17: error: \"+\" is not declared on type \"Foo\" (foo ?? .FOO) + 2 ^ ") # Abstract functions are only allowed on certain types test(" class C { def foo } enum E { def foo } flags F { def foo } interface I { def foo } namespace N { def foo } type T : int { def foo } ", " :2:14: error: Non-imported function \"foo\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) enum E { def foo } ~~~ :3:15: error: Non-imported function \"foo\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) flags F { def foo } ~~~ :5:19: error: Non-imported function \"foo\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) namespace N { def foo } ~~~ :6:20: error: Non-imported function \"foo\" is missing an implementation (use the \"@import\" annotation if it's implemented externally) type T : int { def foo } ~~~ ") # Forward-declaring a function should not count as abstract test(" class C { def foo } enum E { def foo } flags F { def foo } interface I { def foo } namespace N { def foo } type T : int { def foo } class C { def foo {} } enum E { def foo {} } flags F { def foo {} } interface I { def foo {} } namespace N { def foo {} } type T { def foo {} } ", " ") # Forward-declaring a function should not count as abstract test(" class C { def foo {} } enum E { def foo {} } flags F { def foo {} } interface I { def foo {} } namespace N { def foo {} } type T : int { def foo {} } class C { def foo } enum E { def foo } flags F { def foo } interface I { def foo } namespace N { def foo } type T { def foo } ", " ") # Warn on statement-level dynamic property access test(" def test(foo dynamic) { var bar = foo.bar foo.bar foo.bar = 1 foo.bar - 2 } ", " :3:3: warning: Unused expression foo.bar ~~~~~~~ :2:7: warning: Local variable \"bar\" is never read var bar = foo.bar ~~~ ") } } ================================================ FILE: tests/unicode.sk ================================================ namespace Skew.Tests { def testUnicodeText(text string, codePoints List) { test("codePoints " + text, expectString => expectString(toString(codePoints), toString(text.codePoints))) test("fromCodePoints " + text, expectString => expectString(text, string.fromCodePoints(codePoints))) test("codeUnits " + text, expectString => expectString(text, string.fromCodeUnits(text.codeUnits))) # Call fromCodePoint() for each code point for codePoint in codePoints { test("fromCodePoint " + codePoint.toString, expectString => expectString(toString([codePoint]), toString(string.fromCodePoint(codePoint).codePoints))) } # Check forward iteration var nextCodePoints = codePoints.clone nextCodePoints.append(-1) testExpect("StringIterator.nextCodePoint " + text, () List => { var iterator = Unicode.StringIterator.INSTANCE.reset(text, 0) var observedCodePoints List = [] for codePoint in 0..codePoints.count + 1 { observedCodePoints.append(iterator.nextCodePoint) } return observedCodePoints }, nextCodePoints) # Check backward iteration var previousCodePoints = codePoints.clone previousCodePoints.reverse previousCodePoints.append(-1) testExpect("StringIterator.previousCodePoint " + text, () List => { var iterator = Unicode.StringIterator.INSTANCE.reset(text, text.count) var observedCodePoints List = [] for codePoint in 0..codePoints.count + 1 { observedCodePoints.append(iterator.previousCodePoint) } return observedCodePoints }, previousCodePoints) } def testUnicodeCount(text string, count int) { test("count " + text, expectString => expectString(count.toString, text.count.toString)) } def testUnicode { # Make sure encoding and decoding works testUnicodeText("Цлїςσδε", [1062, 1083, 1111, 962, 963, 948, 949]) testUnicodeText("フムアムカモケモ", [65420, 65425, 65393, 65425, 65398, 65427, 65401, 65427]) testUnicodeText("フムヤムカモケモ", [12501, 12512, 12516, 12512, 12459, 12514, 12465, 12514]) testUnicodeText("㊀㊁㊂㊃㊄", [12928, 12929, 12930, 12931, 12932]) testUnicodeText("☳☶☲", [9779, 9782, 9778]) testUnicodeText("𡇙𝌆", [135641, 119558]) testUnicodeText("🙉🙈🙊", [128585, 128584, 128586]) # Make sure target-specific constant folding of string.count works correctly testUnicodeCount("Цлїςσδε", "Цлїςσδε".count) testUnicodeCount("フムアムカモケモ", "フムアムカモケモ".count) testUnicodeCount("フムヤムカモケモ", "フムヤムカモケモ".count) testUnicodeCount("㊀㊁㊂㊃㊄", "㊀㊁㊂㊃㊄".count) testUnicodeCount("☳☶☲", "☳☶☲".count) testUnicodeCount("𡇙𝌆", "𡇙𝌆".count) } } ================================================ FILE: www/benchmark.html ================================================ ================================================ FILE: www/index.html ================================================ Skew Compiler

Input

Log

Loading... Target:

Code

================================================ FILE: www/index.js ================================================ (function() { var compileTime = document.getElementById('compileTime'); var input = document.getElementById('input'); var optionArea = document.getElementById('optionArea'); var outputCode = document.getElementById('outputCode'); var outputLog = document.getElementById('outputLog'); var targetArea = document.getElementById('targetArea'); var targets = [ {name: 'JavaScript', option: 'js', extension: 'js'}, {name: 'TypeScript', option: 'ts', extension: 'ts'}, {name: 'C#', option: 'c#', extension: 'cs'}, {name: 'C++', option: 'c++', extension: 'cpp'}, {name: 'Lisp Tree', option: 'lisp-tree', extension: 'lisp'}, ]; var targetNames = targets.map(function(target) { return target.name; }); var TARGET_INDEX = 'TARGET_INDEX'; var CONSTANT_FOLDING = 'CONSTANT_FOLDING'; var FUNCTION_INLINING = 'FUNCTION_INLINING'; var GLOBALIZE = 'GLOBALIZE'; var MANGLE = 'MANGLE'; var MINIFY = 'MINIFY'; var MULTIPLE_OUTPUTS = 'MULTIPLE_OUTPUTS'; var SHOW_SYNTAX_TREE = 'SHOW_SYNTAX_TREE'; var SOURCE_MAP = 'SOURCE_MAP'; var STOP_AFTER_RESOLVE = 'STOP_AFTER_RESOLVE'; var USE_WEB_WORKER = 'USE_WEB_WORKER'; var SET_RELEASE = 'SET_RELEASE'; var compiler = null; var start = null; var worker = null; var isBusy = false; var pendingMessage = null; function configGetBool(name) { return configGet(name) === 'true'; } function configGet(name) { return localStorage.getItem(name); } function configSet(name, index) { localStorage.setItem(name, index); } function now() { return window.performance && performance.now ? performance.now() : +new Date; } function handleResult(result) { outputLog.value = result.log.text; outputCode.value = result.outputs.length === 1 ? result.outputs[0].contents : result.outputs.map(function(source) { return '[' + source.name + ']\n' + source.contents; }).join('\n'); compileTime.textContent = +(now() - start).toFixed(1) + 'ms'; } function update() { var index = targetNames.indexOf(configGet(TARGET_INDEX)); var target = targets[index === -1 ? 0 : index]; var options = {}; options.outputFile = 'compiled.' + target.extension; options.inputs = [{name: '', contents: input.value}]; if (configGetBool(CONSTANT_FOLDING)) options.foldAllConstants = true; if (configGetBool(FUNCTION_INLINING)) options.inlineAllFunctions = true; if (configGetBool(GLOBALIZE)) options.globalizeAllFunctions = true; if (configGetBool(MANGLE)) options.jsMangle = true; if (configGetBool(MINIFY)) options.jsMinify = true; if (configGetBool(SOURCE_MAP)) options.jsSourceMap = true; if (configGetBool(STOP_AFTER_RESOLVE)) options.stopAfterResolve = true; if (configGetBool(MULTIPLE_OUTPUTS)) options.outputDirectory = 'output'; if (configGetBool(SET_RELEASE)) options.defines = {RELEASE: 'true'}; if (configGetBool(USE_WEB_WORKER)) { if (!worker) { worker = new Worker(document.getElementById('skew-api').src); worker.onmessage = function(e) { if (pendingMessage) { start = now(); worker.postMessage(pendingMessage); pendingMessage = null; } else { isBusy = false; handleResult(e.data); } }; } options.type = 'compile'; if (isBusy) { pendingMessage = options; } else { start = now(); worker.postMessage(options); isBusy = true; } } else { start = now(); if (compiler === null) { compiler = Skew.create(); } handleResult(compiler.compile(options)); } } function createSelect(values, name) { var element = document.createElement('select'); for (var i = 0; i < values.length; i++) { var option = document.createElement('option'); option.textContent = values[i]; element.appendChild(option); } element.selectedIndex = values.indexOf(configGet(name)); element.onchange = function() { configSet(name, values[element.selectedIndex]); update(); }; return element; } function createCheckbox(label, name) { var element = document.createElement('label'); var checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = configGetBool(name); checkbox.onchange = function() { configSet(name, checkbox.checked); update(); }; element.appendChild(checkbox); element.appendChild(document.createTextNode(' ' + label)); return element; } function main() { targetArea.appendChild(createSelect(targetNames, TARGET_INDEX)); optionArea.appendChild(createCheckbox('Constant folding', CONSTANT_FOLDING)); optionArea.appendChild(createCheckbox('Inlining', FUNCTION_INLINING)); optionArea.appendChild(createCheckbox('Mangle', MANGLE)); optionArea.appendChild(createCheckbox('Minify', MINIFY)); optionArea.appendChild(createCheckbox('Globalize', GLOBALIZE)); optionArea.appendChild(createCheckbox('Syntax tree', SHOW_SYNTAX_TREE)); optionArea.appendChild(createCheckbox('Source map', SOURCE_MAP)); optionArea.appendChild(createCheckbox('IDE mode (no output)', STOP_AFTER_RESOLVE)); optionArea.appendChild(createCheckbox('Multiple outputs', MULTIPLE_OUTPUTS)); optionArea.appendChild(createCheckbox('Use a web worker', USE_WEB_WORKER)); optionArea.appendChild(createCheckbox('Set RELEASE', SET_RELEASE)); input.oninput = update; update(); } main(); })(); ================================================ FILE: www/style.css ================================================ body { font: 12px/15px 'Lucida Grande', sans-serif; margin: 20px; } textarea { font: 12px Monaco, monospace; width: 600px; } div { display: inline-block; vertical-align: top; margin: 20px; } select { width: 100px; } #compileTime { float: right; } label { cursor: default; -webkit-user-select: none; -moz-user-select: -moz-none; margin-right: 10px; } #optionArea { display: block; margin-top: 10px; width: 600px; } #input { height: 600px; }