用特定逻辑替换字符串中的字符

Posted

技术标签:

【中文标题】用特定逻辑替换字符串中的字符【英文标题】:Replacing characters in Strings with specific logic 【发布时间】:2016-04-07 18:13:19 【问题描述】:

我目前正在用 Java 创建一个简单的 Lexer 生成器。我差不多完成了,在这里和那里解决了一些错误,但遇到了一个问题。

我希望能够在 Lexer 中包含评论检测,并希望以特定方式包含 cmets:

注释由任何字符集分隔,存储在字符串中。 示例:single = "//"multi_beg = "/*"multi_end = "*/" 注释字符之间和包括注释字符之间的任何内容都需要替换为空格,因为 Lexer 使用空格来检测标记之间的间隙。 (用 void 替换可能会导致两个令牌融合在一起)

包含这样的东西在技术上很容易,只需几个布尔值和字符串替换。但是,Lexer 还包含可定义的字符串和字符常量。由于可以在这些常量中定义注释字符,因此当当前未定义字符串或字符时,cmets 只能是“可执行的”。


这方面的逻辑可能如下所示:

如果当前已封装,则不执行任何操作,直到找到未封装字符。 如果未封装,并且找到封装字符,则封装。 如果未封装,并且找到 单行 行注释,请将所有 之后的内容(包括注释字符)替换为空格。 (我们处理的是单个字符串,而不是数组,因此 之后的所有内容仅引用一行) 如果没有封装,并且开始了 多行 行注释,请将所有 之后的内容(包括注释字符)替换为空格,直到多行注释为 结束

我的脑海里已经完全记下了这个想法,但不知道如何在 Java 中实现它。

注意:我正在处理一个字符串数组,并且正在考虑通过增强的 for 循环来实现这一点,在 Scanner 中一次处理每一行,在标记堆栈之前处理 cmets。

for (String s : data) 
    // ???

关于如何用 Java 实现这一点有什么想法吗?

更新:这是我希望我的输入/输出的样子:

【问题讨论】:

您要对输入数组执行操作吗?通常词法分析器将输入拆分为标记,一个新的结构,而不修改输入。为什么下一个标记不能只是一个完整的字符串文字? @Basilevs 我已将词法分析器调整为多个部分。首先是 Scanner,其次是 Tokenizer。在将数据显示到 Tokeniser 之前,我想简单地从字符串中完全“擦除” cmets,几乎就像完成了白化一样。 @Basilevs 这将是迄今为止执行的唯一操作。 现在你有两个标记器没有明显的好处,不是吗? @Basilevs 不。我想从字符串数组中的字符串中删除数据。一旦字符串数组中所有可能出现的位置都被删除,发送该数组以进行tokenised 【参考方案1】:

这是一个未经测试的实现。测试是最难的部分,要非常小心。

public class CommentStripper 
    private enum State 
        CODE,
        LINE_COMMENT,
        COMMENT,
        STRING
    
    public static String strip(String input) 
        return strip(input.toCharArray());
    

    public static String strip(char[] input) 
        State currentState = State.CODE;
        StringBuilder rv = new StringBuilder();
        char[] lineSeparator = System.lineSeparator().toCharArray();
        for (int i = 0; i < input.length; i++) 
            STATE_SWITCH: switch (currentState) 
            case CODE: 
                if (input[i] == '"') 
                    currentState = State.STRING;
                    rv.append(input[i]);
                    break;
                
                if (input[i] == '/') 
                    if (i + 1 >= input.length) 
                        rv.append(input[i]);
                        break;
                    
                    if (input[i+1] == '*') 
                        i++;
                        currentState = State.COMMENT;
                        break;
                     else if (input[i+1] == '/') 
                        i++;
                        currentState = State.LINE_COMMENT;
                        break;
                    
                
                rv.append(input[i]);
            break;
            case STRING:
                if (input[i] == '"') 
                    currentState = State.CODE;
                    rv.append(input[i]);
                    break;
                
                rv.append(input[i]);
                break;
            case COMMENT:
                if (input[i] == '*') 
                    if (i + 1 >= input.length) 
                        break;
                    
                    if (input[i + 1] == '/') 
                        i++;
                        currentState = State.CODE;
                        break;
                    
                
                break;
            case LINE_COMMENT:
                for (int sepIndex = 0; sepIndex < lineSeparator.length; sepIndex++) 
                    if (input[i+sepIndex] != lineSeparator[sepIndex]) 
                        break STATE_SWITCH;
                    
                
                i+=lineSeparator.length-1;
                rv.append(lineSeparator);
                currentState = State.CODE;
                break;
             
        
        return rv.toString();
    

初步测试:

import static commentStrip.CommentStripper.strip;
import static org.junit.Assert.*;

import org.junit.Test;

public class CommentStripperTest 

    @Test
    public void test() 
        assertEquals("\"test\"", strip("\"test\"//hello\"test\""));
        assertEquals("\"test\"\"test\"", strip("\"test\"/*hello*/\"test\""));
        assertEquals("test"+System.lineSeparator()+"test", strip("test//linecomment"+System.lineSeparator()+"test"));
        assertEquals("test", strip("test/*test"));
        assertEquals("\"test//hellotest\"", strip("\"test//hellotest\""));
        assertEquals("\"test/*hello*/test\"", strip("\"test/*hello*/test\""));
    


选择基于数组的简约方法是因为它的效率。流式 API 使这变得臃肿或无效。您必须连接您的字符串才能使用它。

请注意,无法将转义的引号放在字符串文字中。您的问题未指定此内容,因此我省略了对此的处理。

考虑使用像 ANTLR 这样的解析库,而不是自己编写解析器。

【讨论】:

抱歉回复晚了。最近一直在工作,真的很忙。我为我的发电机修改了它,效果很好。非常感谢。至于ANTLR,也许我会把发电机挂在里面? :)【参考方案2】:

通常的做法是将这项工作留给分词器,并使 cmets 成为一种空白令牌。

【讨论】:

我明白,但我根本不想要令牌。我希望在将任何数据推送到标记器之前将其删除,回到 Scanner 部分。 为什么要这样? 我用一个扫描标记的结构编写了我的 Lexer,只有当它是一个符号、数字或字符时才停止标记它。如果它是一个符号,则将其映射到其对应的字符,并吐出一个标记。数字,吐出一个数字或实数。 Alphabetical/Constant Char,吐出一个标识符或一个字符串/字符。跳过空格。

以上是关于用特定逻辑替换字符串中的字符的主要内容,如果未能解决你的问题,请参考以下文章

用空格替换 std::string 中的特定字符

替换字符串中的特定单词(Python)

使用正则表达式替换字符串中的特定字母

C++:寻找一种简洁的解决方案,用特定字符替换 std::string 中的一组字符

替换第一列文本中的特定字符

Oracle SQL REGEXP 用不同的列值替换特定字符串