mirror of
https://github.com/erg-lang/erg.git
synced 2025-08-04 10:49:54 +00:00
feat: allow separating sample code and doc-comments
This commit is contained in:
parent
0c579fa6fb
commit
51cae591a3
15 changed files with 135 additions and 79 deletions
|
@ -39,7 +39,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
.collect::<Vec<_>>();
|
||||
for warn in warns {
|
||||
let uri = util::normalize_url(Url::from_file_path(warn.input.full_path()).unwrap());
|
||||
let Some(token) = self.file_cache.get_token(&uri, util::loc_to_pos(warn.core.loc).unwrap())? else {
|
||||
let Some(token) = self.file_cache.get_token(&uri, util::loc_to_pos(warn.core.loc).unwrap()) else {
|
||||
continue;
|
||||
};
|
||||
match visitor.get_min_expr(&token) {
|
||||
|
@ -75,7 +75,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
let Some(token) = self.file_cache.get_token(&uri, diag.range.start)? else {
|
||||
let Some(token) = self.file_cache.get_token(&uri, diag.range.start) else {
|
||||
continue;
|
||||
};
|
||||
let Some(vi) = visitor.get_info(&token) else {
|
||||
|
@ -137,7 +137,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
) -> ELSResult<Vec<CodeAction>> {
|
||||
let mut actions = vec![];
|
||||
let uri = util::normalize_url(params.text_document.uri.clone());
|
||||
if let Some(token) = self.file_cache.get_token(&uri, params.range.start)? {
|
||||
if let Some(token) = self.file_cache.get_token(&uri, params.range.start) {
|
||||
if token.is(TokenKind::Symbol) && !token.is_const() && !token.content.is_snake_case() {
|
||||
let action = self.gen_change_case_action(token, &uri, params.clone());
|
||||
actions.extend(action);
|
||||
|
|
|
@ -22,7 +22,15 @@ use crate::util;
|
|||
fn mark_to_string(mark: MarkedString) -> String {
|
||||
match mark {
|
||||
MarkedString::String(s) => s,
|
||||
MarkedString::LanguageString(ls) => ls.value,
|
||||
MarkedString::LanguageString(ls) => format!("```{}\n{}\n```", ls.language, ls.value),
|
||||
}
|
||||
}
|
||||
|
||||
fn markdown_order(block: &str) -> usize {
|
||||
if block.starts_with("```") {
|
||||
usize::MAX
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,13 +157,11 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
return Self::send(&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": item }));
|
||||
};
|
||||
self.show_doc_comment(None, &mut contents, &def_loc)?;
|
||||
let mut contents = contents.into_iter().map(mark_to_string).collect::<Vec<_>>();
|
||||
contents.sort_by_key(|cont| markdown_order(cont));
|
||||
item.documentation = Some(Documentation::MarkupContent(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: contents
|
||||
.into_iter()
|
||||
.map(mark_to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
value: contents.join("\n"),
|
||||
}));
|
||||
}
|
||||
Self::send(&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": item }))
|
||||
|
|
|
@ -33,7 +33,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
let params = GotoDefinitionParams::deserialize(&msg["params"])?;
|
||||
let uri = util::normalize_url(params.text_document_position_params.text_document.uri);
|
||||
let pos = params.text_document_position_params.position;
|
||||
let result = if let Some(token) = self.file_cache.get_token(&uri, pos)? {
|
||||
let result = if let Some(token) = self.file_cache.get_token(&uri, pos) {
|
||||
if let Some(vi) = self.get_definition(&uri, &token)? {
|
||||
match (vi.def_loc.module, util::loc_to_range(vi.def_loc.loc)) {
|
||||
(Some(path), Some(range)) => {
|
||||
|
|
|
@ -119,16 +119,14 @@ impl FileCache {
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn get_token(&self, uri: &Url, pos: Position) -> ELSResult<Option<Token>> {
|
||||
let Some(tokens) = self.get_token_stream(uri) else {
|
||||
return Ok(None);
|
||||
};
|
||||
pub fn get_token(&self, uri: &Url, pos: Position) -> Option<Token> {
|
||||
let tokens = self.get_token_stream(uri)?;
|
||||
for tok in tokens.iter() {
|
||||
if util::pos_in_loc(tok, pos) {
|
||||
return Ok(Some(tok.clone()));
|
||||
return Some(tok.clone());
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_token_relatively(
|
||||
|
|
|
@ -12,6 +12,35 @@ use lsp_types::{HoverContents, HoverParams, MarkedString, Url};
|
|||
use crate::server::{ELSResult, Server};
|
||||
use crate::util;
|
||||
|
||||
fn lang_code(code: &str) -> LanguageCode {
|
||||
code.lines()
|
||||
.next()
|
||||
.unwrap_or("")
|
||||
.parse()
|
||||
.unwrap_or(LanguageCode::English)
|
||||
}
|
||||
|
||||
fn language(marked: &MarkedString) -> Option<&str> {
|
||||
match marked {
|
||||
MarkedString::String(_) => None,
|
||||
MarkedString::LanguageString(ls) => Some(&ls.language),
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_hovers(mut contents: Vec<MarkedString>) -> Vec<MarkedString> {
|
||||
// swap "erg" code block (not type definition) and the last element
|
||||
let erg_idx = contents
|
||||
.iter()
|
||||
.rposition(|marked| language(marked) == Some("erg"));
|
||||
if let Some(erg_idx) = erg_idx {
|
||||
let last = contents.len() - 1;
|
||||
if erg_idx != last {
|
||||
contents.swap(erg_idx, last);
|
||||
}
|
||||
}
|
||||
contents
|
||||
}
|
||||
|
||||
fn eliminate_top_indent(code: String) -> String {
|
||||
let trimmed = code.trim_start_matches('\n');
|
||||
if !trimmed.starts_with(' ') {
|
||||
|
@ -28,6 +57,9 @@ fn eliminate_top_indent(code: String) -> String {
|
|||
}
|
||||
result.push('\n');
|
||||
}
|
||||
if !result.is_empty() {
|
||||
result.pop();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
|
@ -35,9 +67,8 @@ macro_rules! next {
|
|||
($def_pos: ident, $default_code_block: ident, $contents: ident, $prev_token: ident, $token: ident) => {
|
||||
if $def_pos.line == 0 {
|
||||
if !$default_code_block.is_empty() {
|
||||
$contents.push(MarkedString::from_markdown(eliminate_top_indent(
|
||||
$default_code_block,
|
||||
)));
|
||||
let code_block = eliminate_top_indent($default_code_block);
|
||||
$contents.push(MarkedString::from_markdown(code_block));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -48,9 +79,8 @@ macro_rules! next {
|
|||
($def_pos: ident, $default_code_block: ident, $contents: ident) => {
|
||||
if $def_pos.line == 0 {
|
||||
if !$default_code_block.is_empty() {
|
||||
$contents.push(MarkedString::from_markdown(eliminate_top_indent(
|
||||
$default_code_block,
|
||||
)));
|
||||
let code_block = eliminate_top_indent($default_code_block);
|
||||
$contents.push(MarkedString::from_markdown(code_block));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -71,7 +101,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
let uri = util::normalize_url(params.text_document_position_params.text_document.uri);
|
||||
let pos = params.text_document_position_params.position;
|
||||
let mut contents = vec![];
|
||||
let opt_tok = self.file_cache.get_token(&uri, pos)?;
|
||||
let opt_tok = self.file_cache.get_token(&uri, pos);
|
||||
let opt_token = if let Some(token) = opt_tok {
|
||||
match token.category() {
|
||||
TokenCategory::StrInterpRight => {
|
||||
|
@ -152,7 +182,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
} else {
|
||||
Self::send_log("lex error")?;
|
||||
}
|
||||
let result = json!({ "contents": HoverContents::Array(contents) });
|
||||
let result = json!({ "contents": HoverContents::Array(sort_hovers(contents)) });
|
||||
Self::send(
|
||||
&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": result }),
|
||||
)
|
||||
|
@ -189,18 +219,22 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
.trim_start_matches("'''")
|
||||
.trim_end_matches("'''")
|
||||
.to_string();
|
||||
// TODO: support other languages
|
||||
let lang = if code_block.starts_with("japanese\n") {
|
||||
LanguageCode::Japanese
|
||||
} else {
|
||||
LanguageCode::English
|
||||
};
|
||||
let lang = lang_code(&code_block);
|
||||
if lang.matches_feature() {
|
||||
let code_block = code_block.trim_start_matches(lang.as_str()).to_string();
|
||||
contents.push(MarkedString::from_markdown(eliminate_top_indent(
|
||||
code_block,
|
||||
)));
|
||||
break;
|
||||
let code_block = eliminate_top_indent(
|
||||
code_block.trim_start_matches(lang.as_str()).to_string(),
|
||||
);
|
||||
let marked = if lang.is_erg() {
|
||||
MarkedString::from_language_code("erg".into(), code_block)
|
||||
} else {
|
||||
MarkedString::from_markdown(code_block)
|
||||
};
|
||||
contents.push(marked);
|
||||
if lang.is_erg() {
|
||||
next!(def_pos, default_code_block, contents, prev_token, token);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if lang.is_en() {
|
||||
default_code_block = code_block;
|
||||
|
@ -215,9 +249,8 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
next!(def_pos, default_code_block, contents, prev_token, token);
|
||||
}
|
||||
if !default_code_block.is_empty() {
|
||||
contents.push(MarkedString::from_markdown(eliminate_top_indent(
|
||||
default_code_block,
|
||||
)));
|
||||
let code_block = eliminate_top_indent(default_code_block);
|
||||
contents.push(MarkedString::from_markdown(code_block));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
let params = ReferenceParams::deserialize(&msg["params"])?;
|
||||
let uri = util::normalize_url(params.text_document_position.text_document.uri);
|
||||
let pos = params.text_document_position.position;
|
||||
if let Some(tok) = self.file_cache.get_token(&uri, pos)? {
|
||||
if let Some(tok) = self.file_cache.get_token(&uri, pos) {
|
||||
// Self::send_log(format!("token: {tok}"))?;
|
||||
if let Some(visitor) = self.get_visitor(&uri) {
|
||||
if let Some(vi) = visitor.get_info(&tok) {
|
||||
|
|
|
@ -29,7 +29,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
Self::send_log(format!("rename request: {params:?}"))?;
|
||||
let uri = util::normalize_url(params.text_document_position.text_document.uri);
|
||||
let pos = params.text_document_position.position;
|
||||
if let Some(tok) = self.file_cache.get_token(&uri, pos)? {
|
||||
if let Some(tok) = self.file_cache.get_token(&uri, pos) {
|
||||
// Self::send_log(format!("token: {tok}"))?;
|
||||
if let Some(visitor) = self.get_visitor(&uri) {
|
||||
if let Some(vi) = visitor.get_info(&tok) {
|
||||
|
|
|
@ -39,6 +39,9 @@ pub fn pos_in_loc<L: Locational>(loc: &L, pos: Position) -> bool {
|
|||
}
|
||||
|
||||
pub fn pos_to_byte_index(src: &str, pos: Position) -> usize {
|
||||
if src.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let mut line = 0;
|
||||
let mut col = 0;
|
||||
for (index, c) in src.char_indices() {
|
||||
|
@ -52,7 +55,7 @@ pub fn pos_to_byte_index(src: &str, pos: Position) -> usize {
|
|||
col += 1;
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
src.char_indices().last().unwrap().0
|
||||
}
|
||||
|
||||
pub fn get_token_stream(uri: Url) -> ELSResult<TokenStream> {
|
||||
|
|
|
@ -6,6 +6,7 @@ pub enum LanguageCode {
|
|||
Japanese,
|
||||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
Erg,
|
||||
}
|
||||
|
||||
impl FromStr for LanguageCode {
|
||||
|
@ -16,6 +17,7 @@ impl FromStr for LanguageCode {
|
|||
"japanese" | "ja" | "jp" => Ok(Self::Japanese),
|
||||
"simplified_chinese" | "zh-CN" => Ok(Self::SimplifiedChinese),
|
||||
"traditional_chinese" | "zh-TW" => Ok(Self::TraditionalChinese),
|
||||
"erg" => Ok(Self::Erg),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +30,7 @@ impl From<LanguageCode> for &str {
|
|||
LanguageCode::Japanese => "japanese",
|
||||
LanguageCode::SimplifiedChinese => "simplified_chinese",
|
||||
LanguageCode::TraditionalChinese => "traditional_chinese",
|
||||
LanguageCode::Erg => "erg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,12 +48,16 @@ impl LanguageCode {
|
|||
pub const fn zh_tw_patterns() -> [&'static str; 2] {
|
||||
["zh-TW", "traditional_chinese"]
|
||||
}
|
||||
pub const fn erg_patterns() -> [&'static str; 2] {
|
||||
["erg", "erg"]
|
||||
}
|
||||
pub const fn patterns(&self) -> [&'static str; 2] {
|
||||
match self {
|
||||
Self::English => Self::en_patterns(),
|
||||
Self::Japanese => Self::ja_patterns(),
|
||||
Self::SimplifiedChinese => Self::zh_cn_patterns(),
|
||||
Self::TraditionalChinese => Self::zh_tw_patterns(),
|
||||
Self::Erg => Self::erg_patterns(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,6 +73,9 @@ impl LanguageCode {
|
|||
pub const fn is_zh_tw(&self) -> bool {
|
||||
matches!(self, Self::TraditionalChinese)
|
||||
}
|
||||
pub const fn is_erg(&self) -> bool {
|
||||
matches!(self, Self::Erg)
|
||||
}
|
||||
|
||||
pub const fn matches_feature(&self) -> bool {
|
||||
match self {
|
||||
|
@ -77,6 +87,7 @@ impl LanguageCode {
|
|||
Self::Japanese => cfg!(feature = "japanese"),
|
||||
Self::SimplifiedChinese => cfg!(feature = "simplified_chinese"),
|
||||
Self::TraditionalChinese => cfg!(feature = "traditional_chinese"),
|
||||
Self::Erg => true,
|
||||
}
|
||||
}
|
||||
pub fn as_str(&self) -> &str {
|
||||
|
|
|
@ -221,20 +221,13 @@ impl Context {
|
|||
Public,
|
||||
Some(FUNC_PRED),
|
||||
);
|
||||
int.register_builtin_py_impl(
|
||||
int.register_py_builtin(
|
||||
FUNC_BIT_LENGTH,
|
||||
fn0_met(Int, Nat),
|
||||
Immutable,
|
||||
Public,
|
||||
Some(FUNC_BIT_LENGTH),
|
||||
28,
|
||||
);
|
||||
int.register_builtin_py_impl(
|
||||
FUNC_BIT_COUNT,
|
||||
fn0_met(Int, Nat),
|
||||
Immutable,
|
||||
Public,
|
||||
Some(FUNC_BIT_COUNT),
|
||||
);
|
||||
int.register_py_builtin(FUNC_BIT_COUNT, fn0_met(Int, Nat), Some(FUNC_BIT_COUNT), 17);
|
||||
let t_from_bytes = func(
|
||||
vec![kw(
|
||||
BYTES,
|
||||
|
|
|
@ -217,10 +217,10 @@ impl Context {
|
|||
poly(ZIP, vec![ty_tp(T.clone()), ty_tp(U.clone())]),
|
||||
)
|
||||
.quantify();
|
||||
self.register_py_builtin(FUNC_ABS, t_abs, Some(FUNC_ABS), 17);
|
||||
self.register_py_builtin(FUNC_ALL, t_all, Some(FUNC_ALL), 29);
|
||||
self.register_py_builtin(FUNC_ANY, t_any, Some(FUNC_ANY), 41);
|
||||
self.register_py_builtin(FUNC_ASCII, t_ascii, Some(FUNC_ASCII), 67);
|
||||
self.register_py_builtin(FUNC_ABS, t_abs, Some(FUNC_ABS), 11);
|
||||
self.register_py_builtin(FUNC_ALL, t_all, Some(FUNC_ALL), 22);
|
||||
self.register_py_builtin(FUNC_ANY, t_any, Some(FUNC_ANY), 33);
|
||||
self.register_py_builtin(FUNC_ASCII, t_ascii, Some(FUNC_ASCII), 53);
|
||||
// Leave as `Const`, as it may negatively affect assert casting.
|
||||
self.register_builtin_erg_impl(FUNC_ASSERT, t_assert, Const, vis);
|
||||
self.register_builtin_py_impl(FUNC_BIN, t_bin, Immutable, vis, Some(FUNC_BIN));
|
||||
|
|
|
@ -116,13 +116,13 @@ impl Context {
|
|||
)
|
||||
.quantify();
|
||||
self.register_builtin_py_impl("dir!", t_dir, Immutable, vis, Some("dir"));
|
||||
self.register_py_builtin("print!", t_print, Some("print"), 95);
|
||||
self.register_py_builtin("print!", t_print, Some("print"), 81);
|
||||
self.register_builtin_py_impl("id!", t_id, Immutable, vis, Some("id"));
|
||||
self.register_builtin_py_impl("input!", t_input, Immutable, vis, Some("input"));
|
||||
self.register_builtin_py_impl("globals!", t_globals, Immutable, vis, Some("globals"));
|
||||
self.register_builtin_py_impl("locals!", t_locals, Immutable, vis, Some("locals"));
|
||||
self.register_builtin_py_impl("next!", t_next, Immutable, vis, Some("next"));
|
||||
self.register_py_builtin("open!", t_open, Some("open"), 212);
|
||||
self.register_py_builtin("open!", t_open, Some("open"), 198);
|
||||
let name = if cfg!(feature = "py_compatible") {
|
||||
"if"
|
||||
} else {
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
'''
|
||||
Return the absolute value of the argument.
|
||||
|
||||
```erg
|
||||
assert abs(-1) == 1
|
||||
assert abs(3+4Im) == 5
|
||||
```
|
||||
'''
|
||||
'''japanese
|
||||
引数の絶対値を返す。
|
||||
|
||||
```erg
|
||||
'''
|
||||
'''erg
|
||||
assert abs(-1) == 1
|
||||
assert abs(3+4Im) == 5
|
||||
```
|
||||
'''
|
||||
.abs: (x: Num) -> Nat
|
||||
|
||||
|
@ -20,11 +14,10 @@ assert abs(3+4Im) == 5
|
|||
Return `True` if `x == True` for all values `x` in the `iterable`.
|
||||
|
||||
If the `iterable` is empty, return `True`.
|
||||
|
||||
```erg
|
||||
'''
|
||||
'''erg
|
||||
assert all [1, 2].map(x -> x > 0)
|
||||
assert all []
|
||||
```
|
||||
'''
|
||||
.all: (iterable: Iterable Bool) -> Bool
|
||||
|
||||
|
@ -32,11 +25,10 @@ assert all []
|
|||
Return `True` if `x == True` for any value `x` in the `iterable`.
|
||||
|
||||
If the `iterable` is empty, return `False`.
|
||||
|
||||
```erg
|
||||
'''
|
||||
'''erg
|
||||
assert any [-1, 1, 2].map(x -> x < 0)
|
||||
assert not any []
|
||||
```
|
||||
'''
|
||||
.any: (iterable: Iterable Bool) -> Bool
|
||||
|
||||
|
@ -47,22 +39,16 @@ As repr(), return a string containing a printable representation of an
|
|||
object, but escape the non-ASCII characters in the string returned by
|
||||
repr() using \\x, \\u or \\U escapes. This generates a string similar
|
||||
to that returned by repr() in Python 2.
|
||||
|
||||
```erg
|
||||
assert ascii("a") == "'a'"
|
||||
assert ascii("あ") == "'\\u3042'"
|
||||
```
|
||||
'''
|
||||
'''japanese
|
||||
オブジェクトのASCII表現を返す。
|
||||
|
||||
repr()と同じように、オブジェクトの表現を文字列で返すが、repr()で返される文字列の非ASCII文字は\\x、\\u、\\Uエスケープでエスケープされる。
|
||||
これはPython 2でのrepr()と似た文字列を生成する。
|
||||
|
||||
```erg
|
||||
'''
|
||||
'''erg
|
||||
assert ascii("a") == "'a'"
|
||||
assert ascii("あ") == "'\\u3042'"
|
||||
```
|
||||
'''
|
||||
.ascii: (obj: Obj) -> Str
|
||||
|
||||
|
|
|
@ -127,6 +127,19 @@ cf. https://www.google.co.jp/search?q=answer+to+life+the+universe+and+everything
|
|||
ANSWER = 42
|
||||
```
|
||||
|
||||
Also, if you specify `erg`, it will be displayed as Erg's sample code.
|
||||
|
||||
```python
|
||||
'''
|
||||
the identity function, does nothing but returns the argument
|
||||
'''
|
||||
'''erg
|
||||
assert id(1) == 1
|
||||
assert id("a") == "a"
|
||||
'''
|
||||
id x = x
|
||||
```
|
||||
|
||||
## Expressions, separators
|
||||
|
||||
A script is a series of expressions. An expression is something that can be calculated or evaluated, and in Erg almost everything is an expression.
|
||||
|
|
|
@ -104,6 +104,19 @@ cf. https://www.google.co.jp/search?q=answer+to+life+the+universe+and+everything
|
|||
ANSWER = 42
|
||||
```
|
||||
|
||||
また`erg`と指定すると、Ergのサンプルコードとして表示されます。
|
||||
|
||||
```python
|
||||
'''
|
||||
the identity function, does nothing but returns the argument
|
||||
'''
|
||||
'''erg
|
||||
assert id(1) == 1
|
||||
assert id("a") == "a"
|
||||
'''
|
||||
id x = x
|
||||
```
|
||||
|
||||
## 式、セパレータ
|
||||
|
||||
スクリプトは、式(expression)の連なりです。式とは計算・評価ができるもので、Ergではほとんどすべてのものが式です。
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue