Premiers pas
Modèles et tarifs
Explorer les fonctionnalités
En savoir plus sur Claude
- Construire avec Claude
- Cas d'utilisation
- Fenêtres de contexte
- Glossaire
- Ingénierie de prompts
Centre juridique
Tester et évaluer
- Définir les critères de réussite
- Développer des cas de test
- Renforcer les garde-fous
- Utilisation de l'outil d'évaluation
Composants d'agent
- Outils
- Protocole de Contexte du Modèle (MCP)
- Utilisation d'ordinateur (bêta)
- Extension Google Sheets
Modération de contenu
La modération de contenu est un aspect essentiel du maintien d’un environnement sûr, respectueux et productif dans les applications numériques. Dans ce guide, nous verrons comment Claude peut être utilisé pour modérer le contenu au sein de votre application numérique.
Visitez notre guide pratique de modération de contenu pour voir un exemple d’implémentation de modération de contenu utilisant Claude.
Avant de construire avec Claude
Décider si vous devez utiliser Claude pour la modération de contenu
Voici quelques indicateurs clés qui suggèrent que vous devriez utiliser un LLM comme Claude plutôt qu’une approche traditionnelle basée sur le ML ou des règles pour la modération de contenu :
Générer des exemples de contenu à modérer
Avant de développer une solution de modération de contenu, créez d’abord des exemples de contenu qui devraient être signalés et de contenu qui ne devrait pas l’être. Assurez-vous d’inclure des cas limites et des scénarios difficiles qui peuvent être complexes à gérer efficacement pour un système de modération de contenu. Ensuite, examinez vos exemples pour créer une liste bien définie de catégories de modération. Par exemple, les exemples générés par une plateforme de médias sociaux pourraient inclure ce qui suit :
allowed_user_comments = [
'Ce film était génial, je l\'ai vraiment apprécié. L\'acteur principal a vraiment tué !',
'Je déteste les lundis.',
'C\'est un bon moment pour investir dans l\'or !'
]
disallowed_user_comments = [
'Supprime ce message maintenant ou tu ferais mieux de te cacher. Je viens pour toi et ta famille.',
'Restez à l\'écart des téléphones 5G !! Ils utilisent la 5G pour vous contrôler.',
'Félicitations ! Vous avez gagné une carte-cadeau de 1 000 $. Cliquez ici pour réclamer votre prix !'
]
# Exemples de commentaires d'utilisateurs pour tester la modération de contenu
user_comments = allowed_user_comments + disallowed_user_comments
# Liste des catégories considérées comme dangereuses pour la modération de contenu
unsafe_categories = [
'Exploitation des enfants',
'Théories du complot',
'Haine',
'Armes à destruction massive',
'Propriété intellectuelle',
'Crimes non violents',
'Vie privée',
'Automutilation',
'Crimes sexuels',
'Contenu sexuel',
'Conseils spécialisés',
'Crimes violents'
]
Pour modérer efficacement ces exemples, il faut une compréhension nuancée du langage. Dans le commentaire Ce film était génial, je l'ai vraiment apprécié. L'acteur principal a vraiment tué !
, le système de modération de contenu doit reconnaître que “a vraiment tué” est une métaphore, et non une indication de violence réelle. À l’inverse, malgré l’absence de mentions explicites de violence, le commentaire Supprime ce message maintenant ou tu ferais mieux de te cacher. Je viens pour toi et ta famille.
devrait être signalé par le système de modération de contenu.
La liste unsafe_categories
peut être personnalisée pour répondre à vos besoins spécifiques. Par exemple, si vous souhaitez empêcher les mineurs de créer du contenu sur votre site web, vous pourriez ajouter “Publication par des mineurs” à la liste.
Comment modérer le contenu en utilisant Claude
Sélectionner le bon modèle Claude
Lors de la sélection d’un modèle, il est important de prendre en compte la taille de vos données. Si les coûts sont une préoccupation, un modèle plus petit comme Claude Haiku 3 est un excellent choix en raison de son rapport coût-efficacité. Voici une estimation du coût de modération de texte pour une plateforme de médias sociaux qui reçoit un milliard de publications par mois :
-
Taille du contenu
- Publications par mois : 1 milliard
- Caractères par publication : 100
- Total des caractères : 100 milliards
-
Tokens estimés
- Tokens d’entrée : 28,6 milliards (en supposant 1 token pour 3,5 caractères)
- Pourcentage de messages signalés : 3%
- Tokens de sortie par message signalé : 50
- Total des tokens de sortie : 1,5 milliard
-
Coût estimé pour Claude Haiku 3
- Coût des tokens d’entrée : 2 860 MTok * 0,25 /MTok=715
- Coût des tokens de sortie : 1 500 MTok * 1,25 /MTok=1875
- Coût mensuel : 715 +1875 = 2 590 $
-
Coût estimé pour Claude Sonnet 4
- Coût des tokens d’entrée : 2 860 MTok * 3,00 /MTok=8580
- Coût des tokens de sortie : 1 500 MTok * 15,00 /MTok=22500
- Coût mensuel : 8 580 +22500 = 31 080 $
explanation
de la réponse.Construire une invite solide
Pour utiliser Claude pour la modération de contenu, Claude doit comprendre les exigences de modération de votre application. Commençons par rédiger une invite qui vous permet de définir vos besoins de modération :
import anthropic
import json
# Initialiser le client Anthropic
client = anthropic.Anthropic()
def moderate_message(message, unsafe_categories):
# Convertir la liste des catégories dangereuses en une chaîne, avec chaque catégorie sur une nouvelle ligne
unsafe_category_str = '\n'.join(unsafe_categories)
# Construire l'invite pour Claude, incluant le message et les catégories dangereuses
assessment_prompt = f"""
Déterminez si le message suivant nécessite une modération,
en fonction des catégories dangereuses décrites ci-dessous.
Message:
<message>{message}</message>
Catégories dangereuses:
<categories>
{unsafe_category_str}
</categories>
Répondez UNIQUEMENT avec un objet JSON, en utilisant le format ci-dessous:
{{
"violation": <Champ booléen indiquant si le message doit être modéré>,
"categories": [Liste des catégories enfreintes, séparées par des virgules],
"explanation": [Optionnel. À inclure uniquement s'il y a une violation.]
}}"""
# Envoyer la demande à Claude pour la modération de contenu
response = client.messages.create(
model="claude-3-haiku-20240307", # Utilisation du modèle Haiku pour des coûts plus bas
max_tokens=200,
temperature=0, # Utiliser une température de 0 pour une cohérence accrue
messages=[
{"role": "user", "content": assessment_prompt}
]
)
# Analyser la réponse JSON de Claude
assessment = json.loads(response.content[0].text)
# Extraire le statut de violation de l'évaluation
contains_violation = assessment['violation']
# S'il y a une violation, obtenir les catégories et l'explication; sinon, utiliser des valeurs par défaut vides
violated_categories = assessment.get('categories', []) if contains_violation else []
explanation = assessment.get('explanation') if contains_violation else None
return contains_violation, violated_categories, explanation
# Traiter chaque commentaire et afficher les résultats
for comment in user_comments:
print(f"\nCommentaire: {comment}")
violation, violated_categories, explanation = moderate_message(comment, unsafe_categories)
if violation:
print(f"Catégories enfreintes: {', '.join(violated_categories)}")
print(f"Explication: {explanation}")
else:
print("Aucun problème détecté.")
Dans cet exemple, la fonction moderate_message
contient une invite d’évaluation qui inclut les catégories de contenu dangereux et le message que nous souhaitons évaluer. L’invite demande à Claude d’évaluer si le message doit être modéré, en fonction des catégories dangereuses que nous avons définies.
L’évaluation du modèle est ensuite analysée pour déterminer s’il y a une violation. S’il y a une violation, Claude renvoie également une liste des catégories enfreintes, ainsi qu’une explication sur les raisons pour lesquelles le message est dangereux.
Évaluer votre invite
La modération de contenu est un problème de classification. Ainsi, vous pouvez utiliser les mêmes techniques décrites dans notre guide pratique de classification pour déterminer la précision de votre système de modération de contenu.
Une considération supplémentaire est qu’au lieu de traiter la modération de contenu comme un problème de classification binaire, vous pouvez créer plusieurs catégories pour représenter différents niveaux de risque. La création de plusieurs niveaux de risque vous permet d’ajuster l’agressivité de votre modération. Par exemple, vous pourriez vouloir bloquer automatiquement les requêtes des utilisateurs jugées à haut risque, tandis que les utilisateurs ayant de nombreuses requêtes à risque moyen sont signalés pour un examen humain.
import anthropic
import json
# Initialiser le client Anthropic
client = anthropic.Anthropic()
def assess_risk_level(message, unsafe_categories):
# Convertir la liste des catégories dangereuses en une chaîne, avec chaque catégorie sur une nouvelle ligne
unsafe_category_str = '\n'.join(unsafe_categories)
# Construire l'invite pour Claude, incluant le message, les catégories dangereuses et les définitions des niveaux de risque
assessment_prompt = f"""
Évaluez le niveau de risque du message suivant nécessitant une modération,
en fonction des catégories dangereuses listées ci-dessous.
Message:
<message>{message}</message>
Catégories dangereuses:
<categories>
{unsafe_category_str}
</categories>
Attribuez un niveau de risque en fonction de votre confiance que le message de l'utilisateur devrait être modéré
selon les catégories dangereuses définies:
0 - Aucun risque
1 - Risque faible
2 - Risque moyen
3 - Risque élevé
Répondez UNIQUEMENT avec un objet JSON, en utilisant le format ci-dessous:
{{
"risk_level": <Champ numérique indiquant le niveau de risque>,
"categories": [Liste des catégories enfreintes, séparées par des virgules],
"explanation": <Optionnel. À inclure uniquement si le niveau de risque est supérieur à 0>
}}"""
# Envoyer la demande à Claude pour l'évaluation des risques
response = client.messages.create(
model="claude-3-haiku-20240307", # Utilisation du modèle Haiku pour des coûts plus bas
max_tokens=200,
temperature=0, # Utiliser une température de 0 pour une cohérence accrue
messages=[
{"role": "user", "content": assessment_prompt}
]
)
# Analyser la réponse JSON de Claude
assessment = json.loads(response.content[0].text)
# Extraire le niveau de risque, les catégories enfreintes et l'explication de l'évaluation
risk_level = assessment["risk_level"]
violated_categories = assessment["categories"]
explanation = assessment.get("explanation")
return risk_level, violated_categories, explanation
# Traiter chaque commentaire et afficher les résultats
for comment in user_comments:
print(f"\nCommentaire: {comment}")
risk_level, violated_categories, explanation = assess_risk_level(comment, unsafe_categories)
print(f"Niveau de risque: {risk_level}")
if violated_categories:
print(f"Catégories enfreintes: {', '.join(violated_categories)}")
if explanation:
print(f"Explication: {explanation}")
Ce code implémente une fonction assess_risk_level
qui utilise Claude pour évaluer le niveau de risque d’un message. La fonction accepte un message et une liste de catégories dangereuses comme entrées.
Dans la fonction, une invite est générée pour Claude, incluant le message à évaluer, les catégories dangereuses et des instructions spécifiques pour évaluer le niveau de risque. L’invite demande à Claude de répondre avec un objet JSON qui inclut le niveau de risque, les catégories enfreintes et une explication optionnelle.
Cette approche permet une modération de contenu flexible en attribuant des niveaux de risque. Elle peut être intégrée de manière transparente dans un système plus large pour automatiser le filtrage de contenu ou signaler des commentaires pour un examen humain en fonction de leur niveau de risque évalué. Par exemple, lors de l’exécution de ce code, le commentaire Supprime ce message maintenant ou tu ferais mieux de te cacher. Je viens pour toi et ta famille.
est identifié comme à haut risque en raison de sa menace dangereuse. À l’inverse, le commentaire Restez à l'écart des téléphones 5G !! Ils utilisent la 5G pour vous contrôler.
est classé comme à risque moyen.
Déployer votre invite
Une fois que vous êtes confiant dans la qualité de votre solution, il est temps de la déployer en production. Voici quelques bonnes pratiques à suivre lors de l’utilisation de la modération de contenu en production :
-
Fournir un retour clair aux utilisateurs : Lorsque l’entrée d’un utilisateur est bloquée ou qu’une réponse est signalée en raison de la modération de contenu, fournissez un retour informatif et constructif pour aider les utilisateurs à comprendre pourquoi leur message a été signalé et comment ils peuvent le reformuler de manière appropriée. Dans les exemples de code ci-dessus, cela se fait via la balise
explanation
dans la réponse de Claude. -
Analyser le contenu modéré : Gardez une trace des types de contenu signalés par votre système de modération pour identifier les tendances et les domaines potentiels d’amélioration.
-
Évaluer et améliorer continuellement : Évaluez régulièrement les performances de votre système de modération de contenu en utilisant des métriques telles que le suivi de la précision et du rappel. Utilisez ces données pour affiner itérativement vos invites de modération, vos mots-clés et vos critères d’évaluation.
Améliorer les performances
Dans des scénarios complexes, il peut être utile d’envisager des stratégies supplémentaires pour améliorer les performances au-delà des techniques d’ingénierie d’invite standard. Voici quelques stratégies avancées :
Définir des sujets et fournir des exemples
En plus de lister les catégories dangereuses dans l’invite, des améliorations supplémentaires peuvent être apportées en fournissant des définitions et des phrases liées à chaque catégorie.
import anthropic
import json
# Initialiser le client Anthropic
client = anthropic.Anthropic()
# Dictionnaire des catégories considérées comme dangereuses pour la modération de contenu, avec leurs définitions
unsafe_category_definitions = {
'Exploitation des enfants': 'Contenu qui représente la nudité d\'enfants ou qui permet, encourage, excuse ou représente l\'abus sexuel d\'enfants.',
'Théories du complot': """Contenu qui promeut ou approuve des théories non fondées, fausses ou trompeuses sur des événements, des situations ou des individus,
qui peuvent inciter à nuire ou miner la confiance du public dans les institutions ou les faits.""",
'Haine': """Contenu qui est haineux envers les personnes sur la base de leurs caractéristiques protégées
(race, couleur, ethnicité, origine nationale, handicap, affiliation religieuse, caste, orientation sexuelle, sexe, identité de genre et maladie grave),
ou contenu qui perpétue des stéréotypes négatifs.""",
'Armes à destruction massive': 'Contenu qui permet la création d\'armes de destruction massive.',
'Propriété intellectuelle': 'Contenu qui viole les droits de propriété intellectuelle de tiers.',
'Crimes non violents': 'Contenu qui permet, encourage ou excuse la commission de crimes non violents.',
'Vie privée': 'Contenu qui contient des informations sensibles et personnelles sur des individus privés.',
'Automutilation': 'Contenu qui permet, encourage ou excuse des actes d\'automut�# Shuumatsu/scarlet
# src/parser/parser.rs
use std::collections::HashMap;
use crate::ast::*;
use crate::lexer::*;
use crate::token::*;
#[derive(Debug, PartialEq, PartialOrd)]
enum Precedence {
Lowest,
Equals, // ==
LessGreater, // > or <
Sum, // +
Product, // *
Prefix, // -X or !X
Call, // myFunction(X)
Index, // array[index]
}
impl From<&Token> for Precedence {
fn from(token: &Token) -> Self {
match token {
Token::Equal | Token::NotEqual => Precedence::Equals,
Token::LessThan | Token::GreaterThan => Precedence::LessGreater,
Token::Plus | Token::Minus => Precedence::Sum,
Token::Slash | Token::Asterisk => Precedence::Product,
Token::LParen => Precedence::Call,
Token::LBracket => Precedence::Index,
_ => Precedence::Lowest,
}
}
}
type PrefixParseFn = fn(&mut Parser) -> Option<Expression>;
type InfixParseFn = fn(&mut Parser, Expression) -> Option<Expression>;
pub struct Parser {
lexer: Lexer,
curr_token: Token,
peek_token: Token,
errors: Vec<String>,
prefix_parse_fns: HashMap<Token, PrefixParseFn>,
infix_parse_fns: HashMap<Token, InfixParseFn>,
}
impl Parser {
pub fn new(lexer: Lexer) -> Self {
let mut parser = Parser {
lexer,
curr_token: Token::Illegal,
peek_token: Token::Illegal,
errors: Vec::new(),
prefix_parse_fns: HashMap::new(),
infix_parse_fns: HashMap::new(),
};
parser.register_prefix(Token::Ident("".to_string()), Parser::parse_identifier);
parser.register_prefix(Token::Int("".to_string()), Parser::parse_integer_literal);
parser.register_prefix(Token::Bang, Parser::parse_prefix_expression);
parser.register_prefix(Token::Minus, Parser::parse_prefix_expression);
parser.register_prefix(Token::True, Parser::parse_boolean);
parser.register_prefix(Token::False, Parser::parse_boolean);
parser.register_prefix(Token::LParen, Parser::parse_grouped_expression);
parser.register_prefix(Token::If, Parser::parse_if_expression);
parser.register_prefix(Token::Function, Parser::parse_function_literal);
parser.register_prefix(Token::String("".to_string()), Parser::parse_string_literal);
parser.register_prefix(Token::LBracket, Parser::parse_array_literal);
parser.register_prefix(Token::LBrace, Parser::parse_hash_literal);
parser.register_infix(Token::Plus, Parser::parse_infix_expression);
parser.register_infix(Token::Minus, Parser::parse_infix_expression);
parser.register_infix(Token::Slash, Parser::parse_infix_expression);
parser.register_infix(Token::Asterisk, Parser::parse_infix_expression);
parser.register_infix(Token::Equal, Parser::parse_infix_expression);
parser.register_infix(Token::NotEqual, Parser::parse_infix_expression);
parser.register_infix(Token::LessThan, Parser::parse_infix_expression);
parser.register_infix(Token::GreaterThan, Parser::parse_infix_expression);
parser.register_infix(Token::LParen, Parser::parse_call_expression);
parser.register_infix(Token::LBracket, Parser::parse_index_expression);
parser.next_token();
parser.next_token();
parser
}
fn register_prefix(&mut self, token: Token, func: PrefixParseFn) {
self.prefix_parse_fns.insert(token, func);
}
fn register_infix(&mut self, token: Token, func: InfixParseFn) {
self.infix_parse_fns.insert(token, func);
}
fn next_token(&mut self) {
self.curr_token = self.peek_token.clone();
self.peek_token = self.lexer.next_token();
}
pub fn parse_program(&mut self) -> Program {
let mut program = Program { statements: vec![] };
while !self.curr_token_is(Token::Eof) {
if let Some(stmt) = self.parse_statement() {
program.statements.push(stmt);
}
self.next_token();
}
program
}
fn parse_statement(&mut self) -> Option<Statement> {
match self.curr_token {
Token::Let => self.parse_let_statement(),
Token::Return => self.parse_return_statement(),
_ => self.parse_expression_statement(),
}
}
fn parse_let_statement(&mut self) -> Option<Statement> {
let curr_token = self.curr_token.clone();
if !self.expect_peek(Token::Ident("".to_string())) {
return None;
}
let name = Identifier {
token: self.curr_token.clone(),
value: match &self.curr_token {
Token::Ident(value) => value.clone(),
_ => unreachable!(),
},
};
if !self.expect_peek(Token::Assign) {
return None;
}
self.next_token();
let value = match self.parse_expression(Precedence::Lowest) {
Some(expr) => expr,
None => return None,
};
if self.peek_token_is(Token::Semicolon) {
self.next_token();
}
Some(Statement::Let(LetStatement {
token: curr_token,
name,
value,
}))
}
fn parse_return_statement(&mut self) -> Option<Statement> {
let curr_token = self.curr_token.clone();
self.next_token();
let return_value = match self.parse_expression(Precedence::Lowest) {
Some(expr) => expr,
None => return None,
};
if self.peek_token_is(Token::Semicolon) {
self.next_token();
}
Some(Statement::Return(ReturnStatement {
token: curr_token,
return_value,
}))
}
fn parse_expression_statement(&mut self) -> Option<Statement> {
let curr_token = self.curr_token.clone();
let expression = match self.parse_expression(Precedence::Lowest) {
Some(expr) => expr,
None => return None,
};
let stmt = Statement::Expression(ExpressionStatement {
token: curr_token,
expression,
});
if self.peek_token_is(Token::Semicolon) {
self.next_token();
}
Some(stmt)
}
fn parse_expression(&mut self, precedence: Precedence) -> Option<Expression> {
let prefix = match self.prefix_parse_fns.get(&self.curr_token) {
Some(prefix_fn) => prefix_fn,
None => {
self.no_prefix_parse_fn_error(&self.curr_token);
return None;
}
};
let mut left_exp = prefix(self)?;
while !self.peek_token_is(Token::Semicolon) && precedence < Precedence::from(&self.peek_token)
{
let infix = match self.infix_parse_fns.get(&self.peek_token) {
Some(infix_fn) => infix_fn,
None => return Some(left_exp),
};
self.next_token();
left_exp = infix(self, left_exp)?;
}
Some(left_exp)
}
fn parse_identifier(&mut self) -> Option<Expression> {
Some(Expression::Identifier(Identifier {
token: self.curr_token.clone(),
value: match &self.curr_token {
Token::Ident(value) => value.clone(),
_ => unreachable!(),
},
}))
}
fn parse_integer_literal(&mut self) -> Option<Expression> {
let token = self.curr_token.clone();
let value = match &self.curr_token {
Token::Int(value) => match value.parse::<i64>() {
Ok(v) => v,
Err(_) => {
self.errors.push(format!(
"could not parse {} as integer",
value.to_string()
));
return None;
}
},
_ => unreachable!(),
};
Some(Expression::IntegerLiteral(IntegerLiteral { token, value }))
}
fn parse_prefix_expression(&mut self) -> Option<Expression> {
let token = self.curr_token.clone();
let operator = match &self.curr_token {
Token::Bang => "!".to_string(),
Token::Minus => "-".to_string(),
_ => unreachable!(),
};
self.next_token();
let right = match self.parse_expression(Precedence::Prefix) {
Some(expr) => Box::new(expr),
None => return None,
};
Some(Expression::PrefixExpression(PrefixExpression {
token,
operator,
right,
}))
}
fn parse_infix_expression(&mut self, left: Expression) -> Option<Expression> {
let token = self.curr_token.clone();
let operator = match &self.curr_token {
Token::Plus => "+".to_string(),
Token::Minus => "-".to_string(),
Token::Asterisk => "*".to_string(),
Token::Slash => "/".to_string(),
Token::Equal => "==".to_string(),
Token::NotEqual => "!=".to_string(),
Token::LessThan => "<".to_string(),
Token::GreaterThan => ">".to_string(),
_ => unreachable!(),
};
let precedence = Precedence::from(&self.curr_token);
self.next_token();
let right = match self.parse_expression(precedence) {
Some(expr) => Box::new(expr),
None => return None,
};
Some(Expression::InfixExpression(InfixExpression {
token,
left: Box::new(left),
operator,
right,
}))
}
fn parse_boolean(&mut self) -> Option<Expression> {
Some(Expression::Boolean(Boolean {
token: self.curr_token.clone(),
value: self.curr_token_is(Token::True),
}))
}
fn parse_grouped_expression(&mut self) -> Option<Expression> {
self.next_token();
let exp = self.parse_expression(Precedence::Lowest);
if !self.expect_peek(Token::RParen) {
return None;
}
exp
}
fn parse_if_expression(&mut self) -> Option<Expression> {
let token = self.curr_token.clone();
if !self.expect_peek(Token::LParen) {
return None;
}
self.next_token();
let condition = match self.parse_expression(Precedence::Lowest) {
Some(expr) => Box::new(expr),
None => return None,
};
if !self.expect_peek(Token::RParen) {
return None;
}
if !self.expect_peek(Token::LBrace) {
return None;
}
let consequence = self.parse_block_statement();
let alternative = if self.peek_token_is(Token::Else) {
self.next_token();
if !self.expect_peek(Token::LBrace) {
return None;
}
Some(self.parse_block_statement())
} else {
None
};
Some(Expression::IfExpression(IfExpression {
token,
condition,
consequence,
alternative,
}))
}
fn parse_block_statement(&mut self) -> BlockStatement {
let token = self.curr_token.clone();
let mut statements = Vec::new();
self.next_token();
while !self.curr_token_is(Token::RBrace) && !self.curr_token_is(Token::Eof) {
if let Some(stmt) = self.parse_statement() {
statements.push(stmt);
}
self.next_token();
}
BlockStatement { token, statements }
}
fn parse_function_literal(&mut self) -> Option<Expression> {
let token = self.curr_token.clone();
if !self.expect_peek(Token::LParen) {
return None;
}
let parameters = self.parse_function_parameters();
if !self.expect_peek(Token::LBrace) {
return None;
}
let body = self.parse_block_statement();
Some(Expression::FunctionLiteral(FunctionLiteral {
token,
parameters,
body,
}))
}
fn parse_function_parameters(&mut self) -> Vec<Identifier> {
let mut identifiers = Vec::new();
if self.peek_token_is(Token::RParen) {
self.next_token();
return identifiers;
}
self.next_token();
identifiers.push(Identifier {
token: self.curr_token.clone(),
value: match &self.curr_token {
Token::Ident(value) => value.clone(),
_ => unreachable!(),
},
});
while self.peek_token_is(Token::Comma) {
self.next_token();
self.next_token();
identifiers.push(Identifier {
token: self.curr_token.clone(),
value: match &self.curr_token {
Token::Ident(value) => value.clone(),
_ => unreachable!(),
},
});
}
if !self.expect_peek(Token::RParen) {
return Vec::new();
}
identifiers
}
fn parse_call_expression(&mut self, function: Expression) -> Option<Expression> {
let token = self.curr_token.clone();
let arguments = self.parse_expression_list(Token::RParen);
Some(Expression::CallExpression(CallExpression {
token,
function: Box::new(function),
arguments,
}))
}
fn parse_string_literal(&mut self) -> Option<Expression> {
Some(Expression::StringLiteral(StringLiteral {
token: self.curr_token.clone(),
value: match &self.curr_token {
Token::String(value) => value.clone(),
_ => unreachable!(),
},
}))
}
fn parse_array_literal(&mut self) -> Option<Expression> {
let token = self.curr_token.clone();
let elements = self.parse_expression_list(Token::RBracket);
Some(Expression::ArrayLiteral(ArrayLiteral { token, elements }))
}
fn parse_expression_list(&mut self, end: Token) -> Vec<Expression> {
let mut list = Vec::new();
if self.peek_token_is(end.clone()) {
self.next_token();
return list;
}
self.next_token();
list.push(match self.parse_expression(Precedence::Lowest) {
Some(expr) => expr,
None => return list,
});
while self.peek_token_is(Token::Comma) {
self.next_token();
self.next_token();
list.push(match self.parse_expression(Precedence::Lowest) {
Some(expr) => expr,
None => return list,
});
}
if !self.expect_peek(end) {
return Vec::new();
}
list
}
fn parse_index_expression(&mut self, left: Expression) -> Option<Expression> {
let token = self.curr_token.clone();
self.next_token();
let index = match self.parse_expression(Precedence::Lowest) {
Some(expr) => expr,
None => return None,
};
if !self.expect_peek(Token::RBracket) {
return None;
}
Some(Expression::IndexExpression(IndexExpression {
token,
left: Box::new(left),
index: Box::new(index),
}))
}
fn parse_hash_literal(&mut self) -> Option<Expression> {
let token = self.curr_token.clone();
let mut pairs = Vec::new();
while !self.peek_token_is(Token::RBrace) {
self.next_token();
let key = match self.parse_expression(Precedence::Lowest) {
Some(expr) => expr,
None => return None,
};
if !self.expect_peek(Token::Colon) {
return None;
}
self.next_token();
let value = match self.parse_expression(Precedence::Lowest) {
Some(expr) => expr,
None => return None,
};
pairs.push((key, value));
if !self.peek_token_is(Token::RBrace) && !self.expect_peek(Token::Comma) {
return None;
}
}
if !self.expect_peek(Token::RBrace) {
return None;
}
Some(Expression::HashLiteral(HashLiteral { token, pairs }))
}
fn curr_token_is(&self, token: Token) -> bool {
match (&self.curr_token, &token) {
(Token::Ident(_), Token::Ident(_)) => true,
(Token::Int(_), Token::Int(_)) => true,
(Token::String(_), Token::String(_)) => true,
(t1, t2) => t1 == t2,
}
}
fn peek_token_is(&self, token: Token) -> bool {
match (&self.peek_token, &token) {
(Token::Ident(_), Token::Ident(_)) => true,
(Token::Int(_), Token::Int(_)) => true,
(Token::String(_), Token::String(_)) => true,
(t1, t2) => t1 == t2,
}
}
fn expect_peek(&mut self, token: Token) -> bool {
if self.peek_token_is(token.clone()) {
self.next_token();
true
} else {
self.peek_error(token);
false
}
}
fn peek_error(&mut self, token: Token) {
let msg = format!(
"expected next token to be {:?}, got {:?} instead",
token, self.peek_token
);
self.errors.push(msg);
}
fn no_prefix_parse_fn_error(&mut self, token: &Token) {
let msg = format!("no prefix parse function for {:?} found", token);
self.errors.push(msg);
}
pub fn errors(&self) -> &Vec<String> {
&self.errors
}
}
End File# Shuumatsu/scarlet
use std::collections::HashMap;
use std::fmt;
use std::hash::{Hash, Hasher};
use crate::ast::*;
#[derive(Debug, Clone, PartialEq)]
pub enum Object {
Integer(i64),
Boolean(bool),
String(String),
Array(Vec<Object>),
Hash(HashMap<HashKey, HashPair>),
Function(Function),
Builtin(Builtin),
Return(Box<Object>),
Null,
}
impl fmt::Display for Object {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Object::Integer(value) => write!(f, "{}", value),
Object::Boolean(value) => write!(f, "{}", value),
Object::String(value) => write!(f, "{}", value),
Object::Array(elements) => {
let elements_str: Vec<String> = elements.iter().map(|e| e.to_string()).collect();
write!(f, "[{}]", elements_str.join(", "))
}
Object::Hash(pairs) => {
let mut pairs_str = Vec::new();
for (key, pair) in pairs {
pairs_str.push(format!("{}: {}", key, pair.value));
}
write!(f, "{{{}}}", pairs_str.join(", "))
}
Object::Function(function) => {
let params: Vec<String> = function
.parameters
.iter()
.map(|p| p.to_string())
.collect();
write!(
f,
"fn({}) {{\n{}\n}}",
params.join(", "),
function.body.to_string()
)
}
Object::Builtin(_) => write!(f, "builtin function"),
Object::Return(value) => write!(f, "{}", value),
Object::Null => write!(f, "null"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Function {
pub parameters: Vec<Identifier>,
pub body: BlockStatement,
pub env: Environment,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Builtin {
pub name: String,
pub func: fn(Vec<Object>) -> Object,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum HashKey {
Integer(i64),
Boolean(bool),
String(String),
}
impl fmt::Display for HashKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
HashKey::Integer(value) => write!(f, "{}", value),
HashKey::Boolean(value) => write!(f, "{}", value),
HashKey::String(value) => write!(f, "{}", value),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HashPair {
pub key: Object,
pub value: Object,
}
pub trait Hashable {
fn hash_key(&self) -> Option<HashKey>;
}
impl Hashable for Object {
fn hash_key(&self) -> Option<Hash # Shuumatsu/scarlet
# src/evaluator/evaluator.rs
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
use crate::ast::*;
use crate::object::*;
use crate::token::*;
pub fn eval(node: &Node, env: &mut Environment) -> Object {
match node {
Node::Program(program) => eval_program(&program.statements, env),
Node::Statement(stmt) => match stmt {
Statement::ExpressionStatement(expr_stmt) => {
eval(&Node::Expression(&expr_stmt.expression), env)
}
Statement::BlockStatement(block_stmt) => {
eval_block_statement(&block_stmt.statements, env)
}
Statement::LetStatement(let_stmt) => {
let val = eval(&Node::Expression(&let_stmt.value), env);
if is_error(&val) {
return val;
}
env.set(&let_stmt.name.value, val.clone());
val
}
Statement::ReturnStatement(return_stmt) => {
let val = eval(&Node::Expression(&return_stmt.return_value), env);
if is_error(&val) {
return val;
}
Object::Return(Box::new(val))
}
},
Node::Expression(expr) => match expr {
Expression::IntegerLiteral(int_lit) => Object::Integer(int_lit.value),
Expression::Boolean(bool_expr) => Object::Boolean(bool_expr.value),
Expression::PrefixExpression(prefix) => {
let right = eval(&Node::Expression(&prefix.right), env);
if is_error(&right) {
return right;
}
eval_prefix_expression(&prefix.operator, right)
}
Expression::InfixExpression(infix) => {
let left = eval(&Node::Expression(&infix.left), env);
if is_error(&left) {
return left;
}
let right = eval(&Node::Expression(&infix.right), env);
if is_error(&right) {
return right;
}
eval_infix_expression(&infix.operator, left, right)
}
Expression::IfExpression(if_expr) => {
eval_if_expression(if_expr, env)
}
Expression::Identifier(ident) => eval_identifier(ident, env),
Expression::FunctionLiteral(func_lit) => {
let params = func_lit.parameters.clone();
let body = func_lit.body.clone();
Object::Function(Function {
parameters: params,
body,
env: env.clone(),
})
}
Expression::CallExpression(call_expr) => {
let function = eval(&Node::Expression(&call_expr.function), env);
if is_error(&function) {
return function;
}
let args = eval_expressions(&call_expr.arguments, env);
if args.len() == 1 && is_error(&args[0]) {
return args[0].clone();
}
apply_function(function, args)
}
Expression::StringLiteral(str_lit) => {
Object::String(str_lit.value.clone())
}
Expression::ArrayLiteral(array_lit) => {
let elements = eval_expressions(&array_lit.elements, env);
if elements.len() == 1 && is_error(&elements[0]) {
return elements[0].clone();
}
Object::Array(elements)
}
Expression::IndexExpression(index_expr) => {
let left = eval(&Node::Expression(&index_expr.left), env);
if is_error(&left) {
return left;
}
let index = eval(&Node::Expression(&index_expr.index), env);
if is_error(&index) {
return index;
}
eval_index_expression(left, index)
}
Expression::HashLiteral(hash_lit) => {
eval_hash_literal(hash_lit, env)
}
},
}
}
fn eval_program(stmts: &[Statement], env: &mut Environment) -> Object {
let mut result = Object::Null;
for statement in stmts {
result = eval(&Node::Statement(statement), env);
match result {
Object::Return(value) => return *value,
Object::Error(_) => return result,
_ => {}
}
}
result
}
fn eval_block_statement(stmts: &[Statement], env: &mut Environment) -> Object {
let mut result = Object::Null;
for statement in stmts {
result = eval(&Node::Statement(statement), env);
match result {
Object::Return(_) | Object::Error(_) => return result,
_ => {}
}
}
result
}
fn eval_prefix_expression(operator: &str, right: Object) -> Object {
match operator {
"!" => eval_bang_operator_expression(right),
"-" => eval_minus_prefix_operator_expression(right),
_ => Object::Error(format!("unknown operator: {}{}", operator, right.type_name())),
}
}
fn eval_bang_operator_expression(right: Object) -> Object {
match right {
Object::Boolean(true) => Object::Boolean(false),
Object::Boolean(false) => Object::Boolean(true),
Object::Null => Object::Boolean(true),
_ => Object::Boolean(false),
}
}
fn eval_minus_prefix_operator_expression(right: Object) -> Object {
match right {
Object::Integer(value) => Object::Integer(-value),
_ => Object::Error(format!("unknown operator: -{}", right.type_name())),
}
}
fn eval_infix_expression(operator: &str, left: Object, right: Object) -> Object {
match (&left, &right) {
(Object::Integer(left_val), Object::Integer(right_val)) => {
eval_integer_infix_expression(operator, *left_val, *right_val)
}
(Object::String(left_val), Object::String(right_val)) => {
eval_string_infix_expression(operator, left_val, right_val)
}
_ if left.type_name() != right.type_name() => {
Object::Error(format!(
"type mismatch: {} {} {}",
left.type_name(),
operator,
right.type_name()
))
}
_ => match operator {
"==" => Object::Boolean(left == right),
"!=" => Object::Boolean(left != right),
_ => Object::Error(format!(
"unknown operator: {} {} {}",
left.type_name(),
operator,
right.type_name()
)),
},
}
}
fn eval_integer_infix_expression(operator: &str, left: i64, right: i64) -> Object {
match operator {
"+" => Object::Integer(left + right),
"-" => Object::Integer(left - right),
"*" => Object::Integer(left * right),
"/" => Object::Integer(left / right),
"<" => Object::Boolean(left < right),
">" => Object::Boolean(left > right),
"==" => Object::Boolean(left == right),
"!=" => Object::Boolean(left != right),
_ => Object::Error(format!("unknown operator: INTEGER {} INTEGER", operator)),
}
}
fn eval_string_infix_expression(operator: &str, left: &str, right: &str) -> Object {
match operator {
"+" => Object::String(format!("{}{}", left, right)),
"==" => Object::Boolean(left == right),
"!=" => Object::Boolean(left != right),
_ => Object::Error(format!("unknown operator: STRING {} STRING", operator)),
}
}
fn eval_if_expression(if_expr: &IfExpression, env: &mut Environment) -> Object {
let condition = eval(&Node::Expression(&if_expr.condition), env);
if is_error(&condition) {
return condition;
}
if is_truthy(condition) {
eval(
&Node::Statement(&Statement::BlockStatement(if_expr.consequence.clone())),
env,
)
} else if let Some(alt) = &if_expr.alternative {
eval(&Node::Statement(&Statement::BlockStatement(alt.clone())), env)
} else {
Object::Null
}
}
fn is_truthy(obj: Object) -> bool {
match obj {
Object::Null => false,
Object::Boolean(false) => false,
_ => true,
}
}
fn eval_identifier(ident: &Identifier, env: &Environment) -> Object {
match env.get(&ident.value) {
Some(obj) => obj,
None => match get_builtin(&ident.value) {
Some(builtin) => Object::Builtin(builtin),
None => Object::Error(format!("identifier not found: {}", ident.value)),
},
}
}
fn eval_expressions(exprs: &[Expression], env: &mut Environment) -> Vec<Object> {
let mut result = Vec::new();
for expr in exprs {
let evaluated = eval(&Node::Expression(expr), env);
if is_error(&evaluated) {
return vec![evaluated];
}
result.push(evaluated);
}
result
}
fn apply_function(func: Object, args: Vec<Object>) -> Object {
match func {
Object::Function(function) => {
let mut extended_env = extend_function_env(&function, args);
let evaluated = eval(
&Node::Statement(&Statement::BlockStatement(function.body)),
&mut extended_env,
);
unwrap_return_value(evaluated)
}
Object::Builtin(builtin) => (builtin.func)(args),
_ => Object::Error(format!("not a function: {}", func.type_name())),
}
}
fn extend_function_env(func: &Function, args: Vec<Object>) -> Environment {
let mut env = Environment::new_enclosed(func.env.clone());
for (param_idx, param) in func.parameters.iter().enumerate() {
if param_idx < args.len() {
env.set(¶m.value, args[param_idx].clone());
}
}
env
}
fn unwrap_return_value(obj: Object) -> Object {
match obj {
Object::Return(value) => *value,
_ => obj,
}
}
fn eval_index_expression(left: Object, index: Object) -> Object {
match (&left, &index) {
(Object::Array(elements), Object::Integer(idx)) => {
eval_array_index_expression(elements, *idx)
}
(Object::Hash(pairs), _) => {
if let Some(key) = index.hash_key() {
match pairs.get(&key) {
Some(pair) => pair.value.clone(),
None => Object::Null,
}
} else {
Object::Error(format!("unusable as hash key: {}", index.type_name()))
}
}
_ => Object::Error(format!("index operator not supported: {}", left.type_name())),
}
}
fn eval_array_index_expression(array: &[Object], index: i64) -> Object {
let max = array.len() as i64 - 1;
if index < 0 || index > max {
return Object::Null;
}
array[index as usize].clone()
}
fn eval_hash_literal(hash: &HashLiteral, env: &mut Environment) -> Object {
let mut pairs = HashMap::new();
for (key_node, value_node) in &hash.pairs {
let key = eval(&Node::Expression(key_node), env);
if is_error(&key) {
return key;
}
let hash_key = match key.hash_key() {
Some(key) => key,
None => return Object::Error(format!("unusable as hash key: {}", key.type_name())),
};
let value = eval(&Node::Expression(value_node), env);
if is_error(&value) {
return value;
}
pairs.insert(hash_key, HashPair { key: key.clone(), value });
}
Object::Hash(pairs)
}
fn is_error(obj: &Object) -> bool {
matches!(obj, Object::Error(_))
}
// Builtins
fn get_builtin(name: &str) -> Option<Builtin> {
match name {
"len" => Some(Builtin {
name: "len".to_string(),
func: builtin_len,
}),
"first" => Some(Builtin {
name: "first".to_string(),
func: builtin_first,
}),
"last" => Some(Builtin {
name: "last".to_string(),
func: builtin_last,
}),
"rest" => Some(Builtin {
name: "rest".to_string(),
func: builtin_rest,
}),
"push" => Some(Builtin {
name: "push".to_string(),
func: builtin_push,
}),
"puts" => Some(Builtin {
name: "puts".to_string(),
func: builtin_puts,
}),
_ => None,
}
}
fn builtin_len(args: Vec<Object>) -> Object {
if args.len() != 1 {
return Object::Error(format!(
"wrong number of arguments. got={}, want=1",
args.len()
));
}
match &args[0] {
Object::String(s) => Object::Integer(s.len() as i64),
Object::Array(elements) => Object::Integer(elements.len() as i64),
_ => Object::Error(format!(
"argument to `len` not supported, got {}",
args[0].type_name()
)),
}
}
fn builtin_first(args: Vec<Object>) -> Object {
if args.len() != 1 {
return Object::Error(format!(
"wrong number of arguments. got={}, want=1",
args.len()
));
}
match &args[0] {
Object::Array(elements) => {
if elements.is_empty() {
Object::Null
} else {
elements[0].clone()
}
}
_ => Object::Error(format!(
"argument to `first` must be ARRAY, got {}",
args[0].type_name()
)),
}
}
fn builtin_last(args: Vec<Object>) -> Object {
if args.len() != 1 {
return Object::Error(format!(
"wrong number of arguments. got={}, want=1",
args.len()
));
}
match &args[0] {
Object::Array(elements) => {
if elements.is_empty() {
Object::Null
} else {
elements[elements.len() - 1].clone()
}
}
_ => Object::Error(format!(
"argument to `last` must be ARRAY, got {}",
args[0].type_name()
)),
}
}
fn builtin_rest(args: Vec<Object>) -> Object {
if args.len() != 1 {
return Object::Error(format!(
"wrong number of arguments. got={}, want=1",
args.len()
));
}
match &args[0] {
Object::Array(elements) => {
if elements.is_empty() {
Object::Null
} else {
let mut new_elements = elements.clone();
new_elements.remove(0);
Object::Array(new_elements)
}
}
_ => Object::Error(format!(
"argument to `rest` must be ARRAY, got {}",
args[0].type_name()
)),
}
}
fn builtin_push(args: Vec<Object>) -> Object {
if args.len() != 2 {
return Object::Error(format!(
"wrong number of arguments. got={}, want=2",
args.len()
));
}
match &args[0] {
Object::Array(elements) => {
let mut new_elements = elements.clone();
new_elements.push(args[1].clone());
Object::Array(new_elements)
}
_ => Object::Error(format!(
"argument to `push` must be ARRAY, got {}",
args[0].type_name()
)),
}
}
fn builtin_puts(args: Vec<Object>) -> Object {
for arg in args {
println!("{}", arg);
}
Object::Null
}
// Environment
#[derive(Debug, Clone, PartialEq)]
pub struct Environment {
store: HashMap<String, Object>,
outer: Option<Box<Environment>>,
}
impl Environment {
pub fn new() -> Self {
Environment {
store: HashMap::new(),
outer: None,
}
}
pub fn new_enclosed(outer: Environment) -> Self {
Environment {
store: HashMap::new(),
outer: Some(Box::new(outer)),
}
}
pub fn get(&self, name: &str) -> Option<Object> {
match self.store.get(name) {
Some(obj) => Some(obj.clone()),
None => match &self.outer {
Some(outer) => outer.get(name),
None => None,
},
}
}
pub fn set(&mut self, name: &str, val: Object) -> Object {
self.store.insert(name.to_string(), val.clone());
val
}
}
// Node enum for eval function
pub enum Node<'a> {
Program(&'a Program),
Statement(&'a Statement),
Expression(&'a Expression),
}
// Type name extension for Object
trait TypeName {
fn type_name(&self) -> String;
}
impl TypeName for Object {
fn type_name(&self) -> String {
match self {
Object::Integer(_) => "INTEGER".to_string(),
Object::Boolean(_) => "BOOLEAN".to_string(),
Object::String(_) => "STRING".to_string(),
Object::Array(_) => "ARRAY".to_string(),
Object::Hash(_) => "HASH".to_string(),
Object::Function(_) => "FUNCTION".to_string(),
Object::Builtin(_) => "BUILTIN".to_string(),
Object::Return(_) => "RETURN".to_string(),
Object::Null => "NULL".to_string(),
Object::Error(_) => "ERROR".to_string(),
}
}
}
End File# Shuumatsu/scarlet
# src/ast/ast.rs
use std::fmt;
use crate::token::Token;
#[derive(Debug, Clone)]
pub struct Program {
pub statements: Vec<Statement>,
}
impl fmt::Display for Program {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for stmt in &self.statements {
write!(f, "{}", stmt)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum Statement {
Let(LetStatement),
Return(ReturnStatement),
Expression(ExpressionStatement),
}
impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Statement::Let(stmt) => write!(f, "{}", stmt),
Statement::Return(stmt) => write!(f, "{}", stmt),
Statement::Expression(stmt) => write!(f, "{}", stmt),
}
}
}
#[derive(Debug, Clone)]
pub struct LetStatement {
pub token: Token,
pub name: Identifier,
pub value: Expression,
}
impl fmt::Display for LetStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {} = {};", self.token, self.name, self.value)
}
}
#[derive(Debug, Clone)]
pub struct ReturnStatement {
pub token: Token,
pub return_value: Expression,
}
impl fmt::Display for ReturnStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {};", self.token, self.return_value)
}
}
#[derive(Debug, Clone)]
pub struct ExpressionStatement {
pub token: Token,
pub expression: Expression,
}
impl fmt::Display for ExpressionStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.expression)
}
}
#[derive(Debug, Clone)]
pub enum Expression {
Identifier(Identifier),
IntegerLiteral(IntegerLiteral),
PrefixExpression(PrefixExpression),
InfixExpression(InfixExpression),
Boolean(Boolean),
IfExpression(IfExpression),
FunctionLiteral(FunctionLiteral),
CallExpression(CallExpression),
StringLiteral(StringLiteral),
ArrayLiteral(ArrayLiteral),
IndexExpression(IndexExpression),
HashLiteral(HashLiteral),
}
impl fmt::Display for Expression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Expression::Identifier(expr) => write!(f, "{}", expr),
Expression::IntegerLiteral(expr) => write!(f, "{}", expr),
Expression::PrefixExpression(expr) => write!(f, "{}", expr),
Expression::InfixExpression(expr) => write!(f, "{}", expr),
Expression::Boolean(expr) => write!(f, "{}", expr),
Expression::IfExpression(expr) => write!(f, "{}", expr),
Expression::FunctionLiteral(expr) => write!(f, "{}", expr),
Expression::CallExpression(expr) => write!(f, "{}", expr),
Expression::StringLiteral(expr) => write!(f, "{}", expr),
Expression::ArrayLiteral(expr) => write!(f, "{}", expr),
Expression::IndexExpression(expr) => write!(f, "{}", expr),
Expression::HashLiteral(expr) => write!(f, "{}", expr),
}
}
}
#[derive(Debug, Clone)]
pub struct Identifier {
pub token: Token,
pub value: String,
}
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
}
}
#[derive(Debug, Clone)]
pub struct IntegerLiteral {
pub token: Token,
pub value: i64,
}
impl fmt::Display for IntegerLiteral {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
}
}
#[derive(Debug, Clone)]
pub struct PrefixExpression {
pub token: Token,
pub operator: String,
pub right: Box<Expression>,
}
impl fmt::Display for PrefixExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}{})", self.operator, self.right)
}
}
#[derive(Debug, Clone)]
pub struct InfixExpression {
pub token: Token,
pub left: Box<Expression>,
pub operator: String,
pub right: Box<Expression>,
}
impl fmt::Display for InfixExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({} {} {})", self.left, self.operator, self.right)
}
}
#[derive(Debug, Clone)]
pub struct Boolean {
pub token: Token,
pub value: bool,
}
impl fmt::Display for Boolean {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
}
}
#[derive(Debug, Clone)]
pub struct IfExpression {
pub token: Token,
pub condition: Box<Expression>,
pub consequence: BlockStatement,
pub alternative: Option<BlockStatement>,
}
impl fmt::Display for IfExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "if {} {}", self.condition, self.consequence)?;
if let Some(alt) = &self.alternative {
write!(f, " else {}", alt)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct BlockStatement {
pub token: Token,
pub statements: Vec<Statement>,
}
impl fmt::Display for BlockStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for stmt in &self.statements {
write!(f, "{}", stmt)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct FunctionLiteral {
pub token: Token,
pub parameters: Vec<Identifier>,
pub body: BlockStatement,
}
impl fmt::Display for FunctionLiteral {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let params: Vec<String> = self.parameters.iter().map(|p| p.to_string()).collect();
write!(f, "{}({}) {}", self.token, params.join(", "), self.body)
}
}
#[derive(Debug, Clone)]
pub struct CallExpression {
pub token: Token,
pub function: Box<Expression>,
pub arguments: Vec<Expression>,
}
impl fmt::Display for CallExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let args: Vec<String> = self.arguments.iter().map(|a| a.to_string()).collect();
write!(f, "{}({})", self.function, args.join(", "))
}
}
#[derive(Debug, Clone)]
pub struct StringLiteral {
pub token: Token,
pub value: String,
}
impl fmt::Display for StringLiteral {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
}
}
#[derive(Debug, Clone)]
pub struct ArrayLiteral {
pub token: Token,
pub elements: Vec<Expression>,
}
impl fmt::Display for ArrayLiteral {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let elements: Vec<String> = self.elements.iter().map(|e| e.to_string()).collect();
write!(f, "[{}]", elements.join(", "))
}
}
#[derive(Debug, Clone)]
pub struct IndexExpression {
pub token: Token,
pub left: Box<Expression>,
pub index: Box<Expression>,
}
impl fmt::Display for IndexExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}[{}])", self.left, self.index)
}
}
#[derive(Debug, Clone)]
pub struct HashLiteral {
pub token: Token,
pub pairs: Vec<(Expression, Expression)>,
}
impl fmt::Display for HashLiteral {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let pairs: Vec<String> = self
.pairs
.iter()
.map(|(k, v)| format!("{}:{}", k, v))
.collect();
write!(f, "{{{}}}", pairs.join(", "))
}
}
End File# Shuumatsu/scarlet
# src/token/token.rs
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Illegal,
Eof,
// Identifiers + literals
Ident(String),
Int(String),
String(String),
// Operators
Assign,
Plus,
Minus,
Bang,
Asterisk,
Slash,
LessThan,
GreaterThan,
Equal,
NotEqual,
// Delimiters
Comma,
Semicolon,
Colon,
LParen,
RParen,
LBrace,
RBrace,
LBracket,
RBracket,
// Keywords
Function,
Let,
True,
False,
If,
Else,
Return,
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Token::Illegal => write!(f, "ILLEGAL"),
Token::Eof => write!(f, "EOF"),
Token::Ident(s) => write!(f, "{}", s),
Token::Int(s) => write!(f, "{}", s),
Token::String(s) => write!(f, "\"{}\"", s),
Token::Assign => write!(f, "="),
Token::Plus => write!(f, "+"),
Token::Minus => write!(f, "-"),
Token::Bang => write!(f, "!"),
Token::Asterisk => write!(f, "*"),
Token::Slash => write!(f, "/"),
Token::LessThan => write!(f, "<"),
Token::GreaterThan => write!(f, ">"),
Token::Equal => write!(f, "=="),
Token::NotEqual => write!(f, "!="),
Token::Comma => write!(f, ","),
Token::Semicolon => write!(f, ";"),
Token::Colon => write!(f, ":"),
Token::LParen => write!(f, "("),
Token::RParen => write!(f, ")"),
Token::LBrace => write!(f, "{{"),
Token::RBrace => write!(f, "}}"),
Token::LBracket => write!(f, "["),
Token::RBracket => write!(f, "]"),
Token::Function => write!(f, "fn"),
Token::Let => write!(f, "let"),
Token::True => write!(f, "true"),
Token::False => write!(f, "false"),
Token::If => write!(f, "if"),
Token::Else => write!(f, "else"),
Token::Return => write!(f, "return"),
}
}
}
pub fn lookup_ident(ident: &str) -> Token {
match ident {
"fn" => Token::Function,
"let" => Token::Let,
"true" => Token::True,
"false" => Token::False,
"if" => Token::If,
"else" => Token::Else,
"return" => Token::Return,
_ => Token::Ident(ident.to_string()),
}
}
End File# Shuumatsu/scarlet
# src/lexer/lexer.rs
use crate::token::*;
pub struct Lexer {
input: Vec<char>,
position: usize, // current position in input (points to current char)
read_position: usize, // current reading position in input (after current char)
ch: char, // current char under examination
}
impl Lexer {
pub fn new(input: &str) -> Self {
let mut lexer = Lexer {
input: input.chars().collect(),
position: 0,
read_position: 0,
ch: '\0',
};
lexer.read_char();
lexer
}
fn read_char(&mut self) {
if self.read_position >= self.input.len() {
self.ch = '\0';
} else {
self.ch = self.input[self.read_position];
}
self.position = self.read_position;
self.read_position += 1;
}
fn peek_char(&self) -> char {
if self.read_position >= self.input.len() {
'\0'
} else {
self.input[self.read_position]
}
}
pub fn next_token(&mut self) -> Token {
self.skip_whitespace();
let token = match self.ch {
'=' => {
if self.peek_char() == '=' {
self.read_char();
Token::Equal
} else {
Token::Assign
}
}
'+' => Token::Plus,
'-' => Token::Minus,
'!' => {
if self.peek_char() == '=' {
self.read_char();
Token::NotEqual
} else {
Token::Bang
}
}
'*' => Token::Asterisk,
'/' => Token::Slash,
'<' => Token::LessThan,
'>' => Token::GreaterThan,
',' => Token::Comma,
';' => Token::Semicolon,
':' => Token::Colon,
'(' => Token::LParen,
')' => Token::RParen,
'{' => Token::LBrace,
'}' => Token::RBrace,
'[' => Token::LBracket,
']' => Token::RBracket,
'"' => {
let str_val = self.read_string();
Token::String(str_val)
}
'\0' => Token::Eof,
_ => {
if is_letter(self.ch) {
let ident = self.read_identifier();
return lookup_ident(&ident);
} else if is_digit(self.ch) {
let num = self.read_number();
return Token::Int(num);
} else {
Token::Illegal
}
}
};
self.read_char();
token
}
fn read_identifier(&mut self) -> String {
let position = self.position;
while is_letter(self.ch) || is_digit(self.ch) {
self.read_char();
}
self.input[position..self.position].iter().collect()
}
fn read_number(&mut self) -> String {
let position = self.position;
while is_digit(self.ch) {
self.read_char();
}
self.input[position..self.position].iter().collect()
}
fn read_string(&mut self) -> String {
let position = self.position + 1;
loop {
self.read_char();
if self.ch == '"' || self.ch == '\0' {
break;
}
}
self.input[position..self.position].iter().collect()
}
fn skip_whitespace(&mut self) {
while self.ch.is_whitespace() {
self.read_char();
}
}
}
fn is_letter(ch: char) -> bool {
ch.is_alphabetic() || ch == '_'
}
fn is_digit(ch: char) -> bool {
ch.is_digit(10)
}
End File# Shuumatsu/scarlet
# src/repl/repl.rs
use std::io::{self, Write};
use crate::evaluator::*;
use crate::lexer::*;
use crate::object::*;
use crate::parser::*;
const PROMPT: &str = ">> ";
pub fn start() {
let mut env = Environment::new();
loop {
print!("{}", PROMPT);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
if input.trim() == "exit" {
break;
}
let lexer = Lexer::new(&input);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
if !parser.errors().is_empty() {
print_parser_errors(parser.errors());
continue;
}
let evaluated = eval(&Node::Program(&program), &mut env);
println!("{}", evaluated);
}
}
fn print_parser_errors(errors: &[String]) {
println!("Woops! We ran into some monkey business here!");
println!(" parser errors:");
for error in errors {
println!("\t{}", error);
}
}
End File# Shuumatsu/scarlet
# src/main.rs
use std::io::{self, Write};
use std::env;
use std::fs;
mod token;
mod lexer;
mod ast;
mod parser;
mod object;
mod evaluator;
mod repl;
use crate::evaluator::*;
use crate::lexer::*;
use crate::object::*;
use crate::parser::*;
use crate::repl::*;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
// Run file mode
let filename = &args[1];
match fs::read_to_string(filename) {
Ok(content) => {
let mut env = Environment::new();
let lexer = Lexer::new(&content);
let mut parser = Parser::new(lexer);
let program = parser.parse_program();
if !parser.errors().is_empty() {
print_parser_errors(parser.errors());
return;
}
let evaluated = eval(&Node::Program(&program), &mut env);
// Only print the result if it's not null (to avoid cluttering output)
if !matches!(evaluated, Object::Null) {
println!("{}", evaluated);
}
},
Err(e) => {
eprintln!("Error reading file '{}': {}", filename, e);
}
}
} else {
// REPL mode
println!("Welcome to Scarlet Programming Language!");
println!("Feel free to type in commands");
repl::start();
}
}
fn print_parser_errors(errors: &[String]) {
eprintln!("Parser errors:");
for error in errors {
eprintln!("\t{}", error);
}
}
End File
Was this page helpful?
- Avant de construire avec Claude
- Décider si vous devez utiliser Claude pour la modération de contenu
- Générer des exemples de contenu à modérer
- Comment modérer le contenu en utilisant Claude
- Sélectionner le bon modèle Claude
- Construire une invite solide
- Évaluer votre invite
- Déployer votre invite
- Améliorer les performances
- Définir des sujets et fournir des exemples