Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

211 lines
5.8 KiB

use colored::{Color, Colorize};
use phf::{phf_set, Set};
use regex::{Captures, Regex, Replacer};
use rustyline::{
error::ReadlineError,
highlight::Highlighter,
validate::{MatchingBracketValidator, ValidationContext, ValidationResult, Validator},
Completer, Helper, Hinter,
};
use std::borrow::Cow::{self, Borrowed};
const STRING_COLOR: Color = Color::Green;
const KEYWORD_COLOR: Color = Color::Yellow;
const PROPERTY_COLOR: Color = Color::Magenta;
const OPERATOR_COLOR: Color = Color::TrueColor {
r: 214,
g: 95,
b: 26,
};
const UNDEFINED_COLOR: Color = Color::TrueColor {
r: 100,
g: 100,
b: 100,
};
const NUMBER_COLOR: Color = Color::TrueColor {
r: 26,
g: 214,
b: 175,
};
const IDENTIFIER_COLOR: Color = Color::TrueColor {
r: 26,
g: 160,
b: 214,
};
const READLINE_COLOR: Color = Color::Cyan;
#[allow(clippy::upper_case_acronyms, clippy::redundant_pub_crate)]
#[derive(Completer, Helper, Hinter)]
pub(crate) struct RLHelper {
highlighter: LineHighlighter,
validator: MatchingBracketValidator,
colored_prompt: String,
}
impl RLHelper {
pub(crate) fn new(prompt: &str) -> Self {
Self {
highlighter: LineHighlighter::new(),
validator: MatchingBracketValidator::new(),
colored_prompt: prompt.color(READLINE_COLOR).bold().to_string(),
}
}
}
impl Validator for RLHelper {
fn validate(
&self,
context: &mut ValidationContext<'_>,
) -> Result<ValidationResult, ReadlineError> {
self.validator.validate(context)
}
fn validate_while_typing(&self) -> bool {
self.validator.validate_while_typing()
}
}
impl Highlighter for RLHelper {
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
self.highlighter.highlight(line, pos)
}
// Must match signature of Highlighter::highlight_prompt, can't elide lifetimes.
#[allow(single_use_lifetimes)]
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
if default {
Borrowed(&self.colored_prompt)
} else {
Borrowed(prompt)
}
}
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
hint.into()
}
fn highlight_candidate<'c>(
&self,
candidate: &'c str,
_completion: rustyline::CompletionType,
) -> Cow<'c, str> {
self.highlighter.highlight(candidate, 0)
}
fn highlight_char(&self, line: &str, _: usize) -> bool {
!line.is_empty()
}
}
static KEYWORDS: Set<&'static str> = phf_set! {
"break",
"case",
"catch",
"class",
"const",
"continue",
"default",
"delete",
"do",
"else",
"export",
"extends",
"finally",
"for",
"function",
"if",
"import",
"instanceof",
"new",
"return",
"super",
"switch",
"this",
"throw",
"try",
"typeof",
"var",
"void",
"while",
"with",
"yield",
"await",
"enum",
"let",
};
struct LineHighlighter {
regex: Regex,
}
impl LineHighlighter {
fn new() -> Self {
// Precompiles the regex to avoid creating it again after every highlight
let regex = Regex::new(
r#"(?x)
(?P<identifier>\b[$_\p{ID_Start}][$_\p{ID_Continue}\u{200C}\u{200D}]*\b) |
(?P<string_double_quote>"([^"\\]|\\.)*") |
(?P<string_single_quote>'([^'\\]|\\.)*') |
(?P<template_literal>`([^`\\]|\\.)*`) |
(?P<op>[+\-/*%~^!&|=<>;:]) |
(?P<number>0[bB][01](_?[01])*n?|0[oO][0-7](_?[0-7])*n?|0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?|(([0-9](_?[0-9])*\.([0-9](_?[0-9])*)?)|(([0-9](_?[0-9])*)?\.[0-9](_?[0-9])*)|([0-9](_?[0-9])*))([eE][+-]?[0-9](_?[0-9])*)?n?)"#,
).expect("could not compile regular expression");
Self { regex }
}
}
impl Highlighter for LineHighlighter {
fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> {
use std::fmt::Write;
struct Colorizer;
impl Replacer for Colorizer {
// Changing to map_or_else moves the handling of "identifier" after all other kinds,
// which reads worse than this version.
#[allow(clippy::option_if_let_else)]
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
let colored = if let Some(cap) = caps.name("identifier") {
let cap = cap.as_str();
let colored = match cap {
"true" | "false" | "null" | "Infinity" | "globalThis" => {
cap.color(PROPERTY_COLOR)
}
"undefined" => cap.color(UNDEFINED_COLOR),
identifier if KEYWORDS.contains(identifier) => {
cap.color(KEYWORD_COLOR).bold()
}
_ => cap.color(IDENTIFIER_COLOR),
};
Some(colored)
} else if let Some(cap) = caps
.name("string_double_quote")
.or_else(|| caps.name("string_single_quote"))
.or_else(|| caps.name("template_literal"))
{
Some(cap.as_str().color(STRING_COLOR))
} else if let Some(cap) = caps.name("op") {
Some(cap.as_str().color(OPERATOR_COLOR))
} else {
caps.name("number")
.map(|cap| cap.as_str().color(NUMBER_COLOR))
};
if let Some(colored) = colored {
write!(dst, "{colored}").expect("could not append data to dst");
} else {
dst.push_str(&caps[0]);
}
}
}
self.regex.replace_all(line, Colorizer)
}
}