├── .gitignore ├── Cargo.toml ├── README.md ├── scripts ├── build-wasm ├── deploy ├── start-web └── test ├── src ├── lib.rs ├── metadata.rs ├── process.rs ├── sanitize.rs └── token.rs ├── wasm ├── .gitignore ├── Cargo.toml └── src │ └── lib.rs └── web ├── .gitignore ├── config-overrides.js ├── package.json ├── public ├── .htaccess ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── hooks └── context.js ├── index.css ├── index.js ├── logo.svg ├── serviceWorker.js ├── setupTests.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | bin/ 5 | pkg/ 6 | wasm-pack.log 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pipers" 3 | version = "0.1.0" 4 | authors = ["Denis Isidoro "] 5 | edition = "2018" 6 | description = "Easily share data between different terminal windows" 7 | homepage = "https://github.com/denisidoro/pipers" 8 | documentation = "https://github.com/denisidoro/pipers" 9 | repository = "https://github.com/denisidoro/pipers" 10 | keywords = ["prometheus", "terminal", "cli"] 11 | categories = ["command-line-utilities"] 12 | license = "Apache-2.0" 13 | 14 | [dependencies] 15 | lazy_static = "1.4.0" 16 | 17 | [dependencies.regex] 18 | version = "1.3.0" 19 | default-features = false 20 | features = ["std", "unicode-perl"] 21 | 22 | [lib] 23 | name = "pipers" 24 | path = "src/lib.rs" 25 | 26 | [[bin]] 27 | name = "pipersj" 28 | path = "src/bin/main.rs" 29 | bench = false 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pipers 2 | 3 | Use pipe expressions in your PromQL queries or code! 4 | 5 | For example, the following query... 6 | ```coffee 7 | sum(irate(node_cpu_seconds_total{instance=~"$node:$port",job=~"$job"}(node_cpu_seconds_total){mode='idle'}[5m](node_cpu_seconds_total{instance=~"$node:$port",job=~"$job"}(node_cpu_seconds_total)))) / count(count(node_cpu_seconds_total{instance=~"$node:$port",job=~"$job"}(node_cpu_seconds_total)) by (cpu)) 8 | ``` 9 | ...can be written as: 10 | ```coffee 11 | cpuSeconds = node_cpu_seconds_total 12 | | x -> x{instance=~"$node:$port",job=~"$job"} 13 | 14 | cpuCount = cpuSeconds 15 | | s -> count(s) by (cpu) 16 | | count 17 | 18 | cpuIdle = cpuSeconds 19 | | s -> s{mode='idle'}[5m] 20 | | irate 21 | | sum 22 | 23 | cpuIdle / cpuCount 24 | ``` 25 | 26 | ## Syntax and live playground 27 | 28 | Check [this page](https://denisidoro.github.io/pipers/) to try it out! 29 | 30 | ## Using it inside your IDE 31 | 32 | ### add the binary to your `$PATH` 33 | ```bash 34 | git clone https://github.com/denisidoro/pipers 35 | cargo build --release 36 | ``` 37 | ### setup your IDE accordingly 38 | 39 | E.g., in VSCode you can use the [Filter Text](https://marketplace.visualstudio.com/items?itemName=yhirose.FilterText) extension: 40 | 41 | ![Demo](https://user-images.githubusercontent.com/3226564/109806044-f9f08380-7c02-11eb-9429-92d26ee7084c.gif) 42 | -------------------------------------------------------------------------------- /scripts/build-wasm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | export PIPERS_HOME="$(cd "$(dirname "$0")/.." && pwd)" 5 | source "${DOTFILES}/scripts/core/main.sh" 6 | 7 | main() { 8 | cd "${PIPERS_HOME}" 9 | 10 | rm -rf "${PIPERS_HOME}/pkg" &>/dev/null || true 11 | rm -rf "${PIPERS_HOME}/web/pkg" &>/dev/null || true 12 | rm -rf "${PIPERS_HOME}/wasm/pkg" &>/dev/null || true 13 | rm -rf "${PIPERS_HOME}/web/node_modules/pipers" &>/dev/null || true 14 | mkdir -p "${PIPERS_HOME}/web/node_modules" &>/dev/null || true 15 | 16 | rm "${PIPERS_HOME}/wasm/lib.rs.bk" &>/dev/null || true 17 | cp "${PIPERS_HOME}/wasm/src/lib.rs" "${PIPERS_HOME}/wasm/lib.rs.bk" 18 | rm -rf "${PIPERS_HOME}/wasm/src" 19 | cp -r "${PIPERS_HOME}/src" "${PIPERS_HOME}/wasm/src" 20 | rm "${PIPERS_HOME}/wasm/src/lib.rs" 21 | mv "${PIPERS_HOME}/wasm/lib.rs.bk" "${PIPERS_HOME}/wasm/src/lib.rs" 22 | rm -rf "${PIPERS_HOME}/wasm/src/bin" 23 | 24 | cd "${PIPERS_HOME}/wasm" 25 | wasm-pack build # --target web 26 | 27 | cp -r "${PIPERS_HOME}/wasm/pkg" "${PIPERS_HOME}/pkg" 28 | cp -r "${PIPERS_HOME}/wasm/pkg" "${PIPERS_HOME}/web/pkg" 29 | cp -r "${PIPERS_HOME}/wasm/pkg" "${PIPERS_HOME}/web/node_modules/pipers" 30 | } 31 | 32 | main "$@" -------------------------------------------------------------------------------- /scripts/deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | export PIPERS_HOME="$(cd "$(dirname "$0")/.." && pwd)" 5 | source "${DOTFILES}/scripts/core/main.sh" 6 | 7 | main() { 8 | "${PIPERS_HOME}/scripts/build-wasm" 9 | 10 | cd "${PIPERS_HOME}/web" 11 | yarn install 12 | yarn run deploy 13 | } 14 | 15 | main "$@" 16 | -------------------------------------------------------------------------------- /scripts/start-web: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | export PIPERS_HOME="$(cd "$(dirname "$0")/.." && pwd)" 5 | source "${DOTFILES}/scripts/core/main.sh" 6 | 7 | main() { 8 | "${PIPERS_HOME}/scripts/build-wasm" 9 | 10 | cd "${PIPERS_HOME}/web" 11 | yarn install 12 | yarn start 13 | } 14 | 15 | main "$@" -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | export PIPERS_HOME="$(cd "$(dirname "$0")/.." && pwd)" 5 | source "${DOTFILES}/scripts/core/main.sh" 6 | 7 | main() { 8 | cd "${PIPERS_HOME}" 9 | 10 | cargo test 11 | } 12 | 13 | main "$@" -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | 4 | mod metadata; 5 | mod process; 6 | mod sanitize; 7 | mod token; 8 | 9 | pub use process::convert; 10 | -------------------------------------------------------------------------------- /src/metadata.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | 3 | lazy_static! { 4 | static ref LINE_REGEX: Regex = 5 | Regex::new(r"^\s*([a-zA-Z_]+[a-zA-Z_0-9]*)\s*=\s*(.*)").expect("Invalid regex"); 6 | static ref PART_REGEX: Regex = 7 | Regex::new(r"^\s*([a-zA-Z_]+[a-zA-Z_0-9]*)\s*->\s*(.*)").expect("Invalid regex"); 8 | } 9 | 10 | pub(super) fn line_metadata(txt: &str) -> (Option, String) { 11 | if let Some(captures) = LINE_REGEX.captures(txt) { 12 | (Some(captures[1].to_string()), captures[2].to_string()) 13 | } else { 14 | (None, txt.to_string()) 15 | } 16 | } 17 | 18 | pub(super) fn part_metadata(txt: &str) -> (Option, String) { 19 | if let Some(captures) = PART_REGEX.captures(txt) { 20 | (Some(captures[1].to_string()), captures[2].to_string()) 21 | } else { 22 | (None, txt.to_string()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/process.rs: -------------------------------------------------------------------------------- 1 | use super::metadata::{line_metadata, part_metadata}; 2 | use super::sanitize::{sanitize_input, sanitize_output}; 3 | use super::token::tokenize; 4 | use std::collections::HashMap; 5 | 6 | type State = HashMap; 7 | 8 | const FINAL_RESULT: &str = "FINAL_RESULT"; 9 | 10 | pub fn convert(txt: &str) -> String { 11 | let txt = sanitize_input(txt); 12 | let lines = txt.split('\n'); 13 | 14 | let mut state = HashMap::new(); 15 | 16 | for line in lines { 17 | let l = line.trim(); 18 | if !l.is_empty() { 19 | state = process_line(l, state); 20 | } 21 | } 22 | 23 | let output = state.get(FINAL_RESULT).unwrap(); 24 | 25 | sanitize_output(output) 26 | } 27 | 28 | fn process_line(txt: &str, state: State) -> State { 29 | let (line_name, piped_code) = line_metadata(txt.trim()); 30 | let parts = piped_code.split('|'); 31 | let mut code = String::from(""); 32 | 33 | for (i, part) in parts.enumerate() { 34 | let (var_name, this_piped_code) = part_metadata(part); 35 | let mut this_code = String::from(""); 36 | let tokens = tokenize(this_piped_code.trim()); 37 | let mut found_parentheses = false; 38 | 39 | for token in tokens { 40 | if var_name.is_some() && var_name.as_ref().unwrap() == &token { 41 | this_code.push_str(&code); 42 | } else if let Some(v) = state.get(&token) { 43 | this_code.push_str(v); 44 | } else { 45 | this_code.push_str(&token); 46 | if token.as_str() == "(" { 47 | found_parentheses = true; 48 | if var_name.is_none() { 49 | this_code.push_str(&code); 50 | this_code.push_str(", "); 51 | } 52 | } 53 | } 54 | } 55 | 56 | if !found_parentheses && i > 0 { 57 | this_code.push_str(&format!("({})", &code)); 58 | } 59 | 60 | code = this_code; 61 | } 62 | 63 | let mut new_state = state; 64 | new_state.insert(line_name.unwrap_or_else(|| FINAL_RESULT.to_string()), code); 65 | new_state 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::convert; 71 | 72 | #[test] 73 | fn test_convert() { 74 | let cases = vec![ 75 | ("42 | f", "f(42)"), 76 | ("42 | g(3)", "g(42, 3)"), 77 | ("42 | f | g(3)", "g(f(42), 3)"), 78 | ("42 | f | x -> g(3, x)", "g(3, f(42))"), 79 | ("42 | f | foo -> g(3, foo, x)", "g(3, f(42), x)"), 80 | ( 81 | r#"42 | f | x -> g(x \|\| z(6 == 3))"#, 82 | "g(f(42) || z(6 == 3))", 83 | ), 84 | ( 85 | r#"x = 42 | f 86 | y = 6 | g 87 | x \= y"#, 88 | "f(42) = g(6)", 89 | ), 90 | (r#"42 | g(f->5)"#, "g(42, f->5)"), 91 | (r#"42 | x \-> f(3)"#, "x -> f(42, 3)"), 92 | ( 93 | "42 94 | | f 95 | | g(3)", 96 | "g(f(42), 3)", 97 | ), 98 | ( 99 | "x = 42 100 | | f 101 | | g(3) 102 | 103 | y = 53 | h 104 | 105 | 106 | x + y", 107 | "g(f(42), 3) + h(53)", 108 | ), 109 | ]; 110 | 111 | for (input, output) in cases { 112 | assert_eq!(convert(input), output) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/sanitize.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | 3 | lazy_static! { 4 | static ref NEWLINE_REGEX: Regex = Regex::new(r"[ \t]*\n+[ \t]+").expect("Invalid regex"); 5 | } 6 | 7 | const PIPE_ESCAPE_CHAR: char = '\u{02A3}'; 8 | const EQUAL_ESCAPE_CHAR: char = '\u{02A4}'; 9 | const ARROW_ESCAPE_CHAR: char = '\u{02A5}'; 10 | 11 | pub(super) fn sanitize_input(txt: &str) -> String { 12 | let replaced = txt 13 | .replace("\\|", &PIPE_ESCAPE_CHAR.to_string()) 14 | .replace("\\=", &EQUAL_ESCAPE_CHAR.to_string()) 15 | .replace("\\->", &ARROW_ESCAPE_CHAR.to_string()); 16 | 17 | NEWLINE_REGEX.replace_all(&replaced, " ").into() 18 | } 19 | 20 | pub(super) fn sanitize_output(txt: &str) -> String { 21 | txt.replace(&PIPE_ESCAPE_CHAR.to_string(), "|") 22 | .replace(&EQUAL_ESCAPE_CHAR.to_string(), "=") 23 | .replace(&ARROW_ESCAPE_CHAR.to_string(), "->") 24 | } 25 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | pub(super) fn tokenize(txt: &str) -> Vec { 2 | let mut tokens = vec![]; 3 | if txt.is_empty() { 4 | return tokens; 5 | } 6 | 7 | let mut chars = txt.chars(); 8 | let mut previous_char = chars.next().unwrap(); 9 | let mut token = previous_char.to_string(); 10 | 11 | for c in chars { 12 | if should_continue(previous_char, c) { 13 | token.push_str(&c.to_string()); 14 | } else { 15 | tokens.push(token); 16 | token = c.to_string(); 17 | } 18 | previous_char = c; 19 | } 20 | if !token.is_empty() { 21 | tokens.push(token); 22 | } 23 | 24 | tokens 25 | } 26 | 27 | fn is_variable_char(c: char) -> bool { 28 | c == '_' || c.is_alphanumeric() 29 | } 30 | 31 | fn should_continue(previous_char: char, c: char) -> bool { 32 | c == previous_char || (is_variable_char(c) && is_variable_char(previous_char)) 33 | } 34 | -------------------------------------------------------------------------------- /wasm/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | bin/ 5 | pkg/ 6 | wasm-pack.log 7 | 8 | src/*.rs 9 | src/bin 10 | !src/lib.rs -------------------------------------------------------------------------------- /wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pipers" 3 | version = "0.1.0" 4 | authors = ["Denis Isidoro "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | wasm-bindgen = "0.2" 15 | lazy_static = "1.4.0" 16 | console_error_panic_hook = { version = "0.1.1", optional = true } 17 | wee_alloc = { version = "0.4.2", optional = true } 18 | 19 | [dependencies.regex] 20 | version = "1.3.0" 21 | default-features = false 22 | features = ["std", "unicode-perl"] 23 | 24 | [dev-dependencies] 25 | wasm-bindgen-test = "0.2" 26 | 27 | [profile.release] 28 | opt-level = "s" 29 | -------------------------------------------------------------------------------- /wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | #[macro_use] 4 | extern crate lazy_static; 5 | 6 | mod metadata; 7 | mod process; 8 | mod sanitize; 9 | mod token; 10 | 11 | #[cfg(feature = "wee_alloc")] 12 | #[global_allocator] 13 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 14 | 15 | #[wasm_bindgen] 16 | pub fn convert(txt: &str) -> String { 17 | process::convert(txt) 18 | } 19 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /web/config-overrides.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = function override(config, env) { 4 | const wasmExtensionRegExp = /\.wasm$/; 5 | 6 | config.resolve.extensions.push(".wasm"); 7 | 8 | config.module.rules.forEach(rule => { 9 | (rule.oneOf || []).forEach(oneOf => { 10 | if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) { 11 | // make file-loader ignore WASM files 12 | oneOf.exclude.push(wasmExtensionRegExp); 13 | } 14 | }); 15 | }); 16 | 17 | // add a dedicated loader for WASM 18 | config.module.rules.push({ 19 | test: wasmExtensionRegExp, 20 | include: path.resolve(__dirname, "src"), 21 | use: [{ loader: require.resolve("wasm-loader"), options: {} }] 22 | }); 23 | 24 | return config; 25 | }; 26 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webassembly-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "http://denisidoro.github.io/pipers", 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "react": "^17.0.1", 11 | "react-dom": "^17.0.1", 12 | "react-scripts": "3.4.0", 13 | "pipers": "file:./pkg" 14 | }, 15 | "scripts": { 16 | "start": "react-app-rewired start", 17 | "build": "react-app-rewired build", 18 | "test": "react-app-rewired test", 19 | "deploy": "gh-pages -d build" 20 | }, 21 | "eslintConfig": { 22 | "extends": "react-app" 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | }, 36 | "devDependencies": { 37 | "react-app-rewired": "^2.1.5", 38 | "wasm-loader": "^1.3.0", 39 | "gh-pages": "^3.1.0" 40 | } 41 | } -------------------------------------------------------------------------------- /web/public/.htaccess: -------------------------------------------------------------------------------- 1 | AddType application/wasm module.wasm 2 | AddType application/wasm wasm -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denisidoro/pipers/cd86a088003c72d10122b197f7eb17ce1dee7176/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /web/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denisidoro/pipers/cd86a088003c72d10122b197f7eb17ce1dee7176/web/public/logo192.png -------------------------------------------------------------------------------- /web/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denisidoro/pipers/cd86a088003c72d10122b197f7eb17ce1dee7176/web/public/logo512.png -------------------------------------------------------------------------------- /web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /web/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | flex-direction: column; 6 | } 7 | .text { 8 | display: flex; 9 | justify-content: center; 10 | } 11 | .buttons { 12 | display: flex; 13 | align-content: center; 14 | justify-content: space-around; 15 | flex-direction: row; 16 | } 17 | .buttonWrapper { 18 | display: flex; 19 | justify-content: space-between; 20 | justify-content: center; 21 | } 22 | button { 23 | padding: 5px; 24 | margin-top: 5px; 25 | width: 200px; 26 | } 27 | 28 | .charts { 29 | margin-top: 20px; 30 | display: flex; 31 | flex-direction: row; 32 | justify-content: center; 33 | align-content: center; 34 | } 35 | 36 | .innerChartContainer { 37 | display: flex; 38 | } 39 | .wrapper { 40 | display: flex; 41 | flex-direction: column; 42 | } 43 | 44 | @media only screen and (max-width: 850px) { 45 | .buttons { 46 | flex-direction: column; 47 | align-items: center; 48 | } 49 | .charts { 50 | flex-direction: column; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /web/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, useState, useCallback, useEffect } from "react"; 2 | import { MainContext, useValue, useMainContext } from './hooks/context' 3 | 4 | const exprs = [ 5 | ["example", "make queries more readable", `cpuSeconds = node_cpu_seconds_total 6 | | x -> x{instance=~"$node:$port",job=~"$job"} 7 | 8 | cpuCount = cpuSeconds 9 | | s -> count(s) by (cpu) 10 | | count 11 | 12 | cpuIdle = cpuSeconds 13 | | s -> s{mode='idle'}[5m] 14 | | irate 15 | | sum 16 | 17 | cpuIdle / cpuCount`], 18 | ["simple pipe", "pipes will turn into parentheses", "42 | f"], 19 | ["input as first arg", "for functions with multiple args, the input will be passed as the first one", "42 | f | g(3) | h"], 20 | ["input as last arg", "if you want to determine the arg position, use the x -> syntax", "42 | f | x -> g(3, x) | h"], 21 | ["newlines", "you can use indented newlines to improve readability", `42 22 | | f 23 | | x -> g(3, x) 24 | | h`], 25 | ["named variables", "you can split a expression into multiple named ones", `x = 42 | f 26 | 27 | y = 53 | g(3) 28 | 29 | x + y | sum`], 30 | ["escaping", "if you need to use a | inside your expressions, escape it with \\", `53 | x -> g(x \\|\\| true)`], 31 | ] 32 | 33 | function Example({ id }) { 34 | const { convert } = useMainContext() 35 | const [value, setValue] = useState(exprs[id][2]) 36 | 37 | const onChange = useCallback((event) => { 38 | const v = event.target.value; 39 | console.log({ v, c: convert(v) }) 40 | setValue(v) 41 | }, [setValue, convert]) 42 | 43 | return ( 44 |
45 |
46 |
47 |

{id + ". " + exprs[id][0]} 48 | {exprs[id][1]} 49 |

50 |
51 | 52 |
53 |