├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── VERSION ├── examples ├── error_handling.pseudo.yaml ├── error_handling.py ├── f.pseudo.yaml ├── f.py ├── fib.py ├── file.pseudo.yaml ├── file.py ├── football.cs ├── football.go ├── football.js ├── football.pseudo.yaml ├── football.py ├── football.rb ├── football.txt ├── in.pseudo.yaml ├── in.py ├── int.pseudo.yaml ├── int.py ├── int_test.pseudon.yaml ├── int_test.py ├── lisp.py ├── logical.pseudo.yaml ├── logical.py ├── map_test.pseudon.yaml ├── map_test.py ├── oop.pseudo.yaml ├── oop.py ├── swap.pseudo.yaml ├── swap.py ├── typing.py ├── verbal_expressions.pseudo.yaml └── verbal_expressions.py ├── pseudo_python ├── __init__.py ├── api_translator.py ├── ast_translator.py ├── builtin_typed_api.py ├── env.py ├── errors.py ├── helpers.py ├── main.py ├── parser.py └── standard.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── test_language.py └── test_python.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/python 2 | 3 | ### Python ### 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | *.clj 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | env/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *,cover 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | 59 | # Sphinx documentation 60 | docs/_build/ 61 | 62 | # PyBuilder 63 | target/ 64 | 65 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.4" 4 | - "3.5" 5 | script: python -m unittest discover tests 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.2.28 2 | 3 | 18.03.2016 4 | 5 | Fixes: 6 | * Fix unary negative numbers 7 | 8 | 0.2.26 9 | 10 | 18.03.2016 11 | 12 | Features: 13 | 14 | * Type checking for == 15 | 16 | 0.2.22 17 | 18 | 17.03.2016 19 | 20 | Fixes: 21 | 22 | * Fix unary op handling 23 | * Convert ** to `math:pow` 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Alexander Ivanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/alehander42/pseudo-python.svg?branch=master)](https://travis-ci.org/alehander42/pseudo-python) 2 | [![MIT License](http://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) 3 | 4 | # pseudo-python 5 | 6 | A restricted Python to idiomatic JavaScript / Ruby / Go / C# translator 7 | 8 | [Pseudo](https://github.com/alehander42/pseudo) is a framework for high level code generation: it is used by this compiler to translate a subset of Python to all Pseudo-supported languages 9 | 10 | **If you are using Python3.5 and you experience problems with an already installed version of pseudo-python, please upgrade it to `0.2.34` (`pip3 install pseudo-python --upgrade`)** 11 | 12 | 13 | # git history 14 | 15 | Sorry: I lied, manipulating the git history in a way that it appears most of the work is after 1 march: I was finishing my work in a company in february and I wanted to hide the fact i've worked on pseudo in that time: I actually started more active work somewhere around 20-th february if I remember correctly, somewhere around the second half of february. I am not sure if i have that original git history anymore: sorry to Clevertech(the company)/any observers, this was a really ugly thing to do. 16 | 17 | 18 | ## Supported subset 19 | 20 | Pseudo-Python compiles to `pseudo ast`. 21 | 22 | [Pseudo](https://github.com/alehander42/pseudo) defines a language-independent AST model and an unified standard library. 23 | It can map its own standard library to target language libraries and concepts automatically and it tries to generate readable and idiomatic code. 24 | 25 | Pseudo-Python translates a subset of Python to Pseudo AST and then it receives the JS/Ruby/C#/Go backends for free. (+ at least 4-5 backends in the future) 26 | 27 | Pseudo was inspired by the need to generate algorithms/code in different languages or portint tools/libraries to a new environment 28 | 29 | That's why it can be mapped to a well defined subset of a language 30 | 31 | It is meant as a framework consuming ast from parser generators / compilers / various tools and generating snippets / codebases in different target languages 32 | 33 | # Plan 34 | 35 | ![a diagram illustrating the pseudo framework: compilers -> ast -> api translation -> target code](http://i.imgur.com/7ySfy5j.jpg?2) 36 | 37 | # Pseudo supports 38 | 39 | * basic types and collections and standard library methods for them 40 | 41 | * integer, float, string, boolean 42 | * lists 43 | * dicts 44 | * sets 45 | * tuples/structs(fixed length heterogeneous lists) 46 | * fixed size arrays 47 | * regular expressions 48 | 49 | * functions with normal parameters (no default/keyword/vararg parameters) 50 | * classes 51 | * single inheritance 52 | * polymorphism 53 | * no dynamic instance variables 54 | * basically a constructor + a collection of instance methods, no fancy metaprogramming etc supported 55 | 56 | * exception-based error handling with support for custom exceptions 57 | (target languages support return-based error handling too) 58 | 59 | * io: print/input, file read/write, system and subprocess commands 60 | 61 | * iteration (for-in-range / for-each / iterating over several collections / while) 62 | * conditionals (if / else if / else) 63 | * standard math/logical operations 64 | 65 | ## Installation 66 | 67 | 68 | ```bash 69 | pip install pseudo-python 70 | ``` 71 | 72 | ## Usage 73 | 74 | ```bash 75 | pseudo-python ruby 76 | pseudo-python csharp 77 | ``` 78 | etc for all the supported pseudo targets (javascript, c#, go, ruby and python) 79 | 80 | ## examples 81 | 82 | Each example contains a detailed README and working translations to Python, JS, Ruby, Go and C#, generated by Pseudo 83 | 84 | [fibonacci](https://github.com/alehander42/pseudo/tree/master/examples/fib) 85 | 86 | [a football results processing command line tool](https://github.com/alehander42/pseudo/tree/master/examples/football) 87 | 88 | [a verbal expressions-like library ported to all the target languages](https://github.com/alehander42/pseudo/tree/master/examples/verbal_expressions) 89 | 90 | 91 | ## Error messages 92 | 93 | A lot of work has been put into making pseudo-python error messages as clear and helpful as possible: they show the offending snippet of code and 94 | often they offer suggestions, list possible fixes or right/wrong ways to write something 95 | 96 | ![Screenshot of error messages](http://i.imgur.com/Et3X9W1.png) 97 | 98 | Beware, pseudo and especially pseudo-python are still in early stage, so if there is anything weird going on, don't hesitate to submit an issue 99 | 100 | ## Type inference 101 | 102 | pseudo-python checks if your program is using a valid pseudo-translatable subset of Python, type checks it according to pseudo type rules and then generates a pseudo ast and passes it to pseudo for code generation. 103 | 104 | 105 | The rules are relatively simple: currently pseudo-python infers everything 106 | from the usage of functions/classes, so has sufficient information when the program is calling/initializing all 107 | of its functions/classes (except for no-arg functions) 108 | 109 | Often you don't really need to do that for **all** of them, you just need to do it in a way that can create call graphs covering all of them (e.g. often you'll have `a` calling `b` calling `x` and you only need to have an `a` invocation in your source) 110 | 111 | You can also use type annotations. We are trying to respect existing Python3 type annotation conventions and currently pseudo-python recognizes `int`, `float`, `str`, `bool`, `List[]`, 112 | `Dict[, ]`, `Tuple[..]`, `Set[]` and `Callable[[..], ]` 113 | 114 | Beware, you can't just annotate one param, if you provide type annotations for a function/method, pseudo-python expects type hints for all params and a return type 115 | 116 | Variables can't change their types, the equivalents for builtin types are 117 | ```python 118 | list : List[@element_type] # generic 119 | dict: Dictionary[@key_type @value_type] # generic 120 | set: Set[@element_type] # generic 121 | tuple: Array[@element_type] # for homogeneous tuples 122 | Tuple[@element0_type, @element1_type..] # for heterogeneous tuples 123 | int: Int 124 | float: Float 125 | int/float: Number 126 | str: String 127 | bool: Boolean 128 | ``` 129 | 130 | There are several limitations which will probably be fixed in v0.3 131 | 132 | If you initialize a variable/do first call to a function with a collection literal, it should have at least one element(that limitation will be until v0.3) 133 | 134 | All attributes used in a class should be initialized in its `__init__` 135 | 136 | Other pseudo-tips: 137 | 138 | * Homogeneous tuples are converted to `pseudo` fixed length arrays and heterogeneous to `pseudo` tuples. [Pseudo](https://github.com/alehander42/pseudo) analyzes the tuples usage in the code and sometimes it translates them to classes/structs with meaningful names if the target language is `C#` `C++` or `Go` 139 | 140 | * Attributes that aren't called from other classes are translated as `private`, the other ones as `public`. The rule for methods is different: 141 | `_name` ones are only translated as `private`. That can be added as 142 | config option in the future 143 | 144 | * Multiple returns values are supported, but they are converted to `array`/`tuple` 145 | 146 | * Single inheritance is supported, `pseudo-python` supports polymorphism 147 | but methods in children should accept the same types as their equivalents in the hierarchy (except `__init__`) 148 | 149 | The easiest way to play with the type system is to just try several programs: `pseudo-python` errors should be enough to guide you, if not, 150 | you can always open an issue 151 | 152 | ## How does Pseudo work? 153 | 154 | 155 | The implementation goal is to make the definitions of new supported languages really clear and simple. 156 | 157 | If you dive in, you'll find out 158 | a lot of the code/api transformations are defined using a declarative dsl with rare ocassions 159 | of edge case handling helpers. 160 | 161 | That has a lot of advantages: 162 | 163 | * Less bugs: the core transformation code is really generalized, it's reused as a dsl and its results are well tested 164 | 165 | * Easy to comprehend: it almost looks like a config file 166 | 167 | * Easy to add support for other languages: I was planning to support just python and c# in the initial version but it is so easy to add support for a language similar to the current supported ones, that I 168 | added support for 4 more. 169 | 170 | * Easy to test: there is a simple test dsl too which helps all 171 | language tests to share input examples [like that](pseudo/tests/test_ruby.py) 172 | 173 | However language translation is related to a lot of details and 174 | a lot of little gotchas, tuning and refining some of them took days. Pseudo uses different abstractions to streamline the process and to reuse logic across languages. 175 | 176 | ```ruby 177 | PSEUDO AST: 178 | NORMAL CODE PSEUDO STANDARD LIBRARY INVOCATIONS 179 | || || 180 | || || 181 | || API TRANSLATOR 182 | || || 183 | || || 184 | || \/ 185 | || IDIOMATIC TARGET LANGUAGE 186 | || STANDARD LIBRARY INVOCATIONS 187 | || || 188 | \/ \/ 189 | STANDARD OR LANGUAGE-SPECIFIC MIDDLEWARES 190 | e.g. 191 | name camel_case/snake_case middleware 192 | convert-tuples-to-classes middleware 193 | convert-exception-based errors handling 194 | to return-based error handling middleware 195 | etc 196 | 197 | || 198 | || 199 | || 200 | || 201 | TARGET LANGUAGE CODE GENERATOR 202 | 203 | defined with a dsl aware 204 | that handles formatting 205 | automatically 206 | || 207 | || 208 | || 209 | \/ 210 | 211 | OUTPUT 212 | ``` 213 | 214 | 215 | ## What's the difference between Pseudo and Haxe? 216 | 217 | They might seem comparable at a first glance, but they have completely different goals. 218 | 219 | Pseudo wants to generate readable code, ideally something that looks like a human wrote it/ported it 220 | 221 | Pseudo doesn't use a target language runtime, it uses the target language standard library for everything (except for JS, but even there is uses `lodash` which is pretty popular and standard) 222 | 223 | Pseudo's goal is to help with automated translation for cases 224 | like algorithm generation, parser generation, refactoring, porting codebases etc. The fact that you can write compilers targetting Pseudo and receiver translation to many languages for free is just a happy accident 225 | 226 | 227 | ## License 228 | 229 | Copyright © 2015 2016 [Alexander Ivanov](https://twitter.com/alehander42) 230 | 231 | Distributed under the MIT License. 232 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.28 2 | -------------------------------------------------------------------------------- /examples/error_handling.pseudo.yaml: -------------------------------------------------------------------------------- 1 | constants: [] 2 | definitions: [] 3 | dependencies: [] 4 | main: 5 | - {base: null, name: NeptunError, type: custom_exception} 6 | - {base: NeptunError, name: IntergallacticError, type: custom_exception} 7 | - local: f 8 | pseudo_type: Void 9 | type: local_assignment 10 | value: {pseudo_type: Int, type: int, value: 2} 11 | value_type: Int 12 | - block: 13 | - local: f 14 | pseudo_type: Void 15 | type: local_assignment 16 | value: {pseudo_type: Int, type: int, value: 2} 17 | value_type: Int 18 | - block: 19 | - exception: NeptunError 20 | pseudo_type: Void 21 | type: throw_statement 22 | value: {pseudo_type: String, type: string, value: why f} 23 | otherwise: null 24 | pseudo_type: Void 25 | test: 26 | left: {name: f, pseudo_type: Int, type: local} 27 | op: == 28 | pseudo_type: Boolean 29 | right: {pseudo_type: Int, type: int, value: 2} 30 | type: binary_op 31 | type: if_statement 32 | - local: h 33 | pseudo_type: Void 34 | type: local_assignment 35 | value: {pseudo_type: Int, type: int, value: 2} 36 | value_type: Int 37 | handlers: 38 | - block: 39 | - args: 40 | - {name: e, pseudo_type: Exception, type: local} 41 | function: display 42 | namespace: io 43 | pseudo_type: Void 44 | type: standard_call 45 | exception: IntergallacticError 46 | instance: e 47 | is_builtin: false 48 | pseudo_type: Void 49 | type: exception_handler 50 | - block: 51 | - args: 52 | - {name: e, pseudo_type: Exception, type: local} 53 | function: display 54 | namespace: io 55 | pseudo_type: Void 56 | type: standard_call 57 | exception: NeptunError 58 | instance: e 59 | is_builtin: false 60 | pseudo_type: Void 61 | type: exception_handler 62 | pseudo_type: Void 63 | type: try_statement 64 | type: module 65 | -------------------------------------------------------------------------------- /examples/error_handling.py: -------------------------------------------------------------------------------- 1 | class NeptunError(Exception): 2 | pass 3 | 4 | class IntergallacticError(NeptunError): 5 | pass 6 | 7 | f = 2 8 | try: 9 | f = 2 10 | if f == 2: 11 | raise NeptunError("why f") 12 | h = 2 13 | except IntergallacticError as e: 14 | print(e) 15 | except NeptunError as e: 16 | print(e) 17 | 18 | -------------------------------------------------------------------------------- /examples/f.pseudo.yaml: -------------------------------------------------------------------------------- 1 | constants: [] 2 | custom_exceptions: 3 | - {base: null, name: ExError, type: custom_exception} 4 | definitions: [] 5 | dependencies: [] 6 | main: 7 | - exception: ExError 8 | pseudo_type: Void 9 | type: throw_statement 10 | value: {pseudo_type: String, type: string, value: s} 11 | type: module 12 | -------------------------------------------------------------------------------- /examples/f.py: -------------------------------------------------------------------------------- 1 | class L: 2 | def __init__(self, a): 3 | self.a = a 4 | 5 | s = L(2) 6 | e = 'x' 7 | e.k() 8 | -------------------------------------------------------------------------------- /examples/fib.py: -------------------------------------------------------------------------------- 1 | def fib(n): 2 | if n <= 1: 3 | return 1 4 | else: 5 | return fib(n - 1) + fib(n - 2) 6 | 7 | print(fib(4)) 8 | -------------------------------------------------------------------------------- /examples/file.pseudo.yaml: -------------------------------------------------------------------------------- 1 | constants: [] 2 | definitions: [] 3 | dependencies: [] 4 | main: 5 | - local: f 6 | pseudo_type: Void 7 | type: local_assignment 8 | value: {pseudo_type: String, type: string, value: apolonia.txt} 9 | value_type: String 10 | - local: source 11 | pseudo_type: Void 12 | type: local_assignment 13 | value: 14 | args: 15 | - {name: f, pseudo_type: String, type: local} 16 | function: read_file 17 | namespace: io 18 | pseudo_type: String 19 | type: standard_call 20 | value_type: String 21 | - args: 22 | - args: 23 | - {pseudo_type: String, type: string, value: \n} 24 | message: split 25 | pseudo_type: [List, String] 26 | receiver: {name: source, pseudo_type: String, type: local} 27 | type: standard_method_call 28 | function: display 29 | namespace: io 30 | pseudo_type: Void 31 | type: standard_call 32 | type: module 33 | -------------------------------------------------------------------------------- /examples/file.py: -------------------------------------------------------------------------------- 1 | f = 'apolonia.txt' 2 | with open(f, 'r') as handler: 3 | source = handler.read() 4 | 5 | print(source.split('\n')) 6 | -------------------------------------------------------------------------------- /examples/football.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Collections.Generic; 6 | 7 | public class Result 8 | { 9 | private readonly string host; 10 | public string Host { get { return host; } } 11 | 12 | private readonly string away; 13 | public string Away { get { return away; } } 14 | 15 | private readonly int[] goals; 16 | public int[] Goals { get { return goals; } } 17 | 18 | public Result(string host, string away, int[] goals) 19 | { 20 | this.host = host; 21 | this.away = away; 22 | this.goals = goals; 23 | } 24 | } 25 | 26 | public class Program 27 | { 28 | static List LoadResults(string filename) 29 | { 30 | var raw = File.ReadAllText(filename); 31 | var lines = raw.Split('\n'); 32 | return lines 33 | .Where(line => line.Length != 0) 34 | .Select(line => ParseResult(line)) 35 | .ToList(); 36 | } 37 | 38 | static Result ParseResult(string line) 39 | { 40 | var awayIndex = line.IndexOf(" - ") + 3; 41 | var resultIndex = line.IndexOf(" ", awayIndex) + 1; 42 | var goals = line.Substring(resultIndex).Split(':'); 43 | return new Result(line.Substring(0, awayIndex - 3), line.Substring(awayIndex, resultIndex - 1 - awayIndex), new[] { Int32.Parse(goals[0]), Int32.Parse(goals[1]) }); 44 | } 45 | 46 | static int CalculatePoints(List results, string team) 47 | { 48 | return results.Aggregate(0, (memo, result) => memo + ResultPoints(team, result)); 49 | } 50 | 51 | static int ResultPoints(string team, Result result) 52 | { 53 | if (result.Host == team && result.Goals[0] > result.Goals[1] || result.Away == team && result.Goals[0] < result.Goals[1]) 54 | { 55 | return 3; 56 | } 57 | else if (result.Goals[0] == result.Goals[1] && (result.Host == team || result.Away == team)) 58 | { 59 | return 1; 60 | } 61 | else 62 | { 63 | return 0; 64 | } 65 | } 66 | 67 | public static void Main(string[] args) 68 | { 69 | if (args.Length < 2) 70 | { 71 | Console.WriteLine("usage: football "); 72 | } 73 | else 74 | { 75 | var results = LoadResults(args[0]); 76 | Console.WriteLine(CalculatePoints(results, args[1])); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/football.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | "strconv" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | ) 10 | 11 | func LoadResults(filename string) []Result { 12 | _contents, _ := ioutil.ReadFile(filename) 13 | raw := string(_contents) 14 | lines := strings.Split(raw, "\n") 15 | var _results []Result 16 | for _, line := range lines { 17 | if len(line) > 0 { 18 | _results = append(_results, ParseResult(line)) 19 | } 20 | } 21 | return _results 22 | 23 | } 24 | 25 | func ParseResult(line string) *Result { 26 | awayIndex := strings.Index(line, " - ") + 3 27 | resultIndex := awayIndex + strings.Index(line[awayIndex:], " ") + 1 28 | goals := strings.Split(line[resultIndex:], ":") 29 | _int, _ := strconv.Atoi(goals[0]) 30 | _int1, _ := strconv.Atoi(goals[1]) 31 | return Result{line[:awayIndex - 3], line[awayIndex:resultIndex - 1], [...]int{_int, _int1}} 32 | } 33 | 34 | func CalculatePoints(results []Result, team string) int { 35 | accumulator := 0 36 | for _, result := range results { 37 | accumulator += ResultPoints(team, result) 38 | } 39 | 40 | return accumulator 41 | } 42 | 43 | func ResultPoints(team string, result Result) int { 44 | if result.Host == team && result.Goals[0] > result.Goals[1] || result.Away == team && result.Goals[0] < result.Goals[1] { 45 | return 3 46 | } else if result.Goals[0] == result.Goals[1] && (result.Host == team || result.Away == team) { 47 | return 1 48 | } else { 49 | return 0 50 | } 51 | } 52 | 53 | type Result struct { 54 | Host string 55 | Away string 56 | Goals [2]int 57 | } 58 | 59 | func main() { 60 | if len(os.Args) < 3 { 61 | fmt.Println("usage: football ") 62 | } else { 63 | results := LoadResults(os.Args[1]) 64 | fmt.Println(CalculatePoints(results, os.Args[2])) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/football.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var fs = require('fs'); 3 | function load_results(filename) { 4 | var raw = fs.readFileSync(filename, 'utf8'); 5 | var lines = raw.split('\n'); 6 | return _.filter(lines, function (line) { 7 | return line; 8 | }).map(function (line) { 9 | return parse_result(line); 10 | }); 11 | } 12 | 13 | function parse_result(line) { 14 | var away_index = line.search(' - ') + 3; 15 | var result_index = away_index + line.slice(away_index).search(' ') + 1; 16 | var goals = line.slice(result_index).split(':'); 17 | return [line.slice(0, away_index - 3), line.slice(away_index, result_index - 1), [parseInt(goals[0]), parseInt(goals[1])]]; 18 | } 19 | 20 | function calculate_points(results, team) { 21 | return _.reduce(results, function (memo, result) { 22 | return memo + result_points(team, result[0], result[1], result[2]); 23 | }, 0); 24 | } 25 | 26 | function result_points(team, host, away, goals) { 27 | if (host == team && goals[0] > goals[1] || away == team && goals[0] < goals[1]) { 28 | return 3; 29 | } else if (goals[0] == goals[1] && (host == team || away == team)) { 30 | return 1; 31 | } else { 32 | return 0; 33 | } 34 | } 35 | 36 | if (process.argv.length < 4) { 37 | console.log('usage: football '); 38 | } else { 39 | var results = load_results(process.argv[2]); 40 | console.log(calculate_points(results, process.argv[3])); 41 | } 42 | 43 | -------------------------------------------------------------------------------- /examples/football.pseudo.yaml: -------------------------------------------------------------------------------- 1 | constants: [] 2 | custom_exceptions: [] 3 | definitions: 4 | - block: 5 | - pseudo_type: Void 6 | target: {name: raw, pseudo_type: String, type: local} 7 | type: assignment 8 | value: 9 | args: 10 | - {name: filename, pseudo_type: String, type: local} 11 | function: read_file 12 | namespace: io 13 | pseudo_type: String 14 | type: standard_call 15 | - pseudo_type: Void 16 | target: 17 | name: lines 18 | pseudo_type: [List, String] 19 | type: local 20 | type: assignment 21 | value: 22 | args: 23 | - {pseudo_type: String, type: string, value: \n} 24 | message: split 25 | pseudo_type: [List, String] 26 | receiver: {name: raw, pseudo_type: String, type: local} 27 | type: standard_method_call 28 | - pseudo_type: 29 | - List 30 | - - Tuple 31 | - String 32 | - String 33 | - [Array, Int, 2] 34 | type: implicit_return 35 | value: 36 | block: 37 | - args: 38 | - {name: line, pseudo_type: String, type: local} 39 | function: 40 | name: parse_result 41 | pseudo_type: 42 | - Function 43 | - String 44 | - - Tuple 45 | - String 46 | - String 47 | - [Array, Int, 2] 48 | type: local 49 | pseudo_type: 50 | - Tuple 51 | - String 52 | - String 53 | - [Array, Int, 2] 54 | type: call 55 | function: filter_map 56 | iterators: 57 | iterator: {name: line, pseudo_type: String, type: local} 58 | type: for_iterator 59 | pseudo_type: 60 | - List 61 | - - Tuple 62 | - String 63 | - String 64 | - [Array, Int, 2] 65 | sequences: 66 | sequence: 67 | name: lines 68 | pseudo_type: [List, String] 69 | type: local 70 | type: for_sequence 71 | test: 72 | - args: [] 73 | message: present? 74 | pseudo_type: Boolean 75 | receiver: {name: line, pseudo_type: String, type: local} 76 | type: standard_method_call 77 | type: standard_iterable_call 78 | name: load_results 79 | params: 80 | - {name: filename, pseudo_type: Function, type: local} 81 | pseudo_type: 82 | - Function 83 | - String 84 | - - List 85 | - - Tuple 86 | - String 87 | - String 88 | - [Array, Int, 2] 89 | return_type: 90 | - List 91 | - - Tuple 92 | - String 93 | - String 94 | - [Array, Int, 2] 95 | type: function_definition 96 | - block: 97 | - pseudo_type: Void 98 | target: {name: away_index, pseudo_type: Int, type: local} 99 | type: assignment 100 | value: 101 | left: 102 | args: 103 | - {pseudo_type: String, type: string, value: ' - '} 104 | message: find 105 | pseudo_type: Int 106 | receiver: {name: line, pseudo_type: String, type: local} 107 | type: standard_method_call 108 | op: + 109 | pseudo_type: Int 110 | right: {pseudo_type: Int, type: int, value: 3} 111 | type: binary_op 112 | - pseudo_type: Void 113 | target: {name: result_index, pseudo_type: Int, type: local} 114 | type: assignment 115 | value: 116 | left: 117 | args: 118 | - {pseudo_type: String, type: string, value: ' '} 119 | - {name: away_index, pseudo_type: Int, type: local} 120 | message: find_from 121 | pseudo_type: Int 122 | receiver: {name: line, pseudo_type: String, type: local} 123 | type: standard_method_call 124 | op: + 125 | pseudo_type: Int 126 | right: {pseudo_type: Int, type: int, value: 1} 127 | type: binary_op 128 | - pseudo_type: Void 129 | target: 130 | name: goals 131 | pseudo_type: [List, String] 132 | type: local 133 | type: assignment 134 | value: 135 | args: 136 | - {pseudo_type: String, type: string, value: ':'} 137 | message: split 138 | pseudo_type: [List, String] 139 | receiver: 140 | args: 141 | - {name: result_index, pseudo_type: Int, type: local} 142 | message: substr_from 143 | pseudo_type: String 144 | receiver: {name: line, pseudo_type: String, type: local} 145 | type: standard_method_call 146 | type: standard_method_call 147 | - pseudo_type: 148 | - Tuple 149 | - String 150 | - String 151 | - [Array, Int, 2] 152 | type: implicit_return 153 | value: 154 | elements: 155 | - args: 156 | - left: {name: away_index, pseudo_type: Int, type: local} 157 | op: '-' 158 | pseudo_type: Int 159 | right: {pseudo_type: Int, type: int, value: 3} 160 | type: binary_op 161 | message: substr_to 162 | pseudo_type: String 163 | receiver: {name: line, pseudo_type: String, type: local} 164 | type: standard_method_call 165 | - args: 166 | - {name: away_index, pseudo_type: Int, type: local} 167 | - left: {name: result_index, pseudo_type: Int, type: local} 168 | op: '-' 169 | pseudo_type: Int 170 | right: {pseudo_type: Int, type: int, value: 1} 171 | type: binary_op 172 | message: substr 173 | pseudo_type: String 174 | receiver: {name: line, pseudo_type: String, type: local} 175 | type: standard_method_call 176 | - elements: 177 | - args: [] 178 | message: to_int 179 | pseudo_type: Int 180 | receiver: 181 | index: {pseudo_type: Int, type: int, value: 0} 182 | pseudo_type: String 183 | sequence: 184 | name: goals 185 | pseudo_type: [List, String] 186 | type: local 187 | type: index 188 | type: standard_method_call 189 | - args: [] 190 | message: to_int 191 | pseudo_type: Int 192 | receiver: 193 | index: {pseudo_type: Int, type: int, value: 1} 194 | pseudo_type: String 195 | sequence: 196 | name: goals 197 | pseudo_type: [List, String] 198 | type: local 199 | type: index 200 | type: standard_method_call 201 | pseudo_type: [Array, Int, 2] 202 | type: array 203 | pseudo_type: 204 | - Tuple 205 | - String 206 | - String 207 | - [Array, Int, 2] 208 | type: tuple 209 | name: parse_result 210 | params: 211 | - {name: line, pseudo_type: Function, type: local} 212 | pseudo_type: 213 | - Function 214 | - String 215 | - - Tuple 216 | - String 217 | - String 218 | - [Array, Int, 2] 219 | return_type: 220 | - Tuple 221 | - String 222 | - String 223 | - [Array, Int, 2] 224 | type: function_definition 225 | - block: 226 | - pseudo_type: Int 227 | type: implicit_return 228 | value: 229 | args: 230 | - block: 231 | - pseudo_type: Int 232 | type: implicit_return 233 | value: 234 | left: {name: memo, pseudo_type: Int, type: local} 235 | op: + 236 | pseudo_type: Int 237 | right: 238 | args: 239 | - {name: team, pseudo_type: String, type: local} 240 | - index: {pseudo_type: Int, type: int, value: 0} 241 | pseudo_type: String 242 | sequence: 243 | name: result 244 | pseudo_type: 245 | - Tuple 246 | - String 247 | - String 248 | - [Array, Int, 2] 249 | type: local 250 | type: index 251 | - index: {pseudo_type: Int, type: int, value: 1} 252 | pseudo_type: String 253 | sequence: 254 | name: result 255 | pseudo_type: 256 | - Tuple 257 | - String 258 | - String 259 | - [Array, Int, 2] 260 | type: local 261 | type: index 262 | - index: {pseudo_type: Int, type: int, value: 2} 263 | pseudo_type: [Array, Int, 2] 264 | sequence: 265 | name: result 266 | pseudo_type: 267 | - Tuple 268 | - String 269 | - String 270 | - [Array, Int, 2] 271 | type: local 272 | type: index 273 | function: 274 | name: result_points 275 | pseudo_type: 276 | - Function 277 | - String 278 | - String 279 | - String 280 | - [Array, Int, 2] 281 | - Int 282 | type: local 283 | pseudo_type: Int 284 | type: call 285 | type: binary_op 286 | params: 287 | - {name: memo, pseudo_type: Int, type: local} 288 | - name: result 289 | pseudo_type: 290 | - Tuple 291 | - String 292 | - String 293 | - [Array, Int, 2] 294 | type: local 295 | pseudo_type: 296 | - Function 297 | - Int 298 | - - Tuple 299 | - String 300 | - String 301 | - [Array, Int, 2] 302 | - Int 303 | return_type: Int 304 | type: anonymous_function 305 | - {pseudo_type: Int, type: int, value: 0} 306 | message: reduce 307 | pseudo_type: Int 308 | receiver: 309 | name: results 310 | pseudo_type: 311 | - List 312 | - - Tuple 313 | - String 314 | - String 315 | - [Array, Int, 2] 316 | type: local 317 | type: standard_method_call 318 | name: calculate_points 319 | params: 320 | - {name: results, pseudo_type: Function, type: local} 321 | - name: team 322 | pseudo_type: 323 | - List 324 | - - Tuple 325 | - String 326 | - String 327 | - [Array, Int, 2] 328 | type: local 329 | pseudo_type: 330 | - Function 331 | - - List 332 | - - Tuple 333 | - String 334 | - String 335 | - [Array, Int, 2] 336 | - String 337 | - Int 338 | return_type: Int 339 | type: function_definition 340 | - block: 341 | - block: 342 | - pseudo_type: Int 343 | type: implicit_return 344 | value: {pseudo_type: Int, type: int, value: 3} 345 | otherwise: 346 | block: 347 | - pseudo_type: Int 348 | type: implicit_return 349 | value: {pseudo_type: Int, type: int, value: 1} 350 | otherwise: 351 | block: 352 | - pseudo_type: Int 353 | type: implicit_return 354 | value: {pseudo_type: Int, type: int, value: 0} 355 | pseudo_type: Void 356 | type: else_statement 357 | pseudo_type: Void 358 | test: 359 | left: 360 | left: 361 | index: {pseudo_type: Int, type: int, value: 0} 362 | pseudo_type: Int 363 | sequence: 364 | name: goals 365 | pseudo_type: [Array, Int, 2] 366 | type: local 367 | type: index 368 | op: == 369 | pseudo_type: Boolean 370 | right: 371 | index: {pseudo_type: Int, type: int, value: 1} 372 | pseudo_type: Int 373 | sequence: 374 | name: goals 375 | pseudo_type: [Array, Int, 2] 376 | type: local 377 | type: index 378 | type: comparison 379 | op: and 380 | pseudo_type: Boolean 381 | right: 382 | left: 383 | left: {name: host, pseudo_type: String, type: local} 384 | op: == 385 | pseudo_type: Boolean 386 | right: {name: team, pseudo_type: String, type: local} 387 | type: comparison 388 | op: or 389 | pseudo_type: Boolean 390 | right: 391 | left: {name: away, pseudo_type: String, type: local} 392 | op: == 393 | pseudo_type: Boolean 394 | right: {name: team, pseudo_type: String, type: local} 395 | type: comparison 396 | type: binary_op 397 | type: binary_op 398 | type: elseif_statement 399 | pseudo_type: Void 400 | test: 401 | left: 402 | left: 403 | left: {name: host, pseudo_type: String, type: local} 404 | op: == 405 | pseudo_type: Boolean 406 | right: {name: team, pseudo_type: String, type: local} 407 | type: comparison 408 | op: and 409 | pseudo_type: Boolean 410 | right: 411 | left: 412 | index: {pseudo_type: Int, type: int, value: 0} 413 | pseudo_type: Int 414 | sequence: 415 | name: goals 416 | pseudo_type: [Array, Int, 2] 417 | type: local 418 | type: index 419 | op: '>' 420 | pseudo_type: Boolean 421 | right: 422 | index: {pseudo_type: Int, type: int, value: 1} 423 | pseudo_type: Int 424 | sequence: 425 | name: goals 426 | pseudo_type: [Array, Int, 2] 427 | type: local 428 | type: index 429 | type: comparison 430 | type: binary_op 431 | op: or 432 | pseudo_type: Boolean 433 | right: 434 | left: 435 | left: {name: away, pseudo_type: String, type: local} 436 | op: == 437 | pseudo_type: Boolean 438 | right: {name: team, pseudo_type: String, type: local} 439 | type: comparison 440 | op: and 441 | pseudo_type: Boolean 442 | right: 443 | left: 444 | index: {pseudo_type: Int, type: int, value: 0} 445 | pseudo_type: Int 446 | sequence: 447 | name: goals 448 | pseudo_type: [Array, Int, 2] 449 | type: local 450 | type: index 451 | op: < 452 | pseudo_type: Boolean 453 | right: 454 | index: {pseudo_type: Int, type: int, value: 1} 455 | pseudo_type: Int 456 | sequence: 457 | name: goals 458 | pseudo_type: [Array, Int, 2] 459 | type: local 460 | type: index 461 | type: comparison 462 | type: binary_op 463 | type: binary_op 464 | type: if_statement 465 | name: result_points 466 | params: 467 | - {name: team, pseudo_type: Function, type: local} 468 | - {name: host, pseudo_type: String, type: local} 469 | - {name: away, pseudo_type: String, type: local} 470 | - {name: goals, pseudo_type: String, type: local} 471 | pseudo_type: 472 | - Function 473 | - String 474 | - String 475 | - String 476 | - [Array, Int, 2] 477 | - Int 478 | return_type: Int 479 | type: function_definition 480 | dependencies: [] 481 | main: 482 | - block: 483 | - args: 484 | - {pseudo_type: String, type: string, value: 'usage: football '} 485 | function: display 486 | namespace: io 487 | pseudo_type: Void 488 | type: standard_call 489 | otherwise: 490 | block: 491 | - pseudo_type: Void 492 | target: 493 | name: results 494 | pseudo_type: 495 | - List 496 | - - Tuple 497 | - String 498 | - String 499 | - [Array, Int, 2] 500 | type: local 501 | type: assignment 502 | value: 503 | args: 504 | - args: 505 | - {pseudo_type: Int, type: int, value: 1} 506 | function: index 507 | namespace: system 508 | pseudo_type: String 509 | type: standard_call 510 | function: 511 | name: load_results 512 | pseudo_type: 513 | - Function 514 | - String 515 | - - List 516 | - - Tuple 517 | - String 518 | - String 519 | - [Array, Int, 2] 520 | type: local 521 | pseudo_type: 522 | - List 523 | - - Tuple 524 | - String 525 | - String 526 | - [Array, Int, 2] 527 | type: call 528 | - args: 529 | - args: 530 | - name: results 531 | pseudo_type: 532 | - List 533 | - - Tuple 534 | - String 535 | - String 536 | - [Array, Int, 2] 537 | type: local 538 | - args: 539 | - {pseudo_type: Int, type: int, value: 2} 540 | function: index 541 | namespace: system 542 | pseudo_type: String 543 | type: standard_call 544 | function: 545 | name: calculate_points 546 | pseudo_type: 547 | - Function 548 | - - List 549 | - - Tuple 550 | - String 551 | - String 552 | - [Array, Int, 2] 553 | - String 554 | - Int 555 | type: local 556 | pseudo_type: Int 557 | type: call 558 | function: display 559 | namespace: io 560 | pseudo_type: Void 561 | type: standard_call 562 | pseudo_type: Void 563 | type: else_statement 564 | pseudo_type: Void 565 | test: 566 | left: 567 | args: [] 568 | function: arg_count 569 | namespace: system 570 | pseudo_type: Int 571 | type: standard_call 572 | op: < 573 | pseudo_type: Boolean 574 | right: {pseudo_type: Int, type: int, value: 3} 575 | type: comparison 576 | type: if_statement 577 | type: module 578 | -------------------------------------------------------------------------------- /examples/football.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def load_results(filename): 4 | with open(filename, 'r') as f: 5 | raw = f.read() 6 | 7 | lines = raw.split('\n') # readlines support in v0.3 8 | return [parse_result(line) for line in lines if line] 9 | 10 | def parse_result(line): 11 | away_index = line.index(' - ') + 3 12 | result_index = line.index(' ', away_index) + 1 13 | goals = line[result_index:].split(':') 14 | return line[:away_index - 3], line[away_index:result_index - 1], (int(goals[0]), int(goals[1])) 15 | 16 | def calculate_points(results, team): # call(*arg) supported only for tuples 17 | return sum(result_points(team, *result) for result in results) 18 | 19 | def result_points(team, host, away, goals): 20 | if host == team and goals[0] > goals[1] or away == team and goals[0] < goals[1]: 21 | return 3 22 | elif goals[0] == goals[1] and (host == team or away == team): 23 | return 1 24 | else: 25 | return 0 26 | 27 | if len(sys.argv) < 3: 28 | print('usage: football ') 29 | else: 30 | results = load_results(sys.argv[1]) 31 | print(calculate_points(results, sys.argv[2])) -------------------------------------------------------------------------------- /examples/football.rb: -------------------------------------------------------------------------------- 1 | def load_results(filename) 2 | raw = File.read(filename) 3 | lines = raw.split("\n") 4 | lines.select { |line| !line.empty? }.map { |line| parse_result(line) } 5 | end 6 | 7 | def parse_result(line) 8 | away_index = line.index(' - ') + 3 9 | result_index = line.index(' ', away_index) + 1 10 | goals = line[result_index..-1].split(':') 11 | [line[0...away_index - 3], line[away_index...result_index - 1], [goals[0].to_i, goals[1].to_i]] 12 | end 13 | 14 | def calculate_points(results, team) 15 | results.reduce(0) { |memo, result| memo + result_points(team, result[0], result[1], result[2]) } 16 | end 17 | 18 | def result_points(team, host, away, goals) 19 | if host == team && goals[0] > goals[1] || away == team && goals[0] < goals[1] 20 | 3 21 | elsif goals[0] == goals[1] && (host == team || away == team) 22 | 1 23 | else 24 | 0 25 | end 26 | 27 | end 28 | 29 | if ARGV.length < 2 30 | puts 'usage: football ' 31 | else 32 | results = load_results(ARGV[0]) 33 | puts calculate_points(results, ARGV[1]) 34 | end 35 | 36 | -------------------------------------------------------------------------------- /examples/football.txt: -------------------------------------------------------------------------------- 1 | Liverpool - Arsenal 3:0 2 | Stoke City - Liverpool 1:0 3 | Arsenal - Leicester 2:4 4 | Manchester City - Liverpool 0:1 5 | Arsenal - Liverpool 2:0 6 | Manchester City - Arsenal 1:0 7 | Manchester City - Leicester 0:2 -------------------------------------------------------------------------------- /examples/in.pseudo.yaml: -------------------------------------------------------------------------------- 1 | constants: [] 2 | custom_exceptions: [] 3 | definitions: [] 4 | dependencies: [] 5 | main: 6 | - pseudo_type: Void 7 | target: 8 | name: s 9 | pseudo_type: [List, Int] 10 | type: local 11 | type: assignment 12 | value: 13 | elements: 14 | - {pseudo_type: Int, type: int, value: 4} 15 | - {pseudo_type: Int, type: int, value: 2} 16 | pseudo_type: [List, Int] 17 | type: list 18 | - block: 19 | - args: 20 | - name: s 21 | pseudo_type: [List, Int] 22 | type: local 23 | function: display 24 | namespace: io 25 | pseudo_type: Void 26 | type: standard_call 27 | otherwise: null 28 | pseudo_type: Void 29 | test: 30 | args: 31 | - {pseudo_type: Int, type: int, value: 4} 32 | message: include? 33 | pseudo_type: Boolean 34 | receiver: 35 | name: s 36 | pseudo_type: [List, Int] 37 | type: local 38 | type: standard_method_call 39 | type: if_statement 40 | type: module 41 | -------------------------------------------------------------------------------- /examples/in.py: -------------------------------------------------------------------------------- 1 | s = [4, 2] 2 | if '2' in s: 3 | print(s) 4 | -------------------------------------------------------------------------------- /examples/int.pseudo.yaml: -------------------------------------------------------------------------------- 1 | constants: [] 2 | definitions: 3 | - block: 4 | - pseudo_type: Int 5 | type: implicit_return 6 | value: {pseudo_type: Int, type: int, value: 2} 7 | name: s 8 | params: [e] 9 | pseudo_type: Function[Int, Int] 10 | return_type: Int 11 | type: function_definition 12 | dependencies: [] 13 | main: 14 | - args: 15 | - {pseudo_type: Int, type: int, value: 2} 16 | function: {name: s, type: local} 17 | pseudo_type: Int 18 | type: call 19 | type: module 20 | -------------------------------------------------------------------------------- /examples/int.py: -------------------------------------------------------------------------------- 1 | def s(e): 2 | return 2 3 | 4 | s(2) 5 | -------------------------------------------------------------------------------- /examples/int_test.pseudon.yaml: -------------------------------------------------------------------------------- 1 | type: program 2 | code: 3 | - {type: int, value: 42} 4 | -------------------------------------------------------------------------------- /examples/int_test.py: -------------------------------------------------------------------------------- 1 | 42 2 | -------------------------------------------------------------------------------- /examples/lisp.py: -------------------------------------------------------------------------------- 1 | class SexpNode: 2 | pass 3 | 4 | class ListNode(SexpNode): 5 | def __init__(self, elements): 6 | self.elements = elements 7 | 8 | def __repr__(self): 9 | s = ' 'join([repr(element) for element in self.elements]) 10 | return 'List[{0}]'.format(s) 11 | 12 | class StringNode(SexpNode): 13 | def __init__(self, value): 14 | self.value = value 15 | 16 | def __repr__(self): 17 | return 'String[{0}]'.format(self.value) 18 | 19 | class IntNode(SexpNode): 20 | def __init__(self, value): 21 | self.value = value 22 | 23 | def __repr__(self): 24 | return 'Int[{0}]'.format(self.value) 25 | 26 | class IdentifierNode(SexpNode): 27 | def __init__(self, label): 28 | self.label = label 29 | 30 | def __repr__(self): 31 | return 'Identifier[{0}]'.format(self.label) 32 | 33 | class SexpParser: 34 | def parse(self, code): 35 | sexp = [ListNode([])] 36 | token = '' 37 | in_str = False 38 | for c in code: 39 | if c == '(' and not in_str: 40 | sexp.append(ListNode([])) 41 | elif c == ')' and not in_str: 42 | if token: 43 | sexp[-1].elements.append(self.categorize_token(token)) 44 | token = '' 45 | temp = sexp.pop() 46 | sexp[-1].elements.append(temp) 47 | elif c in (' ', '\n', '\t') and not in_str: 48 | sexp[-1].elements.append(self.categorize_token(token)) 49 | token = '' 50 | elif c == '\"': 51 | in_str = not in_str 52 | else: 53 | token += c 54 | return sexp[0] 55 | 56 | 57 | def categorize_token(self, token): 58 | if token[0] == '"': 59 | return StringNode(token[1:-1]) 60 | elif token.isnumeric(): 61 | return IntNode(token) 62 | else: 63 | return IdentifierNode(token) 64 | 65 | print(repr(SexpParser.parse('(ls 2)'))) 66 | 67 | # List[Identifier[ls] Int[2]] 68 | 69 | -------------------------------------------------------------------------------- /examples/logical.pseudo.yaml: -------------------------------------------------------------------------------- 1 | constants: [] 2 | custom_exceptions: [] 3 | definitions: [] 4 | dependencies: [] 5 | main: 6 | - pseudo_type: Void 7 | target: {name: e, pseudo_type: Int, type: local} 8 | type: assignment 9 | value: {pseudo_type: Int, type: int, value: 4} 10 | - block: 11 | - args: 12 | - {name: e, pseudo_type: Int, type: local} 13 | function: display 14 | namespace: io 15 | pseudo_type: Void 16 | type: standard_call 17 | otherwise: null 18 | pseudo_type: Void 19 | test: 20 | left: {name: e, pseudo_type: Int, type: local} 21 | op: '>' 22 | pseudo_type: Boolean 23 | right: {pseudo_type: Int, type: int, value: 0} 24 | type: comparison 25 | type: if_statement 26 | - block: 27 | - args: 28 | - {name: e, pseudo_type: Int, type: local} 29 | function: display 30 | namespace: io 31 | pseudo_type: Void 32 | type: standard_call 33 | otherwise: null 34 | pseudo_type: Void 35 | test: 36 | op: not 37 | pseudo_type: Boolean 38 | type: unary_op 39 | value: 40 | left: {name: e, pseudo_type: Int, type: local} 41 | op: '>' 42 | pseudo_type: Boolean 43 | right: {pseudo_type: Int, type: int, value: 0} 44 | type: comparison 45 | type: if_statement 46 | - block: 47 | - args: 48 | - {name: e, pseudo_type: Int, type: local} 49 | function: display 50 | namespace: io 51 | pseudo_type: Void 52 | type: standard_call 53 | otherwise: null 54 | pseudo_type: Void 55 | test: 56 | left: {name: e, pseudo_type: Int, type: local} 57 | op: '>=' 58 | pseudo_type: Boolean 59 | right: {pseudo_type: Int, type: int, value: 2} 60 | type: binary_op 61 | type: if_statement 62 | - block: 63 | - args: 64 | - {name: e, pseudo_type: Int, type: local} 65 | function: display 66 | namespace: io 67 | pseudo_type: Void 68 | type: standard_call 69 | otherwise: null 70 | pseudo_type: Void 71 | test: 72 | left: {name: e, pseudo_type: Int, type: local} 73 | op: '!=' 74 | pseudo_type: Boolean 75 | right: {pseudo_type: Int, type: int, value: 0} 76 | type: binary_op 77 | type: if_statement 78 | - block: 79 | - args: 80 | - {pseudo_type: Int, type: int, value: 2} 81 | function: display 82 | namespace: io 83 | pseudo_type: Void 84 | type: standard_call 85 | otherwise: null 86 | pseudo_type: Void 87 | test: 88 | left: 89 | left: {name: e, pseudo_type: Int, type: local} 90 | op: '>' 91 | pseudo_type: Boolean 92 | right: {pseudo_type: Int, type: int, value: 0} 93 | type: comparison 94 | op: and 95 | pseudo_type: Boolean 96 | right: 97 | left: {pseudo_type: Int, type: int, value: 4} 98 | op: '>' 99 | pseudo_type: Boolean 100 | right: {pseudo_type: Int, type: int, value: 0} 101 | type: comparison 102 | type: binary_op 103 | type: if_statement 104 | - block: 105 | - args: 106 | - {pseudo_type: Int, type: int, value: 2} 107 | function: display 108 | namespace: io 109 | pseudo_type: Void 110 | type: standard_call 111 | otherwise: null 112 | pseudo_type: Void 113 | test: 114 | left: 115 | left: {name: e, pseudo_type: Int, type: local} 116 | op: == 117 | pseudo_type: Boolean 118 | right: {pseudo_type: Int, type: int, value: 2} 119 | type: binary_op 120 | op: or 121 | pseudo_type: Boolean 122 | right: 123 | left: {pseudo_type: Int, type: int, value: 4} 124 | op: '>' 125 | pseudo_type: Boolean 126 | right: {pseudo_type: Int, type: int, value: 0} 127 | type: comparison 128 | type: binary_op 129 | type: if_statement 130 | type: module 131 | -------------------------------------------------------------------------------- /examples/logical.py: -------------------------------------------------------------------------------- 1 | e = 4 2 | if e: 3 | print(e) 4 | 5 | if not e: 6 | print(e) 7 | 8 | if e >= 2: 9 | print(e) 10 | 11 | if e != 0: 12 | print(e) 13 | 14 | if e and 4: 15 | print(2) 16 | 17 | if e == 2 or 4: 18 | print(2) 19 | -------------------------------------------------------------------------------- /examples/map_test.pseudon.yaml: -------------------------------------------------------------------------------- 1 | type: program 2 | code: 3 | - type: idiom 4 | value: 5 | type: map 6 | sequence: 7 | type: local 8 | name: seq 9 | iter: 10 | type: local 11 | name: e 12 | lambda: 13 | type: lambda 14 | args: 15 | - type: local 16 | name: e 17 | body: 18 | - type: call 19 | callee: 20 | type: local 21 | name: e 22 | args: 23 | - type: int 24 | value: 42 25 | -------------------------------------------------------------------------------- /examples/map_test.py: -------------------------------------------------------------------------------- 1 | seq = [''] 2 | [len(e) for e in seq] 3 | -------------------------------------------------------------------------------- /examples/oop.pseudo.yaml: -------------------------------------------------------------------------------- 1 | constants: [] 2 | definitions: 3 | - attrs: 4 | - {is_public: false, name: a, pseudo_type: Int, type: class_attr} 5 | base: null 6 | constructor: 7 | block: 8 | - attr: 9 | attr: a 10 | object: {name: self, pseudo_type: Shape, type: local} 11 | pseudo_type: Int 12 | type: attr 13 | pseudo_type: Void 14 | type: attr_assignment 15 | value: {name: a, pseudo_type: Int, type: local} 16 | name: __init__ 17 | params: [a] 18 | pseudo_type: [Function, Int, Shape] 19 | return_type: null 20 | this: {name: Shape, type: typename} 21 | type: constructor 22 | methods: 23 | - block: 24 | - pseudo_type: Int 25 | type: implicit_return 26 | value: {pseudo_type: Int, type: int, value: 0} 27 | is_public: true 28 | name: area 29 | params: [] 30 | pseudo_type: [Function, Int] 31 | return_type: Int 32 | this: {name: Shape, type: typename} 33 | type: method_definition 34 | - block: 35 | - pseudo_type: Int 36 | type: implicit_return 37 | value: {pseudo_type: Int, type: int, value: 0} 38 | is_public: true 39 | name: peri 40 | params: [] 41 | pseudo_type: [Function, Int] 42 | return_type: Int 43 | this: {name: Shape, type: typename} 44 | type: method_definition 45 | name: Shape 46 | type: class_definition 47 | - attrs: 48 | - {is_public: false, name: a, pseudo_type: Int, type: class_attr} 49 | - {is_public: false, name: b, pseudo_type: Int, type: class_attr} 50 | base: Shape 51 | constructor: 52 | block: 53 | - attr: 54 | attr: a 55 | object: {name: self, pseudo_type: Rectangle, type: local} 56 | pseudo_type: Int 57 | type: attr 58 | pseudo_type: Void 59 | type: attr_assignment 60 | value: {name: a, pseudo_type: Int, type: local} 61 | - attr: 62 | attr: b 63 | object: {name: self, pseudo_type: Rectangle, type: local} 64 | pseudo_type: Int 65 | type: attr 66 | pseudo_type: Void 67 | type: attr_assignment 68 | value: {name: b, pseudo_type: Int, type: local} 69 | name: __init__ 70 | params: [a, b] 71 | pseudo_type: [Function, Int, Int, Rectangle] 72 | return_type: null 73 | this: {name: Rectangle, type: typename} 74 | type: constructor 75 | methods: 76 | - block: 77 | - pseudo_type: Int 78 | type: implicit_return 79 | value: 80 | left: 81 | attr: a 82 | object: {name: self, pseudo_type: Rectangle, type: local} 83 | pseudo_type: Int 84 | type: attr 85 | op: '*' 86 | pseudo_type: Int 87 | right: 88 | attr: b 89 | object: {name: self, pseudo_type: Rectangle, type: local} 90 | pseudo_type: Int 91 | type: attr 92 | type: binary_op 93 | is_public: true 94 | name: area 95 | params: [] 96 | pseudo_type: [Function, Int] 97 | return_type: Int 98 | this: {name: Rectangle, type: typename} 99 | type: method_definition 100 | - block: 101 | - pseudo_type: Int 102 | type: implicit_return 103 | value: 104 | left: 105 | left: {pseudo_type: Int, type: int, value: 2} 106 | op: '*' 107 | pseudo_type: Int 108 | right: 109 | attr: a 110 | object: {name: self, pseudo_type: Rectangle, type: local} 111 | pseudo_type: Int 112 | type: attr 113 | type: binary_op 114 | op: + 115 | pseudo_type: Int 116 | right: 117 | left: {pseudo_type: Int, type: int, value: 2} 118 | op: '*' 119 | pseudo_type: Int 120 | right: 121 | attr: b 122 | object: {name: self, pseudo_type: Rectangle, type: local} 123 | pseudo_type: Int 124 | type: attr 125 | type: binary_op 126 | type: binary_op 127 | is_public: true 128 | name: peri 129 | params: [] 130 | pseudo_type: [Function, Int] 131 | return_type: Int 132 | this: {name: Rectangle, type: typename} 133 | type: method_definition 134 | name: Rectangle 135 | type: class_definition 136 | - attrs: 137 | - {is_public: false, name: a, pseudo_type: Int, type: class_attr} 138 | - {is_public: false, name: b, pseudo_type: Int, type: class_attr} 139 | base: Rectangle 140 | constructor: 141 | block: 142 | - attr: 143 | attr: a 144 | object: {name: self, pseudo_type: Square, type: local} 145 | pseudo_type: Int 146 | type: attr 147 | pseudo_type: Void 148 | type: attr_assignment 149 | value: {name: a, pseudo_type: Int, type: local} 150 | - attr: 151 | attr: b 152 | object: {name: self, pseudo_type: Square, type: local} 153 | pseudo_type: Int 154 | type: attr 155 | pseudo_type: Void 156 | type: attr_assignment 157 | value: {name: a, pseudo_type: Int, type: local} 158 | name: __init__ 159 | params: [a] 160 | pseudo_type: [Function, Int, Square] 161 | return_type: null 162 | this: {name: Square, type: typename} 163 | type: constructor 164 | methods: [] 165 | name: Square 166 | type: class_definition 167 | dependencies: [] 168 | main: 169 | - local: s 170 | pseudo_type: Void 171 | type: local_assignment 172 | value: 173 | class: {name: Shape, type: typename} 174 | params: 175 | - {pseudo_type: Int, type: int, value: 0} 176 | pseudo_type: Shape 177 | type: new_instance 178 | value_type: Shape 179 | - local: q 180 | pseudo_type: Void 181 | type: local_assignment 182 | value: 183 | class: {name: Rectangle, type: typename} 184 | params: 185 | - {pseudo_type: Int, type: int, value: 2} 186 | - {pseudo_type: Int, type: int, value: 4} 187 | pseudo_type: Rectangle 188 | type: new_instance 189 | value_type: Rectangle 190 | - local: s2 191 | pseudo_type: Void 192 | type: local_assignment 193 | value: 194 | class: {name: Square, type: typename} 195 | params: 196 | - {pseudo_type: Int, type: int, value: 4} 197 | pseudo_type: Square 198 | type: new_instance 199 | value_type: Square 200 | - local: z 201 | pseudo_type: Void 202 | type: local_assignment 203 | value: 204 | elements: 205 | - {name: s2, pseudo_type: Square, type: local} 206 | - {name: q, pseudo_type: Rectangle, type: local} 207 | - {name: s2, pseudo_type: Square, type: local} 208 | pseudo_type: [List, Rectangle] 209 | type: list 210 | value_type: [List, Rectangle] 211 | type: module 212 | -------------------------------------------------------------------------------- /examples/oop.py: -------------------------------------------------------------------------------- 1 | class Shape: 2 | def __init__(self, a): 3 | self.a = a 4 | 5 | def area(self): 6 | return 0 7 | 8 | def peri(self): 9 | return 0 10 | 11 | class Rectangle(Shape): 12 | def __init__(self, a, b): 13 | self.a = a 14 | self.b = b 15 | 16 | def area(self): 17 | return self.a * self.b 18 | 19 | def peri(self): 20 | return 2 * self.a + 2 * self.b 21 | 22 | class Square(Rectangle): 23 | def __init__(self, a): 24 | self.a = a 25 | self.b = a 26 | 27 | s = Shape(0) 28 | 29 | q = Rectangle(2, 4) 30 | 31 | s2 = Square(4) 32 | 33 | z = [s2, q, s2] 34 | -------------------------------------------------------------------------------- /examples/swap.pseudo.yaml: -------------------------------------------------------------------------------- 1 | constants: [] 2 | custom_exceptions: [] 3 | definitions: 4 | - block: 5 | - pseudo_type: Void 6 | target: {name: _old_0_a, pseudo_type: Int, type: local} 7 | type: assignment 8 | value: 9 | index: {pseudo_type: Int, type: int, value: 0} 10 | pseudo_type: Int 11 | sequence: 12 | name: a 13 | pseudo_type: [List, Int] 14 | type: local 15 | type: index 16 | - pseudo_type: Void 17 | target: 18 | index: {pseudo_type: Int, type: int, value: 0} 19 | pseudo_type: Int 20 | sequence: 21 | name: a 22 | pseudo_type: [List, Int] 23 | type: local 24 | type: index 25 | type: assignment 26 | value: 27 | index: {pseudo_type: Int, type: int, value: 1} 28 | pseudo_type: Int 29 | sequence: 30 | name: a 31 | pseudo_type: [List, Int] 32 | type: local 33 | type: index 34 | - pseudo_type: Void 35 | target: 36 | index: {pseudo_type: Int, type: int, value: 1} 37 | pseudo_type: Int 38 | sequence: 39 | name: a 40 | pseudo_type: [List, Int] 41 | type: local 42 | type: index 43 | type: assignment 44 | value: {name: _old_0_a, pseudo_type: Int, type: local} 45 | - pseudo_type: [List, Int] 46 | type: implicit_return 47 | value: 48 | name: a 49 | pseudo_type: [List, Int] 50 | type: local 51 | name: x 52 | params: [a] 53 | pseudo_type: 54 | - Function 55 | - [List, Int] 56 | - [List, Int] 57 | return_type: [List, Int] 58 | type: function_definition 59 | dependencies: [] 60 | main: 61 | - pseudo_type: Void 62 | target: {name: b, pseudo_type: Int, type: local} 63 | type: assignment 64 | value: {pseudo_type: Int, type: int, value: 2} 65 | - pseudo_type: Void 66 | target: {name: a, pseudo_type: Int, type: local} 67 | type: assignment 68 | value: {pseudo_type: Int, type: int, value: 4} 69 | - pseudo_type: Void 70 | target: {name: _old_b, pseudo_type: Int, type: local} 71 | type: assignment 72 | value: {name: b, pseudo_type: Int, type: local} 73 | - pseudo_type: Void 74 | target: {name: b, pseudo_type: Int, type: local} 75 | type: assignment 76 | value: {name: a, pseudo_type: Int, type: local} 77 | - pseudo_type: Void 78 | target: {name: a, pseudo_type: Int, type: local} 79 | type: assignment 80 | value: {name: _old_b, pseudo_type: Int, type: local} 81 | - args: 82 | - {name: a, pseudo_type: Int, type: local} 83 | - {name: b, pseudo_type: Int, type: local} 84 | function: display 85 | namespace: io 86 | pseudo_type: Void 87 | type: standard_call 88 | - args: 89 | - args: 90 | - elements: 91 | - {pseudo_type: Int, type: int, value: 2} 92 | - {pseudo_type: Int, type: int, value: 4} 93 | pseudo_type: [List, Int] 94 | type: list 95 | function: 96 | name: x 97 | pseudo_type: 98 | - Function 99 | - [List, Int] 100 | - [List, Int] 101 | type: local 102 | pseudo_type: [List, Int] 103 | type: call 104 | function: display 105 | namespace: io 106 | pseudo_type: Void 107 | type: standard_call 108 | type: module 109 | -------------------------------------------------------------------------------- /examples/swap.py: -------------------------------------------------------------------------------- 1 | b = 2 2 | a = 4 3 | b, a = a, b 4 | print(a, b) # 2 4 5 | 6 | def x(a): 7 | a[0], a[1] = a[1], a[0] 8 | return a 9 | 10 | print(x([2, 4])) #[4, 2] 11 | 12 | -------------------------------------------------------------------------------- /examples/typing.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Tuple, Callable 2 | 3 | def f(s: Callable[[int], int]) -> int: 4 | return s(2) 5 | 6 | class A: 7 | def expand(self, a: int) -> 'B': 8 | return B(a) 9 | 10 | class B: 11 | def __init__(self, a): 12 | self.a = a 13 | 14 | # f(2) 15 | 16 | -------------------------------------------------------------------------------- /examples/verbal_expressions.pseudo.yaml: -------------------------------------------------------------------------------- 1 | constants: [] 2 | custom_exceptions: [] 3 | definitions: 4 | - attrs: 5 | - {is_public: false, name: raw_source, pseudo_type: String, type: class_attr} 6 | base: null 7 | constructor: 8 | block: 9 | - pseudo_type: Void 10 | target: {name: raw_source, pseudo_type: String, type: instance_variable} 11 | type: assignment 12 | value: {pseudo_type: String, type: string, value: ''} 13 | name: __init__ 14 | params: [] 15 | pseudo_type: [Function, VerbalExpression] 16 | return_type: null 17 | this: {name: VerbalExpression, type: typename} 18 | type: constructor 19 | methods: 20 | - block: 21 | - pseudo_type: Regexp 22 | type: implicit_return 23 | value: 24 | args: 25 | - {name: raw_source, pseudo_type: String, type: instance_variable} 26 | function: compile 27 | namespace: regexp 28 | pseudo_type: Regexp 29 | type: standard_call 30 | is_public: true 31 | name: compile 32 | params: [] 33 | pseudo_type: [Function, Regexp] 34 | return_type: Regexp 35 | this: {name: VerbalExpression, type: typename} 36 | type: method_definition 37 | - block: 38 | - pseudo_type: Void 39 | target: {name: raw_source, pseudo_type: String, type: instance_variable} 40 | type: assignment 41 | value: 42 | args: 43 | - {pseudo_type: String, type: string, value: ^} 44 | message: concat 45 | pseudo_type: String 46 | receiver: {name: raw_source, pseudo_type: String, type: instance_variable} 47 | type: standard_method_call 48 | - pseudo_type: VerbalExpression 49 | type: implicit_return 50 | value: {pseudo_type: VerbalExpression, type: this} 51 | is_public: true 52 | name: start_of_line 53 | params: [] 54 | pseudo_type: [Function, VerbalExpression] 55 | return_type: VerbalExpression 56 | this: {name: VerbalExpression, type: typename} 57 | type: method_definition 58 | - block: 59 | - pseudo_type: Void 60 | target: {name: raw_source, pseudo_type: String, type: instance_variable} 61 | type: assignment 62 | value: 63 | args: 64 | - args: 65 | - {pseudo_type: String, type: interpolation_literal, value: (} 66 | - index: 0 67 | pseudo_type: String 68 | type: interpolation_placeholder 69 | value: 70 | args: 71 | - {name: letter, pseudo_type: String, type: local} 72 | function: escape 73 | namespace: regexp 74 | pseudo_type: String 75 | type: standard_call 76 | - {pseudo_type: String, type: interpolation_literal, value: ')?'} 77 | pseudo_type: String 78 | type: interpolation 79 | message: concat 80 | pseudo_type: String 81 | receiver: {name: raw_source, pseudo_type: String, type: instance_variable} 82 | type: standard_method_call 83 | - pseudo_type: VerbalExpression 84 | type: implicit_return 85 | value: {pseudo_type: VerbalExpression, type: this} 86 | is_public: true 87 | name: maybe 88 | params: 89 | - {name: letter, pseudo_type: Function, type: local} 90 | pseudo_type: [Function, String, VerbalExpression] 91 | return_type: VerbalExpression 92 | this: {name: VerbalExpression, type: typename} 93 | type: method_definition 94 | - block: 95 | - pseudo_type: Void 96 | target: {name: raw_source, pseudo_type: String, type: instance_variable} 97 | type: assignment 98 | value: 99 | args: 100 | - args: 101 | - {pseudo_type: String, type: interpolation_literal, value: (} 102 | - index: 0 103 | pseudo_type: String 104 | type: interpolation_placeholder 105 | value: 106 | args: 107 | - {name: word, pseudo_type: String, type: local} 108 | function: escape 109 | namespace: regexp 110 | pseudo_type: String 111 | type: standard_call 112 | - {pseudo_type: String, type: interpolation_literal, value: )} 113 | pseudo_type: String 114 | type: interpolation 115 | message: concat 116 | pseudo_type: String 117 | receiver: {name: raw_source, pseudo_type: String, type: instance_variable} 118 | type: standard_method_call 119 | - pseudo_type: VerbalExpression 120 | type: implicit_return 121 | value: {pseudo_type: VerbalExpression, type: this} 122 | is_public: true 123 | name: find 124 | params: 125 | - {name: word, pseudo_type: Function, type: local} 126 | pseudo_type: [Function, String, VerbalExpression] 127 | return_type: VerbalExpression 128 | this: {name: VerbalExpression, type: typename} 129 | type: method_definition 130 | - block: 131 | - pseudo_type: Void 132 | target: {name: raw_source, pseudo_type: String, type: instance_variable} 133 | type: assignment 134 | value: 135 | args: 136 | - args: 137 | - {pseudo_type: String, type: interpolation_literal, value: '[^'} 138 | - index: 0 139 | pseudo_type: String 140 | type: interpolation_placeholder 141 | value: 142 | args: 143 | - {name: letter, pseudo_type: String, type: local} 144 | function: escape 145 | namespace: regexp 146 | pseudo_type: String 147 | type: standard_call 148 | - {pseudo_type: String, type: interpolation_literal, value: ']*'} 149 | pseudo_type: String 150 | type: interpolation 151 | message: concat 152 | pseudo_type: String 153 | receiver: {name: raw_source, pseudo_type: String, type: instance_variable} 154 | type: standard_method_call 155 | - pseudo_type: VerbalExpression 156 | type: implicit_return 157 | value: {pseudo_type: VerbalExpression, type: this} 158 | is_public: true 159 | name: anything_but 160 | params: 161 | - {name: letter, pseudo_type: Function, type: local} 162 | pseudo_type: [Function, String, VerbalExpression] 163 | return_type: VerbalExpression 164 | this: {name: VerbalExpression, type: typename} 165 | type: method_definition 166 | - block: 167 | - pseudo_type: Void 168 | target: {name: raw_source, pseudo_type: String, type: instance_variable} 169 | type: assignment 170 | value: 171 | args: 172 | - {pseudo_type: String, type: string, value: $} 173 | message: concat 174 | pseudo_type: String 175 | receiver: {name: raw_source, pseudo_type: String, type: instance_variable} 176 | type: standard_method_call 177 | - pseudo_type: VerbalExpression 178 | type: implicit_return 179 | value: {pseudo_type: VerbalExpression, type: this} 180 | is_public: true 181 | name: end_of_line 182 | params: [] 183 | pseudo_type: [Function, VerbalExpression] 184 | return_type: VerbalExpression 185 | this: {name: VerbalExpression, type: typename} 186 | type: method_definition 187 | - block: 188 | - pseudo_type: RegexpMatch 189 | type: implicit_return 190 | value: 191 | args: 192 | - {name: word, pseudo_type: String, type: local} 193 | message: match 194 | pseudo_type: RegexpMatch 195 | receiver: 196 | args: [] 197 | message: compile 198 | pseudo_type: Regexp 199 | type: this_method_call 200 | type: standard_method_call 201 | is_public: true 202 | name: match 203 | params: 204 | - {name: word, pseudo_type: Function, type: local} 205 | pseudo_type: [Function, String, RegexpMatch] 206 | return_type: RegexpMatch 207 | this: {name: VerbalExpression, type: typename} 208 | type: method_definition 209 | - block: 210 | - pseudo_type: String 211 | type: implicit_return 212 | value: {name: raw_source, pseudo_type: String, type: instance_variable} 213 | is_public: true 214 | name: source 215 | params: [] 216 | pseudo_type: [Function, String] 217 | return_type: String 218 | this: {name: VerbalExpression, type: typename} 219 | type: method_definition 220 | name: VerbalExpression 221 | type: class_definition 222 | dependencies: [] 223 | main: 224 | - pseudo_type: Void 225 | target: {name: v, pseudo_type: VerbalExpression, type: local} 226 | type: assignment 227 | value: 228 | args: [] 229 | class_name: VerbalExpression 230 | pseudo_type: VerbalExpression 231 | type: new_instance 232 | - pseudo_type: Void 233 | target: {name: a, pseudo_type: VerbalExpression, type: local} 234 | type: assignment 235 | value: 236 | args: [] 237 | message: end_of_line 238 | pseudo_type: VerbalExpression 239 | receiver: 240 | args: 241 | - {pseudo_type: String, type: string, value: ' '} 242 | message: anything_but 243 | pseudo_type: VerbalExpression 244 | receiver: 245 | args: 246 | - {pseudo_type: String, type: string, value: www.} 247 | message: maybe 248 | pseudo_type: VerbalExpression 249 | receiver: 250 | args: 251 | - {pseudo_type: String, type: string, value: '://'} 252 | message: find 253 | pseudo_type: VerbalExpression 254 | receiver: 255 | args: 256 | - {pseudo_type: String, type: string, value: s} 257 | message: maybe 258 | pseudo_type: VerbalExpression 259 | receiver: 260 | args: 261 | - {pseudo_type: String, type: string, value: http} 262 | message: find 263 | pseudo_type: VerbalExpression 264 | receiver: 265 | args: [] 266 | message: start_of_line 267 | pseudo_type: VerbalExpression 268 | receiver: {name: v, pseudo_type: VerbalExpression, type: local} 269 | type: method_call 270 | type: method_call 271 | type: method_call 272 | type: method_call 273 | type: method_call 274 | type: method_call 275 | type: method_call 276 | - pseudo_type: Void 277 | target: {name: test_url, pseudo_type: String, type: local} 278 | type: assignment 279 | value: {pseudo_type: String, type: string, value: 'https://www.google.com'} 280 | - block: 281 | - args: 282 | - {pseudo_type: String, type: string, value: Valid URL} 283 | function: display 284 | namespace: io 285 | pseudo_type: Void 286 | type: standard_call 287 | otherwise: 288 | block: 289 | - args: 290 | - {pseudo_type: String, type: string, value: Invalid URL} 291 | function: display 292 | namespace: io 293 | pseudo_type: Void 294 | type: standard_call 295 | pseudo_type: Void 296 | type: else_statement 297 | pseudo_type: Void 298 | test: 299 | args: [] 300 | message: has_match 301 | pseudo_type: Boolean 302 | receiver: 303 | args: 304 | - {name: test_url, pseudo_type: String, type: local} 305 | message: match 306 | pseudo_type: RegexpMatch 307 | receiver: {name: a, pseudo_type: VerbalExpression, type: local} 308 | type: method_call 309 | type: standard_method_call 310 | type: if_statement 311 | - args: 312 | - args: [] 313 | message: source 314 | pseudo_type: String 315 | receiver: {name: a, pseudo_type: VerbalExpression, type: local} 316 | type: method_call 317 | function: display 318 | namespace: io 319 | pseudo_type: Void 320 | type: standard_call 321 | type: module 322 | -------------------------------------------------------------------------------- /examples/verbal_expressions.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class VerbalExpression: 4 | def __init__(self): 5 | self.raw_source = '' 6 | 7 | def compile(self): 8 | return re.compile(self.raw_source) 9 | 10 | def start_of_line(self): 11 | self.raw_source += '^' 12 | return self 13 | 14 | def maybe(self, letter): 15 | self.raw_source += '(%s)?' % re.escape(letter) 16 | return self 17 | 18 | def find(self, word): 19 | self.raw_source += '(%s)' % re.escape(word) 20 | return self 21 | 22 | def anything_but(self, letter): 23 | self.raw_source += '[^%s]*' % re.escape(letter) 24 | return self 25 | 26 | def end_of_line(self): 27 | self.raw_source += '$' 28 | return self 29 | 30 | def match(self, word): 31 | return self.compile().match(word) 32 | 33 | def source(self): 34 | return self.raw_source 35 | 36 | 37 | v = VerbalExpression() 38 | a = (v. 39 | start_of_line(). 40 | find('http'). 41 | maybe('s'). 42 | find('://'). 43 | maybe('www.'). 44 | anything_but(' '). 45 | end_of_line()) 46 | 47 | test_url = 'https://www.google.com' 48 | if a.match(test_url): 49 | print('Valid URL') 50 | else: 51 | print('Invalid URL') 52 | print(a.source()) 53 | -------------------------------------------------------------------------------- /pseudo_python/__init__.py: -------------------------------------------------------------------------------- 1 | import pseudo_python.parser 2 | import pseudo_python.ast_translator 3 | import yaml 4 | 5 | def translate(source): 6 | return pseudo_python.ast_translator.ASTTranslator(pseudo_python.parser.parse(source), source).translate() 7 | 8 | def translate_to_yaml(source): 9 | yaml.Dumper.ignore_aliases = lambda *args : True 10 | return yaml.dump(translate(source)) 11 | -------------------------------------------------------------------------------- /pseudo_python/api_translator.py: -------------------------------------------------------------------------------- 1 | from pseudo_python.builtin_typed_api import builtin_type_check 2 | from pseudo_python.errors import PseudoPythonTypeCheckError 3 | 4 | class Standard: 5 | ''' 6 | Standard classes should respond to expand and to return 7 | valid nodes on expand 8 | ''' 9 | pass 10 | 11 | class StandardCall(Standard): 12 | ''' 13 | converts to a standard call of the given namespace and function 14 | ''' 15 | def __init__(self, namespace, function, expander=None): 16 | self.namespace = namespace 17 | self.function = function 18 | self.expander = expander 19 | 20 | def expand(self, args): 21 | if not self.expander: 22 | q = builtin_type_check(self.namespace, self.function, None, args)[-1] 23 | return {'type': 'standard_call', 'namespace': self.namespace, 'function': self.function, 'args': args, 'pseudo_type': q} 24 | else: 25 | return self.expander(self.namespace, self.function, args) 26 | 27 | class StandardMethodCall(Standard): 28 | ''' 29 | converts to a method call of the same class 30 | ''' 31 | def __init__(self, type, message, default=None, expander=None): 32 | self.type = type 33 | self.message = message 34 | self.default = default 35 | self.expander = expander 36 | 37 | def expand(self, args): 38 | if self.default and len(args) - 1 in self.default: 39 | args += self.default[len(args) - 1] 40 | if not self.expander: 41 | q = builtin_type_check(self.type, self.message, args[0], args[1:])[-1] 42 | return {'type': 'standard_method_call', 'receiver': args[0], 'message': self.message, 'args': args[1:], 'pseudo_type': q} 43 | else: 44 | return self.expander(self.type, self.message, args) 45 | 46 | 47 | class StandardRegex(Standard): 48 | ''' 49 | converts re.compile(r'literal') to {type: regex} node 50 | re.compile(variable) to re.compile(variable) standard call 51 | ''' 52 | def expand(self, args): 53 | if args[0]['type'] == 'String': 54 | return {'type': 'regex', 'value': args[0]['value'], 'pseudo_type': 'Regexp'} 55 | else: 56 | return {'type': 'standard_call', 'namespace': 'regexp', 'function': 'compile', 'args': [args[0]], 'pseudo_type': 'Regexp'} 57 | 58 | 59 | class StandardSwapper(Standard): 60 | def __init__(self, type, message): 61 | self.type = type 62 | self.message = message 63 | 64 | def expand(self, args): 65 | if len(args) < 2: 66 | raise PseudoPythonTypeCheckError('%s expects more args' % self.message) 67 | q = builtin_type_check(self.type, self.message, args[1], [args[0]])[-1] 68 | return {'type': 'standard_method_call', 'receiver': args[1], 'args': [args[0]], 'message': self.message, 'pseudo_type': q} 69 | 70 | def to_int_expander(type, message, args): 71 | return len_expander(type, message, args) 72 | 73 | def len_expander(type, message, args): 74 | receiver_type = args[0]['pseudo_type'] 75 | if isinstance(receiver_type, list): 76 | a = receiver_type[0] 77 | else: 78 | a = receiver_type 79 | # print(a, message, args[0], args[1:]) 80 | # input(0) 81 | if message == 'length' and 'special' in args[0]: # len(sys.argv) 82 | return {'type': 'standard_call', 'namespace': 'system', 'function': 'arg_count', 'args': [], 'pseudo_type': 'Int'} 83 | else: 84 | q = builtin_type_check(a, message, args[0], args[1:]) 85 | return {'type': 'standard_method_call', 'receiver': args[0], 'message': message, 'args': [], 'pseudo_type': q[-1]} 86 | 87 | 88 | FUNCTION_API = { 89 | 'global': { 90 | 'input': StandardCall('io', 'read'), 91 | 'print': StandardCall('io', 'display'), 92 | 'str': StandardCall('global', 'to_string'), 93 | 'len': StandardMethodCall('List', 'length', expander=len_expander), 94 | 'int': StandardMethodCall('String', 'to_int', expander=to_int_expander) 95 | }, 96 | 97 | 'math': { 98 | 'log': { 99 | 1: StandardCall('math', 'ln'), 100 | 2: StandardCall('math', 'log') 101 | }, 102 | 103 | 'sin': StandardCall('math', 'sin'), 104 | 'cos': StandardCall('math', 'cos'), 105 | 'tan': StandardCall('math', 'tan'), 106 | 'pow': lambda left, right, pseudo_type: Node('binary_op', 107 | op='**', left=left, right=right, pseudo_type=pseudo_type) 108 | }, 109 | 110 | 're': { 111 | 'match': StandardMethodCall('Regexp', 'match'), 112 | 'sub': StandardMethodCall('Regexp', 'replace'), 113 | 'compile': StandardRegex(), 114 | 'escape': StandardCall('regexp', 'escape') 115 | } 116 | } 117 | 118 | METHOD_API = { 119 | 'String': { 120 | 'split': StandardMethodCall('String', 'split'), 121 | 'join': StandardSwapper('List', 'join'), 122 | 'upper': StandardMethodCall('String', 'upper'), 123 | 'lower': StandardMethodCall('String', 'lower'), 124 | 'title': StandardMethodCall('String', 'title'), 125 | 'center': StandardMethodCall('String', 'center', default={1: [{'type': 'string', 'value': ' ', 'pseudo_type': 'String'}]}), 126 | 'index': { 127 | 1: StandardMethodCall('String', 'find'), 128 | 2: StandardMethodCall('String', 'find_from') 129 | } 130 | }, 131 | 'List': { 132 | 'append': StandardMethodCall('List', 'push'), 133 | 'pop': StandardMethodCall('List', 'pop'), 134 | 'insert': { 135 | 1: StandardMethodCall('List', 'insert'), 136 | 2: StandardMethodCall('List', 'insert_at') 137 | }, 138 | 'remove': StandardMethodCall('List', 'remove'), 139 | 'extend': StandardMethodCall('List', 'push_many'), 140 | 'map': StandardMethodCall('List', 'map'), 141 | 'filter': StandardMethodCall('List', 'filter') 142 | }, 143 | 144 | 'Dictionary': { 145 | 'keys': StandardMethodCall('Dictionary', 'keys'), 146 | 'values': StandardMethodCall('Dictionary', 'values'), 147 | '[]': StandardMethodCall('Dictionary', 'getitem'), 148 | '[]=': StandardMethodCall('Dictionary', 'setitem') 149 | }, 150 | 151 | 'Array': { 152 | }, 153 | 154 | 'Tuple': { 155 | }, 156 | 157 | 'Set': { 158 | '|': StandardMethodCall('Set', 'union') 159 | }, 160 | 161 | 'Regexp': { 162 | 'match': StandardMethodCall('Regexp', 'match') 163 | }, 164 | 165 | 'RegexpMatch': { 166 | 'group': StandardMethodCall('RegexpMatch', 'group') 167 | } 168 | } 169 | 170 | OPERATOR_API = { 171 | 'List': { 172 | '+': 'concat', 173 | '*': 'repeat' 174 | }, 175 | 'Set': { 176 | '|': 'union', 177 | '&': 'intersection', 178 | '^': 'symmetric_diff', 179 | '-': 'diff' 180 | }, 181 | 'String': { 182 | '+': 'concat', 183 | '*': 'repeat', 184 | '%': 'c_format' 185 | } 186 | } 187 | 188 | -------------------------------------------------------------------------------- /pseudo_python/ast_translator.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import pseudo_python.env 3 | from pseudo_python.builtin_typed_api import TYPED_API, ORIGINAL_METHODS 4 | from pseudo_python.errors import PseudoPythonNotTranslatableError, PseudoPythonTypeCheckError, cant_infer_error, translation_error, type_check_error 5 | from pseudo_python.api_translator import Standard, StandardCall, StandardMethodCall, FUNCTION_API, METHOD_API, OPERATOR_API 6 | from pseudo_python.helpers import serialize_type, prepare_table 7 | 8 | BUILTIN_TYPES = { 9 | 'int': 'Int', 10 | 'float': 'Float', 11 | 'object': 'Object', 12 | 'str': 'String', 13 | 'list': 'List', 14 | 'dict': 'Dictionary', 15 | 'set': 'Set', 16 | 'tuple': 'Tuple', 17 | 'bool': 'Boolean', 18 | 'SRE_Pattern': 'Regexp', 19 | 'SRE_Match': 'RegexpMatch' 20 | } 21 | 22 | PSEUDON_BUILTIN_TYPES = {v: k for k, v in BUILTIN_TYPES.items()} 23 | 24 | BUILTIN_SIMPLE_TYPES = { 25 | 'int': 'Int', 26 | 'float': 'Float', 27 | 'str': 'String', 28 | 'bool': 'Boolean' 29 | } 30 | 31 | KEY_TYPES = {'str', 'int', 'float', 'bool'} 32 | 33 | PSEUDO_KEY_TYPES = {'String', 'Int', 'Float', 'Bool'} 34 | 35 | BUILTIN_FUNCTIONS = {'print', 'input', 'str', 'set', 'int', 'len', 'any', 'all', 'sum'} 36 | 37 | FORBIDDEN_TOP_LEVEL_FUNCTIONS = {'map', 'filter'} 38 | 39 | ITERABLE_TYPES = {'String', 'List', 'Dictionary', 'Set', 'Array'} 40 | 41 | TESTABLE_TYPE = 'Boolean' 42 | 43 | INDEXABLE_TYPES = {'String', 'List', 'Dictionary', 'Array', 'Tuple'} 44 | 45 | COMPARABLE_TYPES = {'Int', 'Float', 'String'} 46 | 47 | TYPES_WITH_LENGTH = {'String', 'List', 'Dictionary', 'Array', 'Tuple', 'Set'} 48 | 49 | NUMBER_TYPES = {'Int', 'Float'} 50 | 51 | PSEUDO_OPS = { 52 | ast.Add: '+', 53 | ast.Sub: '-', 54 | ast.Div: '/', 55 | ast.Mult: '*', 56 | ast.Pow: '**', 57 | 58 | ast.Eq: '==', 59 | ast.Lt: '<', 60 | ast.Gt: '>', 61 | ast.LtE: '<=', 62 | ast.GtE: '>=', 63 | ast.NotEq: '!=', 64 | ast.Mod: '%', 65 | 66 | ast.And: 'and', 67 | ast.Or: 'or', 68 | ast.Not: 'not', 69 | ast.BitAnd: '&', 70 | ast.BitOr: '|', 71 | ast.BitXor: '^' 72 | } 73 | 74 | 75 | 76 | class ASTTranslator: 77 | 78 | def __init__(self, tree, code): 79 | self.tree = tree 80 | self.in_class = False 81 | self.lines = [''] + code.split('\n') # easier 1based access with lineno 82 | self.type_env = pseudo_python.env.Env(dict(TYPED_API.items()), None) 83 | 84 | def translate(self): 85 | self.dependencies = [] 86 | self.definitions = [] 87 | self._definition_index = {'functions': {}} 88 | self.constants = [] 89 | self.main = [] 90 | self._exceptions = {'Exception'} 91 | self.custom_exceptions = [] 92 | self._hierarchy = {} 93 | self._translated = {'functions': set()} 94 | self._attr_index = {} 95 | self._attrs = {} 96 | self._imports = set() 97 | self._typing_imports = set() 98 | self.current_class = None 99 | self._tuple_assigned = [] 100 | self._tuple_used = [] 101 | self.function_name = 'top level' 102 | self.type_env['functions'] = {} 103 | self._translate_top_level(self.tree) 104 | self._translate_hinted_functions() 105 | self._translate_pure_functions() 106 | main = self._translate_main() 107 | definitions = self._translate_definitions() 108 | return {'type': 'module', 'dependencies': self.dependencies, 'custom_exceptions': self.custom_exceptions, 'constants': self.constants, 'definitions': definitions, 'main': main} 109 | 110 | def _translate_definitions(self): 111 | definitions = [] 112 | for definition in self.definitions: 113 | if definition[0] == 'function': 114 | if not isinstance(self._definition_index['functions'][definition[1]], dict): 115 | raise cant_infer_error(definition[1], self.lines[self._definition_index['functions'][definition[1]].lineno]) 116 | 117 | definitions.append(self._definition_index['functions'][definition[1]]) 118 | elif definition[0] == 'class': #inherited 119 | c = {'type': 'class_definition', 'name': definition[1], 'base': definition[2], 120 | 'attrs': [self._attr_index[definition[1]][a][0] 121 | for a 122 | in self._attrs[definition[1]] if not self._attr_index[definition[1]][a][1]], 'methods': [], 'constructor': None} 123 | for method in definition[3]: 124 | m = self._definition_index[definition[1]][method] 125 | if not isinstance(m, dict): 126 | # input(self.type_env[definition[1]]) 127 | self._translate_hinted_fun(method, definition[1]) 128 | m = self._definition_index[definition[1]][method] 129 | if not isinstance(m, dict): 130 | raise cant_infer_error('%s#%s' % (definition[1], method), self.lines[m.lineno]) 131 | 132 | if method == '__init__': 133 | c['constructor'] = m 134 | else: 135 | c['methods'].append(m) 136 | 137 | definitions.append(c) 138 | 139 | return definitions 140 | 141 | def _translate_main(self): 142 | self.current_class = None 143 | self.function_name = 'global scope' 144 | return self._translate_node(self.main) 145 | 146 | def _translate_top_level(self, node): 147 | nodes = node.body 148 | self.current_constant = None 149 | for z, n in enumerate(nodes): # placeholders and index 150 | # for function/class defs to be filled by type inference later 151 | if isinstance(n, ast.Import): 152 | if self.definitions or self.main: 153 | raise translation_error('imports can be only on top', (n.lineno, n.col_offset), self.lines[n.lineno]) 154 | 155 | self._imports.add(n.names[0].name) 156 | self.type_env.top['_%s' % n.names[0].name], self.type_env.top[n.names[0].name] = self.type_env.top[n.names[0].name], 'library' 157 | 158 | elif isinstance(n, ast.ImportFrom): 159 | if self.definitions or self.main: 160 | raise translation_error('imports can be only on top', (n.lineno, n.col_offset), self.lines[n.lineno]) 161 | if n.module != 'typing' or any(al.asname for al in n.names): 162 | raise translation_error('only import and from typing import .. supported', (n.lineno, n.col_offset), self.lines[n.lineno]) 163 | 164 | self._typing_imports |= {al.name for al in n.names} 165 | 166 | elif isinstance(n, ast.FunctionDef): 167 | self.definitions.append(('function', n.name)) 168 | self._definition_index['functions'][n.name] = n 169 | self.type_env.top['functions'][n.name] = ['Function'] + ([None] * len(n.args.args)) + [None] 170 | self.type_env.top[n.name] = self.type_env.top['functions'][n.name] 171 | elif isinstance(n, ast.ClassDef): 172 | self.assert_translatable('class', decorator_list=([], n.decorator_list)) 173 | self._hierarchy[n.name] = (None, set()) 174 | if n.bases: 175 | if len(n.bases) == 1 and isinstance(n.bases[0], ast.Name) and n.bases[0].id in self._exceptions: 176 | self.custom_exceptions.append({ 177 | 'type': 'custom_exception', 178 | 'name': n.name, 179 | 'base': None if n.bases[0].id == 'Exception' else n.bases[0].id 180 | }) 181 | self._exceptions.add(n.name) 182 | self.type_env[n.name] = 'ExceptionType' 183 | continue 184 | elif len(n.bases) != 1 or not isinstance(n.bases[0], ast.Name) or n.bases[0].id not in self._definition_index: 185 | raise translation_error( 186 | 'only single inheritance from an already defined class is supported', 187 | (n.bases[0].lineno, n.bases[0].col_offset), 188 | self.lines[n.lineno]) 189 | 190 | base = n.bases[0].id 191 | self.type_env.top[n.name] = {l: t for l, t in self.type_env.top[base].items()} 192 | self._attr_index[n.name] = {l: [t[0], True] for l, t in self._attr_index[base].items()} 193 | self._hierarchy[n.name] = (base, set()) 194 | self._hierarchy[base][1].add(n.name) 195 | else: 196 | base = None 197 | self._attr_index[n.name] = {} 198 | self.type_env[n.name] = {} 199 | 200 | self.definitions.append(('class', n.name, base, [])) 201 | 202 | self._definition_index[n.name] = {} 203 | self._attrs[n.name] = [] 204 | 205 | for y, m in enumerate(n.body): 206 | if isinstance(m, ast.FunctionDef): 207 | if not m.args.args or m.args.args[0].arg != 'self': 208 | raise translation_error( 209 | 'only methods with a self arguments are supported(class %s)' % n.name, 210 | (m.lineno, m.col_offset + 4 + len(m.name) + 1), 211 | self.lines[m.lineno], 212 | 'example: def method_name(self, x):') 213 | 214 | self.definitions[-1][3].append(m.name) 215 | self._definition_index[n.name][m.name] = m 216 | self.type_env.top[n.name][m.name] = ['Function'] + ([None] * (len(m.args.args) - 1)) + [None] 217 | else: 218 | raise translation_error('only methods are supported in classes', 219 | (m.lineno, m.col_offset), 220 | self.lines[m.lineno]) 221 | 222 | elif isinstance(n, ast.Assign) and len(n.targets) == 1 and isinstance(n.targets[0], ast.Name): 223 | if n.targets[0].id[0].islower(): 224 | self.main.append(n) 225 | elif any(letter.isalpha() and letter.islower() for letter in n.targets[0].id[1:]): 226 | raise type_check_error( 227 | 'you make pseudo-python very pseudo-confused: please use only snake_case or SCREAMING_SNAKE_CASE for variables', 228 | (n.targets[0].lineno, n.targets[0].col_offset), 229 | self.lines[n.targets[0].lineno], 230 | suggestions='example:\ns = 2 # local\nK = 2 # constant') 231 | elif self.main: 232 | raise translation_error( 233 | 'constants must be initialized before all other top level code', 234 | (n.targets[0].lineno, n.targets[0].col_offset), 235 | self.lines[n.targets[0].lineno], 236 | right='K = 2\ndef ..', 237 | wrong='def ..\nK = 2') 238 | 239 | elif self.type_env.top[n.targets[0].id]: 240 | raise translation_error( 241 | "you can't override a constant in pseudo-python", 242 | (n.targets[0].lineno, n.targets[0].col_offset), 243 | self.lines[n.targets[0].lineno]) 244 | 245 | else: 246 | self.current_constant = n.targets[0].id 247 | init = self._translate_node(n.value) 248 | self.constants.append({ 249 | 'type': 'constant', 250 | 'constant': n.targets[0].id, 251 | 'init': init, 252 | 'pseudo_type': init['pseudo_type'] 253 | }) 254 | self.type_env.top[n.targets[0].id] = init['pseudo_type'] 255 | self.current_constant = None 256 | else: 257 | self.current_constant = None 258 | self.main.append(n) 259 | 260 | def _translate_node(self, node, in_call=False): 261 | if isinstance(node, ast.AST): 262 | if self.current_constant and type(node) not in [ast.Num, ast.Str, ast.List]: 263 | raise translation_error( 264 | 'You can initialize constants only with literals', 265 | (node[0].lineno, node[0].col_offset), 266 | self.lines[node[0].lineno], 267 | right='K = [2, 4]', 268 | wrong='K = [2, x]') 269 | 270 | fields = {field: getattr(node, field) for field in node._fields} 271 | l = getattr(node, 'lineno', None) 272 | if l: 273 | fields['location'] = l, node.col_offset 274 | else: 275 | fields['location'] = None 276 | if isinstance(node, ast.Attribute): 277 | fields['in_call'] = in_call 278 | return getattr(self, '_translate_%s' % type(node).__name__.lower())(**fields) 279 | elif isinstance(node, list): 280 | results = [] 281 | for n in node: 282 | x = self._translate_node(n) 283 | if isinstance(x, list): 284 | results.extend(x) 285 | else: 286 | results.append(x) 287 | return results 288 | elif isinstance(node, dict): 289 | return {k: self._translate_node(v) for k, v in node.items()} 290 | else: 291 | return node 292 | 293 | def _translate_num(self, n, location): 294 | type = 'int' if isinstance(n, int) else 'float' 295 | return {'type': type, 'value': n, 'pseudo_type': type.title()} 296 | 297 | def _translate_name(self, id, ctx, location): 298 | if id[0].isupper(): 299 | if id not in self.type_env.top.values: 300 | raise type_check_error( 301 | 'name %s is not defined' % id, 302 | location, 303 | self.lines[location[0]]) 304 | id_type = self.type_env.top[id] 305 | if isinstance(id_type, dict): # class 306 | id_type = id 307 | return {'type': 'typename', 'name': id, 'pseudo_type': id_type} 308 | else: 309 | id_type = self.type_env[id] 310 | if id_type is None: 311 | raise type_check_error( 312 | '%s is not defined' % id, 313 | location, 314 | self.lines[location[0]]) 315 | 316 | # if isinstance(id_type, list): 317 | # id_type = tuple(['Function'] + id_type) 318 | if id == 'self': 319 | return {'type': 'this', 'pseudo_type': id_type} 320 | else: 321 | z = {'type': 'local', 'name': id, 'pseudo_type': id_type} 322 | if z in self._tuple_assigned: 323 | if not any(a[0] == '_old_%s' % id for a in self._tuple_used): 324 | self._tuple_used.append(('_old_%s' % id, z)) 325 | z = {'type': 'local', 'name': '_old_%s' % id, 'pseudo_type': id_type} 326 | return z 327 | 328 | def _translate_call(self, func, args, keywords, starargs=None, kwargs=None, location=None): 329 | self.assert_translatable('call', keywords=([], keywords), kwargs=(None, kwargs)) 330 | #Python3.5 331 | # apparently you can't do e(*a, *a) in Python3.4 wtf 332 | initial_args = args[:] 333 | args = [] 334 | if starargs: # python3.4 335 | initial_args.append(ast.Starred(starargs, None)) 336 | 337 | for arg in initial_args: 338 | if isinstance(arg, ast.Starred): 339 | many_arg = self._translate_node(arg.value) 340 | if isinstance(many_arg['pseudo_type'], list) and many_arg['pseudo_type'][0] == 'Tuple': 341 | args += [{ 342 | 'type': 'index', 343 | 'sequence': many_arg, 344 | 'index': {'type': 'int', 'value': j, 'pseudo_type': 'Int'}, 345 | 'pseudo_type': t 346 | } 347 | for j, t 348 | in enumerate(many_arg['pseudo_type'][1:])] 349 | 350 | else: 351 | raise translation_error("pseudo-python supports (..*args) only for Tuple *args, because otherwise it doesn't know the exact arg count at compile time", 352 | location, self.lines[location[0]], 353 | wrong_type=many_arg['pseudo_type']) 354 | else: 355 | args.append(arg) 356 | 357 | 358 | 359 | if isinstance(func, ast.Name) and func.id in FORBIDDEN_TOP_LEVEL_FUNCTIONS: 360 | raise translation_error('%s supported only as list(%s)' % (func.id, func.id), 361 | location, self.lines[location[0]]) 362 | elif isinstance(func, ast.Name) and func.id == 'list': 363 | if len(args) != 1 or not isinstance(args[0], ast.Call) or not isinstance(args[0].func, ast.Name) or args[0].func.id not in FORBIDDEN_TOP_LEVEL_FUNCTIONS: 364 | raise translation_error('list currently not supported', 365 | location, self.lines[location[0]], 366 | suggestions='list is currently only supported for list(filter)') 367 | if len(args[0].args) != 2: 368 | raise type_check_error('%s expects 2 arg, received %d args' % (args[0].func.id, len(args[0].args)), 369 | location, self.lines[location[0]]) 370 | receiver_node = self._translate_node(args[0].args[1]) 371 | if self._general_type(receiver_node['pseudo_type']) != 'List': 372 | raise type_check_error('#%s currently works only' % args[0].func.id, 373 | location, self.lines[location[0]], 374 | wrong_type=receiver_node['pseudo_type']) 375 | 376 | 377 | if isinstance(args[0].args[0], ast.Lambda): 378 | if len(args[0].args[0].args.args) != 1: 379 | raise translation_error('lambda expected 1 arg, received %d' % len(args[0].args[0].args.args), 380 | location, self.lines[location[0]]) 381 | 382 | arg_nodes = [self._translate_functional_lambda(args[0].args[0], receiver_node['pseudo_type'][1])] 383 | else: 384 | arg_nodes = [self._translate_node(args[0].args.args[0], in_call=True)] 385 | 386 | if args[0].func.id == 'map': 387 | return_type = ['List', arg_nodes[0]['pseudo_type'][-1]] 388 | else: 389 | if arg_nodes[0]['pseudo_type'][-1] != 'Boolean': 390 | l = {'type': 'local', 'name': args[0].args[0].args.args[0].arg, 'pseudo_type': arg_nodes['pseudo_type'][-1]} 391 | arg_nodes[0] = { 392 | 'type': 'anonymous_function', 393 | 'pseudo_type': ['Function', arg_nodes['pseudo_type'][-1], 'Boolean'], 394 | 'return_type': 'Boolean', 395 | 'params': [l], 396 | 'block': [self._testable({ 397 | 'type': 'call', 398 | 'function': arg_nodes[0], 399 | 'args': [l], 400 | 'pseudo_type': arg_nodes[0]['pseudo_type'][-1] 401 | })] 402 | } 403 | 404 | return_type = ['List', 'Boolean'] 405 | return { 406 | 'type': 'standard_method_call', 407 | 'receiver': receiver_node, 408 | 'message': args[0].func.id, 409 | 'args': arg_nodes, 410 | 'pseudo_type': return_type 411 | } 412 | 413 | elif isinstance(func, ast.Name) and func.id in BUILTIN_FUNCTIONS: 414 | if func.id == 'set': 415 | if args: 416 | raise translation_error('only set() supported', 417 | location, self.lines[location[0]], 418 | suggestions='please use {} notation if a set has elements') 419 | return { 420 | 'type': 'set', 421 | 'pseudo_type': ['Set', None], 422 | 'elements': [] 423 | } 424 | 425 | elif func.id in ['any', 'all', 'sum']: 426 | if len(args) != 1: 427 | raise translation_error('%s expected 1 arg, not %d' % (func.id, len(args)), 428 | location, self.lines[location[0]]) 429 | elif isinstance(args[0], ast.ListComp): 430 | raise translation_error('%s expects a generator expression, not a list comprehension' % func.id, 431 | location, self.lines[location[0]]) 432 | elif isinstance(args[0], ast.GeneratorExp): 433 | return self._translate_generatorexp(args[0].generators, args[0].elt, func.id, location) 434 | else: 435 | arg_node = self._translate_node(args[0]) 436 | if func.id != 'sum': 437 | if arg_node['pseudo_type'] != ['List', 'Boolean']: 438 | raise type_check_error('%s expected List[Boolean]' % func.id, 439 | location, 440 | self.lines[location[0]], 441 | wrong_type=arg_node['pseudo_type']) 442 | message = 'boolean_%s?' % func.id 443 | return { 444 | 'type': 'standard_method_call', 445 | 'receiver': arg_node, 446 | 'message': message, 447 | 'args': [], 448 | 'pseudo_type': 'Boolean' 449 | } 450 | else: 451 | if arg_node['pseudo_type'] != ['List', 'Int'] and arg_node['pseudo_type'] != ['List', 'Float']: 452 | raise type_check_error('%s expected List[Int] / List[Float]' % func.id, 453 | location, 454 | self.lines[location[0]], 455 | wrong_type=arg_node['pseudo_type']) 456 | message = func.id 457 | _type = arg_node['pseudo_type'][1] 458 | initial = 0.0 if _type == 'Float' else 0 459 | 460 | return { 461 | 'type': 'standard_method_call', 462 | 'receiver': arg_node, 463 | 'message': 'reduce', 464 | 'args': [{ 465 | 'type': 'anonymous_function', 466 | 'params': [ 467 | {'type': 'local', 'name': 'memo', 'pseudo_type': _type}, 468 | {'type': 'local', 'name': 'value', 'pseudo_type': _type}], 469 | 'pseudo_type': ['Function', _type, _type, _type], 470 | 'return_type': _type, 471 | 'block': [{ 472 | 'type': 'binary_op', 473 | 'op': '+', 474 | 'left': {'type': 'local', 'name': 'memo', 'pseudo_type': _type}, 475 | 'right': {'type': 'local', 'name': 'value', 'pseudo_type': _type}, 476 | 'pseudo_type': 'Boolean' 477 | }] 478 | }, { 479 | 'type': _type.lower(), 480 | 'value': initial, 481 | 'pseudo_type': _type 482 | }], 483 | 'pseudo_type': _type 484 | } 485 | else: 486 | arg_nodes = self._translate_node(args) 487 | return self._translate_builtin_call('global', func.id, arg_nodes, location) 488 | 489 | arg_nodes = [arg if not isinstance(arg, ast.AST) else self._translate_node(arg) for arg in args] 490 | func_node = self._translate_node(func, in_call=True) 491 | 492 | if func_node['type'] == 'attr': 493 | if func_node['object']['pseudo_type'] == 'library': # math.log 494 | return self._translate_builtin_call(func_node['object']['name'], func_node['attr'], arg_nodes, location) 495 | elif self.current_class and self.current_class != 'functions' and isinstance(func.value, ast.Name) and func.value.id == 'self': 496 | node_type = 'this_method_call' 497 | elif self.current_class and self.current_class != 'functions' and func_node['object']['type'] == 'instance_variable': 498 | node_type = 'this_method_call' 499 | elif self._general_type(func_node['object']['pseudo_type']) in PSEUDON_BUILTIN_TYPES: # [2].append 500 | return self._translate_builtin_method_call(self._general_type(func_node['object']['pseudo_type']), func_node['object'], func_node['attr'], arg_nodes, location) 501 | else: 502 | node_type = 'method_call' 503 | 504 | return self._translate_real_method_call(node_type, self._general_type(func_node['object']['pseudo_type']), func_node['object'], func_node['attr'], arg_nodes, location) 505 | elif func_node['type'] == 'instance_variable': 506 | 507 | return self._translate_real_method_call('this_method_call', self.current_class, {'type': 'this', 'pseudo_type': self.current_class}, func_node['name'], arg_nodes, location) 508 | else: 509 | if (func_node['type'] == 'local' or func_node['type'] == 'this') and func_node['pseudo_type'][-1] is None: 510 | return self._translate_real_method_call('call', 'functions', None, 'self' if func_node['type'] == 'this' else func_node['name'], arg_nodes, location) 511 | elif func_node['type'] == 'typename': 512 | return self._translate_init(func_node['name'], arg_nodes, location) 513 | elif func_node['type'] == 'library_function': 514 | return self._translate_builtin_call(func_node['library'], func_node['function'], arg_nodes, location) 515 | else: 516 | if self._general_type(func_node['pseudo_type']) != 'Function': 517 | raise translation_error( 518 | 'only Function[..] type is callable', 519 | location, 520 | self.lines[location[0]], 521 | wrong_type=func_node['pseudo_type']) 522 | 523 | self._real_type_check(func_node['pseudo_type'], [arg_node['pseudo_type'] for arg_node in arg_nodes], (func_node['name'] if 'name' in func_node else func_node['type'])) 524 | z = func_node['pseudo_type'][-1] 525 | return {'type': 'call', 'function': func_node, 'args': arg_nodes, 'pseudo_type': z} 526 | 527 | def _translate_init(self, name, params, location): 528 | 529 | # check or save with the params 530 | # translate this function and then the pure functions in class 531 | class_types = self.type_env.top.values.get(name, None) 532 | if class_types is None: 533 | raise type_check_error( 534 | '%s is undefined' % name, 535 | location, 536 | self.lines[location[0]]) 537 | init = class_types.get('__init__') 538 | if init is None and params: 539 | raise type_check_error( 540 | 'constructor of %s didn\'t expect %d arguments' % (name, len(params)), 541 | location, 542 | self.lines[location[0]]) 543 | 544 | if init: 545 | self._definition_index[name]['__init__'] = self._translate_function(self._definition_index[name]['__init__'], name, {'pseudo_type': name}, '__init__', [p['pseudo_type'] for p in params]) 546 | init[-1] = name 547 | 548 | for label, m in self._definition_index[name].items(): 549 | self._translate_hinted_fun(label, name) 550 | 551 | for label, m in self._definition_index[name].items(): 552 | if len(self.type_env.top[name][label]) == 2 and label != '__init__': 553 | self._definition_index[name][label] = self._translate_function(m, name, {'pseudo_type': name}, label, []) 554 | 555 | return { 556 | 'type': 'new_instance', 557 | 'class_name': name, 558 | 'args': params, 559 | 'pseudo_type': name 560 | } 561 | 562 | def _translate_real_method_call(self, node_type, z, receiver, message, params, location): 563 | c = self.type_env.top[z] 564 | param_types = [param['pseudo_type'] for param in params] 565 | if message in c and len(c[message]) == 2 or len(c[message]) > 2 and c[message][1]: 566 | q = self._type_check(z, message, param_types)[-1] 567 | else: 568 | self._definition_index[z][message] = self._translate_function(self._definition_index[z][message], z, receiver, message, param_types) 569 | q = c[message][-1] 570 | 571 | if node_type == 'call': 572 | result = {'type': node_type, 'function': {'type': 'local', 'name': message, 'pseudo_type': c[message]}, 'args': params, 'pseudo_type': q} 573 | else: 574 | result = {'type': node_type, 'message': message, 'args': params, 'pseudo_type': q} 575 | if node_type == 'method_call': 576 | result['receiver'] = receiver 577 | return result 578 | 579 | def _translate_builtin_call(self, namespace, function, args, location): 580 | if namespace != 'global' and namespace not in self._imports: 581 | raise type_check_error( 582 | 'module %s not imported: impossible to use %s' % (namespace, function), 583 | location, self.lines[location[0]], 584 | suggestions='a tip: pseudo-python currently supports only import, no import as or from..import') 585 | if not namespace in FUNCTION_API: 586 | raise translation_error( 587 | "pseudo-python doesn't support %s" % namespace, 588 | location, self.lines[location[0]], 589 | suggestions='pseudo-python supports methods from\n %s' % ' '.join( 590 | k for k in FUNCTION_API if k != 'global')) 591 | api = FUNCTION_API[namespace].get(function) 592 | if not api: 593 | raise translation_error( 594 | 'pseudo-python doesn\'t support %s %s' % (namespace, function), 595 | location, self.lines[location[0]], 596 | suggestions='pseudo-python supports those %s functions\n %s' % ( 597 | namespace, 598 | prepare_table(TYPED_API[namespace], ORIGINAL_METHODS.get(namespace)).strip())) 599 | 600 | 601 | if not isinstance(api, dict): 602 | return api.expand(args) 603 | else: 604 | for count,(a, b) in api.items(): 605 | if len(args) == count: 606 | return b.expand(args) 607 | raise translation_error( 608 | 'pseudo-python doesn\'t support %s%s with %d args' % (namespace, function, len(args)), 609 | location, self.lines[location[0]]) 610 | 611 | def _translate_builtin_method_call(self, class_type, base, message, args, location): 612 | if class_type not in METHOD_API: 613 | raise translation_error( 614 | "pseudo-python doesn't support %s" % class_type, 615 | location,self.lines[location[0]], 616 | suggestions='pseudo-python support those builtin classes:\n%s' % ' '.join( 617 | PSEUDON_BUILTIN_TYPES[k] for k in METHOD_API.keys())) 618 | 619 | api = METHOD_API.get(class_type, {}).get(message) 620 | if not api: 621 | raise translation_error( 622 | "pseudo-python doesn\'t support %s#%s" % (serialize_type(class_type), message), 623 | location, self.lines[location[0]], 624 | suggestions='pseudo-python supports those %s methods:\n%s' % ( 625 | PSEUDON_BUILTIN_TYPES[class_type], 626 | prepare_table(TYPED_API[class_type], ORIGINAL_METHODS.get(class_type)).strip())) 627 | 628 | if isinstance(api, Standard): 629 | return api.expand([base] + args) 630 | else: 631 | for count, b in api.items(): 632 | if len(args) == count: 633 | return b.expand([base] + args) 634 | raise translation_error( 635 | 'pseudo-python doesn\'t support %s%s with %d args' % (serialize_type(class_type), message, len(args)), 636 | location, self.lines[location[0]]) 637 | 638 | def _translate_function(self, node, z, receiver, name, args): 639 | self.assert_translatable('functiondef', 640 | vararg=(None, node.args.vararg), kwonlyargs=([], node.args.kwonlyargs), 641 | kw_defaults=([], node.args.kw_defaults), defaults=([], node.args.defaults), 642 | decorator_list=([], node.decorator_list)) 643 | 644 | node_args = node.args.args if z == 'functions' else node.args.args[1:] 645 | 646 | if args is not None and len(node_args) != len(args): 647 | raise translation_error('%s expecting %d, got %d args' % (node.name, len(node_args), len(args)), 648 | (node.lineno, node.col_offset), self.lines[node.lineno]) 649 | 650 | if z not in self._translated: 651 | self._translated[z] = set() 652 | 653 | # 0-arg functions are inferred only in the beginning 654 | 655 | if args != [] and name in self._translated[z]: # self.type_env.top[z][name][1]: 656 | raise type_check_error( 657 | 'please move recursion in a next branch in %s' % node.name, 658 | location, self.lines[location[0]], 659 | suggestions='pseudo-python will detect non-recursive branches after the first one in v0.3', 660 | right='def lala(e):\n if e == 0:\n return 0\n else:\n return lala(e - 2)', 661 | wrong='def lala(e):\n if e > 0:\n return lala(e - 2)\n..') 662 | 663 | self._translated[z].add(name) 664 | 665 | if args is not None: 666 | env = {a.arg: type for a, type in zip(node_args, args)} 667 | else: 668 | env = {a.arg: type for a, type in zip(node_args, self.type_env.top[z][name][1:-1])} 669 | 670 | if receiver: 671 | env['self'] = receiver['pseudo_type'] 672 | self.type_env, old_type_env = self.type_env.top.child_env(env), self.type_env 673 | if args is not None: 674 | self.type_env.top[z][name][1:-1] = args 675 | 676 | outer_current_class, self.current_class = self.current_class, z 677 | outer_function_name, self.function_name = self.function_name, name 678 | 679 | children = [] 680 | self.is_last = False 681 | for j, child in enumerate(node.body): 682 | if j == len(node.body) - 1: 683 | self.is_last = True 684 | child_ = self._translate_node(child) 685 | if isinstance(child_, list): 686 | children.extend(child_) 687 | else: 688 | children.append(child_) 689 | # print(args);input() 690 | self.function_name = outer_function_name 691 | self.current_class = outer_current_class 692 | 693 | self.type_env = old_type_env 694 | 695 | 696 | if z == 'functions': 697 | node_name = 'function_definition' 698 | elif name == '__init__': 699 | node_name = 'constructor' 700 | else: 701 | node_name = 'method_definition' 702 | 703 | q = { 704 | 'type': node_name, 705 | 'name': name, 706 | 'params': [{'type': 'local', 'name': node_arg.arg, 'pseudo_type': self.type_env.top[z][name][j]} for j, node_arg in enumerate(node_args)], 707 | 'pseudo_type': self.type_env.top[z][name], 708 | 'return_type': self.type_env.top[z][name][-1], 709 | 'block': children 710 | } 711 | if z != 'functions': 712 | q['this'] = {'type': 'typename', 'name': z} 713 | if name != '__init__': 714 | q['is_public'] = name[0] != '_' 715 | return q 716 | 717 | def _translate_expr(self, value, location): 718 | return self._translate_node(value) 719 | 720 | def _translate_return(self, value, location): 721 | value_node = self._translate_node(value) 722 | whiplash = self.type_env.top[self.current_class][self.function_name] 723 | if value_node is None: 724 | raise type_check_error("expected a non-void return type for %s" % self.function_name, location, self.lines[location[0]], wrong_type='Void') 725 | elif whiplash[-1] and whiplash[-1] != value_node['pseudo_type']: 726 | raise type_check_error( 727 | "expected %s return type for %s" % (serialize_type(whiplash[-1]), self.function_name), location, self.lines[location[0]], wrong_type=value_node['pseudo_type']) 728 | elif whiplash[-1] is None: 729 | whiplash[-1] = value_node['pseudo_type'] 730 | 731 | return { 732 | 'type': 'explicit_return' if not self.is_last else 'implicit_return', 733 | 'value': value_node, 734 | 'pseudo_type': value_node['pseudo_type'] 735 | } 736 | 737 | def _translate_binop(self, op, left, right, location): 738 | op = PSEUDO_OPS[type(op)] 739 | left_node, right_node = self._translate_node(left), self._translate_node(right) 740 | binop_type = TYPED_API['operators'][op](left_node['pseudo_type'], right_node['pseudo_type'])[-1] 741 | if binop_type == 'Float' or binop_type == 'Int': 742 | if op == '**': # math:pow(left, right) 743 | return { 744 | 'type': 'standard_call', 745 | 'namespace': 'math', 746 | 'args': [left_node, right_node], 747 | 'pseudo_type': binop_type, 748 | 'function': 'pow' 749 | } 750 | else: 751 | return { 752 | 'type': 'binary_op', 753 | 'op': op, 754 | 'left': left_node, 755 | 'right': right_node, 756 | 'pseudo_type': binop_type 757 | } 758 | else: 759 | if left_node['pseudo_type'] == 'String' and op == '%': 760 | if left_node['type'] != 'string': 761 | raise translation_error("pseudo expects a literal, it can't transform string interpolation to other languages if it doesn't have the original template string", 762 | location, self.lines[location[0]]) 763 | if right_node['pseudo_type'] == 'Array': 764 | count = right_node['pseudo_type'][2] 765 | elif right_node['pseudo_type'] == 'Tuple': 766 | count = len(right_node['pseudo_type']) - 1 767 | elements = right_node['elements'] 768 | else: 769 | count = 1 770 | 771 | a = left_node['value'].count('%s') + left_node['value'].count('%d') 772 | if a != count: 773 | raise type_check_error('% expected %d formatters, got %d' % (a, count), 774 | location, self.location[lines[0]]) 775 | 776 | if count > 1: 777 | if right_node['type'] in ['array', 'tuple']: 778 | elements = right_node['elements'] 779 | else: 780 | elements = [{ 781 | 'type': 'index', 782 | 'sequence': right_node, 783 | 'index': { 784 | 'value': j, 785 | 'pseudo_type': 'Int', 786 | 'type': 'Int' 787 | }, 788 | 'pseudo_type': right_node['pseudo_type'][j + 1] if right_node['pseudo_type'][0] == 'Tuple' else right_node['pseudo_type'][1] 789 | } 790 | for j 791 | in range(count)] 792 | else: 793 | elements = [right_node] 794 | 795 | y, v = 0, 0 796 | words = [] 797 | # even args of interpolation: strings 798 | # odd are placeholders 799 | for j in range(a): 800 | while left_node['value'][y:y + 2] not in ['%s', '%d']: 801 | y = left_node['value'].index('%', y) 802 | words.append({'type': 'interpolation_literal', 'value': left_node['value'][v:y], 'pseudo_type': 'String'}) 803 | v = y + 2 #ves 804 | if left_node['value'][y:v] == '%d': 805 | if elements[j]['pseudo_type'] != 'Int': 806 | raise type_check_error('%d expects an int', 807 | location, self.lines[location[0]], 808 | wrong_type=elements[j]['pseudo_type']) 809 | words.append({'type': 'interpolation_placeholder', 'value': elements[j], 'index': j, 'pseudo_type': elements[j]['pseudo_type']}) 810 | elif left_node['value'][y:y + 2] == '%s': 811 | if elements[j]['pseudo_type'] != 'String': 812 | raise type_check_error('%s expects an int', 813 | location, self.lines[location[0]], 814 | wrong_type=elements[j]['pseudo_type']) 815 | words.append({'type': 'interpolation_placeholder', 'value': elements[j], 'pseudo_type': elements[j]['pseudo_type'], 'index': j}) 816 | words.append({'type': 'interpolation_literal', 'value': left_node['value'][v:], 'pseudo_type': 'String'}) 817 | 818 | return { 819 | 'type': 'interpolation', 820 | 'args': words, 821 | 'pseudo_type': 'String' 822 | } 823 | else: 824 | return { 825 | 'type': 'standard_method_call', 826 | 'receiver': left_node, 827 | 'message': OPERATOR_API[self._general_type(left_node['pseudo_type'])][op], 828 | 'args': [right_node], 829 | 'pseudo_type': binop_type 830 | } 831 | 832 | def _translate_unaryop(self, operand, op, location): 833 | value = operand 834 | if isinstance(op, ast.USub): 835 | value_node = self._translate_node(value) 836 | if value_node['pseudo_type'] != 'Int' and value_node['pseudo_type'] != 'Float': 837 | raise type_check_error('- expects Int or Float', 838 | location, self.lines[location[0]], 839 | wrong_type=value_node['pseudo_type']) 840 | if value_node['type'] == 'int': 841 | return { 842 | 'type': 'int', 843 | 'value': -value_node['value'], 844 | 'pseudo_type': 'Int' 845 | } 846 | else: 847 | return { 848 | 'type': 'unary_op', 849 | 'op': '-', 850 | 'value': value_node, 851 | 'pseudo_type': value_node['pseudo_type'] 852 | } 853 | elif isinstance(op, ast.Not): 854 | value_node = self._testable(self._translate_node(value)) 855 | if value_node['type'] == 'standard_method_call' and value_node['message'] == 'present?': 856 | value_node['message'] = 'empty?' 857 | return value_node 858 | else: 859 | return { 860 | 'type': 'unary_op', 861 | 'op': 'not', 862 | 'value': value_node, 863 | 'pseudo_type': 'Boolean' 864 | } 865 | else: 866 | raise translation_error('no support for %s as an unary op' % type(op).__name__, 867 | location, self.lines[location[0]], 868 | suggestions='not and - are supported') 869 | 870 | 871 | def _translate_boolop(self, op, values, location): 872 | op = PSEUDO_OPS[type(op)] 873 | right_node = self._testable(self._translate_node(values[0])) 874 | result = right_node 875 | for r in values[1:]: 876 | left_node, right_node = right_node, self._testable(self._translate_node(r)) 877 | result = { 878 | 'type': 'binary_op', 879 | 'op': op, 880 | 'left': result, 881 | 'right': right_node, 882 | 'pseudo_type': left_node['pseudo_type'] 883 | } 884 | return result 885 | 886 | def _translate_in(self, element, sequence, location): 887 | sequence_node = self._translate_node(sequence) 888 | element_node = self._translate_node(element) 889 | if not ( 890 | sequence_node['pseudo_type'] == 'String' and element_node['pseudo_type'] == 'String' or\ 891 | isinstance(sequence_node['pseudo_type'], list) and sequence_node['pseudo_type'][0] != 'Tuple' and sequence_node['pseudo_type'][1] == element_node['pseudo_type']): 892 | raise type_check_error('expected the left side of in to has the element type of the sequence, in supported for string and sequences which are not tuples', 893 | location, self.lines[location[0]], 894 | wrong_type=element_node['pseudo_type']) 895 | return { 896 | 'type': 'standard_method_call', 897 | 'receiver': sequence_node, 898 | 'message': 'contains?', 899 | 'args': [element_node], 900 | 'pseudo_type': 'Boolean' 901 | } 902 | 903 | def _translate_compare(self, left, ops, comparators, location): 904 | if isinstance(ops[0], ast.In) or isinstance(ops[0], ast.NotIn): 905 | if len(comparators) != 1: 906 | raise translation_error('only [not] in supported', 907 | location, self.lines[location[0]], 908 | suggestion='2 in [2] in [[2]] is cute, but it\'s not supported') 909 | else: 910 | in_node = self._translate_in(left, comparators[0], location) 911 | if isinstance(ops[0], ast.In): 912 | return in_node 913 | else: 914 | return {'type': 'unary_op', 'op': 'not', 'value': in_node, 'pseudo_type': 'Boolean'} 915 | 916 | op = PSEUDO_OPS[type(ops[0])] 917 | right_node = self._translate_node(comparators[0]) 918 | left_node = self._translate_node(left) 919 | 920 | self._confirm_comparable(op, left_node['pseudo_type'], right_node['pseudo_type'], location) 921 | 922 | result = { 923 | 'type': 'comparison', 924 | 'op': op, 925 | 'left': left_node, 926 | 'right': right_node, 927 | 'pseudo_type': 'Boolean' 928 | } 929 | if len(comparators) == 1: 930 | return result 931 | else: 932 | for r in comparators[1:]: 933 | left_node, right_node = right_node, self._translate_node(r) 934 | self._confirm_comparable(op, left_node['pseudo_type'], right_node['pseudo_type'], location) 935 | result = { 936 | 'type': 'binary_op', 937 | 'op': 'and', 938 | 'left': result, 939 | 'right': { 940 | 'type': 'comparison', 941 | 'op': op, 942 | 'left': left_node, 943 | 'right': right_node, 944 | 'pseudo_type': 'Boolean' 945 | }, 946 | 'pseudo_type': 'Boolean' 947 | } 948 | return result 949 | 950 | def _confirm_index(self, index_type, expected, location, window): 951 | if index_type != 'Int': 952 | raise type_check_error( 953 | 'expected Int for %s' % window, 954 | location, self.lines[location[0]], 955 | wrong_type=index_type) 956 | 957 | def _confirm_comparable(self, o, l, r, location): 958 | if o == '==' and l != r or o != '==' and (isinstance(l, list) or isinstance(r, list) or\ 959 | l != r or l not in COMPARABLE_TYPES): 960 | raise type_check_error( 961 | '%s not comparable with %s' % (serialize_type(l), serialize_type(r)), 962 | location, self.lines[location[0]], 963 | suggestions='comparable types in pseudo-python: %s' % ' '.join(COMPARABLE_TYPES)) 964 | 965 | def _translate_attribute(self, value, attr, ctx, location, in_call=False): 966 | value_node = self._translate_node(value) 967 | if not isinstance(value_node['pseudo_type'], str) and not in_call: 968 | raise type_check_error( 969 | "you can't access attr of %s, only of normal objects or modules" % serialize_type(value_node['pseudo_type']), 970 | (value.lineno, value.col_offset), self.lines[value.lineno], 971 | suggestions='[2].s is invalid', 972 | right='h = H()\nh.y', 973 | wrong='h = (2, H())\nh.hm') 974 | 975 | if value_node['pseudo_type'] == 'library': 976 | if value_node['name'] == 'sys' and attr == 'argv': 977 | return { 978 | 'type': 'standard_call', 979 | 'namespace': 'system', 980 | 'function': 'args', 981 | 'args': [], 982 | 'pseudo_type': ['List', 'String'], 983 | 'special': None 984 | } 985 | else: 986 | return { 987 | 'type': 'library_function', 988 | 'library': value_node['name'], 989 | 'function': attr, 990 | 'pseudo_type': 'library' 991 | } 992 | else: 993 | value_general_type = self._general_type(value_node['pseudo_type']) 994 | attr_type = self._attr_index.get(value_general_type, {}).get(attr) 995 | 996 | if attr_type is None: 997 | m = METHOD_API.get(value_general_type, {}).get(attr) 998 | if m: 999 | attr_type = m #'builtin_method[%s]' % serialize_type(m) 1000 | else: 1001 | m = self.type_env.top.values.get(value_general_type, {}).get(attr) 1002 | if m: 1003 | attr_type = m #'user_method[%s]' % serialize_type(m) 1004 | 1005 | if not m: 1006 | value_type = value_node['pseudo_type'] 1007 | value_general_type = self._general_type(value_type) 1008 | show_type = serialize_type(TYPED_API.get('_generic_%s' % value_general_type, value_type)) 1009 | raise translation_error( 1010 | "pseudo-python can\'t infer the type of %s#%s" % (serialize_type(value_type), attr), 1011 | location, self.lines[location[0]], 1012 | suggestions='pseudo-python knows about those %s methods:\n%s' % ( 1013 | show_type, 1014 | prepare_table(self.type_env.top[value_general_type], ORIGINAL_METHODS.get(value_general_type)))) 1015 | 1016 | else: 1017 | attr_type = attr_type[0]['pseudo_type'] 1018 | 1019 | if value_node['type'] == 'this': 1020 | result = { 1021 | 'type': 'instance_variable', 1022 | 'name': attr, 1023 | 'pseudo_type': attr_type 1024 | } 1025 | if result in self._tuple_assigned: 1026 | if not any(a[0] == '_old_self_%s' % attr for a in self._tuple_used): 1027 | self._tuple_used.append(('_old_self_%s' % attr, result)) 1028 | 1029 | 1030 | result = {'type': 'local', 'name': '_old_self_%s' % attr, 'pseudo_type': attr_type} 1031 | return result 1032 | else: 1033 | result = { 1034 | 'type': 'attr', 1035 | 'object': value_node, 1036 | 'attr': attr, 1037 | 'pseudo_type': attr_type 1038 | } 1039 | if result in self._tuple_assigned: 1040 | if not any(a[0] == '_old_%s' % attr for a in self._tuple_used): 1041 | self._tuple_used.append(('_old_%s' % attr, result)) 1042 | result = {'type': 'local', 'name': '_old_%s' % attr, 'pseudo_type': attr_type} 1043 | return result 1044 | 1045 | def _translate_assign(self, targets, value, location): 1046 | if isinstance(value, ast.AST): 1047 | value_node = self._translate_node(value) 1048 | else: 1049 | value_node = value 1050 | if isinstance(targets[0], ast.Tuple): 1051 | if not isinstance(value, ast.Tuple): 1052 | raise translation_error( 1053 | 'multiple return values assignment will be supported in v0.4', 1054 | location, self.lines[location[0]]) 1055 | 1056 | if len(targets[0].elts) != len(value.elts): 1057 | raise translation_error( 1058 | 'expected %d number of values on right' % len(targets[0].elts), 1059 | location, self.lines[location[0]]) 1060 | 1061 | rights = [] 1062 | used = [] 1063 | u = 0 1064 | for t, child in zip(targets[0].elts, value.elts): 1065 | child_node = self._translate_node(child) 1066 | x = self._translate_node(t) 1067 | for a in self._tuple_used[u:]: 1068 | used.append({ 1069 | 'type': 'assignment', 1070 | 'target': {'type': 'local', 'name': a[0], 'pseudo_type': a[1]['pseudo_type']}, 1071 | 'value': a[1], 1072 | 'pseudo_type': 'Void' 1073 | }) 1074 | self.type_env[a[0]] = a[1]['pseudo_type'] 1075 | u = len(self._tuple_used) 1076 | rights.append( 1077 | self._translate_assign([t], child_node, location)) 1078 | self._tuple_assigned.append(rights[-1]['target']) 1079 | 1080 | self._tuple_assigned = [] 1081 | self._tuple_used = [] 1082 | return used + rights 1083 | 1084 | elif isinstance(targets[0], ast.Name): 1085 | name = targets[0].id 1086 | e = self.type_env[name] 1087 | if e: 1088 | # raise ValueError(ast.dump(targets[0])) 1089 | # 1090 | a = self._compatible_types(e, value_node['pseudo_type'], "can't change the type of variable %s in %s " % (name, self.function_name)) 1091 | else: 1092 | a = value_node['pseudo_type'] 1093 | self.type_env[name] = a 1094 | return { 1095 | 'type': 'assignment', 1096 | 'target': { 1097 | 'type': 'local', 1098 | 'name': name, 1099 | 'pseudo_type': value_node['pseudo_type'] 1100 | }, 1101 | 'value': value_node, 1102 | 'pseudo_type': 'Void' 1103 | } 1104 | elif isinstance(targets[0], ast.Attribute): 1105 | z = self._translate_node(targets[0].value) 1106 | if z['pseudo_type'] == 'library': 1107 | raise PseudoPythonTypeCheckError("pseudo-python can't redefine a module function %s" % z['name'] + ':' + targets[0].attr) 1108 | 1109 | is_public = not isinstance(targets[0].value, ast.Name) or targets[0].value.id != 'self' 1110 | 1111 | if targets[0].attr in self._attr_index[z['pseudo_type']]: 1112 | a = self._compatible_types(self._attr_index[z['pseudo_type']][targets[0].attr][0]['pseudo_type'], 1113 | value_node['pseudo_type'], "can't change attr type of %s" % serialize_type(z['pseudo_type']) + '.' + targets[0].attr) 1114 | self._attr_index[z['pseudo_type']][targets[0].attr][0]['pseudo_type'] = a 1115 | if is_public: 1116 | self._attr_index[z['pseudo_type']][targets[0].attr][0]['is_public'] = True 1117 | else: 1118 | a = value_node['pseudo_type'] 1119 | self._attr_index[z['pseudo_type']][targets[0].attr] = [{ 1120 | 'type': 'class_attr', 1121 | 'name': targets[0].attr, 1122 | 'pseudo_type': a, 1123 | 'is_public': is_public, 1124 | }, False] 1125 | 1126 | self._attrs[z['pseudo_type']].append(targets[0].attr) 1127 | 1128 | if z['type'] == 'this': 1129 | return { 1130 | 'type': 'assignment', 1131 | 'target': { 1132 | 'type': 'instance_variable', 1133 | 'name': targets[0].attr, 1134 | 'pseudo_type': value_node['pseudo_type'] 1135 | }, 1136 | 'value': value_node, 1137 | 'pseudo_type': 'Void' 1138 | } 1139 | return { 1140 | 'type': 'assignment', 1141 | 'target': { 1142 | 'type': 'attr', 1143 | 'object': z, 1144 | 'attr': targets[0].attr, 1145 | 'pseudo_type': a 1146 | }, 1147 | 'value': value_node, 1148 | 'pseudo_type': 'Void' 1149 | } 1150 | elif isinstance(targets[0], ast.Subscript): 1151 | z = self._translate_node(targets[0]) 1152 | if z['type'] == 'index': 1153 | return { 1154 | 'type': 'assignment', 1155 | 'target': z, 1156 | 'value': value_node, 1157 | 'pseudo_type': 'Void' 1158 | } 1159 | elif z['type'] == 'standard_method_call': # slice 1160 | if z['pseudo_type'] != value_node['pseudo_type']: 1161 | raise type_check_error( 1162 | 'expected %s' % serialize_type(z['pseudo_type']), 1163 | getattr(value, 'location', location), self.lines[location[0]], 1164 | wrong_type=value_node['pseudo_type']) 1165 | 1166 | z['message'] = 'set_%s' % z['message'] 1167 | z['args'].append(value_node) 1168 | z['pseudo_type'] = 'Void' 1169 | return z 1170 | 1171 | def _translate_augassign(self, target, op, value, location): 1172 | return self._translate_assign([target], ast.BinOp(target, op, value), location) 1173 | 1174 | def _translate_if(self, test, orelse, body, location, base=True): 1175 | test_node = self._testable(self._translate_node(test)) 1176 | block = self._translate_node(body) 1177 | #block [self._translate_node(child) for child in body] 1178 | if orelse and len(orelse) == 1 and isinstance(orelse[0], ast.If): 1179 | otherwise = self._translate_if(orelse[0].test, orelse[0].orelse, orelse[0].body, location, False) 1180 | elif orelse: 1181 | otherwise = { 1182 | 'type': 'else_statement', 1183 | 'block': self._translate_node(orelse), 1184 | #block [self._translate_node(node) for node in orelse], 1185 | 'pseudo_type': 'Void' 1186 | } 1187 | else: 1188 | otherwise = None 1189 | 1190 | return { 1191 | 'type': 'if_statement' if base else 'elseif_statement', 1192 | 'test': test_node, 1193 | 'block': block, 1194 | 'pseudo_type': 'Void', 1195 | 'otherwise': otherwise 1196 | } 1197 | 1198 | def _translate_while(self, body, test, orelse, location): 1199 | self.assert_translatable('while', orelse=([], orelse)) 1200 | test_node = self._testable(self._translate_node(test)) 1201 | return { 1202 | 'type': 'while_statement', 1203 | 'test': test_node, 1204 | 'block': self._translate_node(body), 1205 | #block [self._translate_node(node) for node in body], 1206 | 'pseudo_type': 'Void' 1207 | } 1208 | 1209 | def _testable(self, test_node): 1210 | t = self._general_type(test_node['pseudo_type']) 1211 | if t != TESTABLE_TYPE: 1212 | if t in TYPES_WITH_LENGTH: 1213 | return { 1214 | 'type': 'standard_method_call', 1215 | 'receiver': test_node, 1216 | 'message': 'present?', 1217 | 'args': [], 1218 | 'pseudo_type': 'Boolean' 1219 | } 1220 | elif t in NUMBER_TYPES: 1221 | return { 1222 | 'type': 'comparison', 1223 | 'pseudo_type': 'Boolean', 1224 | 'op': '>', 1225 | 'left': test_node, 1226 | 'right': {'type': 'int', 'pseudo_type': 'Int', 'value': 0} 1227 | } 1228 | elif t == 'RegexpMatch': 1229 | return { 1230 | 'type': 'standard_method_call', 1231 | 'pseudo_type': 'Boolean', 1232 | 'receiver': test_node, 1233 | 'message': 'has_match', 1234 | 'args': [] 1235 | } 1236 | elif t == 'Void': 1237 | return { 1238 | 'type': 'not_null_check', 1239 | 'value': test_node, 1240 | 'pseudo_type': 'Boolean' 1241 | } 1242 | else: 1243 | raise PseudoPythonTypeCheckError('pseudo-python expects a bool or RegexpMatch test not %s' % serialize_type(test_node['pseudo_type'])) 1244 | else: 1245 | return test_node 1246 | 1247 | def _translate_slice(self, receiver, upper, lower, step, location): 1248 | self.assert_translatable('slice', step=(step, None)) 1249 | # for some reason python ast has location info for most elements 1250 | # but not for Num. 1251 | # that's possibly genius, inconsisten consistent fucking genius, 1252 | # thank you python ast (almost wishing I've used redbaron) 1253 | base = 'slice' if receiver['pseudo_type'] != 'String' else 'substr' 1254 | if upper: 1255 | upper_node = self._translate_node(upper) 1256 | self._confirm_index(upper_node['pseudo_type'], 'Int', getattr(upper, 'location', location), 'slice index') 1257 | if lower: 1258 | lower_node = self._translate_node(lower) 1259 | self._confirm_index(lower_node['pseudo_type'], 'Int', getattr(lower, 'location', location), 'slice index') 1260 | if upper and lower: 1261 | name = base 1262 | values = [lower_node, upper_node] 1263 | elif upper: 1264 | name = '%s_to' % base 1265 | values = [upper_node] 1266 | elif lower: 1267 | name = '%s_from' % base 1268 | values = [lower_node] 1269 | else: 1270 | name = 'slice_' 1271 | values = [] 1272 | return { 1273 | 'type': 'standard_method_call', 1274 | 'receiver': receiver, 1275 | 'message': name, 1276 | 'args': values, 1277 | 'pseudo_type': receiver['pseudo_type'] 1278 | } 1279 | 1280 | def _translate_list(self, elts, ctx, location): 1281 | if not elts: 1282 | return {'type': 'list', 'elements': [], 'pseudo_type': ['List', None]} 1283 | 1284 | element_nodes, element_type = self._translate_elements(elts, 'list') 1285 | 1286 | return { 1287 | 'type': 'list', 1288 | 'pseudo_type': ['List', element_type], 1289 | 'elements': element_nodes 1290 | } 1291 | 1292 | def _translate_dict(self, keys, values, location): 1293 | if not keys: 1294 | return {'type': 'dictionary', 'pairs': [], 'pseudo_type': ['Dictionary', None, None]} 1295 | 1296 | pairs = [{'type': 'pair', 'key': self._translate_node(keys[0]), 'value': self._translate_node(values[0])}] 1297 | key_type, value_type = pairs[0]['key']['pseudo_type'], pairs[0]['value']['pseudo_type'] 1298 | for a, b in zip(keys[1:], values[1:]): 1299 | pairs.append({'type': 'pair', 'key': self._translate_node(a), 'value': self._translate_node(b)}) 1300 | key_type, value_type = self._compatible_types(key_type, pairs[-1]['key']['pseudo_type'], "can't use different types for keys of a dictionary"),\ 1301 | self._compatible_types(value_type, pairs[-1]['value']['pseudo_type'], "can't use different types for values of a dictionary") 1302 | 1303 | return { 1304 | 'type': 'dictionary', 1305 | 'pseudo_type': ['Dictionary', key_type, value_type], 1306 | 'pairs': pairs 1307 | } 1308 | 1309 | def _translate_set(self, elts, location): 1310 | element_nodes, element_type = self._translate_elements(elts, 'set') 1311 | 1312 | return { 1313 | 'type': 'set', 1314 | 'pseudo_type': ['Set', element_type], 1315 | 'elements': element_nodes 1316 | } 1317 | 1318 | def _translate_tuple(self, elts, ctx, location): 1319 | element_nodes, accidentaly_homogeneous, element_type = self._translate_elements(elts, 'tuple', homogeneous=False) 1320 | return { 1321 | 'type': 'array' if accidentaly_homogeneous else 'tuple', 1322 | 'pseudo_type': ['Array', element_type, len(elts)] if accidentaly_homogeneous else ['Tuple'] + element_type, 1323 | 'elements': element_nodes 1324 | } 1325 | 1326 | def _translate_elements(self, elements, kind, homogeneous=True): 1327 | element_nodes = self._translate_node([elements[0]]) 1328 | #block [self._translate_node(elements[0])] 1329 | element_type = element_nodes[0]['pseudo_type'] 1330 | if not homogeneous: 1331 | element_types = [element_type] 1332 | accidentaly_homogeneous = True 1333 | for j, element in enumerate(elements[1:]): 1334 | element_nodes.append(self._translate_node(element)) 1335 | # print(self._hierarchy) 1336 | if homogeneous: 1337 | element_type = self._compatible_types(element_nodes[-1]['pseudo_type'], element_type, "can't use different types in a %s" % kind) 1338 | else: 1339 | element_types.append(element_nodes[-1]['pseudo_type']) 1340 | if accidentaly_homogeneous: 1341 | element_type = self._compatible_types(element_type, element_nodes[-1]['pseudo_type'], '', silent=True) 1342 | accidentaly_homogeneous = element_type is not False 1343 | 1344 | return (element_nodes, element_type) if homogeneous else (element_nodes, accidentaly_homogeneous, element_type if accidentaly_homogeneous else element_types) 1345 | 1346 | def _translate_subscript(self, value, slice, ctx, location): 1347 | value_node = self._translate_node(value) 1348 | value_general_type = self._general_type(value_node['pseudo_type']) 1349 | if value_general_type not in INDEXABLE_TYPES: 1350 | raise type_check_error('pseudo-python can use [] only on String, List, Dictionary or Tuple', 1351 | location, self.lines[location[0]], 1352 | wrong_type=value_node['pseudo_type']) 1353 | 1354 | if isinstance(slice, ast.Index): 1355 | z = self._translate_node(slice.value) 1356 | if value_general_type in ['String', 'List', 'Tuple'] and z['pseudo_type'] != 'Int': 1357 | raise PseudoPythonTypeCheckError('a non int index for %s %s' % (value_general_type, z['pseudo_type'])) 1358 | 1359 | if value_general_type == 'Dictionary' and z['pseudo_type'] != value_node['pseudo_type'][1]: 1360 | raise PseudoPythonTypeCheckError('a non %s index for %s %s' % (value_node['pseudo_type'][1], value_general_type, z['pseudo_type'])) 1361 | 1362 | if value_general_type == 'String': 1363 | pseudo_type = 'String' 1364 | elif value_general_type == 'List' or value_general_type == 'Array': 1365 | pseudo_type = value_node['pseudo_type'][1] 1366 | elif value_general_type == 'Tuple': 1367 | if z['type'] != 'int': 1368 | raise PseudoPythonTypeCheckError('pseudo-python can support only literal int indices of a heterogenous tuple ' + 1369 | 'because otherwise the index type is not predictable %s %s ' % (serialize_type(value_node['pseudo_type']), z['type'])) 1370 | 1371 | elif z['value'] > len(value_node['pseudo_type']) - 2: 1372 | raise PseudoPythonTypeCheckError('%s has only %d elements' % serialize_type(value_node['pseudo_type']), len(value_node['pseudo_type'])) 1373 | 1374 | pseudo_type = value_node['pseudo_type'][z['value'] + 1] 1375 | 1376 | else: 1377 | pseudo_type = value_node['pseudo_type'][2] 1378 | 1379 | if 'special' in value_node: # sys.argv[index] 1380 | if z['pseudo_type'] != 'Int': 1381 | raise type_check_error('pseudo-python supports only int indices for sys.argv', 1382 | location, self.lines[location[0]], 1383 | wrong_type=z['pseudo_type']) 1384 | return { 1385 | 'type': 'standard_call', 1386 | 'namespace': 'system', 1387 | 'function': 'index', 1388 | 'args': [z], 1389 | 'pseudo_type': 'String' 1390 | } 1391 | result = { 1392 | 'type': 'index', 1393 | 'sequence': value_node, 1394 | 'index': z, 1395 | 'pseudo_type': pseudo_type 1396 | } 1397 | if result in self._tuple_assigned: 1398 | j = z.get('value', z.get('name', z.get('attr', '_x'))) 1399 | k = value_node.get('value', value_node.get('name', value_node.get('attr', '_y'))) 1400 | # i kno c: 1401 | if not any(a[0] == '_old_%s_%s' % (j, k) for a in self._tuple_used): 1402 | self._tuple_used.append(('_old_%s_%s' % (j, k), result)) 1403 | result = {'type': 'local', 'name': '_old_%s_%s' % (j, k), 'pseudo_type': pseudo_type} 1404 | return result 1405 | else: 1406 | return self._translate_slice(receiver=value_node, upper=slice.upper, step=slice.step, lower=slice.lower, location=location) 1407 | 1408 | def _translate_str(self, s, location): 1409 | return {'type': 'string', 'value': s.replace('\n', '\\n'), 'pseudo_type': 'String'} 1410 | 1411 | def _translate_try(self, orelse, finalbody, body, handlers, location): 1412 | self.assert_translatable('try', else_=([], orelse), finally_=([], finalbody)) 1413 | 1414 | return { 1415 | 'type': 'try_statement', 1416 | 'pseudo_type': 'Void', 1417 | 'block': self._translate_node(body), 1418 | #block [self._translate_node(node) for node in body], 1419 | 'handlers': self._translate_node(handlers) 1420 | #block [self._translate_handler(handler) for handler in handlers] 1421 | } 1422 | 1423 | def _translate_raise(self, exc, cause, location): 1424 | self.assert_translatable('raise', cause=(None, cause)) 1425 | if not isinstance(exc.func, ast.Name) or exc.func.id not in self._exceptions: 1426 | raise PseudoPythonTypeCheckError('pseudo-python can raise only Exception or custom exceptions: %s ' % ast.dump(exc.func)) 1427 | 1428 | return { 1429 | 'type': 'throw_statement', 1430 | 'pseudo_type': 'Void', 1431 | 'exception': exc.func.id, 1432 | 'value': self._translate_node(exc.args[0]) 1433 | } 1434 | 1435 | 1436 | def _translate_with(self, items, body, location): 1437 | if len(items) != 1 or not isinstance(items[0].context_expr, ast.Call) or not isinstance(items[0].context_expr.func, ast.Name) or items[0].context_expr.func.id != 'open': 1438 | raise PseudoPythonTypeCheckError('pseudo-python supports with only for opening files') 1439 | elif not isinstance(items[0].optional_vars, ast.Name): 1440 | raise PseudoPythonTypeCheckError('pseudo-python needs exactly one name var for with statements' ) 1441 | optional_vars = items[0].optional_vars 1442 | items = [items[0].context_expr] 1443 | 1444 | if len(body) == 1 and len(items[0].args) > 1: 1445 | arg_node = self._translate_node(items[0].args[0]) 1446 | if arg_node['pseudo_type'] == 'String' and isinstance(body[0], ast.Assign) and len(body[0].targets) == 1 and\ 1447 | isinstance(items[0].args[1], ast.Str) and 'r' in items[0].args[1].s and\ 1448 | isinstance(body[0].value, ast.Call) and isinstance(body[0].value.func, ast.Attribute) and isinstance(body[0].value.func.value, ast.Name) and body[0].value.func.value.id == optional_vars.id and body[0].value.func.attr == 'read' and not body[0].value.args: 1449 | return self._translate_assign(targets=body[0].targets, value= { 1450 | 'type': 'standard_call', 1451 | 'namespace': 'io', 1452 | 'function': 'read_file', 1453 | 'args': [arg_node], 1454 | 'pseudo_type': 'String' 1455 | }, location=(body[0].lineno, body[0].col_offset)) 1456 | elif arg_node['pseudo_type'] == 'String' and isinstance(body[0], ast.Call) and isinstance(body[0].func, ast.Attribute) and isinstance(body[0].func.value, ast.Name) and body[0].func.value.id == optional_vars.id and body[0].func.attr == 'write': 1457 | z == self._translate_node(body[0].func.args[0], in_call=True) 1458 | return { 1459 | 'type': 'standard_call', 1460 | 'namespace': 'io', 1461 | 'function': 'write_file', 1462 | 'args': [arg_node, z], 1463 | 'pseudo_type': 'Void' 1464 | } 1465 | 1466 | raise PseudoPythonTypeCheckError('the supported format for with requires exactly one line in body which is [ =] .read/write(..)') 1467 | 1468 | 1469 | def _translate_handler(self, handler): 1470 | if not isinstance(handler.type, ast.Name) or handler.type.id not in self._exceptions: 1471 | raise PseudoPythonTypeCheckError('%s' % str(ast.dump(handler.type))) 1472 | h = self.type_env[handler.name] 1473 | if h and h != 'Exception': 1474 | raise PseudoPythonTypeCheckError("can't change the type of exception %s to %s" % (handler.name, serialize_type(h))) 1475 | self.type_env[handler.name] = 'Exception' 1476 | return { 1477 | 'type': 'exception_handler', 1478 | 'pseudo_type': 'Void', 1479 | 'exception': handler.type.id, 1480 | 'is_builtin': handler.type.id == 'Exception', 1481 | 'instance': handler.name, 1482 | 'block': self._translate_node(handler.body) 1483 | #block [self._translate_node(z) for z in handler.body] 1484 | } 1485 | 1486 | def _translate_nameconstant(self, value, location): 1487 | if value == True or value == False: 1488 | return {'type': 'boolean', 'value': str(value).lower(), 'pseudo_type': 'Boolean'} 1489 | elif value is None: 1490 | return {'type': 'null', 'pseudo_type': 'Void'} 1491 | 1492 | def _translate_functional_lambda(self, l, arg_type): 1493 | 1494 | params = [{'type': 'local', 'name': l.args.args[0].arg, 'pseudo_type': arg_type}] 1495 | self.type_env = self.type_env.child_env({l.args.args[0].arg: arg_type}) 1496 | old_f, self.function_name = self.function_name, 'lambda' 1497 | node = self._translate_node(l.body) 1498 | nodes = [{ 1499 | 'type': 'implicit_return', 1500 | 'pseudo_type': node['pseudo_type'], 1501 | 'value': node 1502 | }] 1503 | self.function_name = old_f 1504 | self.type_env = self.type_env.parent 1505 | return { 1506 | 'type': 'anonymous_function', 1507 | 'params': params, 1508 | 'block': nodes, 1509 | 'pseudo_type': ['Function', arg_type, node['pseudo_type']], 1510 | 'return_type': node['pseudo_type'] 1511 | } 1512 | 1513 | def _translate_lambda(self, body, args, location): 1514 | raise 1515 | 1516 | def _translate_listcomp(self, generators, elt, location): 1517 | if isinstance(generators[0].target, ast.Name): 1518 | sketchup, env = self._translate_iter(generators[0].target, generators[0].iter) 1519 | 1520 | self.type_env = self.type_env.child_env(env) 1521 | 1522 | old_function_name, self.function_name = self.function_name, 'list comprehension' 1523 | 1524 | sketchup['type'] = 'standard_iterable_call' + sketchup['type'] 1525 | 1526 | 1527 | if not generators[0].ifs: 1528 | if 'index' not in sketchup and self._general_type(sketchup['sequences']['type']) == 'for_sequence': 1529 | elt = self._translate_node(elt) 1530 | self.function_name = old_function_name 1531 | return { 1532 | 'type': 'standard_method_call', 1533 | 'receiver': sketchup['sequences']['sequence'], 1534 | 'message': 'map', 1535 | 'args': [{ 1536 | 'type': 'anonymous_function', 1537 | 'params': [sketchup['iterators']['iterator']], 1538 | 'pseudo_type': ['Function', sketchup['iterators']['iterator']['pseudo_type'], 1539 | elt['pseudo_type']], 1540 | 'return_type': elt['pseudo_type'], 1541 | 'block': [{ 1542 | 'type': 'implicit_return', 1543 | 'value': elt, 1544 | 'pseudo_type': elt['pseudo_type'] 1545 | }] 1546 | }], 1547 | 'pseudo_type': ['List', elt['pseudo_type']] 1548 | } 1549 | 1550 | else: 1551 | sketchup['function'] = 'map' 1552 | else: 1553 | test_node = self._testable(self._translate_node(generators[0].ifs[0])) 1554 | sketchup['function'] = 'filter_map' 1555 | sketchup['test'] = [test_node] 1556 | 1557 | elt_node = self._translate_node(elt) 1558 | 1559 | self.function_name = old_function_name 1560 | sketchup['block'] = [elt_node] 1561 | sketchup['pseudo_type'] = ['List', elt_node['pseudo_type']] 1562 | return sketchup 1563 | 1564 | def _translate_generatorexp(self, generators, elt, x, location): 1565 | if len(generators) != 1 or generators[0].ifs or not isinstance(generators[0].target, ast.Name): 1566 | raise translation_error('support for more complicated generator expressions in v0.4', 1567 | location, self.lines[location[0]]) 1568 | 1569 | iter_node = self._translate_node(generators[0].iter) 1570 | if not isinstance(iter_node['pseudo_type'], list) or iter_node['pseudo_type'][0] != 'List': 1571 | raise type_check_error('generator expression expected a List sequence, not %s' % serialize_type(iter_node['pseudo_type']), 1572 | location, self.lines[location[0]]) 1573 | elt_type = iter_node['pseudo_type'][1] 1574 | self.type_env = self.type_env.child_env() 1575 | self.type_env[generators[0].target.id] = elt_type 1576 | old_function, self.function_name = self.function_name, 'generator expr' 1577 | block = self._translate_node(elt) 1578 | self.function_name = old_function 1579 | self.type_env = self.type_env.parent 1580 | 1581 | # x can be 'any' 'all' or 'sum' 1582 | if x == 'any' or x == 'all': 1583 | return { 1584 | 'type': 'standard_method_call', 1585 | 'receiver': iter_node, 1586 | 'message': '%s?' % x, 1587 | 'args': [{ 1588 | 'type': 'anonymous_function', 1589 | 'params': [{'type': 'local', 'name': generators[0].target.id, 'pseudo_type': elt_type}], 1590 | 'block': [Node('implicit_return', value=self._testable(block), pseudo_type='Boolean')], 1591 | 'pseudo_type': ['Function', elt_type, 'Boolean'], 1592 | 'return_type': 'Boolean' 1593 | }], 1594 | 'pseudo_type': 'Boolean' 1595 | } 1596 | else: # sum 1597 | if block['pseudo_type'] != 'Int' and block['pseudo_type'] != 'Float': 1598 | raise type_check_error('sum expected a generator expression producing Int / Float', 1599 | location, self.lines[location[0]], 1600 | wrong_type=block['pseudo_type']) 1601 | 1602 | initial = 0.0 if block['pseudo_type'] == 'Float' else 0 1603 | return { 1604 | 'type': 'standard_method_call', 1605 | 'receiver': iter_node, 1606 | 'message': 'reduce', 1607 | 'args': [{ 1608 | 'type': 'anonymous_function', 1609 | 'params': [{'type': 'local', 'name': 'memo', 'pseudo_type': block['pseudo_type']}, {'type': 'local', 'name': generators[0].target.id, 'pseudo_type': elt_type}], 1610 | 'block': [{ 1611 | 'type': 'implicit_return', 1612 | 'value': { 1613 | 'type': 'binary_op', 1614 | 'op': '+', 1615 | 'left': {'type': 'local', 'name': 'memo', 'pseudo_type': block['pseudo_type']}, 1616 | 'right': block, 1617 | 'pseudo_type': block['pseudo_type'] 1618 | }, 1619 | 'pseudo_type': block['pseudo_type'] 1620 | }], 1621 | 'return_type': block['pseudo_type'], 1622 | 'pseudo_type': ['Function', block['pseudo_type'], elt_type, block['pseudo_type']], 1623 | }, { 1624 | 'type': block['pseudo_type'].lower(), 1625 | 'value': initial, 1626 | 'pseudo_type': block['pseudo_type'] 1627 | }], 1628 | 'pseudo_type': block['pseudo_type'] 1629 | } 1630 | 1631 | def _translate_iter(self,target, k): 1632 | # fix short names when not 5 am 1633 | if isinstance(k, ast.Call) and isinstance(k.func, ast.Name): 1634 | if k.func.id == 'enumerate': 1635 | if len(k.args) != 1 or not isinstance(target, ast.Tuple) or len(target.elts) != 2: 1636 | raise PseudoPythonTypeCheckError('enumerate expected one arg not %d and two indices' % len(k.args)) 1637 | return self._translate_enumerate(target.elts, k.args[0]) 1638 | elif k.func.id == 'range': 1639 | if isinstance(target, ast.Name): 1640 | return self._translate_range([target], k.args) 1641 | elif not isinstance(target, (ast.Name, ast.Tuple)) or isinstance(target, ast.Tuple) and len(target.elts) != 2: 1642 | raise PseudoPythonTypeCheckError('range expected two indices') 1643 | elif not k.args or len(k.args) > 3: 1644 | raise PseudoPythonTypeCheckError('range expected 1 to 3 args not %d' % len(k.args)) 1645 | return self._translate_range(target.elts, k.args) 1646 | elif k.func.id == 'zip': 1647 | if len(k.args) < 2 or not isinstance(target, ast.Tuple) or len(k.args) != len(target.elts): 1648 | raise PseudoPythonTypeCheckError('zip expected 2 or more args and the same number of indices not %d' % len(k.args)) 1649 | return self._translate_zip(target.elts, k.args) 1650 | 1651 | sequence_node = self._translate_node(k) 1652 | self._confirm_iterable(sequence_node['pseudo_type']) 1653 | 1654 | if isinstance(target, ast.Tuple): 1655 | raise PseudoPythonNotTranslatableError("pseudo doesn't support tuples yet") 1656 | 1657 | elif not isinstance(target, ast.Name): 1658 | raise PseudoPythonNotTranslatableError("pseudo doesn't support %s as an iterator" % sequence_node['type']) 1659 | 1660 | target_pseudo_type = self._element_type(sequence_node['pseudo_type']) 1661 | return { 1662 | 'type': '', 1663 | 'sequences': {'type': 'for_sequence', 'sequence': sequence_node}, 1664 | 'iterators': { 1665 | 'type': 'for_iterator', 1666 | 'iterator': { 1667 | 'type': 'local', 1668 | 'pseudo_type': target_pseudo_type, 1669 | 'name': target.id 1670 | } 1671 | } 1672 | }, {target.id: target_pseudo_type} 1673 | 1674 | 1675 | def _translate_enumerate(self, targets, sequence): 1676 | sequence_node = self._translate_node(sequence) 1677 | self._confirm_iterable(sequence_node['pseudo_type']) 1678 | 1679 | if not isinstance(targets[0], ast.Name) or not isinstance(targets[1], ast.Name): 1680 | raise PseudoPythonTypeCheckError('expected a name for an index not %s' % type(targets[0]).__name__) 1681 | 1682 | if self._general_type(sequence_node['pseudo_type']) == 'Dictionary': 1683 | q = 'items' 1684 | k = 'key' 1685 | v = 'value' 1686 | else: 1687 | q = 'index' 1688 | k = 'index' 1689 | v = 'iterator' 1690 | iterator_type = self._element_type(sequence_node['pseudo_type']) 1691 | return { 1692 | 'type': '', 1693 | 'sequences': {'type': 'for_sequence_with_' + q, 'sequence': sequence_node}, 1694 | 'iterators': {'type': 'for_iterator_with_' + q, 1695 | k: { 1696 | 'type': 'local', 1697 | 'pseudo_type': 'Int', 1698 | 'name': targets[0].id 1699 | }, 1700 | v: { 1701 | 'type': 'local', 1702 | 'pseudo_type': iterator_type, 1703 | 'name': targets[1].id 1704 | }} 1705 | }, {targets[0].id: 'Int', targets[1].id: iterator_type} 1706 | 1707 | def _translate_range(self, targets, range): 1708 | if len(range) == 1: 1709 | start, end, step = {'type': 'int', 'value': 0, 'pseudo_type': 'Int'}, self._translate_node(range[0]), {'type': 'int', 'value': 1, 'pseudo_type': 'Int'} 1710 | elif len(range) == 2: 1711 | start, end, step = self._translate_node(range[0]), self._translate_node(range[1]), {'type': 'int', 'value': 1, 'pseudo_type': 'Int'} 1712 | else: 1713 | start, end, step = tuple(map(self._translate_node, range[:3])) 1714 | 1715 | for label, r in [('start', start), ('end', end), ('step', step)]: 1716 | if r['pseudo_type'] != 'Int': 1717 | raise PseudoPythonTypeCheckError('expected int for range %s index' % label) 1718 | 1719 | if not isinstance(targets[0], ast.Name): 1720 | raise PseudoPythonTypeCheckError('index is not a name %s' % type(targets[0]).__name__) 1721 | 1722 | return { 1723 | 'type': '_range', 1724 | 'start': start, 1725 | 'end': end, 1726 | 'step': step, 1727 | 'index': { 1728 | 'type': 'local', 1729 | 'pseudo_type': 'Int', 1730 | 'name': targets[0].id 1731 | } 1732 | }, {targets[0].id: 'Int'} 1733 | 1734 | def _translate_zip(self, targets, sequences): 1735 | sequence_nodes = [] 1736 | sketchup = {'type': '', 'iterators': {'type': 'for_iterator_zip', 'iterators': []}} 1737 | env = {} 1738 | for s, z in zip(sequences, targets): 1739 | sequence_nodes.append(self._translate_node(s)) 1740 | self._confirm_iterable(sequence_nodes[-1]['pseudo_type']) 1741 | if not isinstance(z, ast.Name): 1742 | raise PseudoPythonTypeCheckError('index is not a name %s' % type(z).__name__) 1743 | z_type = self._element_type(sequence_nodes[-1]['pseudo_type']) 1744 | sketchup['iterators']['iterators'].append({ 1745 | 'type': 'local', 1746 | 'pseudo_type': z_type, 1747 | 'name': z.id 1748 | }) 1749 | env[z.id] = z_type 1750 | 1751 | sketchup['sequences'] = {'type': 'for_sequence_zip', 'sequences': sequence_nodes} 1752 | return sketchup, env 1753 | 1754 | 1755 | def _confirm_iterable(self, sequence_type): 1756 | sequence_general_type = self._general_type(sequence_type) 1757 | if sequence_general_type not in ITERABLE_TYPES: 1758 | raise PseudoPythonTypeCheckError('expected an iterable type, not %s' % serialize_type(sequence_type)) 1759 | 1760 | def _element_type(self, sequence_type): 1761 | if isinstance(sequence_type, list): 1762 | if sequence_type[0] == 'Dictionary': 1763 | return sequence_type[2] 1764 | elif sequence_type[0] == 'List': 1765 | return sequence_type[1] 1766 | elif sequence_type == 'String': 1767 | return 'String' 1768 | 1769 | def assert_translatable(self, node, **pairs): 1770 | for label, (expected, actual) in pairs.items(): 1771 | if actual != expected: 1772 | raise PseudoPythonNotTranslatableError("%s in %s is not a part of pseudo-translatable python" % (label if label[-1] != '_' else label[:-1], node)) 1773 | 1774 | def _translate_pure_functions(self): 1775 | for f in self.definitions: 1776 | if f[0] == 'function' and len(self.type_env['functions'][f[1]]) == 2: 1777 | self._definition_index['functions'][f[1]] = self._translate_function(self._definition_index['functions'][f[1]], 'functions', None, f[1], []) 1778 | 1779 | def _translate_hinted_functions(self): 1780 | for f in self.definitions: 1781 | if f[0] == 'function': 1782 | self._translate_hinted_fun(f[1], 'functions') 1783 | 1784 | def _translate_hinted_fun(self, f, namespace): 1785 | # print(namespace, self.type_env[namespace]) 1786 | if isinstance(self._definition_index[namespace][f], dict): 1787 | return 1788 | if namespace == 'functions': 1789 | args = self._definition_index[namespace][f].args.args 1790 | else: 1791 | args = self._definition_index[namespace][f].args.args[1:] 1792 | if len(self.type_env[namespace][f]) > 2 and args[0].annotation: 1793 | types = [] 1794 | for h in args: 1795 | if h.annotation: 1796 | types.append(self._hint(h.annotation)) 1797 | else: 1798 | raise translation_error('expected annotations for all args, no annotation for %s' % h.arg, 1799 | (h.lineno, h.col_offset), self.lines[h.lineno]) 1800 | 1801 | return_annotation = self._definition_index[namespace][f].returns 1802 | if return_annotation: 1803 | return_type = self._hint(return_annotation) 1804 | else: 1805 | return_type = 'Void' # None 1806 | self.type_env[namespace][f][1:] = types + [return_type] 1807 | self._definition_index[namespace][f] = self._translate_function(self._definition_index[namespace][f], namespace, None, f, None) 1808 | 1809 | def _hint(self, x): 1810 | if isinstance(x, (ast.Name, ast.Str)): 1811 | name = x.id if isinstance(x, ast.Name) else x.s 1812 | if name in BUILTIN_SIMPLE_TYPES: 1813 | return BUILTIN_SIMPLE_TYPES[name] 1814 | elif name in self.type_env.top.values: 1815 | return name 1816 | elif isinstance(x, ast.Subscript) and isinstance(x.value, (ast.Name, ast.Str)): 1817 | name = x.value.id if isinstance(x.value, ast.Name) else x.value.s 1818 | if name in ['List', 'Set', 'Dict', 'Tuple', 'Callable']: 1819 | if name not in self._typing_imports: 1820 | raise translation_error('please add\nfrom typing import %s on top to use it\n' % name, (x.value.lineno, x.value.col_offset), self.lines[x.value.lineno]) 1821 | if not isinstance(x.slice, ast.Index): 1822 | raise translation_error('invalid index', (x.value.lineno, x.value.col_offset), self.lines[x.lineno]) 1823 | index = x.slice.value 1824 | if name in ['List', 'Set']: 1825 | if not isinstance(index, (ast.Name, ast.Subscript)): 1826 | raise type_check_error('%s expects one valid generic arguments' % name, (x.value.lineno, x.value.col_offset), self.lines[x.value.lineno]) 1827 | return [name, self._hint(index)] 1828 | elif name == 'Tuple': 1829 | if not isinstance(index, ast.Tuple) or any(not isinstance(y, (ast.Name, ast.Subscript)) for y in index.elts): 1830 | raise type_check_error('Tuple expected valid generic arguments', (x.value.lineno, x.value.col_offset), self.lines[x.value.lineno]) 1831 | return ['Tuple'] + [self._hint(y) for y in index.elts] 1832 | elif name == 'Dict': 1833 | if not isinstance(index, ast.Tuple) or len(index.elts) != 2 or not isinstance(index.elts[1], (ast.Name, ast.Subscript)): 1834 | raise type_check_error('Dict expected 2 valid generic arguments', (x.value.lineno, x.value.col_offset), self.lines[x.value.lineno]) 1835 | if not isinstance(index.elts[0], ast.Name) or index.elts[0].id not in KEY_TYPES: 1836 | raise type_check_error('type not supported as a dictionary key type', (x.value.lineno, x.value.col_offset), self.lines[x.value.lineno], 1837 | suggestions='only those types are supported:\n %s ' % '\n '.join(PSEUDO_KEY_TYPES), 1838 | right=' Dict[str, List[int]]', 1839 | wrong=' Dict[List[int], Tuple[int]]') 1840 | return ['Dictionary', self._hint(index.elts[0]), self._hint(index.elts[1])] 1841 | else: 1842 | if not isinstance(index, ast.Tuple) or len(index.elts) != 2 or not isinstance(index.elts[0], ast.List) or not isinstance(index.elts[1], (ast.Name, ast.Subscript)) or any(not isinstance(y, (ast.Name, ast.Subscript)) for y in index.elts[0].elts): 1843 | raise type_check_error('Callable expected valid generic arguments of the form Callable[[, *], ]', (x.value.lineno, x.value.col_offset), self.lines[x.value.lineno]) 1844 | return ['Function'] + [self._hint(y) for y in index.elts[0].elts] + [self._hint(index.elts[1])] 1845 | raise type_check_error('type not recognized', 1846 | (x.lineno, x.col_offset), self.lines[x.lineno], 1847 | suggestions='supported type hints are:\n ' + '\n '.join( 1848 | ['int', 'float', 'str', 'bool', 1849 | 'List[]', 'Dict[, ]', 'Tuple[..]', 'Set[]', 'Callable[[*], ]' 1850 | 'your class e.g. Human'])) 1851 | 1852 | def _translate_for(self, iter, target, body, orelse, location): 1853 | self.assert_translatable('for', orelse=([], orelse)) 1854 | sketchup, env = self._translate_iter(target, iter) 1855 | for label, value in env.items(): 1856 | if self.type_env[label]: 1857 | raise PseudoPythonTypeCheckError("pseudo-python forbirds %s shadowing a variable in for" % label) 1858 | self.type_env[label] = value 1859 | self.in_for = True 1860 | sketchup['block'] = self._translate_node(body) 1861 | # [self._translate_node(z) for z in body] 1862 | sketchup['type'] = 'for' + sketchup['type'] + '_statement' 1863 | sketchup['pseudo_type'] = 'Void' 1864 | return sketchup 1865 | 1866 | def _type_check(self, z, message, types): 1867 | g = self.type_env.top.values.get(z, {}).get(message) 1868 | if not g: 1869 | raise PseudoPythonTypeCheckError("%s is not defined" % message) 1870 | 1871 | return self._real_type_check(g, types, '%s#%s' % (z, message)) 1872 | 1873 | def _real_type_check(self, g, types, name): 1874 | if len(g) - 2 != len(types): 1875 | raise PseudoPythonTypeCheckError("%s expected %d args" % (message, len(g) - 2)) 1876 | 1877 | for j, (a, b) in enumerate(zip(g[1:-1], types)): 1878 | general = self._compatible_types(b, a, "can't convert %s %dth arg" % (name, j)) 1879 | 1880 | return g 1881 | 1882 | def _compatible_types(self, from_, to, err, silent=False): 1883 | if isinstance(from_, str): 1884 | if not isinstance(to, str): 1885 | if silent: 1886 | return False 1887 | else: 1888 | raise PseudoPythonTypeCheckError(err + ' from %s to %s' % (serialize_type(from_), serialize_type(to))) 1889 | 1890 | elif from_ == to: 1891 | return to 1892 | 1893 | elif from_ in self._hierarchy: 1894 | if to in self._hierarchy: 1895 | if to in self._hierarchy[from_][1]: 1896 | return from_ 1897 | 1898 | base = to 1899 | while base: 1900 | if from_ in self._hierarchy[base][1]: 1901 | return base 1902 | base = self._hierarchy[base][0] 1903 | 1904 | if silent: 1905 | return False 1906 | else: 1907 | raise PseudoPythonTypeCheckError(err + ' from %s to %s' % (serialize_type(from_), serialize_type(to))) 1908 | 1909 | elif from_ == 'Int' and to == 'Float': 1910 | return 'Float' 1911 | 1912 | elif silent: 1913 | return False 1914 | else: 1915 | raise PseudoPythonTypeCheckError(err + ' from %s to %s' % (serialize_type(from_), serialize_type(to))) 1916 | else: 1917 | if not isinstance(to, list) or len(from_) != len(to) or from_[0] != to[0]: 1918 | if silent: 1919 | return False 1920 | else: 1921 | raise PseudoPythonTypeCheckError(err + ' from %s to %s' % (serialize_type(from_), serialize_type(to))) 1922 | 1923 | for f, t in zip(from_[1:-1], to[1:-1]): 1924 | self._compatible_types(f, t, err) 1925 | 1926 | return to 1927 | 1928 | 1929 | def _general_type(self, t): 1930 | if isinstance(t, list): 1931 | return t[0] 1932 | else: 1933 | return t 1934 | 1935 | 1936 | -------------------------------------------------------------------------------- /pseudo_python/builtin_typed_api.py: -------------------------------------------------------------------------------- 1 | # int Int 2 | # float Float 3 | # boolean Boolean 4 | # str String 5 | # [2] List 6 | # {2: 2.0} Dict 7 | # [] List 8 | # {} Dict 9 | 10 | from pseudo_python.errors import PseudoPythonTypeCheckError 11 | from pseudo_python.helpers import serialize_type 12 | 13 | V = '_' # we don't really typecheck or care for a lot of the arg types, so just use this 14 | _ = () 15 | 16 | # we use lists instead of tuples, because it's easier this way 17 | # for different methods in the same type env to reference and update the same signature 18 | # that helps us with inherited methods: each one updates the type signature for the whole hierarchy 19 | 20 | 21 | def builtin_type_check(namespace, function, receiver, args): 22 | fs = TYPED_API[namespace] 23 | if fs == 'library': 24 | fs = TYPED_API['_%s' % namespace] 25 | # print(namespace, function, receiver, args, TYPED_API[namespace]) 26 | # input(0) 27 | if function not in fs: 28 | raise PseudoPythonTypeCheckError('wrong usage of %s' % str(function)) 29 | x = fs[function] 30 | 31 | a = namespace + '#' + function if receiver else namespace + ':' + function 32 | if namespace == 'List' or namespace == 'Set' or namespace == 'Array': 33 | generics = {'@t': receiver['pseudo_type'][1]} 34 | elif namespace == 'Dictionary': 35 | generics = {'@k': receiver['pseudo_type'][1], '@v': receiver['pseudo_type'][2]} 36 | else: 37 | generics = {} 38 | 39 | s = [] 40 | if x[0][0] == '*': 41 | e = x[0][1:] 42 | for arg in args: 43 | s.append(simplify(e, generics)) 44 | arg_check(s[-1], arg, a) 45 | else: 46 | if len(x) - 1 != len(args): 47 | raise PseudoPythonTypeCheckError("%s expects %d args not %d" % (a, len(x) - 1, args)) 48 | for e, arg in zip(x[:-1], args): 49 | s.append(simplify(e, generics)) 50 | arg_check(s[-1], arg, a) 51 | s.append(simplify(x[-1], generics)) 52 | return s 53 | 54 | def arg_check(expected_type, args, a): 55 | if expected_type != args['pseudo_type'] and expected_type != 'Any' and not(expected_type == 'Number' and (args['pseudo_type'] == 'Int' or args['pseudo_type'] == 'Float')): 56 | raise PseudoPythonTypeCheckError('%s expected %s not %s' % (a, serialize_type(expected_type), serialize_type(args['pseudo_type']))) 57 | 58 | def simplify(kind, generics): 59 | if not generics: 60 | return kind 61 | elif isinstance(kind, str): 62 | if kind[0] == '@' and kind in generics: 63 | return generics[kind] 64 | else: 65 | return kind 66 | else: 67 | return [simplify(child, generics) for child in kind] 68 | 69 | # refactoring here in future 70 | 71 | def add(l, r): 72 | if l == 'Float' and r in ['Float', 'Int'] or r == 'Float' and l in ['Float', 'Int']: 73 | return [l, r, 'Float'] 74 | elif l == 'Int' and r == 'Int': 75 | return [l, r, 'Int'] 76 | elif l == 'String' and r == 'String': 77 | return [l, r, 'String'] 78 | elif isinstance(l, list) and l[0] == 'List' and l == r: 79 | return [l, r, l] 80 | else: 81 | raise PseudoPythonTypeCheckError("wrong types for +: %s and %s" % (serialize_type(l), serialize_type(r))) 82 | 83 | def sub(l, r): 84 | if l == 'Float' and r in ['Float', 'Int'] or r == 'Float' and l in ['Float', 'Int']: 85 | return [l, r, 'Float'] 86 | elif l == 'Int' and r == 'Int': 87 | return [l, r, 'Int'] 88 | else: 89 | raise PseudoPythonTypeCheckError("wrong types for -: %s and %s" % (serialize_type(l), serialize_type(r))) 90 | 91 | def mul(l, r): 92 | if l == 'Float' and r in ['Float', 'Int'] or r == 'Float' and l in ['Float', 'Int']: 93 | return [l, r, 'Float'] 94 | elif l == 'Int' and r == 'Int': 95 | return [l, r, 'Int'] 96 | elif l == 'Int' and (isinstance(r, list) and r[0] == 'List' or r == 'String'): 97 | return [l, r, r] 98 | elif r == 'Int' and (isinstance(l, list) and l[0] == 'List' or l == 'String'): 99 | return [l, r, l] 100 | else: 101 | raise PseudoPythonTypeCheckError("wrong types for *: %s and %s" % (serialize_type(l), serialize_type(r))) 102 | 103 | def div(l, r): 104 | if l == 'Float' and r in ['Float', 'Int'] or r == 'Float' and l in ['Float', 'Int']: 105 | return [l, r, 'Float'] 106 | elif l == 'Int' and r == 'Int': 107 | return [l, r, 'Int'] 108 | else: 109 | raise PseudoPythonTypeCheckError("wrong types for /: %s and %s" % (serialize_type(l), serialize_type(r))) 110 | 111 | def pow_(l, r): 112 | if l == 'Float' and r in ['Float', 'Int'] or r == 'Float' and l in ['Float', 'Int']: 113 | return [l, r, 'Float'] 114 | elif l == 'Int' and r == 'Int': 115 | return [l, r, 'Int'] 116 | else: 117 | raise PseudoPythonTypeCheckError("wrong types for **: %s and %s" % (serialize_type(l), serialize_type(r))) 118 | 119 | def mod(l, r): 120 | if l == 'Int' and r == 'Int': 121 | return [l, r, 'Int'] 122 | elif l == 'String' and (r == 'String' or r == ['Array', 'String']): 123 | return [l, ['Array', 'String'], 'String'] 124 | else: 125 | raise PseudoPythonTypeCheckError("wrong types for %: %s and %s" % (serialize_type(l), serialize_type(r))) 126 | 127 | def and_(l, r): 128 | if l == 'Boolean' and r == 'Boolean': 129 | return 'Boolean' 130 | else: 131 | raise PseudoPythonTypeCheckError("wrong types for and: %s and %s" % (serialize_type(l), serialize_type(r))) 132 | 133 | def or_(l, r): 134 | if l == 'Boolean' and r == 'Boolean': 135 | return 'Boolean' 136 | else: 137 | raise PseudoPythonTypeCheckError("wrong types for or: %s and %s" % (serialize_type(l), serialize_type(r))) 138 | 139 | def binary_and(l, r): 140 | if l == r == 'Int' or l == r == 'Set': 141 | return l 142 | else: 143 | raise PseudoPythonTypeCheckError("wrong types for &: %s and %s" % (serialize_type(l), serialize_type(r))) 144 | 145 | def binary_or(l, r): 146 | if l == r == 'Int' or l == r == 'Set': 147 | return l 148 | else: 149 | raise PseudoPythonTypeCheckError("wrong types for |: %s and %s" % (serialize_type(l), serialize_type(r))) 150 | 151 | def xor_(l, r): 152 | if l == r == 'Int' or l == r == 'Set': 153 | return l 154 | else: 155 | raise PseudoPythonTypeCheckError("wrong types for ^: %s and %s" % (serialize_type(l), serialize_type(r))) 156 | 157 | 158 | # for template types as list, dict @t is the type of list arg and @k, @v of dict args 159 | TYPED_API = { 160 | # methods 161 | 'global': { 162 | 'exit': ['Int', 'Void'], 163 | 'to_string': ['Any', 'String'] 164 | }, 165 | 166 | 'io': { 167 | 'display': ['*Any', 'Void'], 168 | 'read': ['String'], 169 | 'read_file': ['String', 'String'], 170 | 'write_file': ['String', 'String', 'Void'] 171 | }, 172 | 173 | 'system': { 174 | 'args': [['List', 'String']] 175 | }, 176 | 177 | 'regexp': { 178 | 'compile': ['String', 'Regexp'], 179 | 'escape': ['String', 'String'] 180 | }, 181 | 182 | 'math': { 183 | 'tan': ['Number', 'Float'], 184 | 'sin': ['Number', 'Float'], 185 | 'cos': ['Number', 'Float'], 186 | 'ln': ['Number', 'Float'], 187 | 'log': ['Number', 'Number', 'Float'] 188 | }, 189 | 190 | 'operators': { 191 | '+': add, 192 | '-': sub, 193 | '*': mul, 194 | '/': div, 195 | '**': pow_, 196 | '%': mod, 197 | 198 | '&': binary_and, 199 | '|': binary_or, 200 | '^': xor_ 201 | }, 202 | 203 | 'List': { 204 | 'push': ['@t', 'Void'], 205 | 'pop': ['@t'], 206 | 'insert': ['@t', 'Void'], 207 | 'insert_at': ['@t', 'Int', 'Void'], 208 | 'concat': [['List', '@t'], ['List', '@t']], 209 | 'repeat': ['Int', ['List', '@t']], 210 | 'push_many': [['List', '@t'], 'Void'], 211 | 'remove': ['@t', 'Void'], 212 | 'length': ['Int'], 213 | 'join': [['List', 'String'], 'String'], 214 | 'map': [['Function', '@t', '@y'], ['List', '@y']], 215 | 'filter': [['Function', '@t', 'Boolean'], ['List', '@t']] 216 | }, 217 | 218 | 'Dictionary': { 219 | 'keys': ['List', '@k'], 220 | 'values': ['List', '@v'], 221 | 'length': ['Int'] 222 | }, 223 | 'String': { 224 | 'find': ['String', 'Int'], 225 | 'to_int': ['Int'], 226 | 'split': ['String', ['List', 'String']], 227 | 'c_format': [['Array', 'String'], 'String'], 228 | 'upper': ['String'], 229 | 'lower': ['String'], 230 | 'title': ['String'], 231 | 'center': ['Int', 'String', 'String'], 232 | 'find_from': ['String', 'Int', 'Int'], 233 | 'length': ['Int'], 234 | }, 235 | 'Set': { 236 | '|': [['Set', '@t'], ['Set', '@t']], 237 | 'add': ['@t', 'Void'], 238 | 'remove': ['@t', 'Void'], 239 | '&': [['Set', '@t'], ['Set', '@t']], 240 | '^': [['Set', '@t'], ['Set', '@t']], 241 | '-': [['Set', '@t'], ['Set', '@t']] 242 | }, 243 | 'Int': {'to_int': ['Int'], 'to_float': ['Float']}, 244 | 'Float': {'to_int': ['Int'], 'to_float': ['Float']}, 245 | 'Array': { 246 | 'length': ['Int'], 247 | 'index': ['@t', 'Int'], 248 | 'count': ['@t', 'Int'] 249 | }, 250 | 251 | 'Tuple': { 252 | 'length': ['Int'] 253 | }, 254 | 255 | 'Regexp': { 256 | 'match': ['String', 'RegexpMatch'], 257 | 'groups': ['String', ['String']] 258 | }, 259 | 260 | 'RegexpMatch': { 261 | 'group': ['Int', 'String'] 262 | }, 263 | 264 | '_generic_List': ['List', '@t'], 265 | '_generic_Set': ['Set', '@t'], 266 | '_generic_Array': ['Array', '@t'], 267 | '_generic_Tuple': ['Tuple', '@t'], 268 | '_generic_Dictionary': ['Dictionary', '@k', '@v'], 269 | # 'List#pop': [_, '@t'], 270 | # 'List#insert': [_, 'Null'], 271 | # 'List#remove': [_, 'Null'], 272 | # 'List#remove_at': [_, 'Null'], 273 | # 'List#length': [_, 'Int'], 274 | # 'List#concat_one': [_, 'List<@t>'], 275 | # 'List#concat': [_, 'List<@t>'], 276 | # 'List#[]': [_, '@t'], 277 | # 'List#[]=': [_, 'Null'], 278 | # 'List#slice': [_, 'List<@t>'], 279 | 280 | # 'Dict#keys': [_, 'List<@k>'], 281 | # 'Dict#values': [_, 'List<@v>'], 282 | } 283 | 284 | # useful for error messages 285 | 286 | ORIGINAL_METHODS = { 287 | 'List': { 288 | 'push': 'append(element)', 289 | 'pop': 'pop', 290 | 'insert': 'insert(element)', 291 | 'insert_at': 'insert(element, index)', 292 | 'concat': '+', 293 | 'repeat': '*', 294 | 'push_many': 'extend(other)', 295 | 'remove': 'remove', 296 | 'length': 'len', 297 | 'map': 'list comprehension / map', 298 | 'filter': 'list comprehension / filter' 299 | }, 300 | 301 | 'Dictionary': { 302 | 'keys': 'keys', 303 | 'values': 'values', 304 | 'length': 'len' 305 | }, 306 | 307 | 'Int': { 308 | 'to_int': 'int', 309 | 'to_float': 'float' 310 | }, 311 | 'Float': { 312 | 'to_int': 'int', 313 | 'to_float': 'float' 314 | }, 315 | 'String': { 316 | 'find': 'index(substring)', 317 | 'join': 'join(elements)', 318 | 'split': 'split(delimiter)', 319 | 'c_format': '%', 320 | 'format': 'format(*elements)', 321 | 'upper': 'upper', 322 | 'lower': 'lower', 323 | 'title': 'title', 324 | 'center': 'center', 325 | 'find_from': 'index(substring, index)', 326 | 'to_int': 'int' 327 | }, 328 | 'Set': { 329 | '|': '|', 330 | 'add': 'add(element)', 331 | 'remove': 'remove(element)', 332 | '&': '&', 333 | '^': '^', 334 | '-': '-' 335 | }, 336 | 337 | 'Array': { 338 | 'length': 'len', 339 | 'find': 'find(element)', 340 | 'count': 'count(element)' 341 | }, 342 | 343 | 'Tuple': { 344 | 'length': 'len' 345 | }, 346 | 347 | 'Regexp': { 348 | 'match': 'match(value)', 349 | 'groups': 'find_all(value)' 350 | }, 351 | 352 | 'RegexpMatch': { 353 | 'group': 'group(z)' 354 | } 355 | } -------------------------------------------------------------------------------- /pseudo_python/env.py: -------------------------------------------------------------------------------- 1 | class Env: 2 | 3 | def __init__(self, values=None, parent=None): 4 | self.values = values or {} 5 | self.parent = parent 6 | self.top = self if parent is None else parent.top 7 | 8 | def __getitem__(self, key): 9 | current = self 10 | while current is not None: 11 | if key in current.values: 12 | return current.values[key] 13 | current = current.parent 14 | 15 | def __setitem__(self, key, value): 16 | self.values[key] = value 17 | 18 | # def motherify(self): 19 | # return self.parent 20 | 21 | def child_env(self, values=None): 22 | return Env(values, self) 23 | -------------------------------------------------------------------------------- /pseudo_python/errors.py: -------------------------------------------------------------------------------- 1 | from pseudo_python.helpers import serialize_type 2 | 3 | class PseudoError(Exception): 4 | def __init__(self, message, suggestions=None, right=None, wrong=None): 5 | super(PseudoError, self).__init__(message) 6 | 7 | self.suggestions = suggestions 8 | self.right = right 9 | self.wrong = wrong 10 | 11 | class PseudoPythonNotTranslatableError(PseudoError): 12 | pass 13 | 14 | class PseudoPythonTypeCheckError(PseudoError): 15 | pass 16 | 17 | def cant_infer_error(name, line): 18 | return PseudoPythonTypeCheckError("pseudo-python can't infer the types for %s:\n%s\n" % (name, line), 19 | suggestions='you need to either:\n' + 20 | ' ensure %s is reachable from a call in your top scope\n' % name + 21 | ' e.g. a(..) in top scope, b(..) in a body, %s() in b body\n' % name + 22 | ' or provide type hints (see https://docs.python.org/3/library/typing.html)' 23 | ) 24 | 25 | def beautiful_error(exception): 26 | def f(function): 27 | def decorated(data, location=None, code=None, wrong_type=None, **options): 28 | return exception('%s%s%s:\n%s\n%s^' % ( 29 | ('wrong type %s\n' % serialize_type(wrong_type) if wrong_type else ''), 30 | data, 31 | (' on line %d column %d' % location) if location else '', 32 | code or '', 33 | (tab_aware(location[1], code) if location else '')), 34 | **options) 35 | return decorated 36 | return f 37 | 38 | @beautiful_error(PseudoPythonTypeCheckError) 39 | def type_check_error(data, location=None, code=None, wrong_type=None, **options): 40 | pass 41 | 42 | @beautiful_error(PseudoPythonNotTranslatableError) 43 | def translation_error(data, location=None, code=None, wrong_type=None, **options): 44 | pass 45 | 46 | def tab_aware(location, code): 47 | ''' 48 | if tabs in beginning of code, add tabs for them, otherwise spaces 49 | ''' 50 | return ''.join(' ' if c != '\t' else '\t' for c in code[:location]) 51 | -------------------------------------------------------------------------------- /pseudo_python/helpers.py: -------------------------------------------------------------------------------- 1 | def serialize_type(l): 2 | if isinstance(l, str): 3 | return l 4 | elif isinstance(l, list): 5 | return '%s[%s]' % (l[0], ', '.join(map(serialize_type, l[1:]))) 6 | else: 7 | return str(l) 8 | 9 | def safe_serialize_type(l): 10 | '''serialize only with letters, numbers and _''' 11 | 12 | if isinstance(l, str): 13 | return l 14 | elif isinstance(l, list): 15 | return '%s_%s_' % (l[0], ''.join(map(safe_serialize_type, l[1:]))) 16 | else: 17 | return str(l) 18 | 19 | def prepare_table(types, original_methods=None): 20 | names, args, returns = [], [], [] 21 | max_name, max_arg, max_return = 0, 0, 0 22 | for name, t in types.items(): 23 | if original_methods: 24 | if name in original_methods: 25 | name = original_methods[name] 26 | else: 27 | continue 28 | names.append(name) 29 | args.append(' '.join(serialize_type(arg) for arg in t[:-1])) 30 | returns.append(serialize_type(t[-1])) 31 | if len(name) > max_name: 32 | max_name = len(name) 33 | if len(args[-1]) > max_arg: 34 | max_arg = len(args[-1]) 35 | if len(returns[-1]) > max_return: 36 | max_return = len(returns[-1]) 37 | return '\n'.join( 38 | ' %s %s -> %s' % (name.ljust(max_name), arg_types.ljust(max_arg), return_type.ljust(max_return)) for name, arg_types, return_type in zip(names, args, returns)) 39 | -------------------------------------------------------------------------------- /pseudo_python/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | #sys.path.append("/home/alehander42/pseudo") 5 | #sys.path.append("/home/alehander42/pseudo-python") 6 | import pseudo_python 7 | import pseudo_python.errors 8 | import pseudo 9 | import pseudo.errors 10 | from colorama import init 11 | from termcolor import colored 12 | 13 | USAGE = ''' 14 | pseudo-python [ / ] 15 | 16 | where if you omit , pseudo-python will generate a 17 | file with serialized ast 18 | 19 | if is provided, will be extracted from 20 | the extension 21 | 22 | it can be: 23 | py / python 24 | rb / ruby 25 | js / javascript 26 | cs / csharp 27 | go 28 | 29 | examples: 30 | pseudo-python a.py # generates a.pseudo.yaml 31 | pseudo-python z.py o.rb # generates a ruby translation in o.rb 32 | ''' 33 | 34 | def main(): 35 | if len(sys.argv) == 1: 36 | print(USAGE) 37 | return 38 | 39 | filename = sys.argv[1] 40 | with open(filename, 'r') as f: 41 | source = f.read() 42 | base, _ = os.path.splitext(filename) 43 | try: 44 | if len(sys.argv) == 2: 45 | clj = pseudo_python.translate_to_yaml(source) 46 | with open('%s.pseudo.yaml' % base, 'w') as f: 47 | f.write(clj) 48 | print(colored('OK\nsaved pseudo ast as %s.pseudo.yaml' % base, 'green')) 49 | else: 50 | arg = sys.argv[2] 51 | if '.' in arg: 52 | base, language = os.path.splitext(arg) 53 | language = language[1:] 54 | else: 55 | language = arg 56 | if language not in pseudo.SUPPORTED_FORMATS: 57 | print(colored('%s is not supported' % language, 'red')) 58 | exit(1) 59 | if '%s.%s' % (base, pseudo.FILE_EXTENSIONS[language]) == filename: 60 | print(colored('this would overwrite the input file, please choose another name', 'red')) 61 | exit(1) 62 | node = pseudo_python.translate(source) 63 | output = pseudo.generate(node, language) 64 | with open('%s.%s' % (base, pseudo.FILE_EXTENSIONS[language]), 'w') as f: 65 | f.write(output) 66 | print(colored('OK\nsaved as %s.%s' % (base, pseudo.FILE_EXTENSIONS[language]), 'green')) 67 | except pseudo_python.errors.PseudoError as e: 68 | print(colored(e, 'red')) 69 | if e.suggestions: 70 | print(colored(e.suggestions, 'green')) 71 | if e.right: 72 | print(colored('\nright:\n%s' % e.right, 'green')) 73 | if e.wrong: 74 | print(colored('\nwrong:\n%s' % e.wrong, 'red')) 75 | exit(1) 76 | except pseudo.errors.PseudoError as e: 77 | print(colored('Pseudo error:\n%s' % e, 'red')) 78 | exit(1) 79 | 80 | if __name__ == '__main__': 81 | main() 82 | -------------------------------------------------------------------------------- /pseudo_python/parser.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | 4 | def parse(source): 5 | x = ast.parse(source) 6 | return x 7 | -------------------------------------------------------------------------------- /pseudo_python/standard.py: -------------------------------------------------------------------------------- 1 | class Standard: 2 | pass 3 | 4 | class StandardCall(Standard): 5 | def __init__(self, namespace, function, expander=None): 6 | self.namespace = namespace 7 | self.function = function 8 | self.expander = expander 9 | 10 | def expand(self, args): 11 | if not self.expander: 12 | q = builtin_type_check(self.namespace, self.function, None, args) 13 | return {'type': 'standard_call', 'namespace': self.namespace, 'function': self.function, 'args': args, 'pseudon_type': q[-1]} 14 | else: 15 | return self.expander(self.namespace, self.function, args) 16 | 17 | class StandardMethodCall(Standard): 18 | def __init__(self, type, message, expander=None): 19 | self.type = type 20 | self.message = message 21 | self.expander = expander 22 | 23 | def expand(self, args): 24 | if not self.expander: 25 | q = builtin_type_check(self.type, self.message, args[0], args[1:]) 26 | return {'type': 'standard_method_call', 'receiver': args[0], 'message': self.message, 'args': args[1:], 'pseudon_type': q[-1]} 27 | else: 28 | return self.expander(self.type, self.message, args) 29 | 30 | 31 | def len_expander(type, message, args): 32 | receiver_type = args[0]['pseudon_type'] 33 | if isinstance(receiver_type, tuple): 34 | a = receiver_type[0] 35 | else: 36 | a = receiver_type 37 | q = builtin_type_check(a, message, args[0], args[1:]) 38 | return {'type': 'standard_method_call', 'receiver': args[0], 'message': message, 'args': [], 'pseudon_type': q[-1]} 39 | 40 | 41 | FUNCTION_API = { 42 | 'global': { 43 | 'input': StandardCall('io', 'read'), 44 | 'print': StandardCall('io', 'display'), 45 | 'str': StandardCall('global', 'to_string'), 46 | 'len': StandardMethodCall('List', 'length', len_expander) 47 | }, 48 | 49 | 'math': { 50 | 'log': { 51 | 1: StandardCall('math', 'ln'), 52 | 2: StandardCall('math', 'log') 53 | }, 54 | 55 | 'sin': StandardCall('math', 'sin'), 56 | 'cos': StandardCall('math', 'cos') 57 | 58 | } 59 | } 60 | 61 | METHOD_API = { 62 | 'List': { 63 | 'append': StandardMethodCall('List', 'push'), 64 | 'pop': StandardMethodCall('List', 'pop'), 65 | 'insert': { 66 | 1: StandardMethodCall('List', 'insert'), 67 | 2: StandardMethodCall('List', 'insert_at') 68 | }, 69 | 'remove': StandardMethodCall('List', 'remove') 70 | }, 71 | 72 | 'Dictionary': { 73 | 'keys': StandardMethodCall('Dictionary', 'keys'), 74 | 'values': StandardMethodCall('Dictionary', 'values'), 75 | '[]': StandardMethodCall('Dictionary', 'getitem'), 76 | '[]=': StandardMethodCall('Dictionary', 'setitem') 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==5.4 2 | colorama 3 | termcolor 4 | pseudo>=0.2.3 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='pseudo-python', 5 | version='0.2.34', 6 | description='a python3 to pseudo compiler', 7 | author='Alexander Ivanov', 8 | author_email='alehander42@gmail.com', 9 | url='https://github.com/alehander42/pseudo-python', 10 | download_url='https://github.com/alehander42/pseudo-python/archive/v0.2.tar.gz', 11 | keywords=['compiler', 'generation', 'c++', 'ruby', 'c#', 'javascript', 'go', 'python', 'pseudo'], 12 | packages=['pseudo_python'], 13 | license='MIT', 14 | install_requires=[ 15 | 'PyYAML', 16 | 'colorama', 17 | 'termcolor', 18 | 'pseudo>=0.2.3' 19 | ], 20 | classifiers=[ 21 | 'Development Status :: 4 - Beta', 22 | 'Programming Language :: Python', 23 | 'Programming Language :: JavaScript', 24 | 'Programming Language :: Python :: 3', 25 | 'Programming Language :: Python :: 3.2', 26 | 'Programming Language :: Python :: 3.3', 27 | 'Programming Language :: Python :: 3.4', 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: MIT License', 30 | ], 31 | entry_points={ 32 | 'console_scripts': [ 33 | 'pseudo-python=pseudo_python.main:main', 34 | ], 35 | }, 36 | ) 37 | -------------------------------------------------------------------------------- /tests/test_language.py: -------------------------------------------------------------------------------- 1 | class TestLanguage(type): 2 | def __new__(cls, name, bases, namespace, **kwargs): 3 | 4 | def generate_test(name, cases): 5 | def test(self): 6 | for input, exp in cases.items(): 7 | if isinstance(exp, list): 8 | exp = {'type': 'module', 'dependencies': [], 'custom_exceptions': [], 'constants': [], 'definitions': [], 'main': exp} 9 | else: 10 | exp = {'type': 'module', 'dependencies': [], 'custom_exceptions': [], 'constants': exp.get('constants', []), 'definitions': exp.get('definitions', []), 'main': exp['main']} 11 | 12 | self.assertEqual(self.translate(input), exp) 13 | return test 14 | 15 | for name, cases in namespace['suite'].items(): 16 | test_name = 'test_%s' % name 17 | namespace[test_name] = generate_test(name, cases) 18 | 19 | return super().__new__(cls, name, bases, namespace) 20 | -------------------------------------------------------------------------------- /tests/test_python.py: -------------------------------------------------------------------------------- 1 | import test_language 2 | import unittest 3 | import textwrap 4 | from pseudo_python import translate 5 | 6 | class TestPython(unittest.TestCase, metaclass=test_language.TestLanguage): 7 | # several shortcuts for common nodes 8 | def local(name, pseudo_type): 9 | return {'type': 'local', 'pseudo_type': pseudo_type, 'name': name} 10 | 11 | def literal(value): 12 | pseudo_node = {int: 'int', float: 'float', bool: 'boolean', str: 'string'}[type(value)] 13 | return {'type': pseudo_node, 'pseudo_type': pseudo_node.title(), 'value': value} 14 | 15 | def call(function, args, pseudo_type): 16 | return {'type': 'call', 'function': function, 'args': args, 'pseudo_type': pseudo_type} 17 | 18 | def t(s): 19 | return textwrap.dedent(s) 20 | 21 | def translate(self, source): 22 | return translate(source) 23 | 24 | maxDiff = None 25 | # why a dict? well all our test inputs for a node type 26 | # are unique strings, and it makes for a good dsl 27 | 28 | suite = dict( 29 | # int = { 30 | # '42': [literal(42)] 31 | # }, 32 | 33 | # float = { 34 | # '42.42': [literal(42.42)] 35 | # }, 36 | 37 | # bool = { 38 | # 'True': [literal(True)], 39 | # 'False': [literal(False)] 40 | # }, 41 | 42 | # str = { 43 | # '""': [literal('')], 44 | # "'lalaя'": [literal('lalaя')] 45 | # }, 46 | 47 | # none = { 48 | # 'None': [{'type': 'null', 'pseudo_type': 'Void'}] 49 | # }, 50 | 51 | # assignments = { 52 | # 'l = 2': [{ 53 | # 'type': 'assignment', 54 | # 'target': local('l', 'Int'), 55 | # 'value': literal(2), 56 | # 'pseudo_type': 'Void' 57 | # }], 58 | # # 'self.a = 4': { 59 | # # 'type': 'assignment', 60 | # # 'target': {'type': 'instance_variable', 'name': 'a', 'pseudo_type': 'Int'}, 61 | # # 'value': literal(4), 62 | # # 'pseudo_type': 'Void' 63 | # # }, 64 | # t(''' 65 | # l = [42] 66 | # l[0] = 42 67 | # '''): [{ 68 | # 'type': 'assignment', 69 | # 'target': local('l', ['List', 'Int']), 70 | # 'value': {'type': 'list', 'elements': [literal(42)], 'pseudo_type': ['List', 'Int']}, 71 | # 'pseudo_type': 'Void' 72 | # }, { 73 | # 'type': 'assignment', 74 | # 'target': {'type': 'index', 'sequence': local('l', ['List', 'Int']), 'index': literal(0), 'pseudo_type': 'Int'}, 75 | # 'value': literal(42), 76 | # 'pseudo_type': 'Void' 77 | # }] 78 | # }, 79 | # extensive tests in v0.3/v0.4 when ast spec stabilizes 80 | function = { 81 | t(''' 82 | def x(a): 83 | return 42 84 | 85 | x(0) 86 | '''): { 87 | 'definitions': [{ 88 | 'type': 'function_definition', 89 | 'name': 'x', 90 | 'params': [{'name': 'a', 'pseudo_type': 'Function', 'type': 'local'}], 91 | 'block': [{ 92 | 'type': 'implicit_return', 93 | 'value': literal(42), 94 | 'pseudo_type': 'Int' 95 | }], 96 | 'pseudo_type': ['Function', 'Int', 'Int'], 97 | 'return_type': 'Int' 98 | }], 99 | 'main': [ 100 | call(local('x', ['Function', 'Int', 'Int']), [literal(0)], 'Int') 101 | ] 102 | } 103 | } 104 | ) 105 | 106 | 107 | --------------------------------------------------------------------------------