• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Una historia de ds ls en ruby
 

Una historia de ds ls en ruby

on

  • 323 views

Casos de uso y tips de implementacion de DSLs en Ruby

Casos de uso y tips de implementacion de DSLs en Ruby

Presentacion hecha en el meetup lenguajes dinamicos, Enero 2013

Statistics

Views

Total Views
323
Views on SlideShare
304
Embed Views
19

Actions

Likes
0
Downloads
0
Comments
0

1 Embed 19

http://blog.continuum.cl 19

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Una historia de ds ls en ruby Una historia de ds ls en ruby Presentation Transcript

    • Una historia de DSLs en Ruby Leo Soto M. Continuum Lenguajes Dinámicos, Enero 2013Saturday, February 9, 13
    • DSLs?Saturday, February 9, 13
    • Muchas libs Ruby son en el fondo, DSLsSaturday, February 9, 13
    • Capistranonamespace :deploy do task :start, :roles => :app, :except => { :no_release => true } do run "sudo monit start miapp_unicorn" run "sudo monit -g resque start all" endendSaturday, February 9, 13
    • RSpecdescribe Math do describe "#pow" do it "computes the n-th-power of the receiver" do 3.pow(3).should == 27 end endendSaturday, February 9, 13
    • Bundlergem rails, 3.2.11gem pggem jquery-railsgem "unicorn", ">= 4.3.1"gem "haml", ">= 3.1.7"gem "devise", ">= 2.1.2"gem "simple_form", ">= 2.0.4"gem "figaro", ">= 0.5.0"gem "foreigner", ">= 1.3.0"group :assets do gem sass-rails, ~> 3.2.3 gem compass-rails gem coffee-rails, ~> 3.2.1 gem uglifier, >= 1.0.3endSaturday, February 9, 13
    • Sinatrarequire sinatraget /hi do "Hello World!"endSaturday, February 9, 13
    • Routing en RailsSaturday, February 9, 13
    • Modelos en RailsSaturday, February 9, 13
    • Casi todo en RailsSaturday, February 9, 13
    • Lo que en J__A “resuelven” con X_L en Ruby se suele hacer con DSLs internosSaturday, February 9, 13
    • Enter Pharmmd RulesSaturday, February 9, 13
    • age  >=  18  and  (drug_cat(“XYZ”)  or                                drug_cat(“ABC”)) and  dosage(“PARACETAMOL”)  >  1000Saturday, February 9, 13
    • Solución:Saturday, February 9, 13
    • # patient_dsl.rbclass PatientDSL attr_reader :patient def initialize(patient) @patient = patient end delegate :age, :allergies, :gender, :labs, :medications, :prescriptions, :providers, :visits, :survey, :to => :patient def drug_cat(name) (1..6).to_a.reverse.detect do |level| medication = medications.detect do |m| m.send("lvl#{level}conceptname") == name end and break medication end end def dosage(name) drug(name).try(:dosage) || 0 end # Many, many more methodsendSaturday, February 9, 13
    • PatientDSL.new(patient).instance_eval(rule)Saturday, February 9, 13
    • Recuerden, las reglas las ingresas usuarios, en una gran caja de texto en la interfaz web del sistema ¿Que problemas pueden ocurrir con la solución hasta ahora?Saturday, February 9, 13
    • age  >=  18  and  patient.destroy!Saturday, February 9, 13
    • Patient.destroy_allSaturday, February 9, 13
    • system(“rm  -­‐rf  /”)Saturday, February 9, 13
    • PatientDSL.new(patient).instance_eval(rule)Saturday, February 9, 13
    • “Cuando me equivoco en tipear la regla me aparece un error ‘No Method no se cuantito’”Saturday, February 9, 13
    • Solución, versión 2:Saturday, February 9, 13
    • # pharmmd_dsl.treetopgrammar PharmmdDsl rule expression spaces? boolean_expression spaces? end rule boolean_expression boolean_term (spaces logical_binary_operator spaces boolean_term)* end rule logical_binary_operator "and" / "&&" / "or" / "||" end rule boolean_term ("not " / "!") spaces? boolean_expression / (numeric_value spaces? boolean_operator spaces? numeric_value) / ("(" boolean_expression ")") / function_call end # ...Saturday, February 9, 13
    • # pharmmd_dsl.treetop (cont.) rule boolean_operator ">=" / "<=" / ">" / "<" / "==" / "!=" end rule function_call function_name:([a-zA-Z_] [a-zA-Z_0-9]*) arguments:("(" argument("," spaces? argument)* ")")? <FunctionNode> end rule argument string / date / numeric_value endSaturday, February 9, 13
    • # pharmmd_dsl.treetop (cont.) rule numeric_value function_call / number / "(" spaces? numeric_value spaces? ")" end rule number float / integer end rule integer "-"? digits end rule float "-"? (digits)? "." digits end rule digits [0-9]+ endSaturday, February 9, 13
    • # pharmmd_dsl.treetop (cont.) rule spaces [sn]+ end rule string ["] [^"]* ["] end rule date [0-9]+ "." time_unit "s"? ".ago" end rule time_unit "day" / "month" / "year" endendSaturday, February 9, 13
    • Por cierto, el lenguaje de gramáticas de treetop es un DSL “externo”Saturday, February 9, 13
    • Treetop hace el parsing extremadamente natural. ¡Sigamos el proceso a mano! (si es que tenemos pizarra a mano)Saturday, February 9, 13
    • age  >=  18  and  (drug_cat(“XYZ”)  or                                drug_cat(“ABC”)) and  dosage(“PARACETAMOL”)  >  1000Saturday, February 9, 13
    • ¿Pero qué ganamos?Saturday, February 9, 13
    • age  >=  18  and  patient.destroy  #  invalido Patient.destroy_all  #  invalido system(“rm  -­‐rf  /”)  #  valido!Saturday, February 9, 13
    • eval(“Patient.destroy_all”)  #  oopsSaturday, February 9, 13
    • ¡Rara vez la validación sintáctica es suficiente!“Saltarina casa llovió perros perrunos” (¡Español sintácticamente válido!)Saturday, February 9, 13
    • rule function_call function_name:([a-zA-Z_] [a-zA-Z_0-9]*) arguments:("(" argument ("," spaces? argument)* ")")? <FunctionNode> endSaturday, February 9, 13
    • require treetop; require pharmmd_dslclass FunctionNode < Treetop::Runtime::SyntaxNode; endclass PharmmdDslValidator attr_accessor :dsl, :errors def initialize(dsl) @dsl = dsl; @errors = [] end def valid_dsl? parser = PharmmdDslParser.new parse_tree = parser.parse(@dsl) if parse_tree.nil? errors << "You have a syntax error: #{parser.failure_reason}" else validate_functions(parse_tree) end errors.empty? end def valid_functions @valid_functions ||= (PatientDSL.instance_methods - Object.instance_methods) endSaturday, February 9, 13
    • # (cont.) def validate_functions(parse_tree) element = parse_tree if element.is_a? FunctionNode name = element.function_name.text_value unless valid_functions.include? name errors << ("Function name #{element.text_value} is not a valid function call") end end if element.elements parse_tree.elements.each do |element| validate_functions(element) end end end endSaturday, February 9, 13
    • age  >=  18  and  patient.destroy  #  invalido Patient.destroy_all  #  invalido system(“rm  -­‐rf  /”)  #  invalido!Saturday, February 9, 13
    • Errores amigables: “Se esperaba ‘(‘ en linea X, columna Y” “La función ‘system’ no es válida”Saturday, February 9, 13
    • Y mucho más: ¡Solucion v3! (Sólo una mirada rápida, que se nos acaba el tiempo)Saturday, February 9, 13
    • ¡DSL híbrido!Saturday, February 9, 13
    • quantity("lipitor") > 10 or drug("vicodin") treetop, parse trees (PharmdDSLValidator)External DSL quantity("lipitor") > 10 or drug("vicodin") (PharmdDSLPreProcessor) (quantity("lipitor") > 10).or(drug("vicodin")) ruby objs/metodos (PharmdDSL)Internal DSL (NumericExpr(20, ["Lipitor 50mg"]) > 10).or( BooleanExpr(true, ["Vicodin 20mg"]))Saturday, February 9, 13
    • quantity("lipitor") > 10 or drug("vicodin") treetop, parse trees (PharmdDSLValidator)External DSL quantity("lipitor") > 10 or drug("vicodin") (PharmdDSLPreProcessor) (quantity("lipitor") > 10).or(drug("vicodin")) ruby objs/metodos (DenominatorQuery)Internal DSL (OrCondition( Condition(lipitor, {$gt => 10}), Condition(vicodin, {$exists => true}))Saturday, February 9, 13
    • ...y mas MongoDB no tenía OR en esa época, por lo que optimizabamos el árbol de expresiones para dejar los ORs lo mas “arriba” posible. Ejemplo: ((X  or  Y)  and  (Z) (Condition#optimize) ((X  and  Z)  or  (Y  and  Z))Saturday, February 9, 13
    • Conclusión • Parsing, árboles de expresiones, compiladores, etc no fue tiempo perdido en la U :) • Pero siempre hay tradeoffs • Probar un DSL interno primero. La solución más simple que podría funcionar • Luego un DSL externo, treetop lo hace fácil • Finalmente un híbrido, si no queda otraSaturday, February 9, 13