feat: allow separating sample code and doc-comments

This commit is contained in:
Shunsuke Shibayama 2023-02-17 20:37:15 +09:00
parent 0c579fa6fb
commit 51cae591a3
15 changed files with 135 additions and 79 deletions

View file

@ -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);

View file

@ -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 }))

View file

@ -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)) => {

View file

@ -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(

View file

@ -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;
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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> {

View file

@ -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 {

View file

@ -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,

View file

@ -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));

View file

@ -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 {

View file

@ -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

View file

@ -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.

View file

@ -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ではほとんどすべてのものが式です。