├── .ruby-version
├── .rspec
├── peacock
├── peacock-formatter
├── .yarnrc
├── todo
├── .vscodeignore
├── CHANGELOG.md
├── src
│ ├── test
│ │ ├── suite
│ │ │ ├── extension.test.ts
│ │ │ └── index.ts
│ │ └── runTest.ts
│ └── extension.ts
├── .eslintrc.json
├── tsconfig.json
├── package.json
├── webpack.config.js
├── README.md
└── vsc-extension-quickstart.md
├── main.pea
├── spec
├── codegen
│ ├── misc
│ │ ├── not.pea
│ │ ├── dot_operator.pea
│ │ ├── simple_assignment.pea
│ │ ├── simple_float.pea
│ │ ├── bind_2_args.pea
│ │ ├── mod.pea
│ │ ├── self_lookup.pea
│ │ ├── simple_array.pea
│ │ ├── call_zip.pea
│ │ ├── optional_chain.pea
│ │ ├── bind_operator.pea
│ │ ├── bind_with_args.pea
│ │ └── in_and_eq.pea
│ ├── assignment
│ │ ├── reassign.pea
│ │ └── array_deconstruct.pea
│ ├── object_literals
│ │ ├── spread.pea
│ │ ├── object_literal.pea
│ │ ├── object_arrow_method.pea
│ │ └── object_entry_multiline_function.pea
│ ├── functions
│ │ ├── function_call.pea
│ │ ├── short_fn.pea
│ │ ├── no_arg_shorthand.pea
│ │ ├── oneline_function_no_args.pea
│ │ ├── arrow_function_without_parens.pea
│ │ ├── oneline_add.pea
│ │ ├── multiline_add.pea
│ │ └── arrow_function.pea
│ ├── array
│ │ ├── spread.pea
│ │ ├── append.pea
│ │ └── group_by.pea
│ ├── schemas
│ │ ├── schema_definition.pea
│ │ ├── schema_object_int.pea
│ │ ├── schema_union.pea
│ │ ├── short_fn.pea
│ │ ├── null_fn_pattern.pea
│ │ ├── intersection.pea
│ │ ├── iterate_union.pea
│ │ ├── schema_assignment.pea
│ │ ├── schema_named_captures.pea
│ │ ├── int_schema.pea
│ │ └── schema_await.pea
│ ├── classes
│ │ ├── instance_var.pea
│ │ ├── getter.pea
│ │ ├── parser_constructor.pea
│ │ ├── parser.pea
│ │ ├── token.pea
│ │ └── static_from.pea
│ ├── for_comprehension
│ │ ├── range.pea
│ │ └── if.pea
│ ├── range
│ │ ├── spread.pea
│ │ ├── times.pea
│ │ ├── times_1.pea
│ │ ├── times_named.pea
│ │ └── to_a.pea
│ ├── enum
│ │ └── enum_1.pea
│ ├── for_loop
│ │ ├── for_of_loop.pea
│ │ ├── for_in.pea
│ │ └── deconstruct_for_of.pea
│ ├── if
│ │ └── basic.pea
│ ├── when
│ │ └── no_expr.pea
│ └── case_function
│ │ ├── fib.pea
│ │ ├── factorial.pea
│ │ └── to_a.pea
├── snapshot_spec.rb
├── lexer_spec.rb
└── spec_helper.rb
├── lib
├── pea_std_lib
│ ├── std.pea
│ ├── string.pea
│ ├── array.pea
│ ├── range.js
│ ├── symbols.js
│ ├── schema.js
│ └── css_preprocessor.js
├── utils.rb
├── formatter.rb
├── type_checker.rb
├── lexer.rb
├── ast.rb
├── compiler.rb
└── parser.rb
├── logo.png
├── Gemfile
├── public
└── favicon.png
├── examples
├── todo_mvc
│ ├── active
│ │ └── index.pea
│ ├── complete
│ │ └── index.pea
│ ├── Form.pea
│ ├── Page.pea
│ └── List.pea
├── misc
│ └── VideoLibrary.pea
├── css_parser
│ └── lexer.pea
└── parser
│ └── parser.pea
├── .gitignore
├── .solargraph.yml
├── format
├── snapshot.rb
├── package.json
├── main.rb
├── Gemfile.lock
└── README.md
/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.0.2
2 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/peacock:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | to_html $1
--------------------------------------------------------------------------------
/peacock-formatter/.yarnrc:
--------------------------------------------------------------------------------
1 | --ignore-engines true
--------------------------------------------------------------------------------
/main.pea:
--------------------------------------------------------------------------------
1 | enum User = One | Two
2 |
3 | a := User#One
--------------------------------------------------------------------------------
/peacock-formatter/todo:
--------------------------------------------------------------------------------
1 | [ ] move cursor back to place
--------------------------------------------------------------------------------
/spec/codegen/misc/not.pea:
--------------------------------------------------------------------------------
1 | # !true;
2 |
3 | !true
4 |
--------------------------------------------------------------------------------
/lib/pea_std_lib/std.pea:
--------------------------------------------------------------------------------
1 | function to_s = this.toString()
2 |
--------------------------------------------------------------------------------
/spec/codegen/assignment/reassign.pea:
--------------------------------------------------------------------------------
1 | # a = 11;
2 |
3 | a = 11
4 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcellerusu/peacock-lang/HEAD/logo.png
--------------------------------------------------------------------------------
/spec/codegen/misc/dot_operator.pea:
--------------------------------------------------------------------------------
1 | # console.log(a.b);
2 |
3 | console.log a.b
4 |
--------------------------------------------------------------------------------
/spec/codegen/misc/simple_assignment.pea:
--------------------------------------------------------------------------------
1 | # let a;
2 | # a = 1;
3 |
4 | a := 1
5 |
--------------------------------------------------------------------------------
/spec/codegen/misc/simple_float.pea:
--------------------------------------------------------------------------------
1 | # let a;
2 | # a = 1.1;
3 |
4 | a := 1.1
5 |
--------------------------------------------------------------------------------
/spec/codegen/misc/bind_2_args.pea:
--------------------------------------------------------------------------------
1 | # test.call([1], [2], [1]);
2 | [1]::test [2], [1]
3 |
--------------------------------------------------------------------------------
/spec/codegen/misc/mod.pea:
--------------------------------------------------------------------------------
1 | # console.log(31 % 10);
2 |
3 | console.log 31 mod 10
4 |
--------------------------------------------------------------------------------
/spec/codegen/misc/self_lookup.pea:
--------------------------------------------------------------------------------
1 | # let a;
2 | # a = a + 10;
3 |
4 | a := a + 10
5 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | gem "rspec", "~> 3.10"
3 | gem "solargraph"
4 |
--------------------------------------------------------------------------------
/spec/codegen/misc/simple_array.pea:
--------------------------------------------------------------------------------
1 | # let a;
2 | # a = [1, "20"];
3 |
4 | a := [1, "20"]
5 |
--------------------------------------------------------------------------------
/spec/codegen/object_literals/spread.pea:
--------------------------------------------------------------------------------
1 | # let o1;
2 | # o1 = { ...o };
3 | o1 := { ...o }
4 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcellerusu/peacock-lang/HEAD/public/favicon.png
--------------------------------------------------------------------------------
/spec/codegen/functions/function_call.pea:
--------------------------------------------------------------------------------
1 | # add(1, "10", [1, 2]);
2 |
3 | add(1, "10", [1, 2])
4 |
--------------------------------------------------------------------------------
/spec/codegen/array/spread.pea:
--------------------------------------------------------------------------------
1 | # console.log([...[1, 2, 3]]);
2 |
3 | console.log [...[1, 2, 3]]
4 |
--------------------------------------------------------------------------------
/spec/codegen/misc/call_zip.pea:
--------------------------------------------------------------------------------
1 | # console.log(zip.call([1], [2]));
2 |
3 | console.log [1]::zip [2]
4 |
--------------------------------------------------------------------------------
/spec/codegen/schemas/schema_definition.pea:
--------------------------------------------------------------------------------
1 | # const User = { id: s('id') };
2 |
3 | schema User = { id }
4 |
--------------------------------------------------------------------------------
/spec/codegen/schemas/schema_object_int.pea:
--------------------------------------------------------------------------------
1 | # const User = { id: 10 };
2 |
3 | schema User = { id: 10 }
4 |
--------------------------------------------------------------------------------
/spec/codegen/schemas/schema_union.pea:
--------------------------------------------------------------------------------
1 | # const Nums = s.union(1, 2, 3);
2 |
3 | schema Nums = 1 | 2 | 3
4 |
--------------------------------------------------------------------------------
/spec/codegen/functions/short_fn.pea:
--------------------------------------------------------------------------------
1 | # let double;
2 | # double = (_it => _it * 2);
3 |
4 | double := #{ % * 2 }
5 |
--------------------------------------------------------------------------------
/spec/codegen/object_literals/object_literal.pea:
--------------------------------------------------------------------------------
1 | # let a;
2 | # a = { b: 10 };
3 |
4 | a := {
5 | b: 10
6 | }
7 |
--------------------------------------------------------------------------------
/spec/codegen/functions/no_arg_shorthand.pea:
--------------------------------------------------------------------------------
1 | # function ten() {
2 | # return 10;
3 | # };
4 |
5 | function ten = 10
6 |
--------------------------------------------------------------------------------
/spec/codegen/schemas/short_fn.pea:
--------------------------------------------------------------------------------
1 | # const User = { id: (_it => _it > 10) };
2 |
3 | schema User = { id: #{ % > 10 } }
4 |
--------------------------------------------------------------------------------
/spec/codegen/functions/oneline_function_no_args.pea:
--------------------------------------------------------------------------------
1 | # function a() {
2 | # return 10;
3 | # };
4 |
5 | function a() = 10
6 |
--------------------------------------------------------------------------------
/spec/codegen/classes/instance_var.pea:
--------------------------------------------------------------------------------
1 | # class Parser {
2 | # body = [];
3 | # };
4 |
5 | class Parser
6 | body := []
7 | end
8 |
--------------------------------------------------------------------------------
/spec/codegen/functions/arrow_function_without_parens.pea:
--------------------------------------------------------------------------------
1 | # let double;
2 | # double = (x) => x * 2;
3 |
4 | double := x => x * 2
5 |
--------------------------------------------------------------------------------
/spec/codegen/functions/oneline_add.pea:
--------------------------------------------------------------------------------
1 | # function add(a, b) {
2 | # return a + b;
3 | # };
4 |
5 | function add(a, b) = a + b
6 |
--------------------------------------------------------------------------------
/spec/codegen/misc/optional_chain.pea:
--------------------------------------------------------------------------------
1 | # let a;
2 | # a = {};
3 | # console.log(a?.b);
4 |
5 | a := {}
6 |
7 | console.log a?.b
8 |
--------------------------------------------------------------------------------
/spec/codegen/functions/multiline_add.pea:
--------------------------------------------------------------------------------
1 | # function add(a, b) {
2 | # return a + b;
3 | # };
4 |
5 | function add(a, b)
6 | a + b
7 | end
8 |
--------------------------------------------------------------------------------
/spec/codegen/for_comprehension/range.pea:
--------------------------------------------------------------------------------
1 | # console.log(Array.from(new Range(0, 10), num => num * 10));
2 |
3 | console.log [num * 10 for num in 0..10]
4 |
--------------------------------------------------------------------------------
/spec/codegen/assignment/array_deconstruct.pea:
--------------------------------------------------------------------------------
1 | # let a;
2 | # let _temp = [1];
3 | # s.verify([s('a')], _temp, 'Array');
4 | # [a] = _temp;
5 |
6 | [a] := [1]
7 |
--------------------------------------------------------------------------------
/spec/codegen/schemas/null_fn_pattern.pea:
--------------------------------------------------------------------------------
1 | # function a(...args) {
2 | # s.verify(null, args[0], 'null')
3 | # return 1;
4 | # };
5 |
6 | function a(null) = 1
7 |
--------------------------------------------------------------------------------
/spec/codegen/range/spread.pea:
--------------------------------------------------------------------------------
1 | # console.log([...new Range("a", "z")]);
2 | # console.log([...new Range(0, 10)]);
3 |
4 | console.log [..."a".."z"]
5 | console.log [...0..10]
6 |
--------------------------------------------------------------------------------
/examples/todo_mvc/active/index.pea:
--------------------------------------------------------------------------------
1 | import List from "../List"
2 |
3 | component { items }
4 | items := items.filter(item => !item.completed)
5 | in
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/examples/todo_mvc/complete/index.pea:
--------------------------------------------------------------------------------
1 | import List from "../List"
2 |
3 | component { items }
4 | items := items.filter(item => !item.completed)
5 | in
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/spec/codegen/for_comprehension/if.pea:
--------------------------------------------------------------------------------
1 | # console.log(Array.from(new Range(0, 10).filter(num => num > 3), num => num * 10));
2 |
3 | console.log [num * 10 for num in 0..10 if num > 3]
4 |
--------------------------------------------------------------------------------
/spec/codegen/functions/arrow_function.pea:
--------------------------------------------------------------------------------
1 | # let double;
2 | # double = (() => { return 10 * 2; });
3 | # console.log(double());
4 |
5 | double := () => 10 * 2
6 | console.log double()
7 |
--------------------------------------------------------------------------------
/spec/codegen/enum/enum_1.pea:
--------------------------------------------------------------------------------
1 | # let a;
2 | # const User = {
3 | # One: Symbol('One'),
4 | # Two: Symbol('Two'),
5 | # };
6 | # a = User.One;
7 |
8 |
9 | enum User = One | Two
10 |
11 | a := User#One
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | output.html
2 | bin
3 | alltogether.rb
4 | alltogether.rb.js
5 | .vscode
6 | .bundle
7 | node_modules
8 | yarn.lock
9 | index.html
10 | rspec*.pea
11 | test.js
12 | .DS_Store
13 | dist
14 | output.js
--------------------------------------------------------------------------------
/spec/codegen/object_literals/object_arrow_method.pea:
--------------------------------------------------------------------------------
1 | # let PMath;
2 | # PMath = {
3 | # add(a, b) {
4 | # return a + b;
5 | # },
6 | # };
7 |
8 | PMath := {
9 | add(a, b) => a + b,
10 | }
11 |
--------------------------------------------------------------------------------
/spec/codegen/schemas/intersection.pea:
--------------------------------------------------------------------------------
1 | # const User = { id: s('id'), admin: s('admin') };
2 | # const Admin = { ...User, admin: true };
3 |
4 | schema User = { id, admin }
5 | schema Admin = User & { admin: true }
6 |
--------------------------------------------------------------------------------
/spec/codegen/schemas/iterate_union.pea:
--------------------------------------------------------------------------------
1 | # const A = s.union(1, 2, 3);
2 | # for (let a of A) {
3 | # console.log(a);
4 | # };
5 |
6 | schema A = 1 | 2 | 3
7 |
8 | for a of A
9 | console.log a
10 | end
11 |
--------------------------------------------------------------------------------
/spec/codegen/classes/getter.pea:
--------------------------------------------------------------------------------
1 | # class Parser {
2 | # get current_token() {
3 | # return this.tokens[this.pos];
4 | # }
5 | # };
6 |
7 | class Parser
8 | get current_token = this.tokens[this.pos]
9 | end
10 |
--------------------------------------------------------------------------------
/spec/codegen/for_loop/for_of_loop.pea:
--------------------------------------------------------------------------------
1 | # let arr;
2 | # arr = [1, 2, 3];
3 | # for (let elem of arr) {
4 | # console.log(elem);
5 | # };
6 |
7 | arr := [1, 2, 3]
8 |
9 | for elem of arr
10 | console.log elem
11 | end
12 |
--------------------------------------------------------------------------------
/spec/codegen/misc/bind_operator.pea:
--------------------------------------------------------------------------------
1 | # function length() {
2 | # return this.length;
3 | # };
4 | # console.log(length.call("abc"));
5 |
6 | function length
7 | this.length
8 | end
9 |
10 | console.log "abc"::length
11 |
--------------------------------------------------------------------------------
/spec/codegen/object_literals/object_entry_multiline_function.pea:
--------------------------------------------------------------------------------
1 | # let PMath;
2 | # PMath = {
3 | # add(a, b) {
4 | # return a + b;
5 | # }
6 | # };
7 |
8 | PMath := {
9 | function add(a, b)
10 | a + b
11 | end
12 | }
13 |
--------------------------------------------------------------------------------
/spec/codegen/classes/parser_constructor.pea:
--------------------------------------------------------------------------------
1 | # class Parser {
2 | # constructor(name) {
3 | # this.name = name;
4 | # }
5 | # };
6 |
7 | class Parser
8 | function constructor(name)
9 | this.name := name
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/codegen/misc/bind_with_args.pea:
--------------------------------------------------------------------------------
1 | # function length() {
2 | # return this.length;
3 | # };
4 | # console.log(length.call("abc", 10, 20));
5 |
6 | function length
7 | this.length
8 | end
9 |
10 | console.log "abc"::length(10, 20)
11 |
--------------------------------------------------------------------------------
/spec/codegen/for_loop/for_in.pea:
--------------------------------------------------------------------------------
1 | # let obj;
2 | # obj = { a: 10, b: "str" };
3 | # for (let key in obj) {
4 | # console.log(key);
5 | # };
6 |
7 | obj := {
8 | a: 10,
9 | b: "str"
10 | }
11 |
12 | for key in obj
13 | console.log key
14 | end
15 |
--------------------------------------------------------------------------------
/peacock-formatter/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/**
4 | node_modules/**
5 | src/**
6 | .gitignore
7 | .yarnrc
8 | webpack.config.js
9 | vsc-extension-quickstart.md
10 | **/tsconfig.json
11 | **/.eslintrc.json
12 | **/*.map
13 | **/*.ts
14 |
--------------------------------------------------------------------------------
/spec/codegen/for_loop/deconstruct_for_of.pea:
--------------------------------------------------------------------------------
1 | # let arr;
2 | # arr = [{ num: 10 }, { num: 11 }];
3 | # for (let { num } of arr) {
4 | # console.log(num);
5 | # };
6 |
7 | arr := [{ num: 10 }, { num: 11 }]
8 |
9 | for { num } of arr
10 | console.log(num)
11 | end
12 |
--------------------------------------------------------------------------------
/spec/codegen/schemas/schema_assignment.pea:
--------------------------------------------------------------------------------
1 | # let user;
2 | # const User = { id: s('id') };
3 | # user = { id: 10 };
4 | # user = s.verify(User, user, 'User');
5 | # console.log(user);
6 |
7 | schema User = { id }
8 |
9 | user := { id: 10 }
10 | User(user) := user
11 | console.log user
12 |
--------------------------------------------------------------------------------
/peacock-formatter/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to the "peacock-formatter" extension will be documented in this file.
4 |
5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
6 |
7 | ## [Unreleased]
8 |
9 | - Initial release
--------------------------------------------------------------------------------
/spec/codegen/if/basic.pea:
--------------------------------------------------------------------------------
1 | # if (10[Symbol.peacock_equals](11)) {
2 | # console.log("true");
3 | # } else if (false) {
4 | #
5 | # } else {
6 | # console.log("false");
7 | # };
8 |
9 | if 10 == 11
10 | console.log "true"
11 | else if false
12 |
13 | else
14 | console.log "false"
15 | end
16 |
--------------------------------------------------------------------------------
/spec/codegen/array/append.pea:
--------------------------------------------------------------------------------
1 | # function append(item) {
2 | # if (!(this instanceof Array)) throw new MatchError('Expected `this` to be a `Array`');
3 | # return [...this, item];
4 | # };
5 | # console.log(append.call([1], 10));
6 |
7 | function Array::append(item) = [...this, item]
8 |
9 | console.log [1]::append(10)
10 |
--------------------------------------------------------------------------------
/spec/codegen/schemas/schema_named_captures.pea:
--------------------------------------------------------------------------------
1 | # let user;
2 | # const User = { id: s('id'), user: s('id') };
3 | # user = { id: 10, user: 10 };
4 | # user = s.verify(User, user, 'User');
5 | # console.log(user);
6 |
7 | schema User = { id, user: :id }
8 | user := { id: 10, user: 10 }
9 | User(user) := user
10 | console.log user
11 |
--------------------------------------------------------------------------------
/spec/codegen/range/times.pea:
--------------------------------------------------------------------------------
1 | # function times(callback) {
2 | # for (let item of this) {
3 | # callback(item);
4 | # };
5 | # };
6 | # times.call(new Range(0, 100), (_it => console.log(_it)));
7 |
8 | function times(callback)
9 | for item of this
10 | callback item
11 | end
12 | end
13 |
14 | 0..100::times(#{
15 | console.log %
16 | })
17 |
--------------------------------------------------------------------------------
/spec/codegen/range/times_1.pea:
--------------------------------------------------------------------------------
1 | # function times(callback) {
2 | # for (let item of this) {
3 | # callback(item);
4 | # };
5 | # };
6 | # times.call(new Range(0, 100), (_it => console.log(_it)));
7 |
8 | function times(callback)
9 | for item of this
10 | callback item
11 | end
12 | end
13 |
14 | 0..100::times #{
15 | console.log %
16 | }
17 |
--------------------------------------------------------------------------------
/spec/codegen/schemas/int_schema.pea:
--------------------------------------------------------------------------------
1 | # const Ten = 10;
2 | # const Eleven = 11;
3 | # function add(a, b) {
4 | # a = s.verify(Ten, a);
5 | # b = s.verify(Eleven, b);
6 | # return a + b;
7 | # };
8 | # console.log(add(10, 11));
9 |
10 | schema Ten = 10
11 | schema Eleven = 11
12 |
13 | function add(Ten(a), Eleven(b)) = a + b
14 |
15 | console.log add(10, 11)
16 |
--------------------------------------------------------------------------------
/spec/codegen/range/times_named.pea:
--------------------------------------------------------------------------------
1 | # function times(callback) {
2 | # for (let item of this) {
3 | # callback(item);
4 | # };
5 | # };
6 | # times.call(new Range(0, 100), (num => console.log(num)));
7 |
8 | function times(callback)
9 | for item of this
10 | callback item
11 | end
12 | end
13 |
14 | 0..100::times #{ |num|
15 | console.log num
16 | }
17 |
--------------------------------------------------------------------------------
/spec/codegen/when/no_expr.pea:
--------------------------------------------------------------------------------
1 | # if (true && false) {
2 | # console.log("true");
3 | # } else if (false || true && false) {
4 | # console.log("false");
5 | # } else {
6 | # console.log("else");
7 | # };
8 |
9 | case
10 | when true && false
11 | console.log("true")
12 | when false || true && false
13 | console.log("false")
14 | else
15 | console.log("else")
16 | end
17 |
--------------------------------------------------------------------------------
/lib/utils.rb:
--------------------------------------------------------------------------------
1 | class AssertionError < RuntimeError
2 | end
3 |
4 | class NotImplemented < AssertionError
5 | end
6 |
7 | def assert(&block)
8 | raise AssertionError unless yield
9 | end
10 |
11 | def assert_not_reached!
12 | raise AssertionError
13 | end
14 |
15 | def not_implemented!(&block)
16 | if block
17 | block.call
18 | end
19 | raise NotImplemented
20 | end
21 |
--------------------------------------------------------------------------------
/.solargraph.yml:
--------------------------------------------------------------------------------
1 | ---
2 | include:
3 | - "**/*.rb"
4 | exclude:
5 | - spec/**/*
6 | - test/**/*
7 | - vendor/**/*
8 | - ".bundle/**/*"
9 | require: []
10 | domains: []
11 | reporters:
12 | - rubocop
13 | - require_not_found
14 | formatter:
15 | rubocop:
16 | cops: safe
17 | except: []
18 | only: []
19 | extra_args: []
20 | require_paths: []
21 | plugins: []
22 | max_files: 5000
23 |
--------------------------------------------------------------------------------
/spec/codegen/range/to_a.pea:
--------------------------------------------------------------------------------
1 | # let a;
2 | # function to_a() {
3 | # return Array.from(this);
4 | # };
5 | # a = new Range(1, 5);
6 | # console.log(a);
7 | # console.log(to_a.call(a.filter((x) => x > 3)));
8 | # console.log(to_a.call(a));
9 |
10 | function to_a = Array.from this
11 |
12 | a := 1..5
13 |
14 | console.log a
15 | console.log a.filter(x => x > 3)::to_a
16 | console.log a::to_a
17 |
--------------------------------------------------------------------------------
/spec/codegen/misc/in_and_eq.pea:
--------------------------------------------------------------------------------
1 | # let map;
2 | # map = new Map([["a", [1, 2, { a: 11 }]]]);
3 | # console.log(map[Symbol.peacock_contains](["a", [1, 2, { a: 11 }]]));
4 | # console.log(new Map([["a", [1, 2, { a: 11 }]]])[Symbol.peacock_equals](map));
5 |
6 | map := new Map([
7 | ["a", [1, 2, { a: 11 }]]
8 | ])
9 |
10 | console.log ["a", [1, 2, { a: 11 }]] in map
11 | console.log new Map([["a", [1, 2, { a: 11 }]]]) == map
12 |
--------------------------------------------------------------------------------
/examples/misc/VideoLibrary.pea:
--------------------------------------------------------------------------------
1 | schema VideoWithViews = Video({ views: #{ it >= 100 } })
2 |
3 | component { videos }
4 | a := 10
5 | when { videos: [] }
6 |
7 | ..upload a video!
8 |
9 | when { videos: [ VideoWithViews({ views }) ] }
10 |
11 | Congrats on your first video! It reached over {views}
12 |
13 | when videos.length > 10
14 |
15 | 10 Videos!
16 |
17 | else
18 |
19 | end
--------------------------------------------------------------------------------
/spec/codegen/schemas/schema_await.pea:
--------------------------------------------------------------------------------
1 | # let request, todo;
2 | # const Todo = { id: s('id'), userId: s('id') };
3 | # request = await fetch("https://jsonplaceholder.typicode.com/todos/1");
4 | # todo = s.verify(Todo, await request.json(), 'Todo');
5 | # console.log(todo);
6 |
7 | schema Todo = { id, userId: :id }
8 |
9 | request := await fetch("https://jsonplaceholder.typicode.com/todos/1")
10 | Todo(todo) := await request.json()
11 |
12 | console.log todo
13 |
--------------------------------------------------------------------------------
/lib/pea_std_lib/string.pea:
--------------------------------------------------------------------------------
1 | class StringScanner(@str)
2 | index := 0
3 | matched := null
4 | groups := []
5 |
6 | get rest_of_str = this.str.slice(this.index)
7 | get is_end_of_str = this.index >= this.str.length
8 |
9 | function scan(regex)
10 | result := this.rest_of_str.match(regex)
11 | return false if result === null || result.index !== 0
12 | [this.matched, ...this.groups] := Array.from(result)
13 | this.index += this.matched.length
14 | return true
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/spec/codegen/case_function/fib.pea:
--------------------------------------------------------------------------------
1 | # function fib(...args) {
2 | # if (s.check([0], args)) {
3 | # return 0;
4 | # } else if (s.check([1], args)) {
5 | # return 1;
6 | # } else if (s.check([s('n')], args)) {
7 | # let n = args[0];
8 | # return fib(n - 1) + fib(n - 2);
9 | # } else throw new MatchError();
10 | # };
11 | # console.log(fib(14));
12 |
13 | case function fib
14 | when (0)
15 | 0
16 | when (1)
17 | 1
18 | when (n)
19 | fib(n - 1) + fib(n - 2)
20 | end
21 |
22 | console.log fib(14)
23 |
--------------------------------------------------------------------------------
/format:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # IK this sucks, but just to get things working for now
3 | $LOAD_PATH.push "/Users/marcelrusu/Documents/Projects/peacock/lib"
4 |
5 | require "lexer"
6 | require "parser"
7 | require "formatter"
8 |
9 | content = File.read(ARGV[0])
10 |
11 | tokens = Lexer::tokenize(content)
12 | # # puts ast
13 | # # begin
14 | parser = Parser.new(tokens, content)
15 | File.write ARGV[0], Formatter.new(parser.parse!).eval
16 | # rescue Exception => e
17 | # puts e.stack
18 | # puts "---- FAILED ----"
19 | # end
20 |
--------------------------------------------------------------------------------
/spec/codegen/classes/parser.pea:
--------------------------------------------------------------------------------
1 | # class Parser {
2 | # constructor(tokens, program_string, pos) {
3 | # this.tokens = tokens;
4 | # this.program_string = program_string;
5 | # this.pos = pos;
6 | # }
7 | # static from(that) {
8 | # return new this(that.tokens, that.program_string, that.pos);
9 | # }
10 | # };
11 |
12 | class Parser
13 | function constructor(@tokens, @program_string, @pos)
14 |
15 | static function from(that)
16 | new this(that.tokens, that.program_string, that.pos)
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/peacock-formatter/src/test/suite/extension.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 |
3 | // You can import and use all API from the 'vscode' module
4 | // as well as import your extension to test it
5 | import * as vscode from 'vscode';
6 | // import * as myExtension from '../../extension';
7 |
8 | suite('Extension Test Suite', () => {
9 | vscode.window.showInformationMessage('Start all tests.');
10 |
11 | test('Sample test', () => {
12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5));
13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0));
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/examples/todo_mvc/Form.pea:
--------------------------------------------------------------------------------
1 | component { items }
2 | new_item := ""
3 | in
4 |
14 | with
15 |
24 | end
25 |
--------------------------------------------------------------------------------
/spec/codegen/classes/token.pea:
--------------------------------------------------------------------------------
1 | # class Token {
2 | # constructor(raw_token, type, value = null, captures = null) {
3 | # this.raw_token = raw_token;
4 | # this.type = type;
5 | # this.value = value;
6 | # this.captures = captures;
7 | # }
8 | # };
9 | # class Lexer {
10 | # constructor(str) {
11 | # this.str = str;
12 | # }
13 | # tokenize() {
14 | #
15 | # }
16 | # };
17 |
18 |
19 | class Token
20 | function constructor(@raw_token, @type, @value = null, @captures = null)
21 | end
22 |
23 | class Lexer
24 | function constructor(@str)
25 |
26 | function tokenize
27 |
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/peacock-formatter/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "plugins": [
9 | "@typescript-eslint"
10 | ],
11 | "rules": {
12 | "@typescript-eslint/naming-convention": "warn",
13 | "@typescript-eslint/semi": "warn",
14 | "curly": "warn",
15 | "eqeqeq": "warn",
16 | "no-throw-literal": "warn",
17 | "semi": "off"
18 | },
19 | "ignorePatterns": [
20 | "out",
21 | "dist",
22 | "**/*.d.ts"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/spec/codegen/case_function/factorial.pea:
--------------------------------------------------------------------------------
1 | # const Gt1 = (_it => _it > 1);
2 | # function factorial(...args) {
3 | # if (s.check([Gt1], args)) {
4 | # let n = args[0];
5 | # return factorial(n - 1) + factorial(n - 2);
6 | # } else if (s.check([1], args)) {
7 | # return 1;
8 | # } else if (s.check([0], args)) {
9 | # return 0;
10 | # } else throw new MatchError();
11 | # };
12 | # console.log(factorial(20));
13 |
14 | schema Gt1 = #{ % > 1 }
15 |
16 | case function factorial
17 | when (Gt1(n))
18 | factorial(n - 1) + factorial(n - 2)
19 | when (1)
20 | 1
21 | when (0)
22 | 0
23 | end
24 |
25 | console.log factorial(20)
26 |
--------------------------------------------------------------------------------
/spec/codegen/classes/static_from.pea:
--------------------------------------------------------------------------------
1 | # class Parser {
2 | # static from(that) {
3 | # return new this(that.tokens, that.program_string, that.pos);
4 | # }
5 | # constructor(tokens, program_string, pos) {
6 | # this.tokens = tokens;
7 | # this.program_string = program_string;
8 | # this.pos = pos;
9 | # }
10 | # };
11 |
12 | class Parser
13 | static function from(that)
14 | new this(that.tokens, that.program_string, that.pos)
15 | end
16 |
17 | function constructor(tokens, program_string, pos)
18 | this.tokens := tokens
19 | this.program_string := program_string
20 | this.pos := pos
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/peacock-formatter/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES2020",
5 | "lib": [
6 | "ES2020"
7 | ],
8 | "sourceMap": true,
9 | "rootDir": "src",
10 | "strict": true /* enable all strict type-checking options */
11 | /* Additional Checks */
12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
15 | },
16 | "exclude": [
17 | "node_modules",
18 | ".vscode-test"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/spec/codegen/case_function/to_a.pea:
--------------------------------------------------------------------------------
1 | # function to_a(...args) {
2 | # if (s.check(Array, this) && s.check([], args)) {
3 | # return this;
4 | # } else if (s.check(String, this) && s.check([], args)) {
5 | # return Array.from(this);
6 | # } else if (s.check(Object, this) && s.check([], args)) {
7 | # return Object.entries(this);
8 | # } else throw new MatchError();
9 | # };
10 | # console.log(to_a.call({ a: 1 }));
11 | # console.log(to_a.call("abc"));
12 |
13 | case function to_a
14 | when Array::()
15 | this
16 | when String::()
17 | Array.from this
18 | when Object::()
19 | Object.entries this
20 | end
21 |
22 | console.log { a: 1 }::to_a
23 | console.log "abc"::to_a
24 |
--------------------------------------------------------------------------------
/snapshot.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | $LOAD_PATH.push "./lib"
4 |
5 | require "lexer"
6 | require "parser"
7 |
8 | content = File.read(ARGV[0])
9 | tokens = Lexer::tokenize(content)
10 | ast = Parser.new(tokens, content).parse!
11 | ast = ast.map(&:to_h)
12 | spec = <<-EOS
13 | it "#{Time::now}" do
14 | ast = parse('#{content}')
15 | expect(ast).to eq(#{ast})
16 | end
17 | EOS
18 |
19 | SNAPSHOT_SPEC_FILENAME = "spec/snapshot_spec.rb"
20 |
21 | *file, end_line = File.read(SNAPSHOT_SPEC_FILENAME)
22 | .split("\n")
23 |
24 | throw "wtf" if end_line != "end"
25 |
26 | new_file = (file + spec.split("\n") + [end_line]).join("\n")
27 |
28 | File.write(SNAPSHOT_SPEC_FILENAME, new_file)
29 |
--------------------------------------------------------------------------------
/examples/todo_mvc/Page.pea:
--------------------------------------------------------------------------------
1 | import { Routes, Route } from "router"
2 | import List from "./List"
3 | import Form from "./Form"
4 | import TodoList from "./ActiveList"
5 | import TodoList from "./CompleteList"
6 |
7 | component
8 | items := [{ completed: false, desc: "finish app" }]
9 | in
10 |
19 |
26 | end
27 |
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peacock",
3 | "version": "0.0.1",
4 | "description": "A joyful language aimed towards writing front end applications",
5 | "directories": {
6 | "lib": "lib"
7 | },
8 | "scripts": {
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/marcellerusu/peacock-lang.git"
14 | },
15 | "author": "",
16 | "license": "ISC",
17 | "bugs": {
18 | "url": "https://github.com/marcellerusu/peacock-lang/issues"
19 | },
20 | "homepage": "https://github.com/marcellerusu/peacock-lang#readme",
21 | "dependencies": {
22 | "colors": "1.4.0",
23 | "express": "4.17.2",
24 | "jsdom": "^19.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spec/codegen/array/group_by.pea:
--------------------------------------------------------------------------------
1 | # let arr;
2 | # function group_by(key) {
3 | # if (!(this instanceof Array)) throw new MatchError('Expected `this` to be a `Array`');
4 | # let result;
5 | # result = {};
6 | # for (let obj of this) {
7 | # result[obj[key]] ||= [];
8 | # result[obj[key]].push(obj);
9 | # };
10 | # return result;
11 | # };
12 | # arr = [{ id: 1, value: 10 }, { id: 1, value: 10 }];
13 | # console.log(group_by.call(arr, "id"));
14 |
15 | function Array::group_by(key)
16 | result := {}
17 | for obj of this
18 | result[obj[key]] ||= []
19 | result[obj[key]].push(obj)
20 | end
21 |
22 | return result
23 | end
24 |
25 | arr := [{ id: 1, value: 10 }, { id: 1, value: 10 }]
26 |
27 | console.log arr::group_by "id"
28 |
--------------------------------------------------------------------------------
/peacock-formatter/src/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | import { runTests } from '@vscode/test-electron';
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../');
10 |
11 | // The path to test runner
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, './suite/index');
14 |
15 | // Download VS Code, unzip it and run the integration test
16 | await runTests({ extensionDevelopmentPath, extensionTestsPath });
17 | } catch (err) {
18 | console.error('Failed to run tests');
19 | process.exit(1);
20 | }
21 | }
22 |
23 | main();
24 |
--------------------------------------------------------------------------------
/examples/todo_mvc/List.pea:
--------------------------------------------------------------------------------
1 | import { update } from "peacock"
2 |
3 | import { items } from "./items"
4 |
5 | component
6 | def toggleItemCompleted(id)
7 | $items := for $items of
8 | { id: id } as item => { ...item, completed: !item.completed },
9 | else item => item
10 | end
11 | end
12 | newItem := ""
13 | in
14 |
15 |
16 |
17 | #for { completed, desc, id } of $items
18 | - toggleItemCompleted id}
21 | >
22 | {desc}
23 |
24 | end
25 |
26 |
27 |
28 |
33 | end
34 |
--------------------------------------------------------------------------------
/lib/pea_std_lib/array.pea:
--------------------------------------------------------------------------------
1 | function each(callback)
2 | for item of this
3 | callback item
4 | end
5 | end
6 |
7 | function Array::append(item) = [...this, item]
8 |
9 | case function zip
10 | when (first, second)
11 | first.map((x, i) => [x, second[i]])
12 | when Array::(second)
13 | zip this, second
14 | end
15 |
16 | case function to_a
17 | when Array::()
18 | this
19 | when String::()
20 | Array.from this
21 | when Set::()
22 | Array.from this
23 | when Object::()
24 | Object.entries this
25 | end
26 |
27 | function Array::uniq() =
28 | this.filter((item1, i) => i == this.findIndex(item2 => item1 == item2))
29 |
30 | function Array::group_by(key)
31 | result := {}
32 | for obj of this
33 | result[obj[key]] ||= []
34 | result[obj[key]].push(obj)
35 | end
36 |
37 | return result
38 | end
39 |
--------------------------------------------------------------------------------
/lib/pea_std_lib/range.js:
--------------------------------------------------------------------------------
1 | class Range {
2 | #filter_function;
3 | constructor(from, to, filter = () => true) {
4 | this.from = from;
5 | this.to = to;
6 | this.#filter_function = filter;
7 | }
8 | contains(num) {
9 | return num <= this.to && num >= this.from;
10 | }
11 | map(fn) {
12 | return new Range(fn(this.from), fn(this.to));
13 | }
14 | filter(fn) {
15 | return new Range(this.from, this.to, fn);
16 | }
17 | *[Symbol.iterator]() {
18 | let val = this.from;
19 | while (val <= this.to) {
20 | if (this.#filter_function(val)) {
21 | yield val;
22 | }
23 | val = val[Symbol.peacock_next]();
24 | }
25 | }
26 | [Symbol.peacock_equals](other) {
27 | if (!(other instanceof Range)) return false;
28 | return this.from === other.from && this.to === other.to;
29 | }
30 | [Symbol.peacock_contains](value) {
31 | return this.contains(value);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/peacock-formatter/src/test/suite/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as Mocha from 'mocha';
3 | import * as glob from 'glob';
4 |
5 | export function run(): Promise {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: 'tdd',
9 | color: true
10 | });
11 |
12 | const testsRoot = path.resolve(__dirname, '..');
13 |
14 | return new Promise((c, e) => {
15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
16 | if (err) {
17 | return e(err);
18 | }
19 |
20 | // Add files to the test suite
21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
22 |
23 | try {
24 | // Run the mocha test
25 | mocha.run(failures => {
26 | if (failures > 0) {
27 | e(new Error(`${failures} tests failed.`));
28 | } else {
29 | c();
30 | }
31 | });
32 | } catch (err) {
33 | console.error(err);
34 | e(err);
35 | }
36 | });
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/examples/css_parser/lexer.pea:
--------------------------------------------------------------------------------
1 | class Token
2 | function constructor(@raw_token, @start_pos, @end_pos, @type = raw_token, @value = null, @captures = null)
3 |
4 | function is_a(type) = this.type == type
5 | function is_not_a(type) = !this.is_a(type)
6 | function is_one_of(...types) = types.includes(this.type)
7 | function is_not_one_of(...types) = !this.is_one_of(...types)
8 | end
9 |
10 | class Lexer
11 | function constructor(@str)
12 |
13 | index := 0
14 | get rest_of_str = this.str.slice(this.index)
15 | get is_end_of_str = this.index >= this.str.length
16 |
17 |
18 | function scan(regex)
19 | result := this.rest_of_str.match(new Regex(regex))
20 | if result === null || result.index !== 0
21 | return false
22 | end
23 | this.matched := result[0]
24 | this.index += this.matched.length
25 | return true
26 | end
27 |
28 | function tokenize
29 | case
30 | when this.is_end_of_str
31 | 10
32 | when this.scan("+")
33 | end
34 | end
35 | end
--------------------------------------------------------------------------------
/main.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.push "./lib"
2 |
3 | require "lexer"
4 | require "parser"
5 | require "type_checker"
6 | require "compiler"
7 |
8 | content = File.read(ARGV[0])
9 |
10 | tokens = Lexer::tokenize(content)
11 | # # puts ast
12 | # # begin
13 | parser = Parser.new(tokens, content)
14 |
15 | case ARGV[1]
16 | when "-t"
17 | puts "["
18 | puts " #{tokens.map(&:to_s).join ",\n "}"
19 | puts "]"
20 | when /-a+/
21 | ast = parser.parse!
22 | pp ast.map(&:to_h)
23 | when "-n"
24 | ast = parser.parse!
25 | when "-c"
26 | ast = parser.parse!
27 | ast = TypeChecker.new(ast, content).step!
28 | js = Compiler.new(ast, bundle_std_lib: true).eval
29 | puts js
30 | when "-h"
31 | ast = parser.parse!
32 | js = Compiler.new(ast, bundle_std_lib: true).eval
33 | puts "
34 |
35 |
36 |
37 |
40 |
41 | "
42 | else
43 | ast = parser.parse!
44 | js = Compiler.new(ast, bundle_std_lib: true).eval
45 | puts js
46 | end
47 | # rescue Exception => e
48 | # puts e.stack
49 | # puts "---- FAILED ----"
50 | # end
51 |
--------------------------------------------------------------------------------
/spec/snapshot_spec.rb:
--------------------------------------------------------------------------------
1 | require "ast"
2 | require "parser"
3 | require "compiler"
4 |
5 | def compile(str)
6 | tokens = Lexer::tokenize(str.strip)
7 | ast = Parser.new(tokens, str).parse!
8 | Compiler.new(ast).eval
9 | end
10 |
11 | def entries(dir)
12 | Dir.entries(dir).select { |file| ![".", ".."].include? file }
13 | end
14 |
15 | entries("spec/codegen").each do |category|
16 | context category do
17 | entries("spec/codegen/#{category}").each do |name|
18 | contents = File.read("spec/codegen/#{category}/#{name}")
19 | f = false
20 | i = false
21 | case contents[0..1]
22 | when ":f"
23 | f = true
24 | contents.slice! ":f"
25 | when ":i"
26 | i = true
27 | contents.slice! ":i"
28 | end
29 | begin
30 | it name, f: f, i: i do
31 | output = compile(contents)
32 | expected_output = contents.split("\n")
33 | .select { |line| line.start_with? "#" }
34 | .map { |line| line[2..].rstrip }.join "\n"
35 |
36 | expect(output).to eq(expected_output)
37 | end
38 | rescue
39 | binding.pry
40 | end
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/peacock-formatter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peacock-formatter",
3 | "displayName": "peacock-formatter",
4 | "description": "",
5 | "version": "0.0.1",
6 | "engines": {
7 | "vscode": "^1.64.0"
8 | },
9 | "categories": [
10 | "Other"
11 | ],
12 | "activationEvents": [
13 | "onLanguage:peacock"
14 | ],
15 | "main": "./dist/extension.js",
16 | "contributes": {
17 | "commands": [
18 | {
19 | "command": "peacock-formatter.helloWorld",
20 | "title": "Hello World"
21 | }
22 | ]
23 | },
24 | "scripts": {
25 | "vscode:prepublish": "yarn run package",
26 | "compile": "webpack",
27 | "watch": "webpack --watch",
28 | "package": "webpack --mode production --devtool hidden-source-map",
29 | "compile-tests": "tsc -p . --outDir out",
30 | "watch-tests": "tsc -p . -w --outDir out",
31 | "pretest": "yarn run compile-tests && yarn run compile && yarn run lint",
32 | "lint": "eslint src --ext ts",
33 | "test": "node ./out/test/runTest.js"
34 | },
35 | "devDependencies": {
36 | "@types/vscode": "^1.64.0",
37 | "@types/glob": "^7.1.4",
38 | "@types/mocha": "^9.0.0",
39 | "@types/node": "14.x",
40 | "@typescript-eslint/eslint-plugin": "^5.1.0",
41 | "@typescript-eslint/parser": "^5.1.0",
42 | "eslint": "^8.1.0",
43 | "glob": "^7.1.7",
44 | "mocha": "^9.1.3",
45 | "typescript": "^4.4.4",
46 | "ts-loader": "^9.2.5",
47 | "webpack": "^5.52.1",
48 | "webpack-cli": "^4.8.0",
49 | "@vscode/test-electron": "^1.6.2"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/peacock-formatter/webpack.config.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | 'use strict';
4 |
5 | const path = require('path');
6 |
7 | //@ts-check
8 | /** @typedef {import('webpack').Configuration} WebpackConfig **/
9 |
10 | /** @type WebpackConfig */
11 | const extensionConfig = {
12 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
13 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
14 |
15 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
16 | output: {
17 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
18 | path: path.resolve(__dirname, 'dist'),
19 | filename: 'extension.js',
20 | libraryTarget: 'commonjs2'
21 | },
22 | externals: {
23 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
24 | // modules added here also need to be added in the .vscodeignore file
25 | },
26 | resolve: {
27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
28 | extensions: ['.ts', '.js']
29 | },
30 | module: {
31 | rules: [
32 | {
33 | test: /\.ts$/,
34 | exclude: /node_modules/,
35 | use: [
36 | {
37 | loader: 'ts-loader'
38 | }
39 | ]
40 | }
41 | ]
42 | },
43 | devtool: 'nosources-source-map',
44 | infrastructureLogging: {
45 | level: "log", // enables logging required for problem matchers
46 | },
47 | };
48 | module.exports = [ extensionConfig ];
--------------------------------------------------------------------------------
/examples/parser/parser.pea:
--------------------------------------------------------------------------------
1 | import { Tokens } from "./lexer"
2 |
3 | class Parser
4 | static function from(that)
5 | new this(that.tokens, that.program_string, that.pos)
6 | end
7 |
8 | case function constructor
9 | when (tokens, program_string, pos)
10 | this.tokens := tokens
11 | this.program_string := program_string
12 | this.pos := pos
13 | when (tokens, program_string)
14 | new this(tokens, program_string, 0)
15 | end
16 |
17 | get current_token = this.tokens[this.pos]
18 | get prev_token = this.tokens[this.pos - 1]
19 | get peek_token = this.tokens[this.pos + 1]
20 |
21 | function consume_parser!(parser_klass)
22 | parser := parser_klass.from(this)
23 | expr_n := parser.parse!
24 | this.pos := parser.pos
25 | return expr_n
26 | end
27 |
28 | case function consume!
29 | when (token_type)
30 | assert token_type === this.current_token.type
31 | return this.consume!
32 | else
33 | this.pos += 1
34 | return this.prev_token
35 | end
36 |
37 | function parse!
38 | ProgramParser.from(this).parse!
39 | end
40 | end
41 |
42 | class ProgramParser < Parser
43 | body = []
44 |
45 | ALLOWED_PARSERS = [
46 | SimpleAssignmentParser
47 | ]
48 |
49 | function consume_parser!(parser_klass)
50 | this.body.push super(parser_klass)
51 | end
52 |
53 | function parse!
54 | while this.current_token?.is_not_one_of?("end", "}")
55 | consume_parser! this.ALLOWED_PARSERS.find #{ it.can_parse? this }
56 | end
57 |
58 | return this.body
59 | end
60 | end
61 |
62 | class SimpleAssignmentParser < Parser
63 | static function can_parse?(that)
64 | that.current_token?.type == "identifier" &&
65 | that.peek_type?.type == "assign"
66 | end
67 |
68 | function parse!
69 | id_t := consume! "identifier"
70 | consume! "assign"
71 | expr_n := consume_parser! ExprParser
72 | new AST.SimpleAssignment(
73 | id_t.value,
74 | expr_n,
75 | id_t.start_pos,
76 | id_t.end_pos
77 | )
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | ast (2.4.2)
5 | backport (1.2.0)
6 | benchmark (0.2.0)
7 | diff-lcs (1.4.4)
8 | e2mmap (0.1.0)
9 | jaro_winkler (1.5.4)
10 | kramdown (2.3.1)
11 | rexml
12 | kramdown-parser-gfm (1.1.0)
13 | kramdown (~> 2.0)
14 | nokogiri (1.12.5-x86_64-darwin)
15 | racc (~> 1.4)
16 | opal (1.3.2)
17 | ast (>= 2.3.0)
18 | parser (~> 3.0)
19 | parallel (1.21.0)
20 | parser (3.0.3.2)
21 | ast (~> 2.4.1)
22 | racc (1.6.0)
23 | rainbow (3.0.0)
24 | regexp_parser (2.2.0)
25 | reverse_markdown (2.1.1)
26 | nokogiri
27 | rexml (3.2.5)
28 | rspec (3.10.0)
29 | rspec-core (~> 3.10.0)
30 | rspec-expectations (~> 3.10.0)
31 | rspec-mocks (~> 3.10.0)
32 | rspec-core (3.10.1)
33 | rspec-support (~> 3.10.0)
34 | rspec-expectations (3.10.1)
35 | diff-lcs (>= 1.2.0, < 2.0)
36 | rspec-support (~> 3.10.0)
37 | rspec-mocks (3.10.2)
38 | diff-lcs (>= 1.2.0, < 2.0)
39 | rspec-support (~> 3.10.0)
40 | rspec-support (3.10.3)
41 | rubocop (1.24.0)
42 | parallel (~> 1.10)
43 | parser (>= 3.0.0.0)
44 | rainbow (>= 2.2.2, < 4.0)
45 | regexp_parser (>= 1.8, < 3.0)
46 | rexml
47 | rubocop-ast (>= 1.15.0, < 2.0)
48 | ruby-progressbar (~> 1.7)
49 | unicode-display_width (>= 1.4.0, < 3.0)
50 | rubocop-ast (1.15.1)
51 | parser (>= 3.0.1.1)
52 | ruby-progressbar (1.11.0)
53 | solargraph (0.44.2)
54 | backport (~> 1.2)
55 | benchmark
56 | bundler (>= 1.17.2)
57 | diff-lcs (~> 1.4)
58 | e2mmap
59 | jaro_winkler (~> 1.5)
60 | kramdown (~> 2.3)
61 | kramdown-parser-gfm (~> 1.1)
62 | parser (~> 3.0)
63 | reverse_markdown (>= 1.0.5, < 3)
64 | rubocop (>= 0.52)
65 | thor (~> 1.0)
66 | tilt (~> 2.0)
67 | yard (~> 0.9, >= 0.9.24)
68 | thor (1.1.0)
69 | tilt (2.0.10)
70 | unicode-display_width (2.1.0)
71 | webrick (1.7.0)
72 | yard (0.9.27)
73 | webrick (~> 1.7.0)
74 |
75 | PLATFORMS
76 | universal-darwin-21
77 | x86_64-darwin-20
78 |
79 | DEPENDENCIES
80 | opal
81 | rspec (~> 3.10)
82 | solargraph
83 |
84 | BUNDLED WITH
85 | 2.2.22
86 |
--------------------------------------------------------------------------------
/peacock-formatter/src/extension.ts:
--------------------------------------------------------------------------------
1 | // The module 'vscode' contains the VS Code extensibility API
2 | // Import the module and reference it with the alias vscode in your code below
3 | import * as vscode from "vscode";
4 | import { exec } from "child_process";
5 |
6 | // this method is called when your extension is activated
7 | // your extension is activated the very first time the command is executed
8 | export function activate(context: vscode.ExtensionContext) {
9 | // Use the console to output diagnostic information (console.log) and errors (console.error)
10 | // This line of code will only be executed once when your extension is activated
11 | console.log(
12 | 'Congratulations, your extension "peacock-formatter" is now active!'
13 | );
14 |
15 | // The command has been defined in the package.json file
16 | // Now provide the implementation of the command with registerCommand
17 | // The commandId parameter must match the command field in package.json
18 | let disposable = vscode.commands.registerCommand(
19 | "peacock-formatter.helloWorld",
20 | (e) => {
21 | // vscode.debug.activeDebugConsole.append("wdtf");
22 | // console.log(e);
23 | // if (e.fileName.indexOf(".pea") + 4 !== e.fileName.length) return;
24 | // The code you place here will be executed every time your command is executed
25 | // Display a message box to the user
26 |
27 | var onSave = vscode.workspace.onDidSaveTextDocument(
28 | (e: vscode.TextDocument) => {
29 | // execute some child process on save
30 | const editor = vscode.window.activeTextEditor;
31 | if (!editor) return;
32 | const position = editor?.selection;
33 | var child = exec("format " + e.fileName);
34 | child.stdout?.on("data", (data) => {
35 | vscode.window.showInformationMessage(data);
36 | editor.selection = position;
37 | });
38 | child.stderr?.on("data", (data) => {
39 | vscode.window.showErrorMessage(data);
40 | });
41 | }
42 | );
43 | context.subscriptions.push(onSave);
44 | vscode.window.showInformationMessage(
45 | "Hello World from peacock-formatter!"
46 | );
47 | }
48 | );
49 |
50 | context.subscriptions.push(disposable);
51 | }
52 |
53 | // this method is called when your extension is deactivated
54 | export function deactivate() {}
55 |
--------------------------------------------------------------------------------
/peacock-formatter/README.md:
--------------------------------------------------------------------------------
1 | # peacock-formatter README
2 |
3 | This is the README for your extension "peacock-formatter". After writing up a brief description, we recommend including the following sections.
4 |
5 | ## Features
6 |
7 | Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
8 |
9 | For example if there is an image subfolder under your extension project workspace:
10 |
11 | \!\[feature X\]\(images/feature-x.png\)
12 |
13 | > Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
14 |
15 | ## Requirements
16 |
17 | If you have any requirements or dependencies, add a section describing those and how to install and configure them.
18 |
19 | ## Extension Settings
20 |
21 | Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
22 |
23 | For example:
24 |
25 | This extension contributes the following settings:
26 |
27 | * `myExtension.enable`: enable/disable this extension
28 | * `myExtension.thing`: set to `blah` to do something
29 |
30 | ## Known Issues
31 |
32 | Calling out known issues can help limit users opening duplicate issues against your extension.
33 |
34 | ## Release Notes
35 |
36 | Users appreciate release notes as you update your extension.
37 |
38 | ### 1.0.0
39 |
40 | Initial release of ...
41 |
42 | ### 1.0.1
43 |
44 | Fixed issue #.
45 |
46 | ### 1.1.0
47 |
48 | Added features X, Y, and Z.
49 |
50 | -----------------------------------------------------------------------------------------------------------
51 | ## Following extension guidelines
52 |
53 | Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension.
54 |
55 | * [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)
56 |
57 | ## Working with Markdown
58 |
59 | **Note:** You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
60 |
61 | * Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux)
62 | * Toggle preview (`Shift+CMD+V` on macOS or `Shift+Ctrl+V` on Windows and Linux)
63 | * Press `Ctrl+Space` (Windows, Linux) or `Cmd+Space` (macOS) to see a list of Markdown snippets
64 |
65 | ### For more information
66 |
67 | * [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
68 | * [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
69 |
70 | **Enjoy!**
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Not Active Project
2 |
3 | Peacock was a language I designed when I observed a lot of code like this.
4 |
5 | ```jsx
6 | function PersonalYouTubeChannelLibrary() {
7 | let [videos, setVideos] = useState(null);
8 | let [error, setError] = useState(null);
9 | useEffect(() => {
10 | fetch(API.videos)
11 | .then((r) => r.json())
12 | .then(setVideos)
13 | .catch(setError);
14 | }, []);
15 | if (error) {
16 | return You got an error :(
;
17 | } else if (!videos) {
18 | return You Have no Videos yet! Make one
;
19 | } else if (videos.length === 1) {
20 | if (firstTimeViewingChannelSinceCreated(videos[0]))
21 | return Congrats on creating your first video
;
22 | } else if (videos.length >= 50) {
23 | return Congrats on 50 videos, considering upgrading to X tier
;
24 | } else {
25 | return here are your videos
;
26 | }
27 | }
28 | ```
29 |
30 | Expressing complex states in vanilla js is hard.
31 |
32 | At the same time, I did some small projects in Elixir and I was blown away by the expressive power of pattern matching.
33 |
34 | I also read up a fair bit on clojure.spec, and the ideas of "patterns" as data. I thought, that's where peacock came out of:
35 |
36 | The power of clojure's pattern as data, inspired by the usability from elixir and the syntax from typescript.
37 |
38 | Patterns are expressive, but if they are not data, they cannot be reused.
39 |
40 | Peacock patterns are simple data structures, and you can store patterns in variables and create new patterns from combining old patterns.
41 |
42 | Here's some peacock code
43 |
44 | ```
45 | schema User = {name, age}
46 |
47 | User(me) := {name: "marcelle", age: 25}
48 | ```
49 |
50 | Schemas are pattern definitions, and what we were doing here was creating a variable 'me' who was validated by 'User'.
51 |
52 | You could also do this
53 |
54 | ```
55 | schema Teenager = User & {age: #{ it < 20 }}
56 |
57 | Teenager(me) := me # throws error
58 | ```
59 |
60 | We've combined the User schema with a new pattern, and peacock holds me accountable in pretending to be a hip teen.
61 |
62 | The goal of peacock was to take patterns and make them approachable to js/ts folks.
63 |
64 | The aspirations for peacock was to be a full blown frontend framework, but this I think was the ultimately the downfall, it tried to do too much.
65 |
66 | Another flaw I found with this system is that pattern matching is fundamentally limited, it developed closed systems that can not be extended from the outside.
67 |
68 | There are many times this is great - state machines for example, but as a basis for a language it is far too limited.
69 |
70 | To see more recent work, check out my other language coil-lang.
71 |
--------------------------------------------------------------------------------
/peacock-formatter/vsc-extension-quickstart.md:
--------------------------------------------------------------------------------
1 | # Welcome to your VS Code Extension
2 |
3 | ## What's in the folder
4 |
5 | * This folder contains all of the files necessary for your extension.
6 | * `package.json` - this is the manifest file in which you declare your extension and command.
7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.
8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command.
9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
11 |
12 | ## Setup
13 |
14 | * install the recommended extensions (amodio.tsl-problem-matcher and dbaeumer.vscode-eslint)
15 |
16 |
17 | ## Get up and running straight away
18 |
19 | * Press `F5` to open a new window with your extension loaded.
20 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
21 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension.
22 | * Find output from your extension in the debug console.
23 |
24 | ## Make changes
25 |
26 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
27 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
28 |
29 |
30 | ## Explore the API
31 |
32 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
33 |
34 | ## Run tests
35 |
36 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`.
37 | * Press `F5` to run the tests in a new window with your extension loaded.
38 | * See the output of the test result in the debug console.
39 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder.
40 | * The provided test runner will only consider files matching the name pattern `**.test.ts`.
41 | * You can create folders inside the `test` folder to structure your tests any way you want.
42 |
43 | ## Go further
44 |
45 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
46 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace.
47 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
48 |
--------------------------------------------------------------------------------
/lib/formatter.rb:
--------------------------------------------------------------------------------
1 | require "ast"
2 |
3 | class Formatter
4 | attr_reader :context
5 |
6 | def initialize(ast, context = Context.new, indentation = 0)
7 | @ast = ast
8 | @context = context
9 | @indentation = indentation
10 | end
11 |
12 | # maybe take these methods out into an `Indenter` class
13 | def indent!
14 | @indentation += 2
15 | end
16 |
17 | def peek_indent
18 | @indentation + 2
19 | end
20 |
21 | def indentation
22 | " " * @indentation
23 | end
24 |
25 | def dedent!
26 | assert { @indentation >= 2 }
27 | @indentation -= 2
28 | end
29 |
30 | def last_node?
31 | @is_last_node ||= false
32 | end
33 |
34 | def last_node!
35 | @is_last_node = true
36 | end
37 |
38 | def eval
39 | output = ""
40 | index = 0
41 | for node in @ast
42 | last_node! if index == @ast.size - 1
43 | result = indentation + eval_node(node) + "\n"
44 | output += result unless result.strip.empty?
45 | index += 1
46 | end
47 | output.strip! if context.directly_in_a?(:short_fn)
48 | output
49 | end
50 |
51 | def eval_node(node)
52 | case node
53 | when AST::Assign
54 | eval_assign node
55 | when AST::Int
56 | eval_int node
57 | when AST::List
58 | eval_list node
59 | when AST::ObjectLiteral
60 | eval_object_literal node
61 | when AST::OpCall
62 | eval_op_call node
63 | when AST::ShortFn
64 | eval_short_fn node
65 | when AST::Fn
66 | eval_anon_fn node
67 | when AST::Return
68 | eval_return node
69 | when AST::IdLookup
70 | eval_id_lookup node
71 | else
72 | pp node.class
73 | assert_not_reached
74 | end
75 | end
76 |
77 | def eval_assign(node)
78 | return "" if node.name == "pea_module"
79 | "#{node.name} := #{eval_node node.expr}"
80 | end
81 |
82 | def eval_int(node)
83 | node.value
84 | end
85 |
86 | def eval_list(node)
87 | "[#{node.value.map { |n| eval_node n }.join ", "}]"
88 | end
89 |
90 | def eval_record(node)
91 | r = "{"
92 | context.push! :record
93 | node.value.map do |key, value|
94 | r += " #{eval_node key}: #{eval_node value} "
95 | end
96 | context.pop! :record
97 | r + "}"
98 | end
99 |
100 | def eval_id_lookup(node)
101 | return "%" if node.value == ANON_SHORTHAND_ID
102 | node.value
103 | end
104 |
105 | def eval_return(node)
106 | return_val = eval_node node.value
107 | if context.directly_in_a?(:short_fn) || (last_node? && context.directly_in_a?(:anon_fn))
108 | return_val
109 | else
110 | "return #{return_val}"
111 | end
112 | end
113 |
114 | def eval_short_fn(node)
115 | '#{ ' + Formatter.new(node.body, context.push(:short_fn)).eval + " }"
116 | end
117 |
118 | def eval_anon_fn(node)
119 | fn = "do |#{node.args.join(", ").strip}|\n"
120 | fn += Formatter.new(node.body, context.push(:anon_fn), peek_indent).eval
121 | fn += "end"
122 | end
123 |
124 | def eval_op_call(node)
125 | rhs = node.args.first
126 | lhs = node.expr.lhs_expr
127 | method = node.expr.property
128 |
129 | "#{eval_node lhs} #{METHOD_TO_OP[method]} #{eval_node rhs}"
130 | end
131 | end
132 |
--------------------------------------------------------------------------------
/lib/pea_std_lib/symbols.js:
--------------------------------------------------------------------------------
1 | Symbol.peacock_contains = Symbol("peacock_contains");
2 | Symbol.peacock_equals = Symbol("peacock_equals");
3 | Symbol.peacock_next = Symbol("peacock_next");
4 |
5 | Number.prototype[Symbol.peacock_next] = function () {
6 | return this + 1;
7 | };
8 |
9 | String.prototype[Symbol.peacock_next] = function () {
10 | return String.fromCharCode(this.charCodeAt(0) + 1);
11 | };
12 |
13 | BigInt.prototype[Symbol.peacock_next] = function () {
14 | return this + 1;
15 | };
16 |
17 | Number.prototype[Symbol.peacock_equals] = function (other) {
18 | return this === other;
19 | };
20 |
21 | String.prototype[Symbol.peacock_equals] = function (other) {
22 | return this === other;
23 | };
24 |
25 | BigInt.prototype[Symbol.peacock_equals] = function (other) {
26 | return this === other;
27 | };
28 |
29 | Boolean.prototype[Symbol.peacock_equals] = function (other) {
30 | return this === other;
31 | };
32 |
33 | Date.prototype[Symbol.peacock_equals] = function (other) {
34 | if (!(other instanceof Date)) return false;
35 | return this.valueOf() === other.valueOf();
36 | };
37 |
38 | RegExp.prototype[Symbol.peacock_equals] = function (other) {
39 | return this.source === other.source;
40 | };
41 |
42 | Set.prototype[Symbol.peacock_contains] = function (val) {
43 | if (this.has(val)) return true;
44 | return Array.from(this).some((v) => v[Symbol.peacock_equals](val));
45 | };
46 |
47 | Set.prototype[Symbol.peacock_equals] = function (other) {
48 | if (!(other instanceof Set)) return false;
49 | if (other.size !== this.size) return false;
50 | let entries;
51 |
52 | for (let val of this) {
53 | if (other.has(val)) continue;
54 | entries ||= Array.from(other);
55 | if (!entries.some((v) => v === val || v[Symbol.peacock_equals](val)))
56 | return false;
57 | }
58 | return true;
59 | };
60 |
61 | Array.prototype[Symbol.peacock_equals] = function (other) {
62 | if (!(other instanceof Array)) return false;
63 | if (this.length !== other.length) return false;
64 | for (let i = 0; i < this.length; i++) {
65 | if (this[i] === other[i]) continue;
66 | if (!this[i][Symbol.peacock_equals](other[i])) return false;
67 | }
68 | return true;
69 | };
70 |
71 | Array.prototype[Symbol.peacock_contains] = function (value) {
72 | if (this.includes(value)) return true;
73 | for (let _value of this) {
74 | if (_value === value) return true;
75 | if (_value[Symbol.peacock_equals](value)) return true;
76 | }
77 | return false;
78 | };
79 |
80 | Map.prototype[Symbol.peacock_equals] = function (other) {
81 | if (!(other instanceof Map)) return false;
82 | if (this.size !== other.size) return false;
83 | for (let [key, value] of this) {
84 | if (!other.has(key)) return false;
85 | let _value = other.get(key);
86 | if (_value === value) continue;
87 | if (!_value[Symbol.peacock_equals](value)) return false;
88 | }
89 | return true;
90 | };
91 |
92 | Map.prototype[Symbol.peacock_contains] = function ([key, value]) {
93 | if (!this.has(key)) return false;
94 | let _value = this.get(key);
95 | return _value === value || _value[Symbol.peacock_equals](value);
96 | };
97 |
98 | Object.prototype[Symbol.peacock_equals] = function (other) {
99 | if (this.constructor !== Object || other.constructor !== Object)
100 | throw "Not implemented";
101 | if (Object.keys(this).length !== Object.keys(other).length) return false;
102 | for (let [key, value] of Object.entries(this)) {
103 | if (!other[key]) return false;
104 | if (other[key] === value) continue;
105 | if (!other[key][Symbol.peacock_equals](value)) return false;
106 | }
107 | return true;
108 | };
109 |
--------------------------------------------------------------------------------
/lib/pea_std_lib/schema.js:
--------------------------------------------------------------------------------
1 | class ArrayLengthMismatch extends Error {}
2 | class MatchError extends Error {}
3 |
4 | const BUILTIN_CONSTRUCTORS = [
5 | Boolean,
6 | String,
7 | RegExp,
8 | Symbol,
9 | Error,
10 | AggregateError,
11 | EvalError,
12 | RangeError,
13 | ReferenceError,
14 | URIError,
15 | SyntaxError,
16 | TypeError,
17 | Number,
18 | BigInt,
19 | Date,
20 | Function,
21 | Array,
22 | Map,
23 | Set,
24 | WeakMap,
25 | WeakSet,
26 | Promise,
27 | ];
28 |
29 | function isPrimitivePattern(pattern) {
30 | return BUILTIN_CONSTRUCTORS.includes(pattern);
31 | }
32 |
33 | function isCustomClass(pattern) {
34 | return (
35 | pattern?.prototype?.constructor?.toString()?.substring(0, 5) === "class"
36 | );
37 | }
38 |
39 | class Any {
40 | [Symbol.peacock_equals](other) {
41 | return other instanceof Any;
42 | }
43 | }
44 |
45 | class Capture {
46 | constructor(key) {
47 | this.key = key;
48 | }
49 | [Symbol.peacock_equals](other) {
50 | if (!(other instanceof Capture)) return false;
51 | return this.key === other.key;
52 | }
53 | }
54 |
55 | class Union {
56 | constructor(patterns) {
57 | this.patterns = patterns;
58 | }
59 | [Symbol.peacock_equals](other) {
60 | if (!(other instanceof Union)) return false;
61 | return this.patterns[Symbol.peacock_equals](other.patterns);
62 | }
63 | [Symbol.peacock_contains](value) {
64 | return this.patterns[Symbol.peacock_contains](value);
65 | }
66 | [Symbol.iterator]() {
67 | return this.patterns[Symbol.iterator]();
68 | }
69 | }
70 |
71 | function s(key) {
72 | return new Capture(key);
73 | }
74 |
75 | s.checkCapture = function checkCapture(captures, key, value) {
76 | if (captures.has(key)) {
77 | return captures.get(key) === value;
78 | }
79 | captures.set(key, value);
80 | return true;
81 | };
82 |
83 | s.checkObject = function checkObject(pattern, value, captures) {
84 | if (value.constructor !== Object) return false;
85 | for (let key in pattern) {
86 | if (typeof value[key] === "undefined") return false;
87 | if (!s.check(pattern[key], value[key], captures)) return false;
88 | }
89 | return true;
90 | };
91 |
92 | s.checkRegExp = function checkRegExp(pattern, value) {
93 | return pattern.test(value);
94 | };
95 |
96 | s.checkUnion = function checkUnion({ patterns }, value) {
97 | return patterns.some((p) => s.check(p, value));
98 | };
99 |
100 | s.checkFn = function checkFn(patternFn, value) {
101 | return patternFn(value);
102 | };
103 |
104 | const PRIMITIVES = [
105 | "string",
106 | "undefined",
107 | "boolean",
108 | "number",
109 | "bigint",
110 | "string",
111 | "symbol",
112 | ];
113 |
114 | s.checkInstance = function checkInstance(PatternClass, value) {
115 | if (PRIMITIVES.includes(typeof value)) return false;
116 | if (value) return value.constructor === PatternClass;
117 | };
118 |
119 | function _zip(otherArray) {
120 | return this.map((x, i) => [x, otherArray[i]]);
121 | }
122 |
123 | s.checkArray = function checkArray(patternArr, valueArr, captures = new Map()) {
124 | if (patternArr.length !== valueArr.length) return false;
125 | for (let [pattern, value] of _zip.call(patternArr, valueArr)) {
126 | if (!s.check(pattern, value, captures)) return false;
127 | }
128 | return true;
129 | };
130 |
131 | s.any = new Any();
132 |
133 | s.check = function check(pattern, value, captures = new Map()) {
134 | if (pattern instanceof Any) {
135 | return true;
136 | } else if (pattern === null) {
137 | return value === null;
138 | } else if (pattern instanceof Capture) {
139 | return s.checkCapture(captures, pattern.key, value);
140 | } else if (pattern instanceof Union) {
141 | return s.checkUnion(pattern, value);
142 | } else if (pattern instanceof RegExp) {
143 | return s.checkRegExp(pattern, value);
144 | } else if (pattern instanceof Array) {
145 | return s.checkArray(pattern, value, captures);
146 | } else if (pattern.constructor === Object) {
147 | return s.checkObject(pattern, value, captures);
148 | } else if (isPrimitivePattern(pattern) || isCustomClass(pattern)) {
149 | return s.checkInstance(pattern, value);
150 | } else if (typeof pattern === "function") {
151 | return s.checkFn(pattern, value);
152 | } else {
153 | return pattern === value;
154 | }
155 | };
156 |
157 | s.verify = function verify(pattern, value, pattern_name = "RIP") {
158 | if (s.check(pattern, value)) {
159 | return value;
160 | } else {
161 | throw new MatchError(
162 | `
163 | Match Error!
164 | > '${value}' could not conform to '${pattern_name}'
165 | `
166 | );
167 | }
168 | };
169 |
170 | s.union = (...patterns) => new Union(patterns);
171 |
--------------------------------------------------------------------------------
/spec/lexer_spec.rb:
--------------------------------------------------------------------------------
1 | require "lexer"
2 |
3 | describe Lexer, "#tokenize" do
4 | describe "single tokens" do
5 | it "should tokenize variable" do
6 | res = Lexer::tokenize("variable_name!")
7 | expect(res).to eq([
8 | Lexer::Token.new("variable_name!", :identifier, "variable_name!"),
9 | ])
10 | end
11 |
12 | it "true" do
13 | res = Lexer::tokenize("true")
14 | expect(res).to eq([
15 | Lexer::Token.new("true", :bool_lit, true),
16 | ])
17 | end
18 |
19 | it "false" do
20 | res = Lexer::tokenize("false")
21 | expect(res).to eq([Lexer::Token.new("false", :bool_lit, false)])
22 | end
23 |
24 | it "should ignore comments" do
25 | res = Lexer::tokenize("# something-ok = ")
26 | expect(res).to eq([])
27 | end
28 |
29 | it "identifier 'a'" do
30 | res = Lexer::tokenize("a")
31 | expect(res).to eq([
32 | Lexer::Token.new("a", :identifier, "a"),
33 | ])
34 | end
35 |
36 | it "=" do
37 | res = Lexer::tokenize("=")
38 | expect(res).to eq([
39 | Lexer::Token.new("=", :"="),
40 | ])
41 | end
42 |
43 | it ":=" do
44 | res = Lexer::tokenize(":=")
45 | expect(res).to eq([
46 | Lexer::Token.new(":=", :assign),
47 | ])
48 | end
49 |
50 | it "30" do
51 | res = Lexer::tokenize("30")
52 | expect(res).to eq([
53 | Lexer::Token.new("30", :int_lit, 30),
54 | ])
55 | end
56 |
57 | it "30.5" do
58 | res = Lexer::tokenize("30.5")
59 | expect(res).to eq([
60 | Lexer::Token.new("30.5", :float_lit, 30.5),
61 | ])
62 | end
63 |
64 | it "\"string 3.0\"" do
65 | res = Lexer::tokenize("\"string 3.0\"")
66 | expect(res).to eq([
67 | Lexer::Token.new('"string 3.0"', :str_lit, "string 3.0", []),
68 | ])
69 | end
70 |
71 | it "string with space" do
72 | res = Lexer::tokenize(" \"some string\"")
73 |
74 | expect(res).to eq([
75 | Lexer::Token.new(' "some string"', :str_lit, "some string", []),
76 | ])
77 | end
78 |
79 | it "string with no space at start" do
80 | res = Lexer::tokenize("\"some string\"")
81 | expect(res).to eq([
82 | Lexer::Token.new('"some string', :str_lit, "some string", []),
83 | ])
84 | end
85 |
86 | it ":symbol", :i do
87 | res = Lexer::tokenize(":symbol")
88 | expect(res).to eq([
89 | Lexer::Token.new(":symbol", :symbol, "symbol"),
90 | ])
91 | end
92 | end
93 |
94 | describe "single-line" do
95 | it "a := 3" do
96 | res = Lexer::tokenize("a := 3")
97 | expect(res).to eq([
98 | Lexer::Token.new("a", :identifier, "a"),
99 | Lexer::Token.new(":=", :assign),
100 | Lexer::Token.new("3", :int_lit, 3),
101 | ])
102 | end
103 | it "a := 3 === 4.0" do
104 | res = Lexer::tokenize("a := 3 === 4.0")
105 | expect(res).to eq([
106 | Lexer::Token.new("a", :identifier, "a"),
107 | Lexer::Token.new(":=", :assign),
108 | Lexer::Token.new("3", :int_lit, 3),
109 | Lexer::Token.new("===", :"==="),
110 | Lexer::Token.new("4.0", :float_lit, 4.0),
111 | ])
112 | end
113 | it "a := 3 === \"4\"" do
114 | res = Lexer::tokenize("a := 3 === \"4\"")
115 | expect(res).to eq([
116 | Lexer::Token.new("a", :identifier, "a"),
117 | Lexer::Token.new(":=", :assign),
118 | Lexer::Token.new("3", :int_lit, 3),
119 | Lexer::Token.new("===", :"==="),
120 | Lexer::Token.new('"4"', :str_lit, "4", []),
121 | ])
122 | end
123 | describe "array" do
124 | it "[1]" do
125 | res = Lexer::tokenize("[1]")
126 | expect(res).to eq([
127 | Lexer::Token.new("[", :"["),
128 | Lexer::Token.new("1", :int_lit, 1),
129 | Lexer::Token.new("]", :"]"),
130 | ])
131 | end
132 | it "[ 1 ]" do
133 | res = Lexer::tokenize("[ 1 ]")
134 | expect(res).to eq([
135 | Lexer::Token.new("[", :"["),
136 | Lexer::Token.new("1", :int_lit, 1),
137 | Lexer::Token.new("]", :"]"),
138 | ])
139 | end
140 | end
141 | describe "record" do
142 | it "{a: 3}" do
143 | res = Lexer::tokenize("{a: 3}")
144 | expect(res).to eq([
145 | Lexer::Token.new("{", :"{"),
146 | Lexer::Token.new("a", :identifier, "a"),
147 | Lexer::Token.new(":", :colon),
148 | Lexer::Token.new("3", :int_lit, 3),
149 | Lexer::Token.new("}", :"}"),
150 | ])
151 | end
152 | it "{ a : 3, }" do
153 | res = Lexer::tokenize("{ a : 3, }")
154 | expect(res).to eq([
155 | Lexer::Token.new("{", :"{"),
156 | Lexer::Token.new("a", :identifier, "a"),
157 | Lexer::Token.new(":", :colon),
158 | Lexer::Token.new("3", :int_lit, 3),
159 | Lexer::Token.new(",", :comma),
160 | Lexer::Token.new("}", :"}"),
161 | ])
162 | end
163 | end
164 | end
165 | end
166 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # This file was generated by the `rspec --init` command. Conventionally, all
2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3 | # The generated `.rspec` file contains `--require spec_helper` which will cause
4 | # this file to always be loaded, without a need to explicitly require it in any
5 | # files.
6 | #
7 | # Given that it is always loaded, you are encouraged to keep this file as
8 | # light-weight as possible. Requiring heavyweight dependencies from this file
9 | # will add to the boot time of your test suite on EVERY test run, even for an
10 | # individual file that may not need all of that loaded. Instead, consider making
11 | # a separate helper file that requires the additional dependencies and performs
12 | # the additional setup, and require it from the spec files that actually need
13 | # it.
14 | #
15 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
16 | RSpec.configure do |config|
17 | config.filter_run_when_matching :f
18 | config.filter_run_excluding :i
19 | # rspec-expectations config goes here. You can use an alternate
20 | # assertion/expectation library such as wrong or the stdlib/minitest
21 | # assertions if you prefer.
22 | config.expect_with :rspec do |expectations|
23 | # This option will default to `true` in RSpec 4. It makes the `description`
24 | # and `failure_message` of custom matchers include text for helper methods
25 | # defined using `chain`, e.g.:
26 | # be_bigger_than(2).and_smaller_than(4).description
27 | # # => "be bigger than 2 and smaller than 4"
28 | # ...rather than:
29 | # # => "be bigger than 2"
30 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
31 | end
32 |
33 | # rspec-mocks config goes here. You can use an alternate test double
34 | # library (such as bogus or mocha) by changing the `mock_with` option here.
35 | config.mock_with :rspec do |mocks|
36 | # Prevents you from mocking or stubbing a method that does not exist on
37 | # a real object. This is generally recommended, and will default to
38 | # `true` in RSpec 4.
39 | mocks.verify_partial_doubles = true
40 | end
41 |
42 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
43 | # have no way to turn it off -- the option exists only for backwards
44 | # compatibility in RSpec 3). It causes shared context metadata to be
45 | # inherited by the metadata hash of host groups and examples, rather than
46 | # triggering implicit auto-inclusion in groups with matching metadata.
47 | config.shared_context_metadata_behavior = :apply_to_host_groups
48 |
49 | # The settings below are suggested to provide a good initial experience
50 | # with RSpec, but feel free to customize to your heart's content.
51 | =begin
52 | # This allows you to limit a spec run to individual examples or groups
53 | # you care about by tagging them with `:focus` metadata. When nothing
54 | # is tagged with `:focus`, all examples get run. RSpec also provides
55 | # aliases for `it`, `describe`, and `context` that include `:focus`
56 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
57 | config.filter_run_when_matching :focus
58 |
59 | # Allows RSpec to persist some state between runs in order to support
60 | # the `--only-failures` and `--next-failure` CLI options. We recommend
61 | # you configure your source control system to ignore this file.
62 | config.example_status_persistence_file_path = "spec/examples.txt"
63 |
64 | # Limits the available syntax to the non-monkey patched syntax that is
65 | # recommended. For more details, see:
66 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
67 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
68 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
69 | config.disable_monkey_patching!
70 |
71 | # This setting enables warnings. It's recommended, but in some cases may
72 | # be too noisy due to issues in dependencies.
73 | config.warnings = true
74 |
75 | # Many RSpec users commonly either run the entire suite or an individual
76 | # file, and it's useful to allow more verbose output when running an
77 | # individual spec file.
78 | if config.files_to_run.one?
79 | # Use the documentation formatter for detailed output,
80 | # unless a formatter has already been configured
81 | # (e.g. via a command-line flag).
82 | config.default_formatter = "doc"
83 | end
84 |
85 | # Print the 10 slowest examples and example groups at the
86 | # end of the spec run, to help surface which specs are running
87 | # particularly slow.
88 | config.profile_examples = 10
89 |
90 | # Run specs in random order to surface order dependencies. If you find an
91 | # order dependency and want to debug it, you can fix the order by providing
92 | # the seed, which is printed after each run.
93 | # --seed 1234
94 | config.order = :random
95 |
96 | # Seed global randomization in this process using the `--seed` CLI option.
97 | # Setting this allows you to use `--seed` to deterministically reproduce
98 | # test failures related to randomization by passing the same `--seed` value
99 | # as the one that triggered the failure.
100 | Kernel.srand config.seed
101 | =end
102 | end
103 |
--------------------------------------------------------------------------------
/lib/type_checker.rb:
--------------------------------------------------------------------------------
1 | require "ast"
2 | require "utils"
3 |
4 | class Object
5 | def do
6 | yield self
7 | end
8 | end
9 |
10 | module Types
11 | class Type
12 | def to_s
13 | self.class.name.split("::")[1]
14 | end
15 | end
16 |
17 | class Number < Type
18 | end
19 |
20 | class String < Type
21 | end
22 |
23 | class Boolean < Type
24 | end
25 |
26 | class Object < Type
27 | end
28 |
29 | class Any < Type
30 | end
31 |
32 | class Array < Type
33 | attr_reader :type
34 |
35 | def to_s
36 | "Array<#{type}>"
37 | end
38 |
39 | def initialize(type = Any)
40 | @type = type
41 | end
42 | end
43 |
44 | class Undefined < Type
45 | end
46 |
47 | class Function < Type
48 | attr_reader :args_type, :return_type
49 |
50 | def initialize(args_type, return_type)
51 | @args_type = args_type
52 | @return_type = return_type
53 | end
54 | end
55 |
56 | class Tuple < Type
57 | attr_reader :types
58 |
59 | def to_s
60 | "Tuple<#{types.map(&:to_s).join ", "}>"
61 | end
62 |
63 | def initialize(types)
64 | @types = types
65 | end
66 | end
67 |
68 | class Union < Type
69 | attr_reader :types
70 |
71 | def initialize(types)
72 | @types = types
73 | end
74 | end
75 | end
76 |
77 | class TypeChecker
78 | attr_reader :ast, :program_string, :pos, :stack
79 |
80 | def self.from(_self)
81 | self.new(_self.ast, _self.program_string, _self.pos, _self.stack)
82 | end
83 |
84 | def consume_steper!(checker_klass, *step_args)
85 | checker = checker_klass.from(self)
86 | expr_n = checker.step! *step_args
87 | @pos = checker.pos
88 | expr_n
89 | end
90 |
91 | def initialize(ast, program_string, pos = 0, stack = {})
92 | @ast = ast
93 | @program_string = program_string
94 | @pos = pos
95 | @stack = stack
96 | @errors = []
97 | end
98 |
99 | def step!
100 | for i in 0...@ast.size
101 | @ast[i] = consume_steper! StatementChecker, @ast[i]
102 | end
103 | assert_not_reached! if @has_errors
104 | @ast
105 | end
106 |
107 | def ignore!
108 | assert_not_reached!
109 | end
110 |
111 | def pos_to_line_and_col(pos)
112 | line, col, i = 1, 0, 0
113 | @program_string.each_char do |char|
114 | break if i == pos
115 | if char == "\n"
116 | col = 0
117 | line += 1
118 | else
119 | col += 1
120 | end
121 | i += 1
122 | end
123 |
124 | return line, col
125 | end
126 |
127 | def get_line_positions_to_print(node)
128 | previous_line = (@program_string[0..node.pos].rindex("\n") || -1) + 1
129 | previous_line = (@program_string[0...previous_line - 1].rindex("\n")) + 1
130 |
131 | next_line = @program_string[node.pos..].do { |s|
132 | i = s.index("\n")
133 | if i
134 | i + node.pos
135 | else
136 | @program_string.size
137 | end
138 | }
139 |
140 | return previous_line, next_line
141 | end
142 |
143 | def print_error(node, expected, got)
144 | previous_line, next_line = get_line_positions_to_print node
145 | line, col = pos_to_line_and_col node.pos
146 | puts "Type mismatch! [line:#{line}, col:#{col}]"
147 | lines = @program_string[previous_line..next_line].split("\n")
148 | puts "..."
149 | lines.each_with_index do |_line, index|
150 | puts "#{line - (lines.size - index) + 1} | #{_line}"
151 | end
152 | puts " #{" " * col}^ Expected #{expected}, got #{got}"
153 |
154 | abort
155 | end
156 |
157 | def print_failed_id_lookup(node)
158 | previous_line, next_line = get_line_positions_to_print node
159 | line, col = pos_to_line_and_col node.pos
160 |
161 | lines = @program_string[previous_line..next_line].split("\n")
162 | puts "..."
163 | lines.each_with_index do |_line, index|
164 | break if index == lines.size - 1
165 | puts "#{line - (lines.size - index) + 1} | #{_line}"
166 | end
167 | puts "#{line} | #{lines.last}"
168 | puts " #{" " * col}^"
169 | puts "Identifier \"#{node.value}\" not found! [line:#{line}, col:#{col}]"
170 |
171 | abort
172 | end
173 |
174 | def check_type_of_op(node, local_stack)
175 | lhs_type = type_of node.lhs, local_stack
176 | rhs_type = type_of node.rhs, local_stack
177 | op_fn_type = OPERATOR_TYPES[node.type.to_s]
178 |
179 | if !types_match?(op_fn_type.args_type, Types::Tuple.new([lhs_type, rhs_type]))
180 | print_error node, op_fn_type.args_type, Types::Tuple.new([lhs_type, rhs_type])
181 | end
182 |
183 | op_fn_type.return_type
184 | end
185 |
186 | def type_of(node, temp_stack = {})
187 | local_stack = { **@stack, **temp_stack }
188 |
189 | case node
190 | when AST::Int
191 | Types::Number.new
192 | when AST::Float
193 | Types::Number.new
194 | when AST::SimpleString
195 | Types::String.new
196 | when AST::Op
197 | check_type_of_op(node, local_stack)
198 | when AST::IdLookup
199 | if !local_stack[node.value]
200 | print_failed_id_lookup node
201 | end
202 | local_stack[node.value]
203 | else
204 | assert_not_reached!
205 | end
206 | end
207 |
208 | GLOBAL_CONSTRUCTORS = {
209 | "String" => Types::String.new,
210 | "Number" => Types::Number.new,
211 | "Boolean" => Types::Boolean.new,
212 | "Object" => Types::Object.new,
213 | "Array" => Types::Array.new,
214 | }
215 |
216 | CONSOLE_TYPES = {
217 | "log" => Types::Function.new(
218 | Types::Any.new,
219 | Types::Undefined.new
220 | ),
221 | }
222 |
223 | MATH_OPERATOR_TYPE = Types::Function.new(
224 | Types::Tuple.new([
225 | Types::Number.new,
226 | Types::Number.new,
227 | ]),
228 | Types::Number.new
229 | )
230 |
231 | OPERATOR_TYPES = {
232 | "+" => MATH_OPERATOR_TYPE,
233 | "*" => MATH_OPERATOR_TYPE,
234 | "-" => MATH_OPERATOR_TYPE,
235 | "/" => MATH_OPERATOR_TYPE,
236 | "**" => MATH_OPERATOR_TYPE,
237 | }
238 | end
239 |
240 | class StatementChecker < TypeChecker
241 | def step!(node)
242 | case node
243 | when AST::SimpleSchemaAssignment
244 | step_simple_schema_assignment node
245 | when AST::FnCall
246 | step_function_call node
247 | when AST::SimpleAssignment
248 | step_simple_assignment node
249 | when AST::SingleLineDefWithArgs
250 | step_single_line_def_with_args node
251 | else
252 | assert_not_reached!
253 | end
254 | end
255 |
256 | def fn_type_from_native_object(dot_node)
257 | assert { dot_node.lhs.value.is_a? String }
258 | case dot_node.lhs.value
259 | when "console"
260 | CONSOLE_TYPES[dot_node.rhs.value]
261 | end
262 | end
263 |
264 | def function_type(node)
265 | case node
266 | when AST::Dot
267 | fn_type_from_native_object(node)
268 | when AST::IdLookup
269 | @stack[node.value]
270 | else
271 | assert_not_reached!
272 | end
273 | end
274 |
275 | def args_type(args)
276 | arg_types = []
277 | for arg in args
278 | arg_types.push type_of arg
279 | end
280 | Types::Tuple.new arg_types
281 | end
282 |
283 | def type_of_def_args(node_args)
284 | case node_args
285 | when AST::SimpleFnArgs
286 | Types::Tuple.new node_args.value.map { Types::Any.new }
287 | else
288 | assert_not_reached!
289 | end
290 | end
291 |
292 | def infer_type_of(id, expr)
293 | case expr
294 | when AST::Op
295 | type = OPERATOR_TYPES[expr.type.to_s]
296 | if expr.lhs.value == id
297 | type.args_type.types[0]
298 | elsif expr.rhs.value == id
299 | type.args_type.types[1]
300 | else
301 | assert_not_reached!
302 | end
303 | end
304 | end
305 |
306 | def step_single_line_def_with_args(node)
307 | args_type = Types::Tuple.new(
308 | node.args.value.map { |id|
309 | infer_type_of id.name, node.return_value
310 | }
311 | )
312 |
313 | local_stack = node.args.value.map(&:name)
314 | .zip(args_type.types)
315 | .map { |name, type| [name, type] }.to_h
316 | return_type = type_of node.return_value, local_stack
317 |
318 | @stack[node.name] = Types::Function.new(
319 | args_type,
320 | return_type
321 | )
322 | node
323 | end
324 |
325 | def step_function_call(node)
326 | fn_type = function_type node.expr
327 | call_args_type = args_type node.args
328 |
329 | if !types_match?(fn_type.args_type, call_args_type)
330 | print_error node, fn_type.args_type, call_args_type
331 | end
332 |
333 | node
334 | end
335 |
336 | def types_match?(into, from)
337 | return true if into.is_a?(Types::Any) || from.is_a?(Types::Any)
338 |
339 | case into
340 | when Types::Number
341 | from.is_a? Types::Number
342 | when Types::String
343 | from.is_a? Types::String
344 | when Types::Tuple
345 | return false if !from.is_a?(Types::Tuple)
346 | return false if from.types.size != into.types.size
347 | into.types
348 | .zip(from.types)
349 | .all? { |type_a, type_b| types_match? type_a, type_b }
350 | else
351 | binding.pry
352 | assert_not_reached!
353 | end
354 | end
355 |
356 | def step_simple_assignment(node)
357 | rhs_type = type_of node.expr
358 |
359 | assert { rhs_type }
360 |
361 | @stack[node.name] = rhs_type
362 |
363 | node
364 | end
365 |
366 | def step_simple_schema_assignment(node)
367 | schema_type = GLOBAL_CONSTRUCTORS[node.schema_name]
368 | ignore! if !schema_type
369 | rhs_type = type_of node.expr
370 | if !types_match?(rhs_type, schema_type)
371 | print_error node, schema_type, rhs_type
372 | end
373 |
374 | @stack[node.name] = rhs_type
375 |
376 | AST::SimpleAssignment.new(node.name, node.expr)
377 | end
378 | end
379 |
--------------------------------------------------------------------------------
/lib/lexer.rb:
--------------------------------------------------------------------------------
1 | require "strscan"
2 | require "utils"
3 |
4 | module Lexer
5 | class Token
6 | attr_reader :raw_token, :start_pos, :end_pos, :type, :value, :captures
7 | def self.set_position!(pos)
8 | @@current_position = pos
9 | end
10 |
11 | def ==(other)
12 | type == other.type && value == other.value && captures == other.captures
13 | end
14 |
15 | def initialize(raw_token, type, value = nil, captures = nil)
16 | @raw_token = raw_token
17 | @start_pos = @@current_position
18 | @end_pos = @start_pos + @raw_token.size
19 | @type = type
20 | @value = value
21 | @captures = captures
22 | end
23 |
24 | def to_s
25 | s = "{ type: :#{type}"
26 | if value
27 | s += ", value: #{value}"
28 | end
29 | s + " }"
30 | end
31 |
32 | def value=(value)
33 | @value = value
34 | end
35 |
36 | def is?(type)
37 | type == @type
38 | end
39 |
40 | def is_not?(type)
41 | !is?(type)
42 | end
43 |
44 | def is_one_of?(*types)
45 | types.include? @type
46 | end
47 |
48 | def is_not_one_of?(*types)
49 | !is_one_of?(*types)
50 | end
51 | end
52 |
53 | def self.get_escaped_part_of_str(str, offset)
54 | capture_start = i = 0
55 | num_open_braces = 1
56 | while true
57 | case str[i]
58 | when "#"
59 | if str[i + 1] == "{"
60 | assert { false }
61 | end
62 | when "{"
63 | num_open_braces += 1
64 | when "}"
65 | num_open_braces -= 1
66 | if num_open_braces == 0
67 | return { start: offset + capture_start,
68 | end: offset + i,
69 | value: str[capture_start...i] }
70 | end
71 | end
72 | i += 1
73 | assert_not_reached if i >= str.size
74 | end
75 | end
76 |
77 | def self.tokenize(program)
78 | tokens = []
79 | scanner = StringScanner.new(program)
80 | while true
81 | Token.set_position!(scanner.pos)
82 | case
83 | when scanner.eos?
84 | break
85 | when scanner.scan(/\s+/)
86 | next
87 | when scanner.scan(/#\{/)
88 | tokens.push Token.new(scanner.matched, :"#\{")
89 | when scanner.scan(/#([a-zA-Z]+)\b/)
90 | tokens.push Token.new(scanner.matched, :hash_id, scanner.matched[1..])
91 | when scanner.scan(/#.*/)
92 | next
93 | when scanner.scan(/"/)
94 | str = ""
95 | captures = []
96 | i = scanner.pos
97 | while i < program.size
98 | if program[i..i + 1] == "\#{"
99 | captures.push Lexer::get_escaped_part_of_str(program[i + 2..], i + 2 - scanner.pos)
100 | str += program[i..scanner.pos + captures.last[:end]]
101 | i = scanner.pos + captures.last[:end] + 1
102 | end
103 | if program[i] == '"'
104 | scanner.pos = i + 1
105 | break
106 | end
107 | str += program[i]
108 | i += 1
109 | end
110 | captures = captures.map do |capture|
111 | { **capture,
112 | tokens: Lexer::tokenize(capture[:value]) }
113 | end
114 | tokens.push Token.new("\"#{str}\"", :str_lit, str, captures)
115 | when scanner.scan(/\d+\.\d+/)
116 | tokens.push Token.new(scanner.matched, :float_lit, scanner.matched.to_f)
117 | when scanner.scan(/\d+/)
118 | tokens.push Token.new(scanner.matched, :int_lit, scanner.matched.to_i)
119 | when scanner.scan(/_/)
120 | tokens.push Token.new(scanner.matched, :identifier, "_")
121 | when scanner.scan(/(true|false)\b/)
122 | tokens.push Token.new(scanner.matched, :bool_lit, scanner.matched == "true")
123 | when scanner.scan(/null(?!\?)\b/)
124 | tokens.push Token.new(scanner.matched, :null)
125 | when scanner.scan(/undefined(?!\?)\b/)
126 | tokens.push Token.new(scanner.matched, :undefined)
127 | when scanner.scan(/this\b/)
128 | tokens.push Token.new(scanner.matched, :this)
129 | when scanner.scan(/new\b/)
130 | tokens.push Token.new(scanner.matched, :new)
131 | when scanner.scan(/static\b/)
132 | tokens.push Token.new(scanner.matched, :static)
133 | when scanner.scan(/do\b/)
134 | tokens.push Token.new(scanner.matched, :do)
135 | when scanner.scan(/function\b/)
136 | tokens.push Token.new(scanner.matched, :function)
137 | when scanner.scan(/class\b/)
138 | tokens.push Token.new(scanner.matched, :class)
139 | when scanner.scan(/get\b/)
140 | tokens.push Token.new(scanner.matched, :get)
141 | when scanner.scan(/for\b/)
142 | tokens.push Token.new(scanner.matched, :for)
143 | when scanner.scan(/of\b/)
144 | tokens.push Token.new(scanner.matched, :of)
145 | when scanner.scan(/case\b/)
146 | tokens.push Token.new(scanner.matched, :case)
147 | when scanner.scan(/when\b/)
148 | tokens.push Token.new(scanner.matched, :when)
149 | when scanner.scan(/component\b/)
150 | tokens.push Token.new(scanner.matched, :component)
151 | when scanner.scan(/in\b/)
152 | tokens.push Token.new(scanner.matched, :in)
153 | when scanner.scan(/instanceof\b/)
154 | tokens.push Token.new(scanner.matched, :instanceof)
155 | when scanner.scan(/if\b/)
156 | tokens.push Token.new(scanner.matched, :if)
157 | when scanner.scan(/unless\b/)
158 | tokens.push Token.new(scanner.matched, :unless)
159 | when scanner.scan(/else\b/)
160 | tokens.push Token.new(scanner.matched, :else)
161 | when scanner.scan(/end\b/)
162 | tokens.push Token.new(scanner.matched, :end)
163 | when scanner.scan(/enum\b/)
164 | tokens.push Token.new(scanner.matched, :enum)
165 | when scanner.scan(/<\//)
166 | tokens.push Token.new(scanner.matched, :"")
167 | when scanner.scan(/=>/)
168 | tokens.push Token.new(scanner.matched, :"=>")
169 | when scanner.scan(/!==/)
170 | tokens.push Token.new(scanner.matched, :"!==")
171 | when scanner.scan(/===/)
172 | tokens.push Token.new(scanner.matched, :"===")
173 | when scanner.scan(/==/)
174 | tokens.push Token.new(scanner.matched, :"==")
175 | when scanner.scan(/\!/)
176 | tokens.push Token.new(scanner.matched, :not)
177 | when scanner.scan(/@/)
178 | tokens.push Token.new(scanner.matched, :"@")
179 | when scanner.scan(/\+=/)
180 | tokens.push Token.new(scanner.matched, :"+=")
181 | when scanner.scan(/\|\|=/)
182 | tokens.push Token.new(scanner.matched, :"||=")
183 | when scanner.scan(/:=/)
184 | tokens.push Token.new(scanner.matched, :assign)
185 | when scanner.scan(/\(/)
186 | tokens.push Token.new(scanner.matched, :open_paren)
187 | when scanner.scan(/\)/)
188 | tokens.push Token.new(scanner.matched, :close_paren)
189 | when scanner.scan(/\{/)
190 | tokens.push Token.new(scanner.matched, :"{")
191 | when scanner.scan(/\}/)
192 | tokens.push Token.new(scanner.matched, :"}")
193 | when scanner.scan(/\[/)
194 | tokens.push Token.new(scanner.matched, :"[")
195 | when scanner.scan(/\]/)
196 | tokens.push Token.new(scanner.matched, :"]")
197 | when scanner.scan(/\.\.\./)
198 | tokens.push Token.new(scanner.matched, :"...")
199 | when scanner.scan(/\.\./)
200 | tokens.push Token.new(scanner.matched, :"..")
201 | when scanner.scan(/\?\./)
202 | tokens.push Token.new(scanner.matched, :"?.")
203 | when scanner.scan(/::/)
204 | tokens.push Token.new(scanner.matched, :"::")
205 | when scanner.scan(/\./)
206 | tokens.push Token.new(scanner.matched, :dot)
207 | when scanner.scan(/,/)
208 | tokens.push Token.new(scanner.matched, :comma)
209 | when scanner.scan(/\*/)
210 | tokens.push Token.new(scanner.matched, :*)
211 | when scanner.scan(/\//)
212 | tokens.push Token.new(scanner.matched, :/)
213 | when scanner.scan(/\+/)
214 | tokens.push Token.new(scanner.matched, :+)
215 | when scanner.scan(/\>=/)
216 | tokens.push Token.new(scanner.matched, :">=")
217 | when scanner.scan(/<=/)
218 | tokens.push Token.new(scanner.matched, :"<=")
219 | when scanner.scan(/\>/)
220 | tokens.push Token.new(scanner.matched, :>)
221 | when scanner.scan(/)
222 | tokens.push Token.new(scanner.matched, :<)
223 | when scanner.scan(/=/)
224 | tokens.push Token.new(scanner.matched, :"=")
225 | when scanner.scan(/\|\|/)
226 | tokens.push Token.new(scanner.matched, :"||")
227 | when scanner.scan(/&&/)
228 | tokens.push Token.new(scanner.matched, :"&&")
229 | when scanner.scan(/\|/)
230 | tokens.push Token.new(scanner.matched, :|)
231 | when scanner.scan(/&/)
232 | tokens.push Token.new(scanner.matched, :&)
233 | when scanner.scan(/%/)
234 | tokens.push Token.new(scanner.matched, :%)
235 | when scanner.scan(/mod/)
236 | tokens.push Token.new(scanner.matched, :mod)
237 | when scanner.scan(/-/)
238 | tokens.push Token.new(scanner.matched, :-)
239 | when scanner.scan(/await\b/)
240 | tokens.push Token.new(scanner.matched, :await)
241 | when scanner.scan(/import\b/)
242 | tokens.push Token.new(scanner.matched, :import)
243 | when scanner.scan(/export\b/)
244 | tokens.push Token.new(scanner.matched, :export)
245 | when scanner.scan(/default\b/)
246 | tokens.push Token.new(scanner.matched, :default)
247 | when scanner.scan(/return\b/)
248 | tokens.push Token.new(scanner.matched, :return)
249 | when scanner.scan(/schema\b/)
250 | tokens.push Token.new(scanner.matched, :schema)
251 | when scanner.scan(/:[a-zA-Z][a-zA-Z0-9\_!]*/)
252 | tokens.push Token.new(scanner.matched, :capture, scanner.matched[1..])
253 | when scanner.scan(/:/)
254 | tokens.push Token.new(scanner.matched, :colon)
255 | when scanner.scan(/[a-zA-Z][a-zA-Z0-9\_!]*/)
256 | tokens.push Token.new(scanner.matched, :identifier, scanner.matched)
257 | else
258 | raise AssertionError
259 | end
260 | end
261 | return tokens
262 | end
263 | end
264 |
--------------------------------------------------------------------------------
/lib/pea_std_lib/css_preprocessor.js:
--------------------------------------------------------------------------------
1 | class NotReached extends Error {
2 | constructor(...artifacts) {
3 | console.log(...artifacts);
4 | super();
5 | }
6 | }
7 |
8 | class StringScanner {
9 | index = 0;
10 | matched = null;
11 | groups = [];
12 | constructor(str) {
13 | this.str = str;
14 | }
15 |
16 | scan(regex) {
17 | const result = this.rest_of_str().match(regex);
18 | if (result === null || result.index !== 0) return false;
19 | const [matched, ...groups] = Array.from(result);
20 | this.index += matched.length;
21 | this.matched = matched;
22 | this.groups = groups;
23 | return true;
24 | }
25 |
26 | rest_of_str() {
27 | return this.str.slice(this.index);
28 | }
29 |
30 | is_end_of_str() {
31 | return this.index >= this.str.length;
32 | }
33 | }
34 |
35 | class Token {
36 | constructor(value) {
37 | this.value = value;
38 | }
39 | is(TokenType) {
40 | return this instanceof TokenType;
41 | }
42 | }
43 | class ValueToken extends Token {}
44 | class IdentifierToken extends Token {}
45 | class OpenBrace extends Token {}
46 | class CloseBrace extends Token {}
47 | class Ampersand extends Token {}
48 | class PseudoClass extends Token {}
49 | class PseudoElement extends Token {}
50 | class Dot extends Token {}
51 | class Comma extends Token {}
52 |
53 | const tokenizeCSS = (str) => {
54 | const tokens = [];
55 | const scanner = new StringScanner(str);
56 | while (!scanner.is_end_of_str()) {
57 | if (scanner.scan(/\s+/)) {
58 | continue;
59 | } else if (scanner.scan(/&/)) {
60 | tokens.push(new Ampersand());
61 | } else if (scanner.scan(/\./)) {
62 | tokens.push(new Dot());
63 | } else if (scanner.scan(/\,/)) {
64 | tokens.push(new Comma());
65 | } else if (scanner.scan(/::([a-z]+)/)) {
66 | tokens.push(new PseudoElement(scanner.groups[0]));
67 | } else if (scanner.scan(/:([a-z]+)/)) {
68 | tokens.push(new PseudoClass(scanner.groups[0]));
69 | } else if (scanner.scan(/:(\s)*(.*);/)) {
70 | tokens.push(new ValueToken(scanner.groups[1]));
71 | } else if (scanner.scan(/([a-z][a-z1-9\-]*|\*)/)) {
72 | tokens.push(new IdentifierToken(scanner.matched));
73 | } else if (scanner.scan(/\{/)) {
74 | tokens.push(new OpenBrace());
75 | } else if (scanner.scan(/\}/)) {
76 | tokens.push(new CloseBrace());
77 | } else {
78 | throw new NotReached([scanner.index, scanner.rest_of_str()]);
79 | }
80 | }
81 | return tokens;
82 | };
83 |
84 | class TokenMismatch extends Error {
85 | constructor(expected, got) {
86 | super(`expected - ${expected.name}, got - ${got.constructor.name}`);
87 | }
88 | }
89 |
90 | class AstNode {
91 | is(NodeType) {
92 | return this instanceof NodeType;
93 | }
94 | }
95 | class RuleNode extends AstNode {
96 | constructor(name, value) {
97 | super();
98 | this.name = name;
99 | this.value = value;
100 | }
101 | }
102 | class ChildSelectorNode extends AstNode {
103 | constructor(tag_name, rules) {
104 | super();
105 | this.tag_name = tag_name;
106 | this.rules = rules;
107 | }
108 | }
109 |
110 | class PseudoSelectorNode extends AstNode {
111 | constructor(pseudo_class, rules) {
112 | super();
113 | this.pseudo_class = pseudo_class;
114 | this.rules = rules;
115 | }
116 | }
117 |
118 | class PseudoElementNode extends AstNode {
119 | constructor(pseudo_element, rules) {
120 | super();
121 | this.pseudo_element = pseudo_element;
122 | this.rules = rules;
123 | }
124 | }
125 |
126 | class ClassSelectorNode extends AstNode {
127 | constructor(class_name, rules) {
128 | super();
129 | this.class_name = class_name;
130 | this.rules = rules;
131 | }
132 | }
133 |
134 | class MultipleIdentifierNode extends AstNode {
135 | constructor(first_tag_name, second_tag_name, rules) {
136 | super();
137 | this.first_tag_name = first_tag_name;
138 | this.second_tag_name = second_tag_name;
139 | this.rules = rules;
140 | }
141 | }
142 |
143 | class CSSParser {
144 | constructor(tokens, index = 0, end_token = null) {
145 | this.tokens = tokens;
146 | this.index = index;
147 | this.end_token = end_token;
148 | }
149 |
150 | get current() {
151 | return this.tokens[this.index];
152 | }
153 |
154 | get peek() {
155 | return this.tokens[this.index + 1];
156 | }
157 |
158 | get peek_two() {
159 | return this.tokens[this.index + 2];
160 | }
161 |
162 | clone(end_token = null) {
163 | return new CSSParser(this.tokens, this.index, end_token || this.end_token);
164 | }
165 |
166 | consume_clone(parser) {
167 | this.index = parser.index;
168 | }
169 |
170 | consume(TokenClass) {
171 | if (!(this.current instanceof TokenClass))
172 | throw new TokenMismatch(TokenClass, this.current);
173 | const token = this.current;
174 | this.index += 1;
175 | return token;
176 | }
177 |
178 | can_parse() {
179 | if (this.index >= this.tokens.length) return false;
180 | if (!this.end_token) return true;
181 | return !this.current.is(this.end_token);
182 | }
183 |
184 | parse() {
185 | const ast = [];
186 | while (this.can_parse()) {
187 | if (this.current.is(Dot) && this.peek?.is(IdentifierToken)) {
188 | ast.push(this.parse_class_selector());
189 | } else if (
190 | this.current.is(IdentifierToken) &&
191 | this.peek?.is(Comma) &&
192 | this.peek_two?.is(IdentifierToken)
193 | ) {
194 | ast.push(this.parse_multiple_identifier());
195 | } else if (this.current.is(Ampersand) && this.peek?.is(PseudoClass)) {
196 | ast.push(this.parse_child_pseudo_class_selector());
197 | } else if (this.current.is(Ampersand) && this.peek?.is(PseudoElement)) {
198 | ast.push(this.parse_child_pseudo_element_selector());
199 | } else if (this.current.is(IdentifierToken) && this.peek?.is(OpenBrace)) {
200 | ast.push(this.parse_child_selector());
201 | } else if (this.current.is(IdentifierToken)) {
202 | ast.push(this.parse_rule());
203 | } else {
204 | throw new NotReached(this.current);
205 | }
206 | }
207 | return ast;
208 | }
209 |
210 | parse_class_selector() {
211 | this.consume(Dot);
212 | const { value: class_name } = this.consume(IdentifierToken);
213 | this.consume(OpenBrace);
214 | const cloned_parser = this.clone(CloseBrace);
215 | const rules = cloned_parser.parse();
216 | this.consume_clone(cloned_parser);
217 | this.consume(CloseBrace);
218 | return new ClassSelectorNode(class_name, rules);
219 | }
220 |
221 | parse_child_pseudo_class_selector() {
222 | this.consume(Ampersand);
223 | const { value: selector } = this.consume(PseudoClass);
224 | this.consume(OpenBrace);
225 | const cloned_parser = this.clone(CloseBrace);
226 | const rules = cloned_parser.parse();
227 | this.consume_clone(cloned_parser);
228 | this.consume(CloseBrace);
229 | return new PseudoSelectorNode(selector, rules);
230 | }
231 |
232 | parse_multiple_identifier() {
233 | const { value: first_tag_name } = this.consume(IdentifierToken);
234 | this.consume(Comma);
235 | const { value: second_tag_name } = this.consume(IdentifierToken);
236 | this.consume(OpenBrace);
237 | const cloned_parser = this.clone(CloseBrace);
238 | const rules = cloned_parser.parse();
239 | this.consume_clone(cloned_parser);
240 | this.consume(CloseBrace);
241 | return new MultipleIdentifierNode(first_tag_name, second_tag_name, rules);
242 | }
243 |
244 | parse_child_pseudo_element_selector() {
245 | this.consume(Ampersand);
246 | const { value: pseudo_element } = this.consume(PseudoElement);
247 | this.consume(OpenBrace);
248 | const cloned_parser = this.clone(CloseBrace);
249 | const rules = cloned_parser.parse();
250 | this.consume_clone(cloned_parser);
251 | this.consume(CloseBrace);
252 | return new PseudoElementNode(pseudo_element, rules);
253 | }
254 |
255 | parse_child_selector() {
256 | const { value: tag_name } = this.consume(IdentifierToken);
257 | this.consume(OpenBrace);
258 | const cloned_parser = this.clone(CloseBrace);
259 | const rules = cloned_parser.parse();
260 | this.consume_clone(cloned_parser);
261 | this.consume(CloseBrace);
262 | return new ChildSelectorNode(tag_name, rules);
263 | }
264 |
265 | parse_rule() {
266 | const { value: name } = this.consume(IdentifierToken);
267 | const { value } = this.consume(ValueToken);
268 | return new RuleNode(name, value);
269 | }
270 | }
271 |
272 | class CSSCompiler {
273 | constructor(ast, parent_selector, top_level = true) {
274 | this.ast = ast;
275 | this.parent_selector = parent_selector;
276 | this.top_level = top_level;
277 | }
278 |
279 | base_rules_end() {
280 | if (this.top_level) {
281 | return "}\n";
282 | } else {
283 | return "";
284 | }
285 | }
286 |
287 | eval() {
288 | let rules_str = `${this.parent_selector} {\n`;
289 | const [rules, sub_rules] = this.eval_rules_and_sub_rules();
290 | return [rules_str + rules, ...sub_rules];
291 | }
292 |
293 | eval_rules_and_sub_rules() {
294 | let rules = "";
295 | const rule_nodes = this.ast.filter((node) => node instanceof RuleNode);
296 | for (let node of rule_nodes) {
297 | rules += ` ${this.eval_rule(node)}\n`;
298 | }
299 | rules += this.base_rules_end();
300 |
301 | let sub_rules = [];
302 | const sub_rule_nodes = this.ast.filter(
303 | (node) => !rule_nodes.includes(node)
304 | );
305 | for (let node of sub_rule_nodes) {
306 | switch (node.constructor) {
307 | case ClassSelectorNode:
308 | sub_rules = sub_rules.concat(this.eval_class_selector(node));
309 | break;
310 | case PseudoSelectorNode:
311 | sub_rules = sub_rules.concat(this.eval_pseudo_class_selector(node));
312 | break;
313 | case PseudoElementNode:
314 | sub_rules = sub_rules.concat(this.eval_pseudo_element_selector(node));
315 | break;
316 | case ChildSelectorNode:
317 | sub_rules = sub_rules.concat(this.eval_child_selector(node));
318 | break;
319 | default:
320 | throw new NotReached();
321 | }
322 | }
323 | return [rules, sub_rules];
324 | }
325 |
326 | eval_rule({ name, value }) {
327 | return `${name}: ${value};`;
328 | }
329 |
330 | eval_class_selector({ class_name, rules: ast }) {
331 | if (!class_name) throw new NotReached();
332 | const selector = `${this.parent_selector} .${class_name}`;
333 | let output = `${selector} {\n`;
334 | const [rules, sub_rules] = new CSSCompiler(
335 | ast,
336 | selector,
337 | false
338 | ).eval_rules_and_sub_rules();
339 | output += rules;
340 | output += "}\n";
341 |
342 | return [output, ...sub_rules];
343 | }
344 |
345 | eval_pseudo_class_selector({ pseudo_class, rules: ast }) {
346 | if (!pseudo_class) throw new NotReached();
347 | const selector = `${this.parent_selector}:${pseudo_class}`;
348 | let output = `${selector} {\n`;
349 | const [rules, sub_rules] = new CSSCompiler(
350 | ast,
351 | selector,
352 | false
353 | ).eval_rules_and_sub_rules();
354 | output += rules;
355 | output += "}\n";
356 |
357 | return [output, ...sub_rules];
358 | }
359 |
360 | eval_pseudo_element_selector({ pseudo_element, rules: ast }) {
361 | if (!pseudo_element) throw new NotReached();
362 | const selector = `${this.parent_selector}::${pseudo_element}`;
363 | let output = `${selector} {\n`;
364 | const [rules, sub_rules] = new CSSCompiler(
365 | ast,
366 | selector,
367 | false
368 | ).eval_rules_and_sub_rules();
369 | output += rules;
370 | output += "}\n";
371 |
372 | return [output, ...sub_rules];
373 | }
374 |
375 | eval_child_selector({ tag_name, rules: ast }) {
376 | const selector = `${this.parent_selector} ${tag_name}`;
377 | let output = `${selector} {\n`;
378 | const [rules, sub_rules] = new CSSCompiler(
379 | ast,
380 | selector,
381 | false
382 | ).eval_rules_and_sub_rules();
383 | output += rules;
384 | output += "}\n";
385 |
386 | return [output, ...sub_rules];
387 | }
388 | }
389 |
390 | function compile_css(style, tag_name) {
391 | const tokens = tokenizeCSS(style);
392 | const ast = new CSSParser(tokens).parse();
393 | return new CSSCompiler(ast, tag_name).eval();
394 | }
395 |
396 | function parse_css(style) {
397 | const tokens = tokenizeCSS(style);
398 | return new CSSParser(tokens).parse();
399 | }
400 |
--------------------------------------------------------------------------------
/lib/ast.rb:
--------------------------------------------------------------------------------
1 | require "pry"
2 |
3 | module AST
4 | class Node
5 | attr_reader :start_pos, :end_pos
6 |
7 | def initialize(start_pos, end_pos)
8 | @start_pos = start_pos
9 | @end_pos = end_pos
10 | end
11 |
12 | def declare?
13 | false
14 | end
15 |
16 | def schema?
17 | false
18 | end
19 |
20 | def to_h
21 | hash = {
22 | "klass" => self.class.to_s,
23 | }
24 | instance_variables.each do |var|
25 | node = instance_variable_get(var)
26 | value = if node.is_a? Node
27 | node.to_h
28 | elsif node.is_a?(Array) && node.all? { |n| n.is_a?(Node) }
29 | node.map(&:to_h)
30 | else
31 | node
32 | end
33 | hash[var.to_s.delete("@")] = value
34 | end
35 | hash
36 | end
37 |
38 | def is_not_one_of?(*klasses)
39 | !klasses.any? { |klass| is_a? klass }
40 | end
41 |
42 | def is_not_a?(klass)
43 | !is_a?(klass)
44 | end
45 |
46 | def start_pos=(val)
47 | @start_pos = val
48 | end
49 |
50 | def end_pos=(val)
51 | @end_pos = val
52 | end
53 |
54 | def captures
55 | []
56 | end
57 | end
58 |
59 | class Primitive < Node
60 | attr_reader :value
61 |
62 | def initialize(value, start_pos, end_pos)
63 | @value = value
64 | @start_pos = start_pos
65 | @end_pos = end_pos
66 | end
67 | end
68 |
69 | class Int < Primitive
70 | end
71 |
72 | class Float < Primitive
73 | end
74 |
75 | class Bool < Primitive
76 | end
77 |
78 | class SimpleString < Primitive
79 | end
80 |
81 | class Expr < Node
82 | attr_reader :expr
83 |
84 | def initialize(expr, start_pos, end_pos)
85 | @expr = expr
86 | @start_pos = start_pos
87 | @end_pos = end_pos
88 | end
89 | end
90 |
91 | class ArrayLiteral < Expr
92 | def captures
93 | list = []
94 | @value.each do |val|
95 | list.concat val.captures
96 | end
97 | list
98 | end
99 | end
100 |
101 | class ObjectLiteral < Expr
102 | end
103 |
104 | class NamedArg < Node
105 | attr_reader :name
106 |
107 | def initialize(name, start_pos, end_pos)
108 | @name = name
109 | @start_pos = start_pos
110 | @end_pos = end_pos
111 | end
112 | end
113 |
114 | class SimpleArg < NamedArg
115 | end
116 |
117 | class SpreadArg < NamedArg
118 | end
119 |
120 | class ThisSchemaArg < Node
121 | attr_reader :schema
122 |
123 | def initialize(schema, start_pos, end_pos)
124 | @schema = schema
125 | @start_pos = start_pos
126 | @end_pos = end_pos
127 | end
128 | end
129 |
130 | class SimpleSchemaArg < Node
131 | attr_reader :schema_name, :name
132 |
133 | def initialize(schema_name, name, start_pos, end_pos)
134 | @schema_name = schema_name
135 | @name = name
136 | @start_pos = start_pos
137 | @end_pos = end_pos
138 | end
139 | end
140 |
141 | class SimpleFnArgs < Node
142 | attr_reader :args
143 |
144 | def initialize(args, start_pos, end_pos)
145 | @args = args
146 | @start_pos = start_pos
147 | @end_pos = end_pos
148 | end
149 | end
150 |
151 | class ObjectEntry < Node
152 | attr_reader :key_name, :value
153 |
154 | def initialize(key_name, value, start_pos, end_pos)
155 | @key_name = key_name
156 | @value = value
157 | @start_pos = start_pos
158 | @end_pos = end_pos
159 | end
160 | end
161 |
162 | class SimpleObjectEntry < ObjectEntry
163 | end
164 |
165 | class ArrowMethodObjectEntry < ObjectEntry
166 | end
167 |
168 | class FunctionObjectEntry < ObjectEntry
169 | end
170 |
171 | class SpreadObjectEntry < Expr
172 | end
173 |
174 | class Not < Expr
175 | end
176 |
177 | class SchemaDefinition < Node
178 | attr_reader :name, :schema_expr
179 |
180 | def initialize(name, schema_expr, start_pos, end_pos)
181 | @name = name
182 | @schema_expr = schema_expr
183 | @start_pos = start_pos
184 | @end_pos = end_pos
185 | end
186 | end
187 |
188 | class SchemaInt < Node
189 | attr_reader :value
190 |
191 | def initialize(int_token)
192 | @value = int_token.value
193 | @start_pos = int_token.start_pos
194 | @end_pos = int_token.end_pos
195 | end
196 |
197 | def schema?
198 | true
199 | end
200 | end
201 |
202 | class SchemaObjectLiteral < Node
203 | attr_reader :properties
204 |
205 | def to_h
206 | props = []
207 | for key, value in properties
208 | props.push [key, value.to_h]
209 | end
210 | props
211 | end
212 |
213 | def initialize(properties, start_pos, end_pos)
214 | @properties = properties
215 | @start_pos = start_pos
216 | @end_pos = end_pos
217 | end
218 | end
219 |
220 | class New < Node
221 | attr_reader :class_expr, :args
222 |
223 | def initialize(class_expr, args, start_pos, end_pos)
224 | @class_expr = class_expr
225 | @args = args
226 | @start_pos = start_pos
227 | @end_pos = end_pos
228 | end
229 | end
230 |
231 | class CaseFnPattern < Node
232 | attr_reader :this_pattern, :patterns, :body
233 |
234 | def initialize(this_pattern, patterns, body, start_pos, end_pos)
235 | @this_pattern = this_pattern
236 | @patterns = patterns
237 | @body = body
238 | @start_pos = start_pos
239 | @end_pos = end_pos
240 | end
241 | end
242 |
243 | class ArrayComprehension < Node
244 | attr_reader :expr, :variable, :array_expr, :if_expr
245 |
246 | def initialize(expr, variable, array_expr, if_expr, start_pos, end_pos)
247 | @expr = expr
248 | @variable = variable
249 | @array_expr = array_expr
250 | @if_expr = if_expr
251 | @start_pos = start_pos
252 | @end_pos = end_pos
253 | end
254 | end
255 |
256 | class SingleLineBindFunctionDefinition < Node
257 | attr_reader :object_name, :function_name, :args, :return_expr
258 |
259 | def initialize(object_name, function_name, args, return_expr, start_pos, end_pos)
260 | @object_name = object_name
261 | @function_name = function_name
262 | @args = args
263 | @return_expr = return_expr
264 | @start_pos = start_pos
265 | @end_pos = end_pos
266 | end
267 | end
268 |
269 | class DefaultAssignment < Node
270 | attr_reader :lhs, :expr
271 |
272 | def initialize(lhs, expr, start_pos, end_pos)
273 | @lhs = lhs
274 | @expr = expr
275 | @start_pos = start_pos
276 | @end_pos = end_pos
277 | end
278 | end
279 |
280 | class PlusAssignment < Node
281 | attr_reader :lhs, :expr
282 |
283 | def initialize(lhs, expr, start_pos, end_pos)
284 | @lhs = lhs
285 | @expr = expr
286 | @start_pos = start_pos
287 | @end_pos = end_pos
288 | end
289 | end
290 |
291 | class SimpleWhen < Node
292 | attr_reader :expr, :body
293 |
294 | def initialize(expr, body, start_pos, end_pos)
295 | @expr = expr
296 | @body = body
297 | @start_pos = start_pos
298 | @end_pos = end_pos
299 | end
300 | end
301 |
302 | class CaseElse < Node
303 | attr_reader :body
304 |
305 | def initialize(body, start_pos, end_pos)
306 | @body = body
307 | @start_pos = start_pos
308 | @end_pos = end_pos
309 | end
310 | end
311 |
312 | class EmptyCaseExpr < Node
313 | attr_reader :cases
314 |
315 | def initialize(cases, start_pos, end_pos)
316 | @cases = cases
317 | @start_pos = start_pos
318 | @end_pos = end_pos
319 | end
320 | end
321 |
322 | class MultiLineBindFunctionDefinition < Node
323 | attr_reader :object_name, :function_name, :args, :body
324 |
325 | def initialize(object_name, function_name, args, body, start_pos, end_pos)
326 | @object_name = object_name
327 | @function_name = function_name
328 | @args = args
329 | @body = body
330 | @start_pos = start_pos
331 | @end_pos = end_pos
332 | end
333 | end
334 |
335 | class CaseFunctionDefinition < Node
336 | attr_reader :name, :patterns
337 |
338 | def initialize(name, patterns, start_pos, end_pos)
339 | @name = name
340 | @patterns = patterns
341 | @start_pos = start_pos
342 | @end_pos = end_pos
343 | end
344 | end
345 |
346 | class InstanceProperty < Node
347 | attr_reader :name, :expr
348 |
349 | def initialize(name, expr, start_pos, end_pos)
350 | @name = name
351 | @expr = expr
352 | @start_pos = start_pos
353 | @end_pos = end_pos
354 | end
355 | end
356 |
357 | class SpreadExpr < Expr
358 | end
359 |
360 | class OneLineGetter < Node
361 | attr_reader :name, :expr
362 |
363 | def initialize(name, expr, start_pos, end_pos)
364 | @name = name
365 | @expr = expr
366 | @start_pos = start_pos
367 | @end_pos = end_pos
368 | end
369 | end
370 |
371 | class DynamicLookup < Node
372 | attr_reader :lhs, :expr
373 |
374 | def initialize(lhs, expr, start_pos, end_pos)
375 | @lhs = lhs
376 | @expr = expr
377 | @start_pos = start_pos
378 | @end_pos = end_pos
379 | end
380 | end
381 |
382 | class Return < Expr
383 | end
384 |
385 | class If < Node
386 | attr_reader :cond, :pass, :branches
387 |
388 | def initialize(cond, pass, branches, start_pos, end_pos)
389 | @cond = cond
390 | @pass = pass
391 | @branches = branches
392 | @start_pos = start_pos
393 | @end_pos = end_pos
394 | end
395 | end
396 |
397 | class ElseIf < Node
398 | attr_reader :cond, :body
399 |
400 | def initialize(cond, body, start_pos, end_pos)
401 | @cond = cond
402 | @body = body
403 | @start_pos = start_pos
404 | @end_pos = end_pos
405 | end
406 | end
407 |
408 | class Else < Node
409 | attr_reader :body
410 |
411 | def initialize(body, start_pos, end_pos)
412 | @body = body
413 | @start_pos = start_pos
414 | @end_pos = end_pos
415 | end
416 | end
417 |
418 | class FnCall < Node
419 | attr_reader :args, :expr
420 |
421 | def initialize(args, return_expr_n, start_pos, end_pos)
422 | assert { args.is_a? Array }
423 | @args = args
424 | @expr = return_expr_n
425 | @start_pos = start_pos
426 | @end_pos = end_pos
427 | end
428 | end
429 |
430 | class SingleLineDefWithArgs < Node
431 | attr_reader :return_value, :name, :args
432 |
433 | def initialize(name, args, return_value, start_pos, end_pos)
434 | @name = name
435 | @args = args
436 | @return_value = return_value
437 | @start_pos = start_pos
438 | @end_pos = end_pos
439 | end
440 | end
441 |
442 | class SingleLineDefWithoutArgs < Node
443 | attr_reader :return_value, :name
444 |
445 | def initialize(name, return_value, start_pos, end_pos)
446 | @name = name
447 | @return_value = return_value
448 | @start_pos = start_pos
449 | @end_pos = end_pos
450 | end
451 | end
452 |
453 | class Null < Node
454 | end
455 |
456 | class NullSchema < Node
457 | def name
458 | nil
459 | end
460 |
461 | def schema?
462 | true
463 | end
464 | end
465 |
466 | class MultilineDefWithoutArgs < Node
467 | attr_reader :body, :name
468 |
469 | def initialize(name, body, start_pos, end_pos)
470 | @name = name
471 | @body = body
472 | @start_pos = start_pos
473 | @end_pos = end_pos
474 | end
475 | end
476 |
477 | class ConstructorWithoutArgs < MultilineDefWithoutArgs
478 | end
479 |
480 | class ShortHandConstructor < Node
481 | attr_reader :args
482 |
483 | def initialize(args, start_pos, end_pos)
484 | @args = args
485 | @start_pos = start_pos
486 | @end_pos = end_pos
487 | end
488 | end
489 |
490 | class SimpleConstructorArg < Node
491 | attr_reader :name
492 |
493 | def initialize(name, start_pos, end_pos)
494 | @name = name
495 | @start_pos = start_pos
496 | @end_pos = end_pos
497 | end
498 | end
499 |
500 | class DefaultConstructorArg < Node
501 | attr_reader :name, :expr
502 |
503 | def initialize(name, expr, start_pos, end_pos)
504 | @name = name
505 | @expr = expr
506 | @start_pos = start_pos
507 | @end_pos = end_pos
508 | end
509 | end
510 |
511 | class MultilineDefWithArgs < Node
512 | attr_reader :body, :args, :name
513 |
514 | def initialize(name, args, body, start_pos, end_pos)
515 | @name = name
516 | @args = args
517 | @body = body
518 | @start_pos = start_pos
519 | @end_pos = end_pos
520 | end
521 | end
522 |
523 | class StaticMethod < MultilineDefWithArgs
524 | end
525 |
526 | class ConstructorWithArgs < MultilineDefWithArgs
527 | end
528 |
529 | class ShortFnWithArgs < Node
530 | attr_reader :return_expr, :args
531 |
532 | def initialize(args, return_expr, start_pos, end_pos)
533 | @args = args
534 | @return_expr = return_expr
535 | @start_pos = start_pos
536 | @end_pos = end_pos
537 | end
538 | end
539 |
540 | class ShortFn < Node
541 | attr_reader :return_expr
542 |
543 | def initialize(return_expr, start_pos, end_pos)
544 | @return_expr = return_expr
545 | @start_pos = start_pos
546 | @end_pos = end_pos
547 | end
548 | end
549 |
550 | class MultiLineArrowFnWithArgs < Node
551 | attr_reader :args, :body
552 |
553 | def initialize(args, body, start_pos, end_pos)
554 | @args = args
555 | @body = body
556 | @start_pos = start_pos
557 | @end_pos = end_pos
558 | end
559 | end
560 |
561 | class This < Node
562 | end
563 |
564 | class Bind < Node
565 | attr_reader :lhs, :function, :args
566 |
567 | def initialize(lhs, function, args)
568 | @lhs = lhs
569 | @function = function
570 | @args = args
571 | end
572 | end
573 |
574 | class OptionalChain < Node
575 | attr_reader :lhs, :property
576 |
577 | def initialize(lhs, property, start_pos, end_pos)
578 | @lhs = lhs
579 | @property = property
580 | @start_pos = start_pos
581 | @end_pos = end_pos
582 | end
583 | end
584 |
585 | class DotAssignment < Node
586 | attr_reader :lhs, :expr
587 |
588 | def initialize(lhs, expr, start_pos, end_pos)
589 | @lhs = lhs
590 | @expr = expr
591 | @start_pos = start_pos
592 | @end_pos = end_pos
593 | end
594 | end
595 |
596 | class Class < Node
597 | attr_reader :name, :parent_class, :entries
598 |
599 | def initialize(name, parent_class, entries, start_pos, end_pos)
600 | @name = name
601 | @parent_class = parent_class
602 | @entries = entries
603 | @start_pos = start_pos
604 | @end_pos = end_pos
605 | end
606 | end
607 |
608 | class SingleLineArrowFnWithoutArgs < Node
609 | attr_reader :return_expr
610 |
611 | def initialize(return_expr, start_pos, end_pos)
612 | @return_expr = return_expr
613 | @start_pos = start_pos
614 | @end_pos = end_pos
615 | end
616 | end
617 |
618 | class EscapedElementExpr < Expr
619 | end
620 |
621 | class SimpleElement < Node
622 | attr_reader :name, :children
623 |
624 | def initialize(name, children, start_pos, end_pos)
625 | @name = name
626 | @children = children
627 | @start_pos = start_pos
628 | @end_pos = end_pos
629 | end
630 | end
631 |
632 | class BodyComponentWithoutAttrs < Node
633 | attr_reader :name, :constructor_body, :expr
634 |
635 | def initialize(name, constructor_body, expr, start_pos, end_pos)
636 | @name = name
637 | @constructor_body = constructor_body
638 | @expr = expr
639 | @start_pos = start_pos
640 | @end_pos = end_pos
641 | end
642 | end
643 |
644 | class ExprComponentWithAttributes < Node
645 | attr_reader :name, :attributes, :expr
646 |
647 | def initialize(name, attributes, expr, start_pos, end_pos)
648 | @name = name
649 | @attributes = attributes
650 | @expr = expr
651 | @start_pos = start_pos
652 | @end_pos = end_pos
653 | end
654 | end
655 |
656 | class ExprComponent < Node
657 | attr_reader :name, :expr
658 |
659 | def initialize(name, expr, start_pos, end_pos)
660 | @name = name
661 | @expr = expr
662 | @start_pos = start_pos
663 | @end_pos = end_pos
664 | end
665 | end
666 |
667 | class SingleLineArrowFnWithArgs < Node
668 | attr_reader :args, :return_expr
669 |
670 | def initialize(args, return_expr, start_pos, end_pos)
671 | @args = args
672 | @return_expr = return_expr
673 | @start_pos = start_pos
674 | @end_pos = end_pos
675 | end
676 | end
677 |
678 | class SingleLineArrowFnWithOneArg < Node
679 | attr_reader :arg, :return_expr
680 |
681 | def initialize(arg, return_expr, start_pos, end_pos)
682 | @arg = arg
683 | @return_expr = return_expr
684 | @start_pos = start_pos
685 | @end_pos = end_pos
686 | end
687 | end
688 |
689 | class AnonIdLookup < Node
690 | end
691 |
692 | class IdLookup < Primitive
693 | def captures
694 | []
695 | end
696 | end
697 |
698 | class SchemaCapture < Node
699 | attr_reader :name
700 |
701 | def initialize(name, start_pos, end_pos)
702 | @name = name
703 | @start_pos = start_pos
704 | @end_pos = end_pos
705 | end
706 |
707 | def captures
708 | [name]
709 | end
710 | end
711 |
712 | class SimpleForOfLoop < Node
713 | attr_reader :iter_name, :arr_expr, :body
714 |
715 | def initialize(iter_name, arr_expr, body, start_pos, end_pos)
716 | @iter_name = iter_name
717 | @arr_expr = arr_expr
718 | @body = body
719 | @start_pos = start_pos
720 | @end_pos = end_pos
721 | end
722 | end
723 |
724 | class ForOfObjDeconstructLoop < Node
725 | attr_reader :iter_properties, :arr_expr, :body
726 |
727 | def initialize(iter_properties, arr_expr, body, start_pos, end_pos)
728 | @iter_properties = iter_properties
729 | @arr_expr = arr_expr
730 | @body = body
731 | @start_pos = start_pos
732 | @end_pos = end_pos
733 | end
734 | end
735 |
736 | class Op < Node
737 | attr_reader :lhs, :type, :rhs
738 |
739 | def initialize(lhs, type, rhs, start_pos, end_pos)
740 | @lhs = lhs
741 | @type = type
742 | @rhs = rhs
743 | @start_pos = start_pos
744 | @end_pos = end_pos
745 | end
746 | end
747 |
748 | class OneLineEnum < Node
749 | attr_reader :name, :variant_names
750 |
751 | def initialize(name, variant_names, start_pos, end_pos)
752 | @name = name
753 | @variant_names = variant_names
754 | @start_pos = start_pos
755 | @end_pos = end_pos
756 | end
757 | end
758 |
759 | class EnumExpr < Node
760 | attr_reader :enum_name, :variant_name
761 |
762 | def initialize(enum_name, variant_name, start_pos, end_pos)
763 | @enum_name = enum_name
764 | @variant_name = variant_name
765 | @start_pos = start_pos
766 | @end_pos = end_pos
767 | end
768 | end
769 |
770 | class Range < Node
771 | attr_reader :lhs, :rhs
772 |
773 | def initialize(lhs, rhs, start_pos, end_pos)
774 | @lhs = lhs
775 | @rhs = rhs
776 | @start_pos = start_pos
777 | @end_pos = end_pos
778 | end
779 | end
780 |
781 | class Dot < Op
782 | end
783 |
784 | class SchemaUnion < Node
785 | attr_reader :schema_exprs
786 |
787 | def initialize(schema_exprs, start_pos, end_pos)
788 | @schema_exprs = schema_exprs
789 | @start_pos = start_pos
790 | @end_pos = end_pos
791 | end
792 | end
793 |
794 | class Empty < Node
795 | def initialize
796 | end
797 | end
798 |
799 | class SchemaIntersect < Node
800 | attr_reader :schema_exprs
801 |
802 | def initialize(schema_exprs, start_pos, end_pos)
803 | @schema_exprs = schema_exprs
804 | @start_pos = start_pos
805 | @end_pos = end_pos
806 | end
807 | end
808 |
809 | class Assign < Node
810 | attr_reader :name, :expr
811 |
812 | def initialize(name, return_expr_n, start_pos, end_pos)
813 | @name = name
814 | @expr = return_expr_n
815 | @start_pos = start_pos
816 | @end_pos = end_pos
817 | end
818 |
819 | def declare?
820 | true
821 | end
822 | end
823 |
824 | class Await < Expr
825 | end
826 |
827 | # Schema(a) := b
828 | class SimpleSchemaAssignment < Assign
829 | attr_reader :schema_name
830 |
831 | def initialize(schema_name, name, expr, start_pos, end_pos)
832 | @schema_name = schema_name
833 | @name = name
834 | @expr = expr
835 | @start_pos = start_pos
836 | @end_pos = end_pos
837 | end
838 | end
839 |
840 | # a := 1
841 | class SimpleAssignment < Assign
842 | attr_reader :name, :expr
843 |
844 | def initialize(name, return_expr_n, start_pos, end_pos)
845 | @name = name
846 | @expr = return_expr_n
847 | @start_pos = start_pos
848 | @end_pos = end_pos
849 | end
850 |
851 | def captures
852 | [name]
853 | end
854 | end
855 |
856 | # a = 1
857 | class SimpleReassignment < Node
858 | attr_reader :name, :expr
859 |
860 | def initialize(name, return_expr_n, start_pos, end_pos)
861 | @name = name
862 | @expr = return_expr_n
863 | @start_pos = start_pos
864 | @end_pos = end_pos
865 | end
866 |
867 | def declare?
868 | false
869 | end
870 |
871 | def captures
872 | [name]
873 | end
874 | end
875 |
876 | # a :=
877 | class ImcompleteSimpleAssignment < Assign
878 | attr_reader :name
879 |
880 | def initialize(name, start_pos, end_pos)
881 | @name = name
882 | @start_pos = start_pos
883 | @end_pos = end_pos
884 | end
885 |
886 | def captures
887 | [name]
888 | end
889 | end
890 |
891 | class SimpleForInLoop < Node
892 | attr_reader :variable, :object_expr, :body
893 |
894 | def initialize(variable, object_expr, body, start_pos, end_pos)
895 | @variable = variable
896 | @object_expr = object_expr
897 | @body = body
898 | @start_pos = start_pos
899 | @end_pos = end_pos
900 | end
901 | end
902 |
903 | class ArrayAssignment < Node
904 | attr_reader :variables, :expr
905 |
906 | def initialize(variables, expr, start_pos, end_pos)
907 | @variables = variables
908 | @expr = expr
909 | @start_pos = start_pos
910 | @end_pos = end_pos
911 | end
912 |
913 | def declare?
914 | true
915 | end
916 |
917 | def captures
918 | @variables
919 | end
920 | end
921 | end
922 |
--------------------------------------------------------------------------------
/lib/compiler.rb:
--------------------------------------------------------------------------------
1 | require "utils"
2 | require "pry"
3 | require "parser"
4 |
5 | class Compiler
6 | def initialize(ast, indent = 0, fn_arg_names: [], bundle_std_lib: false, is_class_definition: false)
7 | @ast = ast
8 | @indent = indent
9 | @fn_arg_names = fn_arg_names
10 | @bundle_std_lib = bundle_std_lib
11 | @is_class_definition = is_class_definition
12 | end
13 |
14 | def eval
15 | program = ""
16 | program += std_lib if @bundle_std_lib
17 | program += eval_assignment_declarations
18 | # program += collapse_function_overloading
19 | @ast.each do |statement|
20 | program += " " * @indent
21 | program += eval_expr(statement)
22 | program += ";" << "\n"
23 | end
24 | program.rstrip
25 | end
26 |
27 | def eval_without_variable_declarations
28 | program = ""
29 | # program += collapse_function_overloading
30 | @ast.each do |statement|
31 | program += " " * @indent
32 | program += eval_expr(statement)
33 | program += ";" << "\n"
34 | end
35 | program.rstrip
36 | end
37 |
38 | private
39 |
40 | def std_lib
41 | if ARGV[1] == "-s"
42 | ""
43 | else
44 | [symbols, schema_lib, range, pea_std, pea_array].join("\n") + "\n"
45 | end
46 | end
47 |
48 | def pea_std
49 | file_str = File.read(File.dirname(__FILE__) + "/pea_std_lib/std.pea")
50 | tokens = Lexer.tokenize file_str
51 | ast = Parser.new(tokens, file_str).parse!
52 | Compiler.new(ast).eval
53 | end
54 |
55 | def pea_array
56 | file_str = File.read(File.dirname(__FILE__) + "/pea_std_lib/array.pea")
57 | tokens = Lexer.tokenize file_str
58 | ast = Parser.new(tokens, file_str).parse!
59 | Compiler.new(ast).eval
60 | end
61 |
62 | def range
63 | File.read(File.dirname(__FILE__) + "/pea_std_lib/range.js")
64 | end
65 |
66 | def schema_lib
67 | File.read(File.dirname(__FILE__) + "/pea_std_lib/schema.js")
68 | end
69 |
70 | def symbols
71 | File.read(File.dirname(__FILE__) + "/pea_std_lib/symbols.js")
72 | end
73 |
74 | def css_preprocessor
75 | File.read(File.dirname(__FILE__) + "/pea_std_lib/css_preprocessor.js")
76 | end
77 |
78 | def indent!
79 | @indent += 2
80 | end
81 |
82 | def dedent!
83 | @indent -= 2
84 | end
85 |
86 | def padding(by = 0)
87 | assert { @indent + by >= 0 }
88 | " " * (@indent + by)
89 | end
90 |
91 | def eval_assignment_declarations
92 | names = find_assignments
93 | if names.any?
94 | vars = padding
95 | vars += "let "
96 | vars += names.map { |names| sub_q(names) }.join(", ")
97 | vars += ";" << "\n"
98 | end
99 | vars || ""
100 | end
101 |
102 | def find_assignments
103 | nodes = @ast.flat_map do |node|
104 | if node.is_a?(AST::If)
105 | node.pass + node.branches.flatten
106 | else
107 | [node]
108 | end
109 | end
110 |
111 | (nodes
112 | .filter { |node| node.is_a?(AST::Assign) }
113 | .uniq { |node| node.name }
114 | .select { |node| !@fn_arg_names.include?(node.name) }
115 | .map(&:name) +
116 | nodes
117 | .select(&:declare?)
118 | .flat_map { |node| node.captures }
119 | .uniq).uniq
120 | end
121 |
122 | def eval_expr(node)
123 | case node
124 | when AST::Class
125 | eval_class node
126 | when AST::SimpleAssignment, AST::SimpleReassignment
127 | eval_assignment node
128 | when AST::SimpleSchemaAssignment
129 | eval_simple_schema_assignment node
130 | when AST::SchemaUnion
131 | eval_schema_union node
132 | when AST::SchemaIntersect
133 | eval_schema_intersect node
134 | when AST::Assign
135 | eval_assignment node
136 | when AST::ArrayLiteral
137 | eval_array_literal node
138 | when AST::ObjectLiteral
139 | eval_object_literal node
140 | when AST::ArrowMethodObjectEntry
141 | eval_arrow_method_object_entry node
142 | when AST::SimpleObjectEntry
143 | eval_object_entry node
144 | when AST::FunctionObjectEntry
145 | eval_function_object_entry node
146 | when AST::SpreadObjectEntry
147 | eval_spread_object_entry node
148 | when AST::Bool
149 | eval_bool node
150 | when AST::Int
151 | eval_int node
152 | when AST::Float
153 | eval_float node
154 | when AST::SimpleString
155 | eval_simple_string node
156 | when AST::SingleLineDefWithArgs
157 | eval_single_line_fn_with_args node
158 | when AST::SingleLineDefWithoutArgs
159 | eval_single_line_fn_without_args node
160 | when AST::SingleLineArrowFnWithoutArgs
161 | eval_arrow_fn_without_args node
162 | when AST::SingleLineArrowFnWithArgs
163 | eval_single_line_arrow_fn_with_args node
164 | when AST::MultiLineArrowFnWithArgs
165 | eval_arrow_fn_with_args node
166 | when AST::SingleLineArrowFnWithOneArg
167 | eval_arrow_fn_with_one_arg node
168 | when AST::StaticMethod
169 | eval_static_method node
170 | when AST::MultilineDefWithoutArgs
171 | eval_multiline_def_without_args node
172 | when AST::MultilineDefWithArgs
173 | eval_multiline_def_with_args node
174 | when AST::ShortFnWithArgs
175 | eval_short_fn_with_args node
176 | when AST::ShortFn
177 | eval_short_fn node
178 | when AST::AnonIdLookup
179 | eval_anon_id_lookup
180 | when AST::Empty
181 | ""
182 | when AST::Return
183 | eval_return node
184 | when AST::FnCall
185 | eval_function_call node
186 | when AST::IdLookup
187 | eval_identifier_lookup node
188 | when AST::If
189 | eval_if_expression node
190 | when AST::Else
191 | eval_else node
192 | when AST::ElseIf
193 | eval_else_if node
194 | when AST::SchemaCapture
195 | eval_schema_capture node
196 | when AST::DotAssignment
197 | eval_dot_assignment node
198 | when AST::Dot
199 | eval_dot node
200 | when AST::Range
201 | eval_range node
202 | when AST::Op
203 | eval_operator node
204 | when AST::SimpleForOfLoop
205 | eval_simple_for_of_loop node
206 | when AST::ForOfObjDeconstructLoop
207 | eval_for_of_obj_descontruct_loop node
208 | when AST::SchemaDefinition
209 | eval_schema_definition node
210 | when AST::SchemaObjectLiteral
211 | eval_schema_object_literal node
212 | when AST::Await
213 | eval_await node
214 | when AST::ExprComponent
215 | eval_expr_component node
216 | when AST::ExprComponentWithAttributes
217 | eval_expr_component_with_attrs node
218 | when AST::BodyComponentWithoutAttrs
219 | eval_body_component_without_attrs node
220 | when AST::EscapedElementExpr
221 | eval_escaped_element_expr node
222 | when AST::This
223 | "this"
224 | when AST::New
225 | eval_new node
226 | when AST::ShortHandConstructor
227 | eval_short_hand_constructor node
228 | when AST::DynamicLookup
229 | eval_dynamic_lookup node
230 | when AST::OneLineGetter
231 | eval_one_line_getter node
232 | when AST::InstanceProperty
233 | eval_instance_property node
234 | when AST::CaseFunctionDefinition
235 | eval_case_function_definition node
236 | when AST::ThisSchemaArg
237 | eval_this_schema_arg node
238 | when AST::Bind
239 | eval_bind node
240 | when AST::OptionalChain
241 | eval_optional_chain node
242 | when AST::ArrayAssignment
243 | eval_array_assignment node
244 | when AST::SimpleForInLoop
245 | eval_simple_for_in_loop node
246 | when AST::Null
247 | eval_null node
248 | when AST::SpreadExpr
249 | eval_spread_expr node
250 | when AST::SingleLineBindFunctionDefinition
251 | eval_single_line_bind_function_definition node
252 | when AST::MultiLineBindFunctionDefinition
253 | eval_multi_line_bind_function_definition node
254 | when AST::ArrayComprehension
255 | eval_array_comprehension node
256 | when AST::DefaultAssignment
257 | eval_default_assignment node
258 | when AST::PlusAssignment
259 | eval_plus_assignment node
260 | when AST::DefaultConstructorArg
261 | eval_default_constructor_arg node
262 | when AST::SimpleConstructorArg
263 | eval_simple_constructor_arg node
264 | when AST::EmptyCaseExpr
265 | eval_empty_case_expr node
266 | when AST::SimpleWhen
267 | eval_simple_when node
268 | when AST::CaseElse
269 | eval_case_else node
270 | when AST::Not
271 | eval_not node
272 | when AST::SimpleFnArgs
273 | eval_simple_fn_args node
274 | when AST::SimpleArg
275 | eval_simple_arg node
276 | when AST::SpreadArg
277 | eval_spread_arg node
278 | when AST::SimpleSchemaArg
279 | eval_simple_schema_arg node
280 | when AST::OneLineEnum
281 | eval_one_line_enum node
282 | when AST::EnumExpr
283 | eval_enum_expr node
284 | else
285 | binding.pry
286 | puts "no case matched node_type: #{node.class}"
287 | assert_not_reached!
288 | end
289 | end
290 |
291 | def eval_enum_expr(node)
292 | "#{node.enum_name}.#{node.variant_name}"
293 | end
294 |
295 | def eval_one_line_enum(node)
296 | e = "const #{node.name} = {\n"
297 | for variant in node.variant_names
298 | e += padding(2) + "#{variant}: Symbol('#{variant}'),\n"
299 | end
300 | e + padding + "}"
301 | end
302 |
303 | def eval_arrow_method_object_entry(node)
304 | fn = node.value
305 | args = eval_expr fn.args
306 | f = "#{node.key_name}(#{args}) {\n"
307 | f += "#{padding(2)}return #{eval_expr fn.return_expr};\n"
308 | f += "#{padding}},"
309 | end
310 |
311 | def eval_simple_schema_arg(node)
312 | node.name
313 | end
314 |
315 | def eval_spread_arg(node)
316 | "...#{node.name}"
317 | end
318 |
319 | def eval_simple_arg(node)
320 | node.name
321 | end
322 |
323 | def eval_simple_fn_args(node)
324 | node.args.map { |arg| eval_expr arg }.join ", "
325 | end
326 |
327 | def eval_not(node)
328 | "!#{eval_expr node.expr}"
329 | end
330 |
331 | def eval_case_else(node)
332 | # else is put in by `eval_empty_case_expr`
333 | c = "{\n"
334 | c += Compiler.new(node.body, @indent + 2).eval + "\n"
335 | c += "#{padding}}"
336 | end
337 |
338 | def eval_simple_when(node)
339 | c = "if (#{eval_expr node.expr}) {\n"
340 | c += Compiler.new(node.body, @indent + 2).eval + "\n"
341 | c += "#{padding}}"
342 | end
343 |
344 | def eval_empty_case_expr(node)
345 | return "" if node.cases.size == 0
346 | c = "#{eval_expr node.cases.first}"
347 | for case_ in node.cases[1..]
348 | c += " else #{eval_expr case_}"
349 | end
350 | c
351 | end
352 |
353 | def eval_default_constructor_arg(node)
354 | "#{node.name} = #{eval_expr node.expr}"
355 | end
356 |
357 | def eval_simple_constructor_arg(node)
358 | node.name
359 | end
360 |
361 | def eval_plus_assignment(node)
362 | "#{eval_expr node.lhs} += #{eval_expr node.expr}"
363 | end
364 |
365 | def eval_default_assignment(node)
366 | "#{eval_expr node.lhs} ||= #{eval_expr node.expr}"
367 | end
368 |
369 | def eval_multi_line_bind_function_definition(node)
370 | # ugly
371 | args = node.args.args.map(&:name).join ", "
372 | fn = "#{fn_prefix}#{node.function_name}(#{args}) {\n"
373 | indent!
374 | fn += "#{padding}if (!(this instanceof #{node.object_name})) throw new MatchError('Expected `this` to be a `#{node.object_name}`');\n"
375 | fn += Compiler.new(node.body, @indent).eval + "\n"
376 | dedent!
377 | fn += "#{padding}}"
378 | end
379 |
380 | def eval_array_comprehension(node)
381 | arr = eval_expr node.array_expr
382 | if node.if_expr
383 | arr += ".filter(#{node.variable} => #{eval_expr node.if_expr})"
384 | end
385 | "Array.from(#{arr}, #{node.variable} => #{eval_expr node.expr})"
386 | end
387 |
388 | def eval_single_line_bind_function_definition(node)
389 | # ugly args.args
390 | args = node.args.args.map(&:name).join ", "
391 | fn = "#{fn_prefix}#{node.function_name}(#{args}) {\n"
392 | indent!
393 | fn += "#{padding}if (!(this instanceof #{node.object_name})) throw new MatchError('Expected `this` to be a `#{node.object_name}`');\n"
394 | fn += "#{padding}return #{eval_expr node.return_expr};\n"
395 | dedent!
396 | fn += "#{padding}}"
397 | end
398 |
399 | def eval_spread_expr(node)
400 | "...#{eval_expr node.expr}"
401 | end
402 |
403 | def eval_null(node)
404 | "null"
405 | end
406 |
407 | def eval_this_schema_arg(node)
408 | eval_expr node.schema
409 | end
410 |
411 | def eval_simple_for_in_loop(node)
412 | f = "for (let #{node.variable} in #{eval_expr node.object_expr}) {\n"
413 | f += Compiler.new(node.body, @indent + 2).eval + "\n"
414 | f += "}"
415 | end
416 |
417 | def eval_array_assignment(node)
418 | a = "let _temp = #{eval_expr node.expr};\n"
419 | a += "s.verify([#{node.variables.map { |v| "s('#{v}')" }.join ", "}], _temp, 'Array');\n"
420 | a + "[#{node.variables.join ", "}] = _temp"
421 | end
422 |
423 | def eval_optional_chain(node)
424 | "#{padding}#{eval_expr node.lhs}?.#{node.property}"
425 | end
426 |
427 | def eval_bind(node)
428 | args = node.args.map { |arg| eval_expr arg }.join ", "
429 | if args.size > 0
430 | args = ", #{args}"
431 | end
432 | "#{eval_expr node.function}.call(#{eval_expr node.lhs}#{args})"
433 | end
434 |
435 | def eval_case_function_definition(node)
436 | f = "#{padding}#{fn_prefix}#{node.name}(...args) {\n"
437 | # TODO: make this generic
438 | node.patterns.each_with_index do |s_case, i|
439 | if s_case.patterns.all? { |arg| arg.is_a? AST::SimpleSchemaArg }
440 | schemas = s_case.patterns.map(&:schema_name)
441 | args = s_case.patterns.map(&:name)
442 | elsif s_case.patterns.all? { |arg| arg.is_a? AST::SchemaInt }
443 | schemas = s_case.patterns.map(&:value)
444 | args = []
445 | elsif s_case.patterns.all? { |arg| arg.is_a? AST::SimpleArg }
446 | schemas = s_case.patterns.map { |p| "s('#{p.name}')" }
447 | args = s_case.patterns.map(&:name)
448 | else
449 | assert_not_reached!
450 | end
451 | if i == 0
452 | f += "#{padding} if "
453 | else
454 | f += " else if "
455 | end
456 | f += "("
457 | if s_case.this_pattern
458 | f += "s.check(#{eval_expr s_case.this_pattern}, this) && "
459 | end
460 | f += "s.check([#{schemas.join ", "}], args)) {\n"
461 |
462 | args.each_with_index do |arg, i|
463 | f += "#{padding} let #{arg} = args[#{i}];\n"
464 | end
465 | f += "#{padding}#{Compiler.new(s_case.body, @indent + 4).eval}\n"
466 | if i == node.patterns.size - 1
467 | f += "#{padding} }\n"
468 | else
469 | f += "#{padding} }"
470 | end
471 | end
472 | f.rstrip!
473 | f += "#{padding} else throw new MatchError();\n"
474 | f + "}"
475 | end
476 |
477 | def eval_instance_property(node)
478 | "#{padding}#{node.name} = #{eval_expr node.expr};"
479 | end
480 |
481 | def eval_one_line_getter(node)
482 | m = "#{padding}get #{node.name}() {\n"
483 | m += "#{padding} return #{eval_expr node.expr};\n"
484 | m += "#{padding}}"
485 | end
486 |
487 | def eval_dynamic_lookup(node)
488 | "#{eval_expr node.lhs}[#{eval_expr node.expr}]"
489 | end
490 |
491 | def eval_short_hand_constructor(node)
492 | args = node.args.map { |arg| eval_expr arg }.join ", "
493 | c = "#{padding}constructor(#{args}) {\n"
494 | for arg in node.args.map(&:name)
495 | c += "#{padding} this.#{arg} = #{arg};\n"
496 | end
497 | c += "#{padding}}"
498 | end
499 |
500 | def eval_new(node)
501 | args = node.args.map { |node| eval_expr node }.join ", "
502 | "new #{eval_expr node.class_expr}(#{args})"
503 | end
504 |
505 | def eval_static_method(node)
506 | "#{padding}static #{eval_multiline_def_with_args(node).lstrip}"
507 | end
508 |
509 | def eval_dot_assignment(node)
510 | "#{eval_expr node.lhs} = #{eval_expr node.expr}"
511 | end
512 |
513 | def eval_class(node)
514 | super_class = " extends #{node.parent_class}" if node.parent_class
515 | c = "class #{node.name}#{super_class} {\n"
516 | indent!
517 | @is_class_definition = true
518 | for entry in node.entries
519 | c += "#{eval_expr entry}\n"
520 | end
521 | @is_class_definition = false
522 | dedent!
523 | c += "}"
524 | end
525 |
526 | def eval_escaped_element_expr(node)
527 | node = node.expr
528 | value = if node.is_a?(AST::IdLookup)
529 | "this.#{node.value} || #{node.value}"
530 | else
531 | eval_expr node
532 | end
533 | "${#{value}}"
534 | end
535 |
536 | def kebab_case(name)
537 | name.gsub(/([a-z\d])([A-Z])/, '\1-\2').downcase
538 | end
539 |
540 | def create_shadow_root
541 | "this.attachShadow({ mode: 'open' });"
542 | end
543 |
544 | def define_component(node)
545 | "customElements.define('#{kebab_case node.name}', #{node.name})"
546 | end
547 |
548 | def eval_body_component_without_attrs(node)
549 | c = "class #{node.name} extends HTMLElement {\n"
550 | node.constructor_body
551 | .select { |node| node.is_a? AST::SimpleAssignment }
552 | .each do |node|
553 | c += " #{node.name} = #{eval_expr node.expr};\n"
554 | end
555 | c += " constructor() {\n"
556 | c += " super();\n"
557 | c += " #{create_shadow_root}\n"
558 | c += " }\n"
559 | c += " connectedCallback() {\n"
560 | c += " #{eval_render node};\n"
561 | c += " }\n"
562 | c += "}\n"
563 | c + define_component(node)
564 | end
565 |
566 | def get_attr_names(node)
567 | node.attributes.properties.map { |attr, _| attr }
568 | end
569 |
570 | def assign_attributes(node, padding)
571 | node.attributes.properties.map do |attr, schema|
572 | assert { schema.is_a? AST::SchemaCapture }
573 | "#{padding}this.#{attr} = this.getAttribute('#{attr}');"
574 | end.join "\n"
575 | end
576 |
577 | def get_var_queries(elem, path = elem.expr.name)
578 | assert { elem.expr.children.size == 1 }
579 | assert { elem.expr.children[0].is_a? AST::EscapedElementExpr }
580 | assert { elem.expr.children[0].value.is_a? AST::IdLookup }
581 | {
582 | elem.expr.children[0].value.value => {
583 | :path => path,
584 | :expr => elem.expr.children[0],
585 | },
586 | }
587 | end
588 |
589 | def eval_expr_component_with_attrs(node)
590 | c = "class #{node.name} extends HTMLElement {\n"
591 | c += " static get observedAttributes() {\n"
592 | c += " return #{get_attr_names(node).to_s};\n"
593 | c += " }\n"
594 | c += " constructor() {\n"
595 | c += " super();\n"
596 | c += " #{create_shadow_root}\n"
597 | c += " }\n"
598 | c += " connectedCallback() {\n"
599 | c += "#{assign_attributes(node, " ")}\n"
600 | c += " #{eval_render node}\n"
601 | c += " }\n"
602 | c += " attributeChangedCallback(name, oldValue, newValue) {\n"
603 | c += " if (this.shadowRoot.innerHTML.trim() === '') return;\n"
604 | var_name_to_query = get_var_queries node
605 | var_name_to_query.each do |key, value|
606 | c += " if (name === '#{key}') {\n"
607 | c += " this.#{key} = newValue;\n"
608 | c += " this.shadowRoot.querySelector('#{value[:path]}').innerHTML = `#{eval_expr value[:expr]}`;\n"
609 | c += " }\n"
610 | end
611 | c += " }\n"
612 | c += "}\n"
613 | c + define_component(node)
614 | end
615 |
616 | def eval_render(node)
617 | "this.shadowRoot.innerHTML = #{eval_simple_element node.expr};"
618 | end
619 |
620 | def eval_simple_element(node)
621 | assert { node.is_a? AST::SimpleElement }
622 | e = "`<#{node.name}>"
623 | e += " #{node.children.map { |el| eval_expr el }.join("\n")}"
624 | e += "#{node.name}>`"
625 | end
626 |
627 | def eval_expr_component(node)
628 | c = "class #{node.name} extends HTMLElement {\n"
629 | c += " constructor() {\n"
630 | c += " super();\n"
631 | c += " #{create_shadow_root}\n"
632 | c += " #{eval_render node};\n"
633 | c += " }\n"
634 | c += "}\n"
635 | c + define_component(node)
636 | end
637 |
638 | def eval_await(node)
639 | "await #{eval_expr node.expr}"
640 | end
641 |
642 | def eval_simple_schema_assignment(node)
643 | "#{node.name} = s.verify(#{node.schema_name}, #{eval_expr node.expr}, '#{node.schema_name}')"
644 | end
645 |
646 | def unpack_object(object_str)
647 | assert { object_str[0] == "{" }
648 | assert { object_str[-1] == "}" }
649 | object_str[1...-1].strip
650 | end
651 |
652 | def eval_schema_intersect(node)
653 | schema = "{ "
654 | schema += node.schema_exprs.map do |expr|
655 | case expr
656 | when AST::IdLookup
657 | "...#{eval_expr expr}"
658 | when AST::SchemaObjectLiteral
659 | unpack_object eval_expr(expr)
660 | else
661 | assert_not_reached!
662 | end
663 | end.join ", "
664 | schema + " }"
665 | end
666 |
667 | def eval_schema_union(node)
668 | "s.union(#{node.schema_exprs.map { |expr| eval_expr expr }.join ", "})"
669 | end
670 |
671 | def eval_schema_object_literal(node)
672 | schema_obj = "{ "
673 | for name, value in node.properties
674 | schema_obj += "#{name}: #{eval_expr value}, "
675 | end
676 | # remove last ", "
677 | schema_obj[0...-2] + " }"
678 | end
679 |
680 | def eval_schema_definition(node)
681 | "const #{node.name} = #{eval_expr node.schema_expr}"
682 | end
683 |
684 | def eval_for_of_obj_descontruct_loop(node)
685 | properties = node.iter_properties.join ", "
686 | for_loop = "for (let { #{properties} } of #{eval_expr node.arr_expr}) {\n"
687 | for_loop += Compiler.new(node.body, @indent + 2).eval + "\n"
688 | for_loop += "#{padding}}"
689 | end
690 |
691 | def eval_simple_for_of_loop(node)
692 | for_loop = "for (let #{node.iter_name} of #{eval_expr node.arr_expr}) {\n"
693 | for_loop += Compiler.new(node.body, @indent + 2).eval + "\n"
694 | for_loop += "#{padding}}"
695 | end
696 |
697 | def fn_prefix
698 | if @is_class_definition
699 | ""
700 | else
701 | "function "
702 | end
703 | end
704 |
705 | def eval_multiline_def_without_args(fn_node)
706 | fn = "#{padding}#{fn_prefix}#{fn_node.name}() {\n"
707 | fn += Compiler.new(fn_node.body, @indent + 2).eval + "\n"
708 | fn += "#{padding}}"
709 | fn
710 | end
711 |
712 | def eval_dot(node)
713 | "#{eval_expr(node.lhs)}.#{eval_expr(node.rhs)}"
714 | end
715 |
716 | def eval_range(node)
717 | "new Range(#{eval_expr node.lhs}, #{eval_expr node.rhs})"
718 | end
719 |
720 | def eval_dbl_eq(node)
721 | "#{eval_expr(node.lhs)}[Symbol.peacock_equals](#{eval_expr(node.rhs)})"
722 | end
723 |
724 | def eval_in(node)
725 | "#{eval_expr(node.rhs)}[Symbol.peacock_contains](#{eval_expr(node.lhs)})"
726 | end
727 |
728 | def eval_operator(node)
729 | return eval_in(node) if node.type == :in
730 | return eval_dbl_eq(node) if node.type == :"=="
731 | op = node.type
732 | op = "%" if node.type == :mod
733 |
734 | "#{eval_expr(node.lhs)} #{op} #{eval_expr(node.rhs)}"
735 | end
736 |
737 | def eval_schema_capture(node)
738 | "s('#{node.name}')"
739 | end
740 |
741 | def eval_else_if(node)
742 | b = " else if (#{eval_expr node.cond}) {\n"
743 | b += Compiler.new(node.body, @indent + 2).eval + "\n"
744 | b += "}"
745 | b
746 | end
747 |
748 | def eval_else(node)
749 | b = " else {\n"
750 | b += Compiler.new(node.body, @indent + 2).eval + "\n"
751 | b += "}"
752 | b
753 | end
754 |
755 | def eval_if_expression(node)
756 | cond = eval_expr(node.cond)
757 | pass_body = Compiler.new(node.pass, @indent + 2).eval_without_variable_declarations
758 |
759 | i = "if (#{cond}) {\n"
760 | i += "#{pass_body}\n"
761 | i += "#{padding}}"
762 | i += node.branches.map { |branch| eval_expr branch }.join ""
763 |
764 | i
765 | end
766 |
767 | def eval_simple_string(node)
768 | if node.value.include?("\n")
769 | "`#{node.value}`"
770 | else
771 | "\"#{node.value}\""
772 | end
773 | end
774 |
775 | def eval_int(node)
776 | "#{node.value}"
777 | end
778 |
779 | def eval_float(node)
780 | "#{node.value}"
781 | end
782 |
783 | def eval_bool(node)
784 | "#{node.value}"
785 | end
786 |
787 | def eval_array_literal(node)
788 | elements = node.expr.map { |n| eval_expr n }.join(", ")
789 | "[#{elements}]"
790 | end
791 |
792 | def eval_object_entry(node)
793 | "#{padding}#{node.key_name}: #{eval_expr node.value}"
794 | end
795 |
796 | def eval_spread_object_entry(node)
797 | "#{padding}...#{eval_expr node.expr}"
798 | end
799 |
800 | def eval_function_object_entry(node)
801 | fn = node.value
802 | args = eval_expr fn.args
803 | output = "#{node.key_name}(#{args}) {\n"
804 | output += Compiler.new(fn.body, @indent + 2, fn_arg_names: fn.args.args.map(&:name)).eval + "\n"
805 | output + "#{padding}}"
806 | end
807 |
808 | def eval_object_literal(node)
809 | return "{}" if node.expr.size == 0
810 | has_function = node.expr.any? do |node|
811 | node.is_a?(AST::ArrowMethodObjectEntry) || node.is_a?(AST::FunctionObjectEntry)
812 | end
813 | whitespace = if node.expr.size > 3 || has_function
814 | indent!
815 | "\n"
816 | else
817 | " "
818 | end
819 | object_literal = "{#{whitespace}"
820 | object_literal += padding + node.expr.map { |entry| eval_expr entry }.join(",#{whitespace}")
821 | dedent! if whitespace != " "
822 | object_literal += "#{whitespace}#{padding}}"
823 | end
824 |
825 | def sub_q(sym)
826 | sym
827 | .sub("?", "_q")
828 | .sub("!", "_b")
829 | end
830 |
831 | def eval_declaration(node)
832 | "const #{sub_q(node.name)} = #{eval_expr(node.expr)}"
833 | end
834 |
835 | def eval_assignment(node)
836 | "#{sub_q(node.name)} = #{eval_expr(node.expr)}"
837 | end
838 |
839 | def eval_arrow_fn_with_one_arg(node)
840 | "(#{node.arg}) => #{eval_expr node.return_expr}"
841 | end
842 |
843 | def eval_multiline_def_with_args(fn_node)
844 | args = eval_expr fn_node.args
845 |
846 | fn = "#{padding}#{fn_prefix}#{fn_node.name}(#{args}) {\n"
847 | fn += "#{padding(2)}#{schema_arg_assignments fn_node.args}".rstrip
848 | fn += Compiler.new(fn_node.body, @indent + 2).eval + "\n"
849 | fn += "#{padding}}"
850 | fn
851 | end
852 |
853 | def schema_arg_assignments(args_node)
854 | assignments = args_node.args
855 | .select { |node| node.is_a?(AST::SimpleSchemaArg) }
856 | .map do |arg|
857 | "#{padding}#{arg.name} = s.verify(#{arg.schema_name}, #{arg.name});"
858 | end.join("\n")
859 |
860 | assignments += args_node.args
861 | .map.with_index { |node, i| { node: node, i: i } }
862 | .select { |h| h[:node].is_a?(AST::NullSchema) }
863 | .map { |h| "#{padding}s.verify(null, args[#{h[:i]}], 'null')" }
864 | .join("\n")
865 |
866 | if assignments.empty?
867 | ""
868 | else
869 | assignments + "\n"
870 | end
871 | end
872 |
873 | def eval_single_line_fn_without_args(fn_node)
874 | fn = "#{padding}#{fn_prefix}#{fn_node.name}() {\n"
875 | indent!
876 | fn += "#{padding}return #{eval_expr fn_node.return_value};\n"
877 | dedent!
878 | fn += "#{padding}}"
879 | fn
880 | end
881 |
882 | def eval_single_line_fn_with_args(fn_node)
883 | args = if fn_node.args.args.any?(&:schema?)
884 | "...args"
885 | else
886 | eval_expr fn_node.args
887 | end
888 |
889 | fn = "#{padding}#{fn_prefix}#{fn_node.name}(#{args}) {\n"
890 | indent!
891 | fn += schema_arg_assignments(fn_node.args)
892 | fn += "#{padding}return #{eval_expr fn_node.return_value};\n"
893 | dedent!
894 | fn += "#{padding}}"
895 | fn
896 | end
897 |
898 | def eval_single_line_arrow_fn_with_args(node)
899 | args = eval_expr node.args
900 |
901 | fn = "(#{args}) => {\n"
902 | indent!
903 | fn += schema_arg_assignments(node.args)
904 | fn += padding + "return #{eval_expr node.return_expr};\n"
905 | dedent!
906 | fn + "#{padding}}"
907 | end
908 |
909 | def eval_arrow_fn_with_args(node)
910 | args = eval_expr node.args
911 |
912 | fn = "(#{args}) => {\n"
913 | fn += schema_arg_assignments(node.args)
914 | fn += Compiler.new(node.body, @indent + 2, fn_arg_names: node.args.value.map(&:name)).eval + "\n"
915 | fn + "}"
916 | end
917 |
918 | def eval_arrow_fn_without_args(node)
919 | "(() => { return #{eval_expr node.return_expr}; })"
920 | end
921 |
922 | def eval_short_fn_with_args(node)
923 | "(#{node.args.join ", "} => #{eval_expr node.return_expr})"
924 | end
925 |
926 | def eval_anon_id_lookup
927 | "_it"
928 | end
929 |
930 | def eval_short_fn(node)
931 | "(#{eval_anon_id_lookup} => #{eval_expr node.return_expr})"
932 | end
933 |
934 | def eval_return(node)
935 | "return #{eval_expr node.expr}"
936 | end
937 |
938 | def eval_function_call(node)
939 | args = node.args.map { |arg| eval_expr arg }.join ", "
940 | fn = eval_expr node.expr
941 | "#{fn}(#{args})"
942 | end
943 |
944 | def eval_identifier_lookup(node)
945 | assert { node.value }
946 | sub_q(node.value)
947 | end
948 | end
949 |
--------------------------------------------------------------------------------
/lib/parser.rb:
--------------------------------------------------------------------------------
1 | require "utils"
2 | require "ast"
3 | require "lexer"
4 |
5 | class ParserError
6 | attr_reader :msg, :start_pos, :end_pos
7 |
8 | def initialize(msg, start_pos, end_pos)
9 | @msg = msg
10 | @start_pos
11 | @end_pos = end_pos
12 | end
13 | end
14 |
15 | class SimpleAssignmentError < ParserError
16 | end
17 |
18 | class Parser
19 | attr_reader :tokens, :program_string, :pos, :errors
20 |
21 | def self.can_parse?(_self)
22 | not_implemented!
23 | end
24 |
25 | def self.from(_self)
26 | self.new(_self.tokens, _self.program_string, _self.pos, _self.errors)
27 | end
28 |
29 | def initialize(tokens, program_string, pos = 0, errors = [])
30 | @tokens = tokens
31 | @program_string = program_string
32 | @pos = pos
33 | @errors = errors
34 | end
35 |
36 | def consume_parser!(parser_klass, *parse_args, **parse_opts)
37 | parser = parser_klass.from(self)
38 | expr_n = parser.parse! *parse_args, **parse_opts
39 | @pos = parser.pos
40 | expr_n
41 | end
42 |
43 | def current_token
44 | @tokens[@pos]
45 | end
46 |
47 | def prev_token
48 | @tokens[@pos - 1]
49 | end
50 |
51 | def peek_token
52 | @tokens[@pos + 1]
53 | end
54 |
55 | def peek_token_twice
56 | @tokens[@pos + 2]
57 | end
58 |
59 | def peek_token_thrice
60 | @tokens[@pos + 3]
61 | end
62 |
63 | def rest_of_line
64 | rest_of_program = @program_string[current_token.start_pos..]
65 | rest_of_program[0..rest_of_program.index("\n")]
66 | end
67 |
68 | def current_line
69 | @program_string[0..current_token&.start_pos]
70 | .split("\n")
71 | .count
72 | end
73 |
74 | def new_line?(offset = 0)
75 | return true if !prev_token || !current_token
76 | prev = @tokens[@pos + offset - 1]
77 | curr = @tokens[@pos + offset]
78 | @program_string[prev.start_pos..curr.start_pos].include? "\n"
79 | end
80 |
81 | def parser_not_implemented!(parser_klasses)
82 | puts "Not Implemented, only supporting the following parsers - "
83 | pp parser_klasses
84 | not_implemented!
85 | end
86 |
87 | def consume_first_valid_parser!(parser_klasses, &catch_block)
88 | parser_klass = parser_klasses.find { |klass| klass.can_parse? self }
89 | if !parser_klass
90 | if catch_block
91 | catch_block.call
92 | else
93 | parser_not_implemented! parser_klasses
94 | end
95 | end
96 | consume_parser! parser_klass
97 | end
98 |
99 | def consume!(token_type)
100 | assert { token_type == current_token.type }
101 | @pos += 1
102 | return prev_token
103 | end
104 |
105 | def consume_any!
106 | @pos += 1
107 | return prev_token
108 | end
109 |
110 | def consume_if_present!(token_type)
111 | consume! token_type if current_token.type == token_type
112 | end
113 |
114 | def parse!
115 | ProgramParser.from(self).parse!
116 | end
117 | end
118 |
119 | ### FUNCTION ARGS START
120 |
121 | class SimpleSchemaArgParser < Parser
122 | def self.can_parse?(_self)
123 | _self.current_token&.type == :identifier &&
124 | _self.peek_token.type == :open_paren &&
125 | _self.peek_token_twice.type == :identifier &&
126 | _self.peek_token_thrice.type == :close_paren
127 | end
128 |
129 | def parse!
130 | args = []
131 | schema_name_t = consume! :identifier
132 | consume! :open_paren
133 | var_t = consume! :identifier
134 | close_t = consume! :close_paren
135 | AST::SimpleSchemaArg.new(
136 | schema_name_t.value,
137 | var_t.value,
138 | schema_name_t.start_pos,
139 | close_t.end_pos
140 | )
141 | end
142 | end
143 |
144 | # broken :/
145 | class SchemaIntParser < Parser
146 | def self.can_parse?(_self)
147 | _self.current_token.type == :int_lit
148 | end
149 |
150 | def parse!
151 | int_t = consume! :int_lit
152 | AST::SchemaInt.new(int_t)
153 | end
154 | end
155 |
156 | class SimpleArgParser < Parser
157 | def self.can_parse?(_self)
158 | _self.current_token&.type == :identifier &&
159 | _self.peek_token.is_one_of?(:comma, :close_paren)
160 | end
161 |
162 | def parse!
163 | name_t = consume! :identifier
164 | AST::SimpleArg.new(name_t.value, name_t.start_pos, name_t.end_pos)
165 | end
166 | end
167 |
168 | class NullSchemaParser < Parser
169 | def self.can_parse?(_self)
170 | _self.current_token&.type == :null
171 | end
172 |
173 | def parse!
174 | null_t = consume! :null
175 | AST::NullSchema.new(null_t.start_pos, null_t.end_pos)
176 | end
177 | end
178 |
179 | class SchemaArgParser < Parser
180 | PARSERS = [
181 | SimpleSchemaArgParser,
182 | NullSchemaParser,
183 | SchemaIntParser,
184 | SimpleArgParser,
185 | ]
186 |
187 | def self.can_parse?(_self)
188 | PARSERS.any? { |klass| klass.can_parse? _self }
189 | end
190 |
191 | def parse!
192 | consume_first_valid_parser! PARSERS
193 | end
194 | end
195 |
196 | class SpreadArgParser < Parser
197 | def self.can_parse?(_self)
198 | _self.current_token.type == :"..."
199 | end
200 |
201 | def parse!
202 | spread_t = consume! :"..."
203 | name_t = consume! :identifier
204 | AST::SpreadArg.new(name_t.value, spread_t.start_pos, name_t.end_pos)
205 | end
206 | end
207 |
208 | class SimpleFnArgsParser < Parser
209 | ARG_PARSERS = [
210 | SimpleArgParser,
211 | SpreadArgParser,
212 | SchemaArgParser,
213 | ]
214 |
215 | def parse!
216 | open_t = consume! :open_paren
217 |
218 | if current_token.type == :close_paren
219 | close_t = consume! :close_paren
220 | return AST::SimpleFnArgs.new([], open_t.start_pos, close_t.end_pos)
221 | end
222 |
223 | args = []
224 |
225 | loop do
226 | args.push consume_first_valid_parser! ARG_PARSERS
227 | break if current_token.type == :close_paren
228 | consume! :comma
229 | end
230 | close_t = consume! :close_paren
231 |
232 | AST::SimpleFnArgs.new(args, open_t.start_pos, close_t.end_pos)
233 | end
234 | end
235 |
236 | ### FUNCTION ARGS END
237 |
238 | ### FUNCTION DEFINITION START
239 |
240 | class SingleLineDefWithArgsParser < Parser
241 | def self.can_parse?(_self)
242 | _self.current_token&.type == :function &&
243 | _self.peek_token.type == :identifier &&
244 | _self.peek_token_twice.type == :open_paren &&
245 | _self.rest_of_line.include?("=")
246 | end
247 |
248 | def parse!
249 | function_t = consume! :function
250 | fn_name_t = consume! :identifier
251 | args_n = consume_parser! SimpleFnArgsParser
252 | consume! :"="
253 | return_value_n = consume_parser! ExprParser
254 |
255 | AST::SingleLineDefWithArgs.new(
256 | fn_name_t.value,
257 | args_n,
258 | return_value_n,
259 | function_t.start_pos,
260 | return_value_n.end_pos
261 | )
262 | end
263 | end
264 |
265 | class SingleLineDefWithoutArgsParser < Parser
266 | def self.can_parse?(_self)
267 | _self.current_token&.type == :function &&
268 | _self.peek_token.type == :identifier &&
269 | _self.rest_of_line.include?("=")
270 | end
271 |
272 | def parse!
273 | function_t = consume! :function
274 | fn_name_t = consume! :identifier
275 | consume! :"="
276 | return_value_n = consume_parser! ExprParser
277 |
278 | AST::SingleLineDefWithoutArgs.new(
279 | fn_name_t.value,
280 | return_value_n,
281 | function_t.start_pos,
282 | return_value_n.end_pos
283 | )
284 | end
285 | end
286 |
287 | class MultilineDefWithoutArgsParser < Parser
288 | def self.can_parse?(_self)
289 | _self.current_token&.type == :function &&
290 | _self.peek_token.type == :identifier
291 | end
292 |
293 | def parse!
294 | function_t = consume! :function
295 | fn_name_t = consume! :identifier
296 | body = consume_parser! FunctionBodyParser
297 | end_t = consume! :end
298 | AST::MultilineDefWithoutArgs.new(
299 | fn_name_t.value,
300 | body,
301 | function_t.start_pos,
302 | end_t.end_pos
303 | )
304 | end
305 | end
306 |
307 | class MultilineDefWithArgsParser < Parser
308 | def self.can_parse?(_self)
309 | _self.current_token&.type == :function &&
310 | _self.peek_token.type == :identifier &&
311 | _self.peek_token_twice.type == :open_paren
312 | end
313 |
314 | def parse!
315 | function_t = consume! :function
316 | fn_name_t = consume! :identifier
317 | args_n = consume_parser! SimpleFnArgsParser
318 | body = consume_parser! FunctionBodyParser
319 | end_t = consume! :end
320 | AST::MultilineDefWithArgs.new(
321 | fn_name_t.value,
322 | args_n,
323 | body,
324 | function_t.start_pos,
325 | end_t.end_pos
326 | )
327 | end
328 | end
329 |
330 | class ShortAnonFnWithNamedArgParser < Parser
331 | def self.can_parse?(_self)
332 | _self.current_token&.type == :"#\{" &&
333 | _self.peek_token&.type == :"|"
334 | end
335 |
336 | def parse_args!
337 | args = []
338 |
339 | consume! :"|"
340 | loop do
341 | args.push consume!(:identifier).value
342 | break if current_token.type == :"|"
343 | consume! :comma
344 | end
345 | consume! :"|"
346 |
347 | args
348 | end
349 |
350 | def parse!
351 | open_anon_t = consume! :"#\{"
352 | args = parse_args!
353 | return_expr_n = consume_parser! ExprParser
354 | return_expr_n ||= AST::Empty.new
355 | end_t = consume! :"}"
356 | AST::ShortFnWithArgs.new(args, return_expr_n, open_anon_t.start_pos, end_t.end_pos)
357 | end
358 | end
359 |
360 | class ShortAnonFnParser < Parser
361 | def self.can_parse?(_self)
362 | _self.current_token&.type == :"#\{"
363 | end
364 |
365 | def parse!
366 | open_anon_t = consume! :"#\{"
367 | return_expr_n = consume_parser! ExprParser
368 | return_expr_n ||= AST::Empty.new
369 | end_t = consume! :"}"
370 | AST::ShortFn.new(return_expr_n, open_anon_t.start_pos, end_t.end_pos)
371 | end
372 | end
373 |
374 | class AnonIdLookupParser < Parser
375 | def self.can_parse?(_self)
376 | _self.current_token&.type == :%
377 | end
378 |
379 | def parse!
380 | id_t = consume! :%
381 | AST::AnonIdLookup.new(id_t.start_pos, id_t.end_pos)
382 | end
383 | end
384 |
385 | class SingleLineArrowFnWithoutArgsParser < Parser
386 | def self.can_parse?(_self)
387 | _self.current_token&.type == :open_paren &&
388 | _self.peek_token&.type == :close_paren &&
389 | _self.peek_token_twice&.type == :"=>"
390 | end
391 |
392 | def parse!
393 | open_p_t = consume! :open_paren
394 | consume! :close_paren
395 | consume! :"=>"
396 | return_expr_n = consume_parser! ExprParser
397 | AST::SingleLineArrowFnWithoutArgs.new(
398 | return_expr_n,
399 | open_p_t.start_pos,
400 | return_expr_n.end_pos
401 | )
402 | end
403 | end
404 |
405 | class MultiLineArrowFnWithArgsParser < Parser
406 | def self.can_parse?(_self)
407 | _self.current_token&.type == :open_paren &&
408 | _self.rest_of_line&.include?("=>") &&
409 | _self.rest_of_line&.include?("{")
410 | end
411 |
412 | def parse!
413 | args_n = consume_parser! SimpleFnArgsParser
414 | consume! :"=>"
415 | consume! :"{"
416 | body_n = consume_parser! FunctionBodyParser
417 | end_t = consume! :"}"
418 | AST::MultiLineArrowFnWithArgs.new(args_n, body_n, args_n.start_pos, end_t.end_pos)
419 | end
420 | end
421 |
422 | class SingleLineArrowFnWithArgsParser < Parser
423 | def self.can_parse?(_self)
424 | _self.current_token&.type == :open_paren &&
425 | _self.rest_of_line&.include?("=>")
426 | end
427 |
428 | def parse!
429 | args_n = consume_parser! SimpleFnArgsParser
430 | consume! :"=>"
431 | return_expr_n = consume_parser! ExprParser
432 | AST::SingleLineArrowFnWithArgs.new(args_n, return_expr_n, args_n.start_pos, return_expr_n.end_pos)
433 | end
434 | end
435 |
436 | class SingleLineArrowFnWithOneArgParser < Parser
437 | def self.can_parse?(_self)
438 | _self.current_token&.type == :identifier &&
439 | _self.peek_token&.type == :"=>"
440 | end
441 |
442 | def parse!
443 | arg_t = consume! :identifier
444 | consume! :"=>"
445 | return_expr_n = consume_parser! ExprParser
446 | AST::SingleLineArrowFnWithOneArg.new(arg_t.value, return_expr_n, arg_t.start_pos, return_expr_n.end_pos)
447 | end
448 | end
449 |
450 | class ReturnParser < Parser
451 | def self.can_parse?(_self)
452 | _self.current_token&.type == :return
453 | end
454 |
455 | def parse!
456 | return_t = consume! :return
457 | expr_n = consume_parser! ExprParser
458 | AST::Return.new(expr_n, return_t.start_pos, expr_n.end_pos)
459 | end
460 | end
461 |
462 | ### FUNCTION DEFINITION END
463 |
464 | ### FUNCTION CALLS START
465 |
466 | class FunctionCallWithoutArgs < Parser
467 | def self.can_parse?(_self, lhs)
468 | _self.current_token&.type == :open_paren &&
469 | _self.peek_token.type == :close_paren
470 | end
471 |
472 | def parse!(lhs_n)
473 | open_p_t = consume! :open_paren
474 | close_p_t = consume! :close_paren
475 |
476 | AST::FnCall.new([], lhs_n, open_p_t.start_pos, close_p_t.end_pos)
477 | end
478 | end
479 |
480 | class FunctionCallWithArgs < Parser
481 | def self.can_parse?(_self, lhs)
482 | _self.current_token&.type == :open_paren
483 | end
484 |
485 | def parse!(lhs_n)
486 | open_p_t = consume! :open_paren
487 | args = []
488 | loop do
489 | args.push consume_parser! ExprParser
490 | consume_if_present! :comma
491 | break if current_token.type == :close_paren
492 | end
493 | close_p_t = consume! :close_paren
494 |
495 | AST::FnCall.new(args, lhs_n, open_p_t.start_pos, close_p_t.end_pos)
496 | end
497 | end
498 |
499 | class FunctionCallWithArgsWithoutParens < Parser
500 | def self.can_parse?(_self, lhs)
501 | _self.current_token&.is_not_one_of?(:assign, :comma, :"]", :for, :if, :close_paren, :"}") &&
502 | !_self.new_line?
503 | end
504 |
505 | def end_of_expr?
506 | current_token&.is_one_of? :"}", :close_paren
507 | end
508 |
509 | def parse!(lhs_n)
510 | args = []
511 |
512 | loop do
513 | args.push consume_parser! ExprParser
514 | break if new_line? || end_of_expr?
515 | consume! :comma
516 | end
517 |
518 | AST::FnCall.new(args, lhs_n, lhs_n.end_pos, args.last.end_pos)
519 | end
520 | end
521 |
522 | ### FUNCTION CALLS END
523 |
524 | ### OPERATORS START
525 |
526 | class NotParser < Parser
527 | def self.can_parse?(_self)
528 | _self.current_token&.type == :not
529 | end
530 |
531 | def parse!
532 | not_t = consume! :not
533 | expr_n = consume_parser! ExprParser
534 | AST::Not.new(expr_n, not_t.start_pos, expr_n.end_pos)
535 | end
536 | end
537 |
538 | OPERATORS = [:+, :-, :*, :/, :"&&", :"||", :"===", :"!==", :>, :<, :">=", :"<=", :mod, :"==", :in]
539 |
540 | class OperatorParser < Parser
541 | def self.can_parse?(_self, lhs_n)
542 | _self.current_token&.is_one_of?(*OPERATORS) &&
543 | !_self.new_line?
544 | end
545 |
546 | def parse!(lhs_n)
547 | operator_t = consume_any!
548 | rhs_n = consume_parser! ExprParser
549 | AST::Op.new(lhs_n, operator_t.type, rhs_n, lhs_n.start_pos, rhs_n.end_pos)
550 | end
551 | end
552 |
553 | class DotParser < Parser
554 | def self.can_parse?(_self, lhs)
555 | _self.current_token&.type == :dot
556 | end
557 |
558 | def parse!(lhs_n)
559 | dot_t = consume! :dot
560 | rhs_n = consume_parser! IdentifierLookupParser
561 | AST::Dot.new(lhs_n, ".", rhs_n, dot_t.start_pos, rhs_n.end_pos)
562 | end
563 | end
564 |
565 | class BindParser < Parser
566 | def self.can_parse?(_self, lhs)
567 | _self.current_token&.type == :"::"
568 | end
569 |
570 | def parse_args_without_parens!
571 | args = []
572 |
573 | start_line = current_line
574 | loop do
575 | break if current_token.nil?
576 | break if current_line != start_line
577 | args.push consume_parser! ExprParser
578 | break if current_token.nil?
579 | break if current_line != start_line
580 | consume! :comma
581 | end
582 |
583 | args
584 | end
585 |
586 | def parse_args!
587 | return [] if new_line?
588 | return parse_args_without_parens! if current_token.type != :open_paren
589 |
590 | consume! :open_paren
591 | args = []
592 | loop do
593 | args.push consume_parser! ExprParser
594 | break if current_token.type == :close_paren
595 | consume! :comma
596 | end
597 | consume! :close_paren
598 |
599 | args
600 | end
601 |
602 | def parse!(lhs_n)
603 | bind_t = consume! :"::"
604 | fn_name_n = consume_parser! IdentifierLookupParser
605 | args = parse_args!
606 | AST::Bind.new(lhs_n, fn_name_n, args)
607 | end
608 | end
609 |
610 | class OptionalChainParser < Parser
611 | def self.can_parse?(_self, lhs)
612 | _self.current_token&.type == :"?."
613 | end
614 |
615 | def parse!(lhs)
616 | q_t = consume! :"?."
617 | property_t = consume! :identifier
618 | AST::OptionalChain.new(
619 | lhs,
620 | property_t.value,
621 | q_t.start_pos,
622 | property_t.end_pos
623 | )
624 | end
625 | end
626 |
627 | class AwaitParser < Parser
628 | def self.can_parse?(_self)
629 | _self.current_token&.type == :await
630 | end
631 |
632 | def parse!
633 | await_t = consume! :await
634 | expr_n = consume_parser! ExprParser
635 | AST::Await.new(expr_n, await_t.start_pos, expr_n.end_pos)
636 | end
637 | end
638 |
639 | ### OPERATORS END
640 |
641 | ### OBJECT LITERALS START
642 |
643 | class SimpleObjectEntryParser < Parser
644 | def self.can_parse?(_self)
645 | _self.current_token&.type == :identifier &&
646 | _self.peek_token.type == :colon
647 | end
648 |
649 | def parse!
650 | key_t = consume! :identifier
651 | consume! :colon
652 | value_n = consume_parser! ExprParser
653 |
654 | AST::SimpleObjectEntry.new(key_t.value, value_n, key_t.start_pos, value_n.end_pos)
655 | end
656 | end
657 |
658 | class ArrowMethodObjectEntryParser < Parser
659 | def self.can_parse?(_self)
660 | _self.current_token&.type == :identifier &&
661 | _self.peek_token.type == :open_paren &&
662 | _self.rest_of_line.include?("=>")
663 | end
664 |
665 | def parse!
666 | key_t = consume! :identifier
667 | args_n = consume_parser! SimpleFnArgsParser
668 | consume! :"=>"
669 | return_expr_n = consume_parser! ExprParser
670 | AST::ArrowMethodObjectEntry.new(
671 | key_t.value,
672 | AST::SingleLineArrowFnWithArgs.new(
673 | args_n,
674 | return_expr_n,
675 | args_n.start_pos,
676 | return_expr_n.end_pos
677 | ),
678 | key_t.start_pos,
679 | return_expr_n.end_pos
680 | )
681 | end
682 | end
683 |
684 | class FunctionObjectEntryParser < Parser
685 | def self.can_parse?(_self)
686 | _self.current_token&.type == :function
687 | end
688 |
689 | def parse!
690 | fn_n = consume_parser! FunctionDefinitionParser
691 | AST::FunctionObjectEntry.new(fn_n.name, fn_n, fn_n.start_pos, fn_n.end_pos)
692 | end
693 | end
694 |
695 | class IdentifierLookupParser < Parser
696 | def self.can_parse?(_self)
697 | _self.current_token&.type == :identifier
698 | end
699 |
700 | def parse!
701 | id_t = consume! :identifier
702 | AST::IdLookup.new(id_t.value, id_t.start_pos, id_t.end_pos)
703 | end
704 | end
705 |
706 | class SpreadObjectEntryParser < Parser
707 | def self.can_parse?(_self)
708 | _self.current_token&.type == :"..."
709 | end
710 |
711 | def parsers
712 | [
713 | IdentifierLookupParser,
714 | ObjectParser,
715 | ]
716 | end
717 |
718 | def parse!
719 | spread_t = consume! :"..."
720 | expr_n = consume_first_valid_parser! parsers
721 | AST::SpreadObjectEntry.new(expr_n, spread_t.start_pos, spread_t.end_pos)
722 | end
723 | end
724 |
725 | class ObjectParser < Parser
726 | def self.can_parse?(_self)
727 | _self.current_token&.type == :"{"
728 | end
729 |
730 | ENTRY_PARSERS = [
731 | SimpleObjectEntryParser,
732 | ArrowMethodObjectEntryParser,
733 | FunctionObjectEntryParser,
734 | SpreadObjectEntryParser,
735 | ]
736 |
737 | def parse!
738 | open_brace_t = consume! :"{"
739 | values = []
740 | loop do
741 | break if current_token.type == :"}"
742 | values.push consume_first_valid_parser! ENTRY_PARSERS
743 | consume_if_present! :comma
744 | break if current_token.type == :"}"
745 | end
746 | close_brace_t = consume! :"}"
747 | AST::ObjectLiteral.new(values, open_brace_t.start_pos, close_brace_t.end_pos)
748 | end
749 | end
750 |
751 | ### OBJECT LITERALS END
752 |
753 | ### ARRAY LITERALS START
754 |
755 | class ArrayComprehensionParser < Parser
756 | def parse!(expr_n, start_pos)
757 | consume! :for
758 | id_t = consume! :identifier
759 | consume! :in
760 | array_expr_n = consume_parser! ExprParser
761 | if_expr_n = nil
762 | if current_token.type == :if
763 | consume! :if
764 | if_expr_n = consume_parser! ExprParser
765 | end
766 | close_sq_b_t = consume! :"]"
767 | AST::ArrayComprehension.new(
768 | expr_n,
769 | id_t.value,
770 | array_expr_n,
771 | if_expr_n,
772 | start_pos,
773 | close_sq_b_t.end_pos
774 | )
775 | end
776 | end
777 |
778 | class ArrayParser < Parser
779 | def self.can_parse?(_self)
780 | _self.current_token&.type == :"["
781 | end
782 |
783 | def parse!
784 | open_sq_b_t = consume! :"["
785 | elems = []
786 | if current_token.type == :"]"
787 | close_sq_b_t = consume! :"]"
788 | return AST::ArrayLiteral.new(elems, open_sq_b_t.start_pos, close_sq_b_t.end_pos)
789 | end
790 |
791 | first_expr_n = consume_parser! ExprParser
792 |
793 | if current_token.type == :for
794 | return consume_parser! ArrayComprehensionParser, first_expr_n, open_sq_b_t.start_pos
795 | end
796 | elems.push first_expr_n
797 | loop do
798 | break if current_token.type == :"]"
799 | consume_if_present! :comma
800 | elems.push consume_parser! ExprParser
801 | end
802 | close_sq_b_t = consume! :"]"
803 | AST::ArrayLiteral.new(elems, open_sq_b_t.start_pos, close_sq_b_t.end_pos)
804 | end
805 | end
806 |
807 | ### ARRAY LITERALS END
808 |
809 | ### PRIMATIVES START
810 |
811 | class IntParser < Parser
812 | def self.can_parse?(_self)
813 | _self.current_token&.type == :int_lit
814 | end
815 |
816 | def parse!
817 | int_t = consume! :int_lit
818 | AST::Int.new(int_t.value, int_t.start_pos, int_t.end_pos)
819 | end
820 | end
821 |
822 | class FloatParser < Parser
823 | def self.can_parse?(_self)
824 | _self.current_token&.type == :float_lit
825 | end
826 |
827 | def parse!
828 | float_t = consume! :float_lit
829 | AST::Float.new(float_t.value, float_t.start_pos, float_t.end_pos)
830 | end
831 | end
832 |
833 | class SimpleStringParser < Parser
834 | def self.can_parse?(_self)
835 | _self.current_token&.type == :str_lit
836 | end
837 |
838 | def parse!
839 | str_t = consume! :str_lit
840 | AST::SimpleString.new(str_t.value, str_t.start_pos, str_t.end_pos)
841 | end
842 | end
843 |
844 | class BoolParser < Parser
845 | def self.can_parse?(_self)
846 | _self.current_token&.type == :bool_lit
847 | end
848 |
849 | def parse!
850 | bool_t = consume! :bool_lit
851 | AST::Bool.new(bool_t.value, bool_t.start_pos, bool_t.end_pos)
852 | end
853 | end
854 |
855 | class NullParser < Parser
856 | def self.can_parse?(_self)
857 | _self.current_token&.type == :null
858 | end
859 |
860 | def parse!
861 | null_t = consume! :null
862 | AST::Null.new(null_t.start_pos, null_t.end_pos)
863 | end
864 | end
865 |
866 | ### PRIMATIVES END
867 |
868 | ### ASSIGNMENTS START
869 |
870 | class SimpleReassignmentParser < Parser
871 | def self.can_parse?(_self)
872 | _self.current_token&.type == :identifier &&
873 | _self.peek_token&.type == :"="
874 | end
875 |
876 | def parse!
877 | id_t = consume! :identifier
878 | consume! :"="
879 | expr_n = consume_parser! ExprParser
880 |
881 | AST::SimpleReassignment.new(id_t.value, expr_n, id_t.start_pos, expr_n.end_pos)
882 | end
883 | end
884 |
885 | class SimpleAssignmentParser < Parser
886 | def self.can_parse?(_self)
887 | _self.current_token&.type == :identifier &&
888 | _self.peek_token&.type == :assign
889 | end
890 |
891 | def parse!
892 | id_t = consume! :identifier
893 | consume! :assign
894 | expr_n = consume_parser! ExprParser
895 |
896 | AST::SimpleAssignment.new(id_t.value, expr_n, id_t.start_pos, expr_n.end_pos)
897 | end
898 | end
899 |
900 | class DefaultAssignmentParser < Parser
901 | def self.can_parse?(_self, lhs)
902 | _self.current_token&.type == :"||="
903 | end
904 |
905 | def parse!(lhs_n)
906 | consume! :"||="
907 | expr_n = consume_parser! ExprParser
908 | AST::DefaultAssignment.new(lhs_n, expr_n, lhs_n.start_pos, expr_n.end_pos)
909 | end
910 | end
911 |
912 | class PlusAssignmentParser < Parser
913 | def self.can_parse?(_self, lhs)
914 | _self.current_token&.type == :"+="
915 | end
916 |
917 | def parse!(lhs_n)
918 | consume! :"+="
919 | expr_n = consume_parser! ExprParser
920 | AST::PlusAssignment.new(lhs_n, expr_n, lhs_n.start_pos, expr_n.end_pos)
921 | end
922 | end
923 |
924 | class DotAssignmentParser < Parser
925 | def self.can_parse?(_self, lhs)
926 | lhs.is_a?(AST::Dot) &&
927 | _self.current_token&.type == :assign
928 | end
929 |
930 | def parse!(lhs_n)
931 | assign_t = consume! :assign
932 | expr_n = consume_parser! ExprParser
933 | AST::DotAssignment.new(
934 | lhs_n,
935 | expr_n,
936 | assign_t.start_pos,
937 | expr_n.end_pos
938 | )
939 | end
940 | end
941 |
942 | class ArrayAssignmentParser < Parser
943 | def self.can_parse?(_self)
944 | _self.current_token.type == :"[" &&
945 | _self.rest_of_line.include?(":=")
946 | end
947 |
948 | def parse!
949 | open_t = consume! :"["
950 | variables = []
951 | loop do
952 | variables.push consume!(:identifier).value
953 | break if current_token.type == :"]"
954 | consume! :comma
955 | end
956 | close_t = consume! :"]"
957 | consume! :assign
958 | expr_n = consume_parser! ExprParser
959 | AST::ArrayAssignment.new(
960 | variables,
961 | expr_n,
962 | open_t.start_pos,
963 | expr_n.end_pos
964 | )
965 | end
966 | end
967 |
968 | ### ASSIGNMENTS END
969 |
970 | ### CASE EXPRESSIONS START
971 |
972 | class SimpleWhenParser < Parser
973 | def self.can_parse?(_self)
974 | _self.current_token&.type == :when
975 | end
976 |
977 | def parse!
978 | when_t = consume! :when
979 | expr_n = consume_parser! ExprParser
980 | body = consume_parser! ProgramParser, end_tokens: [:when, :else]
981 | AST::SimpleWhen.new(expr_n, body, when_t.start_pos, body.last&.end_pos || expr_n.end_pos)
982 | end
983 | end
984 |
985 | class CaseElseParser < Parser
986 | def self.can_parse?(_self)
987 | _self.current_token&.type == :else
988 | end
989 |
990 | def parse!
991 | else_t = consume! :else
992 | body = consume_parser! ProgramParser
993 | AST::CaseElse.new(body, else_t.start_pos, body.last&.end_pos || else_t.end_pos)
994 | end
995 | end
996 |
997 | class WhenParser < Parser
998 | PARSERS = [SimpleWhenParser, CaseElseParser]
999 |
1000 | def self.can_parse?(_self)
1001 | PARSERS.any? { |klass| klass.can_parse? _self }
1002 | end
1003 |
1004 | def parse!
1005 | consume_first_valid_parser! PARSERS
1006 | end
1007 | end
1008 |
1009 | class EmptyCaseExprParser < Parser
1010 | def self.can_parse?(_self)
1011 | _self.current_token&.type == :case &&
1012 | _self.new_line?(1)
1013 | end
1014 |
1015 | def parse!
1016 | case_t = consume! :case
1017 | cases = []
1018 | loop do
1019 | break if current_token&.type == :end
1020 | cases.push consume_parser! WhenParser
1021 | end
1022 | end_t = consume! :end
1023 |
1024 | AST::EmptyCaseExpr.new(cases, case_t.start_pos, end_t.end_pos)
1025 | end
1026 | end
1027 |
1028 | ### CASE EXPRESSIONS END
1029 |
1030 | ### FOR LOOPS START
1031 |
1032 | class SimpleForOfLoopParser < Parser
1033 | def self.can_parse?(_self)
1034 | _self.current_token&.type == :for &&
1035 | _self.peek_token.type == :identifier &&
1036 | _self.peek_token_twice.type == :of
1037 | end
1038 |
1039 | def parse!
1040 | for_t = consume! :for
1041 | iter_var_t = consume! :identifier
1042 | consume! :of
1043 | arr_expr_n = consume_parser! ExprParser
1044 | body = consume_parser! ProgramParser
1045 | end_t = consume! :end
1046 | AST::SimpleForOfLoop.new(iter_var_t.value, arr_expr_n, body, for_t.start_pos, end_t.end_pos)
1047 | end
1048 | end
1049 |
1050 | class ForOfObjDeconstructLoopParser < Parser
1051 | def self.can_parse?(_self)
1052 | _self.current_token&.type == :for &&
1053 | _self.peek_token.type == :"{"
1054 | end
1055 |
1056 | def parse!
1057 | for_t = consume! :for
1058 | properties = []
1059 | consume! :"{"
1060 | loop do
1061 | properties.push consume!(:identifier).value
1062 | break if current_token.type == :"}"
1063 | consume! :comma
1064 | end
1065 | consume! :"}"
1066 | consume! :of
1067 | arr_expr_n = consume_parser! ExprParser
1068 | body = consume_parser! ProgramParser
1069 | end_t = consume! :end
1070 | AST::ForOfObjDeconstructLoop.new(properties, arr_expr_n, body, for_t.start_pos, end_t.end_pos)
1071 | end
1072 | end
1073 |
1074 | class SimpleForInLoopParser < Parser
1075 | def self.can_parse?(_self)
1076 | _self.current_token.type == :for &&
1077 | _self.peek_token.type == :identifier &&
1078 | _self.peek_token_twice.type == :in
1079 | end
1080 |
1081 | def parse!
1082 | for_t = consume! :for
1083 | variable_t = consume! :identifier
1084 | consume! :in
1085 | object_n = consume_parser! ExprParser
1086 | body = consume_parser! ProgramParser
1087 | end_t = consume! :end
1088 | AST::SimpleForInLoop.new(
1089 | variable_t.value,
1090 | object_n,
1091 | body,
1092 | for_t.start_pos,
1093 | end_t.end_pos
1094 | )
1095 | end
1096 | end
1097 |
1098 | ### FOR LOOPS END
1099 |
1100 | ### XML START
1101 |
1102 | class SimpleElementParser < Parser
1103 | def self.can_parse?(_self)
1104 | _self.current_token&.type == :< &&
1105 | _self.peek_token&.type == :identifier &&
1106 | _self.peek_token_twice&.type == :>
1107 | end
1108 |
1109 | def parse!
1110 | open_t = consume! :<
1111 | name_t = consume! :identifier
1112 | consume! :>
1113 | children = []
1114 | loop do
1115 | break if !ElementParser.can_parse?(self)
1116 | children.push consume_parser! ElementParser
1117 | end
1118 | consume! :""
1119 | consume! :identifier
1120 | close_t = consume! :>
1121 | AST::SimpleElement.new(
1122 | name_t.value,
1123 | children,
1124 | open_t.start_pos,
1125 | close_t.end_pos
1126 | )
1127 | end
1128 | end
1129 |
1130 | class EscapedElementExprParser < Parser
1131 | def self.can_parse?(_self)
1132 | _self.current_token&.type == :"{"
1133 | end
1134 |
1135 | def parse!
1136 | open_t = consume! :"{"
1137 | expr_n = consume_parser! ExprParser
1138 | close_t = consume! :"}"
1139 | AST::EscapedElementExpr.new(
1140 | expr_n,
1141 | open_t.start_pos,
1142 | close_t.end_pos
1143 | )
1144 | end
1145 | end
1146 |
1147 | class ElementParser < Parser
1148 | PARSERS = [
1149 | SimpleElementParser,
1150 | EscapedElementExprParser,
1151 | ]
1152 |
1153 | def self.can_parse?(_self)
1154 | PARSERS.any? { |klass| klass.can_parse? _self }
1155 | end
1156 |
1157 | def parse!
1158 | consume_first_valid_parser! PARSERS
1159 | end
1160 | end
1161 |
1162 | ### XML END
1163 |
1164 | class ThisParser < Parser
1165 | def self.can_parse?(_self)
1166 | _self.current_token&.type == :this
1167 | end
1168 |
1169 | def parse!
1170 | this_t = consume! :this
1171 | AST::This.new(this_t.start_pos, this_t.end_pos)
1172 | end
1173 | end
1174 |
1175 | class ConstructableParser < Parser
1176 | PARSERS = [
1177 | IdentifierLookupParser,
1178 | ThisParser,
1179 | ]
1180 |
1181 | def self.can_parse?(_self)
1182 | PARSERS.any? { |klass| klass.can_parse? _self }
1183 | end
1184 |
1185 | def parse!
1186 | consume_first_valid_parser! PARSERS
1187 | end
1188 | end
1189 |
1190 | class NewParser < Parser
1191 | def self.can_parse?(_self)
1192 | _self.current_token&.type == :new
1193 | end
1194 |
1195 | def parse!
1196 | new_t = consume! :new
1197 | class_expr_n = consume_parser! ConstructableParser
1198 | open_p_t = consume! :open_paren
1199 | args = []
1200 | loop do
1201 | args.push consume_parser! ExprParser
1202 | consume_if_present! :comma
1203 | break if current_token.type == :close_paren
1204 | end
1205 | close_p_t = consume! :close_paren
1206 | AST::New.new(
1207 | class_expr_n,
1208 | args,
1209 | new_t.start_pos,
1210 | close_p_t.end_pos
1211 | )
1212 | end
1213 | end
1214 |
1215 | class DynamicLookupParser < Parser
1216 | def self.can_parse?(_self, lhs)
1217 | # a[1] is valid, but a [1]
1218 | _self.current_token&.type == :"[" &&
1219 | _self.current_token.start_pos == _self.prev_token.end_pos
1220 | end
1221 |
1222 | def parse!(lhs)
1223 | open_t = consume! :"["
1224 | expr_n = consume_parser! ExprParser
1225 | close_t = consume! :"]"
1226 | AST::DynamicLookup.new(
1227 | lhs,
1228 | expr_n,
1229 | open_t.start_pos,
1230 | close_t.end_pos
1231 | )
1232 | end
1233 | end
1234 |
1235 | class SchemaCaptureParser < Parser
1236 | def self.can_parse?(_self)
1237 | _self.current_token&.type == :capture
1238 | end
1239 |
1240 | def parse!
1241 | id_t = consume! :capture
1242 | AST::SchemaCapture.new(id_t.value, id_t.start_pos, id_t.end_pos)
1243 | end
1244 | end
1245 |
1246 | ### RANGE START
1247 |
1248 | class RangeOperandParser < Parser
1249 | PARSERS = [
1250 | IdentifierLookupParser,
1251 | IntParser,
1252 | FloatParser,
1253 | SimpleStringParser,
1254 | ]
1255 |
1256 | def self.can_parse?(_self)
1257 | PARSERS.any? { |klass| klass.can_parse _self }
1258 | end
1259 |
1260 | def parse!
1261 | consume_first_valid_parser! PARSERS
1262 | end
1263 | end
1264 |
1265 | class RangeParser < Parser
1266 | def self.can_parse?(_self, lhs_n)
1267 | _self.current_token&.type == :".."
1268 | end
1269 |
1270 | def parse!(lhs_n)
1271 | consume! :".."
1272 | rhs_n = consume_parser! RangeOperandParser
1273 | AST::Range.new(lhs_n, rhs_n, lhs_n.start_pos, rhs_n.end_pos)
1274 | end
1275 | end
1276 |
1277 | ### RANGE END
1278 |
1279 | ### SCHEMA START
1280 |
1281 | class SchemaObjectParser < Parser
1282 | def self.can_parse?(_self)
1283 | _self.current_token&.type == :"{"
1284 | end
1285 |
1286 | VALUE_PARSERS = [
1287 | IntParser,
1288 | FloatParser,
1289 | SimpleStringParser,
1290 | ShortAnonFnParser,
1291 | SchemaCaptureParser,
1292 | BoolParser,
1293 | ]
1294 |
1295 | def parse_value!(key_name, start_pos, end_pos)
1296 | return AST::SchemaCapture.new(key_name, start_pos, end_pos) if current_token.type != :colon
1297 | consume! :colon
1298 | consume_first_valid_parser! VALUE_PARSERS
1299 | end
1300 |
1301 | def parse!
1302 | open_b_t = consume! :"{"
1303 | properties = []
1304 | loop do
1305 | property_t = consume! :identifier
1306 | properties.push [property_t.value, parse_value!(property_t.value, property_t.start_pos, property_t.end_pos)]
1307 | break if current_token.type == :"}"
1308 | consume! :comma
1309 | end
1310 | end_b_t = consume! :"}"
1311 | AST::SchemaObjectLiteral.new(properties, open_b_t.start_pos, end_b_t.end_pos)
1312 | end
1313 | end
1314 |
1315 | class SchemaExprParser < Parser
1316 | SCHEMA_PARSERS = [
1317 | SchemaObjectParser,
1318 | IntParser,
1319 | SimpleStringParser,
1320 | IdentifierLookupParser,
1321 | BoolParser,
1322 | ShortAnonFnParser,
1323 | NullSchemaParser,
1324 | ]
1325 |
1326 | def parse!
1327 | consume_first_valid_parser! SCHEMA_PARSERS
1328 | end
1329 | end
1330 |
1331 | class SchemaUnionParser < Parser
1332 | def self.can_parse?(_self)
1333 | _self.current_token&.type == :"|"
1334 | end
1335 |
1336 | def parse!(first_schema_expr_n)
1337 | schema_exprs = [first_schema_expr_n]
1338 | loop do
1339 | break if !self.class.can_parse?(self)
1340 | consume! :"|"
1341 | schema_exprs.push consume_parser! SchemaExprParser
1342 | end
1343 |
1344 | AST::SchemaUnion.new(schema_exprs, first_schema_expr_n.start_pos, schema_exprs[-1].end_pos)
1345 | end
1346 | end
1347 |
1348 | class SchemaIntersectParser < Parser
1349 | def self.can_parse?(_self)
1350 | _self.current_token&.type == :"&"
1351 | end
1352 |
1353 | def parse!(first_schema_expr_n)
1354 | schema_exprs = [first_schema_expr_n]
1355 |
1356 | loop do
1357 | break if !self.class.can_parse?(self)
1358 | consume! :"&"
1359 | schema_exprs.push consume_parser! SchemaExprParser
1360 | end
1361 |
1362 | AST::SchemaIntersect.new(schema_exprs, first_schema_expr_n.start_pos, schema_exprs[-1].end_pos)
1363 | end
1364 | end
1365 |
1366 | class SchemaDefinitionParser < Parser
1367 | def self.can_parse?(_self)
1368 | _self.current_token&.type == :schema
1369 | end
1370 |
1371 | OP_PARSERS = [
1372 | SchemaUnionParser,
1373 | SchemaIntersectParser,
1374 | ]
1375 |
1376 | def parse!
1377 | schema_t = consume! :schema
1378 | name_t = consume! :identifier
1379 | consume! :"="
1380 | expr_n = consume_parser! SchemaExprParser
1381 |
1382 | loop do
1383 | op_parser_klass = OP_PARSERS.find do |parser_klass|
1384 | parser_klass.can_parse?(self)
1385 | end
1386 | break if !op_parser_klass
1387 | expr_n = consume_parser! op_parser_klass, expr_n
1388 | end
1389 |
1390 | AST::SchemaDefinition.new(name_t.value, expr_n, schema_t.start_pos, expr_n.end_pos)
1391 | end
1392 | end
1393 |
1394 | class SimpleSchemaAssignmentParser < Parser
1395 | def self.can_parse?(_self)
1396 | _self.current_token&.type == :identifier &&
1397 | _self.peek_token.type == :open_paren &&
1398 | _self.peek_token_twice.type == :identifier &&
1399 | _self.peek_token_thrice.type == :close_paren
1400 | end
1401 |
1402 | def parse!
1403 | schema_name_t = consume! :identifier
1404 | consume! :open_paren
1405 | var_t = consume! :identifier
1406 | consume! :close_paren
1407 | consume! :assign
1408 | expr_n = consume_parser! ExprParser
1409 | AST::SimpleSchemaAssignment.new(
1410 | schema_name_t.value,
1411 | var_t.value,
1412 | expr_n,
1413 | schema_name_t.start_pos,
1414 | expr_n.end_pos
1415 | )
1416 | end
1417 | end
1418 |
1419 | class ThisSchemaArgParser < Parser
1420 | def self.can_parse?(_self)
1421 | _self.rest_of_line.include?("::")
1422 | end
1423 |
1424 | def parse!
1425 | schema = consume_parser! SchemaExprParser
1426 | bind_t = consume! :"::"
1427 | AST::ThisSchemaArg.new(schema, schema.start_pos, bind_t.end_pos)
1428 | end
1429 | end
1430 |
1431 | class SpreadExprParser < Parser
1432 | def self.can_parse?(_self)
1433 | _self.current_token&.type == :"..."
1434 | end
1435 |
1436 | def parse!
1437 | spread_t = consume! :"..."
1438 | expr_n = consume_parser! ExprParser
1439 |
1440 | AST::SpreadExpr.new(expr_n, spread_t.start_pos, expr_n.end_pos)
1441 | end
1442 | end
1443 |
1444 | ### SCHEMA END
1445 |
1446 | class EnumExpr < Parser
1447 | def self.can_parse?(_self)
1448 | _self.current_token&.type == :identifier &&
1449 | _self.peek_token&.type == :hash_id
1450 | end
1451 |
1452 | def parse!
1453 | enum_name_t = consume! :identifier
1454 | variant_t = consume! :hash_id
1455 | AST::EnumExpr.new(enum_name_t.value, variant_t.value, enum_name_t.start_pos, variant_t.end_pos)
1456 | end
1457 | end
1458 |
1459 | class ExprParser < Parser
1460 | # order matters
1461 | PRIMARY_PARSERS = [
1462 | EnumExpr,
1463 | NullParser,
1464 | IntParser,
1465 | FloatParser,
1466 | ThisParser,
1467 | BoolParser,
1468 | SimpleStringParser,
1469 | AnonIdLookupParser,
1470 | NewParser,
1471 | ArrayParser,
1472 | ObjectParser,
1473 | MultiLineArrowFnWithArgsParser,
1474 | SingleLineArrowFnWithOneArgParser,
1475 | IdentifierLookupParser,
1476 | ShortAnonFnWithNamedArgParser,
1477 | ShortAnonFnParser,
1478 | SingleLineArrowFnWithoutArgsParser,
1479 | SingleLineArrowFnWithArgsParser,
1480 | AwaitParser,
1481 | SpreadExprParser,
1482 | ElementParser,
1483 | SchemaCaptureParser,
1484 | EmptyCaseExprParser,
1485 | NotParser,
1486 | ]
1487 |
1488 | SECONDARY_PARSERS = [
1489 | RangeParser,
1490 | OperatorParser,
1491 | DotAssignmentParser,
1492 | DotParser,
1493 | BindParser,
1494 | OptionalChainParser,
1495 | DynamicLookupParser,
1496 | DefaultAssignmentParser,
1497 | PlusAssignmentParser,
1498 | FunctionCallWithoutArgs,
1499 | FunctionCallWithArgs,
1500 | FunctionCallWithArgsWithoutParens,
1501 | ]
1502 |
1503 | def parse!
1504 | expr_n = consume_first_valid_parser! PRIMARY_PARSERS do
1505 | binding.pry
1506 | end
1507 | loop do
1508 | secondary_klass = SECONDARY_PARSERS.find { |parser_klass| parser_klass.can_parse?(self, expr_n) }
1509 | break if !secondary_klass
1510 | expr_n = consume_parser! secondary_klass, expr_n
1511 | end
1512 | expr_n
1513 | end
1514 | end
1515 |
1516 | class CaseFunctionParser < Parser
1517 | def self.can_parse?(_self)
1518 | _self.current_token.type == :case &&
1519 | _self.peek_token.type == :function
1520 | end
1521 |
1522 | def parse_case!
1523 | when_t = consume! :when
1524 | this_schema_arg = consume_parser! ThisSchemaArgParser if ThisSchemaArgParser.can_parse? self
1525 | has_open_paren = !!consume!(:open_paren) if current_token.type == :open_paren
1526 | arg_patterns = []
1527 | loop do
1528 | break if current_token.type == :close_paren
1529 | arg_patterns.push consume_parser! SchemaArgParser
1530 | break if current_token.type == :close_paren
1531 | break if !has_open_paren
1532 | consume! :comma
1533 | end
1534 | consume! :close_paren if has_open_paren
1535 | body_n = consume_parser! FunctionBodyParser, end_tokens: [:when]
1536 | AST::CaseFnPattern.new(this_schema_arg, arg_patterns, body_n, when_t.start_pos, body_n[-1].end_pos)
1537 | end
1538 |
1539 | def parse!
1540 | case_t = consume! :case
1541 | consume! :function
1542 | name_t = consume! :identifier
1543 | patterns = []
1544 | while current_token.type != :end
1545 | patterns.push parse_case!
1546 | end
1547 | end_t = consume! :end
1548 | AST::CaseFunctionDefinition.new(
1549 | name_t.value,
1550 | patterns,
1551 | case_t.start_pos,
1552 | end_t.end_pos
1553 | )
1554 | end
1555 | end
1556 |
1557 | class SingleLineBindFunctionDefinitionParser < Parser
1558 | def self.can_parse?(_self)
1559 | _self.current_token&.type == :function &&
1560 | _self.peek_token&.type == :identifier &&
1561 | _self.peek_token_twice&.type == :"::" &&
1562 | _self.rest_of_line.include?("=")
1563 | end
1564 |
1565 | def parse!
1566 | fn_t = consume! :function
1567 | obj_name_t = consume! :identifier
1568 | consume! :"::"
1569 | fn_name_t = consume! :identifier
1570 | args = consume_parser! SimpleFnArgsParser
1571 | consume! :"="
1572 | return_expr_n = consume_parser! ExprParser
1573 | AST::SingleLineBindFunctionDefinition.new(
1574 | obj_name_t.value,
1575 | fn_name_t.value,
1576 | args,
1577 | return_expr_n,
1578 | fn_t.start_pos,
1579 | return_expr_n.end_pos
1580 | )
1581 | end
1582 | end
1583 |
1584 | class MultiLineBindFunctionDefinitionParser < Parser
1585 | def self.can_parse?(_self)
1586 | _self.current_token&.type == :function &&
1587 | _self.peek_token&.type == :identifier &&
1588 | _self.peek_token_twice&.type == :"::" &&
1589 | !_self.rest_of_line.include?("=")
1590 | end
1591 |
1592 | def parse!
1593 | fn_t = consume! :function
1594 | obj_name_t = consume! :identifier
1595 | consume! :"::"
1596 | fn_name_t = consume! :identifier
1597 | args = consume_parser! SimpleFnArgsParser
1598 | body = consume_parser! FunctionBodyParser
1599 | end_t = consume! :end
1600 | AST::MultiLineBindFunctionDefinition.new(
1601 | obj_name_t.value,
1602 | fn_name_t.value,
1603 | args,
1604 | body,
1605 | fn_t.start_pos,
1606 | end_t.end_pos
1607 | )
1608 | end
1609 | end
1610 |
1611 | class FunctionDefinitionParser < Parser
1612 | PARSERS = [
1613 | SingleLineDefWithArgsParser,
1614 | SingleLineBindFunctionDefinitionParser,
1615 | MultiLineBindFunctionDefinitionParser,
1616 | SingleLineDefWithoutArgsParser,
1617 | MultilineDefWithArgsParser,
1618 | MultilineDefWithoutArgsParser,
1619 | CaseFunctionParser,
1620 | ]
1621 |
1622 | def self.can_parse?(_self)
1623 | PARSERS.find { |klass| klass.can_parse? _self }
1624 | end
1625 |
1626 | def parse!
1627 | consume_first_valid_parser! PARSERS
1628 | end
1629 | end
1630 |
1631 | ### COMPONENTS START
1632 |
1633 | class BodyComponentWithoutAttrsParser < Parser
1634 | def self.can_parse?(_self)
1635 | _self.current_token.type == :component
1636 | end
1637 |
1638 | def parse!
1639 | component_t = consume! :component
1640 | name_t = consume! :identifier
1641 | constructor_body = consume_parser! ComponentConstructorParser
1642 | assert { constructor_body.all? { |node| node.is_a? AST::SimpleAssignment } }
1643 | consume! :in
1644 | expr_n = consume_parser! ExprParser
1645 | end_t = consume! :end
1646 | AST::BodyComponentWithoutAttrs.new(
1647 | name_t.value,
1648 | constructor_body,
1649 | expr_n,
1650 | component_t.start_pos,
1651 | end_t.end_pos
1652 | )
1653 | end
1654 | end
1655 |
1656 | class SimpleComponentWithAttrsParser < Parser
1657 | def self.can_parse?(_self)
1658 | _self.current_token.type == :component &&
1659 | _self.peek_token_twice.type == :"{" &&
1660 | _self.rest_of_line.include?("in")
1661 | end
1662 |
1663 | def parse!
1664 | component_t = consume! :component
1665 | name_t = consume! :identifier
1666 | attributes = consume_parser! SchemaObjectParser
1667 | consume! :in
1668 | expr_n = consume_parser! ExprParser
1669 | end_t = consume! :end
1670 | AST::ExprComponentWithAttributes.new(
1671 | name_t.value,
1672 | attributes,
1673 | expr_n,
1674 | component_t.start_pos,
1675 | end_t.end_pos
1676 | )
1677 | end
1678 | end
1679 |
1680 | class SimpleComponentParser < Parser
1681 | def self.can_parse?(_self)
1682 | _self.current_token.type == :component &&
1683 | _self.peek_token_twice.type == :in
1684 | end
1685 |
1686 | def parse!
1687 | component_t = consume! :component
1688 | name_t = consume! :identifier
1689 | consume! :in
1690 | expr_n = consume_parser! ExprParser
1691 | end_t = consume! :end
1692 | AST::ExprComponent.new(
1693 | name_t.value,
1694 | expr_n,
1695 | component_t.start_pos,
1696 | end_t.end_pos
1697 | )
1698 | end
1699 | end
1700 |
1701 | ### COMPONENTS END
1702 |
1703 | ### CLASSES START
1704 |
1705 | class ConstructorWithoutArgsParser < Parser
1706 | def self.can_parse?(_self)
1707 | _self.current_token&.type == :function &&
1708 | _self.peek_token&.value == "constructor"
1709 | end
1710 |
1711 | def parse!
1712 | function_t = consume! :function
1713 | fn_name_t = consume! :identifier
1714 | body = consume_parser! ProgramParser
1715 | end_t = consume! :end
1716 | AST::ConstructorWithoutArgs.new(
1717 | fn_name_t.value,
1718 | body,
1719 | function_t.start_pos,
1720 | end_t.end_pos
1721 | )
1722 | end
1723 | end
1724 |
1725 | class ConstructorWithArgsParser < Parser
1726 | def self.can_parse?(_self)
1727 | _self.current_token&.type == :function &&
1728 | _self.peek_token&.value == "constructor" &&
1729 | _self.peek_token_twice.type == :open_paren
1730 | end
1731 |
1732 | def parse!
1733 | function_t = consume! :function
1734 | fn_name_t = consume! :identifier
1735 | args_n = consume_parser! SimpleFnArgsParser
1736 | body = consume_parser! ProgramParser
1737 | end_t = consume! :end
1738 | AST::ConstructorWithArgs.new(
1739 | fn_name_t.value,
1740 | args_n,
1741 | body,
1742 | function_t.start_pos,
1743 | end_t.end_pos
1744 | )
1745 | end
1746 | end
1747 |
1748 | class StaticMethodWithArgsParser < Parser
1749 | def self.can_parse?(_self)
1750 | _self.current_token.type == :static &&
1751 | _self.peek_token.type == :function
1752 | end
1753 |
1754 | def parse!
1755 | static_t = consume! :static
1756 | consume! :function
1757 | name_t = consume! :identifier
1758 | args_n = consume_parser! SimpleFnArgsParser
1759 | body = consume_parser! FunctionBodyParser
1760 | end_t = consume! :end
1761 | AST::StaticMethod.new(
1762 | name_t.value,
1763 | args_n,
1764 | body,
1765 | static_t.start_pos,
1766 | end_t.end_pos
1767 | )
1768 | end
1769 | end
1770 |
1771 | class DefaultConstructorArgParser < Parser
1772 | def self.can_parse?(_self)
1773 | _self.current_token&.type == :"@" &&
1774 | _self.peek_token&.type == :identifier &&
1775 | _self.peek_token_twice&.type == :"="
1776 | end
1777 |
1778 | def parse!
1779 | at_t = consume! :"@"
1780 | name_t = consume! :identifier
1781 | consume! :"="
1782 | expr_n = consume_parser! ExprParser
1783 |
1784 | AST::DefaultConstructorArg.new(name_t.value, expr_n, at_t.start_pos, expr_n.end_pos)
1785 | end
1786 | end
1787 |
1788 | class SimpleConstructorArgParser < Parser
1789 | def self.can_parse?(_self)
1790 | _self.current_token&.type == :"@" &&
1791 | _self.peek_token&.type == :identifier
1792 | end
1793 |
1794 | def parse!
1795 | at_t = consume! :"@"
1796 | name_t = consume! :identifier
1797 |
1798 | AST::SimpleConstructorArg.new(name_t.value, at_t.start_pos, name_t.end_pos)
1799 | end
1800 | end
1801 |
1802 | class ConstructorArgParser < Parser
1803 | PARSERS = [
1804 | DefaultConstructorArgParser,
1805 | SimpleConstructorArgParser,
1806 | ]
1807 |
1808 | def self.can_parse?(_self)
1809 | PARSERS.any? { |klass| klass.can_parse? _self }
1810 | end
1811 |
1812 | def parse!
1813 | consume_first_valid_parser! PARSERS
1814 | end
1815 | end
1816 |
1817 | class ShortHandConstructorParser < Parser
1818 | def self.can_parse?(_self)
1819 | _self.current_token.type == :function &&
1820 | _self.peek_token.value == "constructor" &&
1821 | _self.peek_token_thrice.type == :"@"
1822 | end
1823 |
1824 | def parse!
1825 | function_t = consume! :function
1826 | consume! :identifier
1827 | consume! :open_paren
1828 | args = []
1829 | loop do
1830 | args.push consume_parser! ConstructorArgParser
1831 | break if current_token.type == :close_paren
1832 | consume! :comma
1833 | end
1834 | close_t = consume! :close_paren
1835 | AST::ShortHandConstructor.new(
1836 | args,
1837 | function_t.start_pos,
1838 | close_t.end_pos
1839 | )
1840 | end
1841 | end
1842 |
1843 | class OneLineGetterParser < Parser
1844 | def self.can_parse?(_self)
1845 | _self.current_token.type == :get &&
1846 | _self.peek_token.type == :identifier &&
1847 | _self.peek_token_twice.type == :"="
1848 | end
1849 |
1850 | def parse!
1851 | get_t = consume! :get
1852 | name_t = consume! :identifier
1853 | consume! :"="
1854 | expr_n = consume_parser! ExprParser
1855 | AST::OneLineGetter.new(
1856 | name_t.value,
1857 | expr_n,
1858 | get_t.start_pos,
1859 | expr_n.end_pos
1860 | )
1861 | end
1862 | end
1863 |
1864 | class InstancePropertyParser < Parser
1865 | def self.can_parse?(_self)
1866 | _self.current_token.type == :identifier &&
1867 | _self.peek_token.type == :assign
1868 | end
1869 |
1870 | def parse!
1871 | id_t = consume! :identifier
1872 | consume! :assign
1873 | expr_n = consume_parser! ExprParser
1874 | AST::InstanceProperty.new(
1875 | id_t.value,
1876 | expr_n,
1877 | id_t.start_pos,
1878 | expr_n.end_pos
1879 | )
1880 | end
1881 | end
1882 |
1883 | class ClassParser < Parser
1884 | def self.can_parse?(_self)
1885 | _self.current_token.type == :class
1886 | end
1887 |
1888 | PARSERS = [
1889 | InstancePropertyParser,
1890 | ShortHandConstructorParser,
1891 | ConstructorWithArgsParser,
1892 | ConstructorWithoutArgsParser,
1893 | StaticMethodWithArgsParser,
1894 | OneLineGetterParser,
1895 | FunctionDefinitionParser,
1896 | ]
1897 |
1898 | def parse_parent_class!
1899 | return nil if current_token.type != :<
1900 | consume! :<
1901 | parent_class_t = consume! :identifier
1902 | return parent_class_t.value
1903 | end
1904 |
1905 | def parse!
1906 | class_t = consume! :class
1907 | class_name_t = consume! :identifier
1908 | parent_class = parse_parent_class!
1909 | entries = []
1910 | loop do
1911 | break if !PARSERS.any? { |klass| klass.can_parse? self }
1912 | entries.push consume_first_valid_parser! PARSERS
1913 | end
1914 | end_t = consume! :end
1915 | AST::Class.new(
1916 | class_name_t.value,
1917 | parent_class,
1918 | entries,
1919 | class_t.start_pos,
1920 | end_t.end_pos
1921 | )
1922 | end
1923 | end
1924 |
1925 | ### CLASSES END
1926 |
1927 | ### IF STATEMENTS START
1928 |
1929 | class ElseIfParser < Parser
1930 | def self.can_parse?(_self)
1931 | _self.current_token&.type == :else &&
1932 | _self.peek_token&.type == :if
1933 | end
1934 |
1935 | def parse!
1936 | else_t = consume! :else
1937 | consume! :if
1938 | cond_n = consume_parser! ExprParser
1939 | body_n = consume_parser! ProgramParser, end_tokens: [:else]
1940 | AST::ElseIf.new(cond_n, body_n, else_t.start_pos, body_n.last&.end_pos)
1941 | end
1942 | end
1943 |
1944 | class ElseParser < Parser
1945 | def self.can_parse?(_self)
1946 | _self.current_token&.type == :else
1947 | end
1948 |
1949 | def parse!
1950 | else_t = consume! :else
1951 | body_n = consume_parser! ProgramParser
1952 | AST::Else.new(body_n, else_t.start_pos, body_n.last&.end_pos)
1953 | end
1954 | end
1955 |
1956 | class ElseBranchParser < Parser
1957 | PARSERS = [
1958 | ElseIfParser,
1959 | ElseParser,
1960 | ]
1961 |
1962 | def self.can_parse?(_self)
1963 | PARSERS.any? { |klass| klass.can_parse? _self }
1964 | end
1965 |
1966 | def parse!
1967 | consume_first_valid_parser! PARSERS
1968 | end
1969 | end
1970 |
1971 | class IfParser < Parser
1972 | def self.can_parse?(_self)
1973 | _self.current_token&.type == :if
1974 | end
1975 |
1976 | def parse!
1977 | if_t = consume! :if
1978 | cond_n = consume_parser! ExprParser
1979 | pass_n = consume_parser! ProgramParser, end_tokens: [:else]
1980 | branches = []
1981 | loop do
1982 | break if !ElseBranchParser.can_parse?(self)
1983 | branches.push consume_parser! ElseBranchParser
1984 | end
1985 | end_t = consume! :end
1986 | AST::If.new(cond_n, pass_n, branches, if_t.start_pos, end_t.end_pos)
1987 | end
1988 | end
1989 |
1990 | ### IF STATEMENTS END
1991 |
1992 | class EnumParser < Parser
1993 | def self.can_parse?(_self)
1994 | _self.current_token&.type == :enum
1995 | end
1996 |
1997 | def parse!
1998 | enum_t = consume! :enum
1999 | name_t = consume! :identifier
2000 | consume! :"="
2001 | variants = []
2002 | loop do
2003 | variant_t = consume! :identifier
2004 | variants.push(variant_t)
2005 | break if current_token&.type != :|
2006 | consume! :|
2007 | end
2008 | AST::OneLineEnum.new(name_t.value, variants.map(&:value), enum_t.start_pos, variants[-1].end_pos)
2009 | end
2010 | end
2011 |
2012 | class ProgramParser < Parser
2013 | def initialize(*args)
2014 | super(*args)
2015 | @body = []
2016 | end
2017 |
2018 | ALLOWED_PARSERS = [
2019 | IfParser,
2020 | FunctionDefinitionParser,
2021 | SimpleAssignmentParser,
2022 | SimpleReassignmentParser,
2023 | ArrayAssignmentParser,
2024 | ForOfObjDeconstructLoopParser,
2025 | SimpleForInLoopParser,
2026 | SimpleForOfLoopParser,
2027 | SchemaDefinitionParser,
2028 | SimpleSchemaAssignmentParser,
2029 | SimpleComponentWithAttrsParser,
2030 | SimpleComponentParser,
2031 | BodyComponentWithoutAttrsParser,
2032 | ClassParser,
2033 | ReturnParser,
2034 | EnumParser,
2035 | ]
2036 |
2037 | def consume_parser!(parser_klass)
2038 | expr_n = super parser_klass
2039 | @body.push expr_n
2040 | end
2041 |
2042 | def parse!(additional_parsers: [], end_tokens: [])
2043 | while current_token&.is_not_one_of?(*end_tokens, :end, :"}")
2044 | klass = (ALLOWED_PARSERS + additional_parsers).find { |klass| klass.can_parse?(self) }
2045 |
2046 | if !klass
2047 | klass = ExprParser
2048 | end
2049 |
2050 | expr_n = consume_parser! klass
2051 | break if !expr_n
2052 | end
2053 |
2054 | @body
2055 | end
2056 | end
2057 |
2058 | class ComponentConstructorParser < ProgramParser
2059 | def parse!
2060 | super end_tokens: [:in]
2061 | end
2062 | end
2063 |
2064 | class FunctionBodyParser < ProgramParser
2065 | def parse!(end_tokens: [])
2066 | super additional_parsers: ALLOWED_PARSERS, end_tokens: end_tokens
2067 | return [] if @body.size == 0
2068 |
2069 | last_n = @body[-1]
2070 | if last_n.is_a? AST::SimpleAssignment
2071 | @body.push AST::Return.new(
2072 | AST::IdLookup.new(last_n.name, last_n.start_pos, last_n.end_pos),
2073 | last_n.start_pos,
2074 | last_n.end_pos
2075 | )
2076 | elsif last_n.is_a? AST::EmptyCaseExpr
2077 | last_n.cases.each do |case_|
2078 | next if case_.body.size == 0
2079 | case_.body[-1] = AST::Return.new(case_.body[-1], case_.body[-1].start_pos, case_.body[-1].end_pos)
2080 | end
2081 | elsif last_n.is_not_one_of? AST::Return, AST::SimpleForOfLoop, AST::SimpleForInLoop, AST::ForOfObjDeconstructLoop
2082 | @body[-1] = AST::Return.new(last_n, last_n.start_pos, last_n.end_pos)
2083 | end
2084 |
2085 | @body
2086 | end
2087 | end
2088 |
--------------------------------------------------------------------------------