非英语语言的编程语言解释器;
Programming language interpreter in a non-english language;
我试图使用不同的语言来实现它。但它似乎无法处理已插入的字符,这实际上适用于英语我在阿姆哈拉语中对其进行了调整,如果处理工作像创建编辑器或其他东西一样,我正在考虑添加更多内容。如果有人愿意提供帮助,我将不胜感激。我知道我应该规范化 unicode 字符,但我不知道这样做的有效方法。
function InputStream(input) {
var pos = 0, line = 1, col = 0;
return {
next : next,
peek : peek,
eof : eof,
croak : croak,
};
function next() {
var ch = input.charAt(pos++);
if (ch == "\n") line++, col = 0; else col++;
return ch;
}
function peek() {
return input.charAt(pos);
}
function eof() {
return peek() == "";
}
function croak(msg) {
throw new Error(msg + " (" + line + ":" + col + ")");
}
}
function TokenStream(input) {
var current = null;
var keywords = " ከ ወደ ሌላ ስራ ስራ አውነት ሀሰት ";
return {
next : next,
peek : peek,
eof : eof,
croak : input.croak
};
function is_keyword(x) {
return keywords.indexOf(" " + x + " ") >= 0;
}
function is_digit(ch) {
return /[0-9]/i.test(ch);
}
function is_id_start(ch) {
return /[^\u1380-\u1380f]+[a-z_]/i.test(ch);
}
function is_id(ch) {
return is_id_start(ch) || "?!-<>=0123456789".indexOf(ch) >= 0;
}
function is_op_char(ch) {
return "+-*/%=&|<>!".indexOf(ch) >= 0;
}
function is_punc(ch) {
return "፣፤(){}[]".indexOf(ch) >= 0;
}
function is_whitespace(ch) {
return " \t\n".indexOf(ch) >= 0;
}
function read_while(predicate) {
var str = "";
while (!input.eof() && predicate(input.peek()))
str += input.next();
return str;
}
function read_number() {
var has_dot = false;
var number = read_while(function(ch){
if (ch == ".") {
if (has_dot) return false;
has_dot = true;
return true;
}
return is_digit(ch);
});
return { type: "num", value: parseFloat(number) };
}
function read_ident() {
var id = read_while(is_id);
return {
type : is_keyword(id) ? "kw" : "var",
value : id
};
}
function read_escaped(end) {
var escaped = false, str = "";
input.next();
while (!input.eof()) {
var ch = input.next();
if (escaped) {
str += ch;
escaped = false;
} else if (ch == "\") {
escaped = true;
} else if (ch == end) {
break;
} else {
str += ch;
}
}
return str;
}
function read_string() {
return { type: "str", value: read_escaped('"') };
}
function skip_comment() {
read_while(function(ch){ return ch != "\n" });
input.next();
}
function read_next() {
read_while(is_whitespace);
if (input.eof()) return null;
var ch = input.peek();
if (ch == "#") {
skip_comment();
return read_next();
}
if (ch == '"') return read_string();
if (is_digit(ch)) return read_number();
if (is_id_start(ch)) return read_ident();
if (is_punc(ch)) return {
type : "punc",
value : input.next()
};
if (is_op_char(ch)) return {
type : "op",
value : read_while(is_op_char)
};
input.croak("Can't handle character: " + ch);
}
function peek() {
return current || (current = read_next());
}
function next() {
var tok = current;
current = null;
return tok || read_next();
}
function eof() {
return peek() == null;
}
}
function parse(input) {
var PRECEDENCE = {
"=": 1,
"||": 2,
"&&": 3,
"<": 7, ">": 7, "<=": 7, ">=": 7, "==": 7, "!=": 7,
"+": 10, "-": 10,
"*": 20, "/": 20, "%": 20,
};
var FALSE = { type: "bool", value: false };
return parse_toplevel();
function is_punc(ch) {
var tok = input.peek();
return tok && tok.type == "punc" && (!ch || tok.value == ch) && tok;
}
function is_kw(kw) {
var tok = input.peek();
return tok && tok.type == "kw" && (!kw || tok.value == kw) && tok;
}
function is_op(op) {
var tok = input.peek();
return tok && tok.type == "op" && (!op || tok.value == op) && tok;
}
function skip_punc(ch) {
if (is_punc(ch)) input.next();
else input.croak("Expecting punctuation: \"" + ch + "\"");
}
function skip_kw(kw) {
if (is_kw(kw)) input.next();
else input.croak("Expecting keyword: \"" + kw + "\"");
}
function skip_op(op) {
if (is_op(op)) input.next();
else input.croak("Expecting operator: \"" + op + "\"");
}
function unexpected() {
input.croak("Unexpected token: " + JSON.stringify(input.peek()));
}
function maybe_binary(left, my_prec) {
var tok = is_op();
if (tok) {
var his_prec = PRECEDENCE[tok.value];
if (his_prec > my_prec) {
input.next();
return maybe_binary({
type : tok.value == "=" ? "assign" : "binary",
operator : tok.value,
left : left,
right : maybe_binary(parse_atom(), his_prec)
}, my_prec);
}
}
return left;
}
function delimited(start, stop, separator, parser) {
var a = [], first = true;
skip_punc(start);
while (!input.eof()) {
if (is_punc(stop)) break;
if (first) first = false; else skip_punc(separator);
if (is_punc(stop)) break;
a.push(parser());
}
skip_punc(stop);
return a;
}
function parse_call(func) {
return {
type: "call",
func: func,
args: delimited("(", ")", "፣", parse_expression),
};
}
function parse_varname() {
var name = input.next();
if (name.type != "var") input.croak("Expecting variable name");
return name.value;
}
function parse_if() {
skip_kw("ከ");
var cond = parse_expression();
if (!is_punc("{")) skip_kw("ወደ");
var then = parse_expression();
var ret = {
type: "if",
cond: cond,
then: then,
};
if (is_kw("ሌላ")) {
input.next();
ret.else = parse_expression();
}
return ret;
}
function parse_lambda() {
return {
type: "lambda",
vars: delimited("(", ")", "፣", parse_varname),
body: parse_expression()
};
}
function parse_bool() {
return {
type : "bool",
value : input.next().value == "አውነት"
};
}
function maybe_call(expr) {
expr = expr();
return is_punc("(") ? parse_call(expr) : expr;
}
function parse_atom() {
return maybe_call(function(){
if (is_punc("(")) {
input.next();
var exp = parse_expression();
skip_punc(")");
return exp;
}
if (is_punc("{")) return parse_prog();
if (is_kw("if")) return parse_if();
if (is_kw("አውነት") || is_kw("ሀሰት")) return parse_bool();
if (is_kw("ስራ") || is_kw("ሥራ")) {
input.next();
return parse_lambda();
}
var tok = input.next();
if (tok.type == "var" || tok.type == "num" || tok.type == "str")
return tok;
unexpected();
});
}
function parse_toplevel() {
var prog = [];
while (!input.eof()) {
prog.push(parse_expression());
if (!input.eof()) skip_punc("፤");
}
return { type: "prog", prog: prog };
}
function parse_prog() {
var prog = delimited("{", "}", "፤", parse_expression);
if (prog.length == 0) return FALSE;
if (prog.length == 1) return prog[0];
return { type: "prog", prog: prog };
}
function parse_expression() {
return maybe_call(function(){
return maybe_binary(parse_atom(), 0);
});
}
}
function Environment(parent) {
this.vars = Object.create(parent ? parent.vars : null);
this.parent = parent;
}
Environment.prototype = {
extend: function() {
return new Environment(this);
},
lookup: function(name) {
var scope = this;
while (scope) {
if (Object.prototype.hasOwnProperty.call(scope.vars, name))
return scope;
scope = scope.parent;
}
},
get: function(name) {
if (name in this.vars)
return this.vars[name];
throw new Error("Undefined variable " + name);
},
set: function(name, value) {
var scope = this.lookup(name);
if (!scope && this.parent)
throw new Error("Undefined variable " + name);
return (scope || this).vars[name] = value;
},
def: function(name, value) {
return this.vars[name] = value;
}
};
function evaluate(exp, env) {
switch (exp.type) {
case "num":
case "str":
case "bool":
return exp.value;
case "var":
return env.get(exp.value);
case "assign":
if (exp.left.type != "ተጠቀመም")
throw new Error("Cannot assign to " + JSON.stringify(exp.left));
return env.set(exp.left.value, evaluate(exp.right, env));
case "binary":
return apply_op(exp.operator,
evaluate(exp.left, env),
evaluate(exp.right, env));
case "lambda":
return make_lambda(env, exp);
case "if":
var cond = evaluate(exp.cond, env);
if (cond !== false) return evaluate(exp.then, env);
return exp.else ? evaluate(exp.else, env) : false;
case "prog":
var val = false;
exp.prog.forEach(function(exp){ val = evaluate(exp, env) });
return val;
case "call":
var func = evaluate(exp.func, env);
return func.apply(null, exp.args.map(function(arg){
return evaluate(arg, env);
}));
default:
throw new Error("I don't know how to evaluate " + exp.type);
}
}
function apply_op(op, a, b) {
function num(x) {
if (typeof x != "number")
throw new Error("Expected number but got " + x);
return x;
}
function div(x) {
if (num(x) == 0)
throw new Error("Divide by zero");
return x;
}
switch (op) {
case "+": return num(a) + num(b);
case "-": return num(a) - num(b);
case "*": return num(a) * num(b);
case "/": return num(a) / div(b);
case "%": return num(a) % div(b);
case "&&": return a !== false && b;
case "||": return a !== false ? a : b;
case "<": return num(a) < num(b);
case ">": return num(a) > num(b);
case "<=": return num(a) <= num(b);
case ">=": return num(a) >= num(b);
case "==": return a === b;
case "!=": return a !== b;
}
throw new Error("Can't apply operator " + op);
}
function make_lambda(env, exp) {
function lambda() {
var names = exp.vars;
var scope = env.extend();
for (var i = 0; i < names.length; ++i)
scope.def(names[i], i < arguments.length ? arguments[i] : false);
return evaluate(exp.body, scope);
}
return lambda;
}
/* -----[ entry point for NodeJS ]----- */
var globalEnv = new Environment();
globalEnv.def("time", function(func){
try {
console.time("time");
return func();
} finally {
console.timeEnd("time");
}
});
if (typeof process != "undefined") (function(){
var util = require("util");
globalEnv.def("ፃፍ", function(val){
console.log(val);
});
globalEnv.def("print", function(val){
util.print(val);
});
var code = "";
process.stdin.setEncoding("utf8");
process.stdin.on("readable", function(){
var chunk = process.stdin.read();
if (chunk) code += chunk;
});
process.stdin.on("end", function(){
var ast = parse(TokenStream(InputStream(code)));
evaluate(ast, globalEnv);
});
})();
这是英文版 这应该是 print 的工作方式 "println("hello world!");"我想使用“ፃፍ(“ሰላምሰላም”)፤”其中 'print' 和 ';'被转换为 'ፃፍ' 和 "፤
这是很多代码,除了“它似乎无法处理已插入的字符”之外,没有解释它无法执行的操作,这可能意味着任何事情。如果我有阿姆哈拉语键盘布局并且知道如何使用它打字,我可能会尝试编写代码看看有什么问题,但我没有,也不知道如何使用它,所以我没有试试。
但是,我认为您的 is_id
和 is_id_start
功能不太可能起作用。你有:
function is_id_start(ch) {
return /[^\u1380-\u1380f]+[a-z_]/i.test(ch);
}
function is_id(ch) {
return is_id_start(ch) || "?!-<>=0123456789".indexOf(ch) >= 0;
}
看到 !-<>=
可能是标识符字符,我有点困惑,因为它们也是运算符字符。据推测,其意图是如果标识符后跟这些运算符之一,则两者之间必须有空格。但我认为这不是您在使用阿姆哈拉语时遇到的问题。这似乎更可能与这个有点奇怪的正则表达式有关:/[^\u1380-\u1380f]+[a-z_]/i
.
首先,\u1380f
不是单个 unicode 转义字符。 \u
后面必须紧跟四个十六进制数字,因此 f
不是转义的一部分。就是一个普通的f
。这使得范围 \u1380-\u1380
由单个字符 ᎀ.
组成
您可能指的是 \u1380-\u138f
,即 453 个埃塞俄比亚字母中的 16 个。我对阿姆哈拉语的了解非常有限,当然不足以理解这些特定字符的区别,所以我什至无法开始猜测这是否合理。
但是,您在 反转 正则表达式中使用该范围;即使进行了该更正,[^\u1380-\u138f]
匹配的是 除了 该范围内的任何字符。这将包括来自整个 Unicode 星系的字符,包括许多其他脚本,所以我很确定这不是您想要的。
此外,您的 ID 起始模式实际上是 /[^\u1380-\u1380f]+[a-z_]/i
,意思是“一个或多个 [...] 后跟一个拉丁字母字符或下划线”。换句话说,该模式至少需要两个字符才能匹配,第一个是阿姆哈拉语字符(或者,按照书面形式,除这些阿姆哈拉语字符之一之外的任何字符),然后是拉丁字母字符。
这显然是错误的,因为您将该模式与 input.peek()
的结果进行匹配,后者只能是单个字符。所以is_id
保证returnfalse
,这可能和你的问题有关
就个人而言,我认为使用 Unicode Property classes 比尝试写下有效标识符字符列表要好得多。 (例如,为什么要将标识符限制为仅英语和阿姆哈拉语?)Ecmascript 方便地附带了 Unicode 技术专家推荐用于标识符的标准字符集。你可以直接使用它们:
function is_id_start(ch) {
return /\p{XID_Start}/u.test(ch);
}
function is_id(ch) {
return /[\p{XID_Continue}?!-<>=]/u.test(ch);
}
请注意,您必须使用 u
标志才能启用 Unicode 属性。
如果您真的想将其限制为阿姆哈拉语和拉丁语,您可以通过还要求字符也匹配 [\p{Script=Ethiopic}\p{Script=Latin}]
来实现。 (您可以使用先行断言来做到这一点。)
我试图使用不同的语言来实现它。但它似乎无法处理已插入的字符,这实际上适用于英语我在阿姆哈拉语中对其进行了调整,如果处理工作像创建编辑器或其他东西一样,我正在考虑添加更多内容。如果有人愿意提供帮助,我将不胜感激。我知道我应该规范化 unicode 字符,但我不知道这样做的有效方法。
function InputStream(input) {
var pos = 0, line = 1, col = 0;
return {
next : next,
peek : peek,
eof : eof,
croak : croak,
};
function next() {
var ch = input.charAt(pos++);
if (ch == "\n") line++, col = 0; else col++;
return ch;
}
function peek() {
return input.charAt(pos);
}
function eof() {
return peek() == "";
}
function croak(msg) {
throw new Error(msg + " (" + line + ":" + col + ")");
}
}
function TokenStream(input) {
var current = null;
var keywords = " ከ ወደ ሌላ ስራ ስራ አውነት ሀሰት ";
return {
next : next,
peek : peek,
eof : eof,
croak : input.croak
};
function is_keyword(x) {
return keywords.indexOf(" " + x + " ") >= 0;
}
function is_digit(ch) {
return /[0-9]/i.test(ch);
}
function is_id_start(ch) {
return /[^\u1380-\u1380f]+[a-z_]/i.test(ch);
}
function is_id(ch) {
return is_id_start(ch) || "?!-<>=0123456789".indexOf(ch) >= 0;
}
function is_op_char(ch) {
return "+-*/%=&|<>!".indexOf(ch) >= 0;
}
function is_punc(ch) {
return "፣፤(){}[]".indexOf(ch) >= 0;
}
function is_whitespace(ch) {
return " \t\n".indexOf(ch) >= 0;
}
function read_while(predicate) {
var str = "";
while (!input.eof() && predicate(input.peek()))
str += input.next();
return str;
}
function read_number() {
var has_dot = false;
var number = read_while(function(ch){
if (ch == ".") {
if (has_dot) return false;
has_dot = true;
return true;
}
return is_digit(ch);
});
return { type: "num", value: parseFloat(number) };
}
function read_ident() {
var id = read_while(is_id);
return {
type : is_keyword(id) ? "kw" : "var",
value : id
};
}
function read_escaped(end) {
var escaped = false, str = "";
input.next();
while (!input.eof()) {
var ch = input.next();
if (escaped) {
str += ch;
escaped = false;
} else if (ch == "\") {
escaped = true;
} else if (ch == end) {
break;
} else {
str += ch;
}
}
return str;
}
function read_string() {
return { type: "str", value: read_escaped('"') };
}
function skip_comment() {
read_while(function(ch){ return ch != "\n" });
input.next();
}
function read_next() {
read_while(is_whitespace);
if (input.eof()) return null;
var ch = input.peek();
if (ch == "#") {
skip_comment();
return read_next();
}
if (ch == '"') return read_string();
if (is_digit(ch)) return read_number();
if (is_id_start(ch)) return read_ident();
if (is_punc(ch)) return {
type : "punc",
value : input.next()
};
if (is_op_char(ch)) return {
type : "op",
value : read_while(is_op_char)
};
input.croak("Can't handle character: " + ch);
}
function peek() {
return current || (current = read_next());
}
function next() {
var tok = current;
current = null;
return tok || read_next();
}
function eof() {
return peek() == null;
}
}
function parse(input) {
var PRECEDENCE = {
"=": 1,
"||": 2,
"&&": 3,
"<": 7, ">": 7, "<=": 7, ">=": 7, "==": 7, "!=": 7,
"+": 10, "-": 10,
"*": 20, "/": 20, "%": 20,
};
var FALSE = { type: "bool", value: false };
return parse_toplevel();
function is_punc(ch) {
var tok = input.peek();
return tok && tok.type == "punc" && (!ch || tok.value == ch) && tok;
}
function is_kw(kw) {
var tok = input.peek();
return tok && tok.type == "kw" && (!kw || tok.value == kw) && tok;
}
function is_op(op) {
var tok = input.peek();
return tok && tok.type == "op" && (!op || tok.value == op) && tok;
}
function skip_punc(ch) {
if (is_punc(ch)) input.next();
else input.croak("Expecting punctuation: \"" + ch + "\"");
}
function skip_kw(kw) {
if (is_kw(kw)) input.next();
else input.croak("Expecting keyword: \"" + kw + "\"");
}
function skip_op(op) {
if (is_op(op)) input.next();
else input.croak("Expecting operator: \"" + op + "\"");
}
function unexpected() {
input.croak("Unexpected token: " + JSON.stringify(input.peek()));
}
function maybe_binary(left, my_prec) {
var tok = is_op();
if (tok) {
var his_prec = PRECEDENCE[tok.value];
if (his_prec > my_prec) {
input.next();
return maybe_binary({
type : tok.value == "=" ? "assign" : "binary",
operator : tok.value,
left : left,
right : maybe_binary(parse_atom(), his_prec)
}, my_prec);
}
}
return left;
}
function delimited(start, stop, separator, parser) {
var a = [], first = true;
skip_punc(start);
while (!input.eof()) {
if (is_punc(stop)) break;
if (first) first = false; else skip_punc(separator);
if (is_punc(stop)) break;
a.push(parser());
}
skip_punc(stop);
return a;
}
function parse_call(func) {
return {
type: "call",
func: func,
args: delimited("(", ")", "፣", parse_expression),
};
}
function parse_varname() {
var name = input.next();
if (name.type != "var") input.croak("Expecting variable name");
return name.value;
}
function parse_if() {
skip_kw("ከ");
var cond = parse_expression();
if (!is_punc("{")) skip_kw("ወደ");
var then = parse_expression();
var ret = {
type: "if",
cond: cond,
then: then,
};
if (is_kw("ሌላ")) {
input.next();
ret.else = parse_expression();
}
return ret;
}
function parse_lambda() {
return {
type: "lambda",
vars: delimited("(", ")", "፣", parse_varname),
body: parse_expression()
};
}
function parse_bool() {
return {
type : "bool",
value : input.next().value == "አውነት"
};
}
function maybe_call(expr) {
expr = expr();
return is_punc("(") ? parse_call(expr) : expr;
}
function parse_atom() {
return maybe_call(function(){
if (is_punc("(")) {
input.next();
var exp = parse_expression();
skip_punc(")");
return exp;
}
if (is_punc("{")) return parse_prog();
if (is_kw("if")) return parse_if();
if (is_kw("አውነት") || is_kw("ሀሰት")) return parse_bool();
if (is_kw("ስራ") || is_kw("ሥራ")) {
input.next();
return parse_lambda();
}
var tok = input.next();
if (tok.type == "var" || tok.type == "num" || tok.type == "str")
return tok;
unexpected();
});
}
function parse_toplevel() {
var prog = [];
while (!input.eof()) {
prog.push(parse_expression());
if (!input.eof()) skip_punc("፤");
}
return { type: "prog", prog: prog };
}
function parse_prog() {
var prog = delimited("{", "}", "፤", parse_expression);
if (prog.length == 0) return FALSE;
if (prog.length == 1) return prog[0];
return { type: "prog", prog: prog };
}
function parse_expression() {
return maybe_call(function(){
return maybe_binary(parse_atom(), 0);
});
}
}
function Environment(parent) {
this.vars = Object.create(parent ? parent.vars : null);
this.parent = parent;
}
Environment.prototype = {
extend: function() {
return new Environment(this);
},
lookup: function(name) {
var scope = this;
while (scope) {
if (Object.prototype.hasOwnProperty.call(scope.vars, name))
return scope;
scope = scope.parent;
}
},
get: function(name) {
if (name in this.vars)
return this.vars[name];
throw new Error("Undefined variable " + name);
},
set: function(name, value) {
var scope = this.lookup(name);
if (!scope && this.parent)
throw new Error("Undefined variable " + name);
return (scope || this).vars[name] = value;
},
def: function(name, value) {
return this.vars[name] = value;
}
};
function evaluate(exp, env) {
switch (exp.type) {
case "num":
case "str":
case "bool":
return exp.value;
case "var":
return env.get(exp.value);
case "assign":
if (exp.left.type != "ተጠቀመም")
throw new Error("Cannot assign to " + JSON.stringify(exp.left));
return env.set(exp.left.value, evaluate(exp.right, env));
case "binary":
return apply_op(exp.operator,
evaluate(exp.left, env),
evaluate(exp.right, env));
case "lambda":
return make_lambda(env, exp);
case "if":
var cond = evaluate(exp.cond, env);
if (cond !== false) return evaluate(exp.then, env);
return exp.else ? evaluate(exp.else, env) : false;
case "prog":
var val = false;
exp.prog.forEach(function(exp){ val = evaluate(exp, env) });
return val;
case "call":
var func = evaluate(exp.func, env);
return func.apply(null, exp.args.map(function(arg){
return evaluate(arg, env);
}));
default:
throw new Error("I don't know how to evaluate " + exp.type);
}
}
function apply_op(op, a, b) {
function num(x) {
if (typeof x != "number")
throw new Error("Expected number but got " + x);
return x;
}
function div(x) {
if (num(x) == 0)
throw new Error("Divide by zero");
return x;
}
switch (op) {
case "+": return num(a) + num(b);
case "-": return num(a) - num(b);
case "*": return num(a) * num(b);
case "/": return num(a) / div(b);
case "%": return num(a) % div(b);
case "&&": return a !== false && b;
case "||": return a !== false ? a : b;
case "<": return num(a) < num(b);
case ">": return num(a) > num(b);
case "<=": return num(a) <= num(b);
case ">=": return num(a) >= num(b);
case "==": return a === b;
case "!=": return a !== b;
}
throw new Error("Can't apply operator " + op);
}
function make_lambda(env, exp) {
function lambda() {
var names = exp.vars;
var scope = env.extend();
for (var i = 0; i < names.length; ++i)
scope.def(names[i], i < arguments.length ? arguments[i] : false);
return evaluate(exp.body, scope);
}
return lambda;
}
/* -----[ entry point for NodeJS ]----- */
var globalEnv = new Environment();
globalEnv.def("time", function(func){
try {
console.time("time");
return func();
} finally {
console.timeEnd("time");
}
});
if (typeof process != "undefined") (function(){
var util = require("util");
globalEnv.def("ፃፍ", function(val){
console.log(val);
});
globalEnv.def("print", function(val){
util.print(val);
});
var code = "";
process.stdin.setEncoding("utf8");
process.stdin.on("readable", function(){
var chunk = process.stdin.read();
if (chunk) code += chunk;
});
process.stdin.on("end", function(){
var ast = parse(TokenStream(InputStream(code)));
evaluate(ast, globalEnv);
});
})();
这是英文版 这应该是 print 的工作方式 "println("hello world!");"我想使用“ፃፍ(“ሰላምሰላም”)፤”其中 'print' 和 ';'被转换为 'ፃፍ' 和 "፤
这是很多代码,除了“它似乎无法处理已插入的字符”之外,没有解释它无法执行的操作,这可能意味着任何事情。如果我有阿姆哈拉语键盘布局并且知道如何使用它打字,我可能会尝试编写代码看看有什么问题,但我没有,也不知道如何使用它,所以我没有试试。
但是,我认为您的 is_id
和 is_id_start
功能不太可能起作用。你有:
function is_id_start(ch) {
return /[^\u1380-\u1380f]+[a-z_]/i.test(ch);
}
function is_id(ch) {
return is_id_start(ch) || "?!-<>=0123456789".indexOf(ch) >= 0;
}
看到 !-<>=
可能是标识符字符,我有点困惑,因为它们也是运算符字符。据推测,其意图是如果标识符后跟这些运算符之一,则两者之间必须有空格。但我认为这不是您在使用阿姆哈拉语时遇到的问题。这似乎更可能与这个有点奇怪的正则表达式有关:/[^\u1380-\u1380f]+[a-z_]/i
.
首先,\u1380f
不是单个 unicode 转义字符。 \u
后面必须紧跟四个十六进制数字,因此 f
不是转义的一部分。就是一个普通的f
。这使得范围 \u1380-\u1380
由单个字符 ᎀ.
您可能指的是 \u1380-\u138f
,即 453 个埃塞俄比亚字母中的 16 个。我对阿姆哈拉语的了解非常有限,当然不足以理解这些特定字符的区别,所以我什至无法开始猜测这是否合理。
但是,您在 反转 正则表达式中使用该范围;即使进行了该更正,[^\u1380-\u138f]
匹配的是 除了 该范围内的任何字符。这将包括来自整个 Unicode 星系的字符,包括许多其他脚本,所以我很确定这不是您想要的。
此外,您的 ID 起始模式实际上是 /[^\u1380-\u1380f]+[a-z_]/i
,意思是“一个或多个 [...] 后跟一个拉丁字母字符或下划线”。换句话说,该模式至少需要两个字符才能匹配,第一个是阿姆哈拉语字符(或者,按照书面形式,除这些阿姆哈拉语字符之一之外的任何字符),然后是拉丁字母字符。
这显然是错误的,因为您将该模式与 input.peek()
的结果进行匹配,后者只能是单个字符。所以is_id
保证returnfalse
,这可能和你的问题有关
就个人而言,我认为使用 Unicode Property classes 比尝试写下有效标识符字符列表要好得多。 (例如,为什么要将标识符限制为仅英语和阿姆哈拉语?)Ecmascript 方便地附带了 Unicode 技术专家推荐用于标识符的标准字符集。你可以直接使用它们:
function is_id_start(ch) {
return /\p{XID_Start}/u.test(ch);
}
function is_id(ch) {
return /[\p{XID_Continue}?!-<>=]/u.test(ch);
}
请注意,您必须使用 u
标志才能启用 Unicode 属性。
如果您真的想将其限制为阿姆哈拉语和拉丁语,您可以通过还要求字符也匹配 [\p{Script=Ethiopic}\p{Script=Latin}]
来实现。 (您可以使用先行断言来做到这一点。)